summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 17:33:58 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-05-10 17:33:58 +0000
commit7ca1da6657ae4875d44c1af534a35b640dd17522 (patch)
treee4ac261957a0f9039a4a497e552cae54fdd1238c
parent58f9f4a81951595ef4fdeaa92a023af5899eb0a1 (diff)
parent40b9553c50f4055754db4cb141540518e635b63e (diff)
downloadnative-busytown-mac-infra-release.tar.gz
Merge "Snap for 11819167 from 408bcba4f2db18ab32455b48bbda525233c0f58f to busytown-mac-infra-release" into busytown-mac-infra-releasebusytown-mac-infra-release
-rw-r--r--.gitignore3
-rw-r--r--Android.bp14
-rw-r--r--PREUPLOAD.cfg3
-rw-r--r--TEST_MAPPING103
-rw-r--r--aidl/binder/android/os/PersistableBundle.aidl2
-rw-r--r--aidl/gui/android/view/Surface.aidl2
-rw-r--r--cmds/atrace/Android.bp6
-rw-r--r--cmds/atrace/atrace.cpp26
-rw-r--r--cmds/atrace/atrace.rc73
-rw-r--r--cmds/atrace/ftrace_synthetic_events.conf2
-rw-r--r--cmds/bugreport/OWNERS2
-rw-r--r--cmds/bugreportz/OWNERS2
-rw-r--r--cmds/cmd/Android.bp3
-rw-r--r--cmds/cmd/cmd.cpp23
-rw-r--r--cmds/cmd/fuzzer/Android.bp8
-rw-r--r--cmds/dumpstate/Android.bp15
-rw-r--r--cmds/dumpstate/DumpstateService.cpp78
-rw-r--r--cmds/dumpstate/DumpstateService.h16
-rw-r--r--cmds/dumpstate/DumpstateUtil.cpp9
-rw-r--r--cmds/dumpstate/DumpstateUtil.h8
-rw-r--r--cmds/dumpstate/OWNERS3
-rw-r--r--cmds/dumpstate/binder/android/os/IDumpstate.aidl53
-rw-r--r--cmds/dumpstate/binder/android/os/IDumpstateListener.aidl7
-rw-r--r--cmds/dumpstate/dumpstate.cpp336
-rw-r--r--cmds/dumpstate/dumpstate.h50
-rw-r--r--cmds/dumpstate/dumpstate.rc3
-rw-r--r--cmds/dumpstate/main.cpp2
-rw-r--r--cmds/dumpstate/tests/dumpstate_smoke_test.cpp29
-rw-r--r--cmds/dumpstate/tests/dumpstate_test.cpp53
-rw-r--r--cmds/dumpsys/OWNERS2
-rw-r--r--cmds/dumpsys/dumpsys.cpp4
-rw-r--r--cmds/evemu-record/Android.bp13
-rw-r--r--cmds/evemu-record/OWNERS1
-rw-r--r--cmds/evemu-record/evdev.rs299
-rw-r--r--cmds/evemu-record/main.rs213
-rw-r--r--cmds/flatland/Android.bp54
-rw-r--r--cmds/flatland/Android.mk32
-rw-r--r--cmds/flatland/GLHelper.cpp13
-rw-r--r--cmds/flatland/GLHelper.h4
-rw-r--r--cmds/flatland/Main.cpp64
-rw-r--r--cmds/idlcli/vibrator.h2
-rw-r--r--cmds/installd/Android.bp2
-rw-r--r--cmds/installd/CrateManager.cpp3
-rw-r--r--cmds/installd/CrateManager.h1
-rw-r--r--cmds/installd/InstalldNativeService.cpp411
-rw-r--r--cmds/installd/InstalldNativeService.h33
-rw-r--r--cmds/installd/OWNERS3
-rw-r--r--cmds/installd/SysTrace.h12
-rw-r--r--cmds/installd/binder/android/os/CreateAppDataResult.aidl1
-rw-r--r--cmds/installd/binder/android/os/IInstalld.aidl17
-rw-r--r--cmds/installd/dexopt.cpp38
-rw-r--r--cmds/installd/dexopt.h5
-rw-r--r--cmds/installd/installd.cpp4
-rw-r--r--cmds/installd/installd.rc1
-rw-r--r--cmds/installd/installd_deps.h3
-rw-r--r--cmds/installd/otapreopt.cpp123
-rw-r--r--cmds/installd/otapreopt_chroot.cpp162
-rw-r--r--cmds/installd/otapreopt_script.sh73
-rw-r--r--cmds/installd/run_dex2oat.cpp37
-rw-r--r--cmds/installd/run_dex2oat_test.cpp18
-rw-r--r--cmds/installd/tests/Android.bp32
-rw-r--r--cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp53
-rw-r--r--cmds/installd/tests/installd_cache_test.cpp4
-rw-r--r--cmds/installd/tests/installd_dexopt_test.cpp163
-rw-r--r--cmds/installd/tests/installd_service_test.cpp129
-rw-r--r--cmds/installd/utils.h1
-rw-r--r--cmds/installd/utils_default.cpp2
-rw-r--r--cmds/installd/view_compiler.cpp111
-rw-r--r--cmds/ip-up-vpn/ip-up-vpn.c139
-rw-r--r--cmds/lshal/ListCommand.cpp183
-rw-r--r--cmds/lshal/Lshal.cpp17
-rw-r--r--cmds/lshal/Lshal.h9
-rw-r--r--cmds/lshal/NullableOStream.h5
-rw-r--r--cmds/lshal/Timeout.h102
-rw-r--r--cmds/lshal/libprocpartition/Android.bp4
-rw-r--r--cmds/lshal/main.cpp3
-rw-r--r--cmds/lshal/test.cpp122
-rw-r--r--cmds/service/service.cpp2
-rw-r--r--cmds/servicemanager/Access.cpp8
-rw-r--r--cmds/servicemanager/Access.h2
-rw-r--r--cmds/servicemanager/Android.bp27
-rw-r--r--cmds/servicemanager/NameUtil.h58
-rw-r--r--cmds/servicemanager/OWNERS3
-rw-r--r--cmds/servicemanager/ServiceManager.cpp329
-rw-r--r--cmds/servicemanager/ServiceManager.h2
-rw-r--r--cmds/servicemanager/ServiceManagerUnittest.cpp39
-rw-r--r--cmds/servicemanager/main.cpp39
-rw-r--r--cmds/servicemanager/servicemanager.rc2
-rw-r--r--cmds/servicemanager/servicemanager.recovery.rc1
-rw-r--r--cmds/servicemanager/test_sm.cpp57
-rw-r--r--cmds/sfdo/Android.bp17
-rw-r--r--cmds/sfdo/sfdo.cpp166
-rw-r--r--cmds/surfacereplayer/Android.bp13
-rw-r--r--cmds/surfacereplayer/proto/Android.bp23
-rw-r--r--cmds/surfacereplayer/proto/src/trace.proto225
-rw-r--r--cmds/surfacereplayer/replayer/Android.bp71
-rw-r--r--cmds/surfacereplayer/replayer/BufferQueueScheduler.cpp109
-rw-r--r--cmds/surfacereplayer/replayer/BufferQueueScheduler.h82
-rw-r--r--cmds/surfacereplayer/replayer/Color.h124
-rw-r--r--cmds/surfacereplayer/replayer/Event.cpp51
-rw-r--r--cmds/surfacereplayer/replayer/Event.h57
-rw-r--r--cmds/surfacereplayer/replayer/Main.cpp116
-rw-r--r--cmds/surfacereplayer/replayer/README.md262
-rw-r--r--cmds/surfacereplayer/replayer/Replayer.cpp1
-rw-r--r--cmds/surfacereplayer/replayer/Replayer.h170
-rw-r--r--cmds/surfacereplayer/replayer/trace_creator/trace_creator.py285
-rw-r--r--data/etc/Android.bp186
-rw-r--r--data/etc/android.hardware.thread_network.xml19
-rw-r--r--data/etc/android.hardware.type.automotive.xml4
-rw-r--r--data/etc/android.software.credentials.xml19
-rw-r--r--data/etc/android.software.opengles.deqp.level-2023-03-01.xml21
-rw-r--r--data/etc/android.software.opengles.deqp.level-2024-03-01.xml21
-rw-r--r--data/etc/android.software.opengles.deqp.level-latest.xml21
-rw-r--r--data/etc/android.software.vulkan.deqp.level-2023-03-01.xml21
-rw-r--r--data/etc/android.software.vulkan.deqp.level-2024-03-01.xml21
-rw-r--r--data/etc/android.software.vulkan.deqp.level-latest.xml21
-rw-r--r--data/etc/aosp_excluded_hardware.xml1
-rw-r--r--data/etc/handheld_core_hardware.xml1
-rw-r--r--data/etc/input/Android.bp23
-rw-r--r--data/etc/input/motion_predictor_config.xml35
-rw-r--r--data/etc/input/motion_predictor_model.tflitebin0 -> 179532 bytes
-rw-r--r--data/etc/tablet_core_hardware.xml1
-rw-r--r--headers/media_plugin/media/hardware/VideoAPI.h8
-rw-r--r--headers/media_plugin/media/openmax/OMX_AsString.h1
-rw-r--r--headers/media_plugin/media/openmax/OMX_IndexExt.h1
-rw-r--r--include/android/OWNERS1
-rw-r--r--include/android/bitmap.h4
-rw-r--r--include/android/choreographer.h102
-rw-r--r--include/android/configuration.h44
-rw-r--r--include/android/font.h4
-rw-r--r--include/android/font_matcher.h4
-rw-r--r--include/android/input.h151
-rw-r--r--include/android/keycodes.h64
-rw-r--r--include/android/looper.h52
-rw-r--r--include/android/performance_hint.h234
-rw-r--r--include/android/sensor.h23
-rw-r--r--include/android/sharedmem.h22
-rw-r--r--include/android/surface_control.h295
-rw-r--r--include/android/surface_control_jni.h68
-rw-r--r--include/android/system_fonts.h3
-rw-r--r--include/android/thermal.h70
-rw-r--r--include/audiomanager/AudioManager.h52
-rw-r--r--include/audiomanager/IAudioManager.h7
l---------include/binder/unique_fd.h1
-rw-r--r--include/ftl/OWNERS (renamed from cmds/surfacereplayer/OWNERS)2
-rw-r--r--include/ftl/algorithm.h96
-rw-r--r--include/ftl/concat.h4
-rw-r--r--include/ftl/details/concat.h36
-rw-r--r--include/ftl/details/function.h135
-rw-r--r--include/ftl/details/match.h59
-rw-r--r--include/ftl/details/mixins.h (renamed from services/surfaceflinger/CompositionEngine/tests/TestUtils.h)25
-rw-r--r--include/ftl/details/optional.h68
-rw-r--r--include/ftl/details/type_traits.h33
-rw-r--r--include/ftl/enum.h81
-rw-r--r--include/ftl/flags.h6
-rw-r--r--include/ftl/function.h297
-rw-r--r--include/ftl/match.h62
-rw-r--r--include/ftl/mixins.h148
-rw-r--r--include/ftl/non_null.h116
-rw-r--r--include/ftl/optional.h137
-rw-r--r--include/ftl/shared_mutex.h47
-rw-r--r--include/ftl/small_map.h52
-rw-r--r--include/ftl/small_vector.h9
-rw-r--r--include/ftl/unit.h61
-rw-r--r--include/input/BlockingQueue.h (renamed from services/inputflinger/BlockingQueue.h)72
-rw-r--r--include/input/DisplayViewport.h25
-rw-r--r--include/input/Input.h220
-rw-r--r--include/input/InputDevice.h95
-rw-r--r--include/input/InputEventBuilders.h249
-rw-r--r--include/input/InputEventLabels.h58
-rw-r--r--include/input/InputTransport.h19
-rw-r--r--include/input/InputVerifier.h60
-rw-r--r--include/input/KeyCharacterMap.h81
-rw-r--r--include/input/KeyLayoutMap.h12
-rw-r--r--include/input/Keyboard.h5
-rw-r--r--include/input/MotionPredictor.h106
-rw-r--r--include/input/MotionPredictorMetricsManager.h207
-rw-r--r--include/input/PrintTools.h74
-rw-r--r--include/input/PropertyMap.h44
-rw-r--r--include/input/RingBuffer.h298
-rw-r--r--include/input/TfLiteMotionPredictor.h167
-rw-r--r--include/input/TouchVideoFrame.h9
-rw-r--r--include/input/TraceTools.h29
-rw-r--r--include/input/VelocityControl.h19
-rw-r--r--include/input/VelocityTracker.h320
-rw-r--r--include/input/VirtualInputDevice.h104
-rw-r--r--include/input/VirtualKeyMap.h5
-rw-r--r--include/powermanager/PowerHalController.h22
-rw-r--r--include/powermanager/PowerHalLoader.h12
-rw-r--r--include/powermanager/PowerHalWrapper.h167
-rw-r--r--include/private/performance_hint_private.h47
-rw-r--r--include/private/surface_control_private.h9
-rw-r--r--include/private/thermal_private.h31
-rw-r--r--libs/arect/Android.bp1
-rw-r--r--libs/attestation/Android.bp1
-rw-r--r--libs/binder/ActivityManager.cpp76
-rw-r--r--libs/binder/Android.bp233
-rw-r--r--libs/binder/Binder.cpp91
-rw-r--r--libs/binder/BpBinder.cpp127
-rw-r--r--libs/binder/Debug.cpp2
-rw-r--r--libs/binder/FdTrigger.cpp17
-rw-r--r--libs/binder/FdTrigger.h7
-rw-r--r--libs/binder/FdUtils.h99
-rw-r--r--libs/binder/IActivityManager.cpp103
-rw-r--r--libs/binder/IBatteryStats.cpp19
-rw-r--r--libs/binder/IInterface.cpp1
-rw-r--r--libs/binder/IPCThreadState.cpp75
-rw-r--r--libs/binder/IResultReceiver.cpp1
-rw-r--r--libs/binder/IServiceManager.cpp42
-rw-r--r--libs/binder/IUidObserver.cpp6
-rw-r--r--libs/binder/LazyServiceRegistrar.cpp4
-rw-r--r--libs/binder/MemoryDealer.cpp12
-rw-r--r--libs/binder/MemoryHeapBase.cpp6
-rw-r--r--libs/binder/OS.h26
-rw-r--r--libs/binder/OS_android.cpp59
-rw-r--r--libs/binder/OS_non_android_linux.cpp57
-rw-r--r--libs/binder/OS_unix_base.cpp (renamed from libs/binder/OS.cpp)40
-rw-r--r--libs/binder/OWNERS5
-rw-r--r--libs/binder/Parcel.cpp280
-rw-r--r--libs/binder/ParcelFileDescriptor.cpp4
-rw-r--r--libs/binder/PermissionCache.cpp5
-rw-r--r--libs/binder/ProcessState.cpp110
-rw-r--r--libs/binder/RecordedTransaction.cpp186
-rw-r--r--libs/binder/RpcServer.cpp91
-rw-r--r--libs/binder/RpcSession.cpp86
-rw-r--r--libs/binder/RpcState.cpp185
-rw-r--r--libs/binder/RpcState.h46
-rw-r--r--libs/binder/RpcTlsUtils.cpp2
-rw-r--r--libs/binder/RpcTransportRaw.cpp27
-rw-r--r--libs/binder/RpcTransportTipcAndroid.cpp46
-rw-r--r--libs/binder/RpcTransportTls.cpp51
-rw-r--r--libs/binder/RpcTransportUtils.h4
-rw-r--r--libs/binder/RpcTrusty.cpp9
-rw-r--r--libs/binder/ServiceManagerHost.cpp20
-rw-r--r--libs/binder/ServiceManagerHost.h1
-rw-r--r--libs/binder/Stability.cpp10
-rw-r--r--libs/binder/TEST_MAPPING26
-rw-r--r--libs/binder/Utils.cpp18
-rw-r--r--libs/binder/Utils.h43
-rw-r--r--libs/binder/UtilsHost.cpp54
-rw-r--r--libs/binder/UtilsHost.h16
-rw-r--r--libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl25
-rw-r--r--libs/binder/binder_module.h73
-rw-r--r--libs/binder/file.cpp59
-rw-r--r--libs/binder/file.h41
-rw-r--r--libs/binder/include/binder/Binder.h10
-rw-r--r--libs/binder/include/binder/BpBinder.h24
-rw-r--r--libs/binder/include/binder/Delegate.h5
-rw-r--r--libs/binder/include/binder/Functional.h50
-rw-r--r--libs/binder/include/binder/IBinder.h4
-rw-r--r--libs/binder/include/binder/IInterface.h159
-rw-r--r--libs/binder/include/binder/IPCThreadState.h16
-rw-r--r--libs/binder/include/binder/IServiceManager.h12
-rw-r--r--libs/binder/include/binder/LazyServiceRegistrar.h12
-rw-r--r--libs/binder/include/binder/Parcel.h103
-rw-r--r--libs/binder/include/binder/ParcelFileDescriptor.h10
-rw-r--r--libs/binder/include/binder/ProcessState.h31
-rw-r--r--libs/binder/include/binder/RecordedTransaction.h17
-rw-r--r--libs/binder/include/binder/RpcServer.h48
-rw-r--r--libs/binder/include/binder/RpcSession.h44
-rw-r--r--libs/binder/include/binder/RpcThreads.h11
-rw-r--r--libs/binder/include/binder/RpcTransport.h59
-rw-r--r--libs/binder/include/binder/SafeInterface.h31
-rw-r--r--libs/binder/include/binder/TextOutput.h2
-rw-r--r--libs/binder/include/binder/Trace.h24
-rw-r--r--libs/binder/include/binder/unique_fd.h114
-rw-r--r--libs/binder/include_activitymanager/binder/ActivityManager.h8
-rw-r--r--libs/binder/include_activitymanager/binder/IActivityManager.h19
-rw-r--r--libs/binder/include_activitymanager/binder/IUidObserver.h2
-rw-r--r--libs/binder/include_batterystats/batterystats/IBatteryStats.h5
-rw-r--r--libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp24
-rw-r--r--libs/binder/libbinder_rpc_unstable.cpp76
-rw-r--r--libs/binder/libbinder_rpc_unstable.map.txt2
-rw-r--r--libs/binder/liblog_stub/Android.bp44
-rw-r--r--libs/binder/liblog_stub/include/android/log.h49
-rw-r--r--libs/binder/liblog_stub/include/log/log.h93
-rw-r--r--libs/binder/ndk/.clang-format2
-rw-r--r--libs/binder/ndk/Android.bp47
-rw-r--r--libs/binder/ndk/ibinder.cpp139
-rw-r--r--libs/binder/ndk/ibinder_internal.h4
-rw-r--r--libs/binder/ndk/include_cpp/android/binder_auto_utils.h27
-rw-r--r--libs/binder/ndk/include_cpp/android/binder_interface_utils.h2
-rw-r--r--libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h528
-rw-r--r--libs/binder/ndk/include_ndk/android/binder_ibinder.h6
-rw-r--r--libs/binder/ndk/include_ndk/android/binder_parcel.h4
-rw-r--r--libs/binder/ndk/include_ndk/android/binder_status.h6
-rw-r--r--libs/binder/ndk/include_ndk/android/persistable_bundle.h963
-rw-r--r--libs/binder/ndk/include_platform/android/binder_manager.h22
-rw-r--r--libs/binder/ndk/include_platform/android/binder_process.h29
-rw-r--r--libs/binder/ndk/include_platform/android/binder_stability.h12
-rw-r--r--libs/binder/ndk/libbinder_ndk.map.txt89
-rw-r--r--libs/binder/ndk/parcel.cpp40
-rw-r--r--libs/binder/ndk/persistable_bundle.cpp316
-rw-r--r--libs/binder/ndk/persistable_bundle_internal.h85
-rw-r--r--libs/binder/ndk/process.cpp12
-rw-r--r--libs/binder/ndk/service_manager.cpp40
-rw-r--r--libs/binder/ndk/stability.cpp6
-rw-r--r--libs/binder/ndk/status.cpp10
-rw-r--r--libs/binder/ndk/tests/Android.bp22
-rw-r--r--libs/binder/ndk/tests/iface.cpp3
-rw-r--r--libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp138
-rw-r--r--libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp104
-rw-r--r--libs/binder/rust/Android.bp86
-rw-r--r--libs/binder/rust/binder_tokio/lib.rs7
-rw-r--r--libs/binder/rust/libbinder_ndk_bindgen_flags.txt24
-rw-r--r--libs/binder/rust/rpcbinder/Android.bp4
-rw-r--r--libs/binder/rust/rpcbinder/BinderBindings.hpp1
-rw-r--r--libs/binder/rust/rpcbinder/src/lib.rs4
-rw-r--r--libs/binder/rust/rpcbinder/src/server.rs162
-rw-r--r--libs/binder/rust/rpcbinder/src/server/android.rs187
-rw-r--r--libs/binder/rust/rpcbinder/src/server/trusty.rs161
-rw-r--r--libs/binder/rust/rpcbinder/src/session.rs59
-rw-r--r--libs/binder/rust/src/binder.rs150
-rw-r--r--libs/binder/rust/src/error.rs331
-rw-r--r--libs/binder/rust/src/lib.rs24
-rw-r--r--libs/binder/rust/src/native.rs296
-rw-r--r--libs/binder/rust/src/parcel.rs171
-rw-r--r--libs/binder/rust/src/parcel/file_descriptor.rs78
-rw-r--r--libs/binder/rust/src/parcel/parcelable.rs626
-rw-r--r--libs/binder/rust/src/parcel/parcelable_holder.rs31
-rw-r--r--libs/binder/rust/src/proxy.rs530
-rw-r--r--libs/binder/rust/src/service.rs230
-rw-r--r--libs/binder/rust/src/state.rs63
-rw-r--r--libs/binder/rust/sys/lib.rs17
-rw-r--r--libs/binder/rust/tests/binderRustNdkInteropTest.cpp12
-rw-r--r--libs/binder/rust/tests/integration.rs9
-rw-r--r--libs/binder/rust/tests/ndk_rust_interop.rs12
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/Android.bp27
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs4
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp1
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp13
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs22
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp4
-rw-r--r--libs/binder/rust/tests/parcel_fuzzer/read_utils.rs3
-rw-r--r--libs/binder/rust/tests/serialization.cpp10
-rw-r--r--libs/binder/rust/tests/serialization.hpp3
-rw-r--r--libs/binder/rust/tests/serialization.rs40
-rw-r--r--libs/binder/servicedispatcher.cpp55
-rw-r--r--libs/binder/tests/Android.bp187
-rw-r--r--libs/binder/tests/FileUtils.cpp99
-rw-r--r--libs/binder/tests/FileUtils.h49
-rw-r--r--libs/binder/tests/IBinderRecordReplayTest.aidl78
-rw-r--r--libs/binder/tests/IBinderRpcBenchmark.aidl3
-rw-r--r--libs/binder/tests/IBinderRpcTest.aidl4
-rw-r--r--libs/binder/tests/binderAbiHelper.h52
-rw-r--r--libs/binder/tests/binderAllocationLimits.cpp25
-rw-r--r--libs/binder/tests/binderClearBufTest.cpp8
-rw-r--r--libs/binder/tests/binderDriverInterfaceTest.cpp5
-rw-r--r--libs/binder/tests/binderHostDeviceTest.cpp11
-rw-r--r--libs/binder/tests/binderLibTest.cpp184
-rw-r--r--libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp13
-rw-r--r--libs/binder/tests/binderParcelUnitTest.cpp201
-rw-r--r--libs/binder/tests/binderRecordReplayTest.cpp411
-rw-r--r--libs/binder/tests/binderRecordedTransactionTest.cpp2
-rw-r--r--libs/binder/tests/binderRpcBenchmark.cpp110
-rw-r--r--libs/binder/tests/binderRpcTest.cpp500
-rw-r--r--libs/binder/tests/binderRpcTestCommon.h78
-rw-r--r--libs/binder/tests/binderRpcTestFixture.h64
-rw-r--r--libs/binder/tests/binderRpcTestService.cpp71
-rw-r--r--libs/binder/tests/binderRpcTestServiceTrusty.cpp29
-rw-r--r--libs/binder/tests/binderRpcTestTrusty.cpp65
-rw-r--r--libs/binder/tests/binderRpcUniversalTests.cpp20
-rw-r--r--libs/binder/tests/binderRpcWireProtocolTest.cpp30
-rw-r--r--libs/binder/tests/binderSafeInterfaceTest.cpp14
-rw-r--r--libs/binder/tests/binderStabilityTest.cpp9
-rw-r--r--libs/binder/tests/binderTextOutputTest.cpp3
-rw-r--r--libs/binder/tests/binderThroughputTest.cpp304
-rw-r--r--libs/binder/tests/binderUtilsHostTest.cpp8
-rw-r--r--libs/binder/tests/format.h29
-rw-r--r--libs/binder/tests/parcel_fuzzer/Android.bp61
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder.cpp36
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder2corpus/README.md31
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp92
-rw-r--r--libs/binder/tests/parcel_fuzzer/binder_ndk.cpp2
-rw-r--r--libs/binder/tests/parcel_fuzzer/hwbinder.cpp5
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_driver.h11
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_ndk_driver.h11
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h14
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h4
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h2
-rw-r--r--libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h46
-rw-r--r--libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp48
-rw-r--r--libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp23
-rw-r--r--libs/binder/tests/parcel_fuzzer/main.cpp5
-rw-r--r--libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl15
-rw-r--r--libs/binder/tests/parcel_fuzzer/random_binder.cpp66
-rw-r--r--libs/binder/tests/parcel_fuzzer/random_fd.cpp97
-rw-r--r--libs/binder/tests/parcel_fuzzer/random_parcel.cpp21
-rw-r--r--libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp306
-rw-r--r--libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp64
-rw-r--r--libs/binder/tests/parcel_fuzzer/test_fuzzer/ITestService.aidl (renamed from services/inputflinger/host/main.cpp)16
-rw-r--r--libs/binder/tests/parcel_fuzzer/test_fuzzer/TestServiceFuzzer.cpp160
-rw-r--r--libs/binder/tests/parcel_fuzzer/test_fuzzer/fuzz_service_test_config.xml22
-rwxr-xr-xlibs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh44
-rw-r--r--libs/binder/tests/rpc_fuzzer/Android.bp3
-rw-r--r--libs/binder/tests/rpc_fuzzer/main.cpp19
-rw-r--r--libs/binder/tests/schd-dbg.cpp3
-rw-r--r--libs/binder/tests/unit_fuzzers/Android.bp51
-rw-r--r--libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h2
-rw-r--r--libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp6
-rw-r--r--libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h9
-rw-r--r--libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp1
-rw-r--r--libs/binder/tests/unit_fuzzers/IBinderFuzzFunctions.h1
-rw-r--r--libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp46
-rw-r--r--libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp64
-rw-r--r--libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp3
-rw-r--r--libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recordingbin0 -> 928 bytes
-rw-r--r--libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transactionbin0 -> 32 bytes
-rw-r--r--libs/binder/trusty/OS.cpp66
-rw-r--r--libs/binder/trusty/RpcServerTrusty.cpp52
-rw-r--r--libs/binder/trusty/RpcTransportTipcTrusty.cpp17
-rw-r--r--libs/binder/trusty/binderRpcTest/manifest.json4
-rw-r--r--libs/binder/trusty/binderRpcTest/rules.mk5
-rw-r--r--libs/binder/trusty/binderRpcTest/service/manifest.json2
-rw-r--r--libs/binder/trusty/binderRpcTest/service/rules.mk1
-rw-r--r--libs/binder/trusty/binder_rpc_unstable/rules.mk32
-rw-r--r--libs/binder/trusty/build-config-usertests1
-rw-r--r--libs/binder/trusty/fuzzer/Android.bp65
-rw-r--r--libs/binder/trusty/include/binder/ARpcServerTrusty.h38
-rw-r--r--libs/binder/trusty/include/binder/RpcServerTrusty.h44
-rw-r--r--libs/binder/trusty/include/log/log.h128
-rw-r--r--libs/binder/trusty/include_mock/trusty_log.h3
-rw-r--r--libs/binder/trusty/kernel/rules.mk42
-rw-r--r--libs/binder/trusty/logging.cpp166
-rw-r--r--libs/binder/trusty/ndk/Android.bp (renamed from libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl)29
-rw-r--r--libs/binder/trusty/ndk/include/android/llndk-versioning.h18
-rw-r--r--libs/binder/trusty/ndk/include/sys/cdefs.h5
-rw-r--r--libs/binder/trusty/ndk/rules.mk1
-rw-r--r--libs/binder/trusty/rules.mk42
-rw-r--r--libs/binder/trusty/rust/binder_ndk_sys/rules.mk42
-rw-r--r--libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp90
-rw-r--r--libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk29
-rw-r--r--libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs24
-rw-r--r--libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk37
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk34
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs174
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk32
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/main.rs217
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/manifest.json9
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/rules.mk37
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/service/main.rs234
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/service/manifest.json10
-rw-r--r--libs/binder/trusty/rust/binder_rpc_test/service/rules.mk36
-rw-r--r--libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp17
-rw-r--r--libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs24
-rw-r--r--libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk40
-rw-r--r--libs/binder/trusty/rust/rpcbinder/rules.mk37
-rw-r--r--libs/binder/trusty/rust/rules.mk43
-rw-r--r--libs/binder/trusty/usertests-inc.mk4
-rw-r--r--libs/binderdebug/Android.bp11
-rw-r--r--libs/binderdebug/BinderDebug.cpp193
-rw-r--r--libs/binderdebug/include/binderdebug/BinderDebug.h8
-rw-r--r--libs/binderdebug/stats.cpp62
-rw-r--r--libs/binderthreadstate/include/binderthreadstate/CallerUtils.h8
-rw-r--r--libs/binderthreadstate/test.cpp50
-rw-r--r--libs/bufferqueueconverter/Android.bp8
-rw-r--r--libs/bufferstreams/Android.bp37
-rw-r--r--libs/bufferstreams/OWNERS7
-rw-r--r--libs/bufferstreams/README.md13
-rw-r--r--libs/bufferstreams/aconfig/bufferstreams_flags.aconfig66
-rw-r--r--libs/bufferstreams/examples/app/Android.bp49
-rw-r--r--libs/bufferstreams/examples/app/AndroidManifest.xml26
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt40
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt27
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt35
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt7
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt7
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt65
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt132
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt11
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt60
-rw-r--r--libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt18
-rw-r--r--libs/bufferstreams/examples/app/jni/Android.bp (renamed from cmds/ip-up-vpn/Android.bp)21
-rw-r--r--libs/bufferstreams/examples/app/jni/main.cpp53
-rw-r--r--libs/bufferstreams/examples/app/proguard-rules.pro23
-rw-r--r--libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml170
-rw-r--r--libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml30
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml6
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml6
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webpbin0 -> 1404 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webpbin0 -> 2898 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webpbin0 -> 982 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webpbin0 -> 1772 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webpbin0 -> 1900 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webpbin0 -> 3918 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webpbin0 -> 2884 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webpbin0 -> 5914 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webpbin0 -> 3844 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webpbin0 -> 7778 bytes
-rw-r--r--libs/bufferstreams/examples/app/res/values/colors.xml10
-rw-r--r--libs/bufferstreams/examples/app/res/values/strings.xml8
-rw-r--r--libs/bufferstreams/examples/app/res/values/themes.xml4
-rw-r--r--libs/bufferstreams/include/bufferstreams.h13
-rw-r--r--libs/bufferstreams/rust/Android.bp37
-rw-r--r--libs/bufferstreams/rust/Cargo.lock7
-rw-r--r--libs/bufferstreams/rust/Cargo.toml6
-rw-r--r--libs/bufferstreams/rust/cbindgen.toml149
-rw-r--r--libs/bufferstreams/rust/src/buffers/buffer.rs80
-rw-r--r--libs/bufferstreams/rust/src/buffers/buffer_owner.rs28
-rw-r--r--libs/bufferstreams/rust/src/buffers/buffer_pool.rs137
-rw-r--r--libs/bufferstreams/rust/src/buffers/mod.rs23
-rw-r--r--libs/bufferstreams/rust/src/lib.rs257
-rw-r--r--libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs112
-rw-r--r--libs/bufferstreams/rust/src/publishers/mod.rs20
-rw-r--r--libs/bufferstreams/rust/src/publishers/testing.rs103
-rw-r--r--libs/bufferstreams/rust/src/stream_config.rs67
-rw-r--r--libs/bufferstreams/rust/src/subscribers/mod.rs20
-rw-r--r--libs/bufferstreams/rust/src/subscribers/shared.rs94
-rw-r--r--libs/bufferstreams/rust/src/subscribers/testing.rs106
-rw-r--r--libs/bufferstreams/rust/src/subscriptions/mod.rs19
-rw-r--r--libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs84
-rwxr-xr-xlibs/bufferstreams/update_include.sh2
-rw-r--r--libs/cputimeinstate/cputimeinstate.cpp32
-rw-r--r--libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp1
-rw-r--r--libs/cputimeinstate/testtimeinstate.cpp5
-rw-r--r--libs/debugstore/OWNERS3
-rw-r--r--libs/debugstore/rust/Android.bp71
-rw-r--r--libs/debugstore/rust/Cargo.toml9
-rw-r--r--libs/debugstore/rust/src/core.rs198
-rw-r--r--libs/debugstore/rust/src/event.rs72
-rw-r--r--libs/debugstore/rust/src/event_type.rs43
-rw-r--r--libs/debugstore/rust/src/lib.rs73
-rw-r--r--libs/debugstore/rust/src/storage.rs134
-rw-r--r--libs/dumputils/dump_utils.cpp27
-rw-r--r--libs/dumputils/include/dumputils/dump_utils.h2
-rw-r--r--libs/fakeservicemanager/Android.bp45
-rw-r--r--libs/fakeservicemanager/FakeServiceManager.cpp160
-rw-r--r--libs/fakeservicemanager/ServiceManager.cpp113
-rw-r--r--libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h (renamed from libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h)6
-rw-r--r--libs/fakeservicemanager/rust/src/lib.rs34
-rw-r--r--libs/fakeservicemanager/rust/wrappers/FakeServiceManagerWrapper.hpp25
-rw-r--r--libs/fakeservicemanager/test_sm.cpp62
-rw-r--r--libs/ftl/Android.bp10
-rw-r--r--libs/ftl/OWNERS1
-rw-r--r--libs/ftl/TEST_MAPPING12
-rw-r--r--libs/ftl/algorithm_test.cpp66
-rw-r--r--libs/ftl/concat_test.cpp17
-rw-r--r--libs/ftl/enum_test.cpp38
-rw-r--r--libs/ftl/flags_test.cpp4
-rw-r--r--libs/ftl/function_test.cpp379
-rw-r--r--libs/ftl/match_test.cpp48
-rw-r--r--libs/ftl/mixins_test.cpp185
-rw-r--r--libs/ftl/non_null_test.cpp75
-rw-r--r--libs/ftl/optional_test.cpp238
-rw-r--r--libs/ftl/shared_mutex_test.cpp60
-rw-r--r--libs/ftl/small_map_test.cpp30
-rw-r--r--libs/gralloc/types/Android.bp9
-rw-r--r--libs/gralloc/types/Gralloc4.cpp4
-rw-r--r--libs/graphicsenv/Android.bp16
-rw-r--r--libs/graphicsenv/GpuStatsInfo.cpp32
-rw-r--r--libs/graphicsenv/GraphicsEnv.cpp563
-rw-r--r--libs/graphicsenv/IGpuService.cpp68
-rw-r--r--libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h19
-rw-r--r--libs/graphicsenv/include/graphicsenv/GraphicsEnv.h120
-rw-r--r--libs/graphicsenv/include/graphicsenv/IGpuService.h9
-rw-r--r--libs/gui/Android.bp153
-rw-r--r--libs/gui/BLASTBufferQueue.cpp492
-rw-r--r--libs/gui/BufferItemConsumer.cpp10
-rw-r--r--libs/gui/BufferQueue.cpp10
-rw-r--r--libs/gui/BufferQueueConsumer.cpp98
-rw-r--r--libs/gui/BufferQueueCore.cpp51
-rw-r--r--libs/gui/BufferQueueProducer.cpp203
-rw-r--r--libs/gui/Choreographer.cpp403
-rw-r--r--libs/gui/CompositorTiming.cpp54
-rw-r--r--libs/gui/ConsumerBase.cpp18
-rw-r--r--libs/gui/CpuConsumer.cpp10
-rw-r--r--libs/gui/DisplayEventDispatcher.cpp22
-rw-r--r--libs/gui/DisplayEventReceiver.cpp29
-rw-r--r--libs/gui/DisplayInfo.cpp18
-rw-r--r--libs/gui/FenceMonitor.cpp89
-rw-r--r--libs/gui/FrameRateUtils.cpp67
-rw-r--r--libs/gui/FrameTimelineInfo.cpp68
-rw-r--r--libs/gui/FrameTimestamps.cpp116
-rw-r--r--libs/gui/GLConsumer.cpp10
-rw-r--r--libs/gui/GLConsumerUtils.cpp11
-rw-r--r--libs/gui/IGraphicBufferProducer.cpp40
-rw-r--r--libs/gui/ISurfaceComposer.cpp1610
-rw-r--r--libs/gui/ISurfaceComposerClient.cpp124
-rw-r--r--libs/gui/ITransactionCompletedListener.cpp84
-rw-r--r--libs/gui/LayerDebugInfo.cpp4
-rw-r--r--libs/gui/LayerMetadata.cpp4
-rw-r--r--libs/gui/LayerState.cpp363
-rw-r--r--libs/gui/LayerStatePermissions.cpp58
-rw-r--r--libs/gui/OWNERS11
-rw-r--r--libs/gui/ScreenCaptureResults.cpp17
-rw-r--r--libs/gui/Surface.cpp214
-rw-r--r--libs/gui/SurfaceComposerClient.cpp1230
-rw-r--r--libs/gui/SurfaceControl.cpp52
-rw-r--r--libs/gui/SyncFeatures.cpp10
-rw-r--r--libs/gui/TEST_MAPPING57
-rw-r--r--libs/gui/TransactionTracing.cpp53
-rw-r--r--libs/gui/VsyncEventData.cpp18
-rw-r--r--libs/gui/WindowInfo.cpp106
-rw-r--r--libs/gui/WindowInfosListenerReporter.cpp48
-rw-r--r--libs/gui/WindowInfosUpdate.cpp72
-rw-r--r--libs/gui/aidl/android/gui/ARect.aidl (renamed from libs/gui/aidl/android/gui/Rect.aidl)2
-rw-r--r--libs/gui/aidl/android/gui/CachingHint.aidl30
-rw-r--r--libs/gui/aidl/android/gui/CaptureArgs.aidl19
-rw-r--r--libs/gui/aidl/android/gui/Color.aidl25
-rw-r--r--libs/gui/aidl/android/gui/CompositionPreference.aidl25
-rw-r--r--libs/gui/aidl/android/gui/ContentSamplingAttributes.aidl24
-rw-r--r--libs/gui/aidl/android/gui/CreateSurfaceResult.aidl25
-rw-r--r--libs/gui/aidl/android/gui/DeviceProductInfo.aidl58
-rw-r--r--libs/gui/aidl/android/gui/DisplayConnectionType.aidl24
-rw-r--r--libs/gui/aidl/android/gui/DisplayDecorationSupport.aidl (renamed from libs/input/android/os/BlockUntrustedTouchesMode.aidl)26
-rw-r--r--libs/gui/aidl/android/gui/DisplayMode.aidl39
-rw-r--r--libs/gui/aidl/android/gui/DisplayModeSpecs.aidl75
-rw-r--r--libs/gui/aidl/android/gui/DisplayPrimaries.aidl33
-rw-r--r--libs/gui/aidl/android/gui/DisplayedFrameStats.aidl40
-rw-r--r--libs/gui/aidl/android/gui/DynamicDisplayInfo.aidl46
-rw-r--r--libs/gui/aidl/android/gui/FrameEvent.aidl35
-rw-r--r--libs/gui/aidl/android/gui/FrameStats.aidl47
-rw-r--r--libs/gui/aidl/android/gui/FrameTimelineInfo.aidl (renamed from libs/gui/include/gui/FrameTimelineInfo.h)35
-rw-r--r--libs/gui/aidl/android/gui/HdrCapabilities.aidl27
-rw-r--r--libs/gui/aidl/android/gui/HdrConversionCapability.aidl25
-rw-r--r--libs/gui/aidl/android/gui/HdrConversionStrategy.aidl25
-rw-r--r--libs/gui/aidl/android/gui/IDisplayEventConnection.aidl6
-rw-r--r--libs/gui/aidl/android/gui/IHdrConversionConstants.aidl30
-rw-r--r--libs/gui/aidl/android/gui/IHdrLayerInfoListener.aidl6
-rw-r--r--libs/gui/aidl/android/gui/ISurfaceComposer.aidl432
-rw-r--r--libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl66
-rw-r--r--libs/gui/aidl/android/gui/ITransactionTraceListener.aidl6
-rw-r--r--libs/gui/aidl/android/gui/LayerDebugInfo.aidl19
-rw-r--r--libs/gui/aidl/android/gui/LayerMetadata.aidl19
-rw-r--r--libs/gui/aidl/android/gui/OverlayProperties.aidl30
-rw-r--r--libs/gui/aidl/android/gui/PullAtomData.aidl23
-rw-r--r--libs/gui/aidl/android/gui/SchedulingPolicy.aidl23
-rw-r--r--libs/gui/aidl/android/gui/StaticDisplayInfo.aidl30
-rw-r--r--libs/gui/aidl/android/gui/TrustedPresentationThresholds.aidl24
-rw-r--r--libs/gui/aidl/android/gui/WindowInfosListenerInfo.aidl25
-rw-r--r--libs/gui/android/gui/FocusRequest.aidl9
-rw-r--r--libs/gui/android/gui/IWindowInfosListener.aidl9
-rw-r--r--libs/gui/android/gui/IWindowInfosPublisher.aidl (renamed from services/inputflinger/tests/IInputFlingerQuery.aidl)12
-rw-r--r--libs/gui/android/gui/StalledTransactionInfo.aidl24
-rw-r--r--libs/gui/android/gui/TouchOcclusionMode.aidl3
-rw-r--r--libs/gui/android/gui/WindowInfosUpdate.aidl22
-rw-r--r--libs/gui/bufferqueue/1.0/Conversion.cpp15
-rw-r--r--libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp14
-rw-r--r--libs/gui/bufferqueue/2.0/H2BGraphicBufferProducer.cpp4
-rw-r--r--libs/gui/fuzzer/Android.bp146
-rw-r--r--libs/gui/fuzzer/README.md219
-rw-r--r--libs/gui/fuzzer/libgui_bufferQueue_fuzzer.cpp392
-rw-r--r--libs/gui/fuzzer/libgui_consumer_fuzzer.cpp82
-rw-r--r--libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp107
-rw-r--r--libs/gui/fuzzer/libgui_fuzzer_utils.h329
-rw-r--r--libs/gui/fuzzer/libgui_parcelable_fuzzer.cpp175
-rw-r--r--libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp328
-rw-r--r--libs/gui/fuzzer/libgui_surfaceComposer_fuzzer.cpp41
-rw-r--r--libs/gui/include/gui/AidlStatusUtil.h114
-rw-r--r--libs/gui/include/gui/BLASTBufferQueue.h77
-rw-r--r--libs/gui/include/gui/BufferQueue.h9
-rw-r--r--libs/gui/include/gui/BufferQueueCore.h14
-rw-r--r--libs/gui/include/gui/BufferQueueProducer.h11
-rw-r--r--libs/gui/include/gui/Choreographer.h141
-rw-r--r--libs/gui/include/gui/CompositorTiming.h43
-rw-r--r--libs/gui/include/gui/DisplayCaptureArgs.h17
-rw-r--r--libs/gui/include/gui/DisplayEventDispatcher.h12
-rw-r--r--libs/gui/include/gui/DisplayEventReceiver.h16
-rw-r--r--libs/gui/include/gui/DisplayInfo.h2
-rw-r--r--libs/gui/include/gui/FenceMonitor.h44
-rw-r--r--libs/gui/include/gui/FrameRateUtils.h (renamed from libs/ui/include_types/ui/DataspaceUtils.h)11
-rw-r--r--libs/gui/include/gui/FrameTimestamps.h42
-rw-r--r--libs/gui/include/gui/GLConsumer.h4
-rw-r--r--libs/gui/include/gui/IConsumerListener.h8
-rw-r--r--libs/gui/include/gui/IGraphicBufferProducer.h8
-rw-r--r--libs/gui/include/gui/IProducerListener.h6
-rw-r--r--libs/gui/include/gui/ISurfaceComposer.h451
-rw-r--r--libs/gui/include/gui/ISurfaceComposerClient.h97
-rw-r--r--libs/gui/include/gui/ITransactionCompletedListener.h32
-rw-r--r--libs/gui/include/gui/JankInfo.h20
-rw-r--r--libs/gui/include/gui/LayerCaptureArgs.h3
-rw-r--r--libs/gui/include/gui/LayerDebugInfo.h6
-rw-r--r--libs/gui/include/gui/LayerMetadata.h19
-rw-r--r--libs/gui/include/gui/LayerState.h159
-rw-r--r--libs/gui/include/gui/LayerStatePermissions.h29
-rw-r--r--libs/gui/include/gui/PidUid.h56
-rw-r--r--libs/gui/include/gui/SchedulingPolicy.h40
-rw-r--r--libs/gui/include/gui/ScreenCaptureResults.h4
-rw-r--r--libs/gui/include/gui/Surface.h14
-rw-r--r--libs/gui/include/gui/SurfaceComposerClient.h250
-rw-r--r--libs/gui/include/gui/SurfaceControl.h19
-rw-r--r--libs/gui/include/gui/SyncScreenCaptureListener.h6
-rw-r--r--libs/gui/include/gui/TraceUtils.h34
-rw-r--r--libs/gui/include/gui/VsyncEventData.h11
-rw-r--r--libs/gui/include/gui/WindowInfo.h28
-rw-r--r--libs/gui/include/gui/WindowInfosListener.h8
-rw-r--r--libs/gui/include/gui/WindowInfosListenerReporter.h23
-rw-r--r--libs/gui/include/gui/WindowInfosUpdate.h44
-rw-r--r--libs/gui/include/gui/bufferqueue/1.0/WGraphicBufferProducer.h2
-rw-r--r--libs/gui/include/gui/fake/BufferData.h51
-rw-r--r--libs/gui/include/gui/test/CallbackUtils.h30
-rw-r--r--libs/gui/include/gui/view/Surface.h4
-rw-r--r--libs/gui/include/private/gui/ComposerServiceAIDL.h23
-rw-r--r--libs/gui/libgui_flags.aconfig18
-rw-r--r--libs/gui/tests/Android.bp28
-rw-r--r--libs/gui/tests/AndroidTest.xml2
-rw-r--r--libs/gui/tests/BLASTBufferQueue_test.cpp345
-rw-r--r--libs/gui/tests/BufferItemConsumer_test.cpp10
-rw-r--r--libs/gui/tests/BufferQueue_test.cpp312
-rw-r--r--libs/gui/tests/CompositorTiming_test.cpp61
-rw-r--r--libs/gui/tests/Constants.h22
-rw-r--r--libs/gui/tests/CpuConsumer_test.cpp20
-rw-r--r--libs/gui/tests/DisplayEventStructLayout_test.cpp10
-rw-r--r--libs/gui/tests/DisplayedContentSampling_test.cpp5
-rw-r--r--libs/gui/tests/EndToEndNativeInputTest.cpp120
-rw-r--r--libs/gui/tests/FrameRateUtilsTest.cpp74
-rw-r--r--libs/gui/tests/GLTest.cpp32
-rw-r--r--libs/gui/tests/IGraphicBufferProducer_test.cpp14
-rw-r--r--libs/gui/tests/LibGuiMain.cpp38
-rw-r--r--libs/gui/tests/Malicious.cpp4
-rw-r--r--libs/gui/tests/OWNERS6
-rw-r--r--libs/gui/tests/RegionSampling_test.cpp102
-rw-r--r--libs/gui/tests/SamplingDemo.cpp20
-rw-r--r--libs/gui/tests/StreamSplitter_test.cpp18
-rw-r--r--libs/gui/tests/SurfaceTextureClient_test.cpp10
-rw-r--r--libs/gui/tests/SurfaceTextureFBO_test.cpp2
-rw-r--r--libs/gui/tests/SurfaceTextureGL.h2
-rw-r--r--libs/gui/tests/SurfaceTextureGL_test.cpp7
-rw-r--r--libs/gui/tests/Surface_test.cpp560
-rw-r--r--libs/gui/tests/VsyncEventData_test.cpp4
-rw-r--r--libs/gui/tests/WindowInfo_test.cpp21
-rw-r--r--libs/gui/view/Surface.cpp51
-rw-r--r--libs/input/Android.bp283
-rw-r--r--libs/input/FromRustToCpp.cpp (renamed from libs/binder/Trace.cpp)16
-rw-r--r--libs/input/Input.cpp332
-rw-r--r--libs/input/InputDevice.cpp47
-rw-r--r--libs/input/InputEventLabels.cpp203
-rw-r--r--libs/input/InputTransport.cpp482
-rw-r--r--libs/input/InputVerifier.cpp59
-rw-r--r--libs/input/InputWrapper.hpp18
-rw-r--r--libs/input/KeyCharacterMap.cpp603
-rw-r--r--libs/input/KeyLayoutMap.cpp223
-rw-r--r--libs/input/Keyboard.cpp35
-rw-r--r--libs/input/MotionPredictor.cpp266
-rw-r--r--libs/input/MotionPredictorMetricsManager.cpp378
-rw-r--r--libs/input/OWNERS1
-rw-r--r--libs/input/PropertyMap.cpp142
-rw-r--r--[-rwxr-xr-x]libs/input/PropertyMap_fuzz.cpp33
-rw-r--r--libs/input/TfLiteMotionPredictor.cpp422
-rw-r--r--libs/input/TouchVideoFrame.cpp15
-rw-r--r--libs/input/VelocityControl.cpp31
-rw-r--r--libs/input/VelocityTracker.cpp1045
-rw-r--r--libs/input/VirtualInputDevice.cpp396
-rw-r--r--libs/input/VirtualKeyMap.cpp19
-rw-r--r--libs/input/android/os/IInputConstants.aidl92
-rw-r--r--libs/input/android/os/InputConfig.aidl6
-rw-r--r--libs/input/android/os/InputEventInjectionResult.aidl3
-rw-r--r--libs/input/android/os/InputEventInjectionSync.aidl3
-rw-r--r--libs/input/ffi/FromRustToCpp.h (renamed from cmds/installd/view_compiler.h)12
-rw-r--r--libs/input/input_flags.aconfig93
-rw-r--r--libs/input/rust/Android.bp72
-rw-r--r--libs/input/rust/input.rs217
-rw-r--r--libs/input/rust/input_verifier.rs638
-rw-r--r--libs/input/rust/lib.rs97
-rw-r--r--libs/input/tests/Android.bp56
-rw-r--r--libs/input/tests/BlockingQueue_test.cpp (renamed from services/inputflinger/tests/BlockingQueue_test.cpp)22
-rw-r--r--libs/input/tests/InputDevice_test.cpp80
-rw-r--r--libs/input/tests/InputEvent_test.cpp370
-rw-r--r--libs/input/tests/InputPublisherAndConsumer_test.cpp239
-rw-r--r--libs/input/tests/InputVerifier_test.cpp54
-rw-r--r--libs/input/tests/MotionPredictorMetricsManager_test.cpp981
-rw-r--r--libs/input/tests/MotionPredictor_test.cpp181
-rw-r--r--libs/input/tests/RingBuffer_test.cpp223
-rw-r--r--libs/input/tests/StructLayout_test.cpp14
-rw-r--r--libs/input/tests/TfLiteMotionPredictor_test.cpp169
-rw-r--r--libs/input/tests/TouchResampling_test.cpp624
-rw-r--r--libs/input/tests/TouchVideoFrame_test.cpp46
-rw-r--r--libs/input/tests/VelocityTracker_test.cpp696
-rw-r--r--libs/input/tests/VerifiedInputEvent_test.cpp18
-rw-r--r--libs/input/tests/data/bad_axis_label.kl17
-rw-r--r--libs/input/tests/data/bad_led_label.kl17
-rw-r--r--libs/input/tests/data/hid_fallback_mapping.kl32
-rw-r--r--libs/math/include/math/mat4.h8
-rw-r--r--libs/nativedisplay/AChoreographer.cpp483
-rw-r--r--libs/nativedisplay/ADisplay.cpp34
-rw-r--r--libs/nativedisplay/Android.bp3
-rw-r--r--libs/nativedisplay/include/surfacetexture/SurfaceTexture.h45
-rw-r--r--libs/nativedisplay/libnativedisplay.map.txt40
-rw-r--r--libs/nativedisplay/surfacetexture/EGLConsumer.cpp8
-rw-r--r--libs/nativedisplay/surfacetexture/ImageConsumer.cpp2
-rw-r--r--libs/nativedisplay/surfacetexture/SurfaceTexture.cpp48
-rw-r--r--libs/nativewindow/AHardwareBuffer.cpp245
-rw-r--r--libs/nativewindow/ANativeWindow.cpp119
-rw-r--r--libs/nativewindow/TEST_MAPPING6
-rw-r--r--libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h11
-rw-r--r--libs/nativewindow/include/android/data_space.h190
-rw-r--r--libs/nativewindow/include/android/hardware_buffer.h4
-rw-r--r--libs/nativewindow/include/android/hardware_buffer_aidl.h36
-rw-r--r--libs/nativewindow/include/android/native_window.h47
-rw-r--r--libs/nativewindow/include/android/native_window_aidl.h38
-rw-r--r--libs/nativewindow/include/system/window.h151
-rw-r--r--libs/nativewindow/libnativewindow.map.txt23
-rw-r--r--libs/nativewindow/rust/Android.bp137
-rw-r--r--libs/nativewindow/rust/src/lib.rs393
-rw-r--r--libs/nativewindow/rust/src/surface.rs143
-rw-r--r--libs/nativewindow/rust/sys/nativewindow_bindings.h22
-rw-r--r--libs/nativewindow/tests/benchmark/Android.bp50
-rw-r--r--libs/nativewindow/tests/benchmark/README.md22
-rw-r--r--libs/nativewindow/tests/benchmark/buffer_benchmarks.cc74
-rw-r--r--libs/nativewindow/tests/benchmark/buffer_benchmarks.rs60
-rw-r--r--libs/permission/AppOpsManager.cpp8
-rw-r--r--libs/permission/IAppOpsService.cpp11
-rw-r--r--libs/permission/aidl/android/content/AttributionSourceState.aidl4
-rw-r--r--libs/permission/include/binder/AppOpsManager.h6
-rw-r--r--libs/permission/include/binder/IAppOpsService.h3
-rw-r--r--libs/renderengine/Android.bp51
-rw-r--r--libs/renderengine/Description.cpp61
-rw-r--r--libs/renderengine/ExternalTexture.cpp27
-rw-r--r--libs/renderengine/Mesh.cpp146
-rw-r--r--libs/renderengine/OWNERS2
-rw-r--r--libs/renderengine/RenderEngine.cpp49
-rw-r--r--libs/renderengine/Texture.cpp77
-rw-r--r--libs/renderengine/benchmark/Android.bp4
-rw-r--r--libs/renderengine/benchmark/RenderEngineBench.cpp62
-rw-r--r--libs/renderengine/gl/GLESRenderEngine.cpp1864
-rw-r--r--libs/renderengine/gl/GLESRenderEngine.h312
-rw-r--r--libs/renderengine/gl/GLFramebuffer.cpp115
-rw-r--r--libs/renderengine/gl/GLFramebuffer.h68
-rw-r--r--libs/renderengine/gl/GLImage.cpp83
-rw-r--r--libs/renderengine/gl/GLImage.h54
-rw-r--r--libs/renderengine/gl/GLShadowTexture.cpp52
-rw-r--r--libs/renderengine/gl/GLShadowVertexGenerator.cpp98
-rw-r--r--libs/renderengine/gl/GLShadowVertexGenerator.h65
-rw-r--r--libs/renderengine/gl/GLSkiaShadowPort.cpp656
-rw-r--r--libs/renderengine/gl/GLSkiaShadowPort.h96
-rw-r--r--libs/renderengine/gl/GLVertexBuffer.cpp55
-rw-r--r--libs/renderengine/gl/ImageManager.cpp148
-rw-r--r--libs/renderengine/gl/ImageManager.h74
-rw-r--r--libs/renderengine/gl/Program.cpp175
-rw-r--r--libs/renderengine/gl/Program.h120
-rw-r--r--libs/renderengine/gl/ProgramCache.cpp830
-rw-r--r--libs/renderengine/gl/ProgramCache.h239
-rw-r--r--libs/renderengine/gl/filters/BlurFilter.cpp268
-rw-r--r--libs/renderengine/gl/filters/BlurFilter.h95
-rw-r--r--libs/renderengine/gl/filters/GenericProgram.cpp122
-rw-r--r--libs/renderengine/gl/filters/GenericProgram.h51
-rw-r--r--libs/renderengine/include/renderengine/BorderRenderInfo.h (renamed from libs/renderengine/include/renderengine/mock/Image.h)24
-rw-r--r--libs/renderengine/include/renderengine/DisplaySettings.h18
-rw-r--r--libs/renderengine/include/renderengine/ExternalTexture.h2
-rw-r--r--libs/renderengine/include/renderengine/LayerSettings.h60
-rw-r--r--libs/renderengine/include/renderengine/Mesh.h205
-rw-r--r--libs/renderengine/include/renderengine/RenderEngine.h85
-rw-r--r--libs/renderengine/include/renderengine/Texture.h60
-rw-r--r--libs/renderengine/include/renderengine/impl/ExternalTexture.h2
-rw-r--r--libs/renderengine/include/renderengine/mock/FakeExternalTexture.h7
-rw-r--r--libs/renderengine/include/renderengine/mock/RenderEngine.h27
-rw-r--r--libs/renderengine/include/renderengine/private/Description.h95
-rw-r--r--libs/renderengine/mock/Framebuffer.cpp30
-rw-r--r--libs/renderengine/skia/AutoBackendTexture.cpp125
-rw-r--r--libs/renderengine/skia/AutoBackendTexture.h2
-rw-r--r--libs/renderengine/skia/Cache.cpp433
-rw-r--r--libs/renderengine/skia/Cache.h2
-rw-r--r--libs/renderengine/skia/ColorSpaces.cpp9
-rw-r--r--libs/renderengine/skia/GLExtensions.cpp (renamed from libs/renderengine/gl/GLExtensions.cpp)18
-rw-r--r--libs/renderengine/skia/GLExtensions.h (renamed from libs/renderengine/gl/GLExtensions.h)4
-rw-r--r--libs/renderengine/skia/SkiaGLRenderEngine.cpp1293
-rw-r--r--libs/renderengine/skia/SkiaGLRenderEngine.h138
-rw-r--r--libs/renderengine/skia/SkiaRenderEngine.cpp1264
-rw-r--r--libs/renderengine/skia/SkiaRenderEngine.h176
-rw-r--r--libs/renderengine/skia/SkiaVkRenderEngine.cpp735
-rw-r--r--libs/renderengine/skia/SkiaVkRenderEngine.h58
-rw-r--r--libs/renderengine/skia/debug/SkiaCapture.cpp17
-rw-r--r--libs/renderengine/skia/debug/SkiaCapture.h4
-rw-r--r--libs/renderengine/skia/debug/SkiaMemoryReporter.cpp15
-rw-r--r--libs/renderengine/skia/debug/SkiaMemoryReporter.h5
-rwxr-xr-xlibs/renderengine/skia/debug/record.sh2
-rw-r--r--libs/renderengine/skia/filters/BlurFilter.cpp3
-rw-r--r--libs/renderengine/skia/filters/GaussianBlurFilter.cpp8
-rw-r--r--libs/renderengine/skia/filters/KawaseBlurFilter.cpp96
-rw-r--r--libs/renderengine/tests/Android.bp4
-rw-r--r--libs/renderengine/tests/RenderEngineTest.cpp786
-rw-r--r--libs/renderengine/tests/RenderEngineThreadedTest.cpp159
-rw-r--r--libs/renderengine/threaded/RenderEngineThreaded.cpp161
-rw-r--r--libs/renderengine/threaded/RenderEngineThreaded.h27
-rw-r--r--libs/sensor/Android.bp1
-rw-r--r--libs/sensor/ISensorServer.cpp110
-rw-r--r--libs/sensor/OWNERS2
-rw-r--r--libs/sensor/Sensor.cpp18
-rw-r--r--libs/sensor/SensorManager.cpp107
-rw-r--r--libs/sensor/include/sensor/ISensorServer.h6
-rw-r--r--libs/sensor/include/sensor/SensorManager.h9
-rw-r--r--libs/sensorprivacy/SensorPrivacyManager.cpp23
-rw-r--r--libs/shaders/Android.bp7
-rw-r--r--libs/shaders/include/shaders/shaders.h26
-rw-r--r--libs/shaders/shaders.cpp510
-rw-r--r--libs/shaders/tests/Android.bp7
-rw-r--r--libs/shaders/tests/shaders_test.cpp42
-rw-r--r--libs/tonemap/Android.bp6
-rw-r--r--libs/tonemap/tests/Android.bp7
-rw-r--r--libs/ui/Android.bp15
-rw-r--r--libs/ui/DebugUtils.cpp6
-rw-r--r--libs/ui/DeviceProductInfo.cpp79
-rw-r--r--libs/ui/DisplayIdentification.cpp24
-rw-r--r--libs/ui/DisplayMode.cpp69
-rw-r--r--libs/ui/DynamicDisplayInfo.cpp43
-rw-r--r--libs/ui/Fence.cpp2
-rw-r--r--libs/ui/FenceTime.cpp8
-rw-r--r--libs/ui/Gralloc2.cpp2
-rw-r--r--libs/ui/Gralloc3.cpp2
-rw-r--r--libs/ui/Gralloc4.cpp165
-rw-r--r--libs/ui/Gralloc5.cpp1038
-rw-r--r--libs/ui/GraphicBufferAllocator.cpp41
-rw-r--r--libs/ui/GraphicBufferMapper.cpp97
-rw-r--r--libs/ui/HdrCapabilities.cpp86
-rw-r--r--libs/ui/PublicFormat.cpp19
-rw-r--r--libs/ui/StaticDisplayInfo.cpp57
-rw-r--r--libs/ui/include/ui/ColorMode.h60
-rw-r--r--libs/ui/include/ui/DeviceProductInfo.h13
-rw-r--r--libs/ui/include/ui/DisplayId.h11
-rw-r--r--libs/ui/include/ui/DisplayMap.h40
-rw-r--r--libs/ui/include/ui/DisplayMode.h13
-rw-r--r--libs/ui/include/ui/DynamicDisplayInfo.h9
-rw-r--r--libs/ui/include/ui/FatVector.h11
-rw-r--r--libs/ui/include/ui/FenceResult.h33
-rw-r--r--libs/ui/include/ui/FenceTime.h16
-rw-r--r--libs/ui/include/ui/Gralloc.h76
-rw-r--r--libs/ui/include/ui/Gralloc2.h4
-rw-r--r--libs/ui/include/ui/Gralloc3.h4
-rw-r--r--libs/ui/include/ui/Gralloc4.h40
-rw-r--r--libs/ui/include/ui/Gralloc5.h184
-rw-r--r--libs/ui/include/ui/GraphicBuffer.h19
-rw-r--r--libs/ui/include/ui/GraphicBufferMapper.h52
-rw-r--r--libs/ui/include/ui/GraphicTypes.h3
-rw-r--r--libs/ui/include/ui/HdrCapabilities.h10
-rw-r--r--libs/ui/include/ui/PublicFormat.h1
-rw-r--r--libs/ui/include/ui/Rotation.h9
-rw-r--r--libs/ui/include/ui/ShadowSettings.h65
-rw-r--r--libs/ui/include/ui/StaticDisplayInfo.h10
-rw-r--r--libs/ui/include_types/ui/HdrRenderTypeUtils.h64
-rw-r--r--libs/ui/tests/Android.bp4
-rw-r--r--libs/ui/tests/DataspaceUtils_test.cpp53
-rw-r--r--libs/ui/tests/HdrRenderTypeUtils_test.cpp65
-rw-r--r--libs/ultrahdr/Android.bp85
-rw-r--r--libs/ultrahdr/OWNERS3
-rw-r--r--libs/ultrahdr/adobe-hdr-gain-map-license/Android.bp19
-rw-r--r--libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE1
-rw-r--r--libs/ultrahdr/fuzzer/Android.bp72
-rw-r--r--libs/ultrahdr/fuzzer/ultrahdr_dec_fuzzer.cpp73
-rw-r--r--libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp333
-rw-r--r--libs/ultrahdr/gainmapmath.cpp775
-rw-r--r--libs/ultrahdr/icc.cpp692
-rw-r--r--libs/ultrahdr/include/ultrahdr/gainmapmath.h505
-rw-r--r--libs/ultrahdr/include/ultrahdr/icc.h256
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h143
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h102
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegr.h452
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h61
-rw-r--r--libs/ultrahdr/include/ultrahdr/jpegrutils.h167
-rw-r--r--libs/ultrahdr/include/ultrahdr/multipictureformat.h64
-rw-r--r--libs/ultrahdr/include/ultrahdr/ultrahdr.h82
-rw-r--r--libs/ultrahdr/jpegdecoderhelper.cpp543
-rw-r--r--libs/ultrahdr/jpegencoderhelper.cpp273
-rw-r--r--libs/ultrahdr/jpegr.cpp1503
-rw-r--r--libs/ultrahdr/jpegrutils.cpp600
-rw-r--r--libs/ultrahdr/multipictureformat.cpp94
-rw-r--r--libs/ultrahdr/tests/Android.bp51
-rw-r--r--libs/ultrahdr/tests/AndroidTest.xml26
-rw-r--r--libs/ultrahdr/tests/data/jpeg_image.jpgbin0 -> 24430 bytes
-rw-r--r--libs/ultrahdr/tests/data/minnie-318x240.yu121930
-rw-r--r--libs/ultrahdr/tests/data/minnie-320x240-y.jpgbin0 -> 20193 bytes
-rw-r--r--libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpgbin0 -> 34266 bytes
-rw-r--r--libs/ultrahdr/tests/data/minnie-320x240-yuv.jpgbin0 -> 20193 bytes
-rw-r--r--libs/ultrahdr/tests/data/minnie-320x240.y1930
-rw-r--r--libs/ultrahdr/tests/data/minnie-320x240.yu121930
-rw-r--r--libs/ultrahdr/tests/data/raw_p010_image.p010bin0 -> 2764800 bytes
-rw-r--r--libs/ultrahdr/tests/data/raw_yuv420_image.yuv4201
-rw-r--r--libs/ultrahdr/tests/gainmapmath_test.cpp1359
-rw-r--r--libs/ultrahdr/tests/icchelper_test.cpp77
-rw-r--r--libs/ultrahdr/tests/jpegdecoderhelper_test.cpp156
-rw-r--r--libs/ultrahdr/tests/jpegencoderhelper_test.cpp135
-rw-r--r--libs/ultrahdr/tests/jpegr_test.cpp2035
-rw-r--r--libs/vibrator/ExternalVibration.cpp29
-rw-r--r--libs/vibrator/OWNERS1
-rw-r--r--libs/vibrator/fuzzer/Android.bp14
-rw-r--r--libs/vibrator/include/vibrator/ExternalVibration.h7
-rw-r--r--libs/vibrator/include/vibrator/ExternalVibrationUtils.h2
-rw-r--r--libs/vr/libpdx/private/pdx/rpc/string_wrapper.h4
-rw-r--r--opengl/Android.bp4
-rw-r--r--opengl/TEST_MAPPING3
-rw-r--r--opengl/include/EGL/eglext.h5
-rw-r--r--opengl/libs/Android.bp7
-rw-r--r--opengl/libs/EGL/BlobCache.cpp6
-rw-r--r--opengl/libs/EGL/FileBlobCache.cpp15
-rw-r--r--opengl/libs/EGL/FileBlobCache.h5
-rw-r--r--opengl/libs/EGL/Loader.cpp378
-rw-r--r--opengl/libs/EGL/Loader.h2
-rw-r--r--opengl/libs/EGL/MultifileBlobCache.cpp937
-rw-r--r--opengl/libs/EGL/MultifileBlobCache.h201
-rw-r--r--opengl/libs/EGL/MultifileBlobCache_test.cpp506
-rw-r--r--opengl/libs/EGL/egl_angle_platform.cpp73
-rw-r--r--opengl/libs/EGL/egl_angle_platform.h4
-rw-r--r--opengl/libs/EGL/egl_cache.cpp167
-rw-r--r--opengl/libs/EGL/egl_cache.h30
-rw-r--r--opengl/libs/EGL/egl_display.cpp35
-rw-r--r--opengl/libs/EGL/egl_display.h1
-rw-r--r--opengl/libs/EGL/egl_object.cpp2
-rw-r--r--opengl/libs/EGL/egl_platform_entries.cpp88
-rw-r--r--opengl/libs/EGL/egldefs.h10
-rw-r--r--opengl/libs/EGL/fuzzer/Android.bp42
-rw-r--r--opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp159
-rw-r--r--opengl/tests/EGLTest/Android.bp2
-rw-r--r--opengl/tests/EGLTest/EGL_test.cpp17
-rw-r--r--opengl/tests/EGLTest/egl_cache_test.cpp264
-rw-r--r--opengl/tests/gldual/AndroidManifest.xml5
-rw-r--r--opengl/tests/lib/WindowSurface.cpp9
-rwxr-xr-xopengl/tools/glgen/gen10
-rw-r--r--opengl/tools/glgen/stubs/egl/EGL14Header.java-if1
-rw-r--r--opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if41
-rw-r--r--opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp69
-rwxr-xr-xopengl/tools/glgen/stubs/egl/eglGetDisplay.java2
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp1
-rw-r--r--opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp1
-rw-r--r--services/audiomanager/IAudioManager.cpp15
-rw-r--r--services/automotive/display/Android.bp2
-rw-r--r--services/automotive/display/android.frameworks.automotive.display@1.0-service.rc2
-rw-r--r--services/batteryservice/Android.bp1
-rw-r--r--services/batteryservice/include/batteryservice/BatteryService.h7
-rw-r--r--services/displayservice/Android.bp12
-rw-r--r--services/gpuservice/Android.bp26
-rw-r--r--services/gpuservice/GpuService.cpp51
-rw-r--r--services/gpuservice/OWNERS1
-rw-r--r--services/gpuservice/gpumem/Android.bp19
-rw-r--r--services/gpuservice/gpumem/GpuMem.cpp3
-rw-r--r--services/gpuservice/gpumem/include/gpumem/GpuMem.h9
-rw-r--r--services/gpuservice/gpustats/Android.bp17
-rw-r--r--services/gpuservice/gpustats/GpuStats.cpp97
-rw-r--r--services/gpuservice/gpustats/include/gpustats/GpuStats.h5
-rw-r--r--services/gpuservice/gpuwork/GpuWork.cpp1
-rw-r--r--services/gpuservice/gpuwork/include/gpuwork/GpuWork.h6
-rw-r--r--services/gpuservice/include/gpuservice/GpuService.h (renamed from services/gpuservice/GpuService.h)8
-rw-r--r--services/gpuservice/main_gpuservice.cpp2
-rw-r--r--services/gpuservice/tests/fuzzers/Android.bp25
-rw-r--r--services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp29
-rw-r--r--services/gpuservice/tests/unittests/Android.bp11
-rw-r--r--services/gpuservice/tests/unittests/GpuMemTest.cpp4
-rw-r--r--services/gpuservice/tests/unittests/GpuMemTracerTest.cpp4
-rw-r--r--services/gpuservice/tests/unittests/GpuServiceTest.cpp52
-rw-r--r--services/gpuservice/tests/unittests/GpuStatsTest.cpp107
-rw-r--r--services/gpuservice/tests/unittests/TestableGpuMem.h2
-rw-r--r--services/gpuservice/tracing/Android.bp20
-rw-r--r--services/gpuservice/vts/Android.bp2
-rw-r--r--services/gpuservice/vts/OWNERS1
-rw-r--r--services/gpuservice/vts/src/com/android/tests/gpuservice/GpuWorkTracepointTest.java19
-rw-r--r--services/inputflinger/Android.bp116
-rw-r--r--services/inputflinger/InputCommonConverter.cpp33
-rw-r--r--services/inputflinger/InputDeviceMetricsCollector.cpp414
-rw-r--r--services/inputflinger/InputDeviceMetricsCollector.h186
-rw-r--r--services/inputflinger/InputDeviceMetricsSource.cpp118
-rw-r--r--services/inputflinger/InputDeviceMetricsSource.h60
-rw-r--r--services/inputflinger/InputFilter.cpp145
-rw-r--r--services/inputflinger/InputFilter.h71
-rw-r--r--services/inputflinger/InputListener.cpp416
-rw-r--r--services/inputflinger/InputManager.cpp166
-rw-r--r--services/inputflinger/InputManager.h66
-rw-r--r--services/inputflinger/InputProcessor.cpp (renamed from services/inputflinger/InputClassifier.cpp)178
-rw-r--r--services/inputflinger/InputProcessor.h (renamed from services/inputflinger/InputClassifier.h)87
-rw-r--r--services/inputflinger/InputReaderBase.cpp41
-rw-r--r--services/inputflinger/InputThread.cpp8
-rw-r--r--services/inputflinger/NotifyArgs.cpp215
-rw-r--r--services/inputflinger/OWNERS1
-rw-r--r--services/inputflinger/PointerChoreographer.cpp543
-rw-r--r--services/inputflinger/PointerChoreographer.h148
-rw-r--r--services/inputflinger/PreferStylusOverTouchBlocker.cpp36
-rw-r--r--services/inputflinger/SyncQueue.h66
-rw-r--r--services/inputflinger/TEST_MAPPING215
-rw-r--r--services/inputflinger/UnwantedInteractionBlocker.cpp126
-rw-r--r--services/inputflinger/UnwantedInteractionBlocker.h24
-rw-r--r--services/inputflinger/aidl/Android.bp34
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl (renamed from libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl)18
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl50
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl40
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl25
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl41
-rw-r--r--services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl37
-rw-r--r--services/inputflinger/benchmarks/Android.bp4
-rw-r--r--services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp220
-rw-r--r--services/inputflinger/dispatcher/Android.bp20
-rw-r--r--services/inputflinger/dispatcher/AnrTracker.cpp2
-rw-r--r--services/inputflinger/dispatcher/AnrTracker.h7
-rw-r--r--services/inputflinger/dispatcher/CancelationOptions.h19
-rw-r--r--services/inputflinger/dispatcher/Connection.cpp11
-rw-r--r--services/inputflinger/dispatcher/Connection.h16
-rw-r--r--services/inputflinger/dispatcher/DebugConfig.cpp39
-rw-r--r--services/inputflinger/dispatcher/DebugConfig.h59
-rw-r--r--services/inputflinger/dispatcher/DragState.h5
-rw-r--r--services/inputflinger/dispatcher/Entry.cpp134
-rw-r--r--services/inputflinger/dispatcher/Entry.h114
-rw-r--r--services/inputflinger/dispatcher/FocusResolver.cpp112
-rw-r--r--services/inputflinger/dispatcher/FocusResolver.h8
-rw-r--r--services/inputflinger/dispatcher/InjectionState.cpp18
-rw-r--r--services/inputflinger/dispatcher/InjectionState.h17
-rw-r--r--services/inputflinger/dispatcher/InputDispatcher.cpp3781
-rw-r--r--services/inputflinger/dispatcher/InputDispatcher.h337
-rw-r--r--services/inputflinger/dispatcher/InputDispatcherFactory.cpp2
-rw-r--r--services/inputflinger/dispatcher/InputEventTimeline.cpp16
-rw-r--r--services/inputflinger/dispatcher/InputEventTimeline.h13
-rw-r--r--services/inputflinger/dispatcher/InputState.cpp405
-rw-r--r--services/inputflinger/dispatcher/InputState.h51
-rw-r--r--services/inputflinger/dispatcher/InputTarget.cpp67
-rw-r--r--services/inputflinger/dispatcher/InputTarget.h71
-rw-r--r--services/inputflinger/dispatcher/LatencyAggregator.cpp5
-rw-r--r--services/inputflinger/dispatcher/LatencyAggregator.h20
-rw-r--r--services/inputflinger/dispatcher/LatencyTracker.cpp37
-rw-r--r--services/inputflinger/dispatcher/LatencyTracker.h15
-rw-r--r--services/inputflinger/dispatcher/Monitor.cpp2
-rw-r--r--services/inputflinger/dispatcher/Monitor.h10
-rw-r--r--services/inputflinger/dispatcher/TouchState.cpp219
-rw-r--r--services/inputflinger/dispatcher/TouchState.h60
-rw-r--r--services/inputflinger/dispatcher/TouchedWindow.cpp263
-rw-r--r--services/inputflinger/dispatcher/TouchedWindow.h74
-rw-r--r--services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h5
-rw-r--r--services/inputflinger/dispatcher/include/InputDispatcherFactory.h7
-rw-r--r--services/inputflinger/dispatcher/include/InputDispatcherInterface.h54
-rw-r--r--services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h60
-rw-r--r--services/inputflinger/docs/input_coordinates.md113
-rw-r--r--services/inputflinger/host/Android.bp80
-rw-r--r--services/inputflinger/host/InputDriver.cpp414
-rw-r--r--services/inputflinger/host/InputDriver.h195
-rw-r--r--services/inputflinger/host/InputFlinger.cpp69
-rw-r--r--services/inputflinger/host/InputFlinger.h62
-rw-r--r--services/inputflinger/host/InputHost.h58
-rw-r--r--services/inputflinger/host/inputflinger.rc5
-rw-r--r--services/inputflinger/include/InputListener.h287
-rw-r--r--services/inputflinger/include/InputReaderBase.h316
-rw-r--r--services/inputflinger/include/InputThread.h5
-rw-r--r--services/inputflinger/include/KeyCodeClassifications.h55
-rw-r--r--services/inputflinger/include/NotifyArgs.h243
-rw-r--r--services/inputflinger/include/NotifyArgsBuilders.h236
-rw-r--r--services/inputflinger/include/PointerChoreographerPolicyInterface.h53
-rw-r--r--services/inputflinger/include/PointerControllerInterface.h68
-rw-r--r--services/inputflinger/include/UnwantedInteractionBlockerInterface.h12
-rw-r--r--services/inputflinger/include/VibrationElement.h5
-rw-r--r--services/inputflinger/reader/Android.bp72
-rw-r--r--services/inputflinger/reader/EventHub.cpp1032
-rw-r--r--services/inputflinger/reader/InputDevice.cpp428
-rw-r--r--services/inputflinger/reader/InputReader.cpp229
-rw-r--r--services/inputflinger/reader/Macros.cpp (renamed from services/inputflinger/host/InputHost.cpp)35
-rw-r--r--services/inputflinger/reader/Macros.h87
-rw-r--r--services/inputflinger/reader/TouchVideoDevice.cpp5
-rw-r--r--services/inputflinger/reader/controller/PeripheralController.cpp90
-rw-r--r--services/inputflinger/reader/controller/PeripheralController.h32
-rw-r--r--services/inputflinger/reader/controller/PeripheralControllerInterface.h5
-rw-r--r--services/inputflinger/reader/include/EventHub.h238
-rw-r--r--services/inputflinger/reader/include/InputDevice.h123
-rw-r--r--services/inputflinger/reader/include/InputReader.h69
-rw-r--r--services/inputflinger/reader/include/InputReaderContext.h16
-rw-r--r--services/inputflinger/reader/include/StylusState.h35
-rw-r--r--services/inputflinger/reader/include/TouchVideoDevice.h5
-rw-r--r--services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp328
-rw-r--r--services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h83
-rw-r--r--services/inputflinger/reader/mapper/CursorInputMapper.cpp460
-rw-r--r--services/inputflinger/reader/mapper/CursorInputMapper.h39
-rw-r--r--services/inputflinger/reader/mapper/ExternalStylusInputMapper.cpp52
-rw-r--r--services/inputflinger/reader/mapper/ExternalStylusInputMapper.h29
-rw-r--r--services/inputflinger/reader/mapper/InputMapper.cpp57
-rw-r--r--services/inputflinger/reader/mapper/InputMapper.h64
-rw-r--r--services/inputflinger/reader/mapper/JoystickInputMapper.cpp63
-rw-r--r--services/inputflinger/reader/mapper/JoystickInputMapper.h28
-rw-r--r--services/inputflinger/reader/mapper/KeyboardInputMapper.cpp376
-rw-r--r--services/inputflinger/reader/mapper/KeyboardInputMapper.h114
-rw-r--r--services/inputflinger/reader/mapper/MultiTouchInputMapper.cpp298
-rw-r--r--services/inputflinger/reader/mapper/MultiTouchInputMapper.h108
-rw-r--r--services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp121
-rw-r--r--services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h31
-rw-r--r--services/inputflinger/reader/mapper/SensorInputMapper.cpp102
-rw-r--r--services/inputflinger/reader/mapper/SensorInputMapper.h31
-rw-r--r--services/inputflinger/reader/mapper/SingleTouchInputMapper.cpp20
-rw-r--r--services/inputflinger/reader/mapper/SingleTouchInputMapper.h17
-rw-r--r--services/inputflinger/reader/mapper/SlopController.cpp79
-rw-r--r--services/inputflinger/reader/mapper/SlopController.h54
-rw-r--r--services/inputflinger/reader/mapper/SwitchInputMapper.cpp20
-rw-r--r--services/inputflinger/reader/mapper/SwitchInputMapper.h16
-rw-r--r--services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.cpp127
-rw-r--r--services/inputflinger/reader/mapper/TouchCursorInputMapperCommon.h94
-rw-r--r--services/inputflinger/reader/mapper/TouchInputMapper.cpp3385
-rw-r--r--services/inputflinger/reader/mapper/TouchInputMapper.h494
-rw-r--r--services/inputflinger/reader/mapper/TouchpadInputMapper.cpp529
-rw-r--r--services/inputflinger/reader/mapper/TouchpadInputMapper.h118
-rw-r--r--services/inputflinger/reader/mapper/VibratorInputMapper.cpp49
-rw-r--r--services/inputflinger/reader/mapper/VibratorInputMapper.h27
-rw-r--r--services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.cpp2
-rw-r--r--services/inputflinger/reader/mapper/accumulator/CursorButtonAccumulator.h15
-rw-r--r--services/inputflinger/reader/mapper/accumulator/CursorScrollAccumulator.h5
-rw-r--r--services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp (renamed from libs/gui/include/gui/TransactionTracing.h)40
-rw-r--r--services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h (renamed from libs/renderengine/gl/GLShadowTexture.h)28
-rw-r--r--services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp185
-rw-r--r--services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h97
-rw-r--r--services/inputflinger/reader/mapper/accumulator/SingleTouchMotionAccumulator.h5
-rw-r--r--services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.cpp128
-rw-r--r--services/inputflinger/reader/mapper/accumulator/TouchButtonAccumulator.h65
-rw-r--r--services/inputflinger/reader/mapper/gestures/GestureConverter.cpp605
-rw-r--r--services/inputflinger/reader/mapper/gestures/GestureConverter.h124
-rw-r--r--services/inputflinger/reader/mapper/gestures/GesturesLogging.cpp41
-rw-r--r--services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp80
-rw-r--r--services/inputflinger/reader/mapper/gestures/HardwareProperties.h (renamed from libs/renderengine/include/renderengine/Image.h)16
-rw-r--r--services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp126
-rw-r--r--services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h60
-rw-r--r--services/inputflinger/reader/mapper/gestures/PropertyProvider.cpp311
-rw-r--r--services/inputflinger/reader/mapper/gestures/PropertyProvider.h106
-rw-r--r--services/inputflinger/reader/mapper/gestures/TimerProvider.cpp151
-rw-r--r--services/inputflinger/reader/mapper/gestures/TimerProvider.h88
-rw-r--r--services/inputflinger/reporter/Android.bp4
-rw-r--r--services/inputflinger/reporter/InputReporter.cpp2
-rw-r--r--services/inputflinger/reporter/InputReporterInterface.h5
-rw-r--r--services/inputflinger/rust/Android.bp71
-rw-r--r--services/inputflinger/rust/bounce_keys_filter.rs289
-rw-r--r--services/inputflinger/rust/ffi/InputFlingerBootstrap.h21
-rw-r--r--services/inputflinger/rust/input_filter.rs274
-rw-r--r--services/inputflinger/rust/lib.rs123
-rw-r--r--services/inputflinger/tests/Android.bp49
-rw-r--r--services/inputflinger/tests/AnrTracker_test.cpp22
-rw-r--r--services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp955
-rw-r--r--services/inputflinger/tests/CursorInputMapper_test.cpp216
-rw-r--r--services/inputflinger/tests/EventHub_test.cpp16
-rw-r--r--services/inputflinger/tests/FakeApplicationHandle.h48
-rw-r--r--services/inputflinger/tests/FakeEventHub.cpp628
-rw-r--r--services/inputflinger/tests/FakeEventHub.h224
-rw-r--r--services/inputflinger/tests/FakeInputDispatcherPolicy.h91
-rw-r--r--services/inputflinger/tests/FakeInputReaderPolicy.cpp277
-rw-r--r--services/inputflinger/tests/FakeInputReaderPolicy.h111
-rw-r--r--services/inputflinger/tests/FakePointerController.cpp148
-rw-r--r--services/inputflinger/tests/FakePointerController.h77
-rw-r--r--services/inputflinger/tests/FakeWindowHandle.h274
-rw-r--r--services/inputflinger/tests/FocusResolver_test.cpp205
-rw-r--r--services/inputflinger/tests/GestureConverter_test.cpp2486
-rw-r--r--services/inputflinger/tests/HardwareProperties_test.cpp165
-rw-r--r--services/inputflinger/tests/HardwareStateConverter_test.cpp349
-rw-r--r--services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp530
-rw-r--r--services/inputflinger/tests/InputDeviceMetricsSource_test.cpp296
-rw-r--r--services/inputflinger/tests/InputDispatcher_test.cpp7495
-rw-r--r--services/inputflinger/tests/InputFlingerService_test.cpp243
-rw-r--r--services/inputflinger/tests/InputMapperTest.cpp247
-rw-r--r--services/inputflinger/tests/InputMapperTest.h142
-rw-r--r--services/inputflinger/tests/InputProcessorConverter_test.cpp (renamed from services/inputflinger/tests/InputClassifierConverter_test.cpp)24
-rw-r--r--services/inputflinger/tests/InputProcessor_test.cpp (renamed from services/inputflinger/tests/InputClassifier_test.cpp)119
-rw-r--r--services/inputflinger/tests/InputReader_test.cpp6547
-rw-r--r--services/inputflinger/tests/InstrumentedInputReader.cpp50
-rw-r--r--services/inputflinger/tests/InstrumentedInputReader.h131
-rw-r--r--services/inputflinger/tests/InterfaceMocks.h179
-rw-r--r--services/inputflinger/tests/KeyboardInputMapper_test.cpp176
-rw-r--r--services/inputflinger/tests/LatencyTracker_test.cpp150
-rw-r--r--services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp87
-rw-r--r--services/inputflinger/tests/NotifyArgs_test.cpp85
-rw-r--r--services/inputflinger/tests/PointerChoreographer_test.cpp1548
-rw-r--r--services/inputflinger/tests/PreferStylusOverTouch_test.cpp165
-rw-r--r--services/inputflinger/tests/PropertyProvider_test.cpp351
-rw-r--r--services/inputflinger/tests/SlopController_test.cpp99
-rw-r--r--services/inputflinger/tests/SyncQueue_test.cpp92
-rw-r--r--services/inputflinger/tests/TestConstants.h (renamed from libs/renderengine/include/renderengine/mock/Framebuffer.h)27
-rw-r--r--services/inputflinger/tests/TestEventMatchers.h699
-rw-r--r--services/inputflinger/tests/TestInputListener.cpp101
-rw-r--r--services/inputflinger/tests/TestInputListener.h51
-rw-r--r--services/inputflinger/tests/TimerProvider_test.cpp311
-rw-r--r--services/inputflinger/tests/TouchpadInputMapper_test.cpp238
-rw-r--r--services/inputflinger/tests/UinputDevice.cpp81
-rw-r--r--services/inputflinger/tests/UinputDevice.h97
-rw-r--r--services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp402
-rw-r--r--services/inputflinger/tests/fuzzers/Android.bp162
-rw-r--r--services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp79
-rw-r--r--services/inputflinger/tests/fuzzers/CursorInputFuzzer.cpp106
-rw-r--r--services/inputflinger/tests/fuzzers/FuzzedInputStream.h204
-rw-r--r--services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp95
-rw-r--r--services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp197
-rw-r--r--services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp298
-rw-r--r--services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp111
-rw-r--r--services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp13
-rw-r--r--services/inputflinger/tests/fuzzers/MapperHelpers.h406
-rw-r--r--services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp144
-rw-r--r--services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp59
-rw-r--r--services/inputflinger/tests/fuzzers/ThreadSafeFuzzedDataProvider.h136
-rw-r--r--services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp185
-rw-r--r--services/memtrackproxy/MemtrackProxy.cpp11
-rw-r--r--services/powermanager/Android.bp14
-rw-r--r--services/powermanager/PowerHalController.cpp41
-rw-r--r--services/powermanager/PowerHalLoader.cpp50
-rw-r--r--services/powermanager/PowerHalWrapper.cpp166
-rw-r--r--services/powermanager/WorkDuration.cpp51
-rw-r--r--services/powermanager/benchmarks/Android.bp5
-rw-r--r--services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp62
-rw-r--r--services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp8
-rw-r--r--services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp8
-rw-r--r--services/powermanager/include/android/WorkDuration.h71
-rw-r--r--services/powermanager/tests/Android.bp11
-rw-r--r--services/powermanager/tests/PowerHalControllerTest.cpp10
-rw-r--r--services/powermanager/tests/PowerHalLoaderTest.cpp46
-rw-r--r--services/powermanager/tests/PowerHalWrapperAidlTest.cpp104
-rw-r--r--services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp38
-rw-r--r--services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp66
-rw-r--r--services/powermanager/tests/PowerHalWrapperHidlV1_2Test.cpp200
-rw-r--r--services/powermanager/tests/PowerHalWrapperHidlV1_3Test.cpp210
-rw-r--r--services/sensorservice/AidlSensorHalWrapper.cpp8
-rw-r--r--services/sensorservice/Android.bp10
-rw-r--r--services/sensorservice/BatteryService.cpp12
-rw-r--r--services/sensorservice/BatteryService.h14
-rw-r--r--services/sensorservice/RecentEventLogger.cpp2
-rw-r--r--services/sensorservice/SensorDevice.cpp69
-rw-r--r--services/sensorservice/SensorDevice.h9
-rw-r--r--services/sensorservice/SensorDirectConnection.cpp46
-rw-r--r--services/sensorservice/SensorDirectConnection.h7
-rw-r--r--services/sensorservice/SensorEventConnection.cpp45
-rw-r--r--services/sensorservice/SensorFusion.cpp36
-rw-r--r--services/sensorservice/SensorInterface.cpp39
-rw-r--r--services/sensorservice/SensorInterface.h26
-rw-r--r--services/sensorservice/SensorList.cpp56
-rw-r--r--services/sensorservice/SensorList.h19
-rw-r--r--services/sensorservice/SensorRegistrationInfo.h2
-rw-r--r--services/sensorservice/SensorService.cpp644
-rw-r--r--services/sensorservice/SensorService.h91
-rw-r--r--services/sensorservice/SensorServiceUtils.cpp6
-rw-r--r--services/sensorservice/SensorServiceUtils.h5
-rw-r--r--services/sensorservice/aidl/Android.bp2
-rw-r--r--services/sensorservice/aidl/DirectReportChannel.cpp2
-rw-r--r--services/sensorservice/aidl/SensorManager.cpp22
-rw-r--r--services/sensorservice/aidl/fuzzer/Android.bp5
-rw-r--r--services/sensorservice/aidl/fuzzer/fuzzer.cpp4
-rw-r--r--services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h2
-rw-r--r--services/sensorservice/hidl/SensorManager.cpp11
-rw-r--r--services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h2
-rw-r--r--services/sensorservice/hidl/utils.cpp4
-rw-r--r--services/sensorservice/tests/sensorservicetest.cpp17
-rw-r--r--services/stats/Android.bp1
-rw-r--r--services/stats/StatsAidl.cpp18
-rw-r--r--services/surfaceflinger/Android.bp74
-rw-r--r--services/surfaceflinger/BackgroundExecutor.cpp50
-rw-r--r--services/surfaceflinger/BackgroundExecutor.h21
-rw-r--r--services/surfaceflinger/BufferLayer.cpp814
-rw-r--r--services/surfaceflinger/BufferLayer.h222
-rw-r--r--services/surfaceflinger/BufferLayerConsumer.cpp500
-rw-r--r--services/surfaceflinger/BufferLayerConsumer.h354
-rw-r--r--services/surfaceflinger/BufferQueueLayer.cpp575
-rw-r--r--services/surfaceflinger/BufferQueueLayer.h154
-rw-r--r--services/surfaceflinger/BufferStateLayer.cpp1085
-rw-r--r--services/surfaceflinger/BufferStateLayer.h211
-rw-r--r--services/surfaceflinger/Client.cpp132
-rw-r--r--services/surfaceflinger/Client.h41
-rw-r--r--services/surfaceflinger/ClientCache.cpp50
-rw-r--r--services/surfaceflinger/ClientCache.h23
-rw-r--r--services/surfaceflinger/CompositionEngine/Android.bp70
-rw-r--r--services/surfaceflinger/CompositionEngine/AndroidTest.xml33
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h9
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h41
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h6
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h9
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h2
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h30
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h49
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h59
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h10
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h33
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h9
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h9
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/CompositionEngine.h10
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h5
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h1
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h86
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h36
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h22
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h6
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayerCompositionState.h14
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h6
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h3
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/LayerState.h35
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/CompositionEngine.h6
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h2
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h2
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h15
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h25
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h6
-rw-r--r--services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h4
-rw-r--r--services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp6
-rw-r--r--services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp83
-rw-r--r--services/surfaceflinger/CompositionEngine/src/Display.cpp76
-rw-r--r--services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp26
-rw-r--r--services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp82
-rw-r--r--services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp8
-rw-r--r--services/surfaceflinger/CompositionEngine/src/Output.cpp431
-rw-r--r--services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp11
-rw-r--r--services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp139
-rw-r--r--services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp17
-rw-r--r--services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp116
-rw-r--r--services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp20
-rw-r--r--services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp7
-rw-r--r--services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp30
-rw-r--r--services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp4
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp266
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp57
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp97
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp200
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/MockHWC2.h1
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h35
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h32
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/OutputLayerTest.cpp214
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp1116
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp59
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp302
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp259
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp82
-rw-r--r--services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp10
-rw-r--r--services/surfaceflinger/ContainerLayer.cpp47
-rw-r--r--services/surfaceflinger/Display/DisplayModeRequest.h36
-rw-r--r--services/surfaceflinger/Display/DisplaySnapshot.cpp78
-rw-r--r--services/surfaceflinger/Display/DisplaySnapshot.h62
-rw-r--r--services/surfaceflinger/Display/PhysicalDisplay.h76
-rw-r--r--services/surfaceflinger/DisplayDevice.cpp389
-rw-r--r--services/surfaceflinger/DisplayDevice.h191
-rw-r--r--services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp802
-rw-r--r--services/surfaceflinger/DisplayHardware/AidlComposerHal.h83
-rw-r--r--services/surfaceflinger/DisplayHardware/ComposerHal.h44
-rw-r--r--services/surfaceflinger/DisplayHardware/DisplayMode.h49
-rw-r--r--services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp48
-rw-r--r--services/surfaceflinger/DisplayHardware/FramebufferSurface.h16
-rw-r--r--services/surfaceflinger/DisplayHardware/HWC2.cpp68
-rw-r--r--services/surfaceflinger/DisplayHardware/HWC2.h48
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.cpp263
-rw-r--r--services/surfaceflinger/DisplayHardware/HWComposer.h91
-rw-r--r--services/surfaceflinger/DisplayHardware/Hal.h50
-rw-r--r--services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp132
-rw-r--r--services/surfaceflinger/DisplayHardware/HidlComposerHal.h41
-rw-r--r--services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp743
-rw-r--r--services/surfaceflinger/DisplayHardware/PowerAdvisor.h287
-rw-r--r--services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.cpp26
-rw-r--r--services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h10
-rw-r--r--services/surfaceflinger/DisplayRenderArea.cpp60
-rw-r--r--services/surfaceflinger/DisplayRenderArea.h10
-rw-r--r--services/surfaceflinger/EffectLayer.cpp163
-rw-r--r--services/surfaceflinger/EffectLayer.h70
-rw-r--r--services/surfaceflinger/FlagManager.cpp110
-rw-r--r--services/surfaceflinger/FlagManager.h48
-rw-r--r--services/surfaceflinger/FpsReporter.cpp9
-rw-r--r--services/surfaceflinger/FrameTimeline/Android.bp1
-rw-r--r--services/surfaceflinger/FrameTimeline/FrameTimeline.cpp217
-rw-r--r--services/surfaceflinger/FrameTimeline/FrameTimeline.h32
-rw-r--r--services/surfaceflinger/FrameTracker.cpp3
-rw-r--r--services/surfaceflinger/FrontEnd/DisplayInfo.h52
-rw-r--r--services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp95
-rw-r--r--services/surfaceflinger/FrontEnd/LayerCreationArgs.h66
-rw-r--r--services/surfaceflinger/FrontEnd/LayerHandle.cpp62
-rw-r--r--services/surfaceflinger/FrontEnd/LayerHandle.h58
-rw-r--r--services/surfaceflinger/FrontEnd/LayerHierarchy.cpp501
-rw-r--r--services/surfaceflinger/FrontEnd/LayerHierarchy.h226
-rw-r--r--services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp447
-rw-r--r--services/surfaceflinger/FrontEnd/LayerLifecycleManager.h122
-rw-r--r--services/surfaceflinger/FrontEnd/LayerLog.h45
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshot.cpp508
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshot.h153
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp1235
-rw-r--r--services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.h140
-rw-r--r--services/surfaceflinger/FrontEnd/RequestedLayerState.cpp631
-rw-r--r--services/surfaceflinger/FrontEnd/RequestedLayerState.h134
-rw-r--r--services/surfaceflinger/FrontEnd/SwapErase.h48
-rw-r--r--services/surfaceflinger/FrontEnd/TransactionHandler.cpp224
-rw-r--r--services/surfaceflinger/FrontEnd/TransactionHandler.h100
-rw-r--r--services/surfaceflinger/FrontEnd/Update.h52
-rw-r--r--services/surfaceflinger/FrontEnd/readme.md110
-rw-r--r--services/surfaceflinger/HdrLayerInfoReporter.cpp24
-rw-r--r--services/surfaceflinger/HdrLayerInfoReporter.h33
-rw-r--r--services/surfaceflinger/HdrSdrRatioOverlay.cpp202
-rw-r--r--services/surfaceflinger/HdrSdrRatioOverlay.h57
-rw-r--r--services/surfaceflinger/Layer.cpp2942
-rw-r--r--services/surfaceflinger/Layer.h756
-rw-r--r--services/surfaceflinger/LayerFE.cpp391
-rw-r--r--services/surfaceflinger/LayerFE.h84
-rw-r--r--services/surfaceflinger/LayerProtoHelper.cpp290
-rw-r--r--services/surfaceflinger/LayerProtoHelper.h95
-rw-r--r--services/surfaceflinger/LayerRejecter.cpp143
-rw-r--r--services/surfaceflinger/LayerRejecter.h41
-rw-r--r--services/surfaceflinger/LayerRenderArea.cpp52
-rw-r--r--services/surfaceflinger/LayerRenderArea.h12
-rw-r--r--services/surfaceflinger/LocklessQueue.h82
-rw-r--r--services/surfaceflinger/MonitoredProducer.cpp170
-rw-r--r--services/surfaceflinger/MonitoredProducer.h89
-rw-r--r--services/surfaceflinger/MutexUtils.h10
-rw-r--r--services/surfaceflinger/NativeWindowSurface.cpp2
-rw-r--r--services/surfaceflinger/OWNERS8
-rw-r--r--services/surfaceflinger/RefreshRateOverlay.cpp302
-rw-r--r--services/surfaceflinger/RefreshRateOverlay.h65
-rw-r--r--services/surfaceflinger/RegionSamplingThread.cpp136
-rw-r--r--services/surfaceflinger/RegionSamplingThread.h6
-rw-r--r--services/surfaceflinger/RenderArea.h46
-rw-r--r--services/surfaceflinger/Scheduler/Android.bp13
-rw-r--r--services/surfaceflinger/Scheduler/DispSyncSource.cpp202
-rw-r--r--services/surfaceflinger/Scheduler/DispSyncSource.h69
-rw-r--r--services/surfaceflinger/Scheduler/EventThread.cpp329
-rw-r--r--services/surfaceflinger/Scheduler/EventThread.h118
-rw-r--r--services/surfaceflinger/Scheduler/FrameRateCompatibility.h40
-rw-r--r--services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp57
-rw-r--r--services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h15
-rw-r--r--services/surfaceflinger/Scheduler/ISchedulerCallback.h (renamed from services/surfaceflinger/ContainerLayer.h)31
-rw-r--r--services/surfaceflinger/Scheduler/InjectVSyncSource.h57
-rw-r--r--services/surfaceflinger/Scheduler/LayerHistory.cpp242
-rw-r--r--services/surfaceflinger/Scheduler/LayerHistory.h43
-rw-r--r--services/surfaceflinger/Scheduler/LayerInfo.cpp320
-rw-r--r--services/surfaceflinger/Scheduler/LayerInfo.h170
-rw-r--r--services/surfaceflinger/Scheduler/MessageQueue.cpp166
-rw-r--r--services/surfaceflinger/Scheduler/MessageQueue.h57
-rw-r--r--services/surfaceflinger/Scheduler/OneShotTimer.cpp6
-rw-r--r--services/surfaceflinger/Scheduler/OneShotTimer.h9
-rw-r--r--services/surfaceflinger/Scheduler/RefreshRateConfigs.cpp1024
-rw-r--r--services/surfaceflinger/Scheduler/RefreshRateSelector.cpp1530
-rw-r--r--services/surfaceflinger/Scheduler/RefreshRateSelector.h (renamed from services/surfaceflinger/Scheduler/RefreshRateConfigs.h)351
-rw-r--r--services/surfaceflinger/Scheduler/RefreshRateStats.h20
-rw-r--r--services/surfaceflinger/Scheduler/Scheduler.cpp1113
-rw-r--r--services/surfaceflinger/Scheduler/Scheduler.h455
-rw-r--r--services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp47
-rw-r--r--services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h39
-rw-r--r--services/surfaceflinger/Scheduler/VSyncDispatch.h28
-rw-r--r--services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp166
-rw-r--r--services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h15
-rw-r--r--services/surfaceflinger/Scheduler/VSyncPredictor.cpp343
-rw-r--r--services/surfaceflinger/Scheduler/VSyncPredictor.h68
-rw-r--r--services/surfaceflinger/Scheduler/VSyncReactor.cpp60
-rw-r--r--services/surfaceflinger/Scheduler/VSyncReactor.h13
-rw-r--r--services/surfaceflinger/Scheduler/VSyncTracker.h41
-rw-r--r--services/surfaceflinger/Scheduler/VsyncConfiguration.cpp12
-rw-r--r--services/surfaceflinger/Scheduler/VsyncConfiguration.h14
-rw-r--r--services/surfaceflinger/Scheduler/VsyncController.h11
-rw-r--r--services/surfaceflinger/Scheduler/VsyncModulator.cpp18
-rw-r--r--services/surfaceflinger/Scheduler/VsyncModulator.h49
-rw-r--r--services/surfaceflinger/Scheduler/VsyncSchedule.cpp151
-rw-r--r--services/surfaceflinger/Scheduler/VsyncSchedule.h127
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/Features.h10
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/Fps.h59
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.h43
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h150
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h32
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/PresentLatencyTracker.h54
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/Time.h88
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/Timer.h2
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h30
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/VsyncConfig.h60
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/VsyncId.h (renamed from libs/renderengine/include/renderengine/Framebuffer.h)19
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h (renamed from libs/renderengine/mock/Image.cpp)22
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h49
-rw-r--r--services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h57
-rw-r--r--services/surfaceflinger/Scheduler/src/FrameTargeter.cpp155
-rw-r--r--services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp56
-rw-r--r--services/surfaceflinger/Scheduler/src/Timer.cpp14
-rw-r--r--services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp338
-rw-r--r--services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp72
-rw-r--r--services/surfaceflinger/ScreenCaptureOutput.cpp132
-rw-r--r--services/surfaceflinger/ScreenCaptureOutput.h74
-rw-r--r--services/surfaceflinger/ScreenCaptureRenderSurface.h81
-rw-r--r--services/surfaceflinger/SurfaceFlinger.cpp8099
-rw-r--r--services/surfaceflinger/SurfaceFlinger.h1166
-rw-r--r--services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp46
-rw-r--r--services/surfaceflinger/SurfaceFlingerDefaultFactory.h14
-rw-r--r--services/surfaceflinger/SurfaceFlingerFactory.cpp2
-rw-r--r--services/surfaceflinger/SurfaceFlingerFactory.h23
-rw-r--r--services/surfaceflinger/SurfaceFlingerProperties.cpp16
-rw-r--r--services/surfaceflinger/SurfaceFlingerProperties.h7
-rw-r--r--services/surfaceflinger/SurfaceInterceptor.cpp719
-rw-r--r--services/surfaceflinger/SurfaceInterceptor.h211
-rw-r--r--services/surfaceflinger/TEST_MAPPING37
-rw-r--r--services/surfaceflinger/TimeStats/Android.bp36
-rw-r--r--services/surfaceflinger/TimeStats/TimeStats.cpp42
-rw-r--r--services/surfaceflinger/TimeStats/TimeStats.h15
-rw-r--r--services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto7
-rw-r--r--services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp2
-rw-r--r--services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h4
-rw-r--r--services/surfaceflinger/TracedOrdinal.h28
-rw-r--r--services/surfaceflinger/Tracing/LayerDataSource.cpp100
-rw-r--r--services/surfaceflinger/Tracing/LayerDataSource.h77
-rw-r--r--services/surfaceflinger/Tracing/LayerTracing.cpp247
-rw-r--r--services/surfaceflinger/Tracing/LayerTracing.h124
-rw-r--r--services/surfaceflinger/Tracing/TransactionDataSource.cpp82
-rw-r--r--services/surfaceflinger/Tracing/TransactionDataSource.h74
-rw-r--r--services/surfaceflinger/Tracing/TransactionProtoParser.cpp351
-rw-r--r--services/surfaceflinger/Tracing/TransactionProtoParser.h92
-rw-r--r--services/surfaceflinger/Tracing/TransactionRingBuffer.h (renamed from services/surfaceflinger/Tracing/RingBuffer.h)14
-rw-r--r--services/surfaceflinger/Tracing/TransactionTracing.cpp498
-rw-r--r--services/surfaceflinger/Tracing/TransactionTracing.h193
-rw-r--r--services/surfaceflinger/Tracing/tools/Android.bp15
-rw-r--r--services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp344
-rw-r--r--services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h13
-rw-r--r--services/surfaceflinger/Tracing/tools/main.cpp36
-rw-r--r--services/surfaceflinger/Tracing/tools/run.sh14
-rw-r--r--services/surfaceflinger/TransactionCallbackInvoker.cpp18
-rw-r--r--services/surfaceflinger/TransactionCallbackInvoker.h11
-rw-r--r--services/surfaceflinger/TransactionState.h112
-rw-r--r--services/surfaceflinger/TunnelModeEnabledReporter.cpp2
-rw-r--r--services/surfaceflinger/Utils/Dumper.h119
-rw-r--r--services/surfaceflinger/Utils/OverlayUtils.h146
-rw-r--r--services/surfaceflinger/Utils/RingBuffer.h68
-rw-r--r--services/surfaceflinger/WindowInfosListenerInvoker.cpp207
-rw-r--r--services/surfaceflinger/WindowInfosListenerInvoker.h63
-rw-r--r--services/surfaceflinger/common/Android.bp48
-rw-r--r--services/surfaceflinger/common/FlagManager.cpp217
-rw-r--r--services/surfaceflinger/common/include/common/FlagManager.h91
-rw-r--r--services/surfaceflinger/common/include/common/test/FlagUtils.h (renamed from libs/renderengine/gl/GLVertexBuffer.h)35
-rw-r--r--services/surfaceflinger/fuzzer/Android.bp40
-rw-r--r--services/surfaceflinger/fuzzer/README.md19
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer.cpp61
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h9
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp147
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp20
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h345
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp60
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp246
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.h35
-rw-r--r--services/surfaceflinger/fuzzer/surfaceflinger_service_fuzzer.cpp32
-rw-r--r--services/surfaceflinger/layerproto/Android.bp37
-rw-r--r--services/surfaceflinger/layerproto/LayerProtoParser.cpp29
-rw-r--r--services/surfaceflinger/layerproto/common.proto91
-rw-r--r--services/surfaceflinger/layerproto/display.proto38
-rw-r--r--services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h7
-rw-r--r--services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h23
-rw-r--r--services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h6
-rw-r--r--services/surfaceflinger/layerproto/jarjar-rules.txt1
-rw-r--r--services/surfaceflinger/layerproto/layers.proto169
-rw-r--r--services/surfaceflinger/layerproto/layerstrace.proto63
-rw-r--r--services/surfaceflinger/layerproto/transactions.proto283
-rw-r--r--services/surfaceflinger/main_surfaceflinger.cpp12
-rw-r--r--services/surfaceflinger/surfaceflinger_flags.aconfig142
-rw-r--r--services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop42
-rw-r--r--services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt14
-rw-r--r--services/surfaceflinger/tests/Android.bp20
-rw-r--r--services/surfaceflinger/tests/Binder_test.cpp225
-rw-r--r--services/surfaceflinger/tests/BootDisplayMode_test.cpp108
-rw-r--r--services/surfaceflinger/tests/BufferGenerator.cpp9
-rw-r--r--services/surfaceflinger/tests/CommonTypes_test.cpp200
-rw-r--r--services/surfaceflinger/tests/Credentials_test.cpp169
-rw-r--r--services/surfaceflinger/tests/DisplayConfigs_test.cpp126
-rw-r--r--services/surfaceflinger/tests/DisplayEventReceiver_test.cpp12
-rw-r--r--services/surfaceflinger/tests/EffectLayer_test.cpp33
-rw-r--r--services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp89
-rw-r--r--services/surfaceflinger/tests/IPC_test.cpp10
-rw-r--r--services/surfaceflinger/tests/InvalidHandles_test.cpp6
-rw-r--r--services/surfaceflinger/tests/LayerBorder_test.cpp287
-rw-r--r--services/surfaceflinger/tests/LayerCallback_test.cpp156
-rw-r--r--services/surfaceflinger/tests/LayerRenderTypeTransaction_test.cpp154
-rw-r--r--services/surfaceflinger/tests/LayerState_test.cpp50
-rw-r--r--services/surfaceflinger/tests/LayerTransactionTest.h48
-rw-r--r--services/surfaceflinger/tests/LayerTransaction_test.cpp63
-rw-r--r--services/surfaceflinger/tests/LayerTrustedPresentationListener_test.cpp287
-rw-r--r--services/surfaceflinger/tests/LayerTypeAndRenderTypeTransaction_test.cpp4
-rw-r--r--services/surfaceflinger/tests/LayerTypeTransaction_test.cpp8
-rw-r--r--services/surfaceflinger/tests/LayerUpdate_test.cpp4
-rw-r--r--services/surfaceflinger/tests/MirrorLayer_test.cpp49
-rw-r--r--services/surfaceflinger/tests/MultiDisplayLayerBounds_test.cpp4
-rw-r--r--services/surfaceflinger/tests/OWNERS8
-rw-r--r--services/surfaceflinger/tests/RelativeZ_test.cpp4
-rw-r--r--services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp46
-rw-r--r--services/surfaceflinger/tests/ScreenCapture_test.cpp367
-rw-r--r--services/surfaceflinger/tests/SetFrameRateOverride_test.cpp98
-rw-r--r--services/surfaceflinger/tests/Stress_test.cpp10
-rw-r--r--services/surfaceflinger/tests/SurfaceInterceptor_test.cpp967
-rw-r--r--services/surfaceflinger/tests/TextureFiltering_test.cpp216
-rw-r--r--services/surfaceflinger/tests/TransactionTestHarnesses.h15
-rw-r--r--services/surfaceflinger/tests/VirtualDisplay_test.cpp4
-rw-r--r--services/surfaceflinger/tests/WindowInfosListener_test.cpp101
-rw-r--r--services/surfaceflinger/tests/fakehwc/Android.bp63
-rw-r--r--services/surfaceflinger/tests/fakehwc/FakeComposerClient.cpp927
-rw-r--r--services/surfaceflinger/tests/fakehwc/FakeComposerClient.h301
-rw-r--r--services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp179
-rw-r--r--services/surfaceflinger/tests/fakehwc/FakeComposerService.h92
-rw-r--r--services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp193
-rw-r--r--services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h115
-rw-r--r--services/surfaceflinger/tests/fakehwc/MockComposerHal.h47
-rw-r--r--services/surfaceflinger/tests/fakehwc/RenderState.h42
-rw-r--r--services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp1790
-rw-r--r--services/surfaceflinger/tests/tracing/Android.bp5
-rw-r--r--services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp178
-rw-r--r--services/surfaceflinger/tests/tracing/readme.md4
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscopebin0 -> 46969 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscopebin0 -> 49087 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscopebin25386 -> 24186 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscopebin0 -> 24243 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscopebin0 -> 552698 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscopebin0 -> 209854 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscopebin25740 -> 354133 bytes
-rw-r--r--services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscopebin0 -> 383550 bytes
-rw-r--r--services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp148
-rw-r--r--services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp283
-rw-r--r--services/surfaceflinger/tests/unittests/Android.bp68
-rw-r--r--services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp57
-rw-r--r--services/surfaceflinger/tests/unittests/CachingTest.cpp94
-rw-r--r--services/surfaceflinger/tests/unittests/CommitTest.cpp59
-rw-r--r--services/surfaceflinger/tests/unittests/CompositionTest.cpp256
-rw-r--r--services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp315
-rw-r--r--services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp141
-rw-r--r--services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp18
-rw-r--r--services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp85
-rw-r--r--services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h148
-rw-r--r--services/surfaceflinger/tests/unittests/EventThreadTest.cpp497
-rw-r--r--services/surfaceflinger/tests/unittests/FakeDisplayInjector.h96
-rw-r--r--services/surfaceflinger/tests/unittests/FlagManagerTest.cpp210
-rw-r--r--services/surfaceflinger/tests/unittests/FpsReporterTest.cpp51
-rw-r--r--services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp8
-rw-r--r--services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp (renamed from services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp)56
-rw-r--r--services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp224
-rw-r--r--services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp825
-rw-r--r--services/surfaceflinger/tests/unittests/FrameTracerTest.cpp164
-rw-r--r--services/surfaceflinger/tests/unittests/GameModeTest.cpp62
-rw-r--r--services/surfaceflinger/tests/unittests/HWComposerTest.cpp385
-rw-r--r--services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp757
-rw-r--r--services/surfaceflinger/tests/unittests/LayerHierarchyTest.h541
-rw-r--r--services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp1096
-rw-r--r--services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp760
-rw-r--r--services/surfaceflinger/tests/unittests/LayerInfoTest.cpp110
-rw-r--r--services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp563
-rw-r--r--services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp4
-rw-r--r--services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp1186
-rw-r--r--services/surfaceflinger/tests/unittests/LayerTest.cpp1
-rw-r--r--services/surfaceflinger/tests/unittests/LayerTestUtils.cpp42
-rw-r--r--services/surfaceflinger/tests/unittests/LayerTestUtils.h6
-rw-r--r--services/surfaceflinger/tests/unittests/MessageQueueTest.cpp89
-rw-r--r--services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp3
-rw-r--r--services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp291
-rw-r--r--services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp2077
-rw-r--r--services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp3747
-rw-r--r--services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp34
-rw-r--r--services/surfaceflinger/tests/unittests/SchedulerTest.cpp877
-rw-r--r--services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp123
-rw-r--r--services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp57
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp137
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp5
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp457
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp68
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp109
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp231
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayNativePrimariesTest.cpp2
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp116
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp85
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp131
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp (renamed from services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp)27
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp180
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp44
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp90
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp6
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp136
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp36
-rw-r--r--services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp194
-rw-r--r--services/surfaceflinger/tests/unittests/TestableScheduler.h157
-rw-r--r--services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h622
-rw-r--r--services/surfaceflinger/tests/unittests/TimeStatsTest.cpp109
-rw-r--r--services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp792
-rw-r--r--services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp49
-rw-r--r--services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp77
-rw-r--r--services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp209
-rw-r--r--services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp108
-rw-r--r--services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp207
-rw-r--r--services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp44
-rw-r--r--services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp144
-rw-r--r--services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp84
-rw-r--r--services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp630
-rw-r--r--services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp239
-rw-r--r--services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp107
-rw-r--r--services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp19
-rw-r--r--services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp272
-rw-r--r--services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp286
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h51
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h34
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h48
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h11
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h31
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h29
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h32
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp (renamed from services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp)10
-rw-r--r--services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h51
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h35
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockEventThread.h52
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h25
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h2
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockLayer.h19
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h12
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp32
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h50
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockTimeStats.h3
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp25
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h38
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp6
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h20
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockVsyncController.h8
-rw-r--r--services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h32
-rw-r--r--services/surfaceflinger/tests/utils/ColorUtils.h12
-rw-r--r--services/surfaceflinger/tests/utils/ScreenshotUtils.h31
-rw-r--r--services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h77
-rw-r--r--services/surfaceflinger/tests/vsync/vsync.cpp9
-rw-r--r--services/vibratorservice/Android.bp6
-rw-r--r--services/vibratorservice/OWNERS2
-rw-r--r--services/vibratorservice/TEST_MAPPING16
-rw-r--r--services/vibratorservice/VibratorCallbackScheduler.cpp17
-rw-r--r--services/vibratorservice/benchmarks/Android.bp1
-rw-r--r--services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp4
-rw-r--r--services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h8
-rw-r--r--services/vibratorservice/test/Android.bp1
-rw-r--r--services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp83
-rw-r--r--services/vibratorservice/test/test_utils.h28
-rw-r--r--services/vr/virtual_touchpad/VirtualTouchpadService.cpp2
-rw-r--r--vulkan/include/vulkan/vk_android_native_buffer.h57
-rw-r--r--vulkan/libvulkan/Android.bp1
-rw-r--r--vulkan/libvulkan/driver.cpp195
-rw-r--r--vulkan/libvulkan/driver.h1
-rw-r--r--vulkan/libvulkan/driver_gen.cpp28
-rw-r--r--vulkan/libvulkan/driver_gen.h5
-rw-r--r--vulkan/libvulkan/layers_extensions.cpp36
-rw-r--r--vulkan/libvulkan/swapchain.cpp1522
-rw-r--r--vulkan/libvulkan/swapchain.h1
-rw-r--r--vulkan/nulldrv/Android.bp5
-rw-r--r--vulkan/nulldrv/null_driver.cpp12
-rw-r--r--vulkan/nulldrv/null_driver_gen.cpp3
-rw-r--r--vulkan/nulldrv/null_driver_gen.h1
-rw-r--r--vulkan/scripts/driver_generator.py6
-rw-r--r--vulkan/scripts/generator_common.py1
-rw-r--r--vulkan/vkjson/Android.bp17
1815 files changed, 165219 insertions, 70027 deletions
diff --git a/.gitignore b/.gitignore
index ed653c6b4a..1ad8a24a4b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,3 +3,6 @@
.idea/
.vscode/
*.code-workspace
+
+# Rust artifacts
+**/target/
diff --git a/Android.bp b/Android.bp
index 3992f82c3d..8c4dfbbea4 100644
--- a/Android.bp
+++ b/Android.bp
@@ -36,6 +36,14 @@ license {
],
}
+cc_library_headers {
+ name: "native_headers",
+ host_supported: true,
+ export_include_dirs: [
+ "include/",
+ ],
+}
+
ndk_headers {
name: "libandroid_headers",
from: "include/android",
@@ -89,6 +97,12 @@ filegroup {
],
}
+aidl_library {
+ name: "PersistableBundle_aidl",
+ hdrs: ["aidl/binder/android/os/PersistableBundle.aidl"],
+ strip_import_prefix: "aidl/binder",
+}
+
cc_library_headers {
name: "libandroid_headers_private",
export_include_dirs: ["include/private"],
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index c71c4a0251..2ce3fb05cc 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -7,6 +7,7 @@ clang_format = true
rustfmt = --config-path=rustfmt.toml
# Only turn on clang-format check for the following subfolders.
clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
+ cmds/dumpstate/
cmds/idlcli/
cmds/installd/
cmds/servicemanager/
@@ -14,6 +15,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
include/powermanager/
libs/binder/fuzzer/
libs/binder/
+ libs/binderdebug/
libs/binderthreadstate/
libs/graphicsenv/
libs/gui/
@@ -40,3 +42,4 @@ dumpstate_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD
dumpsys_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/dumpsys/"
# bugreports matches both cmds/bugreport and cmds/bugreportz
bugreports_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^cmds/bugreport"
+binder_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "^libs/binder/"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index e66bca07ab..9c0116957d 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -4,55 +4,11 @@
"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.*"
+ // TODO(b/305717998): Deflake and re-enable
+ "exclude-filter": "*ChildLayerTest*"
}
]
},
@@ -70,58 +26,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/aidl/binder/android/os/PersistableBundle.aidl b/aidl/binder/android/os/PersistableBundle.aidl
index 493ecb414c..248e9738df 100644
--- a/aidl/binder/android/os/PersistableBundle.aidl
+++ b/aidl/binder/android/os/PersistableBundle.aidl
@@ -17,4 +17,4 @@
package android.os;
-@JavaOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable PersistableBundle cpp_header "binder/PersistableBundle.h" ndk_header "android/persistable_bundle_aidl.h";
diff --git a/aidl/gui/android/view/Surface.aidl b/aidl/gui/android/view/Surface.aidl
index bb3faaff79..668671760c 100644
--- a/aidl/gui/android/view/Surface.aidl
+++ b/aidl/gui/android/view/Surface.aidl
@@ -17,4 +17,4 @@
package android.view;
-@JavaOnlyStableParcelable @NdkOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h";
+@JavaOnlyStableParcelable @NdkOnlyStableParcelable @RustOnlyStableParcelable parcelable Surface cpp_header "gui/view/Surface.h" ndk_header "android/native_window_aidl.h" rust_type "nativewindow::Surface";
diff --git a/cmds/atrace/Android.bp b/cmds/atrace/Android.bp
index aa0ef253bf..1c4e63e323 100644
--- a/cmds/atrace/Android.bp
+++ b/cmds/atrace/Android.bp
@@ -38,6 +38,7 @@ cc_binary {
],
init_rc: ["atrace.rc"],
+ required: ["ftrace_synthetic_events.conf"],
product_variables: {
debuggable: {
@@ -45,3 +46,8 @@ cc_binary {
},
},
}
+
+prebuilt_etc {
+ name: "ftrace_synthetic_events.conf",
+ src: "ftrace_synthetic_events.conf",
+}
diff --git a/cmds/atrace/atrace.cpp b/cmds/atrace/atrace.cpp
index 81056262c3..cd4926ad50 100644
--- a/cmds/atrace/atrace.cpp
+++ b/cmds/atrace/atrace.cpp
@@ -313,12 +313,6 @@ static const char* k_traceClockPath =
static const char* k_traceBufferSizePath =
"buffer_size_kb";
-#if 0
-// TODO: Re-enable after stabilization
-static const char* k_traceCmdlineSizePath =
- "saved_cmdlines_size";
-#endif
-
static const char* k_tracingOverwriteEnablePath =
"options/overwrite";
@@ -545,18 +539,6 @@ static bool setTraceBufferSizeKB(int size)
return writeStr(k_traceBufferSizePath, str);
}
-#if 0
-// TODO: Re-enable after stabilization
-// Set the default size of cmdline hashtable
-static bool setCmdlineSize()
-{
- if (fileExists(k_traceCmdlineSizePath)) {
- return writeStr(k_traceCmdlineSizePath, "8192");
- }
- return true;
-}
-#endif
-
// Set the clock to the best available option while tracing. Use 'boot' if it's
// available; otherwise, use 'mono'. If neither are available use 'global'.
// Any write to the trace_clock sysfs file will reset the buffer, so only
@@ -692,7 +674,7 @@ static bool verifyKernelTraceFuncs(const char* funcs)
while (func) {
if (!strchr(func, '*')) {
String8 fancyFunc = String8::format("\n%s\n", func);
- bool found = funcList.find(fancyFunc.string(), 0) >= 0;
+ bool found = funcList.find(fancyFunc.c_str(), 0) >= 0;
if (!found || func[0] == '\0') {
fprintf(stderr, "error: \"%s\" is not a valid kernel function "
"to trace.\n", func);
@@ -796,11 +778,11 @@ static bool setCategoriesEnableFromFile(const char* categories_file)
bool ok = true;
while (!tokenizer->isEol()) {
String8 token = tokenizer->nextToken(" ");
- if (token.isEmpty()) {
+ if (token.empty()) {
tokenizer->skipDelimiters(" ");
continue;
}
- ok &= setCategoryEnable(token.string());
+ ok &= setCategoryEnable(token.c_str());
}
delete tokenizer;
return ok;
@@ -870,8 +852,6 @@ static bool setUpKernelTracing()
ok &= setCategoriesEnableFromFile(g_categoriesFile);
ok &= setTraceOverwriteEnable(g_traceOverwrite);
ok &= setTraceBufferSizeKB(g_traceBufferSizeKB);
- // TODO: Re-enable after stabilization
- //ok &= setCmdlineSize();
ok &= setClock();
ok &= setPrintTgidEnableIfPresent(true);
ok &= setKernelTraceFuncs(g_kernelTraceFuncs);
diff --git a/cmds/atrace/atrace.rc b/cmds/atrace/atrace.rc
index dff4c44671..fdac5db1d1 100644
--- a/cmds/atrace/atrace.rc
+++ b/cmds/atrace/atrace.rc
@@ -13,6 +13,8 @@ on late-init
# Access control to these files is now entirely in selinux policy.
chmod 0666 /sys/kernel/debug/tracing/trace_clock
chmod 0666 /sys/kernel/tracing/trace_clock
+ chmod 0666 /sys/kernel/debug/tracing/buffer_percent
+ chmod 0666 /sys/kernel/tracing/buffer_percent
chmod 0666 /sys/kernel/debug/tracing/buffer_size_kb
chmod 0666 /sys/kernel/tracing/buffer_size_kb
chmod 0666 /sys/kernel/debug/tracing/options/overwrite
@@ -93,6 +95,10 @@ on late-init
chmod 0666 /sys/kernel/tracing/events/binder/binder_unlock/enable
chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_set_priority/enable
chmod 0666 /sys/kernel/tracing/events/binder/binder_set_priority/enable
+ chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_command/enable
+ chmod 0666 /sys/kernel/tracing/events/binder/binder_command/enable
+ chmod 0666 /sys/kernel/debug/tracing/events/binder/binder_return/enable
+ chmod 0666 /sys/kernel/tracing/events/binder/binder_return/enable
chmod 0666 /sys/kernel/debug/tracing/events/i2c/enable
chmod 0666 /sys/kernel/tracing/events/i2c/enable
chmod 0666 /sys/kernel/debug/tracing/events/i2c/i2c_read/enable
@@ -228,10 +234,6 @@ on late-init
chmod 0666 /sys/kernel/debug/tracing/events/thermal/cdev_update/enable
chmod 0666 /sys/kernel/tracing/events/thermal/cdev_update/enable
-# Tracing disabled by default
- write /sys/kernel/debug/tracing/tracing_on 0
- write /sys/kernel/tracing/tracing_on 0
-
# Read and truncate the kernel trace.
chmod 0666 /sys/kernel/debug/tracing/trace
chmod 0666 /sys/kernel/tracing/trace
@@ -291,12 +293,10 @@ on late-init
# Setup synthetic events
chmod 0666 /sys/kernel/tracing/synthetic_events
chmod 0666 /sys/kernel/debug/tracing/synthetic_events
+ copy /system/etc/ftrace_synthetic_events.conf /sys/kernel/tracing/synthetic_events
+ copy /system/etc/ftrace_synthetic_events.conf /sys/kernel/debug/tracing/synthetic_events
- # rss_stat_throttled
- write /sys/kernel/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size"
- write /sys/kernel/debug/tracing/synthetic_events "rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size"
-
- # allow creating event triggers
+ # allow creating rss_stat event triggers
chmod 0666 /sys/kernel/tracing/events/kmem/rss_stat/trigger
chmod 0666 /sys/kernel/debug/tracing/events/kmem/rss_stat/trigger
@@ -304,16 +304,22 @@ on late-init
chmod 0666 /sys/kernel/tracing/events/synthetic/rss_stat_throttled/enable
chmod 0666 /sys/kernel/debug/tracing/events/synthetic/rss_stat_throttled/enable
-on late-init && property:ro.boot.fastboot.boottrace=enabled
- setprop debug.atrace.tags.enableflags 802922
- setprop persist.traced.enable 0
- write /sys/kernel/debug/tracing/tracing_on 1
- write /sys/kernel/tracing/tracing_on 1
+ # allow creating suspend_resume triggers
+ chmod 0666 /sys/kernel/tracing/events/power/suspend_resume/trigger
+ chmod 0666 /sys/kernel/debug/tracing/events/power/suspend_resume/trigger
+
+ # allow enabling suspend_resume_minimal
+ chmod 0666 /sys/kernel/tracing/events/synthetic/suspend_resume_minimal/enable
+ chmod 0666 /sys/kernel/debug/tracing/events/synthetic/suspend_resume_minimal/enable
+
+on late-init && property:ro.boot.fastboot.boottrace=
+ write /sys/kernel/debug/tracing/tracing_on 0
+ write /sys/kernel/tracing/tracing_on 0
# Only create the tracing instance if persist.mm_events.enabled
# Attempting to remove the tracing instance after it has been created
# will likely fail with EBUSY as it would be in use by traced_probes.
-on post-fs-data && property:persist.mm_events.enabled=true
+on mm_events_property_available && property:persist.mm_events.enabled=true
# Create MM Events Tracing Instance for Kmem Activity Trigger
mkdir /sys/kernel/debug/tracing/instances/mm_events 0755 system system
mkdir /sys/kernel/tracing/instances/mm_events 0755 system system
@@ -398,6 +404,9 @@ on post-fs-data && property:persist.mm_events.enabled=true
chmod 0666 /sys/kernel/debug/tracing/instances/mm_events/per_cpu/cpu23/trace
chmod 0666 /sys/kernel/tracing/instances/mm_events/per_cpu/cpu23/trace
+on property:ro.persistent_properties.ready=true
+ trigger mm_events_property_available
+
# Handle hyp tracing instance
on late-init && property:ro.boot.hypervisor.vm.supported=1
@@ -494,18 +503,38 @@ on late-init && property:ro.boot.hypervisor.vm.supported=1
chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/hyp_exit/id
chmod 0440 /sys/kernel/tracing/hyp/events/hyp/hyp_exit/id
+# host_hcall event
+ chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/host_hcall/enable
+ chmod 0660 /sys/kernel/tracing/hyp/events/hyp/host_hcall/enable
+# TODO(b/249050813): should this be handled in kernel?
+ chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_hcall/format
+ chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_hcall/format
+ chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_hcall/id
+ chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_hcall/id
+
+# host_smc event
+ chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/host_smc/enable
+ chmod 0660 /sys/kernel/tracing/hyp/events/hyp/host_smc/enable
+# TODO(b/249050813): should this be handled in kernel?
+ chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_smc/format
+ chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_smc/format
+ chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_smc/id
+ chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_smc/id
+
+# host_mem_abort event
+ chmod 0660 /sys/kernel/debug/tracing/hyp/events/hyp/host_mem_abort/enable
+ chmod 0660 /sys/kernel/tracing/hyp/events/hyp/host_mem_abort/enable
+# TODO(b/249050813): should this be handled in kernel?
+ chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_mem_abort/format
+ chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_mem_abort/format
+ chmod 0440 /sys/kernel/debug/tracing/hyp/events/hyp/host_mem_abort/id
+ chmod 0440 /sys/kernel/tracing/hyp/events/hyp/host_mem_abort/id
on property:persist.debug.atrace.boottrace=1
start boottrace
# Run atrace with the categories written in a file
service boottrace /system/bin/atrace --async_start -f /data/misc/boottrace/categories
+ user root
disabled
oneshot
-
-on property:sys.boot_completed=1 && property:ro.boot.fastboot.boottrace=enabled
- setprop debug.atrace.tags.enableflags 0
- setprop persist.traced.enable 1
- write /sys/kernel/debug/tracing/tracing_on 0
- write /sys/kernel/tracing/tracing_on 0
-
diff --git a/cmds/atrace/ftrace_synthetic_events.conf b/cmds/atrace/ftrace_synthetic_events.conf
new file mode 100644
index 0000000000..e2257fe3da
--- /dev/null
+++ b/cmds/atrace/ftrace_synthetic_events.conf
@@ -0,0 +1,2 @@
+rss_stat_throttled unsigned int mm_id; unsigned int curr; int member; long size
+suspend_resume_minimal bool start
diff --git a/cmds/bugreport/OWNERS b/cmds/bugreport/OWNERS
index 5f56531754..41bfd26774 100644
--- a/cmds/bugreport/OWNERS
+++ b/cmds/bugreport/OWNERS
@@ -1,5 +1,5 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
diff --git a/cmds/bugreportz/OWNERS b/cmds/bugreportz/OWNERS
index 5f56531754..41bfd26774 100644
--- a/cmds/bugreportz/OWNERS
+++ b/cmds/bugreportz/OWNERS
@@ -1,5 +1,5 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
diff --git a/cmds/cmd/Android.bp b/cmds/cmd/Android.bp
index c3d2601444..27ef78854c 100644
--- a/cmds/cmd/Android.bp
+++ b/cmds/cmd/Android.bp
@@ -27,6 +27,9 @@ cc_library_static {
"libselinux",
"libbinder",
],
+ whole_static_libs: [
+ "libc++fs",
+ ],
cflags: [
"-Wall",
diff --git a/cmds/cmd/cmd.cpp b/cmds/cmd/cmd.cpp
index 8f1c01ab2c..9695e07753 100644
--- a/cmds/cmd/cmd.cpp
+++ b/cmds/cmd/cmd.cpp
@@ -27,6 +27,7 @@
#include <utils/Mutex.h>
#include <utils/Vector.h>
+#include <filesystem>
#include <getopt.h>
#include <stdlib.h>
#include <stdio.h>
@@ -69,16 +70,14 @@ public:
virtual int openFile(const String16& path, const String16& seLinuxContext,
const String16& mode) {
String8 path8(path);
- char cwd[256];
- getcwd(cwd, 256);
- String8 fullPath(cwd);
- fullPath.appendPath(path8);
+ auto fullPath = std::filesystem::current_path();
+ fullPath /= path8.c_str();
if (!mActive) {
mErrorLog << "Open attempt after active for: " << fullPath << endl;
return -EPERM;
}
#if DEBUG
- ALOGD("openFile: %s, full=%s", path8.string(), fullPath.string());
+ ALOGD("openFile: %s, full=%s", path8.c_str(), fullPath.c_str());
#endif
int flags = 0;
bool checkRead = false;
@@ -96,10 +95,10 @@ public:
flags = O_RDWR;
checkRead = checkWrite = true;
} else {
- mErrorLog << "Invalid mode requested: " << mode.string() << endl;
+ mErrorLog << "Invalid mode requested: " << mode << endl;
return -EINVAL;
}
- int fd = open(fullPath.string(), flags, S_IRWXU|S_IRWXG);
+ int fd = open(fullPath.c_str(), flags, S_IRWXU|S_IRWXG);
#if DEBUG
ALOGD("openFile: fd=%d", fd);
#endif
@@ -109,29 +108,29 @@ public:
if (is_selinux_enabled() && seLinuxContext.size() > 0) {
String8 seLinuxContext8(seLinuxContext);
char* tmp = nullptr;
- getfilecon(fullPath.string(), &tmp);
+ getfilecon(fullPath.c_str(), &tmp);
Unique_SecurityContext context(tmp);
if (checkWrite) {
- int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+ int accessGranted = selinux_check_access(seLinuxContext8.c_str(), context.get(),
"file", "write", nullptr);
if (accessGranted != 0) {
#if DEBUG
ALOGD("openFile: failed selinux write check!");
#endif
close(fd);
- mErrorLog << "System server has no access to write file context " << context.get() << " (from path " << fullPath.string() << ", context " << seLinuxContext8.string() << ")" << endl;
+ mErrorLog << "System server has no access to write file context " << context.get() << " (from path " << fullPath.c_str() << ", context " << seLinuxContext8.c_str() << ")" << endl;
return -EPERM;
}
}
if (checkRead) {
- int accessGranted = selinux_check_access(seLinuxContext8.string(), context.get(),
+ int accessGranted = selinux_check_access(seLinuxContext8.c_str(), context.get(),
"file", "read", nullptr);
if (accessGranted != 0) {
#if DEBUG
ALOGD("openFile: failed selinux read check!");
#endif
close(fd);
- mErrorLog << "System server has no access to read file context " << context.get() << " (from path " << fullPath.string() << ", context " << seLinuxContext8.string() << ")" << endl;
+ mErrorLog << "System server has no access to read file context " << context.get() << " (from path " << fullPath.c_str() << ", context " << seLinuxContext8.c_str() << ")" << endl;
return -EPERM;
}
}
diff --git a/cmds/cmd/fuzzer/Android.bp b/cmds/cmd/fuzzer/Android.bp
index a65f6deb8a..faf461abc4 100644
--- a/cmds/cmd/fuzzer/Android.bp
+++ b/cmds/cmd/fuzzer/Android.bp
@@ -42,5 +42,13 @@ cc_fuzz {
"android-media-fuzzing-reports@google.com",
],
componentid: 155276,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libcmd",
+ vector: "local_no_privileges_required",
+ service_privilege: "constrained",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
},
}
diff --git a/cmds/dumpstate/Android.bp b/cmds/dumpstate/Android.bp
index a62bd01a5b..4a77967bb2 100644
--- a/cmds/dumpstate/Android.bp
+++ b/cmds/dumpstate/Android.bp
@@ -37,9 +37,6 @@ cc_library_shared {
name: "libdumpstateutil",
defaults: ["dumpstate_cflag_defaults"],
vendor_available: true,
- vndk: {
- enabled: true,
- },
srcs: [
"DumpstateInternal.cpp",
"DumpstateUtil.cpp",
@@ -82,7 +79,10 @@ filegroup {
cc_defaults {
name: "dumpstate_defaults",
- defaults: ["dumpstate_cflag_defaults"],
+ defaults: [
+ "aconfig_lib_cc_static_link.defaults",
+ "dumpstate_cflag_defaults",
+ ],
shared_libs: [
"android.hardware.dumpstate@1.0",
"android.hardware.dumpstate@1.1",
@@ -104,6 +104,8 @@ cc_defaults {
"libvintf",
"libbinderdebug",
"packagemanager_aidl-cpp",
+ "server_configurable_flags",
+ "device_policy_aconfig_flags_c_lib",
],
srcs: [
"DumpstateService.cpp",
@@ -155,7 +157,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 e42ee0540b..ba0a38aad9 100644
--- a/cmds/dumpstate/DumpstateService.cpp
+++ b/cmds/dumpstate/DumpstateService.cpp
@@ -37,6 +37,8 @@ struct DumpstateInfo {
Dumpstate* ds = nullptr;
int32_t calling_uid = -1;
std::string calling_package;
+ int32_t user_id = -1;
+ bool keep_bugreport_on_retrieval = false;
};
static binder::Status exception(uint32_t code, const std::string& msg,
@@ -51,13 +53,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<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(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<DumpstateInfo> ds_info(static_cast<DumpstateInfo*>(data));
+ ds_info->ds->Retrieve(ds_info->calling_uid, ds_info->calling_package, ds_info->keep_bugreport_on_retrieval);
+ MYLOGD("Finished retrieving a bugreport. Exiting.\n");
+ exit(0);
+}
+
[[noreturn]] static void signalErrorAndExit(sp<IDumpstateListener> listener, int error_code) {
listener->onError(error_code);
exit(0);
@@ -84,11 +93,28 @@ status_t DumpstateService::Start() {
return android::OK;
}
+binder::Status DumpstateService::preDumpUiData(const std::string&) {
+ std::lock_guard<std::mutex> 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<IDumpstateListener>& listener,
bool is_screenshot_requested) {
MYLOGI("startBugreport() with mode: %d\n", bugreport_mode);
@@ -96,12 +122,12 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid,
// Ensure there is only one bugreport in progress at a time.
std::lock_guard<std::mutex> 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
@@ -117,14 +143,15 @@ binder::Status DumpstateService::startBugreport(int32_t calling_uid,
bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_WEAR &&
bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_TELEPHONY &&
bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_WIFI &&
+ bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_ONBOARDING &&
bugreport_mode != Dumpstate::BugreportMode::BUGREPORT_DEFAULT) {
MYLOGE("Invalid input: bad bugreport mode: %d", bugreport_mode);
signalErrorAndExit(listener, IDumpstateListener::BUGREPORT_ERROR_INVALID_INPUT);
}
std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
- options->Initialize(static_cast<Dumpstate::BugreportMode>(bugreport_mode), bugreport_fd,
- screenshot_fd, is_screenshot_requested);
+ options->Initialize(static_cast<Dumpstate::BugreportMode>(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 +175,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 +202,44 @@ 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, int32_t user_id,
+ android::base::unique_fd bugreport_fd,
+ const std::string& bugreport_file,
+ const bool keep_bugreport_on_retrieval,
+ const sp<IDumpstateListener>& 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_info->user_id = user_id;
+ ds_info->keep_bugreport_on_retrieval = keep_bugreport_on_retrieval;
+ ds_->listener_ = listener;
+ std::unique_ptr<Dumpstate::DumpOptions> options = std::make_unique<Dumpstate::DumpOptions>();
+ // 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<String16>&) {
std::lock_guard<std::mutex> lock(lock_);
if (ds_ == nullptr) {
diff --git a/cmds/dumpstate/DumpstateService.h b/cmds/dumpstate/DumpstateService.h
index 3ec8471c04..7b76c36380 100644
--- a/cmds/dumpstate/DumpstateService.h
+++ b/cmds/dumpstate/DumpstateService.h
@@ -38,13 +38,25 @@ class DumpstateService : public BinderService<DumpstateService>, public BnDumpst
status_t dump(int fd, const Vector<String16>& 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<IDumpstateListener>& listener,
+ int bugreport_flags, const sp<IDumpstateListener>& 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,
+ int32_t user_id,
+ android::base::unique_fd bugreport_fd,
+ const std::string& bugreport_file,
+ const bool keep_bugreport_on_retrieval,
+ const sp<IDumpstateListener>& 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/DumpstateUtil.cpp b/cmds/dumpstate/DumpstateUtil.cpp
index aa42541a66..615701ccc1 100644
--- a/cmds/dumpstate/DumpstateUtil.cpp
+++ b/cmds/dumpstate/DumpstateUtil.cpp
@@ -207,6 +207,7 @@ std::string PropertiesHelper::build_type_ = "";
int PropertiesHelper::dry_run_ = -1;
int PropertiesHelper::unroot_ = -1;
int PropertiesHelper::parallel_run_ = -1;
+int PropertiesHelper::strict_run_ = -1;
bool PropertiesHelper::IsUserBuild() {
if (build_type_.empty()) {
@@ -237,6 +238,14 @@ bool PropertiesHelper::IsParallelRun() {
return parallel_run_ == 1;
}
+bool PropertiesHelper::IsStrictRun() {
+ if (strict_run_ == -1) {
+ // Defaults to using stricter timeouts.
+ strict_run_ = android::base::GetBoolProperty("dumpstate.strict_run", true) ? 1 : 0;
+ }
+ return strict_run_ == 1;
+}
+
int DumpFileToFd(int out_fd, const std::string& title, const std::string& path) {
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
if (fd.get() < 0) {
diff --git a/cmds/dumpstate/DumpstateUtil.h b/cmds/dumpstate/DumpstateUtil.h
index b00c46e0db..9e955e3c04 100644
--- a/cmds/dumpstate/DumpstateUtil.h
+++ b/cmds/dumpstate/DumpstateUtil.h
@@ -193,11 +193,19 @@ class PropertiesHelper {
*/
static bool IsParallelRun();
+ /*
+ * Strict-run mode is determined by the `dumpstate.strict_run` sysprop which
+ * will default to true. This results in shortened timeouts for flaky
+ * sections.
+ */
+ static bool IsStrictRun();
+
private:
static std::string build_type_;
static int dry_run_;
static int unroot_;
static int parallel_run_;
+ static int strict_run_;
};
/*
diff --git a/cmds/dumpstate/OWNERS b/cmds/dumpstate/OWNERS
index 5f56531754..c24bf39ad7 100644
--- a/cmds/dumpstate/OWNERS
+++ b/cmds/dumpstate/OWNERS
@@ -1,5 +1,6 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
+smoreland@google.com \ No newline at end of file
diff --git a/cmds/dumpstate/binder/android/os/IDumpstate.aidl b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
index 0793f0b95f..97c470e08c 100644
--- a/cmds/dumpstate/binder/android/os/IDumpstate.aidl
+++ b/cmds/dumpstate/binder/android/os/IDumpstate.aidl
@@ -49,6 +49,32 @@ interface IDumpstate {
// Default mode.
const int BUGREPORT_MODE_DEFAULT = 6;
+ // Bugreport taken for onboarding related flows.
+ const int BUGREPORT_MODE_ONBOARDING = 7;
+
+ // Use pre-dumped data.
+ const int BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA = 0x1;
+
+ // Defer user consent.
+ const int BUGREPORT_FLAG_DEFER_CONSENT = 0x2;
+
+ // Keep bugreport stored after retrieval.
+ const int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 0x4;
+
+ /**
+ * Speculatively pre-dumps UI data for a bugreport request that might come later.
+ *
+ * <p>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.
+ *
+ * <p>{@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 +89,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 +109,26 @@ 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.
+ *
+ * <p>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 userId user Id of the original package 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 keepBugreportOnRetrieval boolean to indicate if the bugreport should be kept in the
+ * platform after it has been retrieved by the caller.
+ * @param listener callback for updates; optional
+ */
+ void retrieveBugreport(int callingUid, @utf8InCpp String callingPackage, int userId,
+ FileDescriptor bugreportFd,
+ @utf8InCpp String bugreportFile,
+ boolean keepBugreportOnRetrieval,
+ IDumpstateListener listener);
}
diff --git a/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl b/cmds/dumpstate/binder/android/os/IDumpstateListener.aidl
index 50c1624dc2..e8891d3236 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 47a513be81..c501921399 100644
--- a/cmds/dumpstate/dumpstate.cpp
+++ b/cmds/dumpstate/dumpstate.cpp
@@ -59,6 +59,7 @@
#include <vector>
#include <aidl/android/hardware/dumpstate/IDumpstateDevice.h>
+#include <android_app_admin_flags.h>
#include <android-base/file.h>
#include <android-base/properties.h>
#include <android-base/scopeguard.h>
@@ -74,6 +75,7 @@
#include <android/hidl/manager/1.0/IServiceManager.h>
#include <android/os/IIncidentCompanion.h>
#include <binder/IServiceManager.h>
+#include <cutils/multiuser.h>
#include <cutils/native_handle.h>
#include <cutils/properties.h>
#include <cutils/sockets.h>
@@ -176,6 +178,7 @@ void add_mountinfo();
#define PROFILE_DATA_DIR_CUR "/data/misc/profiles/cur"
#define PROFILE_DATA_DIR_REF "/data/misc/profiles/ref"
#define XFRM_STAT_PROC_FILE "/proc/net/xfrm_stat"
+#define KERNEL_CONFIG "/proc/config.gz"
#define WLUTIL "/vendor/xbin/wlutil"
#define WMTRACE_DATA_DIR "/data/misc/wmtrace"
#define OTA_METADATA_DIR "/metadata/ota"
@@ -186,6 +189,7 @@ void add_mountinfo();
#define CGROUPFS_DIR "/sys/fs/cgroup"
#define SDK_EXT_INFO "/apex/com.android.sdkext/bin/derive_sdk"
#define DROPBOX_DIR "/data/system/dropbox"
+#define PRINT_FLAGS "/system/bin/printflags"
// TODO(narayan): Since this information has to be kept in sync
// with tombstoned, we should just put it in a common header.
@@ -195,6 +199,7 @@ static const std::string TOMBSTONE_DIR = "/data/tombstones/";
static const std::string TOMBSTONE_FILE_PREFIX = "tombstone_";
static const std::string ANR_DIR = "/data/anr/";
static const std::string ANR_FILE_PREFIX = "anr_";
+static const std::string ANR_TRACE_FILE_PREFIX = "trace_";
static const std::string SHUTDOWN_CHECKPOINTS_DIR = "/data/system/shutdown-checkpoints/";
static const std::string SHUTDOWN_CHECKPOINTS_FILE_PREFIX = "checkpoints-";
@@ -242,6 +247,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 {
@@ -814,13 +820,18 @@ void Dumpstate::PrintHeader() const {
printf("Kernel: ");
DumpFileToFd(STDOUT_FILENO, "", "/proc/version");
printf("Command line: %s\n", strtok(cmdline_buf, "\n"));
+ printf("Bootconfig: ");
+ DumpFileToFd(STDOUT_FILENO, "", "/proc/bootconfig");
printf("Uptime: ");
RunCommandToFd(STDOUT_FILENO, "", {"uptime", "-p"},
CommandOptions::WithTimeout(1).Always().Build());
printf("Bugreport format version: %s\n", version_.c_str());
- printf("Dumpstate info: id=%d pid=%d dry_run=%d parallel_run=%d args=%s bugreport_mode=%s\n",
- id_, pid_, PropertiesHelper::IsDryRun(), PropertiesHelper::IsParallelRun(),
- options_->args.c_str(), options_->bugreport_mode_string.c_str());
+ printf(
+ "Dumpstate info: id=%d pid=%d dry_run=%d parallel_run=%d strict_run=%d args=%s "
+ "bugreport_mode=%s\n",
+ id_, pid_, PropertiesHelper::IsDryRun(), PropertiesHelper::IsParallelRun(),
+ PropertiesHelper::IsStrictRun(), options_->args.c_str(),
+ options_->bugreport_mode_string.c_str());
printf("\n");
}
@@ -1026,8 +1037,6 @@ static void DoLogcat() {
CommandOptions::WithTimeoutInMs(timeout_ms).Build(), true /* verbose_duration */);
DoRadioLogcat();
- RunCommand("LOG STATISTICS", {"logcat", "-b", "all", "-S"});
-
/* kernels must set CONFIG_PSTORE_PMSG, slice up pstore with device tree */
RunCommand("LAST LOGCAT", {"logcat", "-L", "-b", "all", "-v", "threadtime", "-v", "printable",
"-v", "uid", "-d", "*:v"});
@@ -1042,7 +1051,8 @@ static void DumpIncidentReport() {
MYLOGE("Could not open %s to dump incident report.\n", path.c_str());
return;
}
- RunCommandToFd(fd, "", {"incident", "-u"}, CommandOptions::WithTimeout(20).Build());
+ RunCommandToFd(fd, "", {"incident", "-u"},
+ CommandOptions::WithTimeout(PropertiesHelper::IsStrictRun() ? 20 : 120).Build());
bool empty = 0 == lseek(fd, 0, SEEK_END);
if (!empty) {
// Use a different name from "incident.proto"
@@ -1065,7 +1075,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,
@@ -1079,8 +1089,14 @@ static void MaybeAddSystemTraceToZip() {
// This function copies into the .zip the system trace that was snapshotted
// by the early call to MaybeSnapshotSystemTrace(), if any background
// tracing was happening.
- if (!ds.has_system_trace_) {
- // No background trace was happening at the time dumpstate was invoked.
+ bool system_trace_exists = access(SYSTEM_TRACE_SNAPSHOT, F_OK) == 0;
+ if (!system_trace_exists) {
+ // No background trace was happening at the time MaybeSnapshotSystemTrace() was invoked.
+ if (!PropertiesHelper::IsUserBuild()) {
+ MYLOGI(
+ "No system traces found. Check for previously uploaded traces by looking for "
+ "go/trace-uuid in logcat")
+ }
return;
}
ds.AddZipEntry(
@@ -1171,6 +1187,10 @@ static void AddAnrTraceDir(const std::string& anr_traces_dir) {
} else {
printf("*** NO ANRs to dump in %s\n\n", ANR_DIR.c_str());
}
+
+ // Add Java anr traces (such as generated by the Finalizer Watchdog).
+ AddDumps(ds.anr_trace_data_.begin(), ds.anr_trace_data_.end(), "JAVA ANR TRACES",
+ true /* add_to_zip */);
}
static void AddAnrTraceFiles() {
@@ -1227,7 +1247,7 @@ static void DumpPacketStats() {
static void DumpIpAddrAndRules() {
/* The following have a tendency to get wedged when wifi drivers/fw goes belly-up. */
- RunCommand("NETWORK INTERFACES", {"ip", "link"});
+ RunCommand("NETWORK INTERFACES", {"ip", "-s", "link"});
RunCommand("IPv4 ADDRESSES", {"ip", "-4", "addr", "show"});
RunCommand("IPv6 ADDRESSES", {"ip", "-6", "addr", "show"});
RunCommand("IP RULES", {"ip", "rule", "show"});
@@ -1252,8 +1272,10 @@ static Dumpstate::RunStatus RunDumpsysTextByPriority(const std::string& title, i
dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
dumpsys.writeDumpFooter(STDOUT_FILENO, service, std::chrono::milliseconds(1));
} else {
- status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP, service, args);
- if (status == OK) {
+ status_t status = dumpsys.startDumpThread(Dumpsys::TYPE_DUMP | Dumpsys::TYPE_PID |
+ Dumpsys::TYPE_CLIENTS | Dumpsys::TYPE_THREAD,
+ service, args);
+ if (status == OK) {
dumpsys.writeDumpHeader(STDOUT_FILENO, service, priority);
std::chrono::duration<double> elapsed_seconds;
if (priority == IServiceManager::DUMP_FLAG_PRIORITY_HIGH &&
@@ -1270,6 +1292,9 @@ static Dumpstate::RunStatus RunDumpsysTextByPriority(const std::string& title, i
dumpsys.writeDumpFooter(STDOUT_FILENO, service, elapsed_seconds);
bool dump_complete = (status == OK);
dumpsys.stopDumpThread(dump_complete);
+ } else {
+ MYLOGE("Failed to start dump thread for service: %s, status: %d",
+ String8(service).c_str(), status);
}
}
@@ -1407,12 +1432,12 @@ static void DumpHals(int out_fd = STDOUT_FILENO) {
auto ret = sm->list([&](const auto& interfaces) {
for (const std::string& interface : interfaces) {
std::string cleanName = interface;
- std::replace_if(cleanName.begin(),
- cleanName.end(),
- [](char c) {
- return !isalnum(c) &&
- std::string("@-_:.").find(c) == std::string::npos;
- }, '_');
+ std::replace_if(
+ cleanName.begin(), cleanName.end(),
+ [](char c) {
+ return !isalnum(c) && std::string("@-_.").find(c) == std::string::npos;
+ },
+ '_');
const std::string path = ds.bugreport_internal_dir_ + "/lshal_debug_" + cleanName;
bool empty = false;
@@ -1505,7 +1530,7 @@ static void DumpExternalFragmentationInfo() {
}
static void DumpstateLimitedOnly() {
- // Trimmed-down version of dumpstate to only include a whitelisted
+ // Trimmed-down version of dumpstate to only include a allowlisted
// set of logs (system log, event log, and system server / system app
// crashes, and networking logs). See b/136273873 and b/138459828
// for context.
@@ -1616,16 +1641,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<std::string> 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(
@@ -1743,6 +1768,14 @@ static Dumpstate::RunStatus dumpstate() {
RunCommand("SYSTEM PROPERTIES", {"getprop"});
+ DumpFile("SYSTEM BUILD-TIME RELEASE FLAGS", "/system/etc/build_flags.json");
+ DumpFile("SYSTEM_EXT BUILD-TIME RELEASE FLAGS", "/system_ext/etc/build_flags.json");
+ DumpFile("PRODUCT BUILD-TIME RELEASE FLAGS", "/product/etc/build_flags.json");
+ DumpFile("VENDOR BUILD-TIME RELEASE FLAGS", "/vendor/etc/build_flags.json");
+
+ RunCommand("ACONFIG FLAGS", {PRINT_FLAGS},
+ CommandOptions::WithTimeout(10).Always().DropRoot().Build());
+
RunCommand("STORAGED IO INFO", {"storaged", "-u", "-p"});
RunCommand("FILESYSTEMS & FREE SPACE", {"df"});
@@ -1757,11 +1790,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_) {
@@ -1841,6 +1869,8 @@ static Dumpstate::RunStatus dumpstate() {
DumpIncidentReport);
}
+ MaybeAddUiTracesToZip();
+
return Dumpstate::RunStatus::OK;
}
@@ -1879,6 +1909,7 @@ Dumpstate::RunStatus Dumpstate::DumpstateDefaultAfterCritical() {
if (!PropertiesHelper::IsDryRun()) {
ds.tombstone_data_ = GetDumpFds(TOMBSTONE_DIR, TOMBSTONE_FILE_PREFIX);
ds.anr_data_ = GetDumpFds(ANR_DIR, ANR_FILE_PREFIX);
+ ds.anr_trace_data_ = GetDumpFds(ANR_DIR, ANR_TRACE_FILE_PREFIX);
ds.shutdown_checkpoints_ = GetDumpFds(
SHUTDOWN_CHECKPOINTS_DIR, SHUTDOWN_CHECKPOINTS_FILE_PREFIX);
}
@@ -1895,6 +1926,9 @@ Dumpstate::RunStatus Dumpstate::DumpstateDefaultAfterCritical() {
}
ds.AddDir(PREREBOOT_DATA_DIR, false);
add_mountinfo();
+ for (const char* path : {"/proc/cpuinfo", "/proc/meminfo"}) {
+ ds.AddZipEntry(ZIP_ROOT_DIR + path, path);
+ }
DumpIpTablesAsRoot();
DumpDynamicPartitionInfo();
ds.AddDir(OTA_METADATA_DIR, true);
@@ -1924,6 +1958,8 @@ Dumpstate::RunStatus Dumpstate::DumpstateDefaultAfterCritical() {
DumpFile("PSI memory", "/proc/pressure/memory");
DumpFile("PSI io", "/proc/pressure/io");
+ ds.AddZipEntry(ZIP_ROOT_DIR + KERNEL_CONFIG, KERNEL_CONFIG);
+
RunCommand("SDK EXTENSIONS", {SDK_EXT_INFO, "--dump"},
CommandOptions::WithTimeout(10).Always().DropRoot().Build());
@@ -2055,6 +2091,10 @@ static void DumpstateTelephonyOnly(const std::string& calling_package) {
SEC_TO_MSEC(10));
RunDumpsys("DUMPSYS", {"telephony.registry"}, CommandOptions::WithTimeout(90).Build(),
SEC_TO_MSEC(10));
+ RunDumpsys("DUMPSYS", {"isub"}, CommandOptions::WithTimeout(90).Build(),
+ SEC_TO_MSEC(10));
+ RunDumpsys("DUMPSYS", {"telecom"}, CommandOptions::WithTimeout(90).Build(),
+ SEC_TO_MSEC(10));
if (include_sensitive_info) {
// Contains raw IP addresses, omit from reports on user builds.
RunDumpsys("DUMPSYS", {"netd"}, CommandOptions::WithTimeout(90).Build(), SEC_TO_MSEC(10));
@@ -2137,6 +2177,11 @@ static void DumpstateWifiOnly() {
printf("========================================================\n");
}
+// Collects a lightweight dumpstate to be used for debugging onboarding related flows.
+static void DumpstateOnboardingOnly() {
+ ds.AddDir(LOGPERSIST_DATA_DIR, false);
+}
+
Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
const std::string temp_file_pattern = ds.bugreport_internal_dir_ + "/dumptrace_XXXXXX";
const size_t buf_size = temp_file_pattern.length() + 1;
@@ -2185,6 +2230,16 @@ Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
continue;
}
+ // Skip cached processes.
+ if (IsCached(pid)) {
+ // For consistency, the header and footer to this message match those
+ // dumped by debuggerd in the success case.
+ dprintf(fd, "\n---- pid %d at [unknown] ----\n", pid);
+ dprintf(fd, "Dump skipped for cached process.\n");
+ dprintf(fd, "---- end %d ----", pid);
+ continue;
+ }
+
const std::string link_name = android::base::StringPrintf("/proc/%d/exe", pid);
std::string exe;
if (!android::base::Readlink(link_name, &exe)) {
@@ -2215,8 +2270,7 @@ Dumpstate::RunStatus Dumpstate::DumpTraces(const char** path) {
const uint64_t start = Nanotime();
const int ret = dump_backtrace_to_file_timeout(
- pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace,
- is_java_process ? 5 : 20, fd);
+ pid, is_java_process ? kDebuggerdJavaBacktrace : kDebuggerdNativeBacktrace, 3, fd);
if (ret == -1) {
// For consistency, the header and footer to this message match those
@@ -2260,6 +2314,7 @@ static dumpstate_hal_hidl::DumpstateMode GetDumpstateHalModeHidl(
return dumpstate_hal_hidl::DumpstateMode::CONNECTIVITY;
case Dumpstate::BugreportMode::BUGREPORT_WIFI:
return dumpstate_hal_hidl::DumpstateMode::WIFI;
+ case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
return dumpstate_hal_hidl::DumpstateMode::DEFAULT;
}
@@ -2281,6 +2336,7 @@ static dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode GetDumpstateHalModeAi
return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::CONNECTIVITY;
case Dumpstate::BugreportMode::BUGREPORT_WIFI:
return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::WIFI;
+ case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
return dumpstate_hal_aidl::IDumpstateDevice::DumpstateMode::DEFAULT;
}
@@ -2628,10 +2684,13 @@ bool Dumpstate::FinishZipFile() {
return true;
}
-static void SendBroadcast(const std::string& action, const std::vector<std::string>& args) {
+static void SendBroadcast(const std::string& action,
+ const std::vector<std::string>& args,
+ int32_t user_id) {
// clang-format off
- std::vector<std::string> am = {"/system/bin/cmd", "activity", "broadcast", "--user", "0",
- "--receiver-foreground", "--receiver-include-background", "-a", action};
+ std::vector<std::string> 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());
@@ -2761,11 +2820,18 @@ static inline const char* ModeToString(Dumpstate::BugreportMode mode) {
return "BUGREPORT_TELEPHONY";
case Dumpstate::BugreportMode::BUGREPORT_WIFI:
return "BUGREPORT_WIFI";
+ case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
+ return "BUGREPORT_ONBOARDING";
case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
return "BUGREPORT_DEFAULT";
}
}
+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
@@ -2787,6 +2853,7 @@ static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOpt
options->do_screenshot = false;
break;
case Dumpstate::BugreportMode::BUGREPORT_WEAR:
+ options->do_vibrate = false;
options->do_progress_updates = true;
options->do_screenshot = is_screenshot_requested;
break;
@@ -2800,6 +2867,10 @@ static void SetOptionsFromMode(Dumpstate::BugreportMode mode, Dumpstate::DumpOpt
options->wifi_only = true;
options->do_screenshot = false;
break;
+ case Dumpstate::BugreportMode::BUGREPORT_ONBOARDING:
+ options->onboarding_only = true;
+ options->do_screenshot = false;
+ break;
case Dumpstate::BugreportMode::BUGREPORT_DEFAULT:
break;
}
@@ -2820,9 +2891,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));
@@ -2905,10 +2979,70 @@ 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,
+ const bool keep_bugreport_on_retrieval) {
+ Dumpstate::RunStatus status = RetrieveInternal(calling_uid, calling_package,
+ keep_bugreport_on_retrieval);
+ HandleRunStatus(status);
+ return status;
+}
+
+Dumpstate::RunStatus Dumpstate::RetrieveInternal(int32_t calling_uid,
+ const std::string& calling_package,
+ const bool keep_bugreport_on_retrieval) {
+ consent_callback_ = new ConsentCallback();
+ const String16 incidentcompanion("incidentcompanion");
+ sp<android::IBinder> 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<android::os::IIncidentCompanion>(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<android::os::IIncidentCompanion>(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::app::admin::flags::onboarding_bugreport_v2_enabled()
+ || !keep_bugreport_on_retrieval)) {
+ 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;
@@ -2926,9 +3060,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_);
@@ -2938,6 +3070,7 @@ void Dumpstate::Cancel() {
}
tombstone_data_.clear();
anr_data_.clear();
+ anr_trace_data_.clear();
shutdown_checkpoints_.clear();
// Instead of shutdown the pool, we delete temporary files directly since
@@ -2950,6 +3083,11 @@ void Dumpstate::Cancel() {
}
}
+void Dumpstate::PreDumpUiData() {
+ MaybeSnapshotSystemTrace();
+ MaybeSnapshotUiTraces();
+}
+
/*
* Dumps relevant information to a bugreport based on the given options.
*
@@ -3029,6 +3167,12 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
MYLOGI("Running on dry-run mode (to disable it, call 'setprop dumpstate.dry_run false')\n");
}
+ if (PropertiesHelper::IsStrictRun()) {
+ MYLOGI(
+ "Running on strict-run mode, which has shorter timeouts "
+ "(to disable, call 'setprop dumpstate.strict_run false')\n");
+ }
+
MYLOGI("dumpstate info: id=%d, args='%s', bugreport_mode= %s bugreport format version: %s\n",
id_, options_->args.c_str(), options_->bugreport_mode_string.c_str(), version_.c_str());
@@ -3060,7 +3204,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());
}
@@ -3126,25 +3271,23 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
// duration is logged into MYLOG instead.
PrintHeader();
- bool is_dumpstate_restricted = options_->telephony_only
- || options_->wifi_only
- || options_->limited_only;
- if (!is_dumpstate_restricted) {
- // Invoke critical dumpsys first to preserve system state, before doing anything else.
- RunDumpsysCritical();
- }
- MaybeTakeEarlyScreenshot();
-
+ bool is_dumpstate_restricted =
+ options_->telephony_only || options_->wifi_only || options_->limited_only;
if (!is_dumpstate_restricted) {
// Snapshot the system trace now (if running) to avoid that dumpstate's
// own activity pushes out interesting data from the trace ring buffer.
// 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();
+ // Invoke critical dumpsys to preserve system state, before doing anything else.
+ RunDumpsysCritical();
+
+ // Snapshot the UI traces now (if running).
+ // The trace files will be added to bugreport later.
+ MaybeSnapshotUiTraces();
}
+
+ MaybeTakeEarlyScreenshot();
onUiIntensiveBugreportDumpsFinished(calling_uid);
MaybeCheckUserConsent(calling_uid, calling_package);
if (options_->telephony_only) {
@@ -3153,6 +3296,8 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
DumpstateWifiOnly();
} else if (options_->limited_only) {
DumpstateLimitedOnly();
+ } else if (options_->onboarding_only) {
+ DumpstateOnboardingOnly();
} else {
// Dump state for the default case. This also drops root.
RunStatus s = DumpstateDefaultAfterCritical();
@@ -3174,7 +3319,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) {
@@ -3222,6 +3367,7 @@ Dumpstate::RunStatus Dumpstate::RunInternal(int32_t calling_uid,
tombstone_data_.clear();
anr_data_.clear();
+ anr_trace_data_.clear();
shutdown_checkpoints_.clear();
return (consent_callback_ != nullptr &&
@@ -3239,6 +3385,19 @@ void Dumpstate::MaybeTakeEarlyScreenshot() {
}
void Dumpstate::MaybeSnapshotSystemTrace() {
+ // When capturing traces via bugreport handler (BH), this function will be invoked twice:
+ // 1) When BH invokes IDumpstate::PreDumpUiData()
+ // 2) When BH invokes IDumpstate::startBugreport(flags = BUGREPORT_USE_PREDUMPED_UI_DATA)
+ // In this case we don't want to re-invoke perfetto in step 2.
+ // In all other standard invocation states, this function is invoked once
+ // without the flag BUGREPORT_USE_PREDUMPED_UI_DATA.
+ if (options_->use_predumped_ui_data) {
+ return;
+ }
+
+ // If a stale file exists already, remove it.
+ unlink(SYSTEM_TRACE_SNAPSHOT);
+
// If a background system trace is happening and is marked as "suitable for
// bugreport" (i.e. bugreport_score > 0 in the trace config), this command
// will stop it and serialize into SYSTEM_TRACE_SNAPSHOT. In the (likely)
@@ -3246,40 +3405,46 @@ void Dumpstate::MaybeSnapshotSystemTrace() {
// Note: this should not be enqueued as we need to freeze the trace before
// dumpstate starts. Otherwise the trace ring buffers will contain mostly
// the dumpstate's own activity which is irrelevant.
- int res = RunCommand(
- "SERIALIZE PERFETTO TRACE",
- {"perfetto", "--save-for-bugreport"},
- CommandOptions::WithTimeout(10)
- .DropRoot()
- .CloseAllFileDescriptorsOnExec()
- .Build());
- has_system_trace_ = res == 0;
+ RunCommand("SERIALIZE PERFETTO TRACE", {"perfetto", "--save-for-bugreport"},
+ CommandOptions::WithTimeout(10).DropRoot().CloseAllFileDescriptorsOnExec().Build());
// MaybeAddSystemTraceToZip() will take care of copying the trace in the zip
// 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<std::vector<std::string>> 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());
}
}
+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) {
@@ -3290,9 +3455,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();
@@ -3301,9 +3468,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<android::os::IIncidentCompanion>(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");
}
@@ -3372,7 +3542,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();
@@ -3443,7 +3613,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;
}
@@ -3973,14 +4143,14 @@ int Dumpstate::DumpFile(const std::string& title, const std::string& path) {
}
int read_file_as_long(const char *path, long int *output) {
- int fd = TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
- if (fd < 0) {
+ android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(path, O_RDONLY | O_NONBLOCK | O_CLOEXEC)));
+ if (fd.get() < 0) {
int err = errno;
MYLOGE("Error opening file descriptor for %s: %s\n", path, strerror(err));
return -1;
}
char buffer[50];
- ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
+ ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd.get(), buffer, sizeof(buffer)));
if (bytes_read == -1) {
MYLOGE("Error reading file %s: %s\n", path, strerror(errno));
return -2;
diff --git a/cmds/dumpstate/dumpstate.h b/cmds/dumpstate/dumpstate.h
index 7ffe80eba3..de732c0dd9 100644
--- a/cmds/dumpstate/dumpstate.h
+++ b/cmds/dumpstate/dumpstate.h
@@ -201,9 +201,20 @@ class Dumpstate {
BUGREPORT_WEAR = android::os::IDumpstate::BUGREPORT_MODE_WEAR,
BUGREPORT_TELEPHONY = android::os::IDumpstate::BUGREPORT_MODE_TELEPHONY,
BUGREPORT_WIFI = android::os::IDumpstate::BUGREPORT_MODE_WIFI,
+ BUGREPORT_ONBOARDING = android::os::IDumpstate::BUGREPORT_MODE_ONBOARDING,
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,
+ BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
+ android::os::IDumpstate::BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
+ };
+
static android::os::dumpstate::CommandOptions DEFAULT_DUMPSYS;
static Dumpstate& GetInstance();
@@ -333,6 +344,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 +358,16 @@ 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,
+ const bool keep_bugreport_on_retrieval);
+
+
+
RunStatus ParseCommandlineAndRun(int argc, char* argv[]);
/* Deletes in-progress files */
@@ -384,10 +411,12 @@ 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;
bool wifi_only = false;
+ bool onboarding_only = false;
// Trimmed-down version of dumpstate to only include whitelisted logs.
bool limited_only = false;
// Whether progress updates should be published.
@@ -396,6 +425,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 +445,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);
@@ -444,11 +476,6 @@ class Dumpstate {
// Whether it should take an screenshot earlier in the process.
bool do_early_screenshot_ = false;
- // This is set to true when the trace snapshot request in the early call to
- // MaybeSnapshotSystemTrace(). When this is true, the later stages of
- // dumpstate will append the trace to the zip archive.
- bool has_system_trace_ = false;
-
std::unique_ptr<Progress> progress_;
// When set, defines a socket file-descriptor use to report progress to bugreportz
@@ -499,6 +526,9 @@ class Dumpstate {
// List of open ANR dump files.
std::vector<DumpData> anr_data_;
+ // List of open Java traces files in the anr directory.
+ std::vector<DumpData> anr_trace_data_;
+
// List of open shutdown checkpoint files.
std::vector<DumpData> shutdown_checkpoints_;
@@ -533,12 +563,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,
+ const bool keep_bugreport_on_retrieval);
RunStatus DumpstateDefaultAfterCritical();
+ RunStatus dumpstate();
void MaybeTakeEarlyScreenshot();
void MaybeSnapshotSystemTrace();
- void MaybeSnapshotWinTrace();
+ void MaybeSnapshotUiTraces();
+ void MaybeAddUiTracesToZip();
void onUiIntensiveBugreportDumpsFinished(int32_t calling_uid);
@@ -554,6 +588,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/dumpstate.rc b/cmds/dumpstate/dumpstate.rc
index a80da4ec55..d0030dd820 100644
--- a/cmds/dumpstate/dumpstate.rc
+++ b/cmds/dumpstate/dumpstate.rc
@@ -8,6 +8,7 @@ service dumpstate /system/bin/dumpstate -s
socket dumpstate stream 0660 shell log
disabled
oneshot
+ user root
# dumpstatez generates a zipped bugreport but also uses a socket to print the file location once
# it is finished.
@@ -16,9 +17,11 @@ service dumpstatez /system/bin/dumpstate -S
class main
disabled
oneshot
+ user root
# bugreportd starts dumpstate binder service and makes it wait for a listener to connect.
service bugreportd /system/bin/dumpstate -w
class main
disabled
oneshot
+ user root
diff --git a/cmds/dumpstate/main.cpp b/cmds/dumpstate/main.cpp
index ec89c0dd6e..a634f9371f 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 28e5ee2ca9..ccf64fe54e 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<std::mutex> 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<DumpstateListener> 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<DumpstateListener> 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<DumpstateListener> 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<DumpstateListener> 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 7234d419d6..fc828864d5 100644
--- a/cmds/dumpstate/tests/dumpstate_test.cpp
+++ b/cmds/dumpstate/tests/dumpstate_test.cpp
@@ -31,6 +31,7 @@
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
+#include <filesystem>
#include <thread>
#include <aidl/android/hardware/dumpstate/IDumpstateDevice.h>
@@ -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,14 +280,14 @@ 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);
// Other options retain default values
- EXPECT_TRUE(options_.do_vibrate);
EXPECT_FALSE(options_.progress_updates_to_socket);
+ EXPECT_FALSE(options_.do_vibrate);
EXPECT_FALSE(options_.show_header_only);
EXPECT_FALSE(options_.is_remote_mode);
EXPECT_FALSE(options_.stream_to_socket);
@@ -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,23 @@ 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<std::filesystem::path> uiTraces = {
+ 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 +1078,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/dumpsys/OWNERS b/cmds/dumpsys/OWNERS
index 97a63ca8fe..03143ae659 100644
--- a/cmds/dumpsys/OWNERS
+++ b/cmds/dumpsys/OWNERS
@@ -1,6 +1,6 @@
set noparent
-gavincorkery@google.com
+ronish@google.com
nandana@google.com
jsharkey@android.com
diff --git a/cmds/dumpsys/dumpsys.cpp b/cmds/dumpsys/dumpsys.cpp
index 3d2bdf1d6f..6c4e4b3bc3 100644
--- a/cmds/dumpsys/dumpsys.cpp
+++ b/cmds/dumpsys/dumpsys.cpp
@@ -543,7 +543,7 @@ status_t Dumpsys::writeDump(int fd, const String16& serviceName, std::chrono::mi
if ((status == TIMED_OUT) && (!asProto)) {
std::string msg = StringPrintf("\n*** SERVICE '%s' DUMP TIMEOUT (%llums) EXPIRED ***\n\n",
- String8(serviceName).string(), timeout.count());
+ String8(serviceName).c_str(), timeout.count());
WriteStringToFd(msg, fd);
}
@@ -562,6 +562,6 @@ void Dumpsys::writeDumpFooter(int fd, const String16& serviceName,
oss << std::put_time(&finish_tm, "%Y-%m-%d %H:%M:%S");
std::string msg =
StringPrintf("--------- %.3fs was the duration of dumpsys %s, ending at: %s\n",
- elapsedDuration.count(), String8(serviceName).string(), oss.str().c_str());
+ elapsedDuration.count(), String8(serviceName).c_str(), oss.str().c_str());
WriteStringToFd(msg, fd);
}
diff --git a/cmds/evemu-record/Android.bp b/cmds/evemu-record/Android.bp
new file mode 100644
index 0000000000..1edacec384
--- /dev/null
+++ b/cmds/evemu-record/Android.bp
@@ -0,0 +1,13 @@
+package {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_binary {
+ name: "evemu-record",
+ srcs: ["main.rs"],
+ rustlibs: [
+ "libclap",
+ "liblibc",
+ "libnix",
+ ],
+}
diff --git a/cmds/evemu-record/OWNERS b/cmds/evemu-record/OWNERS
new file mode 100644
index 0000000000..c88bfe97ca
--- /dev/null
+++ b/cmds/evemu-record/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/cmds/evemu-record/evdev.rs b/cmds/evemu-record/evdev.rs
new file mode 100644
index 0000000000..35feea1930
--- /dev/null
+++ b/cmds/evemu-record/evdev.rs
@@ -0,0 +1,299 @@
+/*
+ * 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.
+ */
+
+//! Wrappers for the Linux evdev APIs.
+
+use std::fs::File;
+use std::io;
+use std::mem;
+use std::os::fd::{AsRawFd, OwnedFd};
+use std::path::Path;
+
+use libc::c_int;
+use nix::sys::time::TimeVal;
+
+pub const SYN_CNT: usize = 0x10;
+pub const KEY_CNT: usize = 0x300;
+pub const REL_CNT: usize = 0x10;
+pub const ABS_CNT: usize = 0x40;
+pub const MSC_CNT: usize = 0x08;
+pub const SW_CNT: usize = 0x11;
+pub const LED_CNT: usize = 0x10;
+pub const SND_CNT: usize = 0x08;
+pub const REP_CNT: usize = 0x02;
+
+// Disable naming warnings, as these are supposed to match the EV_ constants in input-event-codes.h.
+#[allow(non_camel_case_types)]
+// Some of these types aren't referenced for evemu purposes, but are included for completeness.
+#[allow(dead_code)]
+#[derive(Clone, Copy, Debug, Eq, PartialEq)]
+#[repr(u16)]
+pub enum EventType {
+ SYN = 0x00,
+ KEY = 0x01,
+ REL = 0x02,
+ ABS = 0x03,
+ MSC = 0x04,
+ SW = 0x05,
+ LED = 0x11,
+ SND = 0x12,
+ REP = 0x14,
+ FF = 0x15,
+ PWR = 0x16,
+ FF_STATUS = 0x17,
+}
+
+impl EventType {
+ fn code_count(&self) -> usize {
+ match self {
+ EventType::SYN => SYN_CNT,
+ EventType::KEY => KEY_CNT,
+ EventType::REL => REL_CNT,
+ EventType::ABS => ABS_CNT,
+ EventType::MSC => MSC_CNT,
+ EventType::SW => SW_CNT,
+ EventType::LED => LED_CNT,
+ EventType::SND => SND_CNT,
+ EventType::REP => REP_CNT,
+ _ => {
+ panic!("Event type {self:?} does not have a defined code count.");
+ }
+ }
+ }
+}
+
+pub const EVENT_TYPES_WITH_BITMAPS: [EventType; 7] = [
+ EventType::KEY,
+ EventType::REL,
+ EventType::ABS,
+ EventType::MSC,
+ EventType::SW,
+ EventType::LED,
+ EventType::SND,
+];
+
+const INPUT_PROP_CNT: usize = 32;
+
+/// The `ioctl_*!` macros create public functions by default, so this module makes them private.
+mod ioctl {
+ use nix::{ioctl_read, ioctl_read_buf};
+
+ ioctl_read!(eviocgid, b'E', 0x02, super::DeviceId);
+ ioctl_read_buf!(eviocgname, b'E', 0x06, u8);
+ ioctl_read_buf!(eviocgprop, b'E', 0x09, u8);
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct DeviceId {
+ pub bus_type: u16,
+ pub vendor: u16,
+ pub product: u16,
+ pub version: u16,
+}
+
+#[derive(Default)]
+#[repr(C)]
+pub struct AbsoluteAxisInfo {
+ pub value: i32,
+ pub minimum: i32,
+ pub maximum: i32,
+ pub fuzz: i32,
+ pub flat: i32,
+ pub resolution: i32,
+}
+
+#[repr(C)]
+pub struct InputEvent {
+ pub time: TimeVal,
+ pub type_: u16,
+ pub code: u16,
+ pub value: i32,
+}
+
+impl InputEvent {
+ pub fn offset_time_by(&self, offset: TimeVal) -> InputEvent {
+ InputEvent { time: self.time - offset, ..*self }
+ }
+}
+
+impl Default for InputEvent {
+ fn default() -> Self {
+ InputEvent { time: TimeVal::new(0, 0), type_: 0, code: 0, value: 0 }
+ }
+}
+
+/// An object representing an input device using Linux's evdev protocol.
+pub struct Device {
+ fd: OwnedFd,
+}
+
+/// # Safety
+///
+/// `ioctl` must be safe to call with the given file descriptor and a pointer to a buffer of
+/// `initial_buf_size` `u8`s.
+unsafe fn buf_from_ioctl(
+ ioctl: unsafe fn(c_int, &mut [u8]) -> nix::Result<c_int>,
+ fd: &OwnedFd,
+ initial_buf_size: usize,
+) -> Result<Vec<u8>, nix::errno::Errno> {
+ let mut buf = vec![0; initial_buf_size];
+ // SAFETY:
+ // Here we're relying on the safety guarantees for `ioctl` made by the caller.
+ match unsafe { ioctl(fd.as_raw_fd(), buf.as_mut_slice()) } {
+ Ok(len) if len < 0 => {
+ panic!("ioctl returned invalid length {len}");
+ }
+ Ok(len) => {
+ buf.truncate(len as usize);
+ Ok(buf)
+ }
+ Err(err) => Err(err),
+ }
+}
+
+impl Device {
+ /// Opens a device from the evdev node at the given path.
+ pub fn open(path: &Path) -> io::Result<Device> {
+ Ok(Device { fd: OwnedFd::from(File::open(path)?) })
+ }
+
+ /// Returns the name of the device, as set by the relevant kernel driver.
+ pub fn name(&self) -> Result<String, nix::errno::Errno> {
+ // There's no official maximum length for evdev device names. The Linux HID driver
+ // currently supports names of at most 151 bytes (128 from the device plus a suffix of up
+ // to 23 bytes). 256 seems to be the buffer size most commonly used in evdev bindings, so
+ // we use it here.
+ //
+ // SAFETY:
+ // We know that fd is a valid file descriptor as it comes from a File that we have open.
+ //
+ // The ioctl_read_buf macro prevents the retrieved data from overflowing the buffer created
+ // by buf_from_ioctl by passing in the size to the ioctl, meaning that the kernel's
+ // str_to_user function will truncate the string to that length.
+ let mut buf = unsafe { buf_from_ioctl(ioctl::eviocgname, &self.fd, 256)? };
+ assert!(!buf.is_empty(), "buf is too short for an empty null-terminated string");
+ assert_eq!(buf.pop().unwrap(), 0, "buf is not a null-terminated string");
+ Ok(String::from_utf8_lossy(buf.as_slice()).into_owned())
+ }
+
+ pub fn ids(&self) -> Result<DeviceId, nix::errno::Errno> {
+ let mut ids = DeviceId::default();
+ // SAFETY:
+ // We know that fd is a valid file descriptor as it comes from a File that we have open.
+ //
+ // We know that the pointer to ids is valid because we just allocated it.
+ unsafe { ioctl::eviocgid(self.fd.as_raw_fd(), &mut ids) }.map(|_| ids)
+ }
+
+ pub fn properties_bitmap(&self) -> Result<Vec<u8>, nix::errno::Errno> {
+ let buf_size = (INPUT_PROP_CNT + 7) / 8;
+ // SAFETY:
+ // We know that fd is a valid file descriptor as it comes from a File that we have open.
+ //
+ // The ioctl_read_buf macro prevents the retrieved data from overflowing the buffer created
+ // by buf_from_ioctl by passing in the size to the ioctl, meaning that the kernel's
+ // str_to_user function will truncate the string to that length.
+ unsafe { buf_from_ioctl(ioctl::eviocgprop, &self.fd, buf_size) }
+ }
+
+ pub fn bitmap_for_event_type(&self, event_type: EventType) -> nix::Result<Vec<u8>> {
+ let buf_size = (event_type.code_count() + 7) / 8;
+ let mut buf = vec![0; buf_size];
+
+ // The EVIOCGBIT ioctl can't be bound using ioctl_read_buf! like the others, since it uses
+ // part of its ioctl code as an additional parameter, for the event type. Hence this unsafe
+ // block is a manual expansion of ioctl_read_buf!.
+ //
+ // SAFETY:
+ // We know that fd is a valid file descriptor as it comes from a File that we have open.
+ //
+ // We prevent the retrieved data from overflowing buf by passing in the size of buf to the
+ // ioctl, meaning that the kernel's str_to_user function will truncate the string to that
+ // length. We also panic if the ioctl returns a length longer than buf, hopefully before the
+ // overflow can do any damage.
+ match nix::errno::Errno::result(unsafe {
+ libc::ioctl(
+ self.fd.as_raw_fd(),
+ nix::request_code_read!(b'E', 0x20 + event_type as u16, buf.len())
+ as nix::sys::ioctl::ioctl_num_type,
+ buf.as_mut_ptr(),
+ )
+ }) {
+ Ok(len) if len < 0 => {
+ panic!("EVIOCGBIT returned invalid length {len} for event type {event_type:?}");
+ }
+ Ok(len) => {
+ buf.truncate(len as usize);
+ Ok(buf)
+ }
+ Err(err) => Err(err),
+ }
+ }
+
+ pub fn supported_axes_of_type(&self, event_type: EventType) -> nix::Result<Vec<u16>> {
+ let mut axes = Vec::new();
+ for (i, byte_ref) in self.bitmap_for_event_type(event_type)?.iter().enumerate() {
+ let mut byte = *byte_ref;
+ for j in 0..8 {
+ if byte & 1 == 1 {
+ axes.push((i * 8 + j) as u16);
+ }
+ byte >>= 1;
+ }
+ }
+ Ok(axes)
+ }
+
+ pub fn absolute_axis_info(&self, axis: u16) -> nix::Result<AbsoluteAxisInfo> {
+ let mut info = AbsoluteAxisInfo::default();
+ // The EVIOCGABS ioctl can't be bound using ioctl_read! since it uses part of its ioctl code
+ // as an additional parameter, for the axis code. Hence this unsafe block is a manual
+ // expansion of ioctl_read!.
+ //
+ // SAFETY:
+ // We know that fd is a valid file descriptor as it comes from a File that we have open.
+ //
+ // We know that the pointer to info is valid because we just allocated it.
+ nix::errno::Errno::result(unsafe {
+ nix::libc::ioctl(
+ self.fd.as_raw_fd(),
+ nix::request_code_read!(b'E', 0x40 + axis, mem::size_of::<AbsoluteAxisInfo>()),
+ &mut info,
+ )
+ })
+ .map(|_| info)
+ }
+
+ pub fn read_event(&self) -> nix::Result<InputEvent> {
+ let mut event = InputEvent::default();
+
+ // SAFETY:
+ // We know that fd is a valid file descriptor as it comes from a File that we have open.
+ //
+ // We know that the pointer to event is valid because we just allocated it, and that the
+ // data structures match up because InputEvent is repr(C) and all its members are repr(C)
+ // or primitives that support all representations without niches.
+ nix::errno::Errno::result(unsafe {
+ libc::read(
+ self.fd.as_raw_fd(),
+ &mut event as *mut _ as *mut std::ffi::c_void,
+ mem::size_of::<InputEvent>(),
+ )
+ })
+ .map(|_| event)
+ }
+}
diff --git a/cmds/evemu-record/main.rs b/cmds/evemu-record/main.rs
new file mode 100644
index 0000000000..db3fd77520
--- /dev/null
+++ b/cmds/evemu-record/main.rs
@@ -0,0 +1,213 @@
+/*
+ * 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.
+ */
+
+//! A Rust implementation of the evemu-record command from the [FreeDesktop evemu suite][evemu] of
+//! tools.
+//!
+//! [evemu]: https://gitlab.freedesktop.org/libevdev/evemu
+
+use std::cmp;
+use std::error::Error;
+use std::fs;
+use std::io;
+use std::io::{BufRead, Write};
+use std::path::PathBuf;
+
+use clap::{Parser, ValueEnum};
+use nix::sys::time::TimeVal;
+
+mod evdev;
+
+/// Records evdev events from an input device in a format compatible with the FreeDesktop evemu
+/// library.
+#[derive(Parser, Debug)]
+struct Args {
+ /// The path to the input device to record. If omitted, offers a list of devices to choose from.
+ device: Option<PathBuf>,
+ /// The file to save the recording to. Defaults to standard output.
+ output_file: Option<PathBuf>,
+
+ /// The base time that timestamps should be relative to (Android-specific extension)
+ #[arg(long, value_enum, default_value_t = TimestampBase::FirstEvent)]
+ timestamp_base: TimestampBase,
+}
+
+#[derive(Clone, Debug, ValueEnum)]
+enum TimestampBase {
+ /// The first event received from the device.
+ FirstEvent,
+
+ /// The time when the system booted.
+ Boot,
+}
+
+fn get_choice(max: u32) -> u32 {
+ fn read_u32() -> Result<u32, std::num::ParseIntError> {
+ io::stdin().lock().lines().next().unwrap().unwrap().parse::<u32>()
+ }
+ let mut choice = read_u32();
+ while choice.is_err() || choice.clone().unwrap() > max {
+ eprint!("Enter a number between 0 and {max} inclusive: ");
+ choice = read_u32();
+ }
+ choice.unwrap()
+}
+
+fn pick_input_device() -> Result<PathBuf, io::Error> {
+ eprintln!("Available devices:");
+ let mut entries =
+ fs::read_dir("/dev/input")?.filter_map(|entry| entry.ok()).collect::<Vec<_>>();
+ entries.sort_by_key(|entry| entry.path());
+ let mut highest_number = 0;
+ for entry in entries {
+ let path = entry.path();
+ let file_name = path.file_name().unwrap().to_str().unwrap();
+ if path.is_dir() || !file_name.starts_with("event") {
+ continue;
+ }
+ let number = file_name.strip_prefix("event").unwrap().parse::<u32>();
+ if number.is_err() {
+ continue;
+ }
+ let number = number.unwrap();
+ match evdev::Device::open(path.as_path()) {
+ Ok(dev) => {
+ highest_number = cmp::max(highest_number, number);
+ eprintln!(
+ "{}:\t{}",
+ path.display(),
+ dev.name().unwrap_or("[could not read name]".to_string()),
+ );
+ }
+ Err(_) => {
+ eprintln!("Couldn't open {}", path.display());
+ }
+ }
+ }
+ eprint!("Select the device event number [0-{highest_number}]: ");
+ let choice = get_choice(highest_number);
+ Ok(PathBuf::from(format!("/dev/input/event{choice}")))
+}
+
+fn print_device_description(
+ device: &evdev::Device,
+ output: &mut impl Write,
+) -> Result<(), Box<dyn Error>> {
+ // TODO(b/302297266): report LED and SW states, then bump the version to EVEMU 1.3.
+ writeln!(output, "# EVEMU 1.2")?;
+ writeln!(output, "N: {}", device.name()?)?;
+
+ let ids = device.ids()?;
+ writeln!(
+ output,
+ "I: {:04x} {:04x} {:04x} {:04x}",
+ ids.bus_type, ids.vendor, ids.product, ids.version,
+ )?;
+
+ fn print_in_8_byte_chunks(
+ output: &mut impl Write,
+ prefix: &str,
+ data: &[u8],
+ ) -> Result<(), io::Error> {
+ for (i, byte) in data.iter().enumerate() {
+ if i % 8 == 0 {
+ write!(output, "{prefix}")?;
+ }
+ write!(output, " {:02x}", byte)?;
+ if (i + 1) % 8 == 0 {
+ writeln!(output)?;
+ }
+ }
+ if data.len() % 8 != 0 {
+ for _ in (data.len() % 8)..8 {
+ write!(output, " 00")?;
+ }
+ writeln!(output)?;
+ }
+ Ok(())
+ }
+
+ let props = device.properties_bitmap()?;
+ print_in_8_byte_chunks(output, "P:", &props)?;
+
+ // The SYN event type can't be queried through the EVIOCGBIT ioctl, so just hard-code it to
+ // SYN_REPORT, SYN_CONFIG, and SYN_DROPPED.
+ writeln!(output, "B: 00 0b 00 00 00 00 00 00 00")?;
+ for event_type in evdev::EVENT_TYPES_WITH_BITMAPS {
+ let bits = device.bitmap_for_event_type(event_type)?;
+ print_in_8_byte_chunks(output, format!("B: {:02x}", event_type as u16).as_str(), &bits)?;
+ }
+
+ for axis in device.supported_axes_of_type(evdev::EventType::ABS)? {
+ let info = device.absolute_axis_info(axis)?;
+ writeln!(
+ output,
+ "A: {axis:02x} {} {} {} {} {}",
+ info.minimum, info.maximum, info.fuzz, info.flat, info.resolution
+ )?;
+ }
+ Ok(())
+}
+
+fn print_events(
+ device: &evdev::Device,
+ output: &mut impl Write,
+ timestamp_base: TimestampBase,
+) -> Result<(), Box<dyn Error>> {
+ fn print_event(output: &mut impl Write, event: &evdev::InputEvent) -> Result<(), io::Error> {
+ // TODO(b/302297266): Translate events into human-readable names and add those as comments.
+ writeln!(
+ output,
+ "E: {}.{:06} {:04x} {:04x} {:04}",
+ event.time.tv_sec(),
+ event.time.tv_usec(),
+ event.type_,
+ event.code,
+ event.value,
+ )?;
+ Ok(())
+ }
+ let event = device.read_event()?;
+ let start_time = match timestamp_base {
+ // Due to a bug in the C implementation of evemu-play [0] that has since become part of the
+ // API, the timestamp of the first event in a recording shouldn't be exactly 0.0 seconds,
+ // so offset it by 1µs.
+ //
+ // [0]: https://gitlab.freedesktop.org/libevdev/evemu/-/commit/eba96a4d2be7260b5843e65c4b99c8b06a1f4c9d
+ TimestampBase::FirstEvent => event.time - TimeVal::new(0, 1),
+ TimestampBase::Boot => TimeVal::new(0, 0),
+ };
+ print_event(output, &event.offset_time_by(start_time))?;
+ loop {
+ let event = device.read_event()?;
+ print_event(output, &event.offset_time_by(start_time))?;
+ }
+}
+
+fn main() -> Result<(), Box<dyn Error>> {
+ let args = Args::parse();
+
+ let device_path = args.device.unwrap_or_else(|| pick_input_device().unwrap());
+
+ let device = evdev::Device::open(device_path.as_path())?;
+ let mut output = match args.output_file {
+ Some(path) => Box::new(fs::File::create(path)?) as Box<dyn Write>,
+ None => Box::new(io::stdout().lock()),
+ };
+ print_device_description(&device, &mut output)?;
+ print_events(&device, &mut output, args.timestamp_base)?;
+ Ok(())
+}
diff --git a/cmds/flatland/Android.bp b/cmds/flatland/Android.bp
new file mode 100644
index 0000000000..39a0d752a5
--- /dev/null
+++ b/cmds/flatland/Android.bp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "frameworks_native_license",
+ ],
+}
+
+cc_benchmark {
+ name: "flatland",
+ auto_gen_config: false,
+ srcs: [
+ "Composers.cpp",
+ "GLHelper.cpp",
+ "Renderers.cpp",
+ "Main.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ stem: "flatland",
+ },
+ lib64: {
+ stem: "flatland64",
+ },
+ },
+ shared_libs: [
+ "libEGL",
+ "libGLESv2",
+ "libcutils",
+ "libgui",
+ "libui",
+ "libutils",
+ ],
+}
diff --git a/cmds/flatland/Android.mk b/cmds/flatland/Android.mk
deleted file mode 100644
index 754a99caf6..0000000000
--- a/cmds/flatland/Android.mk
+++ /dev/null
@@ -1,32 +0,0 @@
-local_target_dir := $(TARGET_OUT_DATA)/local/tmp
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_SRC_FILES:= \
- Composers.cpp \
- GLHelper.cpp \
- Renderers.cpp \
- Main.cpp \
-
-LOCAL_CFLAGS := -Wall -Werror
-
-LOCAL_MODULE:= flatland
-LOCAL_LICENSE_KINDS:= SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS:= notice
-LOCAL_NOTICE_FILE:= $(LOCAL_PATH)/../../NOTICE
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_MODULE_PATH := $(local_target_dir)
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := flatland
-LOCAL_MODULE_STEM_64 := flatland64
-LOCAL_SHARED_LIBRARIES := \
- libEGL \
- libGLESv2 \
- libcutils \
- libgui \
- libui \
- libutils \
-
-include $(BUILD_EXECUTABLE)
diff --git a/cmds/flatland/GLHelper.cpp b/cmds/flatland/GLHelper.cpp
index 01f7d30a42..c163095c50 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<IBinder>& 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<IBinder> 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 d09463a9b8..5194f5084f 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<IBinder>& displayToken, const ShaderDesc* shaderDescs, size_t numShaders);
void tearDown();
@@ -87,6 +87,8 @@ private:
size_t mNumShaders;
GLuint mDitherTexture;
+
+ sp<IBinder> mDisplayToken;
};
} // namespace android
diff --git a/cmds/flatland/Main.cpp b/cmds/flatland/Main.cpp
index 7ceb397032..6d14d568a4 100644
--- a/cmds/flatland/Main.cpp
+++ b/cmds/flatland/Main.cpp
@@ -20,6 +20,7 @@
#include <gui/SurfaceControl.h>
#include <gui/GLConsumer.h>
#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
#include <ui/Fence.h>
#include <utils/Trace.h>
@@ -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<IBinder> 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<PhysicalDisplayId> 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<PhysicalDisplayId>(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/idlcli/vibrator.h b/cmds/idlcli/vibrator.h
index dfbb8863bd..e100eacdd5 100644
--- a/cmds/idlcli/vibrator.h
+++ b/cmds/idlcli/vibrator.h
@@ -74,7 +74,7 @@ inline auto getService<android::hardware::vibrator::V1_3::IVibrator>(std::string
}
template <typename I>
-using shared_ptr = std::result_of_t<decltype(getService<I>)&(std::string)>;
+using shared_ptr = std::invoke_result_t<decltype(getService<I>)&, std::string>;
template <typename I>
class HalWrapper {
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp
index ac101ecb29..334bae44e3 100644
--- a/cmds/installd/Android.bp
+++ b/cmds/installd/Android.bp
@@ -34,7 +34,6 @@ cc_defaults {
"unique_file.cpp",
"utils.cpp",
"utils_default.cpp",
- "view_compiler.cpp",
":installd_aidl",
],
shared_libs: [
@@ -254,7 +253,6 @@ cc_binary {
"unique_file.cpp",
"utils.cpp",
"utils_default.cpp",
- "view_compiler.cpp",
],
static_libs: [
diff --git a/cmds/installd/CrateManager.cpp b/cmds/installd/CrateManager.cpp
index b17cba15d5..fd1df35aee 100644
--- a/cmds/installd/CrateManager.cpp
+++ b/cmds/installd/CrateManager.cpp
@@ -29,9 +29,10 @@
#include <sys/xattr.h>
#include <unistd.h>
+#include <utils.h>
#include <fstream>
+#include <functional>
#include <string>
-#include <utils.h>
#include "utils.h"
diff --git a/cmds/installd/CrateManager.h b/cmds/installd/CrateManager.h
index 1f30b5dc79..d9b590f784 100644
--- a/cmds/installd/CrateManager.h
+++ b/cmds/installd/CrateManager.h
@@ -25,6 +25,7 @@
#include <sys/stat.h>
#include <sys/types.h>
+#include <functional>
#include <optional>
#include <string>
#include <vector>
diff --git a/cmds/installd/InstalldNativeService.cpp b/cmds/installd/InstalldNativeService.cpp
index bb6639e1a8..4486bd6d3e 100644
--- a/cmds/installd/InstalldNativeService.cpp
+++ b/cmds/installd/InstalldNativeService.cpp
@@ -19,6 +19,7 @@
#include <errno.h>
#include <fts.h>
#include <inttypes.h>
+#include <linux/fsverity.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -39,6 +40,7 @@
#include <fstream>
#include <functional>
#include <regex>
+#include <thread>
#include <unordered_set>
#include <android-base/file.h>
@@ -51,6 +53,7 @@
#include <android-base/unique_fd.h>
#include <cutils/ashmem.h>
#include <cutils/fs.h>
+#include <cutils/misc.h>
#include <cutils/properties.h>
#include <cutils/sched_policy.h>
#include <linux/quota.h>
@@ -67,7 +70,6 @@
#include "installd_deps.h"
#include "otapreopt_utils.h"
#include "utils.h"
-#include "view_compiler.h"
#include "CacheTracker.h"
#include "CrateManager.h"
@@ -84,6 +86,8 @@
using android::base::ParseUint;
using android::base::Split;
using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::os::ParcelFileDescriptor;
using std::endl;
namespace android {
@@ -229,6 +233,14 @@ binder::Status checkArgumentFileName(const std::string& path) {
return ok();
}
+binder::Status checkArgumentAppId(int32_t appId) {
+ if (FIRST_APPLICATION_UID <= appId && appId <= LAST_APPLICATION_UID) {
+ return ok();
+ }
+ return exception(binder::Status::EX_ILLEGAL_ARGUMENT,
+ StringPrintf("appId %d is outside of the range", appId));
+}
+
#define ENFORCE_UID(uid) { \
binder::Status status = checkUid((uid)); \
if (!status.isOk()) { \
@@ -236,6 +248,22 @@ binder::Status checkArgumentFileName(const std::string& path) {
} \
}
+// we could have tighter checks, but this is only to avoid hard errors. Negative values are defined
+// in UserHandle.java and carry specific meanings that may not be handled by certain APIs here.
+#define ENFORCE_VALID_USER(userId) \
+ { \
+ if (static_cast<uid_t>(userId) >= std::numeric_limits<uid_t>::max() / AID_USER_OFFSET) { \
+ return error("userId invalid: " + std::to_string(userId)); \
+ } \
+ }
+
+#define ENFORCE_VALID_USER_OR_NULL(userId) \
+ { \
+ if (static_cast<uid_t>(userId) != USER_NULL) { \
+ ENFORCE_VALID_USER(userId); \
+ } \
+ }
+
#define CHECK_ARGUMENT_UUID(uuid) { \
binder::Status status = checkArgumentUuid((uuid)); \
if (!status.isOk()) { \
@@ -273,6 +301,14 @@ binder::Status checkArgumentFileName(const std::string& path) {
} \
}
+#define CHECK_ARGUMENT_APP_ID(appId) \
+ { \
+ binder::Status status = checkArgumentAppId((appId)); \
+ if (!status.isOk()) { \
+ return status; \
+ } \
+ }
+
#ifdef GRANULAR_LOCKS
/**
@@ -373,6 +409,32 @@ using PackageLockGuard = std::lock_guard<PackageLock>;
} // namespace
+binder::Status InstalldNativeService::FsveritySetupAuthToken::authenticate(
+ const ParcelFileDescriptor& authFd, int32_t uid) {
+ int open_flags = fcntl(authFd.get(), F_GETFL);
+ if (open_flags < 0) {
+ return exception(binder::Status::EX_SERVICE_SPECIFIC, "fcntl failed");
+ }
+ if ((open_flags & O_ACCMODE) != O_WRONLY && (open_flags & O_ACCMODE) != O_RDWR) {
+ return exception(binder::Status::EX_SECURITY, "Received FD with unexpected open flag");
+ }
+ if (fstat(authFd.get(), &this->mStatFromAuthFd) < 0) {
+ return exception(binder::Status::EX_SERVICE_SPECIFIC, "fstat failed");
+ }
+ if (!S_ISREG(this->mStatFromAuthFd.st_mode)) {
+ return exception(binder::Status::EX_SECURITY, "Not a regular file");
+ }
+ // Don't accept a file owned by a different app.
+ if (this->mStatFromAuthFd.st_uid != (uid_t)uid) {
+ return exception(binder::Status::EX_SERVICE_SPECIFIC, "File not owned by uid");
+ }
+ return ok();
+}
+
+bool InstalldNativeService::FsveritySetupAuthToken::isSameStat(const struct stat& st) const {
+ return memcmp(&st, &mStatFromAuthFd, sizeof(st)) == 0;
+}
+
status_t InstalldNativeService::start() {
IPCThreadState::self()->disableBackgroundScheduling(true);
status_t ret = BinderService<InstalldNativeService>::publish();
@@ -409,6 +471,49 @@ status_t InstalldNativeService::dump(int fd, const Vector<String16>& /* args */)
return NO_ERROR;
}
+constexpr const char kXattrRestoreconInProgress[] = "user.restorecon_in_progress";
+
+static std::string lgetfilecon(const std::string& path) {
+ char* context;
+ if (::lgetfilecon(path.c_str(), &context) < 0) {
+ PLOG(ERROR) << "Failed to lgetfilecon for " << path;
+ return {};
+ }
+ std::string result{context};
+ free(context);
+ return result;
+}
+
+static bool getRestoreconInProgress(const std::string& path) {
+ bool inProgress = false;
+ if (getxattr(path.c_str(), kXattrRestoreconInProgress, &inProgress, sizeof(inProgress)) !=
+ sizeof(inProgress)) {
+ if (errno != ENODATA) {
+ PLOG(ERROR) << "Failed to check in-progress restorecon for " << path;
+ }
+ return false;
+ }
+ return inProgress;
+}
+
+struct RestoreconInProgress {
+ explicit RestoreconInProgress(const std::string& path) : mPath(path) {
+ bool inProgress = true;
+ if (setxattr(mPath.c_str(), kXattrRestoreconInProgress, &inProgress, sizeof(inProgress),
+ 0) != 0) {
+ PLOG(ERROR) << "Failed to set in-progress restorecon for " << path;
+ }
+ }
+ ~RestoreconInProgress() {
+ if (removexattr(mPath.c_str(), kXattrRestoreconInProgress) < 0) {
+ PLOG(ERROR) << "Failed to clear in-progress restorecon for " << mPath;
+ }
+ }
+
+private:
+ const std::string& mPath;
+};
+
/**
* Perform restorecon of the given path, but only perform recursive restorecon
* if the label of that top-level file actually changed. This can save us
@@ -416,54 +521,71 @@ status_t InstalldNativeService::dump(int fd, const Vector<String16>& /* args */)
*/
static int restorecon_app_data_lazy(const std::string& path, const std::string& seInfo, uid_t uid,
bool existing) {
- int res = 0;
- char* before = nullptr;
- char* after = nullptr;
+ ScopedTrace tracer("restorecon-lazy");
if (!existing) {
+ ScopedTrace tracer("new-path");
if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
PLOG(ERROR) << "Failed recursive restorecon for " << path;
- goto fail;
+ return -1;
}
- return res;
+ return 0;
}
- // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by
- // libselinux. Not needed here.
- if (lgetfilecon(path.c_str(), &before) < 0) {
- PLOG(ERROR) << "Failed before getfilecon for " << path;
- goto fail;
- }
- if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
- PLOG(ERROR) << "Failed top-level restorecon for " << path;
- goto fail;
- }
- if (lgetfilecon(path.c_str(), &after) < 0) {
- PLOG(ERROR) << "Failed after getfilecon for " << path;
- goto fail;
+ // Note that SELINUX_ANDROID_RESTORECON_DATADATA flag is set by libselinux. Not needed here.
+
+ // Check to see if there was an interrupted operation.
+ bool inProgress = getRestoreconInProgress(path);
+ std::string before, after;
+ if (!inProgress) {
+ if (before = lgetfilecon(path); before.empty()) {
+ PLOG(ERROR) << "Failed before getfilecon for " << path;
+ return -1;
+ }
+ if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid, 0) < 0) {
+ PLOG(ERROR) << "Failed top-level restorecon for " << path;
+ return -1;
+ }
+ if (after = lgetfilecon(path); after.empty()) {
+ PLOG(ERROR) << "Failed after getfilecon for " << path;
+ return -1;
+ }
}
// If the initial top-level restorecon above changed the label, then go
// back and restorecon everything recursively
- if (strcmp(before, after)) {
+ if (inProgress || before != after) {
if (existing) {
LOG(DEBUG) << "Detected label change from " << before << " to " << after << " at "
- << path << "; running recursive restorecon";
+ << path << "; running recursive restorecon";
}
- if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
- SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
- PLOG(ERROR) << "Failed recursive restorecon for " << path;
- goto fail;
+
+ auto restorecon = [path, seInfo, uid]() {
+ ScopedTrace tracer("label-change");
+
+ // Temporary mark the folder as "in-progress" to resume in case of reboot/other failure.
+ RestoreconInProgress fence(path);
+
+ if (selinux_android_restorecon_pkgdir(path.c_str(), seInfo.c_str(), uid,
+ SELINUX_ANDROID_RESTORECON_RECURSE) < 0) {
+ PLOG(ERROR) << "Failed recursive restorecon for " << path;
+ return -1;
+ }
+ return 0;
+ };
+ if (inProgress) {
+ // The previous restorecon was interrupted. It's either crashed (unlikely), or the phone
+ // was rebooted. Possibly because it took too much time. This time let's move it to a
+ // separate thread - so it won't block the rest of the OS.
+ std::thread(restorecon).detach();
+ } else {
+ if (int result = restorecon(); result) {
+ return result;
+ }
}
}
- goto done;
-fail:
- res = -1;
-done:
- free(before);
- free(after);
- return res;
+ return 0;
}
static bool internal_storage_has_project_id() {
// The following path is populated in setFirstBoot, so if this file is present
@@ -480,11 +602,15 @@ static bool internal_storage_has_project_id() {
static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t uid, gid_t gid,
long project_id) {
- if (fs_prepare_dir_strict(path.c_str(), target_mode, uid, gid) != 0) {
- PLOG(ERROR) << "Failed to prepare " << path;
- return -1;
+ {
+ ScopedTrace tracer("prepare-dir");
+ if (fs_prepare_dir_strict(path.c_str(), target_mode, uid, gid) != 0) {
+ PLOG(ERROR) << "Failed to prepare " << path;
+ return -1;
+ }
}
if (internal_storage_has_project_id()) {
+ ScopedTrace tracer("set-quota");
return set_quota_project_id(path, project_id, true);
}
return 0;
@@ -493,14 +619,20 @@ static int prepare_app_dir(const std::string& path, mode_t target_mode, uid_t ui
static int prepare_app_cache_dir(const std::string& parent, const char* name, mode_t target_mode,
uid_t uid, gid_t gid, long project_id) {
auto path = StringPrintf("%s/%s", parent.c_str(), name);
- int ret = prepare_app_cache_dir(parent, name, target_mode, uid, gid);
+ int ret;
+ {
+ ScopedTrace tracer("prepare-cache-dir");
+ ret = prepare_app_cache_dir(parent, name, target_mode, uid, gid);
+ }
if (ret == 0 && internal_storage_has_project_id()) {
+ ScopedTrace tracer("set-quota-cache-dir");
return set_quota_project_id(path, project_id, true);
}
return ret;
}
static bool prepare_app_profile_dir(const std::string& packageName, int32_t appId, int32_t userId) {
+ ScopedTrace tracer("prepare-app-profile");
int32_t uid = multiuser_get_uid(userId, appId);
int shared_app_gid = multiuser_get_shared_gid(userId, appId);
if (shared_app_gid == -1) {
@@ -633,6 +765,7 @@ static binder::Status createAppDataDirs(const std::string& path, int32_t uid, in
int32_t previousUid, int32_t cacheGid,
const std::string& seInfo, mode_t targetMode,
long projectIdApp, long projectIdCache) {
+ ScopedTrace tracer("create-dirs");
struct stat st{};
bool parent_dir_exists = (stat(path.c_str(), &st) == 0);
@@ -680,8 +813,9 @@ static binder::Status createAppDataDirs(const std::string& path, int32_t uid, in
binder::Status InstalldNativeService::createAppDataLocked(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t flags, int32_t appId, int32_t previousAppId, const std::string& seInfo,
- int32_t targetSdkVersion, int64_t* _aidl_return) {
+ int32_t targetSdkVersion, int64_t* ceDataInode, int64_t* deDataInode) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
@@ -689,7 +823,8 @@ binder::Status InstalldNativeService::createAppDataLocked(
const char* pkgname = packageName.c_str();
// Assume invalid inode unless filled in below
- if (_aidl_return != nullptr) *_aidl_return = -1;
+ if (ceDataInode != nullptr) *ceDataInode = -1;
+ if (deDataInode != nullptr) *deDataInode = -1;
int32_t uid = multiuser_get_uid(userId, appId);
@@ -709,6 +844,7 @@ binder::Status InstalldNativeService::createAppDataLocked(
long projectIdCache = get_project_id(uid, PROJECT_ID_APP_CACHE_START);
if (flags & FLAG_STORAGE_CE) {
+ ScopedTrace tracer("ce");
auto path = create_data_user_ce_package_path(uuid_, userId, pkgname);
auto status = createAppDataDirs(path, uid, uid, previousUid, cacheGid, seInfo, targetMode,
@@ -726,15 +862,16 @@ binder::Status InstalldNativeService::createAppDataLocked(
// And return the CE inode of the top-level data directory so we can
// clear contents while CE storage is locked
- if (_aidl_return != nullptr) {
+ if (ceDataInode != nullptr) {
ino_t result;
if (get_path_inode(path, &result) != 0) {
return error("Failed to get_path_inode for " + path);
}
- *_aidl_return = static_cast<uint64_t>(result);
+ *ceDataInode = static_cast<uint64_t>(result);
}
}
if (flags & FLAG_STORAGE_DE) {
+ ScopedTrace tracer("de");
auto path = create_data_user_de_package_path(uuid_, userId, pkgname);
auto status = createAppDataDirs(path, uid, uid, previousUid, cacheGid, seInfo, targetMode,
@@ -749,16 +886,25 @@ binder::Status InstalldNativeService::createAppDataLocked(
if (!prepare_app_profile_dir(packageName, appId, userId)) {
return error("Failed to prepare profiles for " + packageName);
}
+
+ if (deDataInode != nullptr) {
+ ino_t result;
+ if (get_path_inode(path, &result) != 0) {
+ return error("Failed to get_path_inode for " + path);
+ }
+ *deDataInode = static_cast<uint64_t>(result);
+ }
}
if (flags & FLAG_STORAGE_SDK) {
+ ScopedTrace tracer("sdk");
// Safe to ignore status since we can retry creating this by calling reconcileSdkData
auto ignore = createSdkSandboxDataPackageDirectory(uuid, packageName, userId, appId, flags);
if (!ignore.isOk()) {
PLOG(WARNING) << "Failed to create sdk data package directory for " << packageName;
}
-
} else {
+ ScopedTrace tracer("destroy-sdk");
// Package does not need sdk storage. Remove it.
destroySdkSandboxDataPackageDirectory(uuid, packageName, userId, flags);
}
@@ -773,6 +919,8 @@ binder::Status InstalldNativeService::createAppDataLocked(
binder::Status InstalldNativeService::createSdkSandboxDataPackageDirectory(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t appId, int32_t flags) {
+ ENFORCE_VALID_USER(userId);
+
int32_t sdkSandboxUid = multiuser_get_sdk_sandbox_uid(userId, appId);
if (sdkSandboxUid == -1) {
// There no valid sdk sandbox process for this app. Skip creation of data directory
@@ -809,25 +957,30 @@ binder::Status InstalldNativeService::createSdkSandboxDataPackageDirectory(
binder::Status InstalldNativeService::createAppData(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t flags, int32_t appId, int32_t previousAppId, const std::string& seInfo,
- int32_t targetSdkVersion, int64_t* _aidl_return) {
+ int32_t targetSdkVersion, int64_t* ceDataInode, int64_t* deDataInode) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
return createAppDataLocked(uuid, packageName, userId, flags, appId, previousAppId, seInfo,
- targetSdkVersion, _aidl_return);
+ targetSdkVersion, ceDataInode, deDataInode);
}
binder::Status InstalldNativeService::createAppData(
const android::os::CreateAppDataArgs& args,
android::os::CreateAppDataResult* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(args.userId);
// Locking is performed depeer in the callstack.
int64_t ceDataInode = -1;
+ int64_t deDataInode = -1;
auto status = createAppData(args.uuid, args.packageName, args.userId, args.flags, args.appId,
- args.previousAppId, args.seInfo, args.targetSdkVersion, &ceDataInode);
+ args.previousAppId, args.seInfo, args.targetSdkVersion,
+ &ceDataInode, &deDataInode);
_aidl_return->ceDataInode = ceDataInode;
+ _aidl_return->deDataInode = deDataInode;
_aidl_return->exceptionCode = status.exceptionCode();
_aidl_return->exceptionMessage = status.exceptionMessage();
return ok();
@@ -837,6 +990,10 @@ binder::Status InstalldNativeService::createAppDataBatched(
const std::vector<android::os::CreateAppDataArgs>& args,
std::vector<android::os::CreateAppDataResult>* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ for (const auto& arg : args) {
+ ENFORCE_VALID_USER(arg.userId);
+ }
+
// Locking is performed depeer in the callstack.
std::vector<android::os::CreateAppDataResult> results;
@@ -851,6 +1008,7 @@ binder::Status InstalldNativeService::createAppDataBatched(
binder::Status InstalldNativeService::reconcileSdkData(
const android::os::ReconcileSdkDataArgs& args) {
+ ENFORCE_VALID_USER(args.userId);
// Locking is performed depeer in the callstack.
return reconcileSdkData(args.uuid, args.packageName, args.subDirNames, args.userId, args.appId,
@@ -874,6 +1032,7 @@ binder::Status InstalldNativeService::reconcileSdkData(const std::optional<std::
int userId, int appId, int previousAppId,
const std::string& seInfo, int flags) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -957,6 +1116,7 @@ binder::Status InstalldNativeService::reconcileSdkData(const std::optional<std::
binder::Status InstalldNativeService::migrateAppData(const std::optional<std::string>& uuid,
const std::string& packageName, int32_t userId, int32_t flags) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -1024,6 +1184,7 @@ binder::Status InstalldNativeService::clearAppProfiles(const std::string& packag
binder::Status InstalldNativeService::clearAppData(const std::optional<std::string>& uuid,
const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -1115,6 +1276,7 @@ binder::Status InstalldNativeService::clearAppData(const std::optional<std::stri
binder::Status InstalldNativeService::clearSdkSandboxDataPackageDirectory(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t flags) {
+ ENFORCE_VALID_USER(userId);
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
const char* pkgname = packageName.c_str();
@@ -1201,6 +1363,7 @@ binder::Status InstalldNativeService::deleteReferenceProfile(const std::string&
binder::Status InstalldNativeService::destroyAppData(const std::optional<std::string>& uuid,
const std::string& packageName, int32_t userId, int32_t flags, int64_t ceDataInode) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -1271,6 +1434,8 @@ binder::Status InstalldNativeService::destroyAppData(const std::optional<std::st
binder::Status InstalldNativeService::destroySdkSandboxDataPackageDirectory(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t flags) {
+ ENFORCE_VALID_USER(userId);
+
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
const char* pkgname = packageName.c_str();
@@ -1418,6 +1583,7 @@ binder::Status InstalldNativeService::snapshotAppData(const std::optional<std::s
int32_t userId, int32_t snapshotId,
int32_t storageFlags, int64_t* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID_IS_TEST_OR_NULL(volumeUuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -1552,6 +1718,7 @@ binder::Status InstalldNativeService::restoreAppDataSnapshot(
const int32_t appId, const std::string& seInfo, const int32_t userId,
const int32_t snapshotId, int32_t storageFlags) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID_IS_TEST_OR_NULL(volumeUuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -1624,6 +1791,7 @@ binder::Status InstalldNativeService::destroyAppDataSnapshot(
const int32_t userId, const int64_t ceSnapshotInode, const int32_t snapshotId,
int32_t storageFlags) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID_IS_TEST_OR_NULL(volumeUuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -1657,6 +1825,7 @@ binder::Status InstalldNativeService::destroyCeSnapshotsNotSpecified(
const std::optional<std::string>& volumeUuid, const int32_t userId,
const std::vector<int32_t>& retainSnapshotIds) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID_IS_TEST_OR_NULL(volumeUuid);
LOCK_USER();
@@ -1738,7 +1907,8 @@ binder::Status InstalldNativeService::moveCompleteApp(const std::optional<std::s
}
if (!createAppDataLocked(toUuid, packageName, userId, FLAG_STORAGE_CE | FLAG_STORAGE_DE,
- appId, /* previousAppId */ -1, seInfo, targetSdkVersion, nullptr)
+ appId, /* previousAppId */ -1, seInfo, targetSdkVersion, nullptr,
+ nullptr)
.isOk()) {
res = error("Failed to create package target");
goto fail;
@@ -1847,9 +2017,12 @@ fail:
binder::Status InstalldNativeService::createUserData(const std::optional<std::string>& uuid,
int32_t userId, int32_t userSerial ATTRIBUTE_UNUSED, int32_t flags) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
LOCK_USER();
+ ScopedTrace tracer("create-user-data");
+
const char* uuid_ = uuid ? uuid->c_str() : nullptr;
if (flags & FLAG_STORAGE_DE) {
if (uuid_ == nullptr) {
@@ -1865,6 +2038,7 @@ binder::Status InstalldNativeService::createUserData(const std::optional<std::st
binder::Status InstalldNativeService::destroyUserData(const std::optional<std::string>& uuid,
int32_t userId, int32_t flags) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
LOCK_USER();
@@ -2355,11 +2529,15 @@ static void collectManualExternalStatsForUser(const std::string& path, struct st
p->fts_number = p->fts_parent->fts_number;
switch (p->fts_info) {
case FTS_D:
- if (p->fts_level == 4
+ if (p->fts_level == 3
+ && !strcmp(p->fts_parent->fts_name, "obb")
+ && !strcmp(p->fts_parent->fts_parent->fts_name, "Android")) {
+ p->fts_number = 1;
+ } else if (p->fts_level == 4
&& !strcmp(p->fts_name, "cache")
&& !strcmp(p->fts_parent->fts_parent->fts_name, "data")
&& !strcmp(p->fts_parent->fts_parent->fts_parent->fts_name, "Android")) {
- p->fts_number = 1;
+ p->fts_number = 2;
}
[[fallthrough]]; // to count the directory
case FTS_DEFAULT:
@@ -2368,9 +2546,13 @@ static void collectManualExternalStatsForUser(const std::string& path, struct st
case FTS_SLNONE:
int64_t size = (p->fts_statp->st_blocks * 512);
if (p->fts_number == 1) {
- stats->cacheSize += size;
+ stats->codeSize += size;
+ } else {
+ if (p->fts_number == 2) {
+ stats->cacheSize += size;
+ }
+ stats->dataSize += size;
}
- stats->dataSize += size;
break;
}
}
@@ -2644,6 +2826,7 @@ binder::Status InstalldNativeService::getUserSize(const std::optional<std::strin
int32_t userId, int32_t flags, const std::vector<int32_t>& appIds,
std::vector<int64_t>* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
// NOTE: Locking is relaxed on this method, since it's limited to
// read-only measurements without mutation.
@@ -2716,11 +2899,6 @@ binder::Status InstalldNativeService::getUserSize(const std::optional<std::strin
extStats.dataSize = dataSize;
atrace_pm_end();
} else {
- atrace_pm_begin("obb");
- auto obbPath = create_data_path(uuid_) + "/media/obb";
- calculate_tree_size(obbPath, &extStats.codeSize);
- atrace_pm_end();
-
atrace_pm_begin("code");
calculate_tree_size(create_data_app_path(uuid_), &stats.codeSize);
atrace_pm_end();
@@ -2751,9 +2929,10 @@ binder::Status InstalldNativeService::getUserSize(const std::optional<std::strin
atrace_pm_begin("external");
auto dataMediaPath = create_data_media_path(uuid_, userId);
collectManualExternalStatsForUser(dataMediaPath, &extStats);
+
#if MEASURE_DEBUG
LOG(DEBUG) << "Measured external data " << extStats.dataSize << " cache "
- << extStats.cacheSize;
+ << extStats.cacheSize << " code " << extStats.codeSize;
#endif
atrace_pm_end();
@@ -2783,6 +2962,7 @@ binder::Status InstalldNativeService::getExternalSize(const std::optional<std::s
int32_t userId, int32_t flags, const std::vector<int32_t>& appIds,
std::vector<int64_t>* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
// NOTE: Locking is relaxed on this method, since it's limited to
// read-only measurements without mutation.
@@ -2903,6 +3083,7 @@ binder::Status InstalldNativeService::getAppCrates(
const std::vector<std::string>& packageNames, int32_t userId,
std::optional<std::vector<std::optional<CrateMetadata>>>* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
for (const auto& packageName : packageNames) {
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
@@ -2952,6 +3133,7 @@ binder::Status InstalldNativeService::getUserCrates(
const std::optional<std::string>& uuid, int32_t userId,
std::optional<std::vector<std::optional<CrateMetadata>>>* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
#ifdef ENABLE_STORAGE_CRATES
LOCK_USER();
@@ -2995,6 +3177,7 @@ binder::Status InstalldNativeService::getUserCrates(
binder::Status InstalldNativeService::setAppQuota(const std::optional<std::string>& uuid,
int32_t userId, int32_t appId, int64_t cacheQuota) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
std::lock_guard<std::recursive_mutex> lock(mQuotasLock);
@@ -3131,17 +3314,6 @@ binder::Status InstalldNativeService::controlDexOptBlocking(bool block) {
return ok();
}
-binder::Status InstalldNativeService::compileLayouts(const std::string& apkPath,
- const std::string& packageName,
- const std ::string& outDexFile, int uid,
- bool* _aidl_return) {
- const char* apk_path = apkPath.c_str();
- const char* package_name = packageName.c_str();
- const char* out_dex_file = outDexFile.c_str();
- *_aidl_return = android::installd::view_compiler(apk_path, package_name, out_dex_file, uid);
- return *_aidl_return ? ok() : error("viewcompiler failed");
-}
-
binder::Status InstalldNativeService::linkNativeLibraryDirectory(
const std::optional<std::string>& uuid, const std::string& packageName,
const std::string& nativeLibPath32, int32_t userId) {
@@ -3168,7 +3340,7 @@ binder::Status InstalldNativeService::linkNativeLibraryDirectory(
}
char *con = nullptr;
- if (lgetfilecon(pkgdir, &con) < 0) {
+ if (::lgetfilecon(pkgdir, &con) < 0) {
return error("Failed to lgetfilecon " + _pkgdir);
}
@@ -3238,6 +3410,7 @@ binder::Status InstalldNativeService::restoreconAppData(const std::optional<std:
const std::string& packageName, int32_t userId, int32_t flags, int32_t appId,
const std::string& seInfo) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
LOCK_PACKAGE_USER();
@@ -3248,6 +3421,7 @@ binder::Status InstalldNativeService::restoreconAppDataLocked(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t flags, int32_t appId, const std::string& seInfo) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
@@ -3279,6 +3453,7 @@ binder::Status InstalldNativeService::restoreconSdkDataLocked(
const std::optional<std::string>& uuid, const std::string& packageName, int32_t userId,
int32_t flags, int32_t appId, const std::string& seInfo) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER(userId);
CHECK_ARGUMENT_UUID(uuid);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
@@ -3555,22 +3730,22 @@ binder::Status InstalldNativeService::tryMountDataMirror(
std::lock_guard<std::recursive_mutex> lock(mMountsLock);
std::string mirrorVolCePath(StringPrintf("%s/%s", kDataMirrorCePath, uuid_));
- if (fs_prepare_dir(mirrorVolCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
+ if (fs_prepare_dir(mirrorVolCePath.c_str(), 0511, AID_SYSTEM, AID_SYSTEM) != 0) {
return error("Failed to create CE data mirror");
}
std::string mirrorVolDePath(StringPrintf("%s/%s", kDataMirrorDePath, uuid_));
- if (fs_prepare_dir(mirrorVolDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
+ if (fs_prepare_dir(mirrorVolDePath.c_str(), 0511, AID_SYSTEM, AID_SYSTEM) != 0) {
return error("Failed to create DE data mirror");
}
std::string mirrorVolMiscCePath(StringPrintf("%s/%s", kMiscMirrorCePath, uuid_));
- if (fs_prepare_dir(mirrorVolMiscCePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
+ if (fs_prepare_dir(mirrorVolMiscCePath.c_str(), 0511, AID_SYSTEM, AID_SYSTEM) != 0) {
return error("Failed to create CE misc mirror");
}
std::string mirrorVolMiscDePath(StringPrintf("%s/%s", kMiscMirrorDePath, uuid_));
- if (fs_prepare_dir(mirrorVolMiscDePath.c_str(), 0711, AID_SYSTEM, AID_SYSTEM) != 0) {
+ if (fs_prepare_dir(mirrorVolMiscDePath.c_str(), 0511, AID_SYSTEM, AID_SYSTEM) != 0) {
return error("Failed to create DE misc mirror");
}
@@ -3730,6 +3905,7 @@ binder::Status InstalldNativeService::prepareAppProfile(const std::string& packa
int32_t userId, int32_t appId, const std::string& profileName, const std::string& codePath,
const std::optional<std::string>& dexMetadata, bool* _aidl_return) {
ENFORCE_UID(AID_SYSTEM);
+ ENFORCE_VALID_USER_OR_NULL(userId);
CHECK_ARGUMENT_PACKAGE_NAME(packageName);
CHECK_ARGUMENT_PATH(codePath);
LOCK_PACKAGE_USER();
@@ -3752,6 +3928,7 @@ binder::Status InstalldNativeService::migrateLegacyObbData() {
binder::Status InstalldNativeService::cleanupInvalidPackageDirs(
const std::optional<std::string>& uuid, int32_t userId, int32_t flags) {
+ ENFORCE_VALID_USER(userId);
const char* uuid_cstr = uuid ? uuid->c_str() : nullptr;
if (flags & FLAG_STORAGE_CE) {
@@ -3791,5 +3968,97 @@ binder::Status InstalldNativeService::getOdexVisibility(
return *_aidl_return == -1 ? error() : ok();
}
+// Creates an auth token to be used in enableFsverity. This token is really to store a proof that
+// the caller can write to a file, represented by the authFd. Effectively, system_server as the
+// attacker-in-the-middle cannot enable fs-verity on arbitrary app files. If the FD is not writable,
+// return null.
+//
+// app process uid is passed for additional ownership check, such that one app can not be
+// authenticated for another app's file. These parameters are assumed trusted for this purpose of
+// consistency check.
+//
+// Notably, creating the token allows us to manage the writable FD easily during enableFsverity.
+// Since enabling fs-verity to a file requires no outstanding writable FD, passing the authFd to the
+// server allows the server to hold the only reference (as long as the client app doesn't).
+binder::Status InstalldNativeService::createFsveritySetupAuthToken(
+ const ParcelFileDescriptor& authFd, int32_t uid,
+ sp<IFsveritySetupAuthToken>* _aidl_return) {
+ CHECK_ARGUMENT_APP_ID(multiuser_get_app_id(uid));
+ ENFORCE_VALID_USER(multiuser_get_user_id(uid));
+
+ auto token = sp<FsveritySetupAuthToken>::make();
+ binder::Status status = token->authenticate(authFd, uid);
+ if (!status.isOk()) {
+ return status;
+ }
+ *_aidl_return = token;
+ return ok();
+}
+
+// Enables fs-verity for filePath, which must be an absolute path and the same inode as in the auth
+// token previously returned from createFsveritySetupAuthToken, and owned by the app uid. As
+// installd is more privileged than its client / system server, we attempt to limit what a
+// (compromised) client can do.
+//
+// The reason for this app request to go through installd is to avoid exposing a risky area (PKCS#7
+// signature verification) in the kernel to the app as an attack surface (it can't be system server
+// because it can't override DAC and manipulate app files). Note that we should be able to drop
+// these hops and simply the app calls the ioctl, once all upgrading devices run with a kernel
+// without fs-verity built-in signature (https://r.android.com/2650402).
+binder::Status InstalldNativeService::enableFsverity(const sp<IFsveritySetupAuthToken>& authToken,
+ const std::string& filePath,
+ const std::string& packageName,
+ int32_t* _aidl_return) {
+ ENFORCE_UID(AID_SYSTEM);
+ CHECK_ARGUMENT_PATH(filePath);
+ CHECK_ARGUMENT_PACKAGE_NAME(packageName);
+ LOCK_PACKAGE();
+ if (authToken == nullptr) {
+ return exception(binder::Status::EX_ILLEGAL_ARGUMENT, "Received a null auth token");
+ }
+
+ // Authenticate to check the targeting file is the same inode as the authFd. With O_PATH, we
+ // prevent a malicious client from blocking installd by providing a path to FIFO. After the
+ // authentication, the actual open is safe.
+ sp<IBinder> authTokenBinder = IInterface::asBinder(authToken)->localBinder();
+ if (authTokenBinder == nullptr) {
+ return exception(binder::Status::EX_SECURITY, "Received a non-local auth token");
+ }
+ unique_fd pathFd(open(filePath.c_str(), O_RDONLY | O_CLOEXEC | O_NOFOLLOW | O_PATH));
+ // Returns a constant errno to avoid one app probing file existence of the others, before the
+ // authentication is done.
+ const int kFixedErrno = EPERM;
+ if (pathFd.get() < 0) {
+ PLOG(DEBUG) << "Failed to open the path";
+ *_aidl_return = kFixedErrno;
+ return ok();
+ }
+ std::string procFdPath(StringPrintf("/proc/self/fd/%d", pathFd.get()));
+ struct stat stFromPath;
+ if (stat(procFdPath.c_str(), &stFromPath) < 0) {
+ PLOG(DEBUG) << "Failed to stat proc fd " << pathFd.get() << " -> " << filePath;
+ *_aidl_return = kFixedErrno;
+ return ok();
+ }
+ auto authTokenInstance = sp<FsveritySetupAuthToken>::cast(authTokenBinder);
+ if (!authTokenInstance->isSameStat(stFromPath)) {
+ LOG(DEBUG) << "FD authentication failed";
+ *_aidl_return = kFixedErrno;
+ return ok();
+ }
+
+ unique_fd rfd(open(procFdPath.c_str(), O_RDONLY | O_CLOEXEC));
+ fsverity_enable_arg arg = {};
+ arg.version = 1;
+ arg.hash_algorithm = FS_VERITY_HASH_ALG_SHA256;
+ arg.block_size = 4096;
+ if (ioctl(rfd.get(), FS_IOC_ENABLE_VERITY, &arg) < 0) {
+ *_aidl_return = errno;
+ } else {
+ *_aidl_return = 0;
+ }
+ return ok();
+}
+
} // namespace installd
} // namespace android
diff --git a/cmds/installd/InstalldNativeService.h b/cmds/installd/InstalldNativeService.h
index 521afc3f97..b13d6d7580 100644
--- a/cmds/installd/InstalldNativeService.h
+++ b/cmds/installd/InstalldNativeService.h
@@ -19,6 +19,7 @@
#define COMMANDS_H_
#include <inttypes.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <shared_mutex>
@@ -35,8 +36,25 @@
namespace android {
namespace installd {
+using IFsveritySetupAuthToken = android::os::IInstalld::IFsveritySetupAuthToken;
+
class InstalldNativeService : public BinderService<InstalldNativeService>, public os::BnInstalld {
public:
+ class FsveritySetupAuthToken : public os::IInstalld::BnFsveritySetupAuthToken {
+ public:
+ FsveritySetupAuthToken() : mStatFromAuthFd() {}
+
+ binder::Status authenticate(const android::os::ParcelFileDescriptor& authFd, int32_t uid);
+ bool isSameStat(const struct stat& st) const;
+
+ private:
+ // Not copyable or movable
+ FsveritySetupAuthToken(const FsveritySetupAuthToken&) = delete;
+ FsveritySetupAuthToken& operator=(const FsveritySetupAuthToken&) = delete;
+
+ struct stat mStatFromAuthFd;
+ };
+
static status_t start();
static char const* getServiceName() { return "installd"; }
virtual status_t dump(int fd, const Vector<String16> &args) override;
@@ -49,7 +67,8 @@ public:
binder::Status createAppData(const std::optional<std::string>& uuid,
const std::string& packageName, int32_t userId, int32_t flags,
int32_t appId, int32_t previousAppId, const std::string& seInfo,
- int32_t targetSdkVersion, int64_t* _aidl_return);
+ int32_t targetSdkVersion, int64_t* ceDataInode,
+ int64_t* deDataInode);
binder::Status createAppData(
const android::os::CreateAppDataArgs& args,
@@ -126,9 +145,6 @@ public:
binder::Status controlDexOptBlocking(bool block);
- binder::Status compileLayouts(const std::string& apkPath, const std::string& packageName,
- const std::string& outDexFile, int uid, bool* _aidl_return);
-
binder::Status rmdex(const std::string& codePath, const std::string& instructionSet);
binder::Status mergeProfiles(int32_t uid, const std::string& packageName,
@@ -192,6 +208,13 @@ public:
const std::optional<std::string>& outputPath,
int32_t* _aidl_return);
+ binder::Status createFsveritySetupAuthToken(const android::os::ParcelFileDescriptor& authFd,
+ int32_t uid,
+ android::sp<IFsveritySetupAuthToken>* _aidl_return);
+ binder::Status enableFsverity(const android::sp<IFsveritySetupAuthToken>& authToken,
+ const std::string& filePath, const std::string& packageName,
+ int32_t* _aidl_return);
+
private:
std::recursive_mutex mLock;
std::unordered_map<userid_t, std::weak_ptr<std::shared_mutex>> mUserIdLock;
@@ -212,7 +235,7 @@ private:
const std::string& packageName, int32_t userId,
int32_t flags, int32_t appId, int32_t previousAppId,
const std::string& seInfo, int32_t targetSdkVersion,
- int64_t* _aidl_return);
+ int64_t* ceDataInode, int64_t* deDataInode);
binder::Status restoreconAppDataLocked(const std::optional<std::string>& uuid,
const std::string& packageName, int32_t userId,
int32_t flags, int32_t appId, const std::string& seInfo);
diff --git a/cmds/installd/OWNERS b/cmds/installd/OWNERS
index 643b2c2d11..e9fb85ba60 100644
--- a/cmds/installd/OWNERS
+++ b/cmds/installd/OWNERS
@@ -1,11 +1,10 @@
set noparent
-calin@google.com
jsharkey@android.com
maco@google.com
mast@google.com
+jiakaiz@google.com
narayan@google.com
ngeoffray@google.com
rpl@google.com
-toddke@google.com
patb@google.com
diff --git a/cmds/installd/SysTrace.h b/cmds/installd/SysTrace.h
index 18506a9258..0deaeb4341 100644
--- a/cmds/installd/SysTrace.h
+++ b/cmds/installd/SysTrace.h
@@ -19,4 +19,16 @@
namespace android::installd {
void atrace_pm_begin(const char*);
void atrace_pm_end();
+
+class ScopedTrace {
+public:
+ explicit ScopedTrace(const char* label) { atrace_pm_begin(label); }
+ ~ScopedTrace() { atrace_pm_end(); }
+
+private:
+ ScopedTrace(const ScopedTrace&) = delete;
+ ScopedTrace& operator=(const ScopedTrace&) = delete;
+ ScopedTrace(ScopedTrace&&) = delete;
+ ScopedTrace& operator=(ScopedTrace&&) = delete;
+};
} /* namespace android::installd */
diff --git a/cmds/installd/binder/android/os/CreateAppDataResult.aidl b/cmds/installd/binder/android/os/CreateAppDataResult.aidl
index 3b8fa6b9f2..463489e01e 100644
--- a/cmds/installd/binder/android/os/CreateAppDataResult.aidl
+++ b/cmds/installd/binder/android/os/CreateAppDataResult.aidl
@@ -19,6 +19,7 @@ package android.os;
/** {@hide} */
parcelable CreateAppDataResult {
long ceDataInode;
+ long deDataInode;
int exceptionCode;
@utf8InCpp String exceptionMessage;
}
diff --git a/cmds/installd/binder/android/os/IInstalld.aidl b/cmds/installd/binder/android/os/IInstalld.aidl
index 9ad853b1df..8a2f113bde 100644
--- a/cmds/installd/binder/android/os/IInstalld.aidl
+++ b/cmds/installd/binder/android/os/IInstalld.aidl
@@ -70,8 +70,6 @@ interface IInstalld {
// Blocks (when block is true) or unblock (when block is false) dexopt.
// Blocking also invloves cancelling the currently running dexopt.
void controlDexOptBlocking(boolean block);
- boolean compileLayouts(@utf8InCpp String apkPath, @utf8InCpp String packageName,
- @utf8InCpp String outDexFile, int uid);
void rmdex(@utf8InCpp String codePath, @utf8InCpp String instructionSet);
@@ -134,6 +132,21 @@ interface IInstalld {
int getOdexVisibility(@utf8InCpp String packageName, @utf8InCpp String apkPath,
@utf8InCpp String instructionSet, @nullable @utf8InCpp String outputPath);
+ interface IFsveritySetupAuthToken {
+ // Using an interface here is an easy way to create and maintain an IBinder object across
+ // the processes. When installd creates this binder object, it stores the file stat
+ // privately for later authentication, and only returns the reference to the caller process.
+ // Once the binder object has no reference count, it gets destructed automatically
+ // (alternatively, installd can maintain an internal mapping, but it is more error prone
+ // because the app may crash and not finish the fs-verity setup, keeping the memory unused
+ // forever).
+ //
+ // We don't necessarily need a method here, so it's left blank intentionally.
+ }
+ IFsveritySetupAuthToken createFsveritySetupAuthToken(in ParcelFileDescriptor authFd, int uid);
+ int enableFsverity(in IFsveritySetupAuthToken authToken, @utf8InCpp String filePath,
+ @utf8InCpp String packageName);
+
const int FLAG_STORAGE_DE = 0x1;
const int FLAG_STORAGE_CE = 0x2;
const int FLAG_STORAGE_EXTERNAL = 0x4;
diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp
index 34ea7597b4..1693ed5297 100644
--- a/cmds/installd/dexopt.cpp
+++ b/cmds/installd/dexopt.cpp
@@ -442,6 +442,16 @@ static unique_fd open_current_profile(uid_t uid, userid_t user, const std::strin
static unique_fd open_reference_profile(uid_t uid, const std::string& package_name,
const std::string& location, bool read_write, bool is_secondary_dex) {
std::string profile = create_reference_profile_path(package_name, location, is_secondary_dex);
+ if (read_write && GetBoolProperty("dalvik.vm.useartservice", false)) {
+ // ART Service doesn't use flock and instead assumes profile files are
+ // immutable, so ensure we don't open a file for writing when it's
+ // active.
+ // TODO(b/251921228): Normally installd isn't called at all in that
+ // case, but OTA is still an exception that uses the legacy code.
+ LOG(ERROR) << "Opening ref profile " << profile
+ << " for writing is unsafe when ART Service is enabled.";
+ return invalid_unique_fd();
+ }
return open_profile(
uid,
profile,
@@ -450,14 +460,13 @@ static unique_fd open_reference_profile(uid_t uid, const std::string& package_na
}
static UniqueFile open_reference_profile_as_unique_file(uid_t uid, const std::string& package_name,
- const std::string& location, bool read_write, bool is_secondary_dex) {
+ const std::string& location,
+ bool is_secondary_dex) {
std::string profile_path = create_reference_profile_path(package_name, location,
is_secondary_dex);
- unique_fd ufd = open_profile(
- uid,
- profile_path,
- read_write ? (O_CREAT | O_RDWR) : O_RDONLY,
- S_IRUSR | S_IWUSR | S_IRGRP); // so that ART can also read it when apps run.
+ unique_fd ufd = open_profile(uid, profile_path, O_RDONLY,
+ S_IRUSR | S_IWUSR |
+ S_IRGRP); // so that ART can also read it when apps run.
return UniqueFile(ufd.release(), profile_path, [](const std::string& path) {
clear_profile(path);
@@ -1104,8 +1113,7 @@ UniqueFile maybe_open_reference_profile(const std::string& pkgname,
location = profile_name;
}
}
- return open_reference_profile_as_unique_file(uid, pkgname, location, /*read_write*/false,
- is_secondary_dex);
+ return open_reference_profile_as_unique_file(uid, pkgname, location, is_secondary_dex);
}
// Opens the vdex files and assigns the input fd to in_vdex_wrapper and the output fd to
@@ -1909,10 +1917,11 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins
// Open the reference profile if needed.
UniqueFile reference_profile = maybe_open_reference_profile(
pkgname, dex_path, profile_name, profile_guided, is_public, uid, is_secondary_dex);
-
- if (reference_profile.fd() == -1) {
- // We don't create an app image without reference profile since there is no speedup from
- // loading it in that case and instead will be a small overhead.
+ struct stat sbuf;
+ if (reference_profile.fd() == -1 ||
+ (fstat(reference_profile.fd(), &sbuf) != -1 && sbuf.st_size == 0)) {
+ // We don't create an app image with empty or non existing reference profile since there
+ // is no speedup from loading it in that case and instead will be a small overhead.
generate_app_image = false;
}
@@ -1933,7 +1942,8 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins
RUNTIME_NATIVE_BOOT_NAMESPACE,
ENABLE_JITZYGOTE_IMAGE,
/*default_value=*/ "");
- bool use_jitzygote_image = jitzygote_flag == "true" || IsBootClassPathProfilingEnable();
+ bool compile_without_image = jitzygote_flag == "true" || IsBootClassPathProfilingEnable() ||
+ force_compile_without_image();
// Decide whether to use dex2oat64.
bool use_dex2oat64 = false;
@@ -1955,7 +1965,7 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins
in_dex, in_vdex, dex_metadata, reference_profile, class_loader_context,
join_fds(context_input_fds), swap_fd.get(), instruction_set, compiler_filter,
debuggable, boot_complete, for_restore, target_sdk_version,
- enable_hidden_api_checks, generate_compact_dex, use_jitzygote_image,
+ enable_hidden_api_checks, generate_compact_dex, compile_without_image,
background_job_compile, compilation_reason);
bool cancelled = false;
diff --git a/cmds/installd/dexopt.h b/cmds/installd/dexopt.h
index 5cf402c54c..df02588a22 100644
--- a/cmds/installd/dexopt.h
+++ b/cmds/installd/dexopt.h
@@ -18,6 +18,7 @@
#define DEXOPT_H_
#include "installd_constants.h"
+#include "unique_file.h"
#include <sys/types.h>
@@ -156,6 +157,10 @@ const char* select_execution_binary(
// artifacts.
int get_odex_visibility(const char* apk_path, const char* instruction_set, const char* oat_dir);
+UniqueFile maybe_open_reference_profile(const std::string& pkgname, const std::string& dex_path,
+ const char* profile_name, bool profile_guided,
+ bool is_public, int uid, bool is_secondary_dex);
+
} // namespace installd
} // namespace android
diff --git a/cmds/installd/installd.cpp b/cmds/installd/installd.cpp
index b5bc28c1d3..4f691c9127 100644
--- a/cmds/installd/installd.cpp
+++ b/cmds/installd/installd.cpp
@@ -65,6 +65,10 @@ bool create_cache_path(char path[PKG_PATH_MAX], const char *src, const char *ins
return create_cache_path_default(path, src, instruction_set);
}
+bool force_compile_without_image() {
+ return false;
+}
+
static bool initialize_globals() {
return init_globals_from_data_and_root();
}
diff --git a/cmds/installd/installd.rc b/cmds/installd/installd.rc
index 5b08c776ac..525f0c8761 100644
--- a/cmds/installd/installd.rc
+++ b/cmds/installd/installd.rc
@@ -1,6 +1,7 @@
service installd /system/bin/installd
class main
+ user root
capabilities CHOWN DAC_OVERRIDE DAC_READ_SEARCH FOWNER FSETID KILL SETGID SETUID SYS_ADMIN
on early-boot
diff --git a/cmds/installd/installd_deps.h b/cmds/installd/installd_deps.h
index 509317803e..0d0a7fa925 100644
--- a/cmds/installd/installd_deps.h
+++ b/cmds/installd/installd_deps.h
@@ -57,6 +57,9 @@ extern bool create_cache_path(char path[PKG_PATH_MAX],
const char *src,
const char *instruction_set);
+// If true, pass "--force-jit-zygote" to dex2oat (i.e., compile without a boot image).
+extern bool force_compile_without_image();
+
} // namespace installd
} // namespace android
diff --git a/cmds/installd/otapreopt.cpp b/cmds/installd/otapreopt.cpp
index 6a3120c1b7..8eb74583fe 100644
--- a/cmds/installd/otapreopt.cpp
+++ b/cmds/installd/otapreopt.cpp
@@ -14,20 +14,21 @@
** limitations under the License.
*/
-#include <algorithm>
#include <inttypes.h>
-#include <limits>
-#include <random>
-#include <regex>
#include <selinux/android.h>
#include <selinux/avc.h>
#include <stdlib.h>
#include <string.h>
#include <sys/capability.h>
+#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
-#include <sys/mman.h>
#include <sys/wait.h>
+#include <algorithm>
+#include <iterator>
+#include <limits>
+#include <random>
+#include <regex>
#include <android-base/logging.h>
#include <android-base/macros.h>
@@ -47,6 +48,7 @@
#include "otapreopt_parameters.h"
#include "otapreopt_utils.h"
#include "system_properties.h"
+#include "unique_file.h"
#include "utils.h"
#ifndef LOG_TAG
@@ -87,6 +89,9 @@ static_assert(DEXOPT_GENERATE_APP_IMAGE == 1 << 12, "DEXOPT_GENERATE_APP_IMAGE u
static_assert(DEXOPT_MASK == (0x3dfe | DEXOPT_IDLE_BACKGROUND_JOB),
"DEXOPT_MASK unexpected.");
+constexpr const char* kAotCompilerFilters[]{
+ "space-profile", "space", "speed-profile", "speed", "everything-profile", "everything",
+};
template<typename T>
static constexpr bool IsPowerOfTwo(T x) {
@@ -308,7 +313,7 @@ private:
// This is different from the normal installd. We only do the base
// directory, the rest will be created on demand when each app is compiled.
if (access(GetOtaDirectoryPrefix().c_str(), R_OK) < 0) {
- LOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix();
+ PLOG(ERROR) << "Could not access " << GetOtaDirectoryPrefix();
return false;
}
@@ -415,6 +420,36 @@ private:
return (strcmp(arg, "!") == 0) ? nullptr : arg;
}
+ bool IsAotCompilation() const {
+ if (std::find(std::begin(kAotCompilerFilters), std::end(kAotCompilerFilters),
+ std::string_view(parameters_.compiler_filter)) ==
+ std::end(kAotCompilerFilters)) {
+ return false;
+ }
+
+ int dexopt_flags = parameters_.dexopt_flags;
+ bool profile_guided = (dexopt_flags & DEXOPT_PROFILE_GUIDED) != 0;
+ bool is_secondary_dex = (dexopt_flags & DEXOPT_SECONDARY_DEX) != 0;
+ bool is_public = (dexopt_flags & DEXOPT_PUBLIC) != 0;
+
+ if (profile_guided) {
+ UniqueFile reference_profile =
+ maybe_open_reference_profile(parameters_.pkgName, parameters_.apk_path,
+ parameters_.profile_name, profile_guided,
+ is_public, parameters_.uid, is_secondary_dex);
+ // `maybe_open_reference_profile` installs a hook that clears the profile on
+ // destruction. Disable it.
+ reference_profile.DisableCleanup();
+ struct stat sbuf;
+ if (reference_profile.fd() == -1 ||
+ (fstat(reference_profile.fd(), &sbuf) != -1 && sbuf.st_size == 0)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
bool ShouldSkipPreopt() const {
// There's one thing we have to be careful about: we may/will be asked to compile an app
// living in the system image. This may be a valid request - if the app wasn't compiled,
@@ -439,9 +474,12 @@ private:
// (This is ugly as it's the only thing where we need to understand the contents
// of parameters_, but it beats postponing the decision or using the call-
// backs to do weird things.)
+
+ // In addition, no need to preopt for "verify". The existing vdex files in the OTA package
+ // and the /data partition will still be usable after the OTA update is applied.
const char* apk_path = parameters_.apk_path;
CHECK(apk_path != nullptr);
- if (StartsWith(apk_path, android_root_)) {
+ if (StartsWith(apk_path, android_root_) || !IsAotCompilation()) {
const char* last_slash = strrchr(apk_path, '/');
if (last_slash != nullptr) {
std::string path(apk_path, last_slash - apk_path + 1);
@@ -460,7 +498,7 @@ private:
// this tool will wipe the OTA artifact cache and try again (for robustness after
// a failed OTA with remaining cache artifacts).
if (access(apk_path, F_OK) != 0) {
- LOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path;
+ PLOG(WARNING) << "Skipping A/B OTA preopt of non-existing package " << apk_path;
return true;
}
@@ -471,13 +509,20 @@ private:
// TODO(calin): embed the profile name in the parameters.
int Dexopt() {
std::string error;
+
+ int dexopt_flags = parameters_.dexopt_flags;
+ // Make sure dex2oat is run with background priority.
+ dexopt_flags |= DEXOPT_BOOTCOMPLETE | DEXOPT_IDLE_BACKGROUND_JOB;
+
+ parameters_.compilation_reason = "ab-ota";
+
int res = dexopt(parameters_.apk_path,
parameters_.uid,
parameters_.pkgName,
parameters_.instruction_set,
parameters_.dexopt_needed,
parameters_.oat_dir,
- parameters_.dexopt_flags,
+ dexopt_flags,
parameters_.compiler_filter,
parameters_.volume_uuid,
parameters_.shared_libraries,
@@ -521,61 +566,6 @@ private:
return Dexopt();
}
- ////////////////////////////////////
- // Helpers, mostly taken from ART //
- ////////////////////////////////////
-
- // Choose a random relocation offset. Taken from art/runtime/gc/image_space.cc.
- static int32_t ChooseRelocationOffsetDelta(int32_t min_delta, int32_t max_delta) {
- constexpr size_t kPageSize = PAGE_SIZE;
- static_assert(IsPowerOfTwo(kPageSize), "page size must be power of two");
- CHECK_EQ(min_delta % kPageSize, 0u);
- CHECK_EQ(max_delta % kPageSize, 0u);
- CHECK_LT(min_delta, max_delta);
-
- std::default_random_engine generator;
- generator.seed(GetSeed());
- std::uniform_int_distribution<int32_t> distribution(min_delta, max_delta);
- int32_t r = distribution(generator);
- if (r % 2 == 0) {
- r = RoundUp(r, kPageSize);
- } else {
- r = RoundDown(r, kPageSize);
- }
- CHECK_LE(min_delta, r);
- CHECK_GE(max_delta, r);
- CHECK_EQ(r % kPageSize, 0u);
- return r;
- }
-
- static uint64_t GetSeed() {
-#ifdef __BIONIC__
- // Bionic exposes arc4random, use it.
- uint64_t random_data;
- arc4random_buf(&random_data, sizeof(random_data));
- return random_data;
-#else
-#error "This is only supposed to run with bionic. Otherwise, implement..."
-#endif
- }
-
- void AddCompilerOptionFromSystemProperty(const char* system_property,
- const char* prefix,
- bool runtime,
- std::vector<std::string>& out) const {
- const std::string* value = system_properties_.GetProperty(system_property);
- if (value != nullptr) {
- if (runtime) {
- out.push_back("--runtime-arg");
- }
- if (prefix != nullptr) {
- out.push_back(StringPrintf("%s%s", prefix, value->c_str()));
- } else {
- out.push_back(*value);
- }
- }
- }
-
static constexpr const char* kBootClassPathPropertyName = "BOOTCLASSPATH";
static constexpr const char* kAndroidRootPathPropertyName = "ANDROID_ROOT";
static constexpr const char* kAndroidDataPathPropertyName = "ANDROID_DATA";
@@ -711,6 +701,11 @@ bool create_cache_path(char path[PKG_PATH_MAX],
return true;
}
+bool force_compile_without_image() {
+ // We don't have a boot image anyway. Compile without a boot image.
+ return true;
+}
+
static int log_callback(int type, const char *fmt, ...) {
va_list ap;
int priority;
diff --git a/cmds/installd/otapreopt_chroot.cpp b/cmds/installd/otapreopt_chroot.cpp
index c62734a925..c86adef118 100644
--- a/cmds/installd/otapreopt_chroot.cpp
+++ b/cmds/installd/otapreopt_chroot.cpp
@@ -19,9 +19,12 @@
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/wait.h>
+#include <unistd.h>
+#include <algorithm>
#include <array>
#include <fstream>
+#include <iostream>
#include <sstream>
#include <android-base/file.h>
@@ -29,6 +32,7 @@
#include <android-base/macros.h>
#include <android-base/scopeguard.h>
#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <libdm/dm.h>
#include <selinux/android.h>
@@ -37,7 +41,7 @@
#include "otapreopt_utils.h"
#ifndef LOG_TAG
-#define LOG_TAG "otapreopt"
+#define LOG_TAG "otapreopt_chroot"
#endif
using android::base::StringPrintf;
@@ -45,20 +49,26 @@ using android::base::StringPrintf;
namespace android {
namespace installd {
-static void CloseDescriptor(int fd) {
- if (fd >= 0) {
- int result = close(fd);
- UNUSED(result); // Ignore result. Printing to logcat will open a new descriptor
- // that we do *not* want.
- }
-}
+// We don't know the filesystem types of the partitions in the update package,
+// so just try the possibilities one by one.
+static constexpr std::array kTryMountFsTypes = {"ext4", "erofs"};
static void CloseDescriptor(const char* descriptor_string) {
int fd = -1;
std::istringstream stream(descriptor_string);
stream >> fd;
if (!stream.fail()) {
- CloseDescriptor(fd);
+ if (fd >= 0) {
+ if (close(fd) < 0) {
+ PLOG(ERROR) << "Failed to close " << fd;
+ }
+ }
+ }
+}
+
+static void SetCloseOnExec(int fd) {
+ if (fcntl(fd, F_SETFD, FD_CLOEXEC) < 0) {
+ PLOG(ERROR) << "Failed to set FD_CLOEXEC on " << fd;
}
}
@@ -82,6 +92,27 @@ static void DeactivateApexPackages() {
}
}
+static bool TryMountWithFstypes(const char* block_device, const char* target) {
+ for (int i = 0; i < kTryMountFsTypes.size(); ++i) {
+ const char* fstype = kTryMountFsTypes[i];
+ int mount_result = mount(block_device, target, fstype, MS_RDONLY, /* data */ nullptr);
+ if (mount_result == 0) {
+ return true;
+ }
+ if (errno == EINVAL && i < kTryMountFsTypes.size() - 1) {
+ // Only try the next fstype if mounting failed due to the current one
+ // being invalid.
+ LOG(WARNING) << "Failed to mount " << block_device << " on " << target << " with "
+ << fstype << " - trying " << kTryMountFsTypes[i + 1];
+ } else {
+ PLOG(ERROR) << "Failed to mount " << block_device << " on " << target << " with "
+ << fstype;
+ return false;
+ }
+ }
+ __builtin_unreachable();
+}
+
static void TryExtraMount(const char* name, const char* slot, const char* target) {
std::string partition_name = StringPrintf("%s%s", name, slot);
@@ -91,12 +122,7 @@ static void TryExtraMount(const char* name, const char* slot, const char* target
if (dm.GetState(partition_name) != dm::DmDeviceState::INVALID) {
std::string path;
if (dm.GetDmDevicePathByName(partition_name, &path)) {
- int mount_result = mount(path.c_str(),
- target,
- "ext4",
- MS_RDONLY,
- /* data */ nullptr);
- if (mount_result == 0) {
+ if (TryMountWithFstypes(path.c_str(), target)) {
return;
}
}
@@ -105,33 +131,43 @@ static void TryExtraMount(const char* name, const char* slot, const char* target
// Fall back and attempt a direct mount.
std::string block_device = StringPrintf("/dev/block/by-name/%s", partition_name.c_str());
- int mount_result = mount(block_device.c_str(),
- target,
- "ext4",
- MS_RDONLY,
- /* data */ nullptr);
- UNUSED(mount_result);
+ (void)TryMountWithFstypes(block_device.c_str(), target);
}
// Entry for otapreopt_chroot. Expected parameters are:
-// [cmd] [status-fd] [target-slot] "dexopt" [dexopt-params]
-// The file descriptor denoted by status-fd will be closed. The rest of the parameters will
-// be passed on to otapreopt in the chroot.
+//
+// [cmd] [status-fd] [target-slot-suffix]
+//
+// The file descriptor denoted by status-fd will be closed. Dexopt commands on
+// the form
+//
+// "dexopt" [dexopt-params]
+//
+// are then read from stdin until EOF and passed on to /system/bin/otapreopt one
+// by one. After each call a line with the current command count is written to
+// stdout and flushed.
static int otapreopt_chroot(const int argc, char **arg) {
// Validate arguments
- // We need the command, status channel and target slot, at a minimum.
- if(argc < 3) {
- PLOG(ERROR) << "Not enough arguments.";
+ if (argc == 2 && std::string_view(arg[1]) == "--version") {
+ // Accept a single --version flag, to allow the script to tell this binary
+ // from the earlier one.
+ std::cout << "2" << std::endl;
+ return 0;
+ }
+ if (argc != 3) {
+ LOG(ERROR) << "Wrong number of arguments: " << argc;
exit(208);
}
- // Close all file descriptors. They are coming from the caller, we do not want to pass them
- // on across our fork/exec into a different domain.
- // 1) Default descriptors.
- CloseDescriptor(STDIN_FILENO);
- CloseDescriptor(STDOUT_FILENO);
- CloseDescriptor(STDERR_FILENO);
- // 2) The status channel.
- CloseDescriptor(arg[1]);
+ const char* status_fd = arg[1];
+ const char* slot_suffix = arg[2];
+
+ // Set O_CLOEXEC on standard fds. They are coming from the caller, we do not
+ // want to pass them on across our fork/exec into a different domain.
+ SetCloseOnExec(STDIN_FILENO);
+ SetCloseOnExec(STDOUT_FILENO);
+ SetCloseOnExec(STDERR_FILENO);
+ // Close the status channel.
+ CloseDescriptor(status_fd);
// We need to run the otapreopt tool from the postinstall partition. As such, set up a
// mount namespace and change root.
@@ -150,7 +186,8 @@ static int otapreopt_chroot(const int argc, char **arg) {
// Bind mount necessary directories.
constexpr const char* kBindMounts[] = {
- "/data", "/dev", "/proc", "/sys"
+ "/data", "/dev", "/proc", "/sys",
+ "/sys/fs/selinux" /* Required for apexd which includes libselinux */
};
for (size_t i = 0; i < arraysize(kBindMounts); ++i) {
std::string trg = StringPrintf("/postinstall%s", kBindMounts[i]);
@@ -169,20 +206,20 @@ static int otapreopt_chroot(const int argc, char **arg) {
// 2) We're in a mount namespace here, so when we die, this will be cleaned up.
// 3) Ignore errors. Printing anything at this stage will open a file descriptor
// for logging.
- if (!ValidateTargetSlotSuffix(arg[2])) {
- LOG(ERROR) << "Target slot suffix not legal: " << arg[2];
+ if (!ValidateTargetSlotSuffix(slot_suffix)) {
+ LOG(ERROR) << "Target slot suffix not legal: " << slot_suffix;
exit(207);
}
- TryExtraMount("vendor", arg[2], "/postinstall/vendor");
+ TryExtraMount("vendor", slot_suffix, "/postinstall/vendor");
// Try to mount the product partition. update_engine doesn't do this for us, but we
// want it for product APKs. Same notes as vendor above.
- TryExtraMount("product", arg[2], "/postinstall/product");
+ TryExtraMount("product", slot_suffix, "/postinstall/product");
// Try to mount the system_ext partition. update_engine doesn't do this for
// us, but we want it for system_ext APKs. Same notes as vendor and product
// above.
- TryExtraMount("system_ext", arg[2], "/postinstall/system_ext");
+ TryExtraMount("system_ext", slot_suffix, "/postinstall/system_ext");
constexpr const char* kPostInstallLinkerconfig = "/postinstall/linkerconfig";
// Try to mount /postinstall/linkerconfig. we will set it up after performing the chroot
@@ -313,30 +350,37 @@ static int otapreopt_chroot(const int argc, char **arg) {
exit(218);
}
- // Now go on and run otapreopt.
+ // Now go on and read dexopt lines from stdin and pass them on to otapreopt.
- // Incoming: cmd + status-fd + target-slot + cmd... | Incoming | = argc
- // Outgoing: cmd + target-slot + cmd... | Outgoing | = argc - 1
- std::vector<std::string> cmd;
- cmd.reserve(argc);
- cmd.push_back("/system/bin/otapreopt");
+ int count = 1;
+ for (std::array<char, 10000> linebuf;
+ std::cin.clear(), std::cin.getline(&linebuf[0], linebuf.size()); ++count) {
+ // Subtract one from gcount() since getline() counts the newline.
+ std::string line(&linebuf[0], std::cin.gcount() - 1);
- // The first parameter is the status file descriptor, skip.
- for (size_t i = 2; i < static_cast<size_t>(argc); ++i) {
- cmd.push_back(arg[i]);
- }
+ if (std::cin.fail()) {
+ LOG(ERROR) << "Command exceeds max length " << linebuf.size() << " - skipped: " << line;
+ continue;
+ }
- // Fork and execute otapreopt in its own process.
- std::string error_msg;
- bool exec_result = Exec(cmd, &error_msg);
- if (!exec_result) {
- LOG(ERROR) << "Running otapreopt failed: " << error_msg;
- }
+ std::vector<std::string> tokenized_line = android::base::Tokenize(line, " ");
+ std::vector<std::string> cmd{"/system/bin/otapreopt", slot_suffix};
+ std::move(tokenized_line.begin(), tokenized_line.end(), std::back_inserter(cmd));
- if (!exec_result) {
- exit(213);
+ LOG(INFO) << "Command " << count << ": " << android::base::Join(cmd, " ");
+
+ // Fork and execute otapreopt in its own process.
+ std::string error_msg;
+ bool exec_result = Exec(cmd, &error_msg);
+ if (!exec_result) {
+ LOG(ERROR) << "Running otapreopt failed: " << error_msg;
+ }
+
+ // Print the count to stdout and flush to indicate progress.
+ std::cout << count << std::endl;
}
+ LOG(INFO) << "No more dexopt commands";
return 0;
}
diff --git a/cmds/installd/otapreopt_script.sh b/cmds/installd/otapreopt_script.sh
index f950276090..ae7d8e04d4 100644
--- a/cmds/installd/otapreopt_script.sh
+++ b/cmds/installd/otapreopt_script.sh
@@ -16,7 +16,9 @@
# limitations under the License.
#
-# This script will run as a postinstall step to drive otapreopt.
+# This script runs as a postinstall step to drive otapreopt. It comes with the
+# OTA package, but runs /system/bin/otapreopt_chroot in the (old) active system
+# image. See system/extras/postinst/postinst.sh for some docs.
TARGET_SLOT="$1"
STATUS_FD="$2"
@@ -31,12 +33,11 @@ BOOT_PROPERTY_NAME="dev.bootcomplete"
BOOT_COMPLETE=$(getprop $BOOT_PROPERTY_NAME)
if [ "$BOOT_COMPLETE" != "1" ] ; then
- echo "Error: boot-complete not detected."
+ echo "$0: Error: boot-complete not detected."
# We must return 0 to not block sideload.
exit 0
fi
-
# Compute target slot suffix.
# TODO: Once bootctl is not restricted, we should query from there. Or get this from
# update_engine as a parameter.
@@ -45,45 +46,69 @@ if [ "$TARGET_SLOT" = "0" ] ; then
elif [ "$TARGET_SLOT" = "1" ] ; then
TARGET_SLOT_SUFFIX="_b"
else
- echo "Unknown target slot $TARGET_SLOT"
+ echo "$0: Unknown target slot $TARGET_SLOT"
exit 1
fi
+if pm art on-ota-staged --slot "$TARGET_SLOT_SUFFIX"; then
+ # Handled by Pre-reboot Dexopt.
+ exit 0
+fi
+echo "Pre-reboot Dexopt not enabled. Fall back to otapreopt."
+
+if [ "$(/system/bin/otapreopt_chroot --version)" != 2 ]; then
+ # We require an updated chroot wrapper that reads dexopt commands from stdin.
+ # Even if we kept compat with the old binary, the OTA preopt wouldn't work due
+ # to missing sepolicy rules, so there's no use spending time trying to dexopt
+ # (b/291974157).
+ echo "$0: Current system image is too old to work with OTA preopt - skipping."
+ exit 0
+fi
PREPARE=$(cmd otadexopt prepare)
# Note: Ignore preparation failures. Step and done will fail and exit this.
# This is necessary to support suspends - the OTA service will keep
# the state around for us.
-PROGRESS=$(cmd otadexopt progress)
-print -u${STATUS_FD} "global_progress $PROGRESS"
-
-i=0
-while ((i<MAXIMUM_PACKAGES)) ; do
- DEXOPT_PARAMS=$(cmd otadexopt next)
-
- /system/bin/otapreopt_chroot $STATUS_FD $TARGET_SLOT_SUFFIX $DEXOPT_PARAMS >&- 2>&-
-
- PROGRESS=$(cmd otadexopt progress)
- print -u${STATUS_FD} "global_progress $PROGRESS"
-
+# Create an array with all dexopt commands in advance, to know how many there are.
+otadexopt_cmds=()
+while (( ${#otadexopt_cmds[@]} < MAXIMUM_PACKAGES )) ; do
DONE=$(cmd otadexopt done)
- if [ "$DONE" = "OTA incomplete." ] ; then
- sleep 1
- i=$((i+1))
- continue
+ if [ "$DONE" = "OTA complete." ] ; then
+ break
fi
- break
+ otadexopt_cmds+=("$(cmd otadexopt next)")
done
DONE=$(cmd otadexopt done)
+cmd otadexopt cleanup
+
+echo "$0: Using streaming otapreopt_chroot on ${#otadexopt_cmds[@]} packages"
+
+function print_otadexopt_cmds {
+ for cmd in "${otadexopt_cmds[@]}" ; do
+ print "$cmd"
+ done
+}
+
+function report_progress {
+ while read count ; do
+ # mksh can't do floating point arithmetic, so emulate a fixed point calculation.
+ (( permilles = 1000 * count / ${#otadexopt_cmds[@]} ))
+ printf 'global_progress %d.%03d\n' $((permilles / 1000)) $((permilles % 1000)) >&${STATUS_FD}
+ done
+}
+
+print_otadexopt_cmds | \
+ /system/bin/otapreopt_chroot $STATUS_FD $TARGET_SLOT_SUFFIX | \
+ report_progress
+
if [ "$DONE" = "OTA incomplete." ] ; then
- echo "Incomplete."
+ echo "$0: Incomplete."
else
- echo "Complete or error."
+ echo "$0: Complete or error."
fi
print -u${STATUS_FD} "global_progress 1.0"
-cmd otadexopt cleanup
exit 0
diff --git a/cmds/installd/run_dex2oat.cpp b/cmds/installd/run_dex2oat.cpp
index 4221a3a593..7648265f0f 100644
--- a/cmds/installd/run_dex2oat.cpp
+++ b/cmds/installd/run_dex2oat.cpp
@@ -208,36 +208,13 @@ void RunDex2Oat::PrepareCompilerConfigFlags(const UniqueFile& input_vdex,
}
// Compute compiler filter.
- {
- std::string dex2oat_compiler_filter_arg;
- {
- // If we are booting without the real /data, don't spend time compiling.
- std::string vold_decrypt = GetProperty("vold.decrypt", "");
- bool skip_compilation = vold_decrypt == "trigger_restart_min_framework" ||
- vold_decrypt == "1";
-
- bool have_dex2oat_relocation_skip_flag = false;
- if (skip_compilation) {
- dex2oat_compiler_filter_arg = "--compiler-filter=extract";
- have_dex2oat_relocation_skip_flag = true;
- } else if (compiler_filter != nullptr) {
- dex2oat_compiler_filter_arg = StringPrintf("--compiler-filter=%s",
- compiler_filter);
- }
- if (have_dex2oat_relocation_skip_flag) {
- AddRuntimeArg("-Xnorelocate");
- }
- }
-
- if (dex2oat_compiler_filter_arg.empty()) {
- dex2oat_compiler_filter_arg = MapPropertyToArg("dalvik.vm.dex2oat-filter",
- "--compiler-filter=%s");
- }
- AddArg(dex2oat_compiler_filter_arg);
-
- if (compilation_reason != nullptr) {
- AddArg(std::string("--compilation-reason=") + compilation_reason);
- }
+ if (compiler_filter != nullptr) {
+ AddArg(StringPrintf("--compiler-filter=%s", compiler_filter));
+ } else {
+ AddArg(MapPropertyToArg("dalvik.vm.dex2oat-filter", "--compiler-filter=%s"));
+ }
+ if (compilation_reason != nullptr) {
+ AddArg(std::string("--compilation-reason=") + compilation_reason);
}
AddArg(MapPropertyToArg("dalvik.vm.dex2oat-max-image-block-size",
diff --git a/cmds/installd/run_dex2oat_test.cpp b/cmds/installd/run_dex2oat_test.cpp
index 304ba7b04f..56f84a5e12 100644
--- a/cmds/installd/run_dex2oat_test.cpp
+++ b/cmds/installd/run_dex2oat_test.cpp
@@ -441,24 +441,6 @@ TEST_F(RunDex2OatTest, Runtime) {
VerifyExpectedFlags();
}
-TEST_F(RunDex2OatTest, SkipRelocationInMinFramework) {
- setSystemProperty("vold.decrypt", "trigger_restart_min_framework");
- CallRunDex2Oat(RunDex2OatArgs::MakeDefaultTestArgs());
-
- SetExpectedFlagUsed("--compiler-filter", "=extract");
- SetExpectedFlagUsed("-Xnorelocate", "");
- VerifyExpectedFlags();
-}
-
-TEST_F(RunDex2OatTest, SkipRelocationIfDecryptedWithFullDiskEncryption) {
- setSystemProperty("vold.decrypt", "1");
- CallRunDex2Oat(RunDex2OatArgs::MakeDefaultTestArgs());
-
- SetExpectedFlagUsed("--compiler-filter", "=extract");
- SetExpectedFlagUsed("-Xnorelocate", "");
- VerifyExpectedFlags();
-}
-
TEST_F(RunDex2OatTest, DalvikVmDex2oatFilter) {
setSystemProperty("dalvik.vm.dex2oat-filter", "speed");
auto args = RunDex2OatArgs::MakeDefaultTestArgs();
diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp
index 07f73b9029..61fe316225 100644
--- a/cmds/installd/tests/Android.bp
+++ b/cmds/installd/tests/Android.bp
@@ -77,10 +77,8 @@ cc_test {
},
}
-cc_test {
- name: "installd_service_test",
- test_suites: ["device-tests"],
- srcs: ["installd_service_test.cpp"],
+cc_defaults {
+ name: "installd_service_test_defaults",
cflags: [
"-Wall",
"-Werror",
@@ -106,8 +104,6 @@ cc_test {
"liblogwrap",
"libc++fs",
],
- test_config: "installd_service_test.xml",
-
product_variables: {
arc: {
exclude_srcs: [
@@ -125,6 +121,14 @@ cc_test {
}
cc_test {
+ name: "installd_service_test",
+ test_suites: ["device-tests"],
+ srcs: ["installd_service_test.cpp"],
+ defaults: ["installd_service_test_defaults"],
+ test_config: "installd_service_test.xml",
+}
+
+cc_test {
name: "installd_dexopt_test",
test_suites: ["device-tests"],
srcs: ["installd_dexopt_test.cpp"],
@@ -209,3 +213,19 @@ cc_test {
"liblog",
],
}
+
+cc_fuzz {
+ name: "installd_service_fuzzer",
+ defaults: [
+ "service_fuzzer_defaults",
+ "fuzzer_disable_leaks",
+ "installd_service_test_defaults",
+ ],
+ srcs: ["fuzzers/InstalldServiceFuzzer.cpp"],
+ fuzz_config: {
+ cc: [
+ "android-package-manager-team@google.com",
+ ],
+ triage_assignee: "waghpawan@google.com",
+ },
+}
diff --git a/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp b/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
new file mode 100644
index 0000000000..b1c6940207
--- /dev/null
+++ b/cmds/installd/tests/fuzzers/InstalldServiceFuzzer.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 <fuzzbinder/libbinder_driver.h>
+
+#include "InstalldNativeService.h"
+#include "dexopt.h"
+
+using ::android::fuzzService;
+using ::android::sp;
+using ::android::installd::InstalldNativeService;
+
+namespace android {
+namespace installd {
+
+bool calculate_oat_file_path(char path[PKG_PATH_MAX], const char* oat_dir, const char* apk_path,
+ const char* instruction_set) {
+ return calculate_oat_file_path_default(path, oat_dir, apk_path, instruction_set);
+}
+
+bool calculate_odex_file_path(char path[PKG_PATH_MAX], const char* apk_path,
+ const char* instruction_set) {
+ return calculate_odex_file_path_default(path, apk_path, instruction_set);
+}
+
+bool create_cache_path(char path[PKG_PATH_MAX], const char* src, const char* instruction_set) {
+ return create_cache_path_default(path, src, instruction_set);
+}
+
+bool force_compile_without_image() {
+ return false;
+}
+
+} // namespace installd
+} // namespace android
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ auto service = sp<InstalldNativeService>::make();
+ fuzzService(service, FuzzedDataProvider(data, size));
+ return 0;
+} \ No newline at end of file
diff --git a/cmds/installd/tests/installd_cache_test.cpp b/cmds/installd/tests/installd_cache_test.cpp
index 4976646595..a00c2c704d 100644
--- a/cmds/installd/tests/installd_cache_test.cpp
+++ b/cmds/installd/tests/installd_cache_test.cpp
@@ -67,6 +67,10 @@ bool create_cache_path(char path[PKG_PATH_MAX] ATTRIBUTE_UNUSED,
return false;
}
+bool force_compile_without_image() {
+ return false;
+}
+
static void mkdir(const char* path) {
const std::string fullPath = StringPrintf("/data/local/tmp/user/0/%s", path);
::mkdir(fullPath.c_str(), 0755);
diff --git a/cmds/installd/tests/installd_dexopt_test.cpp b/cmds/installd/tests/installd_dexopt_test.cpp
index 3b589dc581..ee91d80a3b 100644
--- a/cmds/installd/tests/installd_dexopt_test.cpp
+++ b/cmds/installd/tests/installd_dexopt_test.cpp
@@ -73,6 +73,10 @@ bool create_cache_path(char path[PKG_PATH_MAX], const char *src, const char *ins
return create_cache_path_default(path, src, instruction_set);
}
+bool force_compile_without_image() {
+ return false;
+}
+
static void run_cmd(const std::string& cmd) {
system(cmd.c_str());
}
@@ -185,7 +189,7 @@ protected:
std::optional<std::string> volume_uuid_;
std::string package_name_;
std::string apk_path_;
- std::string empty_dm_file_;
+ std::string dm_file_;
std::string app_apk_dir_;
std::string app_private_dir_ce_;
std::string app_private_dir_de_;
@@ -193,12 +197,17 @@ protected:
std::string app_oat_dir_;
int64_t ce_data_inode_;
+ int64_t de_data_inode_;
std::string secondary_dex_ce_;
std::string secondary_dex_ce_link_;
std::string secondary_dex_de_;
virtual void SetUp() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+ }
+
setenv("ANDROID_LOG_TAGS", "*:v", 1);
android::base::InitLogging(nullptr);
// Initialize the globals holding the file system main paths (/data/, /system/ etc..).
@@ -219,6 +228,10 @@ protected:
}
virtual void TearDown() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP();
+ }
+
if (!kDebug) {
service_->controlDexOptBlocking(false);
service_->destroyAppData(
@@ -248,37 +261,11 @@ protected:
<< " : " << error_msg;
}
- // Create an empty dm file.
- empty_dm_file_ = apk_path_ + ".dm";
- {
- int fd = open(empty_dm_file_.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
- if (fd < 0) {
- return ::testing::AssertionFailure() << "Could not open " << empty_dm_file_;
- }
- FILE* file = fdopen(fd, "wb");
- if (file == nullptr) {
- return ::testing::AssertionFailure() << "Null file for " << empty_dm_file_
- << " fd=" << fd;
- }
- ZipWriter writer(file);
- // Add vdex to zip.
- writer.StartEntry("primary.prof", ZipWriter::kCompress);
- writer.FinishEntry();
- writer.Finish();
- fclose(file);
- }
-
// Create the app user data.
- binder::Status status = service_->createAppData(
- volume_uuid_,
- package_name_,
- kTestUserId,
- kAppDataFlags,
- kTestAppUid,
- 0 /* previousAppId */,
- se_info_,
- kOSdkVersion,
- &ce_data_inode_);
+ binder::Status status =
+ service_->createAppData(volume_uuid_, package_name_, kTestUserId, kAppDataFlags,
+ kTestAppUid, 0 /* previousAppId */, se_info_, kOSdkVersion,
+ &ce_data_inode_, &de_data_inode_);
if (!status.isOk()) {
return ::testing::AssertionFailure() << "Could not create app data: "
<< status.toString8().c_str();
@@ -316,6 +303,46 @@ protected:
<< secondary_dex_de_ << " : " << error_msg;
}
+ // Create a non-empty dm file.
+ dm_file_ = apk_path_ + ".dm";
+ {
+ android::base::unique_fd fd(open(dm_file_.c_str(),
+ O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR));
+ if (fd.get() < 0) {
+ return ::testing::AssertionFailure() << "Could not open " << dm_file_;
+ }
+ FILE* file = fdopen(fd.release(), "wb");
+ if (file == nullptr) {
+ return ::testing::AssertionFailure() << "Null file for " << dm_file_
+ << " fd=" << fd.get();
+ }
+
+ // Create a profile file.
+ std::string profile_file = app_private_dir_ce_ + "/primary.prof";
+ run_cmd("profman --generate-test-profile=" + profile_file);
+
+ // Add profile to zip.
+ ZipWriter writer(file);
+ writer.StartEntry("primary.prof", ZipWriter::kCompress);
+ android::base::unique_fd profile_fd(open(profile_file.c_str(), O_RDONLY));
+ if (profile_fd.get() < 0) {
+ return ::testing::AssertionFailure() << "Failed to open profile '"
+ << profile_file << "'";
+ }
+ std::string profile_content;
+ if (!android::base::ReadFdToString(profile_fd, &profile_content)) {
+ return ::testing::AssertionFailure() << "Failed to read profile "
+ << profile_file << "'";
+ }
+ writer.WriteBytes(profile_content.c_str(), profile_content.length());
+ writer.FinishEntry();
+ writer.Finish();
+ fclose(file);
+
+ // Delete the temp file.
+ unlink(profile_file.c_str());
+ }
+
// Fix app data uid.
status = service_->fixupAppData(volume_uuid_, kTestUserId);
if (!status.isOk()) {
@@ -608,7 +635,7 @@ protected:
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
int64_t odex_size = GetSize(GetPrimaryDexArtifact(oat_dir, apk_path_,
@@ -657,13 +684,13 @@ protected:
DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_PUBLIC |
DEXOPT_GENERATE_APP_IMAGE,
oat_dir, kTestAppGid, DEX2OAT_FROM_SCRATCH,
- /*binder_result=*/nullptr, empty_dm_file_.c_str());
+ /*binder_result=*/nullptr, dm_file_.c_str());
checkVisibility(in_dalvik_cache, ODEX_IS_PUBLIC);
CompilePrimaryDexOk("speed-profile",
DEXOPT_BOOTCOMPLETE | DEXOPT_PROFILE_GUIDED | DEXOPT_GENERATE_APP_IMAGE,
oat_dir, kTestAppGid, DEX2OAT_FROM_SCRATCH,
- /*binder_result=*/nullptr, empty_dm_file_.c_str());
+ /*binder_result=*/nullptr, dm_file_.c_str());
checkVisibility(in_dalvik_cache, ODEX_IS_PRIVATE);
}
};
@@ -787,7 +814,7 @@ TEST_F(DexoptTest, DexoptPrimaryProfileNonPublic) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
}
TEST_F(DexoptTest, DexoptPrimaryProfilePublic) {
@@ -799,7 +826,7 @@ TEST_F(DexoptTest, DexoptPrimaryProfilePublic) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
}
TEST_F(DexoptTest, DexoptPrimaryBackgroundOk) {
@@ -811,7 +838,7 @@ TEST_F(DexoptTest, DexoptPrimaryBackgroundOk) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
}
TEST_F(DexoptTest, DexoptBlockPrimary) {
@@ -874,7 +901,7 @@ TEST_F(DexoptTest, ResolveStartupConstStrings) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
run_cmd_and_process_output(
"oatdump --header-only --oat-file=" + odex,
[&](const std::string& line) {
@@ -893,7 +920,7 @@ TEST_F(DexoptTest, ResolveStartupConstStrings) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
run_cmd_and_process_output(
"oatdump --header-only --oat-file=" + odex,
[&](const std::string& line) {
@@ -926,7 +953,7 @@ TEST_F(DexoptTest, DexoptDex2oat64Enabled) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
// Enable the property and use dex2oat64.
ASSERT_TRUE(android::base::SetProperty(property, "true")) << property;
CompilePrimaryDexOk("speed-profile",
@@ -936,12 +963,16 @@ TEST_F(DexoptTest, DexoptDex2oat64Enabled) {
kTestAppGid,
DEX2OAT_FROM_SCRATCH,
/*binder_result=*/nullptr,
- empty_dm_file_.c_str());
+ dm_file_.c_str());
}
class PrimaryDexReCompilationTest : public DexoptTest {
public:
virtual void SetUp() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+ }
+
DexoptTest::SetUp();
CompilePrimaryDexOk("verify",
DEXOPT_BOOTCOMPLETE | DEXOPT_PUBLIC,
@@ -956,6 +987,10 @@ class PrimaryDexReCompilationTest : public DexoptTest {
}
virtual void TearDown() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP();
+ }
+
first_compilation_odex_fd_.reset(-1);
first_compilation_vdex_fd_.reset(-1);
DexoptTest::TearDown();
@@ -978,6 +1013,10 @@ TEST_F(PrimaryDexReCompilationTest, DexoptPrimaryUpdateInPlaceVdex) {
class ReconcileTest : public DexoptTest {
virtual void SetUp() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+ }
+
DexoptTest::SetUp();
CompileSecondaryDex(secondary_dex_ce_, DEXOPT_STORAGE_CE,
/*binder_ok*/ true, /*compile_ok*/ true);
@@ -1043,6 +1082,10 @@ class ProfileTest : public DexoptTest {
static constexpr const char* kPrimaryProfile = "primary.prof";
virtual void SetUp() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+ }
+
DexoptTest::SetUp();
cur_profile_ = create_current_profile_path(
kTestUserId, package_name_, kPrimaryProfile, /*is_secondary_dex*/ false);
@@ -1143,7 +1186,7 @@ class ProfileTest : public DexoptTest {
service_->prepareAppProfile(package_name, has_user_id ? kTestUserId : USER_NULL,
kTestAppId, profile_name, apk_path_,
has_dex_metadata ? std::make_optional<std::string>(
- empty_dm_file_)
+ dm_file_)
: std::nullopt,
&result));
ASSERT_EQ(expected_result, result);
@@ -1302,16 +1345,10 @@ TEST_F(ProfileTest, ProfileDirOkAfterFixup) {
ASSERT_EQ(0, chmod(ref_profile_dir.c_str(), 0700));
// Run createAppData again which will offer to fix-up the profile directories.
- ASSERT_BINDER_SUCCESS(service_->createAppData(
- volume_uuid_,
- package_name_,
- kTestUserId,
- kAppDataFlags,
- kTestAppUid,
- 0 /* previousAppId */,
- se_info_,
- kOSdkVersion,
- &ce_data_inode_));
+ ASSERT_BINDER_SUCCESS(service_->createAppData(volume_uuid_, package_name_, kTestUserId,
+ kAppDataFlags, kTestAppUid, 0 /* previousAppId */,
+ se_info_, kOSdkVersion, &ce_data_inode_,
+ &de_data_inode_));
// Check the file access.
CheckFileAccess(cur_profile_dir, kTestAppUid, kTestAppUid, 0700 | S_IFDIR);
@@ -1416,6 +1453,9 @@ class BootProfileTest : public ProfileTest {
std::vector<int64_t> extra_ce_data_inodes_;
virtual void SetUp() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP() << "Skipping legacy dexopt tests when ART Service is enabled";
+ }
ProfileTest::SetUp();
intial_android_profiles_dir = android_profiles_dir;
@@ -1429,6 +1469,10 @@ class BootProfileTest : public ProfileTest {
}
virtual void TearDown() {
+ if (base::GetBoolProperty("dalvik.vm.useartservice", false)) {
+ GTEST_SKIP();
+ }
+
android_profiles_dir = intial_android_profiles_dir;
deleteAppProfilesForBootMerge();
ProfileTest::TearDown();
@@ -1437,18 +1481,13 @@ class BootProfileTest : public ProfileTest {
void createAppProfilesForBootMerge(size_t number_of_profiles) {
for (size_t i = 0; i < number_of_profiles; i++) {
int64_t ce_data_inode;
+ int64_t de_data_inode;
std::string package_name = "dummy_test_pkg" + std::to_string(i);
LOG(INFO) << package_name;
- ASSERT_BINDER_SUCCESS(service_->createAppData(
- volume_uuid_,
- package_name,
- kTestUserId,
- kAppDataFlags,
- kTestAppUid,
- 0 /* previousAppId */,
- se_info_,
- kOSdkVersion,
- &ce_data_inode));
+ ASSERT_BINDER_SUCCESS(
+ service_->createAppData(volume_uuid_, package_name, kTestUserId, kAppDataFlags,
+ kTestAppUid, 0 /* previousAppId */, se_info_,
+ kOSdkVersion, &ce_data_inode, &de_data_inode));
extra_apps_.push_back(package_name);
extra_ce_data_inodes_.push_back(ce_data_inode);
std::string profile = create_current_profile_path(
diff --git a/cmds/installd/tests/installd_service_test.cpp b/cmds/installd/tests/installd_service_test.cpp
index f86f1d55fa..023491fc10 100644
--- a/cmds/installd/tests/installd_service_test.cpp
+++ b/cmds/installd/tests/installd_service_test.cpp
@@ -42,9 +42,12 @@
#include "binder_test_utils.h"
#include "dexopt.h"
#include "globals.h"
+#include "unique_file.h"
#include "utils.h"
using android::base::StringPrintf;
+using android::base::unique_fd;
+using android::os::ParcelFileDescriptor;
using std::filesystem::is_empty;
namespace android {
@@ -113,6 +116,10 @@ bool create_cache_path(char path[PKG_PATH_MAX], const char *src, const char *ins
return create_cache_path_default(path, src, instruction_set);
}
+bool force_compile_without_image() {
+ return false;
+}
+
static std::string get_full_path(const std::string& path) {
return StringPrintf("%s/%s", kTestPath.c_str(), path.c_str());
}
@@ -132,6 +139,16 @@ static int create(const std::string& path, uid_t owner, gid_t group, mode_t mode
return fd;
}
+static void create_with_content(const std::string& path, uid_t owner, gid_t group, mode_t mode,
+ const std::string& content) {
+ int fd = ::open(path.c_str(), O_RDWR | O_CREAT, mode);
+ EXPECT_NE(fd, -1);
+ EXPECT_TRUE(android::base::WriteStringToFd(content, fd));
+ EXPECT_EQ(::fchown(fd, owner, group), 0);
+ EXPECT_EQ(::fchmod(fd, mode), 0);
+ close(fd);
+}
+
static void touch(const std::string& path, uid_t owner, gid_t group, mode_t mode) {
EXPECT_EQ(::close(create(path.c_str(), owner, group, mode)), 0);
}
@@ -177,6 +194,12 @@ static bool exists_renamed_deleted_dir(const std::string& rootDirectory) {
});
}
+static void unlink_path(const std::string& path) {
+ if (unlink(path.c_str()) < 0) {
+ PLOG(DEBUG) << "Failed to unlink " + path;
+ }
+}
+
class ServiceTest : public testing::Test {
protected:
InstalldNativeService* service;
@@ -523,6 +546,112 @@ TEST_F(ServiceTest, GetAppSizeWrongSizes) {
externalStorageAppId, ceDataInodes, codePaths,
&externalStorageSize));
}
+
+class FsverityTest : public ServiceTest {
+protected:
+ binder::Status createFsveritySetupAuthToken(const std::string& path, int open_mode,
+ sp<IFsveritySetupAuthToken>* _aidl_return) {
+ unique_fd ufd(open(path.c_str(), open_mode));
+ EXPECT_GE(ufd.get(), 0) << "open failed: " << strerror(errno);
+ ParcelFileDescriptor rfd(std::move(ufd));
+ return service->createFsveritySetupAuthToken(std::move(rfd), kTestAppId, _aidl_return);
+ }
+};
+
+TEST_F(FsverityTest, enableFsverity) {
+ const std::string path = kTestPath + "/foo";
+ create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content");
+ UniqueFile raii(/*fd=*/-1, path, &unlink_path);
+
+ // Expect to fs-verity setup to succeed
+ sp<IFsveritySetupAuthToken> authToken;
+ binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(authToken != nullptr);
+
+ // Verity auth token works to enable fs-verity
+ int32_t errno_local;
+ status = service->enableFsverity(authToken, path, "fake.package.name", &errno_local);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(errno_local, 0);
+}
+
+TEST_F(FsverityTest, enableFsverity_nullAuthToken) {
+ const std::string path = kTestPath + "/foo";
+ create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content");
+ UniqueFile raii(/*fd=*/-1, path, &unlink_path);
+
+ // Verity null auth token fails
+ sp<IFsveritySetupAuthToken> authToken;
+ int32_t errno_local;
+ binder::Status status =
+ service->enableFsverity(authToken, path, "fake.package.name", &errno_local);
+ EXPECT_FALSE(status.isOk());
+}
+
+TEST_F(FsverityTest, enableFsverity_differentFile) {
+ const std::string path = kTestPath + "/foo";
+ create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content");
+ UniqueFile raii(/*fd=*/-1, path, &unlink_path);
+
+ // Expect to fs-verity setup to succeed
+ sp<IFsveritySetupAuthToken> authToken;
+ binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(authToken != nullptr);
+
+ // Verity auth token does not work for a different file
+ const std::string anotherPath = kTestPath + "/bar";
+ ASSERT_TRUE(android::base::WriteStringToFile("content", anotherPath));
+ UniqueFile raii2(/*fd=*/-1, anotherPath, &unlink_path);
+ int32_t errno_local;
+ status = service->enableFsverity(authToken, anotherPath, "fake.package.name", &errno_local);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_NE(errno_local, 0);
+}
+
+TEST_F(FsverityTest, enableFsverity_errnoBeforeAuthenticated) {
+ const std::string path = kTestPath + "/foo";
+ create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content");
+ UniqueFile raii(/*fd=*/-1, path, &unlink_path);
+
+ // Expect to fs-verity setup to succeed
+ sp<IFsveritySetupAuthToken> authToken;
+ binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(authToken != nullptr);
+
+ // Verity errno before the fd authentication is constant (EPERM)
+ int32_t errno_local;
+ status = service->enableFsverity(authToken, path + "-non-exist", "fake.package.name",
+ &errno_local);
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(errno_local, EPERM);
+}
+
+TEST_F(FsverityTest, createFsveritySetupAuthToken_ReadonlyFdDoesNotAuthenticate) {
+ const std::string path = kTestPath + "/foo";
+ create_with_content(path, kTestAppUid, kTestAppUid, 0600, "content");
+ UniqueFile raii(/*fd=*/-1, path, &unlink_path);
+
+ // Expect the fs-verity setup to fail
+ sp<IFsveritySetupAuthToken> authToken;
+ binder::Status status = createFsveritySetupAuthToken(path, O_RDONLY, &authToken);
+ EXPECT_FALSE(status.isOk());
+}
+
+TEST_F(FsverityTest, createFsveritySetupAuthToken_UnownedFile) {
+ const std::string path = kTestPath + "/foo";
+ // Simulate world-writable file owned by another app
+ create_with_content(path, kTestAppUid + 1, kTestAppUid + 1, 0666, "content");
+ UniqueFile raii(/*fd=*/-1, path, &unlink_path);
+
+ // Expect the fs-verity setup to fail
+ sp<IFsveritySetupAuthToken> authToken;
+ binder::Status status = createFsveritySetupAuthToken(path, O_RDWR, &authToken);
+ EXPECT_FALSE(status.isOk());
+}
+
static bool mkdirs(const std::string& path, mode_t mode) {
struct stat sb;
if (stat(path.c_str(), &sb) != -1 && S_ISDIR(sb.st_mode)) {
diff --git a/cmds/installd/utils.h b/cmds/installd/utils.h
index ecea1d2b1c..c43fdbd547 100644
--- a/cmds/installd/utils.h
+++ b/cmds/installd/utils.h
@@ -18,6 +18,7 @@
#ifndef UTILS_H_
#define UTILS_H_
+#include <functional>
#include <string>
#include <vector>
diff --git a/cmds/installd/utils_default.cpp b/cmds/installd/utils_default.cpp
index a6025e67c8..85ce4509e1 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/installd/view_compiler.cpp b/cmds/installd/view_compiler.cpp
deleted file mode 100644
index 8c000a11c9..0000000000
--- a/cmds/installd/view_compiler.cpp
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-#include "view_compiler.h"
-
-#include <string>
-
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <sys/wait.h>
-#include <unistd.h>
-
-#include "utils.h"
-
-#include "android-base/logging.h"
-#include "android-base/stringprintf.h"
-#include "android-base/unique_fd.h"
-
-namespace android {
-namespace installd {
-
-namespace {
-
-using ::android::base::unique_fd;
-
-constexpr int kTimeoutMs = 300000;
-
-} // namespace
-
-bool view_compiler(const char* apk_path, const char* package_name, const char* out_dex_file,
- int uid) {
- CHECK(apk_path != nullptr);
- CHECK(package_name != nullptr);
- CHECK(out_dex_file != nullptr);
-
- // viewcompiler won't have permission to open anything, so we have to open the files first
- // and pass file descriptors.
-
- // Open input file
- unique_fd infd{open(apk_path, O_RDONLY)}; // NOLINT(android-cloexec-open)
- if (infd.get() < 0) {
- PLOG(ERROR) << "Could not open input file: " << apk_path;
- return false;
- }
-
- // Set up output file. viewcompiler can't open outputs by fd, but it can write to stdout, so
- // we close stdout and open it towards the right output.
- unique_fd outfd{open(out_dex_file, O_CREAT | O_TRUNC | O_WRONLY | O_CLOEXEC, 0644)};
- if (outfd.get() < 0) {
- PLOG(ERROR) << "Could not open output file: " << out_dex_file;
- return false;
- }
- if (fchmod(outfd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) != 0) {
- PLOG(ERROR) << "Could not change output file permissions";
- return false;
- }
- if (dup2(outfd, STDOUT_FILENO) < 0) {
- PLOG(ERROR) << "Could not duplicate output file descriptor";
- return false;
- }
-
- // Prepare command line arguments for viewcompiler
- std::string args[] = {"/system/bin/viewcompiler",
- "--apk",
- "--infd",
- android::base::StringPrintf("%d", infd.get()),
- "--dex",
- "--package",
- package_name};
- char* const argv[] = {const_cast<char*>(args[0].c_str()), const_cast<char*>(args[1].c_str()),
- const_cast<char*>(args[2].c_str()), const_cast<char*>(args[3].c_str()),
- const_cast<char*>(args[4].c_str()), const_cast<char*>(args[5].c_str()),
- const_cast<char*>(args[6].c_str()), nullptr};
-
- pid_t pid = fork();
- if (pid == 0) {
- // Now that we've opened the files we need, drop privileges.
- drop_capabilities(uid);
- execv("/system/bin/viewcompiler", argv);
- _exit(1);
- }
-
- int return_code = wait_child_with_timeout(pid, kTimeoutMs);
- if (!WIFEXITED(return_code)) {
- LOG(WARNING) << "viewcompiler failed for " << package_name << ":" << apk_path;
- if (WTERMSIG(return_code) == SIGKILL) {
- // If the subprocess is killed while it's writing to the file, the file is likely
- // corrupted, so we should remove it.
- remove_file_at_fd(outfd.get());
- }
- return false;
- }
- return WEXITSTATUS(return_code) == 0;
-}
-
-} // namespace installd
-} // namespace android
diff --git a/cmds/ip-up-vpn/ip-up-vpn.c b/cmds/ip-up-vpn/ip-up-vpn.c
deleted file mode 100644
index 71f08375f7..0000000000
--- a/cmds/ip-up-vpn/ip-up-vpn.c
+++ /dev/null
@@ -1,139 +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.
- */
-
-#define LOG_TAG "ip-up-vpn"
-
-#include <arpa/inet.h>
-#include <errno.h>
-#include <linux/if.h>
-#include <linux/route.h>
-#include <netinet/in.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-
-#include <log/log.h>
-
-#define DIR "/data/misc/vpn/"
-
-static const char *env(const char *name) {
- const char *value = getenv(name);
- return value ? value : "";
-}
-
-static int set_address(struct sockaddr *sa, const char *address) {
- sa->sa_family = AF_INET;
- errno = EINVAL;
- return inet_pton(AF_INET, address, &((struct sockaddr_in *)sa)->sin_addr);
-}
-
-/*
- * The primary goal is to create a file with VPN parameters. Currently they
- * are interface, addresses, routes, DNS servers, and search domains and VPN
- * server address. Each parameter occupies one line in the file, and it can be
- * an empty string or space-separated values. The order and the format must be
- * consistent with com.android.server.connectivity.Vpn. Here is an example.
- *
- * ppp0
- * 192.168.1.100/24
- * 0.0.0.0/0
- * 192.168.1.1 192.168.1.2
- * example.org
- * 192.0.2.1
- *
- * The secondary goal is to unify the outcome of VPN. The current baseline
- * is to have an interface configured with the given address and netmask
- * and maybe add a host route to protect the tunnel. PPP-based VPN already
- * does this, but others might not. Routes, DNS servers, and search domains
- * are handled by the framework since they can be overridden by the users.
- */
-int main(int argc, char **argv)
-{
- FILE *state = fopen(DIR ".tmp", "wb");
- if (!state) {
- ALOGE("Cannot create state: %s", strerror(errno));
- return 1;
- }
-
- if (argc >= 6) {
- /* Invoked by pppd. */
- fprintf(state, "%s\n", argv[1]);
- fprintf(state, "%s/32\n", argv[4]);
- fprintf(state, "0.0.0.0/0\n");
- fprintf(state, "%s %s\n", env("DNS1"), env("DNS2"));
- fprintf(state, "\n");
- fprintf(state, "\n");
- } else if (argc == 2) {
- /* Invoked by racoon. */
- const char *interface = env("INTERFACE");
- const char *address = env("INTERNAL_ADDR4");
- const char *routes = env("SPLIT_INCLUDE_CIDR");
-
- int s = socket(AF_INET, SOCK_DGRAM, 0);
- struct ifreq ifr;
- memset(&ifr, 0, sizeof(ifr));
-
- /* Bring up the interface. */
- ifr.ifr_flags = IFF_UP;
- strncpy(ifr.ifr_name, interface, IFNAMSIZ);
- if (ioctl(s, SIOCSIFFLAGS, &ifr)) {
- ALOGE("Cannot bring up %s: %s", interface, strerror(errno));
- fclose(state);
- return 1;
- }
-
- /* Set the address. */
- if (!set_address(&ifr.ifr_addr, address) ||
- ioctl(s, SIOCSIFADDR, &ifr)) {
- ALOGE("Cannot set address: %s", strerror(errno));
- fclose(state);
- return 1;
- }
-
- /* Set the netmask. */
- if (set_address(&ifr.ifr_netmask, env("INTERNAL_NETMASK4"))) {
- if (ioctl(s, SIOCSIFNETMASK, &ifr)) {
- ALOGE("Cannot set netmask: %s", strerror(errno));
- fclose(state);
- return 1;
- }
- }
-
- /* TODO: Send few packets to trigger phase 2? */
-
- fprintf(state, "%s\n", interface);
- fprintf(state, "%s/%s\n", address, env("INTERNAL_CIDR4"));
- fprintf(state, "%s\n", routes[0] ? routes : "0.0.0.0/0");
- fprintf(state, "%s\n", env("INTERNAL_DNS4_LIST"));
- fprintf(state, "%s\n", env("DEFAULT_DOMAIN"));
- fprintf(state, "%s\n", env("REMOTE_ADDR"));
- } else {
- ALOGE("Cannot parse parameters");
- fclose(state);
- return 1;
- }
-
- fclose(state);
- if (chmod(DIR ".tmp", 0444) || rename(DIR ".tmp", DIR "state")) {
- ALOGE("Cannot write state: %s", strerror(errno));
- return 1;
- }
- return 0;
-}
diff --git a/cmds/lshal/ListCommand.cpp b/cmds/lshal/ListCommand.cpp
index e54f9d3df7..0c1feb8fec 100644
--- a/cmds/lshal/ListCommand.cpp
+++ b/cmds/lshal/ListCommand.cpp
@@ -44,6 +44,7 @@
#include "Timeout.h"
#include "utils.h"
+using ::android::hardware::hidl_array;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hidl::base::V1_0::DebugInfo;
@@ -522,27 +523,35 @@ Status ListCommand::fetchAllLibraries(const sp<IServiceManager> &manager) {
using namespace ::android::hardware;
using namespace ::android::hidl::manager::V1_0;
using namespace ::android::hidl::base::V1_0;
- using std::literals::chrono_literals::operator""s;
- auto ret = timeoutIPC(10s, manager, &IServiceManager::debugDump, [&] (const auto &infos) {
- std::map<std::string, TableEntry> entries;
- for (const auto &info : infos) {
- std::string interfaceName = std::string{info.interfaceName.c_str()} + "/" +
- std::string{info.instanceName.c_str()};
- entries.emplace(interfaceName, TableEntry{
- .interfaceName = interfaceName,
- .transport = vintf::Transport::PASSTHROUGH,
- .clientPids = info.clientPids,
- }).first->second.arch |= fromBaseArchitecture(info.arch);
- }
- for (auto &&pair : entries) {
- putEntry(HalType::PASSTHROUGH_LIBRARIES, std::move(pair.second));
- }
- });
+
+ // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+ // even though the interface function call is synchronous.
+ // However, there's no need to lock because if ret.isOk(), the background thread has
+ // already ended, so it is safe to dereference entries.
+ auto entries = std::make_shared<std::map<std::string, TableEntry>>();
+ auto ret =
+ timeoutIPC(mLshal.getDebugDumpWait(), manager, &IServiceManager::debugDump,
+ [entries](const auto& infos) {
+ for (const auto& info : infos) {
+ std::string interfaceName = std::string{info.interfaceName.c_str()} +
+ "/" + std::string{info.instanceName.c_str()};
+ entries->emplace(interfaceName,
+ TableEntry{
+ .interfaceName = interfaceName,
+ .transport = vintf::Transport::PASSTHROUGH,
+ .clientPids = info.clientPids,
+ })
+ .first->second.arch |= fromBaseArchitecture(info.arch);
+ }
+ });
if (!ret.isOk()) {
err() << "Error: Failed to call list on getPassthroughServiceManager(): "
<< ret.description() << std::endl;
return DUMP_ALL_LIBS_ERROR;
}
+ for (auto&& pair : *entries) {
+ putEntry(HalType::PASSTHROUGH_LIBRARIES, std::move(pair.second));
+ }
return OK;
}
@@ -553,27 +562,40 @@ Status ListCommand::fetchPassthrough(const sp<IServiceManager> &manager) {
using namespace ::android::hardware::details;
using namespace ::android::hidl::manager::V1_0;
using namespace ::android::hidl::base::V1_0;
- auto ret = timeoutIPC(manager, &IServiceManager::debugDump, [&] (const auto &infos) {
- for (const auto &info : infos) {
- if (info.clientPids.size() <= 0) {
- continue;
- }
- putEntry(HalType::PASSTHROUGH_CLIENTS, {
- .interfaceName =
- std::string{info.interfaceName.c_str()} + "/" +
- std::string{info.instanceName.c_str()},
- .transport = vintf::Transport::PASSTHROUGH,
- .serverPid = info.clientPids.size() == 1 ? info.clientPids[0] : NO_PID,
- .clientPids = info.clientPids,
- .arch = fromBaseArchitecture(info.arch)
- });
- }
- });
+
+ // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+ // even though the interface function call is synchronous.
+ // However, there's no need to lock because if ret.isOk(), the background thread has
+ // already ended, so it is safe to dereference entries.
+ auto entries = std::make_shared<std::vector<TableEntry>>();
+ auto ret =
+ timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::debugDump,
+ [entries](const auto& infos) {
+ for (const auto& info : infos) {
+ if (info.clientPids.size() <= 0) {
+ continue;
+ }
+ entries->emplace_back(
+ TableEntry{.interfaceName =
+ std::string{info.interfaceName.c_str()} +
+ "/" +
+ std::string{info.instanceName.c_str()},
+ .transport = vintf::Transport::PASSTHROUGH,
+ .serverPid = info.clientPids.size() == 1
+ ? info.clientPids[0]
+ : NO_PID,
+ .clientPids = info.clientPids,
+ .arch = fromBaseArchitecture(info.arch)});
+ }
+ });
if (!ret.isOk()) {
err() << "Error: Failed to call debugDump on defaultServiceManager(): "
<< ret.description() << std::endl;
return DUMP_PASSTHROUGH_ERROR;
}
+ for (auto&& entry : *entries) {
+ putEntry(HalType::PASSTHROUGH_CLIENTS, std::move(entry));
+ }
return OK;
}
@@ -583,11 +605,14 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) {
if (!shouldFetchHalType(HalType::BINDERIZED_SERVICES)) { return OK; }
const vintf::Transport mode = vintf::Transport::HWBINDER;
- hidl_vec<hidl_string> fqInstanceNames;
- // copying out for timeoutIPC
- auto listRet = timeoutIPC(manager, &IServiceManager::list, [&] (const auto &names) {
- fqInstanceNames = names;
- });
+
+ // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+ // even though the interface function call is synchronous.
+ // However, there's no need to lock because if listRet.isOk(), the background thread has
+ // already ended, so it is safe to dereference fqInstanceNames.
+ auto fqInstanceNames = std::make_shared<hidl_vec<hidl_string>>();
+ auto listRet = timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::list,
+ [fqInstanceNames](const auto& names) { *fqInstanceNames = names; });
if (!listRet.isOk()) {
err() << "Error: Failed to list services for " << mode << ": "
<< listRet.description() << std::endl;
@@ -596,7 +621,7 @@ Status ListCommand::fetchBinderized(const sp<IServiceManager> &manager) {
Status status = OK;
std::map<std::string, TableEntry> allTableEntries;
- for (const auto &fqInstanceName : fqInstanceNames) {
+ for (const auto& fqInstanceName : *fqInstanceNames) {
// create entry and default assign all fields.
TableEntry& entry = allTableEntries[fqInstanceName];
entry.interfaceName = fqInstanceName;
@@ -623,7 +648,8 @@ Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager,
const auto pair = splitFirst(entry->interfaceName, '/');
const auto &serviceName = pair.first;
const auto &instanceName = pair.second;
- auto getRet = timeoutIPC(manager, &IServiceManager::get, serviceName, instanceName);
+ auto getRet = timeoutIPC(mLshal.getIpcCallWait(), manager, &IServiceManager::get, serviceName,
+ instanceName);
if (!getRet.isOk()) {
handleError(TRANSACTION_ERROR,
"cannot be fetched from service manager:" + getRet.description());
@@ -637,30 +663,33 @@ Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager,
// getDebugInfo
do {
- DebugInfo debugInfo;
- auto debugRet = timeoutIPC(service, &IBase::getDebugInfo, [&] (const auto &received) {
- debugInfo = received;
- });
+ // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+ // even though the interface function call is synchronous.
+ // However, there's no need to lock because if debugRet.isOk(), the background thread has
+ // already ended, so it is safe to dereference debugInfo.
+ auto debugInfo = std::make_shared<DebugInfo>();
+ auto debugRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::getDebugInfo,
+ [debugInfo](const auto& received) { *debugInfo = received; });
if (!debugRet.isOk()) {
handleError(TRANSACTION_ERROR,
"debugging information cannot be retrieved: " + debugRet.description());
break; // skip getPidInfo
}
- entry->serverPid = debugInfo.pid;
- entry->serverObjectAddress = debugInfo.ptr;
- entry->arch = fromBaseArchitecture(debugInfo.arch);
+ entry->serverPid = debugInfo->pid;
+ entry->serverObjectAddress = debugInfo->ptr;
+ entry->arch = fromBaseArchitecture(debugInfo->arch);
- if (debugInfo.pid != NO_PID) {
- const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo.pid);
+ if (debugInfo->pid != NO_PID) {
+ const BinderPidInfo* pidInfo = getPidInfoCached(debugInfo->pid);
if (pidInfo == nullptr) {
handleError(IO_ERROR,
- "no information for PID " + std::to_string(debugInfo.pid) +
- ", are you root?");
+ "no information for PID " + std::to_string(debugInfo->pid) +
+ ", are you root?");
break;
}
- if (debugInfo.ptr != NO_PTR) {
- auto it = pidInfo->refPids.find(debugInfo.ptr);
+ if (debugInfo->ptr != NO_PTR) {
+ auto it = pidInfo->refPids.find(debugInfo->ptr);
if (it != pidInfo->refPids.end()) {
entry->clientPids = it->second;
}
@@ -672,39 +701,47 @@ Status ListCommand::fetchBinderizedEntry(const sp<IServiceManager> &manager,
// hash
do {
- ssize_t hashIndex = -1;
- auto ifaceChainRet = timeoutIPC(service, &IBase::interfaceChain, [&] (const auto& c) {
- for (size_t i = 0; i < c.size(); ++i) {
- if (serviceName == c[i]) {
- hashIndex = static_cast<ssize_t>(i);
- break;
- }
- }
- });
+ // The lambda function may be executed asynchrounously because it is passed to timeoutIPC,
+ // even though the interface function call is synchronous.
+ auto hashIndexStore = std::make_shared<ssize_t>(-1);
+ auto ifaceChainRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::interfaceChain,
+ [hashIndexStore, serviceName](const auto& c) {
+ for (size_t i = 0; i < c.size(); ++i) {
+ if (serviceName == c[i]) {
+ *hashIndexStore = static_cast<ssize_t>(i);
+ break;
+ }
+ }
+ });
if (!ifaceChainRet.isOk()) {
handleError(TRANSACTION_ERROR,
"interfaceChain fails: " + ifaceChainRet.description());
break; // skip getHashChain
}
+ // if ifaceChainRet.isOk(), the background thread has already ended, so it is safe to
+ // dereference hashIndex without any locking.
+ auto hashIndex = *hashIndexStore;
if (hashIndex < 0) {
handleError(BAD_IMPL, "Interface name does not exist in interfaceChain.");
break; // skip getHashChain
}
- auto hashRet = timeoutIPC(service, &IBase::getHashChain, [&] (const auto& hashChain) {
- if (static_cast<size_t>(hashIndex) >= hashChain.size()) {
- handleError(BAD_IMPL,
- "interfaceChain indicates position " + std::to_string(hashIndex) +
- " but getHashChain returns " + std::to_string(hashChain.size()) +
- " hashes");
- return;
- }
-
- auto&& hashArray = hashChain[hashIndex];
- entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
- });
+ // See comments about hashIndex above.
+ auto hashChain = std::make_shared<hidl_vec<hidl_array<uint8_t, 32>>>();
+ auto hashRet = timeoutIPC(mLshal.getIpcCallWait(), service, &IBase::getHashChain,
+ [hashChain](const auto& ret) { *hashChain = std::move(ret); });
if (!hashRet.isOk()) {
handleError(TRANSACTION_ERROR, "getHashChain failed: " + hashRet.description());
+ break; // skip getHashChain
+ }
+ if (static_cast<size_t>(hashIndex) >= hashChain->size()) {
+ handleError(BAD_IMPL,
+ "interfaceChain indicates position " + std::to_string(hashIndex) +
+ " but getHashChain returns " + std::to_string(hashChain->size()) +
+ " hashes");
+ break; // skip getHashChain
}
+ auto&& hashArray = (*hashChain)[hashIndex];
+ entry->hash = android::base::HexString(hashArray.data(), hashArray.size());
} while (0);
if (status == OK) {
entry->serviceStatus = ServiceStatus::ALIVE;
diff --git a/cmds/lshal/Lshal.cpp b/cmds/lshal/Lshal.cpp
index a5f98c2a9e..5cdcb23d13 100644
--- a/cmds/lshal/Lshal.cpp
+++ b/cmds/lshal/Lshal.cpp
@@ -232,6 +232,11 @@ Status Lshal::main(const Arg &arg) {
return static_cast<HelpCommand*>(help)->usageOfCommand(mCommand);
}
+ // After Lshal::main() finishes, caller may call _exit(), causing debug
+ // information to prematurely ends. Hence flush().
+ err().flush();
+ out().flush();
+
return status;
}
@@ -250,5 +255,17 @@ const sp<IServiceManager> &Lshal::passthroughManager() const {
return mPassthroughManager;
}
+void Lshal::setWaitTimeForTest(std::chrono::milliseconds ipcCallWait,
+ std::chrono::milliseconds debugDumpWait) {
+ mIpcCallWait = ipcCallWait;
+ mDebugDumpWait = debugDumpWait;
+}
+std::chrono::milliseconds Lshal::getIpcCallWait() const {
+ return mIpcCallWait;
+}
+std::chrono::milliseconds Lshal::getDebugDumpWait() const {
+ return mDebugDumpWait;
+}
+
} // namespace lshal
} // namespace android
diff --git a/cmds/lshal/Lshal.h b/cmds/lshal/Lshal.h
index 50279d4d7a..cb2820c535 100644
--- a/cmds/lshal/Lshal.h
+++ b/cmds/lshal/Lshal.h
@@ -16,6 +16,7 @@
#pragma once
+#include <chrono>
#include <iostream>
#include <string>
@@ -58,6 +59,11 @@ public:
void forEachCommand(const std::function<void(const Command* c)>& f) const;
+ void setWaitTimeForTest(std::chrono::milliseconds ipcCallWait,
+ std::chrono::milliseconds debugDumpWait);
+ std::chrono::milliseconds getIpcCallWait() const;
+ std::chrono::milliseconds getDebugDumpWait() const;
+
private:
Status parseArgs(const Arg &arg);
@@ -70,6 +76,9 @@ private:
std::vector<std::unique_ptr<Command>> mRegisteredCommands;
+ std::chrono::milliseconds mIpcCallWait{500};
+ std::chrono::milliseconds mDebugDumpWait{10000};
+
DISALLOW_COPY_AND_ASSIGN(Lshal);
};
diff --git a/cmds/lshal/NullableOStream.h b/cmds/lshal/NullableOStream.h
index 7cffcf8193..1576486c1d 100644
--- a/cmds/lshal/NullableOStream.h
+++ b/cmds/lshal/NullableOStream.h
@@ -59,6 +59,11 @@ public:
operator bool() const { // NOLINT(google-explicit-constructor)
return mOs != nullptr;
}
+ void flush() {
+ if (mOs) {
+ mOs->flush();
+ }
+ }
private:
template<typename>
friend class NullableOStream;
diff --git a/cmds/lshal/Timeout.h b/cmds/lshal/Timeout.h
index e8d22d9b58..d97ba89122 100644
--- a/cmds/lshal/Timeout.h
+++ b/cmds/lshal/Timeout.h
@@ -16,92 +16,44 @@
#pragma once
-#include <condition_variable>
#include <chrono>
-#include <functional>
-#include <mutex>
-#include <thread>
+#include <future>
#include <hidl/Status.h>
+#include <utils/Errors.h>
namespace android {
namespace lshal {
-static constexpr std::chrono::milliseconds IPC_CALL_WAIT{500};
-
-class BackgroundTaskState {
-public:
- explicit BackgroundTaskState(std::function<void(void)> &&func)
- : mFunc(std::forward<decltype(func)>(func)) {}
- void notify() {
- std::unique_lock<std::mutex> lock(mMutex);
- mFinished = true;
- lock.unlock();
- mCondVar.notify_all();
- }
- template<class C, class D>
- bool wait(std::chrono::time_point<C, D> end) {
- std::unique_lock<std::mutex> lock(mMutex);
- mCondVar.wait_until(lock, end, [this](){ return this->mFinished; });
- return mFinished;
- }
- void operator()() {
- mFunc();
- }
-private:
- std::mutex mMutex;
- std::condition_variable mCondVar;
- bool mFinished = false;
- std::function<void(void)> mFunc;
-};
-
-void *callAndNotify(void *data) {
- BackgroundTaskState &state = *static_cast<BackgroundTaskState *>(data);
- state();
- state.notify();
- return nullptr;
-}
-
-template<class R, class P>
-bool timeout(std::chrono::duration<R, P> delay, std::function<void(void)> &&func) {
- auto now = std::chrono::system_clock::now();
- BackgroundTaskState state{std::forward<decltype(func)>(func)};
- pthread_t thread;
- if (pthread_create(&thread, nullptr, callAndNotify, &state)) {
- std::cerr << "FATAL: could not create background thread." << std::endl;
- return false;
- }
- bool success = state.wait(now + delay);
- if (!success) {
- pthread_kill(thread, SIGINT);
- }
- pthread_join(thread, nullptr);
- return success;
-}
-
+// Call function on interfaceObject and wait for result until the given timeout has reached.
+// Callback functions pass to timeoutIPC() may be executed after the this function
+// has returned, especially if deadline has been reached. Hence, care must be taken when passing
+// data between the background thread and the main thread. See b/311143089.
template<class R, class P, class Function, class I, class... Args>
-typename std::result_of<Function(I *, Args...)>::type
+typename std::invoke_result<Function, I *, Args...>::type
timeoutIPC(std::chrono::duration<R, P> wait, const sp<I> &interfaceObject, Function &&func,
Args &&... args) {
using ::android::hardware::Status;
- typename std::result_of<Function(I *, Args...)>::type ret{Status::ok()};
- auto boundFunc = std::bind(std::forward<Function>(func),
- interfaceObject.get(), std::forward<Args>(args)...);
- bool success = timeout(wait, [&ret, &boundFunc] {
- ret = std::move(boundFunc());
- });
- if (!success) {
- return Status::fromStatusT(TIMED_OUT);
- }
- return ret;
-}
-template<class Function, class I, class... Args>
-typename std::result_of<Function(I *, Args...)>::type
-timeoutIPC(const sp<I> &interfaceObject, Function &&func, Args &&... args) {
- return timeoutIPC(IPC_CALL_WAIT, interfaceObject, func, args...);
-}
+ // Execute on a background thread but do not defer execution.
+ auto future =
+ std::async(std::launch::async, func, interfaceObject, std::forward<Args>(args)...);
+ auto status = future.wait_for(wait);
+ if (status == std::future_status::ready) {
+ return future.get();
+ }
+ // This future belongs to a background thread that we no longer care about.
+ // Putting this in the global list avoids std::future::~future() that may wait for the
+ // result to come back.
+ // This leaks memory, but lshal is a debugging tool, so this is fine.
+ static std::vector<decltype(future)> gDeadPool{};
+ gDeadPool.emplace_back(std::move(future));
-} // namespace lshal
-} // namespace android
+ if (status == std::future_status::timeout) {
+ return Status::fromStatusT(TIMED_OUT);
+ }
+ return Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE, "Illegal future_status");
+}
+} // namespace lshal
+} // namespace android
diff --git a/cmds/lshal/libprocpartition/Android.bp b/cmds/lshal/libprocpartition/Android.bp
index af85666276..d0e4b74543 100644
--- a/cmds/lshal/libprocpartition/Android.bp
+++ b/cmds/lshal/libprocpartition/Android.bp
@@ -37,4 +37,8 @@ cc_library_static {
"include",
],
min_sdk_version: "30",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.neuralnetworks",
+ ],
}
diff --git a/cmds/lshal/main.cpp b/cmds/lshal/main.cpp
index 366c9383a2..bd5fa32521 100644
--- a/cmds/lshal/main.cpp
+++ b/cmds/lshal/main.cpp
@@ -18,5 +18,6 @@
int main(int argc, char **argv) {
using namespace ::android::lshal;
- return Lshal{}.main(Arg{argc, argv});
+ // Use _exit() to force terminate background threads in Timeout.h
+ _exit(Lshal{}.main(Arg{argc, argv}));
}
diff --git a/cmds/lshal/test.cpp b/cmds/lshal/test.cpp
index cba7c4bf2a..c24f827e73 100644
--- a/cmds/lshal/test.cpp
+++ b/cmds/lshal/test.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#include <chrono>
+#include <future>
+#include <mutex>
+#include "android/hidl/base/1.0/IBase.h"
#define LOG_TAG "Lshal"
#include <android-base/logging.h>
@@ -36,6 +40,8 @@
using namespace testing;
+using std::chrono_literals::operator""ms;
+
using ::android::hidl::base::V1_0::DebugInfo;
using ::android::hidl::base::V1_0::IBase;
using ::android::hidl::manager::V1_0::IServiceManager;
@@ -934,12 +940,9 @@ TEST_F(ListTest, DumpDebug) {
return hardware::Void();
}));
EXPECT_CALL(*serviceManager, get(_, _))
- .WillRepeatedly(
- Invoke([&](const hidl_string&, const hidl_string& instance) -> sp<IBase> {
- int id = getIdFromInstanceName(instance);
- if (id > inheritanceLevel) return nullptr;
- return sp<IBase>(service);
- }));
+ .WillRepeatedly(Invoke([&](const hidl_string&, const hidl_string&) -> sp<IBase> {
+ return sp<IBase>(service);
+ }));
const std::string expected = "[fake description 0]\n"
"Interface\n"
@@ -957,6 +960,110 @@ TEST_F(ListTest, DumpDebug) {
EXPECT_EQ("", err.str());
}
+// In SlowService, everything goes slooooooow. Each IPC call will wait for
+// the specified time before calling the callback function or returning.
+class SlowService : public IBase {
+public:
+ explicit SlowService(std::chrono::milliseconds wait) : mWait(wait) {}
+ android::hardware::Return<void> interfaceDescriptor(interfaceDescriptor_cb cb) override {
+ std::this_thread::sleep_for(mWait);
+ cb(getInterfaceName(1));
+ storeHistory("interfaceDescriptor");
+ return hardware::Void();
+ }
+ android::hardware::Return<void> interfaceChain(interfaceChain_cb cb) override {
+ std::this_thread::sleep_for(mWait);
+ std::vector<hidl_string> ret;
+ ret.push_back(getInterfaceName(1));
+ ret.push_back(IBase::descriptor);
+ cb(ret);
+ storeHistory("interfaceChain");
+ return hardware::Void();
+ }
+ android::hardware::Return<void> getHashChain(getHashChain_cb cb) override {
+ std::this_thread::sleep_for(mWait);
+ std::vector<hidl_hash> ret;
+ ret.push_back(getHashFromId(0));
+ ret.push_back(getHashFromId(0xff));
+ cb(ret);
+ storeHistory("getHashChain");
+ return hardware::Void();
+ }
+ android::hardware::Return<void> debug(const hidl_handle&,
+ const hidl_vec<hidl_string>&) override {
+ std::this_thread::sleep_for(mWait);
+ storeHistory("debug");
+ return Void();
+ }
+
+ template <class R, class P, class Pred>
+ bool waitForHistory(std::chrono::duration<R, P> wait, Pred predicate) {
+ std::unique_lock<std::mutex> lock(mLock);
+ return mCv.wait_for(lock, wait, [&]() { return predicate(mCallHistory); });
+ }
+
+private:
+ void storeHistory(std::string hist) {
+ {
+ std::lock_guard<std::mutex> lock(mLock);
+ mCallHistory.emplace_back(std::move(hist));
+ }
+ mCv.notify_all();
+ }
+
+ const std::chrono::milliseconds mWait;
+ std::mutex mLock;
+ std::condition_variable mCv;
+ // List of functions that have finished being called on this interface.
+ std::vector<std::string> mCallHistory;
+};
+
+class TimeoutTest : public ListTest {
+public:
+ void setMockServiceManager(sp<IBase> service) {
+ EXPECT_CALL(*serviceManager, list(_))
+ .WillRepeatedly(Invoke([&](IServiceManager::list_cb cb) {
+ std::vector<hidl_string> ret;
+ ret.push_back(getInterfaceName(1) + "/default");
+ cb(ret);
+ return hardware::Void();
+ }));
+ EXPECT_CALL(*serviceManager, get(_, _))
+ .WillRepeatedly(Invoke([&](const hidl_string&, const hidl_string&) -> sp<IBase> {
+ return service;
+ }));
+ }
+};
+
+TEST_F(TimeoutTest, BackgroundThreadIsKept) {
+ auto lshalIpcTimeout = 100ms;
+ auto serviceIpcTimeout = 200ms;
+ lshal->setWaitTimeForTest(lshalIpcTimeout, lshalIpcTimeout);
+ sp<SlowService> service = new SlowService(serviceIpcTimeout);
+ setMockServiceManager(service);
+
+ optind = 1; // mimic Lshal::parseArg()
+ EXPECT_NE(0u, mockList->main(createArg({"lshal", "--types=b", "-i", "--neat"})));
+ EXPECT_THAT(err.str(), HasSubstr("Skipping \"a.h.foo1@1.0::IFoo/default\""));
+ EXPECT_TRUE(service->waitForHistory(serviceIpcTimeout * 5, [](const auto& hist) {
+ return hist.size() == 1 && hist[0] == "interfaceChain";
+ })) << "The background thread should continue after the main thread moves on, but it is killed";
+}
+
+TEST_F(TimeoutTest, BackgroundThreadDoesNotBlockMainThread) {
+ auto lshalIpcTimeout = 100ms;
+ auto serviceIpcTimeout = 2000ms;
+ auto start = std::chrono::system_clock::now();
+ lshal->setWaitTimeForTest(lshalIpcTimeout, lshalIpcTimeout);
+ sp<SlowService> service = new SlowService(serviceIpcTimeout);
+ setMockServiceManager(service);
+
+ optind = 1; // mimic Lshal::parseArg()
+ EXPECT_NE(0u, mockList->main(createArg({"lshal", "--types=b", "-i", "--neat"})));
+ EXPECT_LE(std::chrono::system_clock::now(), start + 5 * lshalIpcTimeout)
+ << "The main thread should not be blocked by the background task";
+}
+
class ListVintfTest : public ListTest {
public:
virtual void SetUp() override {
@@ -1079,5 +1186,6 @@ TEST_F(HelpTest, UnknownOptionHelp2) {
int main(int argc, char **argv) {
::testing::InitGoogleMock(&argc, argv);
- return RUN_ALL_TESTS();
+ // Use _exit() to force terminate background threads in Timeout.h
+ _exit(RUN_ALL_TESTS());
}
diff --git a/cmds/service/service.cpp b/cmds/service/service.cpp
index d5ca725eb9..5e8ef5d7d8 100644
--- a/cmds/service/service.cpp
+++ b/cmds/service/service.cpp
@@ -75,7 +75,7 @@ int main(int argc, char* const argv[])
ProcessState::initWithDriver("/dev/vndbinder");
#endif
#ifndef __ANDROID__
- setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingThreads = 1}));
+ setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingConnections = 1}));
#endif
sp<IServiceManager> sm = defaultServiceManager();
fflush(stdout);
diff --git a/cmds/servicemanager/Access.cpp b/cmds/servicemanager/Access.cpp
index 711038ce63..809872417d 100644
--- a/cmds/servicemanager/Access.cpp
+++ b/cmds/servicemanager/Access.cpp
@@ -22,6 +22,8 @@
#include <selinux/android.h>
#include <selinux/avc.h>
+#include <sstream>
+
namespace android {
#ifdef VENDORSERVICEMANAGER
@@ -80,6 +82,12 @@ static int auditCallback(void *data, security_class_t /*cls*/, char *buf, size_t
}
#endif
+std::string Access::CallingContext::toDebugString() const {
+ std::stringstream ss;
+ ss << "Caller(pid=" << debugPid << ",uid=" << uid << ",sid=" << sid << ")";
+ return ss.str();
+}
+
Access::Access() {
#ifdef __ANDROID__
union selinux_callback cb;
diff --git a/cmds/servicemanager/Access.h b/cmds/servicemanager/Access.h
index 77c2cd4ed6..4ee9b907d7 100644
--- a/cmds/servicemanager/Access.h
+++ b/cmds/servicemanager/Access.h
@@ -36,6 +36,8 @@ public:
pid_t debugPid;
uid_t uid;
std::string sid;
+
+ std::string toDebugString() const;
};
virtual CallingContext getCallingContext();
diff --git a/cmds/servicemanager/Android.bp b/cmds/servicemanager/Android.bp
index 1386660eb6..3897197bd5 100644
--- a/cmds/servicemanager/Android.bp
+++ b/cmds/servicemanager/Android.bp
@@ -24,7 +24,6 @@ cc_defaults {
shared_libs: [
"libbase",
- "libbinder", // also contains servicemanager_interface
"libvintf",
"libcutils",
"liblog",
@@ -33,6 +32,21 @@ cc_defaults {
],
target: {
+ android: {
+ shared_libs: [
+ "libbinder",
+ "libutils",
+ ],
+ },
+ host: {
+ static_libs: [
+ "libbinder",
+ "libutils",
+ ],
+ },
+ darwin: {
+ enabled: false,
+ },
vendor: {
exclude_shared_libs: ["libvintf"],
},
@@ -72,6 +86,7 @@ cc_binary {
cc_test {
name: "servicemanager_test",
+ host_supported: true,
test_suites: ["device-tests"],
defaults: ["servicemanager_defaults"],
srcs: [
@@ -80,6 +95,16 @@ cc_test {
static_libs: ["libgmock"],
}
+cc_test_host {
+ name: "servicemanager_unittest",
+ test_suites: ["general-tests"],
+ defaults: ["servicemanager_defaults"],
+ srcs: [
+ "ServiceManagerUnittest.cpp",
+ ],
+ static_libs: ["libgmock"],
+}
+
cc_fuzz {
name: "servicemanager_fuzzer",
defaults: [
diff --git a/cmds/servicemanager/NameUtil.h b/cmds/servicemanager/NameUtil.h
new file mode 100644
index 0000000000..b08093960d
--- /dev/null
+++ b/cmds/servicemanager/NameUtil.h
@@ -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.
+ */
+
+#pragma once
+
+#include <string>
+#include <string_view>
+
+#include <android-base/strings.h>
+
+namespace android {
+
+#ifndef VENDORSERVICEMANAGER
+
+struct NativeName {
+ std::string package;
+ std::string instance;
+
+ // Parse {package}/{instance}
+ static bool fill(std::string_view name, NativeName* nname) {
+ size_t slash = name.find('/');
+ if (slash == std::string_view::npos) {
+ return false;
+ }
+ // no extra slashes
+ if (name.find('/', slash + 1) != std::string_view::npos) {
+ return false;
+ }
+ // every part should be non-empty
+ if (slash == 0 || slash + 1 == name.size()) {
+ return false;
+ }
+ // no dots in package
+ if (name.rfind('.', slash) != std::string_view::npos) {
+ return false;
+ }
+ nname->package = name.substr(0, slash);
+ nname->instance = name.substr(slash + 1);
+ return true;
+ }
+};
+
+#endif
+
+} // namespace android
diff --git a/cmds/servicemanager/OWNERS b/cmds/servicemanager/OWNERS
new file mode 100644
index 0000000000..7f5a8115d1
--- /dev/null
+++ b/cmds/servicemanager/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 32456
+
+smoreland@google.com
diff --git a/cmds/servicemanager/ServiceManager.cpp b/cmds/servicemanager/ServiceManager.cpp
index eadf67821f..95a05cdcde 100644
--- a/cmds/servicemanager/ServiceManager.cpp
+++ b/cmds/servicemanager/ServiceManager.cpp
@@ -18,6 +18,7 @@
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <android-base/strings.h>
#include <binder/BpBinder.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
@@ -34,11 +35,18 @@
#include <vintf/constants.h>
#endif // !VENDORSERVICEMANAGER
+#include "NameUtil.h"
+
using ::android::binder::Status;
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 {
@@ -78,6 +86,10 @@ static bool forEachManifest(const std::function<bool(const ManifestWithDescripti
return false;
}
+static std::string getNativeInstanceName(const vintf::ManifestInstance& instance) {
+ return instance.package() + "/" + instance.instance();
+}
+
struct AidlName {
std::string package;
std::string iface;
@@ -99,36 +111,93 @@ struct AidlName {
}
};
-static bool isVintfDeclared(const std::string& name) {
+static std::string getAidlInstanceName(const vintf::ManifestInstance& instance) {
+ return instance.package() + "." + instance.interface() + "/" + instance.instance();
+}
+
+static bool isVintfDeclared(const Access::CallingContext& ctx, const std::string& name) {
+ NativeName nname;
+ if (NativeName::fill(name, &nname)) {
+ bool found = forEachManifest([&](const ManifestWithDescription& mwd) {
+ if (mwd.manifest->hasNativeInstance(nname.package, nname.instance)) {
+ ALOGI("%s Found %s in %s VINTF manifest.", ctx.toDebugString().c_str(),
+ name.c_str(), mwd.description);
+ return true; // break
+ }
+ return false; // continue
+ });
+ if (!found) {
+ ALOGI("%s Could not find %s in the VINTF manifest.", ctx.toDebugString().c_str(),
+ name.c_str());
+ }
+ return found;
+ }
+
AidlName aname;
if (!AidlName::fill(name, &aname)) return false;
bool found = forEachManifest([&](const ManifestWithDescription& mwd) {
if (mwd.manifest->hasAidlInstance(aname.package, aname.iface, aname.instance)) {
- ALOGI("Found %s in %s VINTF manifest.", name.c_str(), mwd.description);
+ ALOGI("%s Found %s in %s VINTF manifest.", ctx.toDebugString().c_str(), name.c_str(),
+ mwd.description);
return true; // break
}
return false; // continue
});
if (!found) {
+ std::set<std::string> instances;
+ forEachManifest([&](const ManifestWithDescription& mwd) {
+ std::set<std::string> res = mwd.manifest->getAidlInstances(aname.package, aname.iface);
+ instances.insert(res.begin(), res.end());
+ return true;
+ });
+
+ std::string available;
+ if (instances.empty()) {
+ available = "No alternative instances declared in VINTF";
+ } else {
+ // for logging only. We can't return this information to the client
+ // because they may not have permissions to find or list those
+ // instances
+ available = "VINTF declared instances: " + base::Join(instances, ", ");
+ }
// Although it is tested, explicitly rebuilding qualified name, in case it
// becomes something unexpected.
- ALOGI("Could not find %s.%s/%s in the VINTF manifest.", aname.package.c_str(),
- aname.iface.c_str(), aname.instance.c_str());
+ ALOGI("%s Could not find %s.%s/%s in the VINTF manifest. %s.", ctx.toDebugString().c_str(),
+ aname.package.c_str(), aname.iface.c_str(), aname.instance.c_str(),
+ available.c_str());
}
return found;
}
static std::optional<std::string> getVintfUpdatableApex(const std::string& name) {
+ NativeName nname;
+ if (NativeName::fill(name, &nname)) {
+ std::optional<std::string> updatableViaApex;
+
+ forEachManifest([&](const ManifestWithDescription& mwd) {
+ bool cont = mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+ if (manifestInstance.format() != vintf::HalFormat::NATIVE) return true;
+ if (manifestInstance.package() != nname.package) return true;
+ if (manifestInstance.instance() != nname.instance) return true;
+ updatableViaApex = manifestInstance.updatableViaApex();
+ return false; // break (libvintf uses opposite convention)
+ });
+ return !cont;
+ });
+
+ return updatableViaApex;
+ }
+
AidlName aname;
if (!AidlName::fill(name, &aname)) return std::nullopt;
std::optional<std::string> updatableViaApex;
forEachManifest([&](const ManifestWithDescription& mwd) {
- mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
+ bool cont = mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
if (manifestInstance.format() != vintf::HalFormat::AIDL) return true;
if (manifestInstance.package() != aname.package) return true;
if (manifestInstance.interface() != aname.iface) return true;
@@ -136,31 +205,31 @@ static std::optional<std::string> getVintfUpdatableApex(const std::string& name)
updatableViaApex = manifestInstance.updatableViaApex();
return false; // break (libvintf uses opposite convention)
});
- if (updatableViaApex.has_value()) return true; // break (found match)
- return false; // continue
+ return !cont;
});
return updatableViaApex;
}
-static std::vector<std::string> getVintfUpdatableInstances(const std::string& apexName) {
- std::vector<std::string> instances;
+static std::vector<std::string> getVintfUpdatableNames(const std::string& apexName) {
+ std::vector<std::string> names;
forEachManifest([&](const ManifestWithDescription& mwd) {
mwd.manifest->forEachInstance([&](const auto& manifestInstance) {
- if (manifestInstance.format() == vintf::HalFormat::AIDL &&
- manifestInstance.updatableViaApex().has_value() &&
+ if (manifestInstance.updatableViaApex().has_value() &&
manifestInstance.updatableViaApex().value() == apexName) {
- std::string aname = manifestInstance.package() + "." +
- manifestInstance.interface() + "/" + manifestInstance.instance();
- instances.push_back(aname);
+ if (manifestInstance.format() == vintf::HalFormat::NATIVE) {
+ names.push_back(getNativeInstanceName(manifestInstance));
+ } else if (manifestInstance.format() == vintf::HalFormat::AIDL) {
+ names.push_back(getAidlInstanceName(manifestInstance));
+ }
}
return true; // continue (libvintf uses opposite convention)
});
return false; // continue
});
- return instances;
+ return names;
}
static std::optional<ConnectionInfo> getVintfConnectionInfo(const std::string& name) {
@@ -195,6 +264,18 @@ static std::optional<ConnectionInfo> getVintfConnectionInfo(const std::string& n
static std::vector<std::string> getVintfInstances(const std::string& interface) {
size_t lastDot = interface.rfind('.');
if (lastDot == std::string::npos) {
+ // This might be a package for native instance.
+ std::vector<std::string> ret;
+ (void)forEachManifest([&](const ManifestWithDescription& mwd) {
+ auto instances = mwd.manifest->getNativeInstances(interface);
+ ret.insert(ret.end(), instances.begin(), instances.end());
+ return false; // continue
+ });
+ // If found, return it without error log.
+ if (!ret.empty()) {
+ return ret;
+ }
+
ALOGE("VINTF interfaces require names in Java package format (e.g. some.package.foo.IFoo) "
"but got: %s",
interface.c_str());
@@ -213,19 +294,25 @@ static std::vector<std::string> getVintfInstances(const std::string& interface)
return ret;
}
-static bool meetsDeclarationRequirements(const sp<IBinder>& binder, const std::string& name) {
+static bool meetsDeclarationRequirements(const Access::CallingContext& ctx,
+ const sp<IBinder>& binder, const std::string& name) {
if (!Stability::requiresVintfDeclaration(binder)) {
return true;
}
- return isVintfDeclared(name);
+ return isVintfDeclared(ctx, name);
}
#endif // !VENDORSERVICEMANAGER
ServiceManager::Service::~Service() {
- if (!hasClients) {
- // only expected to happen on process death
- LOG(WARNING) << "a service was removed when there are clients";
+ if (hasClients) {
+ // only expected to happen on process death, we don't store the service
+ // name this late (it's in the map that holds this service), but if it
+ // is happening, we might want to change 'unlinkToDeath' to explicitly
+ // clear this bit so that we can abort in other cases, where it would
+ // mean inconsistent logic in servicemanager (unexpected and tested, but
+ // the original lazy service impl here had that bug).
+ ALOGW("A service was removed when there are clients");
}
}
@@ -280,13 +367,10 @@ sp<IBinder> 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)) {
+ LOG(WARNING) << "Isolated app with UID " << ctx.uid << " requested '" << name
+ << "', but the service is not allowed for isolated apps.";
+ return nullptr;
}
out = service->binder;
}
@@ -296,7 +380,7 @@ sp<IBinder> ServiceManager::tryGetService(const std::string& name, bool startIfN
}
if (!out && startIfNotFound) {
- tryStartService(name);
+ tryStartService(ctx, name);
}
if (out) {
@@ -332,39 +416,46 @@ Status ServiceManager::addService(const std::string& name, const sp<IBinder>& bi
auto ctx = mAccess->getCallingContext();
if (multiuser_get_app_id(ctx.uid) >= AID_APP) {
- return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services");
+ return Status::fromExceptionCode(Status::EX_SECURITY, "App UIDs cannot add services.");
}
if (!mAccess->canAdd(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
if (binder == nullptr) {
- return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Null binder");
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Null binder.");
}
if (!isValidServiceName(name)) {
- ALOGE("Invalid service name: %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name");
+ ALOGE("%s Invalid service name: %s", ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name.");
}
#ifndef VENDORSERVICEMANAGER
- if (!meetsDeclarationRequirements(binder, name)) {
+ if (!meetsDeclarationRequirements(ctx, binder, name)) {
// already logged
- return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "VINTF declaration error");
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "VINTF declaration error.");
}
#endif // !VENDORSERVICEMANAGER
+ if ((dumpPriority & DUMP_FLAG_PRIORITY_ALL) == 0) {
+ ALOGW("%s Dump flag priority is not set when adding %s", ctx.toDebugString().c_str(),
+ name.c_str());
+ }
+
// implicitly unlinked when the binder is removed
if (binder->remoteBinder() != nullptr &&
binder->linkToDeath(sp<ServiceManager>::fromExisting(this)) != OK) {
- ALOGE("Could not linkToDeath when adding %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "linkToDeath failure");
+ ALOGE("%s Could not linkToDeath when adding %s", ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
}
auto it = mNameToService.find(name);
+ bool prevClients = false;
if (it != mNameToService.end()) {
const Service& existing = it->second;
+ prevClients = existing.hasClients;
// We could do better than this because if the other service dies, it
// may not have an entry here. However, this case is unlikely. We are
@@ -392,12 +483,14 @@ Status ServiceManager::addService(const std::string& name, const sp<IBinder>& bi
.binder = binder,
.allowIsolated = allowIsolated,
.dumpPriority = dumpPriority,
+ .hasClients = prevClients, // see b/279898063, matters if existing callbacks
+ .guaranteeClient = false,
.ctx = ctx,
};
if (auto it = mNameToRegistrationCallback.find(name); it != mNameToRegistrationCallback.end()) {
- // See also getService - handles case where client never gets the service,
- // we want the service to quit.
+ // If someone is currently waiting on the service, notify the service that
+ // we're waiting and flush it to the service.
mNameToService[name].guaranteeClient = true;
CHECK(handleServiceClientCallback(2 /* sm + transaction */, name, false));
mNameToService[name].guaranteeClient = true;
@@ -413,7 +506,7 @@ Status ServiceManager::addService(const std::string& name, const sp<IBinder>& bi
Status ServiceManager::listServices(int32_t dumpPriority, std::vector<std::string>* outList) {
if (!mAccess->canList(mAccess->getCallingContext())) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
size_t toReserve = 0;
@@ -442,23 +535,33 @@ 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)) {
- ALOGE("Invalid service name: %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+ ALOGE("%s Invalid service name: %s", ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Invalid service name.");
}
if (callback == nullptr) {
- return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Null callback.");
}
if (OK !=
IInterface::asBinder(callback)->linkToDeath(
sp<ServiceManager>::fromExisting(this))) {
- ALOGE("Could not linkToDeath when adding %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ ALOGE("%s Could not linkToDeath when adding %s", ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't link to death.");
}
mNameToRegistrationCallback[name].push_back(callback);
@@ -478,7 +581,7 @@ Status ServiceManager::unregisterForNotifications(
auto ctx = mAccess->getCallingContext();
if (!mAccess->canFind(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
bool found = false;
@@ -489,8 +592,9 @@ Status ServiceManager::unregisterForNotifications(
}
if (!found) {
- ALOGE("Trying to unregister callback, but none exists %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ ALOGE("%s Trying to unregister callback, but none exists %s", ctx.toDebugString().c_str(),
+ name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Nothing to unregister.");
}
return Status::ok();
@@ -500,13 +604,13 @@ Status ServiceManager::isDeclared(const std::string& name, bool* outReturn) {
auto ctx = mAccess->getCallingContext();
if (!mAccess->canFind(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
*outReturn = false;
#ifndef VENDORSERVICEMANAGER
- *outReturn = isVintfDeclared(name);
+ *outReturn = isVintfDeclared(ctx, name);
#endif
return Status::ok();
}
@@ -528,7 +632,7 @@ binder::Status ServiceManager::getDeclaredInstances(const std::string& interface
}
if (outReturn->size() == 0 && allInstances.size() != 0) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
return Status::ok();
@@ -539,7 +643,7 @@ Status ServiceManager::updatableViaApex(const std::string& name,
auto ctx = mAccess->getCallingContext();
if (!mAccess->canFind(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
*outReturn = std::nullopt;
@@ -554,21 +658,21 @@ Status ServiceManager::getUpdatableNames([[maybe_unused]] const std::string& ape
std::vector<std::string>* outReturn) {
auto ctx = mAccess->getCallingContext();
- std::vector<std::string> apexUpdatableInstances;
+ std::vector<std::string> apexUpdatableNames;
#ifndef VENDORSERVICEMANAGER
- apexUpdatableInstances = getVintfUpdatableInstances(apexName);
+ apexUpdatableNames = getVintfUpdatableNames(apexName);
#endif
outReturn->clear();
- for (const std::string& instance : apexUpdatableInstances) {
- if (mAccess->canFind(ctx, instance)) {
- outReturn->push_back(instance);
+ for (const std::string& name : apexUpdatableNames) {
+ if (mAccess->canFind(ctx, name)) {
+ outReturn->push_back(name);
}
}
- if (outReturn->size() == 0 && apexUpdatableInstances.size() != 0) {
- return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denial");
+ if (outReturn->size() == 0 && apexUpdatableNames.size() != 0) {
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
return Status::ok();
@@ -579,7 +683,7 @@ Status ServiceManager::getConnectionInfo(const std::string& name,
auto ctx = mAccess->getCallingContext();
if (!mAccess->canFind(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
*outReturn = std::nullopt;
@@ -614,6 +718,14 @@ void ServiceManager::removeRegistrationCallback(const wp<IBinder>& who,
void ServiceManager::binderDied(const wp<IBinder>& who) {
for (auto it = mNameToService.begin(); it != mNameToService.end();) {
if (who == it->second.binder) {
+ // TODO: currently, this entry contains the state also
+ // associated with mNameToClientCallback. If we allowed
+ // other processes to register client callbacks, we
+ // would have to preserve hasClients (perhaps moving
+ // that state into mNameToClientCallback, which is complicated
+ // because those callbacks are associated w/ particular binder
+ // objects, though they are indexed by name now, they may
+ // need to be indexed by binder at that point).
it = mNameToService.erase(it);
} else {
++it;
@@ -629,18 +741,17 @@ void ServiceManager::binderDied(const wp<IBinder>& who) {
}
}
-void ServiceManager::tryStartService(const std::string& name) {
- ALOGI("Since '%s' could not be found, trying to start it as a lazy AIDL service. (if it's not "
- "configured to be a lazy service, it may be stuck starting or still starting).",
- name.c_str());
+void ServiceManager::tryStartService(const Access::CallingContext& ctx, const std::string& name) {
+ ALOGI("%s Since '%s' could not be found trying to start it as a lazy AIDL service. (if it's "
+ "not configured to be a lazy service, it may be stuck starting or still starting).",
+ ctx.toDebugString().c_str(), name.c_str());
std::thread([=] {
if (!base::SetProperty("ctl.interface_start", "aidl/" + name)) {
- ALOGI("Tried to start aidl service %s as a lazy service, but was unable to. Usually "
- "this happens when a "
- "service is not installed, but if the service is intended to be used as a "
- "lazy service, then it may be configured incorrectly.",
- name.c_str());
+ ALOGI("%s Tried to start aidl service %s as a lazy service, but was unable to. Usually "
+ "this happens when a service is not installed, but if the service is intended to "
+ "be used as a lazy service, then it may be configured incorrectly.",
+ ctx.toDebugString().c_str(), name.c_str());
}
}).detach();
}
@@ -648,40 +759,57 @@ void ServiceManager::tryStartService(const std::string& name) {
Status ServiceManager::registerClientCallback(const std::string& name, const sp<IBinder>& service,
const sp<IClientCallback>& cb) {
if (cb == nullptr) {
- return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Callback null.");
}
auto ctx = mAccess->getCallingContext();
if (!mAccess->canAdd(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
auto serviceIt = mNameToService.find(name);
if (serviceIt == mNameToService.end()) {
- ALOGE("Could not add callback for nonexistent service: %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+ ALOGE("%s Could not add callback for nonexistent service: %s", ctx.toDebugString().c_str(),
+ name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Service doesn't exist.");
}
if (serviceIt->second.ctx.debugPid != IPCThreadState::self()->getCallingPid()) {
- ALOGW("Only a server can register for client callbacks (for %s)", name.c_str());
- return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION);
+ ALOGW("%s Only a server can register for client callbacks (for %s)",
+ ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+ "Only service can register client callback for itself.");
}
if (serviceIt->second.binder != service) {
- ALOGW("Tried to register client callback for %s but a different service is registered "
+ ALOGW("%s Tried to register client callback for %s but a different service is registered "
"under this name.",
- name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT);
+ ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, "Service mismatch.");
}
if (OK !=
IInterface::asBinder(cb)->linkToDeath(sp<ServiceManager>::fromExisting(this))) {
- ALOGE("Could not linkToDeath when adding client callback for %s", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ ALOGE("%s Could not linkToDeath when adding client callback for %s",
+ ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Couldn't linkToDeath.");
+ }
+
+ // WARNING: binderDied makes an assumption about this. If we open up client
+ // callbacks to other services, certain race conditions may lead to services
+ // getting extra client callback notifications.
+ // Make sure all callbacks have been told about a consistent state - b/278038751
+ if (serviceIt->second.hasClients) {
+ cb->onClients(service, true);
}
mNameToClientCallback[name].push_back(cb);
+ // Flush updated info to client callbacks (especially if guaranteeClient
+ // and !hasClient, see b/285202885). We may or may not have clients at
+ // this point, so ignore the return value.
+ (void)handleServiceClientCallback(2 /* sm + transaction */, name, false);
+
return Status::ok();
}
@@ -791,39 +919,44 @@ void ServiceManager::sendClientCallbackNotifications(const std::string& serviceN
Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IBinder>& binder) {
if (binder == nullptr) {
- return Status::fromExceptionCode(Status::EX_NULL_POINTER);
+ return Status::fromExceptionCode(Status::EX_NULL_POINTER, "Null service.");
}
auto ctx = mAccess->getCallingContext();
if (!mAccess->canAdd(ctx, name)) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
auto serviceIt = mNameToService.find(name);
if (serviceIt == mNameToService.end()) {
- ALOGW("Tried to unregister %s, but that service wasn't registered to begin with.",
- name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ ALOGW("%s Tried to unregister %s, but that service wasn't registered to begin with.",
+ ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE, "Service not registered.");
}
if (serviceIt->second.ctx.debugPid != IPCThreadState::self()->getCallingPid()) {
- ALOGW("Only a server can unregister itself (for %s)", name.c_str());
- return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION);
+ ALOGW("%s Only a server can unregister itself (for %s)", ctx.toDebugString().c_str(),
+ name.c_str());
+ return Status::fromExceptionCode(Status::EX_UNSUPPORTED_OPERATION,
+ "Service can only unregister itself.");
}
sp<IBinder> storedBinder = serviceIt->second.binder;
if (binder != storedBinder) {
- ALOGW("Tried to unregister %s, but a different service is registered under this name.",
- name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ ALOGW("%s Tried to unregister %s, but a different service is registered under this name.",
+ ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
+ "Different service registered under this name.");
}
// important because we don't have timer-based guarantees, we don't want to clear
// this
if (serviceIt->second.guaranteeClient) {
- ALOGI("Tried to unregister %s, but there is about to be a client.", name.c_str());
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ ALOGI("%s Tried to unregister %s, but there is about to be a client.",
+ ctx.toDebugString().c_str(), name.c_str());
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
+ "Can't unregister, pending client.");
}
// - kernel driver will hold onto one refcount (during this transaction)
@@ -831,17 +964,19 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB
constexpr size_t kKnownClients = 2;
if (handleServiceClientCallback(kKnownClients, name, false)) {
- ALOGI("Tried to unregister %s, but there are clients.", name.c_str());
+ ALOGI("%s Tried to unregister %s, but there are clients.", ctx.toDebugString().c_str(),
+ name.c_str());
// Since we had a failed registration attempt, and the HIDL implementation of
// delaying service shutdown for multiple periods wasn't ported here... this may
// help reduce thrashing, but we should be able to remove it.
serviceIt->second.guaranteeClient = true;
- return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE);
+ return Status::fromExceptionCode(Status::EX_ILLEGAL_STATE,
+ "Can't unregister, known client.");
}
- ALOGI("Unregistering %s", name.c_str());
+ ALOGI("%s Unregistering %s", ctx.toDebugString().c_str(), name.c_str());
mNameToService.erase(name);
return Status::ok();
@@ -849,7 +984,7 @@ Status ServiceManager::tryUnregisterService(const std::string& name, const sp<IB
Status ServiceManager::getServiceDebugInfo(std::vector<ServiceDebugInfo>* outReturn) {
if (!mAccess->canList(mAccess->getCallingContext())) {
- return Status::fromExceptionCode(Status::EX_SECURITY);
+ return Status::fromExceptionCode(Status::EX_SECURITY, "SELinux denied.");
}
outReturn->reserve(mNameToService.size());
diff --git a/cmds/servicemanager/ServiceManager.h b/cmds/servicemanager/ServiceManager.h
index 3aa6731eb3..3b925a48cb 100644
--- a/cmds/servicemanager/ServiceManager.h
+++ b/cmds/servicemanager/ServiceManager.h
@@ -67,7 +67,7 @@ public:
void clear();
protected:
- virtual void tryStartService(const std::string& name);
+ virtual void tryStartService(const Access::CallingContext& ctx, const std::string& name);
private:
struct Service {
diff --git a/cmds/servicemanager/ServiceManagerUnittest.cpp b/cmds/servicemanager/ServiceManagerUnittest.cpp
new file mode 100644
index 0000000000..39d20b0025
--- /dev/null
+++ b/cmds/servicemanager/ServiceManagerUnittest.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include "NameUtil.h"
+
+namespace android {
+
+TEST(ServiceManager, NativeName) {
+ NativeName nname;
+ EXPECT_TRUE(NativeName::fill("mapper/default", &nname));
+ EXPECT_EQ("mapper", nname.package);
+ EXPECT_EQ("default", nname.instance);
+}
+
+TEST(ServiceManager, NativeName_Malformed) {
+ NativeName nname;
+ EXPECT_FALSE(NativeName::fill("mapper", &nname));
+ EXPECT_FALSE(NativeName::fill("mapper/", &nname));
+ EXPECT_FALSE(NativeName::fill("/default", &nname));
+ EXPECT_FALSE(NativeName::fill("mapper/default/0", &nname));
+ EXPECT_FALSE(NativeName::fill("aidl.like.IType/default", &nname));
+}
+
+} // namespace android
diff --git a/cmds/servicemanager/main.cpp b/cmds/servicemanager/main.cpp
index c1a04dde89..07908ba5b3 100644
--- a/cmds/servicemanager/main.cpp
+++ b/cmds/servicemanager/main.cpp
@@ -40,15 +40,12 @@ class BinderCallback : public LooperCallback {
public:
static sp<BinderCallback> setupTo(const sp<Looper>& looper) {
sp<BinderCallback> cb = sp<BinderCallback>::make();
+ cb->mLooper = looper;
- int binder_fd = -1;
- IPCThreadState::self()->setupPolling(&binder_fd);
- LOG_ALWAYS_FATAL_IF(binder_fd < 0, "Failed to setupPolling: %d", binder_fd);
+ IPCThreadState::self()->setupPolling(&cb->mBinderFd);
+ LOG_ALWAYS_FATAL_IF(cb->mBinderFd < 0, "Failed to setupPolling: %d", cb->mBinderFd);
- int ret = looper->addFd(binder_fd,
- Looper::POLL_CALLBACK,
- Looper::EVENT_INPUT,
- cb,
+ int ret = looper->addFd(cb->mBinderFd, Looper::POLL_CALLBACK, Looper::EVENT_INPUT, cb,
nullptr /*data*/);
LOG_ALWAYS_FATAL_IF(ret != 1, "Failed to add binder FD to Looper");
@@ -59,13 +56,26 @@ public:
IPCThreadState::self()->handlePolledCommands();
return 1; // Continue receiving callbacks.
}
+
+ void repoll() {
+ if (!mLooper->repoll(mBinderFd)) {
+ ALOGE("Failed to repoll binder FD.");
+ }
+ }
+
+private:
+ sp<Looper> mLooper;
+ int mBinderFd = -1;
};
// LooperCallback for IClientCallback
class ClientCallbackCallback : public LooperCallback {
public:
- static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper, const sp<ServiceManager>& manager) {
+ static sp<ClientCallbackCallback> setupTo(const sp<Looper>& looper,
+ const sp<ServiceManager>& manager,
+ sp<BinderCallback> binderCallback) {
sp<ClientCallbackCallback> cb = sp<ClientCallbackCallback>::make(manager);
+ cb->mBinderCallback = binderCallback;
int fdTimer = timerfd_create(CLOCK_MONOTONIC, 0 /*flags*/);
LOG_ALWAYS_FATAL_IF(fdTimer < 0, "Failed to timerfd_create: fd: %d err: %d", fdTimer, errno);
@@ -102,12 +112,15 @@ public:
}
mManager->handleClientCallbacks();
+ mBinderCallback->repoll(); // b/316829336
+
return 1; // Continue receiving callbacks.
}
private:
friend sp<ClientCallbackCallback>;
ClientCallbackCallback(const sp<ServiceManager>& manager) : mManager(manager) {}
sp<ServiceManager> mManager;
+ sp<BinderCallback> mBinderCallback;
};
int main(int argc, char** argv) {
@@ -125,18 +138,22 @@ int main(int argc, char** argv) {
ps->setThreadPoolMaxThreadCount(0);
ps->setCallRestriction(ProcessState::CallRestriction::FATAL_IF_NOT_ONEWAY);
+ IPCThreadState::self()->disableBackgroundScheduling(true);
+
sp<ServiceManager> manager = sp<ServiceManager>::make(std::make_unique<Access>());
if (!manager->addService("manager", manager, false /*allowIsolated*/, IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT).isOk()) {
LOG(ERROR) << "Could not self register servicemanager";
}
IPCThreadState::self()->setTheContextObject(manager);
- ps->becomeContextManager();
+ if (!ps->becomeContextManager()) {
+ LOG(FATAL) << "Could not become context manager";
+ }
sp<Looper> looper = Looper::prepare(false /*allowNonCallbacks*/);
- BinderCallback::setupTo(looper);
- ClientCallbackCallback::setupTo(looper, manager);
+ sp<BinderCallback> binderCallback = BinderCallback::setupTo(looper);
+ ClientCallbackCallback::setupTo(looper, manager, binderCallback);
#ifndef VENDORSERVICEMANAGER
if (!SetProperty("servicemanager.ready", "true")) {
diff --git a/cmds/servicemanager/servicemanager.rc b/cmds/servicemanager/servicemanager.rc
index 4f92b3a374..6c450ab4e3 100644
--- a/cmds/servicemanager/servicemanager.rc
+++ b/cmds/servicemanager/servicemanager.rc
@@ -11,5 +11,5 @@ service servicemanager /system/bin/servicemanager
onrestart class_restart --only-enabled main
onrestart class_restart --only-enabled hal
onrestart class_restart --only-enabled early_hal
- task_profiles ServiceCapacityLow
+ task_profiles ProcessCapacityHigh
shutdown critical
diff --git a/cmds/servicemanager/servicemanager.recovery.rc b/cmds/servicemanager/servicemanager.recovery.rc
index b927c018df..6354fd7d53 100644
--- a/cmds/servicemanager/servicemanager.recovery.rc
+++ b/cmds/servicemanager/servicemanager.recovery.rc
@@ -1,5 +1,6 @@
service servicemanager /system/bin/servicemanager
disabled
group system readproc
+ user root
onrestart setprop servicemanager.ready false
seclabel u:r:servicemanager:s0
diff --git a/cmds/servicemanager/test_sm.cpp b/cmds/servicemanager/test_sm.cpp
index 0fd8d8ee2a..b57505302c 100644
--- a/cmds/servicemanager/test_sm.cpp
+++ b/cmds/servicemanager/test_sm.cpp
@@ -27,11 +27,14 @@
#include "Access.h"
#include "ServiceManager.h"
-using android::sp;
using android::Access;
using android::BBinder;
using android::IBinder;
using android::ServiceManager;
+using android::sp;
+using android::base::EndsWith;
+using android::base::GetProperty;
+using android::base::StartsWith;
using android::binder::Status;
using android::os::BnServiceCallback;
using android::os::IServiceManager;
@@ -62,7 +65,7 @@ public:
class MockServiceManager : public ServiceManager {
public:
MockServiceManager(std::unique_ptr<Access>&& access) : ServiceManager(std::move(access)) {}
- MOCK_METHOD1(tryStartService, void(const std::string& name));
+ MOCK_METHOD2(tryStartService, void(const Access::CallingContext&, const std::string& name));
};
static sp<ServiceManager> getPermissiveServiceManager() {
@@ -77,9 +80,11 @@ static sp<ServiceManager> getPermissiveServiceManager() {
return sm;
}
-static bool isCuttlefish() {
- return android::base::StartsWith(android::base::GetProperty("ro.product.vendor.device", ""),
- "vsoc_");
+// Determines if test device is a cuttlefish phone device
+static bool isCuttlefishPhone() {
+ auto device = GetProperty("ro.product.vendor.device", "");
+ auto product = GetProperty("ro.product.vendor.name", "");
+ return StartsWith(device, "vsoc_") && EndsWith(product, "_phone");
}
TEST(AddService, HappyHappy) {
@@ -314,7 +319,7 @@ TEST(ListServices, CriticalServices) {
}
TEST(Vintf, UpdatableViaApex) {
- if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+ if (!isCuttlefishPhone()) GTEST_SKIP() << "Skipping non-Cuttlefish-phone devices";
auto sm = getPermissiveServiceManager();
std::optional<std::string> updatableViaApex;
@@ -326,7 +331,7 @@ TEST(Vintf, UpdatableViaApex) {
}
TEST(Vintf, UpdatableViaApex_InvalidNameReturnsNullOpt) {
- if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+ if (!isCuttlefishPhone()) GTEST_SKIP() << "Skipping non-Cuttlefish-phone devices";
auto sm = getPermissiveServiceManager();
std::optional<std::string> updatableViaApex;
@@ -337,7 +342,7 @@ TEST(Vintf, UpdatableViaApex_InvalidNameReturnsNullOpt) {
}
TEST(Vintf, GetUpdatableNames) {
- if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+ if (!isCuttlefishPhone()) GTEST_SKIP() << "Skipping non-Cuttlefish-phone devices";
auto sm = getPermissiveServiceManager();
std::vector<std::string> names;
@@ -348,7 +353,7 @@ TEST(Vintf, GetUpdatableNames) {
}
TEST(Vintf, GetUpdatableNames_InvalidApexNameReturnsEmpty) {
- if (!isCuttlefish()) GTEST_SKIP() << "Skipping non-Cuttlefish devices";
+ if (!isCuttlefishPhone()) GTEST_SKIP() << "Skipping non-Cuttlefish-phone devices";
auto sm = getPermissiveServiceManager();
std::vector<std::string> names;
@@ -356,6 +361,24 @@ TEST(Vintf, GetUpdatableNames_InvalidApexNameReturnsEmpty) {
EXPECT_EQ(std::vector<std::string>{}, names);
}
+TEST(Vintf, IsDeclared_native) {
+ if (!isCuttlefishPhone()) GTEST_SKIP() << "Skipping non-Cuttlefish-phone devices";
+
+ auto sm = getPermissiveServiceManager();
+ bool declared = false;
+ EXPECT_TRUE(sm->isDeclared("mapper/minigbm", &declared).isOk());
+ EXPECT_TRUE(declared);
+}
+
+TEST(Vintf, GetDeclaredInstances_native) {
+ if (!isCuttlefishPhone()) GTEST_SKIP() << "Skipping non-Cuttlefish-phone devices";
+
+ auto sm = getPermissiveServiceManager();
+ std::vector<std::string> instances;
+ EXPECT_TRUE(sm->getDeclaredInstances("mapper", &instances).isOk());
+ EXPECT_EQ(std::vector<std::string>{"minigbm"}, instances);
+}
+
class CallbackHistorian : public BnServiceCallback {
Status onRegistration(const std::string& name, const sp<IBinder>& binder) override {
registrations.push_back(name);
@@ -383,6 +406,22 @@ TEST(ServiceNotifications, NoPermissionsRegister) {
sp<CallbackHistorian> cb = sp<CallbackHistorian>::make();
+ EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(), Status::EX_SECURITY);
+}
+
+TEST(GetService, IsolatedCantRegister) {
+ std::unique_ptr<MockAccess> access = std::make_unique<NiceMock<MockAccess>>();
+
+ EXPECT_CALL(*access, getCallingContext())
+ .WillOnce(Return(Access::CallingContext{
+ .uid = AID_ISOLATED_START,
+ }));
+ EXPECT_CALL(*access, canFind(_, _)).WillOnce(Return(true));
+
+ sp<ServiceManager> sm = sp<ServiceManager>::make(std::move(access));
+
+ sp<CallbackHistorian> cb = sp<CallbackHistorian>::make();
+
EXPECT_EQ(sm->registerForNotifications("foofoo", cb).exceptionCode(),
Status::EX_SECURITY);
}
diff --git a/cmds/sfdo/Android.bp b/cmds/sfdo/Android.bp
new file mode 100644
index 0000000000..c19c9da7bf
--- /dev/null
+++ b/cmds/sfdo/Android.bp
@@ -0,0 +1,17 @@
+cc_binary {
+ name: "sfdo",
+
+ srcs: ["sfdo.cpp"],
+
+ shared_libs: [
+ "libutils",
+ "libgui",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wunused",
+ "-Wunreachable-code",
+ ],
+}
diff --git a/cmds/sfdo/sfdo.cpp b/cmds/sfdo/sfdo.cpp
new file mode 100644
index 0000000000..de0e1718ab
--- /dev/null
+++ b/cmds/sfdo/sfdo.cpp
@@ -0,0 +1,166 @@
+/*
+ * 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 <inttypes.h>
+#include <stdint.h>
+#include <any>
+#include <map>
+
+#include <cutils/properties.h>
+#include <sys/resource.h>
+#include <utils/Log.h>
+
+#include <gui/ISurfaceComposer.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/SurfaceControl.h>
+#include <private/gui/ComposerServiceAIDL.h>
+
+using namespace android;
+
+std::map<std::string, std::any> g_functions;
+
+enum class ParseToggleResult {
+ kError,
+ kFalse,
+ kTrue,
+};
+
+const std::map<std::string, std::string> g_function_details = {
+ {"debugFlash", "[optional(delay)] Perform a debug flash."},
+ {"frameRateIndicator", "[hide | show] displays the framerate in the top left corner."},
+ {"scheduleComposite", "Force composite ahead of next VSYNC."},
+ {"scheduleCommit", "Force commit ahead of next VSYNC."},
+ {"scheduleComposite", "PENDING - if you have a good understanding let me know!"},
+ {"forceClientComposition",
+ "[enabled | disabled] When enabled, it disables "
+ "Hardware Overlays, and routes all window composition to the GPU. This can "
+ "help check if there is a bug in HW Composer."},
+};
+
+static void ShowUsage() {
+ std::cout << "usage: sfdo [help, frameRateIndicator show, debugFlash enabled, ...]\n\n";
+ for (const auto& sf : g_functions) {
+ const std::string fn = sf.first;
+ std::string fdetails = "TODO";
+ if (g_function_details.find(fn) != g_function_details.end())
+ fdetails = g_function_details.find(fn)->second;
+ std::cout << " " << fn << ": " << fdetails << "\n";
+ }
+}
+
+// Returns 1 for positive keywords and 0 for negative keywords.
+// If the string does not match any it will return -1.
+ParseToggleResult parseToggle(const char* str) {
+ const std::unordered_set<std::string> positive{"1", "true", "y", "yes",
+ "on", "enabled", "show"};
+ const std::unordered_set<std::string> negative{"0", "false", "n", "no",
+ "off", "disabled", "hide"};
+
+ const std::string word(str);
+ if (positive.count(word)) {
+ return ParseToggleResult::kTrue;
+ }
+ if (negative.count(word)) {
+ return ParseToggleResult::kFalse;
+ }
+
+ return ParseToggleResult::kError;
+}
+
+int frameRateIndicator(int argc, char** argv) {
+ bool hide = false, show = false;
+ if (argc == 3) {
+ show = strcmp(argv[2], "show") == 0;
+ hide = strcmp(argv[2], "hide") == 0;
+ }
+
+ if (show || hide) {
+ ComposerServiceAIDL::getComposerService()->enableRefreshRateOverlay(show);
+ } else {
+ std::cerr << "Incorrect usage of frameRateIndicator. Missing [hide | show].\n";
+ return -1;
+ }
+ return 0;
+}
+
+int debugFlash(int argc, char** argv) {
+ int delay = 0;
+ if (argc == 3) {
+ delay = atoi(argv[2]) == 0;
+ }
+
+ ComposerServiceAIDL::getComposerService()->setDebugFlash(delay);
+ return 0;
+}
+
+int scheduleComposite([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+ ComposerServiceAIDL::getComposerService()->scheduleComposite();
+ return 0;
+}
+
+int scheduleCommit([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
+ ComposerServiceAIDL::getComposerService()->scheduleCommit();
+ return 0;
+}
+
+int forceClientComposition(int argc, char** argv) {
+ bool enabled = true;
+ // A valid command looks like this:
+ // adb shell sfdo forceClientComposition enabled
+ if (argc >= 3) {
+ const ParseToggleResult toggle = parseToggle(argv[2]);
+ if (toggle == ParseToggleResult::kError) {
+ std::cerr << "Incorrect usage of forceClientComposition. "
+ "Missing [enabled | disabled].\n";
+ return -1;
+ }
+ if (argc > 3) {
+ std::cerr << "Too many arguments after [enabled | disabled]. "
+ "Ignoring extra arguments.\n";
+ }
+ enabled = (toggle == ParseToggleResult::kTrue);
+ } else {
+ std::cerr << "Incorrect usage of forceClientComposition. Missing [enabled | disabled].\n";
+ return -1;
+ }
+
+ ComposerServiceAIDL::getComposerService()->forceClientComposition(enabled);
+ return 0;
+}
+
+int main(int argc, char** argv) {
+ std::cout << "Execute SurfaceFlinger internal commands.\n";
+ std::cout << "sfdo requires to be run with root permissions..\n";
+
+ g_functions["frameRateIndicator"] = frameRateIndicator;
+ g_functions["debugFlash"] = debugFlash;
+ g_functions["scheduleComposite"] = scheduleComposite;
+ g_functions["scheduleCommit"] = scheduleCommit;
+ g_functions["forceClientComposition"] = forceClientComposition;
+
+ if (argc > 1 && g_functions.find(argv[1]) != g_functions.end()) {
+ std::cout << "Running: " << argv[1] << "\n";
+ const std::string key(argv[1]);
+ const auto fn = g_functions[key];
+ int result = std::any_cast<int (*)(int, char**)>(fn)(argc, argv);
+ if (result == 0) {
+ std::cout << "Success.\n";
+ }
+ return result;
+ } else {
+ ShowUsage();
+ }
+ return 0;
+} \ No newline at end of file
diff --git a/cmds/surfacereplayer/Android.bp b/cmds/surfacereplayer/Android.bp
deleted file mode 100644
index 34fc8b10ea..0000000000
--- 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/proto/Android.bp b/cmds/surfacereplayer/proto/Android.bp
deleted file mode 100644
index 23b54ee5b0..0000000000
--- 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 a177027e5c..0000000000
--- 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 3985230f08..0000000000
--- 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 77de8dc44c..0000000000
--- 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 <android/native_window.h>
-#include <gui/Surface.h>
-
-using namespace android;
-
-BufferQueueScheduler::BufferQueueScheduler(
- const sp<SurfaceControl>& 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<std::mutex> 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<std::mutex> lock(mMutex);
- mBufferEvents.push(event);
- mCondition.notify_one();
-}
-
-void BufferQueueScheduler::stopScheduling() {
- std::lock_guard<std::mutex> lock(mMutex);
- mContinueScheduling = false;
- mCondition.notify_one();
-}
-
-void BufferQueueScheduler::setSurfaceControl(
- const sp<SurfaceControl>& surfaceControl, const HSV& color) {
- std::lock_guard<std::mutex> lock(mMutex);
- mSurfaceControl = surfaceControl;
- mColor = color;
- mCondition.notify_one();
-}
-
-void BufferQueueScheduler::bufferUpdate(const Dimensions& dimensions) {
- sp<Surface> s = mSurfaceControl->getSurface();
- s->setBuffersDimensions(dimensions.width, dimensions.height);
-}
-
-void BufferQueueScheduler::fillSurface(const std::shared_ptr<Event>& event) {
- ANativeWindow_Buffer outBuffer;
- sp<Surface> 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<uint8_t*>(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 cb20fcc798..0000000000
--- 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 <gui/SurfaceControl.h>
-
-#include <utils/StrongPointer.h>
-
-#include <atomic>
-#include <condition_variable>
-#include <mutex>
-#include <queue>
-#include <utility>
-
-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<Event> e, Dimensions d) : event(e), dimensions(d) {}
-
- std::shared_ptr<Event> event;
- Dimensions dimensions;
-};
-
-class BufferQueueScheduler {
- public:
- BufferQueueScheduler(const sp<SurfaceControl>& surfaceControl, const HSV& color, int id);
-
- void startScheduling();
- void addEvent(const BufferEvent&);
- void stopScheduling();
-
- void setSurfaceControl(const sp<SurfaceControl>& 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>& event);
-
- sp<SurfaceControl> mSurfaceControl;
- HSV mColor;
- const int mSurfaceId;
-
- bool mContinueScheduling;
-
- std::queue<BufferEvent> 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 ce644be7be..0000000000
--- 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 <cmath>
-#include <cstdlib>
-
-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<int>(h) % 360;
- tempHue = tempHue / 60;
-
- int i = static_cast<int>(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 64db5f07b1..0000000000
--- 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<std::mutex> lock(mLock);
- mCond.wait(lock, [this, state] { return (mState == state); });
-}
-
-void Event::changeState(Event::EventState state) {
- std::unique_lock<std::mutex> 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 09a7c248d5..0000000000
--- 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 <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
-
-#include <condition_variable>
-#include <mutex>
-
-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 fbfcacf1aa..0000000000
--- 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 <Replayer.h>
-
-#include <csignal>
-#include <iostream>
-#include <stdlib.h>
-#include <unistd.h>
-
-using namespace android;
-
-void printHelpMenu() {
- std::cout << "SurfaceReplayer options:\n";
- std::cout << "Usage: surfacereplayer [OPTIONS...] <TRACE FILE>\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 893f0dc0f6..0000000000
--- 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 <replayer/Replayer.h>`
-
-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 <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>`
-
-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 3f7c7d6a7b..44235ccdef 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 d62522a497..0000000000
--- 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 <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
-
-#include <gui/SurfaceComposerClient.h>
-#include <gui/SurfaceControl.h>
-
-#include <utils/Errors.h>
-#include <utils/StrongPointer.h>
-
-#include <stdatomic.h>
-#include <condition_variable>
-#include <memory>
-#include <mutex>
-#include <queue>
-#include <thread>
-#include <unordered_map>
-#include <utility>
-
-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<SurfaceChange> SurfaceChanges;
-typedef google::protobuf::RepeatedPtrField<DisplayChange> 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>& event);
- status_t createSurfaceControl(const SurfaceCreation& create,
- const std::shared_ptr<Event>& event);
- status_t injectVSyncEvent(const VSyncEvent& vsyncEvent, const std::shared_ptr<Event>& event);
- void createDisplay(const DisplayCreation& create, const std::shared_ptr<Event>& event);
- void deleteDisplay(const DisplayDeletion& delete_, const std::shared_ptr<Event>& event);
- void updatePowerMode(const PowerModeUpdate& update, const std::shared_ptr<Event>& 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<layer_id, sp<SurfaceControl>> mLayers;
- std::unordered_map<layer_id, HSV> mColors;
-
- std::mutex mPendingLayersLock;
- std::vector<layer_id> mLayersPendingRemoval;
-
- std::mutex mBufferQueueSchedulerLock;
- std::unordered_map<layer_id, std::shared_ptr<BufferQueueScheduler>> mBufferQueueSchedulers;
-
- std::mutex mDisplayLock;
- std::condition_variable mDisplayCond;
- std::unordered_map<display_id, sp<IBinder>> mDisplays;
-
- sp<SurfaceComposerClient> mComposerClient;
- std::queue<std::shared_ptr<Event>> 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 58bfbf3c43..0000000000
--- 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 bdd5172e60..89c6c0f046 100644
--- a/data/etc/Android.bp
+++ b/data/etc/Android.bp
@@ -77,6 +77,18 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "android.hardware.context_hub.prebuilt.xml",
+ src: "android.hardware.context_hub.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.hardware.consumerir.prebuilt.xml",
+ src: "android.hardware.consumerir.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.ethernet.prebuilt.xml",
src: "android.hardware.ethernet.xml",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -101,36 +113,138 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "android.hardware.nfc.prebuilt.xml",
+ src: "android.hardware.nfc.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.hardware.nfc.hce.prebuilt.xml",
+ src: "android.hardware.nfc.hce.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.reboot_escrow.prebuilt.xml",
src: "android.hardware.reboot_escrow.xml",
defaults: ["frameworks_native_data_etc_defaults"],
}
prebuilt_etc {
+ name: "android.hardware.se.omapi.ese.prebuilt.xml",
+ src: "android.hardware.se.omapi.ese.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.hardware.se.omapi.sd.prebuilt.xml",
+ src: "android.hardware.se.omapi.sd.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.hardware.se.omapi.uicc.prebuilt.xml",
+ src: "android.hardware.se.omapi.uicc.xml",
+ 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",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -155,6 +269,18 @@ prebuilt_etc {
}
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",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -167,6 +293,12 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "android.hardware.thread_network.prebuilt.xml",
+ src: "android.hardware.thread_network.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.usb.accessory.prebuilt.xml",
src: "android.hardware.usb.accessory.xml",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -185,12 +317,30 @@ prebuilt_etc {
}
prebuilt_etc {
+ name: "android.hardware.vulkan.compute-0.prebuilt.xml",
+ src: "android.hardware.vulkan.compute-0.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.hardware.vulkan.level-1.prebuilt.xml",
+ src: "android.hardware.vulkan.level-1.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.vulkan.version-1_0_3.prebuilt.xml",
src: "android.hardware.vulkan.version-1_0_3.xml",
defaults: ["frameworks_native_data_etc_defaults"],
}
prebuilt_etc {
+ name: "android.hardware.vulkan.version-1_3.prebuilt.xml",
+ src: "android.hardware.vulkan.version-1_3.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.hardware.wifi.prebuilt.xml",
src: "android.hardware.wifi.xml",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -233,6 +383,24 @@ prebuilt_etc {
}
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.opengles.deqp.level-2024-03-01.prebuilt.xml",
+ src: "android.software.opengles.deqp.level-2024-03-01.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.software.opengles.deqp.level-latest.prebuilt.xml",
+ src: "android.software.opengles.deqp.level-latest.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "android.software.sip.voip.prebuilt.xml",
src: "android.software.sip.voip.xml",
defaults: ["frameworks_native_data_etc_defaults"],
@@ -257,6 +425,24 @@ prebuilt_etc {
}
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: "android.software.vulkan.deqp.level-2024-03-01.prebuilt.xml",
+ src: "android.software.vulkan.deqp.level-2024-03-01.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
+ name: "android.software.vulkan.deqp.level-latest.prebuilt.xml",
+ src: "android.software.vulkan.deqp.level-latest.xml",
+ defaults: ["frameworks_native_data_etc_defaults"],
+}
+
+prebuilt_etc {
name: "aosp_excluded_hardware.prebuilt.xml",
src: "aosp_excluded_hardware.xml",
defaults: ["frameworks_native_data_etc_defaults"],
diff --git a/data/etc/android.hardware.thread_network.xml b/data/etc/android.hardware.thread_network.xml
new file mode 100644
index 0000000000..b116ed6bf8
--- /dev/null
+++ b/data/etc/android.hardware.thread_network.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<!-- Adds the feature indicating support for the Thread networking protocol -->
+<permissions>
+ <feature name="android.hardware.thread_network" />
+</permissions>
diff --git a/data/etc/android.hardware.type.automotive.xml b/data/etc/android.hardware.type.automotive.xml
index 113945b0c5..8605d18d19 100644
--- a/data/etc/android.hardware.type.automotive.xml
+++ b/data/etc/android.hardware.type.automotive.xml
@@ -17,6 +17,6 @@
<!-- These features determine that the device running android is a car. -->
<permissions>
<feature name="android.hardware.type.automotive" />
- <!-- TODO: Revert this after enabling work profiles refer b/170332519 -->
- <unavailable-feature name="android.software.managed_users"/>
+
+ <unavailable-feature name="android.software.picture_in_picture"/>
</permissions>
diff --git a/data/etc/android.software.credentials.xml b/data/etc/android.software.credentials.xml
new file mode 100644
index 0000000000..234d14411c
--- /dev/null
+++ b/data/etc/android.software.credentials.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<permissions>
+ <feature name="android.software.credentials" />
+</permissions>
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 0000000000..d0b594c73d
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2023-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+ dEQP tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+ <feature name="android.software.opengles.deqp.level" version="132580097" />
+</permissions>
diff --git a/data/etc/android.software.opengles.deqp.level-2024-03-01.xml b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
new file mode 100644
index 0000000000..4eeed2af4f
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-2024-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+ dEQP tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+ <feature name="android.software.opengles.deqp.level" version="132645633" />
+</permissions>
diff --git a/data/etc/android.software.opengles.deqp.level-latest.xml b/data/etc/android.software.opengles.deqp.level-latest.xml
new file mode 100644
index 0000000000..62bb10161a
--- /dev/null
+++ b/data/etc/android.software.opengles.deqp.level-latest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes OpenGL ES
+ dEQP tests associated with the most recent level for this Android version. -->
+<permissions>
+ <feature name="android.software.opengles.deqp.level" version="132645633" />
+</permissions>
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 0000000000..6ae248ac3c
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2023-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+ tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+ <feature name="android.software.vulkan.deqp.level" version="132580097" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
new file mode 100644
index 0000000000..8b2b4c8199
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-2024-03-01.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan dEQP
+ tests associated with date 2023-03-01 (0x07E70301). -->
+<permissions>
+ <feature name="android.software.vulkan.deqp.level" version="132645633" />
+</permissions>
diff --git a/data/etc/android.software.vulkan.deqp.level-latest.xml b/data/etc/android.software.vulkan.deqp.level-latest.xml
new file mode 100644
index 0000000000..0fc12b3b5f
--- /dev/null
+++ b/data/etc/android.software.vulkan.deqp.level-latest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<!-- This is the standard feature indicating that the device passes Vulkan
+ dEQP tests associated with the most recent level for this Android version. -->
+<permissions>
+ <feature name="android.software.vulkan.deqp.level" version="132645633" />
+</permissions>
diff --git a/data/etc/aosp_excluded_hardware.xml b/data/etc/aosp_excluded_hardware.xml
index c12f4358c5..013f278f34 100644
--- a/data/etc/aosp_excluded_hardware.xml
+++ b/data/etc/aosp_excluded_hardware.xml
@@ -18,5 +18,4 @@
<!-- This should be used to exclude this feature from aosp targets. As aosp configurations
may or may not have a valid location provider -->
<unavailable-feature name="android.hardware.location.network" />
- <unavailable-feature name="android.software.device_id_attestation" />
</permissions>
diff --git a/data/etc/handheld_core_hardware.xml b/data/etc/handheld_core_hardware.xml
index 8fdd8d06d5..08330126c7 100644
--- a/data/etc/handheld_core_hardware.xml
+++ b/data/etc/handheld_core_hardware.xml
@@ -52,6 +52,7 @@
<feature name="android.software.print" />
<feature name="android.software.companion_device_setup" />
<feature name="android.software.autofill" />
+ <feature name="android.software.credentials" />
<feature name="android.software.cant_save_state" />
<feature name="android.software.secure_lock_screen" />
<feature name="android.software.window_magnification" />
diff --git a/data/etc/input/Android.bp b/data/etc/input/Android.bp
new file mode 100644
index 0000000000..b662491272
--- /dev/null
+++ b/data/etc/input/Android.bp
@@ -0,0 +1,23 @@
+package {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+filegroup {
+ name: "motion_predictor_model",
+ srcs: [
+ "motion_predictor_model.tflite",
+ "motion_predictor_config.xml",
+ ],
+}
+
+prebuilt_etc {
+ name: "motion_predictor_model_prebuilt",
+ filename_from_src: true,
+ src: "motion_predictor_model.tflite",
+}
+
+prebuilt_etc {
+ name: "motion_predictor_model_config",
+ filename_from_src: true,
+ src: "motion_predictor_config.xml",
+}
diff --git a/data/etc/input/motion_predictor_config.xml b/data/etc/input/motion_predictor_config.xml
new file mode 100644
index 0000000000..39772aece2
--- /dev/null
+++ b/data/etc/input/motion_predictor_config.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<motion-predictor>
+ <!-- The time interval (ns) between the model's predictions. -->
+ <prediction-interval>4166666</prediction-interval> <!-- 4.167 ms = ~240 Hz -->
+ <!-- The noise floor (px) for predicted distances.
+
+ As the model is trained stochastically, there is some expected minimum
+ variability in its output. This can be a UX issue when the input device
+ is moving slowly and the variability is large relative to the magnitude
+ of the motion. In these cases, it is better to inhibit the prediction,
+ rather than show noisy predictions (and there is little benefit to
+ prediction anyway).
+
+ The value for this parameter should at least be close to the maximum
+ predicted distance when the input device is held stationary (i.e. the
+ expected minimum variability), and perhaps a little larger to capture
+ the UX issue mentioned above.
+ -->
+ <distance-noise-floor>0.2</distance-noise-floor>
+</motion-predictor>
+
diff --git a/data/etc/input/motion_predictor_model.tflite b/data/etc/input/motion_predictor_model.tflite
new file mode 100644
index 0000000000..45fc162cd1
--- /dev/null
+++ b/data/etc/input/motion_predictor_model.tflite
Binary files differ
diff --git a/data/etc/tablet_core_hardware.xml b/data/etc/tablet_core_hardware.xml
index 59d5b10947..6af4d91418 100644
--- a/data/etc/tablet_core_hardware.xml
+++ b/data/etc/tablet_core_hardware.xml
@@ -52,6 +52,7 @@
<feature name="android.software.print" />
<feature name="android.software.companion_device_setup" />
<feature name="android.software.autofill" />
+ <feature name="android.software.credentials" />
<feature name="android.software.cant_save_state" />
<feature name="android.software.secure_lock_screen" />
<feature name="android.software.window_magnification" />
diff --git a/headers/media_plugin/media/hardware/VideoAPI.h b/headers/media_plugin/media/hardware/VideoAPI.h
index a09087698c..54666804c0 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/headers/media_plugin/media/openmax/OMX_AsString.h b/headers/media_plugin/media/openmax/OMX_AsString.h
index ce30b417e0..165a868d57 100644
--- a/headers/media_plugin/media/openmax/OMX_AsString.h
+++ b/headers/media_plugin/media/openmax/OMX_AsString.h
@@ -561,6 +561,7 @@ inline static const char *asString(OMX_INDEXEXTTYPE i, const char *def = "??") {
case OMX_IndexConfigPriority: return "ConfigPriority";
case OMX_IndexConfigOperatingRate: return "ConfigOperatingRate";
case OMX_IndexParamConsumerUsageBits: return "ParamConsumerUsageBits";
+ case OMX_IndexParamConsumerUsageBits64: return "ParamConsumerUsageBits64";
case OMX_IndexConfigLatency: return "ConfigLatency";
default: return asString((OMX_INDEXTYPE)i, def);
}
diff --git a/headers/media_plugin/media/openmax/OMX_IndexExt.h b/headers/media_plugin/media/openmax/OMX_IndexExt.h
index 0af40dd28e..5ddd719ba1 100644
--- a/headers/media_plugin/media/openmax/OMX_IndexExt.h
+++ b/headers/media_plugin/media/openmax/OMX_IndexExt.h
@@ -105,6 +105,7 @@ typedef enum OMX_INDEXEXTTYPE {
OMX_IndexConfigLowLatency, /**< reference: OMX_CONFIG_BOOLEANTYPE */
OMX_IndexConfigAndroidTunnelPeek, /**< reference: OMX_CONFIG_BOOLEANTYPE */
OMX_IndexConfigAndroidTunnelPeekLegacyMode, /**< reference: OMX_CONFIG_BOOLEANTYPE */
+ OMX_IndexParamConsumerUsageBits64, /**< reference: OMX_PARAM_U64TYPE */
OMX_IndexExtOtherEndUnused,
/* Time configurations */
diff --git a/include/android/OWNERS b/include/android/OWNERS
new file mode 100644
index 0000000000..c6cf7ade48
--- /dev/null
+++ b/include/android/OWNERS
@@ -0,0 +1 @@
+per-file input.h,keycodes.h = file:platform/frameworks/base:/INPUT_OWNERS
diff --git a/include/android/bitmap.h b/include/android/bitmap.h
index 35f87f96ae..87a14c021d 100644
--- a/include/android/bitmap.h
+++ b/include/android/bitmap.h
@@ -196,7 +196,7 @@ enum AndroidBitmapCompressFormat {
*
* @param userContext Pointer to user-defined data passed to
* {@link AndroidBitmap_compress}.
- * @param data Compressed data of |size| bytes to write.
+ * @param data Compressed data of `size` bytes to write.
* @param size Length in bytes of data to write.
* @return Whether the operation succeeded.
*/
@@ -205,7 +205,7 @@ typedef bool (*AndroidBitmap_CompressWriteFunc)(void* userContext,
size_t size) __INTRODUCED_IN(30);
/**
- * Compress |pixels| as described by |info|.
+ * Compress `pixels` as described by `info`.
*
* Available since API level 30.
*
diff --git a/include/android/choreographer.h b/include/android/choreographer.h
index cd8e63dec2..6a91d9836e 100644
--- a/include/android/choreographer.h
+++ b/include/android/choreographer.h
@@ -17,8 +17,20 @@
/**
* @addtogroup Choreographer
*
- * Choreographer coordinates the timing of frame rendering. This is the C version of the
- * android.view.Choreographer object in Java.
+ * Choreographer coordinates the timing of frame rendering. This is the C
+ * version of the android.view.Choreographer object in Java. If you do not use
+ * Choreographer to pace your render loop, you may render too quickly for the
+ * display, increasing latency between frame submission and presentation.
+ *
+ * Input events are guaranteed to be processed before the frame callback is
+ * called, and will not be run concurrently. Input and sensor events should not
+ * be handled in the Choregrapher callback.
+ *
+ * The frame callback is also the appropriate place to run any per-frame state
+ * update logic. For example, in a game, the frame callback should be
+ * responsible for updating things like physics, AI, game state, and rendering
+ * the frame. Input and sensors should be handled separately via callbacks
+ * registered with AInputQueue and ASensorManager.
*
* As of API level 33, apps can follow proper frame pacing and even choose a future frame to render.
* The API is used as follows:
@@ -38,6 +50,11 @@
* 4. SurfaceFlinger attempts to follow the chosen frame timeline, by not applying transactions or
* latching buffers before the desired presentation time.
*
+ * On older devices, AChoreographer_postFrameCallback64 or
+ * AChoreographer_postFrameCallback can be used to lesser effect. They cannot be
+ * used to precisely plan your render timeline, but will rate limit to avoid
+ * overloading the display pipeline and increasing frame latency.
+ *
* @{
*/
@@ -119,23 +136,60 @@ typedef void (*AChoreographer_refreshRateCallback)(int64_t vsyncPeriodNanos, voi
AChoreographer* AChoreographer_getInstance() __INTRODUCED_IN(24);
/**
- * Deprecated: Use AChoreographer_postFrameCallback64 instead.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame. The data pointer provided will be passed to the callback function
+ * when it's called.
+ *
+ * The callback will only be run for the next frame, not all subsequent frames,
+ * so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallback.
+ *
+ * \bug The callback receives the frame time in nanoseconds as a long. On 32-bit
+ * systems, long is 32-bit, so the frame time will roll over roughly every two
+ * seconds. If your minSdkVersion is 29 or higher, switch to
+ * AChoreographer_postFrameCallback64, which uses a 64-bit frame time for all
+ * platforms. For older OS versions, you must combine the argument with the
+ * upper bits of clock_gettime(CLOCK_MONOTONIC, ...) on 32-bit systems.
+ *
+ * \deprecated Use AChoreographer_postFrameCallback64, which does not have the
+ * bug described above.
*/
void AChoreographer_postFrameCallback(AChoreographer* choreographer,
AChoreographer_frameCallback callback, void* data)
- __INTRODUCED_IN(24) __DEPRECATED_IN(29);
+ __INTRODUCED_IN(24) __DEPRECATED_IN(29, "Use AChoreographer_postFrameCallback64 instead");
/**
- * Deprecated: Use AChoreographer_postFrameCallbackDelayed64 instead.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame following the specified delay. The data pointer provided will be
+ * passed to the callback function when it's called.
+ *
+ * The callback will only be run for the next frame after the delay, not all
+ * subsequent frames, so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallbackDelayed.
+ *
+ * \bug The callback receives the frame time in nanoseconds as a long. On 32-bit
+ * systems, long is 32-bit, so the frame time will roll over roughly every two
+ * seconds. If your minSdkVersion is 29 or higher, switch to
+ * AChoreographer_postFrameCallbackDelayed64, which uses a 64-bit frame time for
+ * all platforms. For older OS versions, you must combine the argument with the
+ * upper bits of clock_gettime(CLOCK_MONOTONIC, ...) on 32-bit systems.
+ *
+ * \deprecated Use AChoreographer_postFrameCallbackDelayed64, which does not
+ * have the bug described above.
*/
void AChoreographer_postFrameCallbackDelayed(AChoreographer* choreographer,
AChoreographer_frameCallback callback, void* data,
long delayMillis) __INTRODUCED_IN(24)
- __DEPRECATED_IN(29);
+ __DEPRECATED_IN(29, "Use AChoreographer_postFrameCallbackDelayed64 instead");
/**
- * Post a callback to be run on the next frame. The data pointer provided will
- * be passed to the callback function when it's called.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame. The data pointer provided will be passed to the callback function
+ * when it's called.
+ *
+ * The callback will only be run on the next frame, not all subsequent frames,
+ * so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallback64.
*
* Available since API level 29.
*/
@@ -144,9 +198,13 @@ void AChoreographer_postFrameCallback64(AChoreographer* choreographer,
__INTRODUCED_IN(29);
/**
- * Post a callback to be run on the frame following the specified delay. The
- * data pointer provided will be passed to the callback function when it's
- * called.
+ * Post a callback to be run when the application should begin rendering the
+ * next frame following the specified delay. The data pointer provided will be
+ * passed to the callback function when it's called.
+ *
+ * The callback will only be run for the next frame after the delay, not all
+ * subsequent frames, so to render continuously the callback should itself call
+ * AChoreographer_postFrameCallbackDelayed64.
*
* Available since API level 29.
*/
@@ -155,8 +213,13 @@ void AChoreographer_postFrameCallbackDelayed64(AChoreographer* choreographer,
uint32_t delayMillis) __INTRODUCED_IN(29);
/**
- * Posts a callback to be run on the next frame. The data pointer provided will
- * be passed to the callback function when it's called.
+ * Posts a callback to be run when the application should begin rendering the
+ * next frame. The data pointer provided will be passed to the callback function
+ * when it's called.
+ *
+ * The callback will only be run for the next frame, not all subsequent frames,
+ * so to render continuously the callback should itself call
+ * AChoreographer_postVsyncCallback.
*
* Available since API level 33.
*/
@@ -219,12 +282,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 +300,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 +322,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 +334,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 88019ae054..46c7dfeceb 100644
--- a/include/android/configuration.h
+++ b/include/android/configuration.h
@@ -471,10 +471,36 @@ enum {
*/
ACONFIGURATION_COLOR_MODE = 0x10000,
/**
+ * Bit mask for
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">grammatical gender</a>
+ * 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,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: not specified.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_ANY = 0,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: neuter.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_NEUTER = 1,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: feminine.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_FEMININE = 2,
+
+ /**
+ * <a href="/guide/topics/resources/providing-resources.html#GrammaticalInflectionQualifier">Grammatical gender</a>: masculine.
+ */
+ ACONFIGURATION_GRAMMATICAL_GENDER_MASCULINE = 3,
};
/**
@@ -726,6 +752,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
* is different between them.
diff --git a/include/android/font.h b/include/android/font.h
index 8a3a474f25..022572535b 100644
--- a/include/android/font.h
+++ b/include/android/font.h
@@ -31,6 +31,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <sys/cdefs.h>
/******************************************************************
@@ -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 4417422687..60ff95e123 100644
--- a/include/android/font_matcher.h
+++ b/include/android/font_matcher.h
@@ -75,6 +75,7 @@
#include <stdbool.h>
#include <stddef.h>
+#include <stdint.h>
#include <sys/cdefs.h>
#include <android/font.h>
@@ -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 38b27bc587..16d86af44c 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -55,8 +55,11 @@
#include <sys/types.h>
#include <android/keycodes.h>
#include <android/looper.h>
+
#include <jni.h>
+// This file may also be built on glibc or on Windows/MacOS libc's, so no-op
+// definitions are provided.
#if !defined(__INTRODUCED_IN)
#define __INTRODUCED_IN(__api_level) /* nothing */
#endif
@@ -249,13 +252,13 @@ enum {
AKEY_EVENT_FLAG_LONG_PRESS = 0x80,
/**
- * Set when a key event has AKEY_EVENT_FLAG_CANCELED set because a long
+ * Set when a key event has #AKEY_EVENT_FLAG_CANCELED set because a long
* press action was executed while it was down.
*/
AKEY_EVENT_FLAG_CANCELED_LONG_PRESS = 0x100,
/**
- * Set for AKEY_EVENT_ACTION_UP when this event's key code is still being
+ * Set for #AKEY_EVENT_ACTION_UP when this event's key code is still being
* tracked from its initial down. That is, somebody requested that tracking
* started on the key down and a long press has not caused
* the tracking to be canceled.
@@ -275,7 +278,7 @@ enum {
/**
* Bit shift for the action bits holding the pointer index as
- * defined by AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
+ * defined by #AMOTION_EVENT_ACTION_POINTER_INDEX_MASK.
*/
#define AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT 8
@@ -286,8 +289,8 @@ enum {
/**
* Bits in the action code that represent a pointer index, used with
- * AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
- * down by AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
+ * #AMOTION_EVENT_ACTION_POINTER_DOWN and AMOTION_EVENT_ACTION_POINTER_UP. Shifting
+ * down by #AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT provides the actual pointer
* index where the data for the pointer going up or down can be found.
*/
AMOTION_EVENT_ACTION_POINTER_INDEX_MASK = 0xff00,
@@ -302,8 +305,8 @@ enum {
AMOTION_EVENT_ACTION_UP = 1,
/**
- * A change has happened during a press gesture (between AMOTION_EVENT_ACTION_DOWN and
- * AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
+ * A change has happened during a press gesture (between #AMOTION_EVENT_ACTION_DOWN and
+ * #AMOTION_EVENT_ACTION_UP). The motion contains the most recent point, as well as
* any intermediate points since the last down or move event.
*/
AMOTION_EVENT_ACTION_MOVE = 2,
@@ -323,18 +326,18 @@ enum {
/**
* A non-primary pointer has gone down.
- * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+ * The bits in #AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
*/
AMOTION_EVENT_ACTION_POINTER_DOWN = 5,
/**
* A non-primary pointer has gone up.
- * The bits in AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
+ * The bits in #AMOTION_EVENT_ACTION_POINTER_INDEX_MASK indicate which pointer changed.
*/
AMOTION_EVENT_ACTION_POINTER_UP = 6,
/**
- * A change happened but the pointer is not down (unlike AMOTION_EVENT_ACTION_MOVE).
+ * A change happened but the pointer is not down (unlike #AMOTION_EVENT_ACTION_MOVE).
* The motion contains the most recent point, as well as any intermediate points since
* the last hover move event.
*/
@@ -342,8 +345,8 @@ enum {
/**
* The motion event contains relative vertical and/or horizontal scroll offsets.
- * Use getAxisValue to retrieve the information from AMOTION_EVENT_AXIS_VSCROLL
- * and AMOTION_EVENT_AXIS_HSCROLL.
+ * Use {@link AMotionEvent_getAxisValue} to retrieve the information from
+ * #AMOTION_EVENT_AXIS_VSCROLL and #AMOTION_EVENT_AXIS_HSCROLL.
* The pointer may or may not be down when this event is dispatched.
* This action is always delivered to the winder under the pointer, which
* may not be the window currently touched.
@@ -528,7 +531,7 @@ enum {
* is pointing in relation to the vertical axis of the current orientation of the screen.
* The range is from -PI radians to PI radians, where 0 is pointing up,
* -PI/2 radians is pointing left, -PI or PI radians is pointing down, and PI/2 radians
- * is pointing right. See also {@link AMOTION_EVENT_AXIS_TILT}.
+ * is pointing right. See also #AMOTION_EVENT_AXIS_TILT.
*/
AMOTION_EVENT_AXIS_ORIENTATION = 8,
/**
@@ -681,7 +684,7 @@ enum {
/**
* Axis constant: The movement of y position of a motion event.
*
- * Same as {@link AMOTION_EVENT_AXIS_RELATIVE_X}, but for y position.
+ * Same as #AMOTION_EVENT_AXIS_RELATIVE_X, but for y position.
*/
AMOTION_EVENT_AXIS_RELATIVE_Y = 28,
/**
@@ -764,9 +767,81 @@ 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.
+ *
+ * This axis is only set on the first pointer in a motion event.
+ */
+ 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.
+ *
+ * This axis is only set on the first pointer in a motion event.
+ */
+ 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.
+ *
+ * This axis is only set on the first pointer in a motion event.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_PINCH_SCALE_FACTOR = 52,
+
+ /**
+ * Axis constant: the number of fingers being used in a multi-finger swipe gesture.
+ *
+ * - For a touch pad, reports the number of fingers being used in a multi-finger swipe gesture
+ * (with CLASSIFICATION_MULTI_FINGER_SWIPE).
+ *
+ * Since CLASSIFICATION_MULTI_FINGER_SWIPE is a hidden API, so is this axis. It is only set on
+ * the first pointer in a motion event.
+ */
+ AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT = 53,
+
+ /**
+ * 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_SWIPE_FINGER_COUNT,
// 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.
};
/**
@@ -821,7 +896,8 @@ enum AMotionClassification : uint32_t {
* Classification constant: Ambiguous gesture.
*
* The user's intent with respect to the current event stream is not yet determined. Events
- * starting in AMBIGUOUS_GESTURE will eventually resolve into either DEEP_PRESS or NONE.
+ * starting in #AMOTION_EVENT_CLASSIFICATION_AMBIGUOUS_GESTURE will eventually resolve into
+ * either #AMOTION_EVENT_CLASSIFICATION_DEEP_PRESS or #AMOTION_EVENT_CLASSIFICATION_NONE.
* Gestural actions, such as scrolling, should be inhibited until the classification resolves
* to another value or the event stream ends.
*/
@@ -833,6 +909,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,
};
/**
@@ -923,7 +1020,8 @@ enum {
* Refer to the documentation on android.view.InputDevice for more details about input sources
* and their correct interpretation.
*
- * @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*} constants instead.
+ * @deprecated These constants are deprecated. Use {@link AMOTION_EVENT_AXIS AMOTION_EVENT_AXIS_*}
+ * constants instead.
*/
enum {
/** x */
@@ -977,8 +1075,8 @@ int32_t AInputEvent_getSource(const AInputEvent* event);
/**
* Releases interface objects created by {@link AKeyEvent_fromJava()}
* and {@link AMotionEvent_fromJava()}.
- * After returning, the specified AInputEvent* object becomes invalid and should no longer be used.
- * The underlying Java object remains valid and does not change its state.
+ * After returning, the specified {@link AInputEvent}* object becomes invalid and should no longer
+ * be used. The underlying Java object remains valid and does not change its state.
*
* Available since API level 31.
*/
@@ -1031,9 +1129,10 @@ int64_t AKeyEvent_getDownTime(const AInputEvent* key_event);
int64_t AKeyEvent_getEventTime(const AInputEvent* key_event);
/**
- * Creates a native AInputEvent* object that is a copy of the specified Java android.view.KeyEvent.
- * The result may be used with generic and KeyEvent-specific AInputEvent_* functions. The object
- * returned by this function must be disposed using {@link AInputEvent_release()}.
+ * Creates a native {@link AInputEvent}* object that is a copy of the specified Java
+ * android.view.KeyEvent. The result may be used with generic and KeyEvent-specific AInputEvent_*
+ * functions. The object returned by this function must be disposed using
+ * {@link AInputEvent_release()}.
*
* Available since API level 31.
*/
@@ -1221,7 +1320,7 @@ float AMotionEvent_getAxisValue(const AInputEvent* motion_event,
/**
* Get the number of historical points in this event. These are movements that
* have occurred between this event and the previous event. This only applies
- * to AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
+ * to #AMOTION_EVENT_ACTION_MOVE events -- all other actions will have a size of 0.
* Historical samples are indexed from oldest to newest.
*/
size_t AMotionEvent_getHistorySize(const AInputEvent* motion_event);
@@ -1382,7 +1481,7 @@ int32_t AMotionEvent_getClassification(const AInputEvent* motion_event)
__INTRODUCED_IN(__ANDROID_API_T__);
/**
- * Creates a native AInputEvent* object that is a copy of the specified Java
+ * Creates a native {@link AInputEvent}* object that is a copy of the specified Java
* android.view.MotionEvent. The result may be used with generic and MotionEvent-specific
* AInputEvent_* functions. The object returned by this function must be disposed using
* {@link AInputEvent_release()}.
@@ -1402,7 +1501,7 @@ typedef struct AInputQueue AInputQueue;
/**
* Add this input queue to a looper for processing. See
- * ALooper_addFd() for information on the ident, callback, and data params.
+ * {@link ALooper_addFd()} for information on the ident, callback, and data params.
*/
void AInputQueue_attachLooper(AInputQueue* queue, ALooper* looper,
int ident, ALooper_callbackFunc callback, void* data);
@@ -1437,12 +1536,12 @@ int32_t AInputQueue_preDispatchEvent(AInputQueue* queue, AInputEvent* event);
/**
* Report that dispatching has finished with the given event.
- * This must be called after receiving an event with AInputQueue_get_event().
+ * This must be called after receiving an event with {@link AInputQueue_getEvent()}.
*/
void AInputQueue_finishEvent(AInputQueue* queue, AInputEvent* event, int handled);
/**
- * Returns the AInputQueue* object associated with the supplied Java InputQueue
+ * Returns the {@link AInputQueue}* object associated with the supplied Java InputQueue
* object. The returned native object holds a weak reference to the Java object,
* and is only valid as long as the Java object has not yet been disposed. You
* should ensure that there is a strong reference to the Java object and that it
diff --git a/include/android/keycodes.h b/include/android/keycodes.h
index 214559d683..f8fb256fae 100644
--- a/include/android/keycodes.h
+++ b/include/android/keycodes.h
@@ -776,7 +776,69 @@ enum {
AKEYCODE_THUMBS_DOWN = 287,
/** Used to switch current account that is consuming content.
* May be consumed by system to switch current viewer profile. */
- AKEYCODE_PROFILE_SWITCH = 288
+ AKEYCODE_PROFILE_SWITCH = 288,
+ /** Video Application key #1. */
+ AKEYCODE_VIDEO_APP_1 = 289,
+ /** Video Application key #2. */
+ AKEYCODE_VIDEO_APP_2 = 290,
+ /** Video Application key #3. */
+ AKEYCODE_VIDEO_APP_3 = 291,
+ /** Video Application key #4. */
+ AKEYCODE_VIDEO_APP_4 = 292,
+ /** Video Application key #5. */
+ AKEYCODE_VIDEO_APP_5 = 293,
+ /** Video Application key #6. */
+ AKEYCODE_VIDEO_APP_6 = 294,
+ /** Video Application key #7. */
+ AKEYCODE_VIDEO_APP_7 = 295,
+ /** Video Application key #8. */
+ AKEYCODE_VIDEO_APP_8 = 296,
+ /** Featured Application key #1. */
+ AKEYCODE_FEATURED_APP_1 = 297,
+ /** Featured Application key #2. */
+ AKEYCODE_FEATURED_APP_2 = 298,
+ /** Featured Application key #3. */
+ AKEYCODE_FEATURED_APP_3 = 299,
+ /** Featured Application key #4. */
+ AKEYCODE_FEATURED_APP_4 = 300,
+ /** Demo Application key #1. */
+ AKEYCODE_DEMO_APP_1 = 301,
+ /** Demo Application key #2. */
+ AKEYCODE_DEMO_APP_2 = 302,
+ /** Demo Application key #3. */
+ 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 718f703048..d80a3660a6 100644
--- a/include/android/looper.h
+++ b/include/android/looper.h
@@ -26,10 +26,18 @@
#ifndef ANDROID_LOOPER_H
#define ANDROID_LOOPER_H
+#include <sys/cdefs.h>
+
#ifdef __cplusplus
extern "C" {
#endif
+// This file may also be built on glibc or on Windows/MacOS libc's, so
+// deprecated definitions are provided.
+#if !defined(__REMOVED_IN)
+#define __REMOVED_IN(__api_level, msg) __attribute__((__deprecated__(msg)))
+#endif
+
struct ALooper;
/**
* ALooper
@@ -174,23 +182,27 @@ typedef int (*ALooper_callbackFunc)(int fd, int events, void* data);
* If the timeout is zero, returns immediately without blocking.
* If the timeout is negative, waits indefinitely until an event appears.
*
- * Returns ALOOPER_POLL_WAKE if the poll was awoken using wake() before
+ * Returns ALOOPER_POLL_WAKE if the poll was awoken using ALooper_wake() before
* the timeout expired and no callbacks were invoked and no other file
- * descriptors were ready.
+ * descriptors were ready. **All return values may also imply
+ * ALOOPER_POLL_WAKE.**
*
- * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked.
+ * Returns ALOOPER_POLL_CALLBACK if one or more callbacks were invoked. The poll
+ * may also have been explicitly woken by ALooper_wake.
*
- * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given
- * timeout expired.
+ * Returns ALOOPER_POLL_TIMEOUT if there was no data before the given timeout
+ * expired. The poll may also have been explicitly woken by ALooper_wake.
*
- * Returns ALOOPER_POLL_ERROR if an error occurred.
+ * Returns ALOOPER_POLL_ERROR if the calling thread has no associated Looper or
+ * for unrecoverable internal errors. The poll may also have been explicitly
+ * woken by ALooper_wake.
*
- * Returns a value >= 0 containing an identifier (the same identifier
- * `ident` passed to ALooper_addFd()) if its file descriptor has data
- * and it has no callback function (requiring the caller here to
- * handle it). In this (and only this) case outFd, outEvents and
- * outData will contain the poll events and data associated with the
- * fd, otherwise they will be set to NULL.
+ * Returns a value >= 0 containing an identifier (the same identifier `ident`
+ * passed to ALooper_addFd()) if its file descriptor has data and it has no
+ * callback function (requiring the caller here to handle it). In this (and
+ * only this) case outFd, outEvents and outData will contain the poll events and
+ * data associated with the fd, otherwise they will be set to NULL. The poll may
+ * also have been explicitly woken by ALooper_wake.
*
* This method does not return until it has finished invoking the appropriate callbacks
* for all file descriptors that were signalled.
@@ -201,8 +213,22 @@ 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.
+ *
+ * This API cannot be used safely, but a safe alternative exists (see below). As
+ * such, new builds will not be able to call this API and must migrate to the
+ * safe API. Binary compatibility is preserved to support already-compiled apps.
+ *
+ * \bug ALooper_pollAll will not wake in response to ALooper_wake calls if it
+ * also handles another event at the same time.
+ *
+ * \deprecated Calls to ALooper_pollAll should be replaced with
+ * ALooper_pollOnce. If you call ALooper_pollOnce in a loop, you *must* treat
+ * all return values as if they also indicate ALOOPER_POLL_WAKE.
*/
-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,
+ "ALooper_pollAll may ignore wakes. Use ALooper_pollOnce instead. See "
+ "The API documentation for more information");
/**
* Wakes the poll asynchronously.
diff --git a/include/android/performance_hint.h b/include/android/performance_hint.h
index 5fa47f64be..9d2c79139f 100644
--- a/include/android/performance_hint.h
+++ b/include/android/performance_hint.h
@@ -14,6 +14,23 @@
* limitations under the License.
*/
+ /**
+ * @defgroup APerformanceHint Performance Hint Manager
+ *
+ * APerformanceHint allows apps to create performance hint sessions for groups
+ * of threads, and provide hints to the system about the workload of those threads,
+ * to help the system more accurately allocate power for them. It is the NDK
+ * counterpart to the Java PerformanceHintManager SDK API.
+ *
+ * @{
+ */
+
+/**
+ * @file performance_hint.h
+ * @brief API for creating and managing a hint session.
+ */
+
+
#ifndef ANDROID_NATIVE_PERFORMANCE_HINT_H
#define ANDROID_NATIVE_PERFORMANCE_HINT_H
@@ -37,17 +54,39 @@
#include <android/api-level.h>
#include <stdint.h>
+#include <unistd.h>
__BEGIN_DECLS
struct APerformanceHintManager;
struct APerformanceHintSession;
+struct AWorkDuration;
+
+/**
+ * {@link AWorkDuration} is an opaque type that represents the breakdown of the
+ * actual workload duration in each component internally.
+ *
+ * A new {@link AWorkDuration} can be obtained using
+ * {@link AWorkDuration_create()}, when the client finishes using
+ * {@link AWorkDuration}, {@link AWorkDuration_release()} must be
+ * called to destroy and free up the resources associated with
+ * {@link AWorkDuration}.
+ *
+ * This file provides a set of functions to allow clients to set the measured
+ * work duration of each component on {@link AWorkDuration}.
+ *
+ * - AWorkDuration_setWorkPeriodStartTimestampNanos()
+ * - AWorkDuration_setActualTotalDurationNanos()
+ * - AWorkDuration_setActualCpuDurationNanos()
+ * - AWorkDuration_setActualGpuDurationNanos()
+ */
+typedef struct AWorkDuration AWorkDuration;
/**
* An opaque type representing a handle to a performance hint manager.
* It must be released after use.
*
- * <p>To use:<ul>
+ * To use:<ul>
* <li>Obtain the performance hint manager instance by calling
* {@link APerformanceHint_getManager} function.</li>
* <li>Create an {@link APerformanceHintSession} with
@@ -60,54 +99,47 @@ typedef struct APerformanceHintManager APerformanceHintManager;
/**
* An opaque type representing a handle to a performance hint session.
* A session can only be acquired from a {@link APerformanceHintManager}
- * with {@link APerformanceHint_getPreferredUpdateRateNanos}. It must be
+ * with {@link APerformanceHint_createSession}. It must be
* freed with {@link APerformanceHint_closeSession} after use.
*
* A Session represents a group of threads with an inter-related workload such that hints for
* their performance should be considered as a unit. The threads in a given session should be
- * long-life and not created or destroyed dynamically.
- *
- * <p>Each session is expected to have a periodic workload with a target duration for each
- * cycle. The cycle duration is likely greater than the target work duration to allow other
- * parts of the pipeline to run within the available budget. For example, a renderer thread may
- * work at 60hz in order to produce frames at the display's frame but have a target work
- * duration of only 6ms.</p>
- *
- * <p>After each cycle of work, the client is expected to use
- * {@link APerformanceHint_reportActualWorkDuration} to report the actual time taken to
- * complete.</p>
- *
- * <p>To use:<ul>
- * <li>Update a sessions target duration for each cycle of work
- * with {@link APerformanceHint_updateTargetWorkDuration}.</li>
- * <li>Report the actual duration for the last cycle of work with
- * {@link APerformanceHint_reportActualWorkDuration}.</li>
- * <li>Release the session instance with
- * {@link APerformanceHint_closeSession}.</li></ul></p>
+ * long-lived and not created or destroyed dynamically.
+ *
+ * The work duration API can be used with periodic workloads to dynamically adjust thread
+ * performance and keep the work on schedule while optimizing the available power budget.
+ * When using the work duration API, the starting target duration should be specified
+ * while creating the session, and can later be adjusted with
+ * {@link APerformanceHint_updateTargetWorkDuration}. While using the work duration
+ * API, the client is expected to call {@link APerformanceHint_reportActualWorkDuration} each
+ * cycle to report the actual time taken to complete to the system.
+ *
+ * All timings should be from `std::chrono::steady_clock` or `clock_gettime(CLOCK_MONOTONIC, ...)`
*/
typedef struct APerformanceHintSession APerformanceHintSession;
/**
* Acquire an instance of the performance hint manager.
*
- * @return manager instance on success, nullptr on failure.
+ * @return APerformanceHintManager instance on success, nullptr on failure.
*/
-APerformanceHintManager* APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
+APerformanceHintManager* _Nullable APerformanceHint_getManager() __INTRODUCED_IN(__ANDROID_API_T__);
/**
* Creates a session for the given set of threads and sets their initial target work
* duration.
+ *
* @param manager The performance hint manager instance.
* @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 threadIds.
- * @param initialTargetWorkDurationNanos The desired duration in nanoseconds for the new session.
- * This must be positive.
- * @return manager instance on success, nullptr on failure.
- */
-APerformanceHintSession* APerformanceHint_createSession(
- APerformanceHintManager* manager,
- const int32_t* threadIds, size_t size,
+ * this process' thread group.
+ * @param size The size of the list of threadIds.
+ * @param initialTargetWorkDurationNanos The target duration in nanoseconds for the new session.
+ * This must be positive if using the work duration API, or 0 otherwise.
+ * @return APerformanceHintManager instance on success, nullptr on failure.
+ */
+APerformanceHintSession* _Nullable APerformanceHint_createSession(
+ APerformanceHintManager* _Nonnull manager,
+ const int32_t* _Nonnull threadIds, size_t size,
int64_t initialTargetWorkDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
/**
@@ -117,37 +149,36 @@ APerformanceHintSession* APerformanceHint_createSession(
* @return the preferred update rate supported by device software.
*/
int64_t APerformanceHint_getPreferredUpdateRateNanos(
- APerformanceHintManager* manager) __INTRODUCED_IN(__ANDROID_API_T__);
+ APerformanceHintManager* _Nonnull manager) __INTRODUCED_IN(__ANDROID_API_T__);
/**
* Updates this session's target duration for each cycle of work.
*
* @param session The performance hint session instance to update.
- * @param targetDurationNanos the new desired duration in nanoseconds. This must be positive.
- * @return 0 on success
+ * @param targetDurationNanos The new desired duration in nanoseconds. This must be positive.
+ * @return 0 on success.
* EINVAL if targetDurationNanos is not positive.
* EPIPE if communication with the system service has failed.
*/
int APerformanceHint_updateTargetWorkDuration(
- APerformanceHintSession* session,
+ APerformanceHintSession* _Nonnull session,
int64_t targetDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
/**
* Reports the actual duration for the last cycle of work.
*
- * <p>The system will attempt to adjust the core placement of the threads within the thread
- * group and/or the frequency of the core on which they are run to bring the actual duration
- * close to the target duration.</p>
+ * The system will attempt to adjust the scheduling and performance of the
+ * threads within the thread group to bring the actual duration close to the target duration.
*
* @param session The performance hint session instance to update.
- * @param actualDurationNanos how long the thread group took to complete its last task in
- * nanoseconds. This must be positive.
- * @return 0 on success
+ * @param actualDurationNanos The duration of time the thread group took to complete its last
+ * task in nanoseconds. This must be positive.
+ * @return 0 on success.
* EINVAL if actualDurationNanos is not positive.
* EPIPE if communication with the system service has failed.
*/
int APerformanceHint_reportActualWorkDuration(
- APerformanceHintSession* session,
+ APerformanceHintSession* _Nonnull session,
int64_t actualDurationNanos) __INTRODUCED_IN(__ANDROID_API_T__);
/**
@@ -157,8 +188,123 @@ int APerformanceHint_reportActualWorkDuration(
* @param session The performance hint session instance to release.
*/
void APerformanceHint_closeSession(
- APerformanceHintSession* session) __INTRODUCED_IN(__ANDROID_API_T__);
+ APerformanceHintSession* _Nonnull 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 to update.
+ * @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 are not part of
+ the thread group.
+ * EPIPE if communication with the system service has failed.
+ * EPERM if any thread id doesn't belong to the application.
+ */
+int APerformanceHint_setThreads(
+ APerformanceHintSession* _Nonnull session,
+ const pid_t* _Nonnull threadIds,
+ size_t size) __INTRODUCED_IN(__ANDROID_API_U__);
+
+/**
+ * This tells the session that these threads can be
+ * safely scheduled to prefer power efficiency over performance.
+ *
+ * @param session The performance hint session instance to update.
+ * @param enabled The flag which sets whether this session will use power-efficient scheduling.
+ * @return 0 on success.
+ * EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_setPreferPowerEfficiency(
+ APerformanceHintSession* _Nonnull session,
+ bool enabled) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Reports the durations for the last cycle of work.
+ *
+ * The system will attempt to adjust the scheduling and performance of the
+ * threads within the thread group to bring the actual duration close to the target duration.
+ *
+ * @param session The {@link APerformanceHintSession} instance to update.
+ * @param workDuration The {@link AWorkDuration} structure of times the thread group took to
+ * complete its last task in nanoseconds breaking down into different components.
+ *
+ * The work period start timestamp, actual total duration and actual CPU duration must be
+ * positive.
+ *
+ * The actual GPU duration must be non-negative. If the actual GPU duration is 0, it means
+ * the actual GPU duration is not measured.
+ *
+ * @return 0 on success.
+ * EINVAL if session is nullptr or any duration is an invalid number.
+ * EPIPE if communication with the system service has failed.
+ */
+int APerformanceHint_reportActualWorkDuration2(
+ APerformanceHintSession* _Nonnull session,
+ AWorkDuration* _Nonnull workDuration) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Creates a new AWorkDuration. When the client finishes using {@link AWorkDuration}, it should
+ * call {@link AWorkDuration_release()} to destroy {@link AWorkDuration} and release all resources
+ * associated with it.
+ *
+ * @return AWorkDuration on success and nullptr otherwise.
+ */
+AWorkDuration* _Nonnull AWorkDuration_create() __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Destroys {@link AWorkDuration} and free all resources associated to it.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ */
+void AWorkDuration_release(AWorkDuration* _Nonnull WorkDuration) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the work period start timestamp in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ * @param workPeriodStartTimestampNanos The work period start timestamp in nanoseconds based on
+ * CLOCK_MONOTONIC about when the work starts, the timestamp must be positive.
+ */
+void AWorkDuration_setWorkPeriodStartTimestampNanos(AWorkDuration* _Nonnull aWorkDuration,
+ int64_t workPeriodStartTimestampNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the actual total work duration in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ * @param actualTotalDurationNanos The actual total work duration in nanoseconds, the number must be
+ * positive.
+ */
+void AWorkDuration_setActualTotalDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
+ int64_t actualTotalDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the actual CPU work duration in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}
+ * @param actualCpuDurationNanos The actual CPU work duration in nanoseconds, the number must be
+ * positive.
+ */
+void AWorkDuration_setActualCpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
+ int64_t actualCpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
+
+/**
+ * Sets the actual GPU work duration in nanoseconds.
+ *
+ * @param aWorkDuration The {@link AWorkDuration} created by calling {@link AWorkDuration_create()}.
+ * @param actualGpuDurationNanos The actual GPU work duration in nanoseconds, the number must be
+ * non-negative. If the actual GPU duration is 0, it means the actual GPU duration is
+ * measured.
+ */
+void AWorkDuration_setActualGpuDurationNanos(AWorkDuration* _Nonnull aWorkDuration,
+ int64_t actualGpuDurationNanos) __INTRODUCED_IN(__ANDROID_API_V__);
__END_DECLS
#endif // ANDROID_NATIVE_PERFORMANCE_HINT_H
+
+/** @} */
diff --git a/include/android/sensor.h b/include/android/sensor.h
index ba81bc8d8e..e3c1ccfc6c 100644
--- a/include/android/sensor.h
+++ b/include/android/sensor.h
@@ -29,6 +29,8 @@
#ifndef ANDROID_SENSOR_H
#define ANDROID_SENSOR_H
+#include <sys/cdefs.h>
+
/******************************************************************
*
* IMPORTANT NOTICE:
@@ -52,11 +54,13 @@
#include <math.h>
#include <stdint.h>
+// This file may also be built on glibc or on Windows/MacOS libc's, so no-op
+// and deprecated definitions are provided.
#if !defined(__INTRODUCED_IN)
#define __INTRODUCED_IN(__api_level) /* nothing */
#endif
#if !defined(__DEPRECATED_IN)
-#define __DEPRECATED_IN(__api_level) __attribute__((__deprecated__))
+#define __DEPRECATED_IN(__api_level, msg) __attribute__((__deprecated__(msg)))
#endif
#ifdef __cplusplus
@@ -606,10 +610,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 <a href="/reference/android/hardware/SensorEvent#timestamp">
@@ -649,7 +657,7 @@ typedef struct ASensorEvent {
uint32_t flags;
int32_t reserved1[3];
} ASensorEvent;
-// LINT.ThenChange (hardware/libhardware/include/hardware/sensors.h)
+// LINT.ThenChange(hardware/libhardware/include/hardware/sensors.h)
struct ASensorManager;
/**
@@ -740,7 +748,8 @@ typedef ASensorRef const* ASensorList;
* ASensorManager* sensorManager = ASensorManager_getInstance();
*
*/
-ASensorManager* ASensorManager_getInstance() __DEPRECATED_IN(26);
+ASensorManager* ASensorManager_getInstance()
+ __DEPRECATED_IN(26, "Use ASensorManager_getInstanceForPackage instead");
/**
* Get a reference to the sensor manager. ASensorManager is a singleton
diff --git a/include/android/sharedmem.h b/include/android/sharedmem.h
index e0a8045d41..1d513a6abe 100644
--- a/include/android/sharedmem.h
+++ b/include/android/sharedmem.h
@@ -53,27 +53,27 @@ extern "C" {
/**
* Create a shared memory region.
*
- * Create shared memory region and returns an file descriptor. The resulting file descriptor can be
- * mmap'ed to process memory space with PROT_READ | PROT_WRITE | PROT_EXEC. Access to shared memory
- * region can be restricted with {@link ASharedMemory_setProt}.
+ * Create a shared memory region and returns a file descriptor. The resulting file descriptor can be
+ * mapped into the process' memory using mmap(2) with `PROT_READ | PROT_WRITE | PROT_EXEC`.
+ * Access to shared memory regions can be restricted with {@link ASharedMemory_setProt}.
*
- * Use close() to release the shared memory region.
+ * Use close(2) to release the shared memory region.
*
* Use <a href="/reference/android/os/ParcelFileDescriptor">android.os.ParcelFileDescriptor</a>
* to pass the file descriptor to another process. File descriptors may also be sent to other
- * processes over a Unix domain socket with sendmsg and SCM_RIGHTS. See sendmsg(3) and
+ * processes over a Unix domain socket with sendmsg(2) and `SCM_RIGHTS`. See sendmsg(3) and
* cmsg(3) man pages for more information.
*
* If you intend to share this file descriptor with a child process after
- * calling exec(3), note that you will need to use fcntl(2) with FD_SETFD
- * to clear the FD_CLOEXEC flag for this to work on all versions of Android.
+ * calling exec(3), note that you will need to use fcntl(2) with `F_SETFD`
+ * to clear the `FD_CLOEXEC` flag for this to work on all versions of Android.
*
* Available since API level 26.
*
* \param name an optional name.
* \param size size of the shared memory region
* \return file descriptor that denotes the shared memory;
- * -1 and sets errno on failure, or -EINVAL if the error is that size was 0.
+ * -1 and sets `errno` on failure, or `-EINVAL` if the error is that size was 0.
*/
int ASharedMemory_create(const char *name, size_t size) __INTRODUCED_IN(26);
@@ -83,7 +83,7 @@ int ASharedMemory_create(const char *name, size_t size) __INTRODUCED_IN(26);
* Available since API level 26.
*
* \param fd file descriptor of the shared memory region
- * \return size in bytes; 0 if fd is not a valid shared memory file descriptor.
+ * \return size in bytes; 0 if `fd` is not a valid shared memory file descriptor.
*/
size_t ASharedMemory_getSize(int fd) __INTRODUCED_IN(26);
@@ -115,9 +115,9 @@ size_t ASharedMemory_getSize(int fd) __INTRODUCED_IN(26);
* Available since API level 26.
*
* \param fd file descriptor of the shared memory region.
- * \param prot any bitwise-or'ed combination of PROT_READ, PROT_WRITE, PROT_EXEC denoting
+ * \param prot any bitwise-or'ed combination of `PROT_READ`, `PROT_WRITE`, `PROT_EXEC` denoting
* updated access. Note access can only be removed, but not added back.
- * \return 0 for success, -1 and sets errno on failure.
+ * \return 0 for success, -1 and sets `errno` on failure.
*/
int ASharedMemory_setProt(int fd, int prot) __INTRODUCED_IN(26);
diff --git a/include/android/surface_control.h b/include/android/surface_control.h
index 6223ef7f82..14167e6467 100644
--- a/include/android/surface_control.h
+++ b/include/android/surface_control.h
@@ -54,18 +54,26 @@ 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)
- __INTRODUCED_IN(29);
+ASurfaceControl* _Nullable ASurfaceControl_createFromWindow(ANativeWindow* _Nonnull parent,
+ const char* _Nonnull debug_name)
+ __INTRODUCED_IN(29);
/**
* See ASurfaceControl_createFromWindow.
*
* Available since API level 29.
*/
-ASurfaceControl* ASurfaceControl_create(ASurfaceControl* parent, const char* debug_name)
- __INTRODUCED_IN(29);
+ASurfaceControl* _Nullable ASurfaceControl_create(ASurfaceControl* _Nonnull parent,
+ const char* _Nonnull debug_name)
+ __INTRODUCED_IN(29);
/**
* Acquires a reference on the given ASurfaceControl object. This prevents the object
@@ -75,7 +83,7 @@ ASurfaceControl* ASurfaceControl_create(ASurfaceControl* parent, const char* deb
*
* Available since API level 31.
*/
-void ASurfaceControl_acquire(ASurfaceControl* surface_control) __INTRODUCED_IN(31);
+void ASurfaceControl_acquire(ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(31);
/**
* Removes a reference that was previously acquired with one of the following functions:
@@ -86,7 +94,7 @@ void ASurfaceControl_acquire(ASurfaceControl* surface_control) __INTRODUCED_IN(3
*
* Available since API level 29.
*/
-void ASurfaceControl_release(ASurfaceControl* surface_control) __INTRODUCED_IN(29);
+void ASurfaceControl_release(ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(29);
struct ASurfaceTransaction;
@@ -102,14 +110,14 @@ typedef struct ASurfaceTransaction ASurfaceTransaction;
*
* Available since API level 29.
*/
-ASurfaceTransaction* ASurfaceTransaction_create() __INTRODUCED_IN(29);
+ASurfaceTransaction* _Nonnull ASurfaceTransaction_create() __INTRODUCED_IN(29);
/**
* Destroys the \a transaction object.
*
* Available since API level 29.
*/
-void ASurfaceTransaction_delete(ASurfaceTransaction* transaction) __INTRODUCED_IN(29);
+void ASurfaceTransaction_delete(ASurfaceTransaction* _Nullable transaction) __INTRODUCED_IN(29);
/**
* Applies the updates accumulated in \a transaction.
@@ -120,7 +128,7 @@ void ASurfaceTransaction_delete(ASurfaceTransaction* transaction) __INTRODUCED_I
*
* Available since API level 29.
*/
-void ASurfaceTransaction_apply(ASurfaceTransaction* transaction) __INTRODUCED_IN(29);
+void ASurfaceTransaction_apply(ASurfaceTransaction* _Nonnull transaction) __INTRODUCED_IN(29);
/**
* An opaque handle returned during a callback that can be used to query general stats and stats for
@@ -148,9 +156,9 @@ typedef struct ASurfaceTransactionStats ASurfaceTransactionStats;
*
* Available since API level 29.
*/
-typedef void (*ASurfaceTransaction_OnComplete)(void* context, ASurfaceTransactionStats* stats)
- __INTRODUCED_IN(29);
-
+typedef void (*ASurfaceTransaction_OnComplete)(void* _Null_unspecified context,
+ ASurfaceTransactionStats* _Nonnull stats)
+ __INTRODUCED_IN(29);
/**
* The ASurfaceTransaction_OnCommit callback is invoked when transaction is applied and the updates
@@ -177,8 +185,9 @@ typedef void (*ASurfaceTransaction_OnComplete)(void* context, ASurfaceTransactio
*
* Available since API level 31.
*/
-typedef void (*ASurfaceTransaction_OnCommit)(void* context, ASurfaceTransactionStats* stats)
- __INTRODUCED_IN(31);
+typedef void (*ASurfaceTransaction_OnCommit)(void* _Null_unspecified context,
+ ASurfaceTransactionStats* _Nonnull stats)
+ __INTRODUCED_IN(31);
/**
* Returns the timestamp of when the frame was latched by the framework. Once a frame is
@@ -186,8 +195,8 @@ typedef void (*ASurfaceTransaction_OnCommit)(void* context, ASurfaceTransactionS
*
* Available since API level 29.
*/
-int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* surface_transaction_stats)
- __INTRODUCED_IN(29);
+int64_t ASurfaceTransactionStats_getLatchTime(
+ ASurfaceTransactionStats* _Nonnull surface_transaction_stats) __INTRODUCED_IN(29);
/**
* Returns a sync fence that signals when the transaction has been presented.
@@ -198,8 +207,8 @@ int64_t ASurfaceTransactionStats_getLatchTime(ASurfaceTransactionStats* surface_
*
* Available since API level 29.
*/
-int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* surface_transaction_stats)
- __INTRODUCED_IN(29);
+int ASurfaceTransactionStats_getPresentFenceFd(
+ ASurfaceTransactionStats* _Nonnull surface_transaction_stats) __INTRODUCED_IN(29);
/**
* \a outASurfaceControls returns an array of ASurfaceControl pointers that were updated during the
@@ -211,18 +220,18 @@ int ASurfaceTransactionStats_getPresentFenceFd(ASurfaceTransactionStats* surface
*
* \a outASurfaceControlsSize returns the size of the ASurfaceControls array.
*/
-void ASurfaceTransactionStats_getASurfaceControls(ASurfaceTransactionStats* surface_transaction_stats,
- ASurfaceControl*** outASurfaceControls,
- size_t* outASurfaceControlsSize)
- __INTRODUCED_IN(29);
+void ASurfaceTransactionStats_getASurfaceControls(
+ ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
+ ASurfaceControl* _Nullable* _Nullable* _Nonnull outASurfaceControls,
+ size_t* _Nonnull outASurfaceControlsSize) __INTRODUCED_IN(29);
/**
* Releases the array of ASurfaceControls that were returned by
* ASurfaceTransactionStats_getASurfaceControls().
*
* Available since API level 29.
*/
-void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** surface_controls)
- __INTRODUCED_IN(29);
+void ASurfaceTransactionStats_releaseASurfaceControls(
+ ASurfaceControl* _Nonnull* _Nonnull surface_controls) __INTRODUCED_IN(29);
/**
* Returns the timestamp of when the CURRENT buffer was acquired. A buffer is considered
@@ -231,9 +240,9 @@ void ASurfaceTransactionStats_releaseASurfaceControls(ASurfaceControl** surface_
*
* Available since API level 29.
*/
-int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* surface_transaction_stats,
- ASurfaceControl* surface_control)
- __INTRODUCED_IN(29);
+int64_t ASurfaceTransactionStats_getAcquireTime(
+ ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
+ ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(29);
/**
* The returns the fence used to signal the release of the PREVIOUS buffer set on
@@ -258,9 +267,8 @@ int64_t ASurfaceTransactionStats_getAcquireTime(ASurfaceTransactionStats* surfac
* Available since API level 29.
*/
int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
- ASurfaceTransactionStats* surface_transaction_stats,
- ASurfaceControl* surface_control)
- __INTRODUCED_IN(29);
+ ASurfaceTransactionStats* _Nonnull surface_transaction_stats,
+ ASurfaceControl* _Nonnull surface_control) __INTRODUCED_IN(29);
/**
* Sets the callback that will be invoked when the updates from this transaction
@@ -269,8 +277,10 @@ int ASurfaceTransactionStats_getPreviousReleaseFenceFd(
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* transaction, void* context,
- ASurfaceTransaction_OnComplete func) __INTRODUCED_IN(29);
+void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* _Nonnull transaction,
+ void* _Null_unspecified context,
+ ASurfaceTransaction_OnComplete _Nonnull func)
+ __INTRODUCED_IN(29);
/**
* Sets the callback that will be invoked when the updates from this transaction are applied and are
@@ -279,8 +289,10 @@ void ASurfaceTransaction_setOnComplete(ASurfaceTransaction* transaction, void* c
*
* Available since API level 31.
*/
-void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* transaction, void* context,
- ASurfaceTransaction_OnCommit func) __INTRODUCED_IN(31);
+void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* _Nonnull transaction,
+ void* _Null_unspecified context,
+ ASurfaceTransaction_OnCommit _Nonnull func)
+ __INTRODUCED_IN(31);
/**
* Reparents the \a surface_control from its old parent to the \a new_parent surface control.
@@ -290,14 +302,14 @@ void ASurfaceTransaction_setOnCommit(ASurfaceTransaction* transaction, void* con
*
* Available since API level 29.
*/
-void ASurfaceTransaction_reparent(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, ASurfaceControl* new_parent)
- __INTRODUCED_IN(29);
+void ASurfaceTransaction_reparent(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ ASurfaceControl* _Nullable new_parent) __INTRODUCED_IN(29);
/**
* Parameter for ASurfaceTransaction_setVisibility().
*/
-enum {
+enum ASurfaceTransactionVisibility : int8_t {
ASURFACE_TRANSACTION_VISIBILITY_HIDE = 0,
ASURFACE_TRANSACTION_VISIBILITY_SHOW = 1,
};
@@ -308,9 +320,10 @@ enum {
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setVisibility(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, int8_t visibility)
- __INTRODUCED_IN(29);
+void ASurfaceTransaction_setVisibility(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ enum ASurfaceTransactionVisibility visibility)
+ __INTRODUCED_IN(29);
/**
* Updates the z order index for \a surface_control. Note that the z order for a surface
@@ -321,9 +334,9 @@ void ASurfaceTransaction_setVisibility(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setZOrder(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, int32_t z_order)
- __INTRODUCED_IN(29);
+void ASurfaceTransaction_setZOrder(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, int32_t z_order)
+ __INTRODUCED_IN(29);
/**
* Updates the AHardwareBuffer displayed for \a surface_control. If not -1, the
@@ -338,9 +351,10 @@ void ASurfaceTransaction_setZOrder(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setBuffer(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, AHardwareBuffer* buffer,
- int acquire_fence_fd = -1) __INTRODUCED_IN(29);
+void ASurfaceTransaction_setBuffer(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ AHardwareBuffer* _Nonnull buffer, int acquire_fence_fd)
+ __INTRODUCED_IN(29);
/**
* Updates the color for \a surface_control. This will make the background color for the
@@ -350,23 +364,23 @@ void ASurfaceTransaction_setBuffer(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setColor(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, float r, float g, float b,
- float alpha, ADataSpace dataspace)
- __INTRODUCED_IN(29);
+void ASurfaceTransaction_setColor(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, float r, float g,
+ float b, float alpha, enum ADataSpace dataspace)
+ __INTRODUCED_IN(29);
/**
* \param source The sub-rect within the buffer's content to be rendered inside the surface's area
* The surface's source rect is clipped by the bounds of its current buffer. The source rect's width
* and height must be > 0.
*
- * \param destination Specifies the rect in the parent's space where this surface will be drawn. The post
- * source rect bounds are scaled to fit the destination rect. The surface's destination rect is
+ * \param destination Specifies the rect in the parent's space where this surface will be drawn. The
+ * post source rect bounds are scaled to fit the destination rect. The surface's destination rect is
* clipped by the bounds of its parent. The destination rect's width and height must be > 0.
*
- * \param transform The transform applied after the source rect is applied to the buffer. This parameter
- * should be set to 0 for no transform. To specify a transfrom use the NATIVE_WINDOW_TRANSFORM_*
- * enum.
+ * \param transform The transform applied after the source rect is applied to the buffer. This
+ * parameter should be set to 0 for no transform. To specify a transfrom use the
+ * NATIVE_WINDOW_TRANSFORM_* enum.
*
* Available since API level 29.
*
@@ -375,10 +389,10 @@ void ASurfaceTransaction_setColor(ASurfaceTransaction* transaction,
* to set different properties at different times, instead of having to specify all the desired
* properties at once.
*/
-void ASurfaceTransaction_setGeometry(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, const ARect& source,
+void ASurfaceTransaction_setGeometry(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, const ARect& source,
const ARect& destination, int32_t transform)
- __INTRODUCED_IN(29);
+ __INTRODUCED_IN(29);
/**
* Bounds the surface and its children to the bounds specified. The crop and buffer size will be
@@ -389,9 +403,9 @@ void ASurfaceTransaction_setGeometry(ASurfaceTransaction* transaction,
*
* Available since API level 31.
*/
-void ASurfaceTransaction_setCrop(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, const ARect& crop)
- __INTRODUCED_IN(31);
+void ASurfaceTransaction_setCrop(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, const ARect& crop)
+ __INTRODUCED_IN(31);
/**
* Specifies the position in the parent's space where the surface will be drawn.
@@ -401,9 +415,9 @@ void ASurfaceTransaction_setCrop(ASurfaceTransaction* transaction,
*
* Available since API level 31.
*/
-void ASurfaceTransaction_setPosition(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, int32_t x, int32_t y)
- __INTRODUCED_IN(31);
+void ASurfaceTransaction_setPosition(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, int32_t x,
+ int32_t y) __INTRODUCED_IN(31);
/**
* \param transform The transform applied after the source rect is applied to the buffer. This
@@ -412,9 +426,9 @@ void ASurfaceTransaction_setPosition(ASurfaceTransaction* transaction,
*
* Available since API level 31.
*/
-void ASurfaceTransaction_setBufferTransform(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, int32_t transform)
- __INTRODUCED_IN(31);
+void ASurfaceTransaction_setBufferTransform(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ int32_t transform) __INTRODUCED_IN(31);
/**
* Sets an x and y scale of a surface with (0, 0) as the centerpoint of the scale.
@@ -424,13 +438,13 @@ void ASurfaceTransaction_setBufferTransform(ASurfaceTransaction* transaction,
*
* Available since API level 31.
*/
-void ASurfaceTransaction_setScale(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, float xScale, float yScale)
- __INTRODUCED_IN(31);
+void ASurfaceTransaction_setScale(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, float xScale,
+ float yScale) __INTRODUCED_IN(31);
/**
* Parameter for ASurfaceTransaction_setBufferTransparency().
*/
-enum {
+enum ASurfaceTransactionTransparency : int8_t {
ASURFACE_TRANSACTION_TRANSPARENCY_TRANSPARENT = 0,
ASURFACE_TRANSACTION_TRANSPARENCY_TRANSLUCENT = 1,
ASURFACE_TRANSACTION_TRANSPARENCY_OPAQUE = 2,
@@ -442,9 +456,9 @@ enum {
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control,
- int8_t transparency)
+void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ enum ASurfaceTransactionTransparency transparency)
__INTRODUCED_IN(29);
/**
@@ -453,9 +467,10 @@ void ASurfaceTransaction_setBufferTransparency(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, const ARect rects[],
- uint32_t count) __INTRODUCED_IN(29);
+void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ const ARect* _Nullable rects, uint32_t count)
+ __INTRODUCED_IN(29);
/**
* Specifies a desiredPresentTime for the transaction. The framework will try to present
@@ -469,7 +484,7 @@ void ASurfaceTransaction_setDamageRegion(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* transaction,
+void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* _Nonnull transaction,
int64_t desiredPresentTime) __INTRODUCED_IN(29);
/**
@@ -479,8 +494,8 @@ void ASurfaceTransaction_setDesiredPresentTime(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, float alpha)
+void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, float alpha)
__INTRODUCED_IN(29);
/**
@@ -490,9 +505,9 @@ void ASurfaceTransaction_setBufferAlpha(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setBufferDataSpace(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, ADataSpace data_space)
- __INTRODUCED_IN(29);
+void ASurfaceTransaction_setBufferDataSpace(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ enum ADataSpace data_space) __INTRODUCED_IN(29);
/**
* SMPTE ST 2086 "Mastering Display Color Volume" static metadata
@@ -502,9 +517,9 @@ void ASurfaceTransaction_setBufferDataSpace(ASurfaceTransaction* transaction,
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control,
- struct AHdrMetadata_smpte2086* metadata)
+void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ struct AHdrMetadata_smpte2086* _Nullable metadata)
__INTRODUCED_IN(29);
/**
@@ -515,12 +530,59 @@ void ASurfaceTransaction_setHdrMetadata_smpte2086(ASurfaceTransaction* transacti
*
* Available since API level 29.
*/
-void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control,
- struct AHdrMetadata_cta861_3* metadata)
+void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ struct AHdrMetadata_cta861_3* _Nullable 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* _Nonnull transaction,
+ ASurfaceControl* _Nonnull 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).
*
@@ -528,8 +590,8 @@ void ASurfaceTransaction_setHdrMetadata_cta861_3(ASurfaceTransaction* transactio
*
* Available since API level 30.
*/
-void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, float frameRate,
+void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control, float frameRate,
int8_t compatibility) __INTRODUCED_IN(30);
/**
@@ -545,6 +607,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 <em>not</em> need to
@@ -562,10 +626,36 @@ void ASurfaceTransaction_setFrameRate(ASurfaceTransaction* transaction,
*
* Available since API level 31.
*/
-void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control, float frameRate,
- int8_t compatibility, int8_t changeFrameRateStrategy)
- __INTRODUCED_IN(31);
+void ASurfaceTransaction_setFrameRateWithChangeStrategy(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ float frameRate, 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* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control)
+ __INTRODUCED_IN(__ANDROID_API_U__);
/**
* Indicate whether to enable backpressure for buffer submission to a given SurfaceControl.
@@ -587,14 +677,15 @@ 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.
*/
-void ASurfaceTransaction_setEnableBackPressure(ASurfaceTransaction* transaction,
- ASurfaceControl* surface_control,
- bool enableBackPressure)
- __INTRODUCED_IN(31);
+void ASurfaceTransaction_setEnableBackPressure(ASurfaceTransaction* _Nonnull transaction,
+ ASurfaceControl* _Nonnull surface_control,
+ bool enableBackPressure) __INTRODUCED_IN(31);
/**
* Sets the frame timeline to use in SurfaceFlinger.
@@ -608,11 +699,13 @@ 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.
*/
-void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* transaction,
+void ASurfaceTransaction_setFrameTimeline(ASurfaceTransaction* _Nonnull transaction,
AVsyncId vsyncId) __INTRODUCED_IN(33);
__END_DECLS
diff --git a/include/android/surface_control_jni.h b/include/android/surface_control_jni.h
new file mode 100644
index 0000000000..840f6e724b
--- /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 <jni.h>
+#include <sys/cdefs.h>
+
+#include <android/surface_control.h>
+
+__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 b0bbb954a9..94484eaf54 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/android/thermal.h b/include/android/thermal.h
index 32580badc0..0b57e9376d 100644
--- a/include/android/thermal.h
+++ b/include/android/thermal.h
@@ -111,7 +111,7 @@ typedef struct AThermalManager AThermalManager;
* It's passed the updated thermal status as parameter, as well as the
* pointer provided by the client that registered a callback.
*/
-typedef void (*AThermal_StatusCallback)(void *data, AThermalStatus status);
+typedef void (*AThermal_StatusCallback)(void* data, AThermalStatus status);
/**
* Acquire an instance of the thermal manager. This must be freed using
@@ -188,13 +188,13 @@ int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
* Note that this only attempts to track the headroom of slow-moving sensors, such as
* the skin temperature sensor. This means that there is no benefit to calling this function
* more frequently than about once per second, and attempted to call significantly
- * more frequently may result in the function returning {@code NaN}.
+ * more frequently may result in the function returning `NaN`.
*
* In addition, in order to be able to provide an accurate forecast, the system does
* not attempt to forecast until it has multiple temperature samples from which to
* extrapolate. This should only take a few seconds from the time of the first call,
* but during this time, no forecasting will occur, and the current headroom will be
- * returned regardless of the value of {@code forecastSeconds}.
+ * returned regardless of the value of `forecastSeconds`.
*
* The value returned is a non-negative float that represents how much of the thermal envelope
* is in use (or is forecasted to be in use). A value of 1.0 indicates that the device is
@@ -222,6 +222,70 @@ int AThermal_unregisterThermalStatusListener(AThermalManager *manager,
float AThermal_getThermalHeadroom(AThermalManager *manager,
int forecastSeconds) __INTRODUCED_IN(31);
+/**
+ * This struct defines an instance of headroom threshold value and its status.
+ * <p>
+ * The value should be monotonically non-decreasing as the thermal status increases.
+ * For {@link ATHERMAL_STATUS_SEVERE}, its headroom threshold is guaranteed to
+ * be 1.0f. For status below severe status, the value should be lower or equal
+ * to 1.0f, and for status above severe, the value should be larger or equal to 1.0f.
+ * <p>
+ * Also see {@link AThermal_getThermalHeadroom} for explanation on headroom, and
+ * {@link AThermal_getThermalHeadroomThresholds} for how to use this.
+ */
+struct AThermalHeadroomThreshold {
+ float headroom;
+ AThermalStatus thermalStatus;
+};
+
+/**
+ * Gets the thermal headroom thresholds for all available thermal status.
+ *
+ * A thermal status will only exist in output if the device manufacturer has the
+ * corresponding threshold defined for at least one of its slow-moving skin temperature
+ * sensors. If it's set, one should also expect to get it from
+ * {@link #AThermal_getCurrentThermalStatus} or {@link AThermal_StatusCallback}.
+ * <p>
+ * The headroom threshold is used to interpret the possible thermal throttling status based on
+ * the headroom prediction. For example, if the headroom threshold for
+ * {@link ATHERMAL_STATUS_LIGHT} is 0.7, and a headroom prediction in 10s returns 0.75
+ * (or {@code AThermal_getThermalHeadroom(10)=0.75}), one can expect that in 10 seconds the system
+ * could be in lightly throttled state if the workload remains the same. The app can consider
+ * taking actions according to the nearest throttling status the difference between the headroom and
+ * the threshold.
+ * <p>
+ * For new devices it's guaranteed to have a single sensor, but for older devices with multiple
+ * sensors reporting different threshold values, the minimum threshold is taken to be conservative
+ * on predictions. Thus, when reading real-time headroom, it's not guaranteed that a real-time value
+ * of 0.75 (or {@code AThermal_getThermalHeadroom(0)}=0.75) exceeding the threshold of 0.7 above
+ * will always come with lightly throttled state
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_LIGHT}) but it can be lower
+ * (or {@code AThermal_getCurrentThermalStatus()=ATHERMAL_STATUS_NONE}).
+ * While it's always guaranteed that the device won't be throttled heavier than the unmet
+ * threshold's state, so a real-time headroom of 0.75 will never come with
+ * {@link #ATHERMAL_STATUS_MODERATE} but always lower, and 0.65 will never come with
+ * {@link ATHERMAL_STATUS_LIGHT} but {@link #ATHERMAL_STATUS_NONE}.
+ * <p>
+ * The returned list of thresholds is cached on first successful query and owned by the thermal
+ * manager, which will not change between calls to this function. The caller should only need to
+ * free the manager with {@link AThermal_releaseManager}.
+ *
+ * @param manager The manager instance to use.
+ * Acquired via {@link AThermal_acquireManager}.
+ * @param outThresholds non-null output pointer to null AThermalHeadroomThreshold pointer, which
+ * will be set to the cached array of thresholds if thermal thresholds are supported
+ * by the system or device, otherwise nullptr or unmodified.
+ * @param size non-null output pointer whose value will be set to the size of the threshold array
+ * or 0 if it's not supported.
+ * @return 0 on success
+ * EINVAL if outThresholds or size_t is nullptr, or *outThresholds is not nullptr.
+ * EPIPE if communication with the system service has failed.
+ * ENOSYS if the feature is disabled by the current system.
+ */
+int AThermal_getThermalHeadroomThresholds(AThermalManager* manager,
+ const AThermalHeadroomThreshold ** outThresholds,
+ size_t* size) __INTRODUCED_IN(35);
+
#ifdef __cplusplus
}
#endif
diff --git a/include/audiomanager/AudioManager.h b/include/audiomanager/AudioManager.h
index 4aa2f60d45..43048dbff0 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<int>(*this) == static_cast<int>(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 426e10c9bc..769670ea99 100644
--- a/include/audiomanager/IAudioManager.h
+++ b/include/audiomanager/IAudioManager.h
@@ -17,8 +17,10 @@
#ifndef ANDROID_IAUDIOMANAGER_H
#define ANDROID_IAUDIOMANAGER_H
+#include <audiomanager/AudioManager.h>
#include <utils/Errors.h>
#include <binder/IInterface.h>
+#include <binder/PersistableBundle.h>
#include <hardware/power.h>
#include <system/audio.h>
@@ -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<IBinder>& 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<os::PersistableBundle>& extras) = 0;
};
// ----------------------------------------------------------------------------
diff --git a/include/binder/unique_fd.h b/include/binder/unique_fd.h
new file mode 120000
index 0000000000..433c968151
--- /dev/null
+++ b/include/binder/unique_fd.h
@@ -0,0 +1 @@
+../../libs/binder/include/binder/unique_fd.h \ No newline at end of file
diff --git a/cmds/surfacereplayer/OWNERS b/include/ftl/OWNERS
index 32bcc83468..3f6129226a 100644
--- a/cmds/surfacereplayer/OWNERS
+++ b/include/ftl/OWNERS
@@ -1 +1 @@
-include platform/frameworks/native:/services/surfaceflinger/OWNERS
+include platform/frameworks/native:/services/surfaceflinger/OWNERS \ No newline at end of file
diff --git a/include/ftl/algorithm.h b/include/ftl/algorithm.h
new file mode 100644
index 0000000000..c0f67683ab
--- /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 <algorithm>
+#include <functional>
+#include <utility>
+
+#include <ftl/optional.h>
+
+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 <typename Container, typename Predicate, typename V = typename Container::value_type>
+constexpr auto find_if(const Container& container, Predicate&& predicate)
+ -> Optional<std::reference_wrapper<const V>> {
+ const auto it = std::find_if(std::cbegin(container), std::cend(container),
+ std::forward<Predicate>(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<int, ftl::StaticVector<std::string_view, 3>>(
+// 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<Map>));
+//
+// const auto opt = ftl::find_if(map, [](const auto& pair) {
+// return pair.second.size() == 1;
+// }).transform(ftl::to_mapped_ref<Map>);
+//
+// assert(opt);
+// assert(opt->get() == ftl::StaticVector("tiramisu"sv));
+//
+template <typename Map, typename Pair = typename Map::value_type,
+ typename Key = typename Map::key_type>
+constexpr auto to_key(const Pair& pair) -> Key {
+ return pair.first;
+}
+
+template <typename Map, typename Pair = typename Map::value_type,
+ typename Mapped = typename Map::mapped_type>
+constexpr auto to_mapped_ref(const Pair& pair) -> std::reference_wrapper<const Mapped> {
+ return std::cref(pair.second);
+}
+
+// Combinator for ftl::Optional<T>::or_else when T is std::reference_wrapper<const V>. Given a
+// lambda argument that returns a `constexpr` value, ftl::static_ref<T> 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<std::string_view>([] { 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<Map>)
+// .or_else(ftl::static_ref<std::string_view>([] { return "snow cone"sv; }))
+// ->get());
+//
+template <typename T, typename F>
+constexpr auto static_ref(F&& f) {
+ return [f = std::forward<F>(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 ded48f7c8c..e0774d39f3 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 8ce949ef05..726ba0297e 100644
--- a/include/ftl/details/concat.h
+++ b/include/ftl/details/concat.h
@@ -19,6 +19,7 @@
#include <functional>
#include <string_view>
+#include <ftl/details/type_traits.h>
#include <ftl/string.h>
namespace android::ftl::details {
@@ -26,16 +27,42 @@ namespace android::ftl::details {
template <typename T, typename = void>
struct StaticString;
+// Booleans.
template <typename T>
-struct StaticString<T, std::enable_if_t<std::is_integral_v<T>>> {
- static constexpr std::size_t N = to_chars_length_v<T>;
+struct StaticString<T, std::enable_if_t<is_bool_v<T>>> {
+ 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<T> buffer;
const std::string_view view;
};
+// Characters.
+template <typename T>
+struct StaticString<T, std::enable_if_t<is_char_v<T>>> {
+ 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 <typename T>
+struct StaticString<
+ T, std::enable_if_t<std::is_integral_v<remove_cvref_t<T>> && !is_bool_v<T> && !is_char_v<T>>> {
+ using U = remove_cvref_t<T>;
+ static constexpr std::size_t N = to_chars_length_v<U>;
+
+ // TODO: Mark this and to_chars as `constexpr` in C++23.
+ explicit StaticString(U v) : view(to_chars(buffer, v)) {}
+
+ to_chars_buffer_t<U> buffer;
+ const std::string_view view;
+};
+
+// Character arrays.
template <std::size_t M>
struct StaticString<const char (&)[M], void> {
static constexpr std::size_t N = M - 1;
@@ -50,6 +77,7 @@ struct Truncated {
std::string_view view;
};
+// Strings with constrained length.
template <std::size_t M>
struct StaticString<Truncated<M>, void> {
static constexpr std::size_t N = M;
diff --git a/include/ftl/details/function.h b/include/ftl/details/function.h
new file mode 100644
index 0000000000..35c5a8b302
--- /dev/null
+++ b/include/ftl/details/function.h
@@ -0,0 +1,135 @@
+/*
+ * 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 <array>
+#include <cstddef>
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace android::ftl::details {
+
+// The maximum allowed value for the template argument `N` in
+// `ftl::Function<F, N>`.
+constexpr size_t kFunctionMaximumN = 14;
+
+// Converts a member function pointer type `Ret(Class::*)(Args...)` to an equivalent non-member
+// function type `Ret(Args...)`.
+
+template <typename>
+struct remove_member_function_pointer;
+
+template <typename Class, typename Ret, typename... Args>
+struct remove_member_function_pointer<Ret (Class::*)(Args...)> {
+ using type = Ret(Args...);
+};
+
+template <typename Class, typename Ret, typename... Args>
+struct remove_member_function_pointer<Ret (Class::*)(Args...) const> {
+ using type = Ret(Args...);
+};
+
+template <auto MemberFunction>
+using remove_member_function_pointer_t =
+ typename remove_member_function_pointer<decltype(MemberFunction)>::type;
+
+// Helper functions for binding to the supported targets.
+
+template <typename Ret, typename... Args>
+auto bind_opaque_no_op() -> Ret (*)(void*, Args...) {
+ return [](void*, Args...) -> Ret {
+ if constexpr (!std::is_void_v<Ret>) {
+ return Ret{};
+ }
+ };
+}
+
+template <typename F, typename Ret, typename... Args>
+auto bind_opaque_function_object(const F&) -> Ret (*)(void*, Args...) {
+ return [](void* opaque, Args... args) -> Ret {
+ return std::invoke(*static_cast<F*>(opaque), std::forward<Args>(args)...);
+ };
+}
+
+template <auto MemberFunction, typename Class, typename Ret, typename... Args>
+auto bind_member_function(Class* instance, Ret (*)(Args...) = nullptr) {
+ return [instance](Args... args) -> Ret {
+ return std::invoke(MemberFunction, instance, std::forward<Args>(args)...);
+ };
+}
+
+template <auto FreeFunction, typename Ret, typename... Args>
+auto bind_free_function(Ret (*)(Args...) = nullptr) {
+ return [](Args... args) -> Ret { return std::invoke(FreeFunction, std::forward<Args>(args)...); };
+}
+
+// Traits class for the opaque storage used by Function.
+
+template <std::size_t N>
+struct function_opaque_storage {
+ // The actual type used for the opaque storage. An `N` of zero specifies the minimum useful size,
+ // which allows a lambda with zero or one capture args.
+ using type = std::array<std::intptr_t, N + 1>;
+
+ template <typename S>
+ static constexpr bool require_trivially_copyable = std::is_trivially_copyable_v<S>;
+
+ template <typename S>
+ static constexpr bool require_trivially_destructible = std::is_trivially_destructible_v<S>;
+
+ template <typename S>
+ static constexpr bool require_will_fit_in_opaque_storage = sizeof(S) <= sizeof(type);
+
+ template <typename S>
+ static constexpr bool require_alignment_compatible =
+ std::alignment_of_v<S> <= std::alignment_of_v<type>;
+
+ // Copies `src` into the opaque storage, and returns that storage.
+ template <typename S>
+ static type opaque_copy(const S& src) {
+ // TODO: Replace with C++20 concepts/constraints which can give more details.
+ static_assert(require_trivially_copyable<S>,
+ "ftl::Function can only store lambdas that capture trivially copyable data.");
+ static_assert(
+ require_trivially_destructible<S>,
+ "ftl::Function can only store lambdas that capture trivially destructible data.");
+ static_assert(require_will_fit_in_opaque_storage<S>,
+ "ftl::Function has limited storage for lambda captured state. Maybe you need to "
+ "increase N?");
+ static_assert(require_alignment_compatible<S>);
+
+ type opaque;
+ std::memcpy(opaque.data(), &src, sizeof(S));
+ return opaque;
+ }
+};
+
+// Traits class to help determine the template parameters to use for a ftl::Function, given a
+// function object.
+
+template <typename F, typename = decltype(&F::operator())>
+struct function_traits {
+ // The function type `F` with which to instantiate the `Function<F, N>` template.
+ using type = remove_member_function_pointer_t<&F::operator()>;
+
+ // The (minimum) size `N` with which to instantiate the `Function<F, N>` template.
+ static constexpr std::size_t size =
+ (std::max(sizeof(std::intptr_t), sizeof(F)) - 1) / sizeof(std::intptr_t);
+};
+
+} // namespace android::ftl::details
diff --git a/include/ftl/details/match.h b/include/ftl/details/match.h
new file mode 100644
index 0000000000..51b99d2f13
--- /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 <type_traits>
+#include <variant>
+
+namespace android::ftl::details {
+
+template <typename... Ms>
+struct Matcher : Ms... {
+ using Ms::operator()...;
+};
+
+// Deduction guide.
+template <typename... Ms>
+Matcher(Ms...) -> Matcher<Ms...>;
+
+template <typename Matcher, typename... Ts>
+constexpr bool is_exhaustive_match_v = (std::is_invocable_v<Matcher, Ts> && ...);
+
+template <typename...>
+struct Match;
+
+template <typename T, typename U, typename... Ts>
+struct Match<T, U, Ts...> {
+ template <typename Variant, typename Matcher>
+ static decltype(auto) match(Variant& variant, const Matcher& matcher) {
+ if (auto* const ptr = std::get_if<T>(&variant)) {
+ return matcher(*ptr);
+ } else {
+ return Match<U, Ts...>::match(variant, matcher);
+ }
+ }
+};
+
+template <typename T>
+struct Match<T> {
+ template <typename Variant, typename Matcher>
+ static decltype(auto) match(Variant& variant, const Matcher& matcher) {
+ return matcher(std::get<T>(variant));
+ }
+};
+
+} // namespace android::ftl::details
diff --git a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h b/include/ftl/details/mixins.h
index c80fde6ead..9ab9e083ae 100644
--- a/services/surfaceflinger/CompositionEngine/tests/TestUtils.h
+++ b/include/ftl/details/mixins.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 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.
@@ -13,19 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
#pragma once
-#include <future>
+namespace android::ftl::details {
+
+template <typename Self, template <typename> class>
+class Mixin {
+ protected:
+ constexpr Self& self() { return *static_cast<Self*>(this); }
+ constexpr const Self& self() const { return *static_cast<const Self*>(this); }
-namespace android::compositionengine {
-namespace {
+ constexpr auto& mut() { return self().value_; }
+};
-template <class T>
-std::future<T> futureOf(T obj) {
- std::promise<T> resultPromise;
- std::future<T> resultFuture = resultPromise.get_future();
- resultPromise.set_value(std::move(obj));
- return resultFuture;
-}
-} // namespace
-} // namespace android::compositionengine
+} // namespace android::ftl::details
diff --git a/include/ftl/details/optional.h b/include/ftl/details/optional.h
new file mode 100644
index 0000000000..e45c1f5e23
--- /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 <functional>
+#include <optional>
+
+#include <ftl/details/type_traits.h>
+
+namespace android::ftl {
+
+template <typename>
+struct Optional;
+
+namespace details {
+
+template <typename>
+struct is_optional : std::false_type {};
+
+template <typename T>
+struct is_optional<std::optional<T>> : std::true_type {};
+
+template <typename T>
+struct is_optional<Optional<T>> : std::true_type {};
+
+template <typename F, typename T>
+struct transform_result {
+ using type = Optional<std::remove_cv_t<std::invoke_result_t<F, T>>>;
+};
+
+template <typename F, typename T>
+using transform_result_t = typename transform_result<F, T>::type;
+
+template <typename F, typename T>
+struct and_then_result {
+ using type = remove_cvref_t<std::invoke_result_t<F, T>>;
+ static_assert(is_optional<type>{}, "and_then function must return an optional");
+};
+
+template <typename F, typename T>
+using and_then_result_t = typename and_then_result<F, T>::type;
+
+template <typename F, typename T>
+struct or_else_result {
+ using type = remove_cvref_t<std::invoke_result_t<F>>;
+ static_assert(std::is_same_v<type, std::optional<T>> || std::is_same_v<type, Optional<T>>,
+ "or_else function must return an optional T");
+};
+
+template <typename F, typename T>
+using or_else_result_t = typename or_else_result<F, T>::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 0000000000..47bebc5114
--- /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 <type_traits>
+
+namespace android::ftl::details {
+
+// TODO: Replace with std::remove_cvref_t in C++20.
+template <typename U>
+using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
+
+template <typename T>
+constexpr bool is_bool_v = std::is_same_v<remove_cvref_t<T>, bool>;
+
+template <typename T>
+constexpr bool is_char_v = std::is_same_v<remove_cvref_t<T>, char>;
+
+} // namespace android::ftl::details
diff --git a/include/ftl/enum.h b/include/ftl/enum.h
index 82af1d6cf8..2c86e2e4c9 100644
--- a/include/ftl/enum.h
+++ b/include/ftl/enum.h
@@ -25,12 +25,12 @@
#include <ftl/string.h>
-// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view> by parsing the
-// compiler-generated string literal for the signature of this function. The function is defined in
-// the global namespace with a short name and inferred return type to reduce bloat in the read-only
-// data segment.
-template <typename E, E V>
-constexpr auto ftl_enum() {
+// Returns the name of enumerator E::V and optionally the class (i.e. "E::V" or "V") as
+// std::optional<std::string_view> by parsing the compiler-generated string literal for the
+// signature of this function. The function is defined in the global namespace with a short name
+// and inferred return type to reduce bloat in the read-only data segment.
+template <bool S, typename E, E V>
+constexpr auto ftl_enum_builder() {
static_assert(std::is_enum_v<E>);
using R = std::optional<std::string_view>;
@@ -58,7 +58,9 @@ constexpr auto ftl_enum() {
// V = android::test::Enum::kValue
//
view = view.substr(value_begin);
- const auto name_begin = view.rfind("::"sv);
+ const auto pos = S ? view.rfind("::"sv) - 2 : view.npos;
+
+ const auto name_begin = view.rfind("::"sv, pos);
if (name_begin == view.npos) return R{};
// Chop off the leading "::".
@@ -68,6 +70,18 @@ constexpr auto ftl_enum() {
return name.find(')') == view.npos ? R{name} : R{};
}
+// Returns the name of enumerator E::V (i.e. "V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum() {
+ return ftl_enum_builder<false, E, V>();
+}
+
+// Returns the name of enumerator and class E::V (i.e. "E::V") as std::optional<std::string_view>
+template <typename E, E V>
+constexpr auto ftl_enum_full() {
+ return ftl_enum_builder<true, E, V>();
+}
+
namespace android::ftl {
// Trait for determining whether a type is specifically a scoped enum or not. By definition, a
@@ -92,7 +106,7 @@ inline constexpr bool is_scoped_enum_v = is_scoped_enum<T>::value;
// enum class E { A, B, C };
// static_assert(ftl::to_underlying(E::B) == 1);
//
-template <typename E>
+template <typename E, typename = std::enable_if_t<std::is_enum_v<E>>>
constexpr auto to_underlying(E v) {
return static_cast<std::underlying_type_t<E>>(v);
}
@@ -191,6 +205,11 @@ struct EnumName {
static constexpr auto value = ftl_enum<decltype(V), V>();
};
+template <auto V>
+struct EnumNameFull {
+ static constexpr auto value = ftl_enum_full<decltype(V), V>();
+};
+
template <auto I>
struct FlagName {
using E = decltype(I);
@@ -230,6 +249,18 @@ constexpr std::string_view enum_name() {
return *kName;
}
+// Returns a stringified enumerator with class at compile time.
+//
+// enum class E { A, B, C };
+// static_assert(ftl::enum_name<E::B>() == "E::B");
+//
+template <auto V>
+constexpr std::string_view enum_name_full() {
+ constexpr auto kName = ftl_enum_full<decltype(V), V>();
+ static_assert(kName, "Unknown enumerator");
+ return *kName;
+}
+
// Returns a stringified enumerator, possibly at compile time.
//
// enum class E { A, B, C, F = 5, ftl_last = F };
@@ -249,6 +280,25 @@ constexpr std::optional<std::string_view> enum_name(E v) {
return kRange.values[value - kBegin];
}
+// Returns a stringified enumerator with class, possibly at compile time.
+//
+// enum class E { A, B, C, F = 5, ftl_last = F };
+//
+// static_assert(ftl::enum_name(E::C).value_or("?") == "E::C");
+// static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+//
+template <typename E>
+constexpr std::optional<std::string_view> enum_name_full(E v) {
+ const auto value = to_underlying(v);
+
+ constexpr auto kBegin = to_underlying(enum_begin_v<E>);
+ constexpr auto kLast = to_underlying(enum_last_v<E>);
+ if (value < kBegin || value > kLast) return {};
+
+ constexpr auto kRange = details::EnumRange<E, details::EnumNameFull>{};
+ return kRange.values[value - kBegin];
+}
+
// Returns a stringified flag enumerator, possibly at compile time.
//
// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
@@ -282,6 +332,21 @@ inline std::string enum_string(E v) {
return to_string(to_underlying(v));
}
+// Returns a stringified enumerator with class, or its integral value if not named.
+//
+// enum class E { A, B, C, F = 5, ftl_last = F };
+//
+// assert(ftl::enum_string(E::C) == "E::C");
+// assert(ftl::enum_string(E{3}) == "3");
+//
+template <typename E>
+inline std::string enum_string_full(E v) {
+ if (const auto name = enum_name_full(v)) {
+ return std::string(*name);
+ }
+ return to_string(to_underlying(v));
+}
+
// Returns a stringified flag enumerator, or its integral value if not named.
//
// enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
diff --git a/include/ftl/flags.h b/include/ftl/flags.h
index 70aaa0e6dd..dbe3148fc5 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> f) const { return (mFlags & f.mFlags) != 0; }
+ bool any(Flags<F> f = ~Flags<F>()) const { return (mFlags & f.mFlags) != 0; }
/* Tests whether all of the given flags are set */
bool all(Flags<F> f) const { return (mFlags & f.mFlags) == f.mFlags; }
- Flags<F> operator|(Flags<F> rhs) const { return static_cast<F>(mFlags | rhs.mFlags); }
+ constexpr Flags<F> operator|(Flags<F> rhs) const { return static_cast<F>(mFlags | rhs.mFlags); }
Flags<F>& operator|=(Flags<F> rhs) {
mFlags = mFlags | rhs.mFlags;
return *this;
@@ -217,7 +217,7 @@ inline Flags<F> operator~(F f) {
}
template <typename F, typename = std::enable_if_t<is_scoped_enum_v<F>>>
-Flags<F> operator|(F lhs, F rhs) {
+constexpr Flags<F> operator|(F lhs, F rhs) {
return static_cast<F>(to_underlying(lhs) | to_underlying(rhs));
}
diff --git a/include/ftl/function.h b/include/ftl/function.h
new file mode 100644
index 0000000000..3538ca4eae
--- /dev/null
+++ b/include/ftl/function.h
@@ -0,0 +1,297 @@
+/*
+ * 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 <cstddef>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include <ftl/details/function.h>
+
+namespace android::ftl {
+
+// ftl::Function<F, N> is a container for function object, and can mostly be used in place of
+// std::function<F>.
+//
+// Unlike std::function<F>, a ftl::Function<F, N>:
+//
+// * Uses a static amount of memory (controlled by N), and never any dynamic allocation.
+// * Satisfies the std::is_trivially_copyable<> trait.
+// * Satisfies the std::is_trivially_destructible<> trait.
+//
+// However those same limits are also required from the contained function object in turn.
+//
+// The size of a ftl::Function<F, N> is guaranteed to be:
+//
+// sizeof(std::intptr_t) * (N + 2)
+//
+// A ftl::Function<F, N> can always be implicitly converted to a larger size ftl::Function<F, M>.
+// Trying to convert the other way leads to a compilation error.
+//
+// A default-constructed ftl::Function is in an empty state. The operator bool() overload returns
+// false in this state. It is undefined behavior to attempt to invoke the function in this state.
+//
+// The ftl::Function<F, N> can also be constructed or assigned from ftl::no_op. This sets up the
+// ftl::Function to be non-empty, with a function that when called does nothing except
+// default-constructs a return value.
+//
+// The ftl::make_function() helpers construct a ftl::Function<F, N>, including deducing the
+// values of F and N from the arguments it is given.
+//
+// The static ftl::Function<F, N>::make() helpers construct a ftl::Function<F, N> without that
+// deduction, and also allow for implicit argument conversion if the target being called needs them.
+//
+// The construction helpers allow any of the following types of functions to be stored:
+//
+// * Any SMALL function object (as defined by the C++ Standard), such as a lambda with a small
+// capture, or other "functor". The requirements are:
+//
+// 1) The function object must be trivial to destroy (in fact, the destructor will never
+// actually be called once copied to the internal storage).
+// 2) The function object must be trivial to copy (the raw bytes will be copied as the
+// ftl::Function<F, N> is copied/moved).
+// 3) The size of the function object cannot be larger than sizeof(std::intptr_t) * (N + 1),
+// and it cannot require stricter alignment than alignof(std::intptr_t).
+//
+// With the default of N=0, a lambda can only capture a single pointer-sized argument. This is
+// enough to capture `this`, which is why N=0 is the default.
+//
+// * A member function, with the address passed as the template value argument to the construction
+// helper function, along with the instance pointer needed to invoke it passed as an ordinary
+// argument.
+//
+// ftl::make_function<&Class::member_function>(this);
+//
+// Note that the indicated member function will be invoked non-virtually. If you need it to be
+// invoked virtually, you should invoke it yourself with a small lambda like so:
+//
+// ftl::function([this] { virtual_member_function(); });
+//
+// * An ordinary function ("free function"), with the address of the function passed as a template
+// value argument.
+//
+// ftl::make_function<&std::atoi>();
+//
+// As with the member function helper, as the function is known at compile time, it will be called
+// directly.
+//
+// Example usage:
+//
+// class MyClass {
+// public:
+// void on_event() const {}
+// int on_string(int*, std::string_view) { return 1; }
+//
+// auto get_function() {
+// return ftl::function([this] { on_event(); });
+// }
+// } cls;
+//
+// // A function container with no arguments, and returning no value.
+// ftl::Function<void()> f;
+//
+// // Construct a ftl::Function containing a small lambda.
+// f = cls.get_function();
+//
+// // Construct a ftl::Function that calls `cls.on_event()`.
+// f = ftl::function<&MyClass::on_event>(&cls);
+//
+// // Create a do-nothing function.
+// f = ftl::no_op;
+//
+// // Invoke the contained function.
+// f();
+//
+// // Also invokes it.
+// std::invoke(f);
+//
+// // Create a typedef to give a more meaningful name and bound the size.
+// using MyFunction = ftl::Function<int(std::string_view), 2>;
+// int* ptr = nullptr;
+// auto f1 = MyFunction::make_function(
+// [cls = &cls, ptr](std::string_view sv) {
+// return cls->on_string(ptr, sv);
+// });
+// int r = f1("abc"sv);
+//
+// // Returns a default-constructed int (0).
+// f1 = ftl::no_op;
+// r = f1("abc"sv);
+// assert(r == 0);
+
+template <typename F, std::size_t N = 0>
+class Function;
+
+// Used to construct a Function that does nothing.
+struct NoOpTag {};
+
+constexpr NoOpTag no_op;
+
+// Detects that a type is a `ftl::Function<F, N>` regardless of what `F` and `N` are.
+template <typename>
+struct is_function : public std::false_type {};
+
+template <typename F, std::size_t N>
+struct is_function<Function<F, N>> : public std::true_type {};
+
+template <typename T>
+constexpr bool is_function_v = is_function<T>::value;
+
+template <typename Ret, typename... Args, std::size_t N>
+class Function<Ret(Args...), N> final {
+ // Enforce a valid size, with an arbitrary maximum allowed size for the container of
+ // sizeof(std::intptr_t) * 16, though that maximum can be relaxed.
+ static_assert(N <= details::kFunctionMaximumN);
+
+ using OpaqueStorageTraits = details::function_opaque_storage<N>;
+
+ public:
+ // Defining result_type allows ftl::Function to be substituted for std::function.
+ using result_type = Ret;
+
+ // Constructs an empty ftl::Function.
+ Function() = default;
+
+ // Constructing or assigning from nullptr_t also creates an empty ftl::Function.
+ Function(std::nullptr_t) {}
+ Function& operator=(std::nullptr_t) { return *this = Function(nullptr); }
+
+ // Constructing from NoOpTag sets up a a special no-op function which is valid to call, and which
+ // returns a default constructed return value.
+ Function(NoOpTag) : function_(details::bind_opaque_no_op<Ret, Args...>()) {}
+ Function& operator=(NoOpTag) { return *this = Function(no_op); }
+
+ // Constructing/assigning from a function object stores a copy of that function object, however:
+ // * It must be trivially copyable, as the implementation makes a copy with memcpy().
+ // * It must be trivially destructible, as the implementation doesn't destroy the copy!
+ // * It must fit in the limited internal storage, which enforces size/alignment restrictions.
+
+ template <typename F, typename = std::enable_if_t<std::is_invocable_r_v<Ret, F, Args...>>>
+ Function(const F& f)
+ : opaque_(OpaqueStorageTraits::opaque_copy(f)),
+ function_(details::bind_opaque_function_object<F, Ret, Args...>(f)) {}
+
+ template <typename F, typename = std::enable_if_t<std::is_invocable_r_v<Ret, F, Args...>>>
+ Function& operator=(const F& f) noexcept {
+ return *this = Function{OpaqueStorageTraits::opaque_copy(f),
+ details::bind_opaque_function_object<F, Ret, Args...>(f)};
+ }
+
+ // Constructing/assigning from a smaller ftl::Function is allowed, but not anything else.
+
+ template <std::size_t M>
+ Function(const Function<Ret(Args...), M>& other)
+ : opaque_{OpaqueStorageTraits::opaque_copy(other.opaque_)}, function_(other.function_) {}
+
+ template <std::size_t M>
+ auto& operator=(const Function<Ret(Args...), M>& other) {
+ return *this = Function{OpaqueStorageTraits::opaque_copy(other.opaque_), other.function_};
+ }
+
+ // Returns true if a function is set.
+ explicit operator bool() const { return function_ != nullptr; }
+
+ // Checks if the other function has the same contents as this one.
+ bool operator==(const Function& other) const {
+ return other.opaque_ == opaque_ && other.function_ == function_;
+ }
+ bool operator!=(const Function& other) const { return !operator==(other); }
+
+ // Alternative way of testing for a function being set.
+ bool operator==(std::nullptr_t) const { return function_ == nullptr; }
+ bool operator!=(std::nullptr_t) const { return function_ != nullptr; }
+
+ // Invokes the function.
+ Ret operator()(Args... args) const {
+ return std::invoke(function_, opaque_.data(), std::forward<Args>(args)...);
+ }
+
+ // Creation helper for function objects, such as lambdas.
+ template <typename F>
+ static auto make(const F& f) -> decltype(Function{f}) {
+ return Function{f};
+ }
+
+ // Creation helper for a class pointer and a compile-time chosen member function to call.
+ template <auto MemberFunction, typename Class>
+ static auto make(Class* instance) -> decltype(Function{
+ details::bind_member_function<MemberFunction>(instance,
+ static_cast<Ret (*)(Args...)>(nullptr))}) {
+ return Function{details::bind_member_function<MemberFunction>(
+ instance, static_cast<Ret (*)(Args...)>(nullptr))};
+ }
+
+ // Creation helper for a compile-time chosen free function to call.
+ template <auto FreeFunction>
+ static auto make() -> decltype(Function{
+ details::bind_free_function<FreeFunction>(static_cast<Ret (*)(Args...)>(nullptr))}) {
+ return Function{
+ details::bind_free_function<FreeFunction>(static_cast<Ret (*)(Args...)>(nullptr))};
+ }
+
+ private:
+ // Needed so a Function<F, M> can be converted to a Function<F, N>.
+ template <typename, std::size_t>
+ friend class Function;
+
+ // The function pointer type of function stored in `function_`. The first argument is always
+ // `&opaque_`.
+ using StoredFunction = Ret(void*, Args...);
+
+ // The type of the opaque storage, used to hold an appropriate function object.
+ // The type stored here is ONLY known to the StoredFunction.
+ // We always use at least one std::intptr_t worth of storage, and always a multiple of that size.
+ using OpaqueStorage = typename OpaqueStorageTraits::type;
+
+ // Internal constructor for creating from a raw opaque blob + function pointer.
+ Function(const OpaqueStorage& opaque, StoredFunction* function)
+ : opaque_(opaque), function_(function) {}
+
+ // Note: `mutable` so that `operator() const` can use it.
+ mutable OpaqueStorage opaque_{};
+ StoredFunction* function_{nullptr};
+};
+
+// Makes a ftl::Function given a function object `F`.
+template <typename F, typename T = details::function_traits<F>>
+Function(const F&) -> Function<typename T::type, T::size>;
+
+template <typename F>
+auto make_function(const F& f) -> decltype(Function{f}) {
+ return Function{f};
+}
+
+// Makes a ftl::Function given a `MemberFunction` and a instance pointer to the associated `Class`.
+template <auto MemberFunction, typename Class>
+auto make_function(Class* instance)
+ -> decltype(Function{details::bind_member_function<MemberFunction>(
+ instance,
+ static_cast<details::remove_member_function_pointer_t<MemberFunction>*>(nullptr))}) {
+ return Function{details::bind_member_function<MemberFunction>(
+ instance, static_cast<details::remove_member_function_pointer_t<MemberFunction>*>(nullptr))};
+}
+
+// Makes a ftl::Function given an ordinary free function.
+template <auto FreeFunction>
+auto make_function() -> decltype(Function{
+ details::bind_free_function<FreeFunction>(static_cast<decltype(FreeFunction)>(nullptr))}) {
+ return Function{
+ details::bind_free_function<FreeFunction>(static_cast<decltype(FreeFunction)>(nullptr))};
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/match.h b/include/ftl/match.h
new file mode 100644
index 0000000000..7318c45b7b
--- /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 <utility>
+#include <variant>
+
+#include <ftl/details/match.h>
+
+namespace android::ftl {
+
+// Concise alternative to std::visit that compiles to branches rather than a dispatch table. For
+// std::variant<T0, ..., TN> where N is small, this is slightly faster since the branches can be
+// inlined unlike the function pointers.
+//
+// using namespace std::chrono;
+// std::variant<seconds, minutes, hours> 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<hours>(s);
+// return std::to_string(h.count()) + " hours"s;
+// },
+// [](const hours& h) { return std::to_string(h.count() / 24) + " days"s; }));
+//
+template <typename... Ts, typename... Ms>
+decltype(auto) match(std::variant<Ts...>& variant, Ms&&... matchers) {
+ const auto matcher = details::Matcher{std::forward<Ms>(matchers)...};
+ static_assert(details::is_exhaustive_match_v<decltype(matcher), Ts&...>, "Non-exhaustive match");
+
+ return details::Match<Ts...>::match(variant, matcher);
+}
+
+template <typename... Ts, typename... Ms>
+decltype(auto) match(const std::variant<Ts...>& variant, Ms&&... matchers) {
+ const auto matcher = details::Matcher{std::forward<Ms>(matchers)...};
+ static_assert(details::is_exhaustive_match_v<decltype(matcher), const Ts&...>,
+ "Non-exhaustive match");
+
+ return details::Match<Ts...>::match(variant, matcher);
+}
+
+} // namespace android::ftl
diff --git a/include/ftl/mixins.h b/include/ftl/mixins.h
new file mode 100644
index 0000000000..0e1d2004a3
--- /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 <ftl/details/mixins.h>
+
+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<Id, std::int32_t>, ftl::Equatable<Id> {
+// using Constructible::Constructible;
+// };
+//
+// static_assert(!std::is_default_constructible_v<Id>);
+//
+// Unlike `Constructible`, `DefaultConstructible` allows default construction. The default value is
+// zero-initialized unless specified:
+//
+// struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+// ftl::Equatable<Color>,
+// ftl::Orderable<Color> {
+// 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<Sequence, std::int8_t, -1>,
+// ftl::Equatable<Sequence>,
+// ftl::Orderable<Sequence>,
+// ftl::Incrementable<Sequence> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// static_assert(Sequence() == Sequence(-1));
+//
+// The underlying type need not be a fundamental type:
+//
+// struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+// ftl::Equatable<Timeout>,
+// ftl::Addable<Timeout> {
+// using DefaultConstructible::DefaultConstructible;
+// };
+//
+// using namespace std::chrono_literals;
+// static_assert(Timeout() + Timeout(5s) == Timeout(15s));
+//
+template <typename Self, typename T>
+struct Constructible {
+ explicit constexpr Constructible(T value) : value_(value) {}
+
+ explicit constexpr operator const T&() const { return value_; }
+
+ private:
+ template <typename, template <typename> class>
+ friend class details::Mixin;
+
+ T value_;
+};
+
+template <typename Self, typename T, auto kDefault = T{}>
+struct DefaultConstructible : Constructible<Self, T> {
+ using Constructible<Self, T>::Constructible;
+ constexpr DefaultConstructible() : DefaultConstructible(T{kDefault}) {}
+};
+
+// Shorthand for casting a type-safe wrapper to its underlying value.
+template <typename Self, typename T>
+constexpr const T& to_underlying(const Constructible<Self, T>& c) {
+ return static_cast<const T&>(c);
+}
+
+// Comparison operators for equality.
+template <typename Self>
+struct Equatable : details::Mixin<Self, Equatable> {
+ 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 <typename Self>
+struct Orderable : details::Mixin<Self, Orderable> {
+ 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 <typename Self>
+struct Incrementable : details::Mixin<Self, Incrementable> {
+ 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 <typename Self>
+struct Addable : details::Mixin<Self, Addable>, Incrementable<Self> {
+ 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<Self, Addable>;
+ 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 0000000000..35d09d71de
--- /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 <cstdlib>
+#include <type_traits>
+#include <utility>
+
+namespace android::ftl {
+
+// Enforces and documents non-null pre/post-condition for (raw or smart) pointers.
+//
+// void get_length(const ftl::NonNull<std::shared_ptr<std::string>>& string_ptr,
+// ftl::NonNull<std::size_t*> length_ptr) {
+// // No need for `nullptr` checks.
+// *length_ptr = string_ptr->length();
+// }
+//
+// const auto string_ptr = ftl::as_non_null(std::make_shared<std::string>("android"));
+// std::size_t size;
+// get_length(string_ptr, ftl::as_non_null(&size));
+// assert(size == 7u);
+//
+// For compatibility with std::unique_ptr<T> and performance with std::shared_ptr<T>, move
+// operations are allowed despite breaking the invariant:
+//
+// using Pair = std::pair<ftl::NonNull<std::shared_ptr<int>>, std::shared_ptr<int>>;
+//
+// Pair dupe_if(ftl::NonNull<std::unique_ptr<int>> 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<int>(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<int>(42));
+// const auto [ptr1, ptr2] = dupe_if(std::move(ptr), true);
+// assert(ptr1.get() == ptr2);
+//
+template <typename Pointer>
+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 <typename P>
+ constexpr NonNull(Passkey, P&& pointer) : pointer_(std::forward<P>(pointer)) {
+ if (!pointer_) std::abort();
+ }
+
+ private:
+ template <typename P>
+ friend constexpr auto as_non_null(P&&) -> NonNull<std::decay_t<P>>;
+
+ Pointer pointer_;
+};
+
+template <typename P>
+constexpr auto as_non_null(P&& pointer) -> NonNull<std::decay_t<P>> {
+ using Passkey = typename NonNull<std::decay_t<P>>::Passkey;
+ return {Passkey{}, std::forward<P>(pointer)};
+}
+
+template <typename P, typename Q>
+constexpr bool operator==(const NonNull<P>& lhs, const NonNull<Q>& rhs) {
+ return lhs.get() == rhs.get();
+}
+
+template <typename P, typename Q>
+constexpr bool operator!=(const NonNull<P>& lhs, const NonNull<Q>& 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 0000000000..94d8e3d7cb
--- /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 <functional>
+#include <optional>
+#include <utility>
+
+#include <ftl/details/optional.h>
+
+namespace android::ftl {
+
+// Superset of std::optional<T> with monadic operations, as proposed in https://wg21.link/P0798R8.
+//
+// TODO: Remove in C++23.
+//
+template <typename T>
+struct Optional final : std::optional<T> {
+ using std::optional<T>::optional;
+
+ // Implicit downcast.
+ Optional(std::optional<T> other) : std::optional<T>(std::move(other)) {}
+
+ using std::optional<T>::has_value;
+ using std::optional<T>::value;
+
+ // Returns Optional<U> where F is a function that maps T to U.
+ template <typename F>
+ constexpr auto transform(F&& f) const& {
+ using R = details::transform_result_t<F, decltype(value())>;
+ if (has_value()) return R(std::invoke(std::forward<F>(f), value()));
+ return R();
+ }
+
+ template <typename F>
+ constexpr auto transform(F&& f) & {
+ using R = details::transform_result_t<F, decltype(value())>;
+ if (has_value()) return R(std::invoke(std::forward<F>(f), value()));
+ return R();
+ }
+
+ template <typename F>
+ constexpr auto transform(F&& f) const&& {
+ using R = details::transform_result_t<F, decltype(std::move(value()))>;
+ if (has_value()) return R(std::invoke(std::forward<F>(f), std::move(value())));
+ return R();
+ }
+
+ template <typename F>
+ constexpr auto transform(F&& f) && {
+ using R = details::transform_result_t<F, decltype(std::move(value()))>;
+ if (has_value()) return R(std::invoke(std::forward<F>(f), std::move(value())));
+ return R();
+ }
+
+ // Returns Optional<U> where F is a function that maps T to Optional<U>.
+ template <typename F>
+ constexpr auto and_then(F&& f) const& {
+ using R = details::and_then_result_t<F, decltype(value())>;
+ if (has_value()) return std::invoke(std::forward<F>(f), value());
+ return R();
+ }
+
+ template <typename F>
+ constexpr auto and_then(F&& f) & {
+ using R = details::and_then_result_t<F, decltype(value())>;
+ if (has_value()) return std::invoke(std::forward<F>(f), value());
+ return R();
+ }
+
+ template <typename F>
+ constexpr auto and_then(F&& f) const&& {
+ using R = details::and_then_result_t<F, decltype(std::move(value()))>;
+ if (has_value()) return std::invoke(std::forward<F>(f), std::move(value()));
+ return R();
+ }
+
+ template <typename F>
+ constexpr auto and_then(F&& f) && {
+ using R = details::and_then_result_t<F, decltype(std::move(value()))>;
+ if (has_value()) return std::invoke(std::forward<F>(f), std::move(value()));
+ return R();
+ }
+
+ // Returns this Optional<T> if not nullopt, or else the Optional<T> returned by the function F.
+ template <typename F>
+ constexpr auto or_else(F&& f) const& -> details::or_else_result_t<F, T> {
+ if (has_value()) return *this;
+ return std::forward<F>(f)();
+ }
+
+ template <typename F>
+ constexpr auto or_else(F&& f) && -> details::or_else_result_t<F, T> {
+ if (has_value()) return std::move(*this);
+ return std::forward<F>(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 <typename T, typename U>
+constexpr bool operator==(const Optional<T>& lhs, const Optional<U>& rhs) {
+ return static_cast<std::optional<T>>(lhs) == static_cast<std::optional<U>>(rhs);
+}
+
+template <typename T, typename U>
+constexpr bool operator!=(const Optional<T>& lhs, const Optional<U>& rhs) {
+ return !(lhs == rhs);
+}
+
+// Deduction guides.
+template <typename T>
+Optional(T) -> Optional<T>;
+
+template <typename T>
+Optional(std::optional<T>) -> Optional<T>;
+
+} // namespace android::ftl
diff --git a/include/ftl/shared_mutex.h b/include/ftl/shared_mutex.h
new file mode 100644
index 0000000000..146f5ba4a9
--- /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 <shared_mutex>
+
+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 5217e76064..49cde7fedc 100644
--- a/include/ftl/small_map.h
+++ b/include/ftl/small_map.h
@@ -17,11 +17,11 @@
#pragma once
#include <ftl/initializer_list.h>
+#include <ftl/optional.h>
#include <ftl/small_vector.h>
#include <algorithm>
#include <functional>
-#include <optional>
#include <type_traits>
#include <utility>
@@ -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 <typename K, typename V, std::size_t N, typename KeyEqual = std::equal_to<K>>
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<std::reference_wrapper<const mapped_type>> {
- return get(key, [](const mapped_type& v) { return std::cref(v); });
- }
-
- auto get(const key_type& key) -> std::optional<std::reference_wrapper<mapped_type>> {
- return get(key, [](mapped_type& v) { return std::ref(v); });
+ auto get(const key_type& key) const -> Optional<std::reference_wrapper<const mapped_type>> {
+ 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 <typename F, typename R = std::invoke_result_t<F, const mapped_type&>>
- auto get(const key_type& key, F f) const
- -> std::conditional_t<std::is_void_v<R>, bool, std::optional<R>> {
+ auto get(const key_type& key) -> Optional<std::reference_wrapper<mapped_type>> {
for (auto& [k, v] : *this) {
if (KeyEqual{}(k, key)) {
- if constexpr (std::is_void_v<R>) {
- f(v);
- return true;
- } else {
- return f(v);
- }
+ return std::ref(v);
}
}
-
return {};
}
- template <typename F>
- auto get(const key_type& key, F f) {
- return std::as_const(*this).get(
- key, [&f](const mapped_type& v) { return f(const_cast<mapped_type&>(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<SmallMap&>(*this).find(key); }
iterator find(const key_type& key) { return find(key, begin()); }
@@ -286,7 +262,7 @@ bool operator==(const SmallMap<K, V, N, E>& lhs, const SmallMap<Q, W, M, E>& 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 339726e4ea..11294c3ac8 100644
--- a/include/ftl/small_vector.h
+++ b/include/ftl/small_vector.h
@@ -21,11 +21,12 @@
#include <algorithm>
#include <iterator>
-#include <type_traits>
#include <utility>
#include <variant>
#include <vector>
+#include <ftl/details/type_traits.h>
+
namespace android::ftl {
template <typename>
@@ -80,10 +81,6 @@ class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<Sma
using Static = StaticVector<T, N>;
using Dynamic = SmallVector<T, 0>;
- // TODO: Replace with std::remove_cvref_t in C++20.
- template <typename U>
- using remove_cvref_t = std::remove_cv_t<std::remove_reference_t<U>>;
-
public:
FTL_ARRAY_TRAIT(T, value_type);
FTL_ARRAY_TRAIT(T, size_type);
@@ -104,7 +101,7 @@ class SmallVector final : details::ArrayTraits<T>, details::ArrayComparators<Sma
// Constructs at most N elements. See StaticVector for underlying constructors.
template <typename Arg, typename... Args,
- typename = std::enable_if_t<!is_small_vector<remove_cvref_t<Arg>>{}>>
+ typename = std::enable_if_t<!is_small_vector<details::remove_cvref_t<Arg>>{}>>
SmallVector(Arg&& arg, Args&&... args)
: vector_(std::in_place_type<Static>, std::forward<Arg>(arg), std::forward<Args>(args)...) {}
diff --git a/include/ftl/unit.h b/include/ftl/unit.h
new file mode 100644
index 0000000000..e38230b976
--- /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 <type_traits>
+#include <utility>
+
+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<T>::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 <typename F>
+struct UnitFn {
+ F f;
+
+ template <typename... Args>
+ Unit operator()(Args&&... args) {
+ return f(std::forward<Args>(args)...), unit;
+ }
+};
+
+template <typename F>
+constexpr auto unit_fn(F&& f) -> UnitFn<std::decay_t<F>> {
+ return {std::forward<F>(f)};
+}
+
+} // namespace android::ftl
diff --git a/services/inputflinger/BlockingQueue.h b/include/input/BlockingQueue.h
index 8300e8a3da..f848c82c42 100644
--- a/services/inputflinger/BlockingQueue.h
+++ b/include/input/BlockingQueue.h
@@ -14,18 +14,20 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_BLOCKING_QUEUE_H
-#define _UI_INPUT_BLOCKING_QUEUE_H
+#pragma once
-#include "android-base/thread_annotations.h"
#include <condition_variable>
+#include <functional>
+#include <list>
#include <mutex>
-#include <vector>
+#include <optional>
+#include "android-base/thread_annotations.h"
namespace android {
/**
- * A FIFO queue that stores up to <i>capacity</i> objects.
+ * A thread-safe FIFO queue. This list-backed queue stores up to <i>capacity</i> objects if
+ * a capacity is provided at construction, and is otherwise unbounded.
* Objects can always be added. Objects are added immediately.
* If the queue is full, new objects cannot be added.
*
@@ -34,13 +36,13 @@ namespace android {
template <class T>
class BlockingQueue {
public:
- BlockingQueue(size_t capacity) : mCapacity(capacity) {
- mQueue.reserve(mCapacity);
- };
+ explicit BlockingQueue() = default;
+
+ explicit BlockingQueue(size_t capacity) : mCapacity(capacity){};
/**
* Retrieve and remove the oldest object.
- * Blocks execution while queue is empty.
+ * Blocks execution indefinitely while queue is empty.
*/
T pop() {
std::unique_lock lock(mLock);
@@ -52,26 +54,62 @@ public:
};
/**
+ * Retrieve and remove the oldest object.
+ * Blocks execution for the given duration while queue is empty, and returns std::nullopt
+ * if the queue was empty for the entire duration.
+ */
+ std::optional<T> popWithTimeout(std::chrono::nanoseconds duration) {
+ std::unique_lock lock(mLock);
+ android::base::ScopedLockAssertion assumeLock(mLock);
+ if (!mHasElements.wait_for(lock, duration,
+ [this]() REQUIRES(mLock) { return !this->mQueue.empty(); })) {
+ return {};
+ }
+ T t = std::move(mQueue.front());
+ mQueue.erase(mQueue.begin());
+ return t;
+ };
+
+ /**
* Add a new object to the queue.
* Does not block.
* Return true if an element was successfully added.
* Return false if the queue is full.
*/
bool push(T&& t) {
- {
+ { // acquire lock
std::scoped_lock lock(mLock);
- if (mQueue.size() == mCapacity) {
+ if (mCapacity && mQueue.size() == mCapacity) {
return false;
}
mQueue.push_back(std::move(t));
- }
+ } // release lock
+ mHasElements.notify_one();
+ return true;
+ };
+
+ /**
+ * Construct a new object into the queue.
+ * Does not block.
+ * Return true if an element was successfully added.
+ * Return false if the queue is full.
+ */
+ template <class... Args>
+ bool emplace(Args&&... args) {
+ { // acquire lock
+ std::scoped_lock lock(mLock);
+ if (mCapacity && mQueue.size() == mCapacity) {
+ return false;
+ }
+ mQueue.emplace_back(args...);
+ } // release lock
mHasElements.notify_one();
return true;
};
- void erase(const std::function<bool(const T&)>& lambda) {
+ void erase_if(const std::function<bool(const T&)>& pred) {
std::scoped_lock lock(mLock);
- std::erase_if(mQueue, [&lambda](const auto& t) { return lambda(t); });
+ std::erase_if(mQueue, pred);
}
/**
@@ -94,7 +132,7 @@ public:
}
private:
- const size_t mCapacity;
+ const std::optional<size_t> mCapacity;
/**
* Used to signal that mQueue is non-empty.
*/
@@ -103,9 +141,7 @@ private:
* Lock for accessing and waiting on elements.
*/
std::mutex mLock;
- std::vector<T> mQueue GUARDED_BY(mLock);
+ std::list<T> mQueue GUARDED_BY(mLock);
};
-
} // namespace android
-#endif
diff --git a/include/input/DisplayViewport.h b/include/input/DisplayViewport.h
index 9148fee532..b0eceefba0 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 <android-base/stringprintf.h>
#include <ftl/enum.h>
#include <ftl/string.h>
#include <gui/constants.h>
#include <input/Input.h>
+#include <ui/Rotation.h>
#include <cinttypes>
#include <optional>
@@ -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;
@@ -137,12 +130,10 @@ struct DisplayViewport {
"isActive=[%d]",
ftl::enum_string(type).c_str(), displayId, uniqueId.c_str(),
physicalPort ? ftl::to_string(*physicalPort).c_str() : "<none>",
- orientation, logicalLeft, logicalTop, logicalRight, logicalBottom,
- physicalLeft, physicalTop, physicalRight, physicalBottom, deviceWidth,
- deviceHeight, isActive);
+ static_cast<int>(orientation), logicalLeft, logicalTop, logicalRight,
+ logicalBottom, physicalLeft, physicalTop, physicalRight, physicalBottom,
+ deviceWidth, deviceHeight, isActive);
}
};
} // namespace android
-
-#endif // _LIBINPUT_DISPLAY_VIEWPORT_H
diff --git a/include/input/Input.h b/include/input/Input.h
index 7ea297049b..1c4ea6b416 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 <stdint.h>
#include <ui/Transform.h>
#include <utils/BitSet.h>
-#include <utils/RefBase.h>
#include <utils/Timers.h>
#include <array>
#include <limits>
@@ -204,12 +202,65 @@ 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);
+
+struct PointerProperties;
+
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties);
+
/*
* Flags that flow alongside events in the input dispatch system to help with certain
* policy decisions such as waking from device sleep.
@@ -235,8 +286,19 @@ enum {
// Indicates that the key represents a special gesture that has been detected by
// the touch firmware or driver. Causes touch events from the same device to be canceled.
+ // This policy flag prevents key events from changing touch mode state.
POLICY_FLAG_GESTURE = 0x00000008,
+ // Indicates that key usage mapping represents a fallback mapping.
+ // Fallback mappings cannot be used to definitively determine whether a device
+ // supports a key code. For example, a HID device can report a key press
+ // as a HID usage code if it is not mapped to any linux key code in the kernel.
+ // However, we cannot know which HID usage codes that device supports from
+ // userspace through the evdev. We can use fallback mappings to convert HID
+ // usage codes to Android key codes without needing to know if a device can
+ // actually report the usage code.
+ POLICY_FLAG_FALLBACK_USAGE_MAPPING = 0x00000010,
+
POLICY_FLAG_RAW_MASK = 0x0000ffff,
#ifdef __linux__
@@ -290,6 +352,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 +374,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 +431,24 @@ constexpr float AMOTION_EVENT_INVALID_CURSOR_POSITION = std::numeric_limits<floa
* Pointer coordinate data.
*/
struct PointerCoords {
- enum { MAX_AXES = 30 }; // 30 so that sizeof(PointerCoords) == 128
+ enum { MAX_AXES = 30 }; // 30 so that sizeof(PointerCoords) == 136
// Bitfield of axes that are present in this structure.
uint64_t bits __attribute__((aligned(8)));
// Values of axes that are stored in this structure packed in order by axis id
// for each axis that is present in the structure according to 'bits'.
- float values[MAX_AXES];
+ std::array<float, MAX_AXES> 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 +485,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,21 +500,24 @@ 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;
+ bool operator==(const PointerProperties& other) const = default;
inline bool operator!=(const PointerProperties& other) const {
return !(*this == other);
}
- void copyFrom(const PointerProperties& other);
+ PointerProperties& operator=(const PointerProperties&) = default;
};
+// TODO(b/211379801) : Use a strong type from ftl/mixins.h instead
+using DeviceId = int32_t;
+
/*
* Input events.
*/
@@ -439,11 +525,11 @@ 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; }
- inline int32_t getDeviceId() const { return mDeviceId; }
+ inline DeviceId getDeviceId() const { return mDeviceId; }
inline uint32_t getSource() const { return mSource; }
@@ -458,18 +544,20 @@ public:
static int32_t nextId();
protected:
- void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
+ void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac);
void initialize(const InputEvent& from);
int32_t mId;
- int32_t mDeviceId;
+ DeviceId mDeviceId;
uint32_t mSource;
int32_t mDisplayId;
std::array<uint8_t, 32> mHmac;
};
+std::ostream& operator<<(std::ostream& out, const InputEvent& event);
+
/*
* Key events.
*/
@@ -477,7 +565,7 @@ class KeyEvent : public InputEvent {
public:
virtual ~KeyEvent() { }
- virtual int32_t getType() const { return AINPUT_EVENT_TYPE_KEY; }
+ InputEventType getType() const override { return InputEventType::KEY; }
inline int32_t getAction() const { return mAction; }
@@ -498,9 +586,9 @@ 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<int> getKeyCodeFromLabel(const char* label);
- void initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
+ void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac, int32_t action, int32_t flags, int32_t keyCode,
int32_t scanCode, int32_t metaState, int32_t repeatCount, nsecs_t downTime,
nsecs_t eventTime);
@@ -519,6 +607,8 @@ protected:
nsecs_t mEventTime;
};
+std::ostream& operator<<(std::ostream& out, const KeyEvent& event);
+
/*
* Motion events.
*/
@@ -526,7 +616,7 @@ class MotionEvent : public InputEvent {
public:
virtual ~MotionEvent() { }
- virtual int32_t getType() const { return AINPUT_EVENT_TYPE_MOTION; }
+ InputEventType getType() const override { return InputEventType::MOTION; }
inline int32_t getAction() const { return mAction; }
@@ -571,7 +661,7 @@ public:
inline const ui::Transform& getTransform() const { return mTransform; }
- int getSurfaceRotation() const;
+ std::optional<ui::Rotation> getSurfaceRotation() const;
inline float getXPrecision() const { return mXPrecision; }
@@ -605,7 +695,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,9 +846,13 @@ 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,
+ void initialize(int32_t id, DeviceId deviceId, uint32_t source, int32_t displayId,
std::array<uint8_t, 32> hmac, int32_t action, int32_t actionButton,
int32_t flags, int32_t edgeFlags, int32_t metaState, int32_t buttonState,
MotionClassification classification, const ui::Transform& transform,
@@ -805,7 +899,7 @@ public:
}
static const char* getLabel(int32_t axis);
- static int32_t getAxisFromLabel(const char* label);
+ static std::optional<int> getAxisFromLabel(const char* label);
static std::string actionToString(int32_t action);
@@ -818,6 +912,8 @@ public:
const PointerCoords&);
static PointerCoords calculateTransformedCoords(uint32_t source, const ui::Transform&,
const PointerCoords&);
+ // The rounding precision for transformed motion events.
+ static constexpr float ROUNDING_PRECISION = 0.001f;
protected:
int32_t mAction;
@@ -848,7 +944,7 @@ class FocusEvent : public InputEvent {
public:
virtual ~FocusEvent() {}
- virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_FOCUS; }
+ InputEventType getType() const override { return InputEventType::FOCUS; }
inline bool getHasFocus() const { return mHasFocus; }
@@ -867,7 +963,7 @@ class CaptureEvent : public InputEvent {
public:
virtual ~CaptureEvent() {}
- virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_CAPTURE; }
+ InputEventType getType() const override { return InputEventType::CAPTURE; }
inline bool getPointerCaptureEnabled() const { return mPointerCaptureEnabled; }
@@ -886,7 +982,7 @@ class DragEvent : public InputEvent {
public:
virtual ~DragEvent() {}
- virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_DRAG; }
+ InputEventType getType() const override { return InputEventType::DRAG; }
inline bool isExiting() const { return mIsExiting; }
@@ -910,7 +1006,7 @@ class TouchModeEvent : public InputEvent {
public:
virtual ~TouchModeEvent() {}
- virtual int32_t getType() const override { return AINPUT_EVENT_TYPE_TOUCH_MODE; }
+ InputEventType getType() const override { return InputEventType::TOUCH_MODE; }
inline bool isInTouchMode() const { return mIsInTouchMode; }
@@ -934,7 +1030,7 @@ struct __attribute__((__packed__)) VerifiedInputEvent {
};
Type type;
- int32_t deviceId;
+ DeviceId deviceId;
nsecs_t eventTimeNanos;
uint32_t source;
int32_t displayId;
@@ -1042,6 +1138,24 @@ private:
std::queue<std::unique_ptr<TouchModeEvent>> mTouchModeEventPool;
};
+/**
+ * An input event factory implementation that simply creates the input events on the heap, when
+ * needed. The caller is responsible for destroying the returned references.
+ * It is recommended that the caller wrap these return values into std::unique_ptr.
+ */
+class DynamicInputEventFactory : public InputEventFactoryInterface {
+public:
+ explicit DynamicInputEventFactory(){};
+ ~DynamicInputEventFactory(){};
+
+ KeyEvent* createKeyEvent() override { return new KeyEvent(); };
+ MotionEvent* createMotionEvent() override { return new MotionEvent(); };
+ FocusEvent* createFocusEvent() override { return new FocusEvent(); };
+ CaptureEvent* createCaptureEvent() override { return new CaptureEvent(); };
+ DragEvent* createDragEvent() override { return new DragEvent(); };
+ TouchModeEvent* createTouchModeEvent() override { return new TouchModeEvent(); };
+};
+
/*
* Describes a unique request to enable or disable Pointer Capture.
*/
@@ -1061,6 +1175,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 3585392c2b..b7751f704a 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -14,15 +14,19 @@
* limitations under the License.
*/
-#ifndef _LIBINPUT_INPUT_DEVICE_H
-#define _LIBINPUT_INPUT_DEVICE_H
+#pragma once
#include <android/sensor.h>
+#include <ftl/flags.h>
+#include <ftl/mixins.h>
#include <input/Input.h>
#include <input/KeyCharacterMap.h>
+#include <set>
#include <unordered_map>
#include <vector>
+#include <android/os/IInputConstants.h>
+
namespace android {
/*
@@ -55,6 +59,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<std::string> bluetoothAddress;
+
/**
* Return InputDeviceIdentifier.name that has been adjusted as follows:
* - all characters besides alphanumerics, dash,
@@ -63,6 +70,9 @@ struct InputDeviceIdentifier {
* while conforming to the filename limitations.
*/
std::string getCanonicalName() const;
+
+ bool operator==(const InputDeviceIdentifier&) const = default;
+ bool operator!=(const InputDeviceIdentifier&) const = default;
};
/* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */
@@ -104,12 +114,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 {
@@ -168,18 +184,36 @@ struct InputDeviceSensorInfo {
int32_t id;
};
+struct BrightnessLevel : ftl::DefaultConstructible<BrightnessLevel, std::uint8_t>,
+ ftl::Equatable<BrightnessLevel>,
+ ftl::Orderable<BrightnessLevel>,
+ ftl::Addable<BrightnessLevel> {
+ using DefaultConstructible::DefaultConstructible;
+};
+
struct InputDeviceLightInfo {
explicit InputDeviceLightInfo(std::string name, int32_t id, InputDeviceLightType type,
- int32_t ordinal)
- : name(name), id(id), type(type), ordinal(ordinal) {}
+ ftl::Flags<InputDeviceLightCapability> capabilityFlags,
+ int32_t ordinal,
+ std::set<BrightnessLevel> preferredBrightnessLevels)
+ : name(name),
+ id(id),
+ type(type),
+ capabilityFlags(capabilityFlags),
+ ordinal(ordinal),
+ preferredBrightnessLevels(std::move(preferredBrightnessLevels)) {}
// Name string of the light.
std::string name;
// Light id
int32_t id;
// Type of the light.
InputDeviceLightType type;
+ // Light capabilities.
+ ftl::Flags<InputDeviceLightCapability> capabilityFlags;
// Ordinal of the light
int32_t ordinal;
+ // Custom brightness levels for the light
+ std::set<BrightnessLevel> preferredBrightnessLevels;
};
struct InputDeviceBatteryInfo {
@@ -190,6 +224,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 +265,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 +293,11 @@ public:
void setKeyboardType(int32_t keyboardType);
inline int32_t getKeyboardType() const { return mKeyboardType; }
+ void setKeyboardLayoutInfo(KeyboardLayoutInfo keyboardLayoutInfo);
+ inline const std::optional<KeyboardLayoutInfo>& getKeyboardLayoutInfo() const {
+ return mKeyboardLayoutInfo;
+ }
+
inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) {
mKeyCharacterMap = value;
}
@@ -266,6 +326,13 @@ public:
std::vector<InputDeviceLightInfo> getLights();
+ inline void setUsiVersion(std::optional<InputDeviceUsiVersion> usiVersion) {
+ mUsiVersion = std::move(usiVersion);
+ }
+ inline std::optional<InputDeviceUsiVersion> getUsiVersion() const { return mUsiVersion; }
+
+ inline int32_t getAssociatedDisplayId() const { return mAssociatedDisplayId; }
+
private:
int32_t mId;
int32_t mGeneration;
@@ -274,9 +341,13 @@ private:
std::string mAlias;
bool mIsExternal;
bool mHasMic;
+ std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
uint32_t mSources;
int32_t mKeyboardType;
std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
+ std::optional<InputDeviceUsiVersion> mUsiVersion;
+ int32_t mAssociatedDisplayId;
+
bool mHasVibrator;
bool mHasBattery;
bool mHasButtonUnderPad;
@@ -325,6 +396,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 +407,3 @@ enum ReservedInputDeviceId : int32_t {
};
} // namespace android
-
-#endif // _LIBINPUT_INPUT_DEVICE_H
diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h
new file mode 100644
index 0000000000..2d23b97386
--- /dev/null
+++ b/include/input/InputEventBuilders.h
@@ -0,0 +1,249 @@
+/*
+ * 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 <android/input.h>
+#include <attestation/HmacKeyManager.h>
+#include <gui/constants.h>
+#include <input/Input.h>
+#include <utils/Timers.h> // for nsecs_t, systemTime
+
+#include <vector>
+
+namespace android {
+
+// An arbitrary device id.
+static constexpr uint32_t DEFAULT_DEVICE_ID = 1;
+
+// 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;
+
+class PointerBuilder {
+public:
+ PointerBuilder(int32_t id, ToolType toolType) {
+ mProperties.clear();
+ mProperties.id = id;
+ mProperties.toolType = toolType;
+ mCoords.clear();
+ }
+
+ PointerBuilder& x(float x) { return axis(AMOTION_EVENT_AXIS_X, x); }
+
+ PointerBuilder& y(float y) { return axis(AMOTION_EVENT_AXIS_Y, y); }
+
+ PointerBuilder& axis(int32_t axis, float value) {
+ mCoords.setAxisValue(axis, value);
+ return *this;
+ }
+
+ PointerProperties buildProperties() const { return mProperties; }
+
+ PointerCoords buildCoords() const { return mCoords; }
+
+private:
+ PointerProperties mProperties;
+ PointerCoords mCoords;
+};
+
+class MotionEventBuilder {
+public:
+ MotionEventBuilder(int32_t action, int32_t source) {
+ 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) {
+ mEventTime = eventTime;
+ return *this;
+ }
+
+ MotionEventBuilder& displayId(int32_t displayId) {
+ mDisplayId = displayId;
+ return *this;
+ }
+
+ MotionEventBuilder& actionButton(int32_t actionButton) {
+ mActionButton = actionButton;
+ return *this;
+ }
+
+ MotionEventBuilder& buttonState(int32_t buttonState) {
+ mButtonState = buttonState;
+ return *this;
+ }
+
+ MotionEventBuilder& rawXCursorPosition(float rawXCursorPosition) {
+ mRawXCursorPosition = rawXCursorPosition;
+ return *this;
+ }
+
+ MotionEventBuilder& rawYCursorPosition(float rawYCursorPosition) {
+ mRawYCursorPosition = rawYCursorPosition;
+ return *this;
+ }
+
+ MotionEventBuilder& pointer(PointerBuilder pointer) {
+ mPointers.push_back(pointer);
+ return *this;
+ }
+
+ MotionEventBuilder& addFlag(uint32_t flags) {
+ mFlags |= flags;
+ return *this;
+ }
+
+ MotionEvent build() {
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> 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();
+ }
+
+ MotionEvent event;
+ static const ui::Transform kIdentityTransform;
+ event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
+ mAction, mActionButton, mFlags, /*edgeFlags=*/0, AMETA_NONE, mButtonState,
+ MotionClassification::NONE, kIdentityTransform,
+ /*xPrecision=*/0, /*yPrecision=*/0, mRawXCursorPosition,
+ mRawYCursorPosition, kIdentityTransform, mDownTime, mEventTime,
+ mPointers.size(), pointerProperties.data(), pointerCoords.data());
+ return event;
+ }
+
+private:
+ int32_t mAction;
+ int32_t mDeviceId{DEFAULT_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<PointerBuilder> mPointers;
+};
+
+class KeyEventBuilder {
+public:
+ KeyEventBuilder(int32_t action, int32_t source) {
+ mAction = action;
+ mSource = source;
+ mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDownTime = mEventTime;
+ }
+
+ KeyEventBuilder(const KeyEvent& event) {
+ mAction = event.getAction();
+ mDeviceId = event.getDeviceId();
+ mSource = event.getSource();
+ mDownTime = event.getDownTime();
+ mEventTime = event.getEventTime();
+ mDisplayId = event.getDisplayId();
+ mFlags = event.getFlags();
+ mKeyCode = event.getKeyCode();
+ mScanCode = event.getScanCode();
+ mMetaState = event.getMetaState();
+ mRepeatCount = event.getRepeatCount();
+ }
+
+ KeyEventBuilder& deviceId(int32_t deviceId) {
+ mDeviceId = deviceId;
+ return *this;
+ }
+
+ KeyEventBuilder& downTime(nsecs_t downTime) {
+ mDownTime = downTime;
+ return *this;
+ }
+
+ KeyEventBuilder& eventTime(nsecs_t eventTime) {
+ mEventTime = eventTime;
+ return *this;
+ }
+
+ KeyEventBuilder& displayId(int32_t displayId) {
+ mDisplayId = displayId;
+ return *this;
+ }
+
+ KeyEventBuilder& policyFlags(int32_t policyFlags) {
+ mPolicyFlags = policyFlags;
+ return *this;
+ }
+
+ KeyEventBuilder& addFlag(uint32_t flags) {
+ mFlags |= flags;
+ return *this;
+ }
+
+ KeyEventBuilder& keyCode(int32_t keyCode) {
+ mKeyCode = keyCode;
+ return *this;
+ }
+
+ KeyEventBuilder& repeatCount(int32_t repeatCount) {
+ mRepeatCount = repeatCount;
+ return *this;
+ }
+
+ KeyEvent build() const {
+ KeyEvent event{};
+ event.initialize(InputEvent::nextId(), mDeviceId, mSource, mDisplayId, INVALID_HMAC,
+ mAction, mFlags, mKeyCode, mScanCode, mMetaState, mRepeatCount, mDownTime,
+ mEventTime);
+ return event;
+ }
+
+private:
+ int32_t mAction;
+ int32_t mDeviceId = DEFAULT_DEVICE_ID;
+ uint32_t mSource;
+ nsecs_t mDownTime;
+ nsecs_t mEventTime;
+ int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+ uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
+ int32_t mFlags{0};
+ int32_t mKeyCode{AKEYCODE_UNKNOWN};
+ int32_t mScanCode{0};
+ int32_t mMetaState{AMETA_NONE};
+ int32_t mRepeatCount{0};
+};
+
+} // namespace android
diff --git a/include/input/InputEventLabels.h b/include/input/InputEventLabels.h
index 2a742f9cf4..44247c1249 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 <input/Input.h>
#include <android/keycodes.h>
@@ -31,41 +30,70 @@ 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 {
+ /**
+ * This class is not purely static, but uses a singleton pattern in order to delay the
+ * initialization of the maps that it contains. If it were purely static, the maps could be
+ * created early, and would cause sanitizers to report memory leaks.
+ */
public:
- static int lookupValueByLabel(const std::unordered_map<std::string, int>& map,
- const char* literal);
+ InputEventLookup(InputEventLookup& other) = delete;
+
+ void operator=(const InputEventLookup&) = delete;
+
+ static std::optional<int> lookupValueByLabel(const std::unordered_map<std::string, int>& map,
+ const char* literal);
static const char* lookupLabelByValue(const std::vector<InputEventLabel>& vec, int value);
- static int32_t getKeyCodeByLabel(const char* label);
+ static std::optional<int> getKeyCodeByLabel(const char* label);
static const char* getLabelByKeyCode(int32_t keyCode);
- static uint32_t getKeyFlagByLabel(const char* label);
+ static std::optional<int> getKeyFlagByLabel(const char* label);
- static int32_t getAxisByLabel(const char* label);
+ static std::optional<int> getAxisByLabel(const char* label);
static const char* getAxisLabel(int32_t axisId);
- static int32_t getLedByLabel(const char* label);
+ static std::optional<int> getLedByLabel(const char* label);
+
+ static EvdevEventLabel getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value);
+
+ static std::optional<int> getLinuxEvdevEventTypeByLabel(const char* label);
+
+ static std::optional<int> getLinuxEvdevEventCodeByLabel(int32_t type, const char* label);
+
+ static std::optional<int> getLinuxEvdevInputPropByLabel(const char* label);
private:
- static const std::unordered_map<std::string, int> KEYCODES;
+ InputEventLookup();
+
+ static const InputEventLookup& get() {
+ static InputEventLookup sLookup;
+ return sLookup;
+ }
+
+ const std::unordered_map<std::string, int> KEYCODES;
- static const std::vector<InputEventLabel> KEY_NAMES;
+ const std::vector<InputEventLabel> KEY_NAMES;
- static const std::unordered_map<std::string, int> AXES;
+ const std::unordered_map<std::string, int> AXES;
- static const std::vector<InputEventLabel> AXES_NAMES;
+ const std::vector<InputEventLabel> AXES_NAMES;
- static const std::unordered_map<std::string, int> LEDS;
+ const std::unordered_map<std::string, int> LEDS;
- static const std::unordered_map<std::string, int> FLAGS;
+ const std::unordered_map<std::string, int> FLAGS;
};
} // namespace android
-#endif // _LIBINPUT_INPUT_EVENT_LABELS_H
diff --git a/include/input/InputTransport.h b/include/input/InputTransport.h
index 5f9a37d69c..59b9495c42 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 <binder/IBinder.h>
#include <binder/Parcelable.h>
#include <input/Input.h>
+#include <input/InputVerifier.h>
#include <sys/stat.h>
#include <ui/Transform.h>
#include <utils/BitSet.h>
@@ -240,7 +240,7 @@ public:
android::base::unique_fd fd, sp<IBinder> token);
InputChannel() = default;
InputChannel(const InputChannel& other)
- : mName(other.mName), mFd(::dup(other.mFd)), mToken(other.mToken){};
+ : mName(other.mName), mFd(other.dupFd()), mToken(other.mToken){};
InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token);
~InputChannel() override;
/**
@@ -310,7 +310,7 @@ public:
if (fstat(mFd.get(), &lhs) != 0) {
return false;
}
- if (fstat(inputChannel.getFd(), &rhs) != 0) {
+ if (fstat(inputChannel.getFd().get(), &rhs) != 0) {
return false;
}
// If file descriptors are pointing to same inode they are duplicated fds.
@@ -322,7 +322,7 @@ private:
base::unique_fd dupFd() const;
std::string mName;
- android::base::unique_fd mFd;
+ base::unique_fd mFd;
sp<IBinder> mToken;
};
@@ -445,6 +445,7 @@ public:
private:
std::shared_ptr<InputChannel> 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<InputChannel>& channel);
+ /* Create a consumer associated with an input channel, override resampling system property */
+ explicit InputConsumer(const std::shared_ptr<InputChannel>& 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 0000000000..14dd463425
--- /dev/null
+++ b/include/input/InputVerifier.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 <android-base/result.h>
+#include <input/Input.h>
+#include "rust/cxx.h"
+
+namespace android {
+
+namespace input {
+namespace verifier {
+struct InputVerifier;
+}
+} // namespace input
+
+/*
+ * Crash if the provided touch stream is inconsistent.
+ * This class is a pass-through to the rust implementation of InputVerifier.
+ * The rust class could also be used directly, but it would be less convenient.
+ * We can't directly invoke the rust methods on a rust object. So, there's no way to do:
+ * mVerifier.process_movement(...).
+ * This C++ class makes it a bit easier to use.
+ *
+ * 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);
+
+ android::base::Result<void> processMovement(int32_t deviceId, int32_t source, int32_t action,
+ uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, int32_t flags);
+
+ void resetDevice(int32_t deviceId);
+
+private:
+ rust::Box<android::input::verifier::InputVerifier> mVerifier;
+};
+
+} // namespace android
diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h
index f6f8939b7a..dfcf766402 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 <stdint.h>
+#include <list>
#ifdef __linux__
#include <binder/IBinder.h>
@@ -26,9 +26,9 @@
#include <android-base/result.h>
#include <input/Input.h>
#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
#include <utils/Tokenizer.h>
#include <utils/Unicode.h>
+#include <map>
// 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,66 +128,63 @@ public:
bool getEvents(int32_t deviceId, const char16_t* chars, size_t numChars,
Vector<KeyEvent>& 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 <keyCode, metaState> 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<int32_t /*keyCode*/, int32_t /*metaState*/> applyKeyBehavior(int32_t keyCode,
+ int32_t metaState) const;
#ifdef __linux__
/* Reads a key map from a parcel. */
- static std::shared_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
+ static std::unique_ptr<KeyCharacterMap> readFromParcel(Parcel* parcel);
/* Writes a key map to a parcel. */
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<Behavior> 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<int32_t, Key*> mKeys;
- KeyboardType mType;
+ std::map<int32_t, Key> mKeys;
+ KeyboardType mType = KeyboardType::UNKNOWN;
std::string mLoadFileName;
- bool mLayoutOverlayApplied;
+ bool mLayoutOverlayApplied = false;
- KeyedVector<int32_t, int32_t> mKeysByScanCode;
- KeyedVector<int32_t, int32_t> mKeysByUsageCode;
+ std::map<int32_t /* fromAndroidKeyCode */, int32_t /* toAndroidKeyCode */> mKeyRemapping;
+ std::map<int32_t /* fromScanCode */, int32_t /* toAndroidKeyCode */> mKeysByScanCode;
+ std::map<int32_t /* fromHidUsageCode */, int32_t /* toAndroidKeyCode */> 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 1da78aa0c1..b126abecea 100644
--- a/include/input/KeyLayoutMap.h
+++ b/include/input/KeyLayoutMap.h
@@ -14,17 +14,16 @@
* limitations under the License.
*/
-#ifndef _LIBINPUT_KEY_LAYOUT_MAP_H
-#define _LIBINPUT_KEY_LAYOUT_MAP_H
+#pragma once
#include <android-base/result.h>
+#include <input/InputDevice.h>
+
#include <stdint.h>
#include <utils/Errors.h>
#include <utils/Tokenizer.h>
#include <set>
-#include <input/InputDevice.h>
-
namespace android {
struct AxisInfo {
@@ -73,12 +72,13 @@ public:
int32_t* outKeyCode, uint32_t* outFlags) const;
std::vector<int32_t> findScanCodesForKey(int32_t keyCode) const;
std::optional<int32_t> findScanCodeForLed(int32_t ledCode) const;
+ std::vector<int32_t> findUsageCodesForKey(int32_t keyCode) const;
std::optional<int32_t> findUsageCodeForLed(int32_t ledCode) const;
std::optional<AxisInfo> 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<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t absCode);
+ base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t absCode) const;
virtual ~KeyLayoutMap();
@@ -131,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 9a3e15f1cd..f7f960f8e6 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 <input/Input.h>
#include <input/InputDevice.h>
@@ -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 0000000000..3b6e40183f
--- /dev/null
+++ b/include/input/MotionPredictor.h
@@ -0,0 +1,106 @@
+/*
+ * 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 <cstdint>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <unordered_map>
+
+#include <android-base/result.h>
+#include <android-base/thread_annotations.h>
+#include <android/sysprop/InputProperties.sysprop.h>
+#include <input/Input.h>
+#include <input/MotionPredictorMetricsManager.h>
+#include <input/TfLiteMotionPredictor.h>
+#include <utils/Timers.h> // for nsecs_t
+
+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:
+ using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
+ /**
+ * Parameters:
+ * predictionTimestampOffsetNanos: additional, constant shift to apply to the target
+ * prediction time. The prediction will target the time t=(prediction time +
+ * predictionTimestampOffsetNanos).
+ *
+ * checkEnableMotionPrediction: 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.
+ *
+ * reportAtomFunction: the function that will be called to report prediction metrics. If
+ * omitted, the implementation will choose a default metrics reporting mechanism.
+ */
+ MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+ std::function<bool()> checkEnableMotionPrediction = isMotionPredictionEnabled,
+ ReportAtomFunction reportAtomFunction = {});
+
+ /**
+ * 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<void> record(const MotionEvent& event);
+
+ std::unique_ptr<MotionEvent> predict(nsecs_t timestamp);
+
+ bool isPredictionAvailable(int32_t deviceId, int32_t source);
+
+private:
+ const nsecs_t mPredictionTimestampOffsetNanos;
+ const std::function<bool()> mCheckMotionPredictionEnabled;
+
+ std::unique_ptr<TfLiteMotionPredictorModel> mModel;
+
+ std::unique_ptr<TfLiteMotionPredictorBuffers> mBuffers;
+ std::optional<MotionEvent> mLastEvent;
+
+ std::optional<MotionPredictorMetricsManager> mMetricsManager;
+
+ const ReportAtomFunction mReportAtomFunction;
+};
+
+} // namespace android
diff --git a/include/input/MotionPredictorMetricsManager.h b/include/input/MotionPredictorMetricsManager.h
new file mode 100644
index 0000000000..38472d8df7
--- /dev/null
+++ b/include/input/MotionPredictorMetricsManager.h
@@ -0,0 +1,207 @@
+/*
+ * 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 <cstddef>
+#include <cstdint>
+#include <functional>
+#include <limits>
+#include <vector>
+
+#include <input/Input.h> // for MotionEvent
+#include <input/RingBuffer.h>
+#include <utils/Timers.h> // for nsecs_t
+
+#include "Eigen/Core"
+
+namespace android {
+
+/**
+ * Class to handle computing and reporting metrics for MotionPredictor.
+ *
+ * The public API provides two methods: `onRecord` and `onPredict`, which expect to receive the
+ * MotionEvents from the corresponding methods in MotionPredictor.
+ *
+ * This class stores AggregatedStrokeMetrics, updating them as new MotionEvents are passed in. When
+ * onRecord receives an UP or CANCEL event, this indicates the end of the stroke, and the final
+ * AtomFields are computed and reported to the stats library. The number of atoms reported is equal
+ * to the value of `maxNumPredictions` passed to the constructor. Each atom corresponds to one
+ * "prediction time bucket" — the amount of time into the future being predicted.
+ *
+ * If mMockLoggedAtomFields is set, the batch of AtomFields that are reported to the stats library
+ * for one stroke are also stored in mMockLoggedAtomFields at the time they're reported.
+ */
+class MotionPredictorMetricsManager {
+public:
+ struct AtomFields;
+
+ using ReportAtomFunction = std::function<void(const AtomFields&)>;
+
+ static void defaultReportAtomFunction(const AtomFields& atomFields);
+
+ // Parameters:
+ // • predictionInterval: the time interval between successive prediction target timestamps.
+ // Note: the MetricsManager assumes that the input interval equals the prediction interval.
+ // • maxNumPredictions: the maximum number of distinct target timestamps the prediction model
+ // will generate predictions for. The MetricsManager reports this many atoms per stroke.
+ // • [Optional] reportAtomFunction: the function that will be called to report metrics. If
+ // omitted (or if an empty function is given), the `stats_write(…)` function from the Android
+ // stats library will be used.
+ MotionPredictorMetricsManager(
+ nsecs_t predictionInterval,
+ size_t maxNumPredictions,
+ ReportAtomFunction reportAtomFunction = defaultReportAtomFunction);
+
+ // This method should be called once for each call to MotionPredictor::record, receiving the
+ // forwarded MotionEvent argument.
+ void onRecord(const MotionEvent& inputEvent);
+
+ // This method should be called once for each call to MotionPredictor::predict, receiving the
+ // MotionEvent that will be returned by MotionPredictor::predict.
+ void onPredict(const MotionEvent& predictionEvent);
+
+ // Simple structs to hold relevant touch input information. Public so they can be used in tests.
+
+ struct TouchPoint {
+ Eigen::Vector2f position; // (y, x) in pixels
+ float pressure;
+ };
+
+ struct GroundTruthPoint : TouchPoint {
+ nsecs_t timestamp;
+ };
+
+ struct PredictionPoint : TouchPoint {
+ // The timestamp of the last ground truth point when the prediction was made.
+ nsecs_t originTimestamp;
+
+ nsecs_t targetTimestamp;
+
+ // Order by targetTimestamp when sorting.
+ bool operator<(const PredictionPoint& other) const {
+ return this->targetTimestamp < other.targetTimestamp;
+ }
+ };
+
+ // Metrics aggregated so far for the current stroke. These are not the final fields to be
+ // reported in the atom (see AtomFields below), but rather an intermediate representation of the
+ // data that can be conveniently aggregated and from which the atom fields can be derived later.
+ //
+ // Displacement units are in pixels.
+ //
+ // "Along-trajectory error" is the dot product of the prediction error with the unit vector
+ // pointing towards the ground truth point whose timestamp corresponds to the prediction
+ // target timestamp, originating from the preceding ground truth point.
+ //
+ // "Off-trajectory error" is the component of the prediction error orthogonal to the
+ // "along-trajectory" unit vector described above.
+ //
+ // "High-velocity" errors are errors that are only accumulated when the velocity between the
+ // most recent two input events exceeds a certain threshold.
+ //
+ // "Scale-invariant errors" are the errors produced when the path length of the stroke is
+ // scaled to 1. (In other words, the error distances are normalized by the path length.)
+ struct AggregatedStrokeMetrics {
+ // General errors
+ float alongTrajectoryErrorSum = 0;
+ float alongTrajectorySumSquaredErrors = 0;
+ float offTrajectorySumSquaredErrors = 0;
+ float pressureSumSquaredErrors = 0;
+ size_t generalErrorsCount = 0;
+
+ // High-velocity errors
+ float highVelocityAlongTrajectorySse = 0;
+ float highVelocityOffTrajectorySse = 0;
+ size_t highVelocityErrorsCount = 0;
+
+ // Scale-invariant errors
+ float scaleInvariantAlongTrajectorySse = 0;
+ float scaleInvariantOffTrajectorySse = 0;
+ size_t scaleInvariantErrorsCount = 0;
+ };
+
+ // In order to explicitly indicate "no relevant data" for a metric, we report this
+ // large-magnitude negative sentinel value. (Most metrics are non-negative, so this value is
+ // completely unobtainable. For along-trajectory error mean, which can be negative, the
+ // magnitude makes it unobtainable in practice.)
+ static const int NO_DATA_SENTINEL = std::numeric_limits<int32_t>::min();
+
+ // Final metric values reported in the atom.
+ struct AtomFields {
+ int deltaTimeBucketMilliseconds = 0;
+
+ // General errors
+ int alongTrajectoryErrorMeanMillipixels = NO_DATA_SENTINEL;
+ int alongTrajectoryErrorStdMillipixels = NO_DATA_SENTINEL;
+ int offTrajectoryRmseMillipixels = NO_DATA_SENTINEL;
+ int pressureRmseMilliunits = NO_DATA_SENTINEL;
+
+ // High-velocity errors
+ int highVelocityAlongTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+ int highVelocityOffTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+
+ // Scale-invariant errors
+ int scaleInvariantAlongTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+ int scaleInvariantOffTrajectoryRmse = NO_DATA_SENTINEL; // millipixels
+ };
+
+private:
+ // The interval between consecutive predictions' target timestamps. We assume that the input
+ // interval also equals this value.
+ const nsecs_t mPredictionInterval;
+
+ // The maximum number of input frames into the future the model can predict.
+ // Used to perform time-bucketing of metrics.
+ const size_t mMaxNumPredictions;
+
+ // History of mMaxNumPredictions + 1 ground truth points, used to compute scale-invariant
+ // error. (Also, the last two points are used to compute the ground truth trajectory.)
+ RingBuffer<GroundTruthPoint> mRecentGroundTruthPoints;
+
+ // Predictions having a targetTimestamp after the most recent ground truth point's timestamp.
+ // Invariant: sorted in ascending order of targetTimestamp.
+ std::vector<PredictionPoint> mRecentPredictions;
+
+ // Containers for the intermediate representation of stroke metrics and the final atom fields.
+ // These are indexed by the number of input frames into the future being predicted minus one,
+ // and always have size mMaxNumPredictions.
+ std::vector<AggregatedStrokeMetrics> mAggregatedMetrics;
+ std::vector<AtomFields> mAtomFields;
+
+ const ReportAtomFunction mReportAtomFunction;
+
+ // Helper methods for the implementation of onRecord and onPredict.
+
+ // Clears stored ground truth and prediction points, as well as all stored metrics for the
+ // current stroke.
+ void clearStrokeData();
+
+ // Adds the new ground truth point to mRecentGroundTruths, removes outdated predictions from
+ // mRecentPredictions, and updates the aggregated metrics to include the recent predictions that
+ // fuzzily match with the new ground truth point.
+ void incorporateNewGroundTruth(const GroundTruthPoint& groundTruthPoint);
+
+ // Given a new prediction with targetTimestamp matching the latest ground truth point's
+ // timestamp, computes the corresponding metrics and updates mAggregatedMetrics.
+ void updateAggregatedMetrics(const PredictionPoint& predictionPoint);
+
+ // Computes the atom fields to mAtomFields from the values in mAggregatedMetrics.
+ void computeAtomFields();
+
+ // Reports the current data in mAtomFields by calling mReportAtomFunction.
+ void reportMetrics();
+};
+
+} // namespace android
diff --git a/include/input/PrintTools.h b/include/input/PrintTools.h
index 55f730b287..83fffa37c6 100644
--- a/include/input/PrintTools.h
+++ b/include/input/PrintTools.h
@@ -16,24 +16,56 @@
#pragma once
+#include <bitset>
#include <map>
#include <optional>
#include <set>
+#include <sstream>
#include <string>
+#include <vector>
namespace android {
+template <size_t N>
+std::string bitsetToString(const std::bitset<N>& bitset) {
+ if (bitset.none()) {
+ return "<none>";
+ }
+ return bitset.to_string();
+}
+
+template <class T>
+std::string streamableToString(const T& streamable) {
+ std::stringstream out;
+ out << streamable;
+ return out.str();
+}
+
template <typename T>
-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<bool>::reference& value) {
+ return value ? "true" : "false";
+}
+
+inline std::string constToString(const std::string& s) {
+ return s;
+}
+
/**
* Convert an optional type to string.
*/
template <typename T>
-std::string toString(const std::optional<T>& optional,
- std::string (*toString)(const T&) = constToString) {
+inline std::string toString(const std::optional<T>& optional,
+ std::string (*toString)(const T&) = constToString) {
return optional ? toString(*optional) : "<not set>";
}
@@ -51,11 +83,12 @@ std::string dumpSet(const std::set<T>& v, std::string (*toString)(const T&) = co
}
/**
- * Convert a map to string. Both keys and values of the map should be integral type.
+ * Convert a map or multimap to string. Both keys and values of the map should be integral type.
*/
-template <typename K, typename V>
-std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString,
- std::string (*valueToString)(const V&) = constToString) {
+template <typename T>
+std::string dumpMap(const T& map,
+ std::string (*keyToString)(const typename T::key_type&) = constToString,
+ std::string (*valueToString)(const typename T::mapped_type&) = constToString) {
std::string out;
for (const auto& [k, v] : map) {
if (!out.empty()) {
@@ -66,6 +99,31 @@ std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const
return out;
}
+/**
+ * Convert map keys to string. The keys of the map should be integral type.
+ */
+template <typename K, typename V>
+std::string dumpMapKeys(const std::map<K, V>& map,
+ std::string (*keyToString)(const K&) = constToString) {
+ std::string out;
+ for (const auto& [k, _] : map) {
+ out += out.empty() ? "{" : ", ";
+ out += keyToString(k);
+ }
+ return out.empty() ? "{}" : (out + "}");
+}
+
+/** Convert a vector to a string. */
+template <typename T>
+std::string dumpVector(const std::vector<T>& values,
+ std::string (*valueToString)(const T&) = constToString) {
+ std::string dump = valueToString(values[0]);
+ for (size_t i = 1; i < values.size(); i++) {
+ dump += ", " + valueToString(values[i]);
+ }
+ return dump;
+}
+
const char* toString(bool value);
/**
@@ -77,4 +135,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 451918bb46..2e44142927 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 <android-base/result.h>
-#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
-#include <utils/String8.h>
#include <utils/Tokenizer.h>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <unordered_set>
+
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<std::string> 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<std::string> getString(const std::string& key) const;
+ std::optional<bool> getBool(const std::string& key) const;
+ std::optional<int32_t> getInt(const std::string& key) const;
+ std::optional<float> getFloat(const std::string& key) const;
+ std::optional<double> 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<String8, String8>& getProperties() const { return mProperties; }
-
/* Loads a property map from a file. */
static android::base::Result<std::unique_ptr<PropertyMap>> 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<String8, String8> mProperties;
+ std::unordered_map<std::string, std::string> 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 0000000000..d2747d6fad
--- /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 <algorithm>
+#include <compare>
+#include <cstddef>
+#include <iterator>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/stringprintf.h>
+
+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 <typename T>
+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 <typename U>
+ class Iterator;
+ using iterator = Iterator<T>;
+ using const_iterator = Iterator<const T>;
+
+ // Creates an empty ring buffer that can hold some capacity.
+ explicit RingBuffer(size_type capacity)
+ : mBuffer(std::allocator<value_type>().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<value_type>().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<value_type>().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<value_type>(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<value_type>(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<value_type>(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<value_type>(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 <typename U>
+ class Iterator {
+ private:
+ using ContainerType = std::conditional_t<std::is_const_v<U>, 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<U>;
+ 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 {
+ if (elementIndex > size()) {
+ abort();
+ }
+ size_type index = mBegin + elementIndex;
+ if (index >= capacity()) {
+ index -= capacity();
+ }
+ if (index >= capacity()) {
+ abort();
+ }
+ 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 0000000000..2edc138f67
--- /dev/null
+++ b/include/input/TfLiteMotionPredictor.h
@@ -0,0 +1,167 @@
+/*
+ * 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 <array>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include <android-base/mapped_file.h>
+#include <input/RingBuffer.h>
+#include <utils/Timers.h>
+
+#include <tensorflow/lite/core/api/error_reporter.h>
+#include <tensorflow/lite/interpreter.h>
+#include <tensorflow/lite/model.h>
+#include <tensorflow/lite/signature_runner.h>
+
+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<float> mInputR;
+ RingBuffer<float> mInputPhi;
+ RingBuffer<float> mInputPressure;
+ RingBuffer<float> mInputTilt;
+ RingBuffer<float> mInputOrientation;
+
+ // The samples defining the current polar axis.
+ std::optional<TfLiteMotionPredictorSample> mAxisFrom;
+ std::optional<TfLiteMotionPredictorSample> mAxisTo;
+};
+
+// A TFLite model for generating motion predictions.
+class TfLiteMotionPredictorModel {
+public:
+ struct Config {
+ // The time between predictions.
+ nsecs_t predictionInterval = 0;
+ // The noise floor for predictions.
+ // Distances (r) less than this should be discarded as noise.
+ float distanceNoiseFloor = 0;
+ };
+
+ // Creates a model from an encoded Flatbuffer model.
+ static std::unique_ptr<TfLiteMotionPredictorModel> 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;
+
+ const Config& config() const { return mConfig; }
+
+ // 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<float> inputR();
+ std::span<float> inputPhi();
+ std::span<float> inputPressure();
+ std::span<float> inputOrientation();
+ std::span<float> inputTilt();
+
+ // Returns immutable buffers to the output tensors of identical length. Only valid after a
+ // successful call to invoke().
+ std::span<const float> outputR() const;
+ std::span<const float> outputPhi() const;
+ std::span<const float> outputPressure() const;
+
+private:
+ explicit TfLiteMotionPredictorModel(std::unique_ptr<android::base::MappedFile> model,
+ Config config);
+
+ 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<android::base::MappedFile> mFlatBuffer;
+ std::unique_ptr<tflite::ErrorReporter> mErrorReporter;
+ std::unique_ptr<tflite::FlatBufferModel> mModel;
+ std::unique_ptr<tflite::Interpreter> mInterpreter;
+ tflite::SignatureRunner* mRunner = nullptr;
+
+ const Config mConfig = {};
+};
+
+} // namespace android
diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h
index eda628e233..1e4f6e7a4c 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 <ui/Rotation.h>
#include <stdint.h>
#include <sys/time.h>
@@ -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/TraceTools.h b/include/input/TraceTools.h
new file mode 100644
index 0000000000..81fc7af4ae
--- /dev/null
+++ b/include/input/TraceTools.h
@@ -0,0 +1,29 @@
+/*
+ * 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 <utils/Trace.h>
+#include <optional>
+
+// A macro for tracing when the given condition is true.
+// This macro relies on the fact that only one branch of the ternary operator is evaluated. That
+// means if `message` is an expression that evaluates to a std::string value, the value will
+// not be computed unless the condition is true.
+#define ATRACE_NAME_IF(condition, message) \
+ const auto _trace_token = condition \
+ ? std::make_optional<android::ScopedTrace>(ATRACE_TAG, (message).c_str()) \
+ : std::nullopt
diff --git a/include/input/VelocityControl.h b/include/input/VelocityControl.h
index 1acc2aef70..b78f63e1ae 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 <android-base/stringprintf.h>
#include <input/Input.h>
#include <input/VelocityTracker.h>
#include <utils/Timers.h>
+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. */
+ const VelocityControlParameters& getParameters() const;
+
/* 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 886f1f7753..ee7445544b 100644
--- a/include/input/VelocityTracker.h
+++ b/include/input/VelocityTracker.h
@@ -14,12 +14,15 @@
* limitations under the License.
*/
-#ifndef _LIBINPUT_VELOCITY_TRACKER_H
-#define _LIBINPUT_VELOCITY_TRACKER_H
+#pragma once
+#include <android/os/IInputConstants.h>
#include <input/Input.h>
+#include <input/RingBuffer.h>
#include <utils/BitSet.h>
#include <utils/Timers.h>
+#include <map>
+#include <set>
namespace android {
@@ -30,109 +33,108 @@ class VelocityTrackerStrategy;
*/
class VelocityTracker {
public:
+ static const size_t MAX_DEGREE = 4;
+
enum class Strategy : int32_t {
- DEFAULT = -1,
- MIN = 0,
- IMPULSE = 0,
- LSQ1 = 1,
- LSQ2 = 2,
- LSQ3 = 3,
- WLSQ2_DELTA = 4,
- WLSQ2_CENTRAL = 5,
- WLSQ2_RECENT = 6,
- INT1 = 7,
- INT2 = 8,
- LEGACY = 9,
+ DEFAULT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_DEFAULT,
+ IMPULSE = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_IMPULSE,
+ LSQ1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ1,
+ LSQ2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ2,
+ LSQ3 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LSQ3,
+ WLSQ2_DELTA = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA,
+ WLSQ2_CENTRAL = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL,
+ WLSQ2_RECENT = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT,
+ INT1 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT1,
+ INT2 = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_INT2,
+ LEGACY = android::os::IInputConstants::VELOCITY_TRACKER_STRATEGY_LEGACY,
+ MIN = IMPULSE,
MAX = LEGACY,
+ ftl_last = LEGACY,
};
- struct Position {
- float x, y;
- };
-
- struct Estimator {
- static const size_t MAX_DEGREE = 4;
-
- // Estimator time base.
- nsecs_t time;
-
- // Polynomial coefficients describing motion in X and Y.
- float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1];
+ /*
+ * Contains all available velocity data from a VelocityTracker.
+ */
+ struct ComputedVelocity {
+ inline std::optional<float> getVelocity(int32_t axis, int32_t id) const {
+ const auto& axisVelocities = mVelocities.find(axis);
+ if (axisVelocities == mVelocities.end()) {
+ return {};
+ }
- // Polynomial degree (number of coefficients), or zero if no information is
- // available.
- uint32_t degree;
+ const auto& axisIdVelocity = axisVelocities->second.find(id);
+ if (axisIdVelocity == axisVelocities->second.end()) {
+ return {};
+ }
- // Confidence (coefficient of determination), between 0 (no fit) and 1 (perfect fit).
- float confidence;
+ return axisIdVelocity->second;
+ }
- inline void clear() {
- time = 0;
- degree = 0;
- confidence = 0;
- for (size_t i = 0; i <= MAX_DEGREE; i++) {
- xCoeff[i] = 0;
- yCoeff[i] = 0;
- }
+ inline void addVelocity(int32_t axis, int32_t id, float velocity) {
+ mVelocities[axis][id] = velocity;
}
+
+ private:
+ std::map<int32_t /*axis*/, std::map<int32_t /*pointerId*/, float /*velocity*/>> 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<Position>& 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);
+ 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<float> getVelocity(int32_t axis, int32_t pointerId) const;
- // Gets an estimator for the recent movements of the specified pointer id.
- // Returns false and clears the estimator if there is no information available
- // about the pointer.
- bool getEstimator(uint32_t id, Estimator* outEstimator) 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 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<VelocityTrackerStrategy> mStrategy;
-
- bool configureStrategy(const Strategy strategy);
-
- static std::unique_ptr<VelocityTrackerStrategy> createStrategy(const Strategy strategy);
+ std::optional<int32_t> 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<int32_t /*axis*/, std::unique_ptr<VelocityTrackerStrategy>> 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<VelocityTrackerStrategy> createStrategy(const Strategy strategy,
+ bool deltaValues);
};
@@ -146,43 +148,71 @@ 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<VelocityTracker::Position>& 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<float> getVelocity(int32_t pointerId) const = 0;
};
+/**
+ * A `VelocityTrackerStrategy` that accumulates added data points and processes the accumulated data
+ * points when getting velocity.
+ */
+class AccumulatingVelocityTrackerStrategy : public VelocityTrackerStrategy {
+public:
+ AccumulatingVelocityTrackerStrategy(nsecs_t horizonNanos, bool maintainHorizonDuringAdd);
+
+ void addMovement(nsecs_t eventTime, int32_t pointerId, float position) override;
+ void clearPointer(int32_t pointerId) override;
+
+protected:
+ struct Movement {
+ nsecs_t eventTime;
+ float position;
+ };
+
+ // Number of samples to keep.
+ // If different strategies would like to maintain different history size, we can make this a
+ // protected const field.
+ static constexpr uint32_t HISTORY_SIZE = 20;
+
+ /**
+ * Duration, in nanoseconds, since the latest movement where a movement may be considered for
+ * velocity calculation.
+ */
+ const nsecs_t mHorizonNanos;
+ /**
+ * If true, data points outside of horizon (see `mHorizonNanos`) will be cleared after each
+ * addition of a new movement.
+ */
+ const bool mMaintainHorizonDuringAdd;
+ std::map<int32_t /*pointerId*/, RingBuffer<Movement>> mMovements;
+};
/*
* Velocity tracker algorithm based on least-squares linear regression.
*/
-class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
+class LeastSquaresVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
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();
+ // Degree must be no greater than VelocityTracker::MAX_DEGREE.
+ 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<VelocityTracker::Position>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ std::optional<float> getVelocity(int32_t pointerId) const override;
private:
// Sample horizon.
@@ -190,28 +220,19 @@ private:
// changes in direction.
static const nsecs_t HORIZON = 100 * 1000000; // 100 ms
- // Number of samples to keep.
- static const uint32_t HISTORY_SIZE = 20;
-
- 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 chooseWeight(uint32_t index) const;
+ float chooseWeight(int32_t pointerId, uint32_t index) const;
+ /**
+ * An optimized least-squares solver for degree 2 and no weight (i.e. `Weighting.NONE`).
+ * The provided container of movements shall NOT be empty, and shall have the movements in
+ * chronological order.
+ */
+ std::optional<float> solveUnweightedLeastSquaresDeg2(
+ const RingBuffer<Movement>& movements) const;
const uint32_t mDegree;
const Weighting mWeighting;
- uint32_t mIndex;
- Movement mMovements[HISTORY_SIZE];
};
-
/*
* Velocity tracker algorithm that uses an IIR filter.
*/
@@ -219,13 +240,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<VelocityTracker::Position>& 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<float> getVelocity(int32_t pointerId) const override;
private:
// Current state estimate for a particular pointer.
@@ -233,68 +252,42 @@ 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 populateEstimator(const State& state, VelocityTracker::Estimator* outEstimator) const;
+ void initState(State& state, nsecs_t eventTime, float pos) const;
+ void updateState(State& state, nsecs_t eventTime, float pos) const;
};
/*
* Velocity tracker strategy used prior to ICS.
*/
-class LegacyVelocityTrackerStrategy : public VelocityTrackerStrategy {
+class LegacyVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
public:
LegacyVelocityTrackerStrategy();
- virtual ~LegacyVelocityTrackerStrategy();
+ ~LegacyVelocityTrackerStrategy() override;
- virtual void clear();
- virtual void clearPointers(BitSet32 idBits);
- void addMovement(nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ std::optional<float> getVelocity(int32_t pointerId) const override;
private:
// Oldest sample to consider when calculating the velocity.
static const nsecs_t HORIZON = 200 * 1000000; // 100 ms
- // Number of samples to keep.
- static const uint32_t HISTORY_SIZE = 20;
-
// The minimum duration between samples when estimating velocity.
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
-
- 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)];
- }
- };
-
- uint32_t mIndex;
- Movement mMovements[HISTORY_SIZE];
};
-class ImpulseVelocityTrackerStrategy : public VelocityTrackerStrategy {
+class ImpulseVelocityTrackerStrategy : public AccumulatingVelocityTrackerStrategy {
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<VelocityTracker::Position>& positions) override;
- virtual bool getEstimator(uint32_t id, VelocityTracker::Estimator* outEstimator) const;
+ std::optional<float> getVelocity(int32_t pointerId) const override;
private:
// Sample horizon.
@@ -302,23 +295,10 @@ private:
// changes in direction.
static constexpr nsecs_t HORIZON = 100 * 1000000; // 100 ms
- // Number of samples to keep.
- static constexpr size_t HISTORY_SIZE = 20;
-
- 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)];
- }
- };
-
- 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;
};
} // namespace android
-
-#endif // _LIBINPUT_VELOCITY_TRACKER_H
diff --git a/include/input/VirtualInputDevice.h b/include/input/VirtualInputDevice.h
new file mode 100644
index 0000000000..21a28770b6
--- /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 <android-base/unique_fd.h>
+
+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<int, int>& evKeyCodeMapping,
+ const std::map<int, UinputAction>& actionMapping,
+ std::chrono::nanoseconds eventTime);
+};
+
+class VirtualKeyboard : public VirtualInputDevice {
+public:
+ static const std::map<int, int> KEY_CODE_MAPPING;
+ // Expose to share with VirtualDpad.
+ static const std::map<int, UinputAction> 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<int, int> 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<int, UinputAction> BUTTON_ACTION_MAPPING;
+ static const std::map<int, int> 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<int, UinputAction> TOUCH_ACTION_MAPPING;
+ static const std::map<int, int> 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<MAX_POINTERS> 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 6e8e2c9cf4..a4381eaab9 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 <stdint.h>
@@ -77,5 +76,3 @@ private:
};
} // namespace android
-
-#endif // _LIBINPUT_KEY_CHARACTER_MAP_H
diff --git a/include/powermanager/PowerHalController.h b/include/powermanager/PowerHalController.h
index 71a36d09e5..9e426d3ea3 100644
--- a/include/powermanager/PowerHalController.h
+++ b/include/powermanager/PowerHalController.h
@@ -17,11 +17,11 @@
#ifndef ANDROID_POWERHALCONTROLLER_H
#define ANDROID_POWERHALCONTROLLER_H
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <android-base/thread_annotations.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
#include <powermanager/PowerHalWrapper.h>
namespace android {
@@ -53,13 +53,15 @@ public:
: mHalConnector(std::move(connector)) {}
virtual ~PowerHalController() = default;
- void init();
+ virtual void init();
- virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
- virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
- virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
- int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
- int64_t durationNanos) override;
+ virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) override;
+ virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode,
+ bool enabled) override;
+ virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+ createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+ int64_t durationNanos) override;
virtual HalResult<int64_t> getHintSessionPreferredRate() override;
private:
diff --git a/include/powermanager/PowerHalLoader.h b/include/powermanager/PowerHalLoader.h
index ed6f6f35f5..cbbfa597ba 100644
--- a/include/powermanager/PowerHalLoader.h
+++ b/include/powermanager/PowerHalLoader.h
@@ -17,9 +17,11 @@
#ifndef ANDROID_POWERHALLOADER_H
#define ANDROID_POWERHALLOADER_H
+#include <aidl/android/hardware/power/IPower.h>
#include <android-base/thread_annotations.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/IPower.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/1.3/IPower.h>
namespace android {
@@ -29,15 +31,19 @@ namespace power {
class PowerHalLoader {
public:
static void unloadAll();
- static sp<hardware::power::IPower> loadAidl();
+ static std::shared_ptr<aidl::android::hardware::power::IPower> loadAidl();
static sp<hardware::power::V1_0::IPower> loadHidlV1_0();
static sp<hardware::power::V1_1::IPower> loadHidlV1_1();
+ static sp<hardware::power::V1_2::IPower> loadHidlV1_2();
+ static sp<hardware::power::V1_3::IPower> loadHidlV1_3();
private:
static std::mutex gHalMutex;
- static sp<hardware::power::IPower> gHalAidl GUARDED_BY(gHalMutex);
+ static std::shared_ptr<aidl::android::hardware::power::IPower> gHalAidl GUARDED_BY(gHalMutex);
static sp<hardware::power::V1_0::IPower> gHalHidlV1_0 GUARDED_BY(gHalMutex);
static sp<hardware::power::V1_1::IPower> gHalHidlV1_1 GUARDED_BY(gHalMutex);
+ static sp<hardware::power::V1_2::IPower> gHalHidlV1_2 GUARDED_BY(gHalMutex);
+ static sp<hardware::power::V1_3::IPower> gHalHidlV1_3 GUARDED_BY(gHalMutex);
static sp<hardware::power::V1_0::IPower> loadHidlV1_0Locked()
EXCLUSIVE_LOCKS_REQUIRED(gHalMutex);
diff --git a/include/powermanager/PowerHalWrapper.h b/include/powermanager/PowerHalWrapper.h
index dfb0ff59a0..4e4a1b000d 100644
--- a/include/powermanager/PowerHalWrapper.h
+++ b/include/powermanager/PowerHalWrapper.h
@@ -17,12 +17,15 @@
#ifndef ANDROID_POWERHALWRAPPER_H
#define ANDROID_POWERHALWRAPPER_H
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <android-base/thread_annotations.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/1.3/IPower.h>
+#include <binder/Status.h>
namespace android {
@@ -45,7 +48,7 @@ public:
}
static HalResult<T> unsupported() { return HalResult("", /* unsupported= */ true); }
- static HalResult<T> fromStatus(binder::Status status, T data) {
+ static HalResult<T> fromStatus(const binder::Status& status, T data) {
if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
return HalResult<T>::unsupported();
}
@@ -54,14 +57,28 @@ public:
}
return HalResult<T>::failed(std::string(status.toString8().c_str()));
}
- static HalResult<T> fromStatus(hardware::power::V1_0::Status status, T data);
+
+ static HalResult<T> fromStatus(const ndk::ScopedAStatus& status, T data) {
+ if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ return HalResult<T>::unsupported();
+ }
+ if (status.isOk()) {
+ return HalResult<T>::ok(data);
+ }
+ return HalResult<T>::failed(std::string(status.getDescription()));
+ }
template <typename R>
- static HalResult<T> fromReturn(hardware::Return<R>& ret, T data);
+ static HalResult<T> fromReturn(hardware::Return<R>& ret, T data) {
+ return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
+ }
template <typename R>
static HalResult<T> fromReturn(hardware::Return<R>& ret, hardware::power::V1_0::Status status,
- T data);
+ T data) {
+ return ret.isOk() ? HalResult<T>::fromStatus(status, data)
+ : HalResult<T>::failed(ret.description());
+ }
// This will throw std::bad_optional_access if this result is not ok.
const T& value() const { return mValue.value(); }
@@ -89,12 +106,30 @@ public:
static HalResult<void> failed(std::string msg) { return HalResult(std::move(msg)); }
static HalResult<void> unsupported() { return HalResult(/* unsupported= */ true); }
- static HalResult<void> fromStatus(status_t status);
- static HalResult<void> fromStatus(binder::Status status);
- static HalResult<void> fromStatus(hardware::power::V1_0::Status status);
+ static HalResult<void> fromStatus(const binder::Status& status) {
+ if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ return HalResult<void>::unsupported();
+ }
+ if (status.isOk()) {
+ return HalResult<void>::ok();
+ }
+ return HalResult<void>::failed(std::string(status.toString8().c_str()));
+ }
+
+ static HalResult<void> fromStatus(const ndk::ScopedAStatus& status) {
+ if (status.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
+ return HalResult<void>::unsupported();
+ }
+ if (status.isOk()) {
+ return HalResult<void>::ok();
+ }
+ return HalResult<void>::failed(std::string(status.getDescription()));
+ }
template <typename R>
- static HalResult<void> fromReturn(hardware::Return<R>& ret);
+ static HalResult<void> fromReturn(hardware::Return<R>& ret) {
+ return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
+ }
bool isOk() const { return !mUnsupported && !mFailed; }
bool isFailed() const { return !mUnsupported && mFailed; }
@@ -117,11 +152,12 @@ class HalWrapper {
public:
virtual ~HalWrapper() = default;
- virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) = 0;
- virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) = 0;
- virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
- int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
- int64_t durationNanos) = 0;
+ virtual HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) = 0;
+ virtual HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) = 0;
+ virtual HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+ createHintSession(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
+ int64_t durationNanos) = 0;
virtual HalResult<int64_t> getHintSessionPreferredRate() = 0;
};
@@ -129,35 +165,37 @@ public:
class EmptyHalWrapper : public HalWrapper {
public:
EmptyHalWrapper() = default;
- ~EmptyHalWrapper() = default;
+ ~EmptyHalWrapper() override = default;
- virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
- virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
- virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
+ HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) override;
+ HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+ HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
int64_t durationNanos) override;
- virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+ HalResult<int64_t> getHintSessionPreferredRate() override;
};
// Wrapper for the HIDL Power HAL v1.0.
class HidlHalWrapperV1_0 : public HalWrapper {
public:
- explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> Hal)
- : mHandleV1_0(std::move(Hal)) {}
- virtual ~HidlHalWrapperV1_0() = default;
-
- virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
- virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
- virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
+ explicit HidlHalWrapperV1_0(sp<hardware::power::V1_0::IPower> handleV1_0)
+ : mHandleV1_0(std::move(handleV1_0)) {}
+ ~HidlHalWrapperV1_0() override = default;
+
+ HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) override;
+ HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+ HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
int64_t durationNanos) override;
- virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+ HalResult<int64_t> getHintSessionPreferredRate() override;
protected:
- virtual HalResult<void> sendPowerHint(hardware::power::V1_0::PowerHint hintId, uint32_t data);
+ const sp<hardware::power::V1_0::IPower> mHandleV1_0;
+ virtual HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data);
private:
- sp<hardware::power::V1_0::IPower> mHandleV1_0;
HalResult<void> setInteractive(bool enabled);
HalResult<void> setFeature(hardware::power::V1_0::Feature feature, bool enabled);
};
@@ -165,44 +203,71 @@ private:
// Wrapper for the HIDL Power HAL v1.1.
class HidlHalWrapperV1_1 : public HidlHalWrapperV1_0 {
public:
- HidlHalWrapperV1_1(sp<hardware::power::V1_0::IPower> handleV1_0,
- sp<hardware::power::V1_1::IPower> handleV1_1)
- : HidlHalWrapperV1_0(std::move(handleV1_0)), mHandleV1_1(std::move(handleV1_1)) {}
- virtual ~HidlHalWrapperV1_1() = default;
+ explicit HidlHalWrapperV1_1(sp<hardware::power::V1_1::IPower> handleV1_1)
+ : HidlHalWrapperV1_0(std::move(handleV1_1)) {}
+ ~HidlHalWrapperV1_1() override = default;
protected:
- virtual HalResult<void> sendPowerHint(hardware::power::V1_0::PowerHint hintId,
- uint32_t data) override;
+ HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override;
+};
-private:
- sp<hardware::power::V1_1::IPower> mHandleV1_1;
+// Wrapper for the HIDL Power HAL v1.2.
+class HidlHalWrapperV1_2 : public HidlHalWrapperV1_1 {
+public:
+ HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) override;
+ HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+ explicit HidlHalWrapperV1_2(sp<hardware::power::V1_2::IPower> handleV1_2)
+ : HidlHalWrapperV1_1(std::move(handleV1_2)) {}
+ ~HidlHalWrapperV1_2() override = default;
+
+protected:
+ HalResult<void> 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:
+ HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+ explicit HidlHalWrapperV1_3(sp<hardware::power::V1_3::IPower> handleV1_3)
+ : HidlHalWrapperV1_2(std::move(handleV1_3)) {}
+ ~HidlHalWrapperV1_3() override = default;
+
+protected:
+ HalResult<void> sendPowerHint(hardware::power::V1_3::PowerHint hintId, uint32_t data) override;
};
// Wrapper for the AIDL Power HAL.
class AidlHalWrapper : public HalWrapper {
public:
- explicit AidlHalWrapper(sp<hardware::power::IPower> handle) : mHandle(std::move(handle)) {}
- virtual ~AidlHalWrapper() = default;
-
- virtual HalResult<void> setBoost(hardware::power::Boost boost, int32_t durationMs) override;
- virtual HalResult<void> setMode(hardware::power::Mode mode, bool enabled) override;
- virtual HalResult<sp<hardware::power::IPowerHintSession>> createHintSession(
+ explicit AidlHalWrapper(std::shared_ptr<aidl::android::hardware::power::IPower> handle)
+ : mHandle(std::move(handle)) {}
+ ~AidlHalWrapper() override = default;
+
+ HalResult<void> setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) override;
+ HalResult<void> setMode(aidl::android::hardware::power::Mode mode, bool enabled) override;
+ HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>> createHintSession(
int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
int64_t durationNanos) override;
- virtual HalResult<int64_t> getHintSessionPreferredRate() override;
+ HalResult<int64_t> getHintSessionPreferredRate() override;
private:
// Control access to the boost and mode supported arrays.
std::mutex mBoostMutex;
std::mutex mModeMutex;
- sp<hardware::power::IPower> mHandle;
+ std::shared_ptr<aidl::android::hardware::power::IPower> mHandle;
// Android framework only sends boost upto DISPLAY_UPDATE_IMMINENT.
// Need to increase the array size if more boost supported.
- std::array<std::atomic<HalSupport>,
- static_cast<int32_t>(hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) + 1>
+ std::array<
+ std::atomic<HalSupport>,
+ static_cast<int32_t>(aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT) +
+ 1>
mBoostSupportedArray GUARDED_BY(mBoostMutex) = {HalSupport::UNKNOWN};
std::array<std::atomic<HalSupport>,
- static_cast<int32_t>(*(android::enum_range<hardware::power::Mode>().end() - 1)) + 1>
+ static_cast<int32_t>(
+ *(ndk::enum_range<aidl::android::hardware::power::Mode>().end() - 1)) +
+ 1>
mModeSupportedArray GUARDED_BY(mModeMutex) = {HalSupport::UNKNOWN};
};
diff --git a/include/private/performance_hint_private.h b/include/private/performance_hint_private.h
index f27f5f150f..d50c5f846e 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 <stdint.h>
+
__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 7e6c51587d..138926e55b 100644
--- a/include/private/surface_control_private.h
+++ b/include/private/surface_control_private.h
@@ -19,6 +19,8 @@
#include <stdint.h>
+#include <android/choreographer.h>
+
__BEGIN_DECLS
struct ASurfaceControl;
@@ -56,6 +58,13 @@ 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/include/private/thermal_private.h b/include/private/thermal_private.h
new file mode 100644
index 0000000000..951d953267
--- /dev/null
+++ b/include/private/thermal_private.h
@@ -0,0 +1,31 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_PRIVATE_NATIVE_THERMAL_H
+#define ANDROID_PRIVATE_NATIVE_THERMAL_H
+
+#include <stdint.h>
+
+__BEGIN_DECLS
+
+/**
+ * For testing only.
+ */
+void AThermal_setIThermalServiceForTesting(void* iThermalService);
+
+__END_DECLS
+
+#endif // ANDROID_PRIVATE_NATIVE_THERMAL_H \ No newline at end of file
diff --git a/libs/arect/Android.bp b/libs/arect/Android.bp
index 5e539f24e1..1a9766d72e 100644
--- a/libs/arect/Android.bp
+++ b/libs/arect/Android.bp
@@ -72,6 +72,7 @@ cc_library_static {
"//apex_available:platform",
"com.android.media",
"com.android.media.swcodec",
+ "com.android.neuralnetworks",
],
}
diff --git a/libs/attestation/Android.bp b/libs/attestation/Android.bp
index 2bf15d45eb..fddecc0ceb 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 e45a656d29..526427663b 100644
--- a/libs/binder/ActivityManager.cpp
+++ b/libs/binder/ActivityManager.cpp
@@ -21,6 +21,7 @@
#include <binder/ActivityManager.h>
#include <binder/Binder.h>
#include <binder/IServiceManager.h>
+#include <binder/ProcessState.h>
#include <utils/SystemClock.h>
@@ -33,27 +34,36 @@ ActivityManager::ActivityManager()
sp<IActivityManager> ActivityManager::getService()
{
std::lock_guard<Mutex> scoped_lock(mLock);
- int64_t startTime = 0;
sp<IActivityManager> service = mService;
- while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
- sp<IBinder> binder = defaultServiceManager()->checkService(String16("activity"));
- if (binder == nullptr) {
- // Wait for the activity service to come back...
- if (startTime == 0) {
- startTime = uptimeMillis();
- ALOGI("Waiting for activity service");
- } else if ((uptimeMillis() - startTime) > 1000000) {
- ALOGW("Waiting too long for activity service, giving up");
- service = nullptr;
- break;
- }
- usleep(25000);
- } else {
+ if (ProcessState::self()->isThreadPoolStarted()) {
+ if (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+ sp<IBinder> binder = defaultServiceManager()->waitForService(String16("activity"));
service = interface_cast<IActivityManager>(binder);
mService = service;
}
+ } else {
+ ALOGI("Thread pool not started. Polling for activity service.");
+ int64_t startTime = 0;
+ while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+ sp<IBinder> binder = defaultServiceManager()->checkService(String16("activity"));
+ if (binder == nullptr) {
+ // Wait for the activity service to come back...
+ if (startTime == 0) {
+ startTime = uptimeMillis();
+ ALOGI("Waiting for activity service");
+ } else if ((uptimeMillis() - startTime) > 1000000) {
+ ALOGW("Waiting too long for activity service, giving up");
+ service = nullptr;
+ break;
+ }
+ usleep(25000);
+ } else {
+ service = interface_cast<IActivityManager>(binder);
+ mService = service;
+ }
+ }
}
- return service;
+ return mService;
}
int ActivityManager::openContentUri(const String16& stringUri)
@@ -75,6 +85,20 @@ status_t ActivityManager::registerUidObserver(const sp<IUidObserver>& observer,
return DEAD_OBJECT;
}
+status_t ActivityManager::registerUidObserverForUids(const sp<IUidObserver>& observer,
+ const int32_t event, const int32_t cutpoint,
+ const String16& callingPackage,
+ const int32_t uids[], size_t nUids,
+ /*out*/ sp<IBinder>& observerToken) {
+ sp<IActivityManager> 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<IUidObserver>& observer)
{
sp<IActivityManager> service = getService();
@@ -85,6 +109,26 @@ status_t ActivityManager::unregisterUidObserver(const sp<IUidObserver>& observer
return DEAD_OBJECT;
}
+status_t ActivityManager::addUidToObserver(const sp<IBinder>& observerToken,
+ const String16& callingPackage, int32_t uid) {
+ sp<IActivityManager> 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<IBinder>& observerToken,
+ const String16& callingPackage, int32_t uid) {
+ sp<IActivityManager> 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<IActivityManager> service = getService();
diff --git a/libs/binder/Android.bp b/libs/binder/Android.bp
index baeb565b5e..090e35bc37 100644
--- a/libs/binder/Android.bp
+++ b/libs/binder/Android.bp
@@ -22,23 +22,51 @@ package {
}
cc_library_headers {
- name: "libbinder_headers",
+ name: "libbinder_headers_base",
export_include_dirs: ["include"],
vendor_available: true,
recovery_available: true,
host_supported: true,
- // TODO(b/153609531): remove when no longer needed.
native_bridge_supported: true,
header_libs: [
- "libbase_headers",
"libbinder_headers_platform_shared",
+ ],
+ export_header_lib_headers: [
+ "libbinder_headers_platform_shared",
+ ],
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media",
+ "com.android.media.swcodec",
+ ],
+ min_sdk_version: "29",
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
+cc_library_headers {
+ name: "libbinder_headers",
+ vendor_available: true,
+ recovery_available: true,
+ host_supported: true,
+ native_bridge_supported: true,
+
+ header_libs: [
+ "libbase_headers",
+ "libbinder_headers_base",
"libcutils_headers",
"libutils_headers",
],
export_header_lib_headers: [
"libbase_headers",
- "libbinder_headers_platform_shared",
+ "libbinder_headers_base",
"libcutils_headers",
"libutils_headers",
],
@@ -87,24 +115,27 @@ cc_defaults {
"RpcSession.cpp",
"RpcServer.cpp",
"RpcState.cpp",
+ "RpcTransportRaw.cpp",
"Stability.cpp",
"Status.cpp",
"TextOutput.cpp",
- "Trace.cpp",
"Utils.cpp",
+ "file.cpp",
],
- shared_libs: [
- "libcutils",
- "libutils",
- ],
-
- static_libs: [
- "libbase",
+ header_libs: [
+ "libbinder_headers_base",
],
- header_libs: [
- "libbinder_headers",
+ cflags: [
+ "-Wextra",
+ "-Wextra-semi",
+ "-Werror",
+ "-Wzero-as-null-pointer-constant",
+ "-Wreorder-init-list",
+ "-Wunused-const-variable",
+ "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
}
@@ -118,8 +149,8 @@ cc_defaults {
],
srcs: [
- "OS.cpp",
- "RpcTransportRaw.cpp",
+ "OS_android.cpp",
+ "OS_unix_base.cpp",
],
target: {
@@ -128,26 +159,18 @@ cc_defaults {
"UtilsHost.cpp",
],
},
+ android: {
+ lto: {
+ thin: true,
+ },
+ },
},
aidl: {
export_aidl_headers: true,
},
- cflags: [
- "-Wextra",
- "-Wextra-semi",
- "-Werror",
- "-Wzero-as-null-pointer-constant",
- "-Wreorder-init-list",
- "-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION",
- "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
- ],
product_variables: {
- binder32bit: {
- cflags: ["-DBINDER_IPC_32BIT=1"],
- },
-
debuggable: {
cflags: [
"-DBINDER_RPC_DEV_SERVERS",
@@ -157,11 +180,18 @@ cc_defaults {
},
shared_libs: [
+ "libcutils",
"liblog",
+ "libutils",
+ ],
+
+ static_libs: [
+ "libbase",
],
header_libs: [
"jni_headers",
+ "libbinder_headers",
],
export_header_lib_headers: [
@@ -191,6 +221,7 @@ cc_defaults {
"google-*",
"misc-*",
"performance*",
+ "-performance-move-const-arg", // b/273486801
"portability*",
],
}
@@ -198,6 +229,7 @@ cc_defaults {
cc_library_headers {
name: "trusty_mock_headers",
host_supported: true,
+ vendor_available: true,
export_include_dirs: [
"trusty/include",
@@ -212,14 +244,32 @@ cc_library_headers {
cc_defaults {
name: "trusty_mock_defaults",
host_supported: true,
+ vendor_available: true,
header_libs: [
+ "libbinder_headers_base",
+ "liblog_stub",
"trusty_mock_headers",
],
+ export_header_lib_headers: [
+ "libbinder_headers_base",
+ "liblog_stub",
+ "trusty_mock_headers",
+ ],
+
+ shared_libs: [
+ "libutils_binder_sdk",
+ ],
cflags: [
"-DBINDER_RPC_SINGLE_THREADED",
+ "-DBINDER_ENABLE_LIBLOG_ASSERT",
+ "-DBINDER_DISABLE_NATIVE_HANDLE",
+ "-DBINDER_DISABLE_BLOB",
+ "-DBINDER_NO_LIBBASE",
+ // TODO: switch to "vendor: true" rather than copying this
// Trusty libbinder uses vendor stability for its binders
+ "-D__ANDROID_VENDOR__",
"-D__ANDROID_VNDK__",
"-U__ANDROID__",
"-D__TRUSTY__",
@@ -249,7 +299,6 @@ cc_library_shared {
srcs: [
// Trusty-specific files
- "trusty/logging.cpp",
"trusty/OS.cpp",
"trusty/RpcServerTrusty.cpp",
"trusty/RpcTransportTipcTrusty.cpp",
@@ -280,18 +329,28 @@ cc_defaults {
"ServiceManagerHost.cpp",
],
},
+ android: {
+ shared_libs: [
+ "libapexsupport",
+ "libvndksupport",
+ ],
+ },
+ recovery: {
+ exclude_shared_libs: [
+ "libapexsupport",
+ "libvndksupport",
+ ],
+ },
+ native_bridge: {
+ exclude_shared_libs: [
+ "libapexsupport",
+ "libvndksupport",
+ ],
+ },
},
cflags: [
"-DBINDER_WITH_KERNEL_IPC",
],
- arch: {
- // TODO(b/254713216): undefined symbol in BufferedTextOutput::getBuffer
- riscv64: {
- lto: {
- thin: false,
- },
- },
- },
}
cc_library {
@@ -306,9 +365,6 @@ cc_library {
// for vndbinder
vendor_available: true,
- vndk: {
- enabled: true,
- },
recovery_available: true,
double_loadable: true,
// TODO(b/153609531): remove when no longer needed.
@@ -350,14 +406,49 @@ cc_library {
},
afdo: true,
+}
+
+cc_library_host_shared {
+ name: "libbinder_sdk",
- header_abi_checker: {
- diff_flags: ["-allow-adding-removing-weak-symbols"],
+ defaults: [
+ "libbinder_common_defaults",
+ ],
+
+ shared_libs: [
+ "libutils_binder_sdk",
+ ],
+
+ cflags: [
+ "-DBINDER_ENABLE_LIBLOG_ASSERT",
+ "-DBINDER_DISABLE_NATIVE_HANDLE",
+ "-DBINDER_DISABLE_BLOB",
+ "-DBINDER_NO_LIBBASE",
+ ],
+
+ header_libs: [
+ "liblog_stub",
+ ],
+
+ srcs: [
+ "OS_non_android_linux.cpp",
+ "OS_unix_base.cpp",
+ ],
+
+ visibility: [
+ ":__subpackages__",
+ ],
+
+ target: {
+ windows: {
+ enabled: false,
+ },
},
}
-cc_library_static {
+cc_library {
name: "libbinder_rpc_no_kernel",
+ vendor_available: true,
defaults: [
"libbinder_common_defaults",
"libbinder_android_defaults",
@@ -367,7 +458,39 @@ cc_library_static {
],
}
-cc_library_static {
+cc_library {
+ name: "libbinder_rpc_no_blob",
+ vendor_available: true,
+ defaults: [
+ "libbinder_common_defaults",
+ "libbinder_android_defaults",
+ "libbinder_kernel_defaults",
+ ],
+ cflags: [
+ "-DBINDER_DISABLE_BLOB",
+ ],
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
+cc_library {
+ name: "libbinder_rpc_no_native_handle",
+ vendor_available: true,
+ defaults: [
+ "libbinder_common_defaults",
+ "libbinder_android_defaults",
+ "libbinder_kernel_defaults",
+ ],
+ cflags: [
+ "-DBINDER_DISABLE_NATIVE_HANDLE",
+ ],
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
+cc_library {
name: "libbinder_rpc_single_threaded",
defaults: [
"libbinder_common_defaults",
@@ -382,7 +505,7 @@ cc_library_static {
],
}
-cc_library_static {
+cc_library {
name: "libbinder_rpc_single_threaded_no_kernel",
defaults: [
"libbinder_common_defaults",
@@ -410,6 +533,7 @@ cc_defaults {
cc_defaults {
name: "libbinder_tls_defaults",
defaults: ["libbinder_tls_shared_deps"],
+ vendor_available: true,
host_supported: true,
header_libs: [
@@ -436,7 +560,7 @@ cc_library_shared {
defaults: ["libbinder_tls_defaults"],
}
-cc_library_shared {
+cc_library {
name: "libbinder_trusty",
vendor: true,
srcs: [
@@ -492,9 +616,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",
@@ -534,7 +656,6 @@ cc_library {
"libbase",
"libbinder",
"libbinder_ndk",
- "libcutils_sockets",
"liblog",
"libutils",
],
@@ -549,18 +670,13 @@ cc_library {
// Do not expand the visibility.
visibility: [
":__subpackages__",
- "//packages/modules/Virtualization/javalib/jni",
- "//packages/modules/Virtualization/vm_payload",
+ "//packages/modules/Virtualization:__subpackages__",
"//device/google/cuttlefish/shared/minidroid:__subpackages__",
"//system/software_defined_vehicle:__subpackages__",
+ "//visibility:any_system_partition",
],
}
-filegroup {
- name: "libbinder_rpc_unstable_header",
- srcs: ["include_rpc_unstable/binder_rpc_unstable.hpp"],
-}
-
// libbinder historically contained additional interfaces that provided specific
// functionality in the platform but have nothing to do with binder itself. These
// are moved out of libbinder in order to avoid the overhead of their vtables.
@@ -629,4 +745,7 @@ cc_binary {
"libutils",
"android.debug_aidl-cpp",
],
+ static_libs: [
+ "libc++fs",
+ ],
}
diff --git a/libs/binder/Binder.cpp b/libs/binder/Binder.cpp
index 3e49656575..c57c9cdd62 100644
--- a/libs/binder/Binder.cpp
+++ b/libs/binder/Binder.cpp
@@ -19,8 +19,6 @@
#include <atomic>
#include <set>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
#include <binder/BpBinder.h>
#include <binder/IInterface.h>
#include <binder/IPCThreadState.h>
@@ -29,10 +27,8 @@
#include <binder/Parcel.h>
#include <binder/RecordedTransaction.h>
#include <binder/RpcServer.h>
-#include <cutils/compiler.h>
-#include <private/android_filesystem_config.h>
+#include <binder/unique_fd.h>
#include <pthread.h>
-#include <utils/misc.h>
#include <inttypes.h>
#include <stdio.h>
@@ -42,10 +38,15 @@
#endif
#include "BuildFlags.h"
+#include "OS.h"
#include "RpcState.h"
namespace android {
+using android::binder::unique_fd;
+
+constexpr uid_t kUidRoot = 0;
+
// Service implementations inherit from BBinder and IBinder, and this is frozen
// in prebuilts.
#ifdef __LP64__
@@ -58,15 +59,15 @@ static_assert(sizeof(BBinder) == 20);
// global b/c b/230079120 - consistent symbol table
#ifdef BINDER_RPC_DEV_SERVERS
-bool kEnableRpcDevServers = true;
+constexpr bool kEnableRpcDevServers = true;
#else
-bool kEnableRpcDevServers = false;
+constexpr bool kEnableRpcDevServers = false;
#endif
#ifdef BINDER_ENABLE_RECORDING
-bool kEnableRecording = true;
+constexpr bool kEnableRecording = true;
#else
-bool kEnableRecording = false;
+constexpr bool kEnableRecording = false;
#endif
// Log any reply transactions for which the data exceeds this size
@@ -168,8 +169,7 @@ status_t IBinder::getDebugPid(pid_t* out) {
return OK;
}
-status_t IBinder::setRpcClientDebug(android::base::unique_fd socketFd,
- const sp<IBinder>& keepAliveBinder) {
+status_t IBinder::setRpcClientDebug(unique_fd socketFd, const sp<IBinder>& keepAliveBinder) {
if (!kEnableRpcDevServers) {
ALOGW("setRpcClientDebug disallowed because RPC is not enabled");
return INVALID_OPERATION;
@@ -270,11 +270,11 @@ public:
bool mInheritRt = false;
// for below objects
- Mutex mLock;
+ RpcMutex mLock;
std::set<sp<RpcServerLink>> mRpcServerLinks;
BpBinder::ObjectManager mObjects;
- android::base::unique_fd mRecordingFd;
+ unique_fd mRecordingFd;
};
// ---------------------------------------------------------------------------
@@ -301,14 +301,14 @@ status_t BBinder::startRecordingTransactions(const Parcel& data) {
return INVALID_OPERATION;
}
uid_t uid = IPCThreadState::self()->getCallingUid();
- if (uid != AID_ROOT) {
+ if (uid != kUidRoot) {
ALOGE("Binder recording not allowed because client %" PRIu32 " is not root", uid);
return PERMISSION_DENIED;
}
Extras* e = getOrCreateExtras();
- AutoMutex lock(e->mLock);
+ RpcMutexUniqueLock lock(e->mLock);
if (mRecordingOn) {
- LOG(INFO) << "Could not start Binder recording. Another is already in progress.";
+ ALOGI("Could not start Binder recording. Another is already in progress.");
return INVALID_OPERATION;
} else {
status_t readStatus = data.readUniqueFileDescriptor(&(e->mRecordingFd));
@@ -316,7 +316,7 @@ status_t BBinder::startRecordingTransactions(const Parcel& data) {
return readStatus;
}
mRecordingOn = true;
- LOG(INFO) << "Started Binder recording.";
+ ALOGI("Started Binder recording.");
return NO_ERROR;
}
}
@@ -331,19 +331,19 @@ status_t BBinder::stopRecordingTransactions() {
return INVALID_OPERATION;
}
uid_t uid = IPCThreadState::self()->getCallingUid();
- if (uid != AID_ROOT) {
+ if (uid != kUidRoot) {
ALOGE("Binder recording not allowed because client %" PRIu32 " is not root", uid);
return PERMISSION_DENIED;
}
Extras* e = getOrCreateExtras();
- AutoMutex lock(e->mLock);
+ RpcMutexUniqueLock lock(e->mLock);
if (mRecordingOn) {
e->mRecordingFd.reset();
mRecordingOn = false;
- LOG(INFO) << "Stopped Binder recording.";
+ ALOGI("Stopped Binder recording.");
return NO_ERROR;
} else {
- LOG(INFO) << "Could not stop Binder recording. One is not in progress.";
+ ALOGI("Could not stop Binder recording. One is not in progress.");
return INVALID_OPERATION;
}
}
@@ -377,11 +377,11 @@ status_t BBinder::transact(
err = stopRecordingTransactions();
break;
case EXTENSION_TRANSACTION:
- CHECK(reply != nullptr);
+ LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
err = reply->writeStrongBinder(getExtension());
break;
case DEBUG_PID_TRANSACTION:
- CHECK(reply != nullptr);
+ LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
err = reply->writeInt32(getDebugPid());
break;
case SET_RPC_CLIENT_TRANSACTION: {
@@ -402,9 +402,9 @@ status_t BBinder::transact(
}
}
- if (CC_UNLIKELY(kEnableKernelIpc && mRecordingOn && code != START_RECORDING_TRANSACTION)) {
+ if (kEnableKernelIpc && mRecordingOn && code != START_RECORDING_TRANSACTION) [[unlikely]] {
Extras* e = mExtras.load(std::memory_order_acquire);
- AutoMutex lock(e->mLock);
+ RpcMutexUniqueLock lock(e->mLock);
if (mRecordingOn) {
Parcel emptyReply;
timespec ts;
@@ -414,10 +414,10 @@ status_t BBinder::transact(
reply ? *reply : emptyReply, err);
if (transaction) {
if (status_t err = transaction->dumpToFile(e->mRecordingFd); err != NO_ERROR) {
- LOG(INFO) << "Failed to dump RecordedTransaction to file with error " << err;
+ ALOGI("Failed to dump RecordedTransaction to file with error %d", err);
}
} else {
- LOG(INFO) << "Failed to create RecordedTransaction object.";
+ ALOGI("Failed to create RecordedTransaction object.");
}
}
}
@@ -451,7 +451,7 @@ void* BBinder::attachObject(const void* objectID, void* object, void* cleanupCoo
Extras* e = getOrCreateExtras();
LOG_ALWAYS_FATAL_IF(!e, "no memory");
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
return e->mObjects.attach(objectID, object, cleanupCookie, func);
}
@@ -460,7 +460,7 @@ void* BBinder::findObject(const void* objectID) const
Extras* e = mExtras.load(std::memory_order_acquire);
if (!e) return nullptr;
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
return e->mObjects.find(objectID);
}
@@ -468,7 +468,7 @@ void* BBinder::detachObject(const void* objectID) {
Extras* e = mExtras.load(std::memory_order_acquire);
if (!e) return nullptr;
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
return e->mObjects.detach(objectID);
}
@@ -476,7 +476,7 @@ void BBinder::withLock(const std::function<void()>& doWithLock) {
Extras* e = getOrCreateExtras();
LOG_ALWAYS_FATAL_IF(!e, "no memory");
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
doWithLock();
}
@@ -484,7 +484,7 @@ sp<IBinder> BBinder::lookupOrCreateWeak(const void* objectID, object_make_func m
const void* makeArgs) {
Extras* e = getOrCreateExtras();
LOG_ALWAYS_FATAL_IF(!e, "no memory");
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
return e->mObjects.lookupOrCreateWeak(objectID, make, makeArgs);
}
@@ -635,13 +635,13 @@ status_t BBinder::setRpcClientDebug(const Parcel& data) {
return INVALID_OPERATION;
}
uid_t uid = IPCThreadState::self()->getCallingUid();
- if (uid != AID_ROOT) {
+ if (uid != kUidRoot) {
ALOGE("%s: not allowed because client %" PRIu32 " is not root", __PRETTY_FUNCTION__, uid);
return PERMISSION_DENIED;
}
status_t status;
bool hasSocketFd;
- android::base::unique_fd clientFd;
+ unique_fd clientFd;
if (status = data.readBool(&hasSocketFd); status != OK) return status;
if (hasSocketFd) {
@@ -653,8 +653,7 @@ status_t BBinder::setRpcClientDebug(const Parcel& data) {
return setRpcClientDebug(std::move(clientFd), keepAliveBinder);
}
-status_t BBinder::setRpcClientDebug(android::base::unique_fd socketFd,
- const sp<IBinder>& keepAliveBinder) {
+status_t BBinder::setRpcClientDebug(unique_fd socketFd, const sp<IBinder>& keepAliveBinder) {
if (!kEnableRpcDevServers) {
ALOGW("%s: disallowed because RPC is not enabled", __PRETTY_FUNCTION__);
return INVALID_OPERATION;
@@ -691,7 +690,7 @@ status_t BBinder::setRpcClientDebug(android::base::unique_fd socketFd,
auto weakThis = wp<BBinder>::fromExisting(this);
Extras* e = getOrCreateExtras();
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
auto rpcServer = RpcServer::make();
LOG_ALWAYS_FATAL_IF(rpcServer == nullptr, "RpcServer::make returns null");
auto link = sp<RpcServerLink>::make(rpcServer, keepAliveBinder, weakThis);
@@ -705,7 +704,7 @@ status_t BBinder::setRpcClientDebug(android::base::unique_fd socketFd,
return status;
}
rpcServer->setMaxThreads(binderThreadPoolMaxCount);
- LOG(INFO) << "RpcBinder: Started Binder debug on " << getInterfaceDescriptor();
+ ALOGI("RpcBinder: Started Binder debug on %s", String8(getInterfaceDescriptor()).c_str());
rpcServer->start();
e->mRpcServerLinks.emplace(link);
LOG_RPC_DETAIL("%s(fd=%d) successful", __PRETTY_FUNCTION__, socketFdForPrint);
@@ -715,7 +714,7 @@ status_t BBinder::setRpcClientDebug(android::base::unique_fd socketFd,
void BBinder::removeRpcServerLink(const sp<RpcServerLink>& link) {
Extras* e = mExtras.load(std::memory_order_acquire);
if (!e) return;
- AutoMutex _l(e->mLock);
+ RpcMutexUniqueLock _l(e->mLock);
(void)e->mRpcServerLinks.erase(link);
}
@@ -723,20 +722,20 @@ BBinder::~BBinder()
{
if (!wasParceled()) {
if (getExtension()) {
- ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
+ ALOGW("Binder %p destroyed with extension attached before being parceled.", this);
}
if (isRequestingSid()) {
- ALOGW("Binder %p destroyed when requesting SID before being parceled.", this);
+ ALOGW("Binder %p destroyed when requesting SID before being parceled.", this);
}
if (isInheritRt()) {
- ALOGW("Binder %p destroyed after setInheritRt before being parceled.", this);
+ ALOGW("Binder %p destroyed after setInheritRt before being parceled.", this);
}
#ifdef __linux__
if (getMinSchedulerPolicy() != SCHED_NORMAL) {
- ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
+ ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
}
if (getMinSchedulerPriority() != 0) {
- ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
+ ALOGW("Binder %p destroyed after setMinSchedulerPolicy before being parceled.", this);
}
#endif // __linux__
}
@@ -752,7 +751,7 @@ status_t BBinder::onTransact(
{
switch (code) {
case INTERFACE_TRANSACTION:
- CHECK(reply != nullptr);
+ LOG_ALWAYS_FATAL_IF(reply == nullptr, "reply == nullptr");
reply->writeString16(getInterfaceDescriptor());
return NO_ERROR;
@@ -795,7 +794,7 @@ status_t BBinder::onTransact(
}
case SYSPROPS_TRANSACTION: {
- report_sysprop_change();
+ if (!binder::os::report_sysprop_change()) return INVALID_OPERATION;
return NO_ERROR;
}
diff --git a/libs/binder/BpBinder.cpp b/libs/binder/BpBinder.cpp
index 53852d88ed..54457fc568 100644
--- a/libs/binder/BpBinder.cpp
+++ b/libs/binder/BpBinder.cpp
@@ -23,43 +23,52 @@
#include <binder/IResultReceiver.h>
#include <binder/RpcSession.h>
#include <binder/Stability.h>
-#include <cutils/compiler.h>
-#include <utils/Log.h>
#include <stdio.h>
#include "BuildFlags.h"
-
-#include <android-base/file.h>
+#include "file.h"
//#undef ALOGV
//#define ALOGV(...) fprintf(stderr, __VA_ARGS__)
namespace android {
+using android::binder::unique_fd;
+
// ---------------------------------------------------------------------------
-Mutex BpBinder::sTrackingLock;
+RpcMutex BpBinder::sTrackingLock;
std::unordered_map<int32_t, uint32_t> BpBinder::sTrackingMap;
std::unordered_map<int32_t, uint32_t> BpBinder::sLastLimitCallbackMap;
int BpBinder::sNumTrackedUids = 0;
std::atomic_bool BpBinder::sCountByUidEnabled(false);
binder_proxy_limit_callback BpBinder::sLimitCallback;
+binder_proxy_warning_callback BpBinder::sWarningCallback;
bool BpBinder::sBinderProxyThrottleCreate = false;
-static StaticString16 kDescriptorUninit(u"<uninit descriptor>");
+static StaticString16 kDescriptorUninit(u"");
// Arbitrarily high value that probably distinguishes a bad behaving app
uint32_t BpBinder::sBinderProxyCountHighWatermark = 2500;
// Another arbitrary value a binder count needs to drop below before another callback will be called
uint32_t BpBinder::sBinderProxyCountLowWatermark = 2000;
+// Arbitrary value between low and high watermark on a bad behaving app to
+// trigger a warning callback.
+uint32_t BpBinder::sBinderProxyCountWarningWatermark = 2250;
+
+std::atomic<uint32_t> BpBinder::sBinderProxyCount(0);
+std::atomic<uint32_t> BpBinder::sBinderProxyCountWarned(0);
+
+static constexpr uint32_t kBinderProxyCountWarnInterval = 5000;
// Log any transactions for which the data exceeds this size
#define LOG_TRANSACTIONS_OVER_SIZE (300 * 1024)
enum {
LIMIT_REACHED_MASK = 0x80000000, // A flag denoting that the limit has been reached
- COUNTING_VALUE_MASK = 0x7FFFFFFF, // A mask of the remaining bits for the count value
+ WARNING_REACHED_MASK = 0x40000000, // A flag denoting that the warning has been reached
+ COUNTING_VALUE_MASK = 0x3FFFFFFF, // A mask of the remaining bits for the count value
};
BpBinder::ObjectManager::ObjectManager()
@@ -159,9 +168,9 @@ sp<BpBinder> BpBinder::create(int32_t handle) {
int32_t trackedUid = -1;
if (sCountByUidEnabled) {
trackedUid = IPCThreadState::self()->getCallingUid();
- AutoMutex _l(sTrackingLock);
+ RpcMutexUniqueLock _l(sTrackingLock);
uint32_t trackedValue = sTrackingMap[trackedUid];
- if (CC_UNLIKELY(trackedValue & LIMIT_REACHED_MASK)) {
+ if (trackedValue & LIMIT_REACHED_MASK) [[unlikely]] {
if (sBinderProxyThrottleCreate) {
return nullptr;
}
@@ -177,7 +186,13 @@ sp<BpBinder> BpBinder::create(int32_t handle) {
sLastLimitCallbackMap[trackedUid] = trackedValue;
}
} else {
- if ((trackedValue & COUNTING_VALUE_MASK) >= sBinderProxyCountHighWatermark) {
+ uint32_t currentValue = trackedValue & COUNTING_VALUE_MASK;
+ if (currentValue >= sBinderProxyCountWarningWatermark
+ && currentValue < sBinderProxyCountHighWatermark
+ && ((trackedValue & WARNING_REACHED_MASK) == 0)) [[unlikely]] {
+ sTrackingMap[trackedUid] |= WARNING_REACHED_MASK;
+ if (sWarningCallback) sWarningCallback(trackedUid);
+ } else if (currentValue >= sBinderProxyCountHighWatermark) {
ALOGE("Too many binder proxy objects sent to uid %d from uid %d (%d proxies held)",
getuid(), trackedUid, trackedValue);
sTrackingMap[trackedUid] |= LIMIT_REACHED_MASK;
@@ -193,6 +208,18 @@ sp<BpBinder> BpBinder::create(int32_t handle) {
}
sTrackingMap[trackedUid]++;
}
+ uint32_t numProxies = sBinderProxyCount.fetch_add(1, std::memory_order_relaxed);
+ uint32_t numLastWarned = sBinderProxyCountWarned.load(std::memory_order_relaxed);
+ uint32_t numNextWarn = numLastWarned + kBinderProxyCountWarnInterval;
+ if (numProxies >= numNextWarn) {
+ // Multiple threads can get here, make sure only one of them gets to
+ // update the warn counter.
+ if (sBinderProxyCountWarned.compare_exchange_strong(numLastWarned,
+ numNextWarn,
+ std::memory_order_relaxed)) {
+ ALOGW("Unexpectedly many live BinderProxies: %d\n", numProxies);
+ }
+ }
return sp<BpBinder>::make(BinderHandle{handle}, trackedUid);
}
@@ -260,8 +287,8 @@ std::optional<int32_t> BpBinder::getDebugBinderHandle() const {
}
bool BpBinder::isDescriptorCached() const {
- Mutex::Autolock _l(mLock);
- return mDescriptorCache.string() != kDescriptorUninit.string();
+ RpcMutexUniqueLock _l(mLock);
+ return mDescriptorCache.c_str() != kDescriptorUninit.c_str();
}
const String16& BpBinder::getInterfaceDescriptor() const
@@ -276,10 +303,10 @@ const String16& BpBinder::getInterfaceDescriptor() const
status_t err = thiz->transact(INTERFACE_TRANSACTION, data, &reply);
if (err == NO_ERROR) {
String16 res(reply.readString16());
- Mutex::Autolock _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
// mDescriptorCache could have been assigned while the lock was
// released.
- if (mDescriptorCache.string() == kDescriptorUninit.string()) mDescriptorCache = res;
+ if (mDescriptorCache.c_str() == kDescriptorUninit.c_str()) mDescriptorCache = res;
}
}
@@ -303,7 +330,7 @@ status_t BpBinder::pingBinder()
return transact(PING_TRANSACTION, data, &reply);
}
-status_t BpBinder::startRecordingBinder(const android::base::unique_fd& fd) {
+status_t BpBinder::startRecordingBinder(const unique_fd& fd) {
Parcel send, reply;
send.writeUniqueFileDescriptor(fd);
return transact(START_RECORDING_TRANSACTION, send, &reply);
@@ -347,7 +374,7 @@ status_t BpBinder::transact(
Stability::Level required = privateVendor ? Stability::VENDOR
: Stability::getLocalLevel();
- if (CC_UNLIKELY(!Stability::check(stability, required))) {
+ if (!Stability::check(stability, required)) [[unlikely]] {
ALOGE("Cannot do a user transaction on a %s binder (%s) in a %s context.",
Stability::levelString(stability).c_str(),
String8(getInterfaceDescriptor()).c_str(),
@@ -357,7 +384,7 @@ status_t BpBinder::transact(
}
status_t status;
- if (CC_UNLIKELY(isRpcBinder())) {
+ if (isRpcBinder()) [[unlikely]] {
status = rpcSession()->transact(sp<IBinder>::fromExisting(this), code, data, reply,
flags);
} else {
@@ -369,7 +396,7 @@ status_t BpBinder::transact(
status = IPCThreadState::self()->transact(binderHandle(), code, data, reply, flags);
}
if (data.dataSize() > LOG_TRANSACTIONS_OVER_SIZE) {
- Mutex::Autolock _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
ALOGW("Large outgoing transaction of %zu bytes, interface descriptor %s, code %d",
data.dataSize(), String8(mDescriptorCache).c_str(), code);
}
@@ -415,7 +442,7 @@ status_t BpBinder::linkToDeath(
"linkToDeath(): recipient must be non-NULL");
{
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
if (!mObitsSent) {
if (!mObituaries) {
@@ -451,7 +478,7 @@ status_t BpBinder::unlinkToDeath(
return INVALID_OPERATION;
}
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
if (mObitsSent) {
return DEAD_OBJECT;
@@ -539,30 +566,30 @@ void BpBinder::reportOneDeath(const Obituary& obit)
void* BpBinder::attachObject(const void* objectID, void* object, void* cleanupCookie,
object_cleanup_func func) {
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
ALOGV("Attaching object %p to binder %p (manager=%p)", object, this, &mObjects);
return mObjects.attach(objectID, object, cleanupCookie, func);
}
void* BpBinder::findObject(const void* objectID) const
{
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
return mObjects.find(objectID);
}
void* BpBinder::detachObject(const void* objectID) {
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
return mObjects.detach(objectID);
}
void BpBinder::withLock(const std::function<void()>& doWithLock) {
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
doWithLock();
}
sp<IBinder> BpBinder::lookupOrCreateWeak(const void* objectID, object_make_func make,
const void* makeArgs) {
- AutoMutex _l(mLock);
+ RpcMutexUniqueLock _l(mLock);
return mObjects.lookupOrCreateWeak(objectID, make, makeArgs);
}
@@ -572,7 +599,9 @@ BpBinder* BpBinder::remoteBinder()
}
BpBinder::~BpBinder() {
- if (CC_UNLIKELY(isRpcBinder())) return;
+ if (isRpcBinder()) [[unlikely]] {
+ return;
+ }
if constexpr (!kEnableKernelIpc) {
LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
@@ -584,19 +613,18 @@ BpBinder::~BpBinder() {
IPCThreadState* ipc = IPCThreadState::self();
if (mTrackedUid >= 0) {
- AutoMutex _l(sTrackingLock);
+ RpcMutexUniqueLock _l(sTrackingLock);
uint32_t trackedValue = sTrackingMap[mTrackedUid];
- if (CC_UNLIKELY((trackedValue & COUNTING_VALUE_MASK) == 0)) {
+ if ((trackedValue & COUNTING_VALUE_MASK) == 0) [[unlikely]] {
ALOGE("Unexpected Binder Proxy tracking decrement in %p handle %d\n", this,
binderHandle());
} else {
- if (CC_UNLIKELY(
- (trackedValue & LIMIT_REACHED_MASK) &&
- ((trackedValue & COUNTING_VALUE_MASK) <= sBinderProxyCountLowWatermark)
- )) {
+ auto countingValue = trackedValue & COUNTING_VALUE_MASK;
+ if ((trackedValue & (LIMIT_REACHED_MASK | WARNING_REACHED_MASK)) &&
+ (countingValue <= sBinderProxyCountLowWatermark)) [[unlikely]] {
ALOGI("Limit reached bit reset for uid %d (fewer than %d proxies from uid %d held)",
getuid(), sBinderProxyCountLowWatermark, mTrackedUid);
- sTrackingMap[mTrackedUid] &= ~LIMIT_REACHED_MASK;
+ sTrackingMap[mTrackedUid] &= ~(LIMIT_REACHED_MASK | WARNING_REACHED_MASK);
sLastLimitCallbackMap.erase(mTrackedUid);
}
if (--sTrackingMap[mTrackedUid] == 0) {
@@ -604,6 +632,7 @@ BpBinder::~BpBinder() {
}
}
}
+ --sBinderProxyCount;
if (ipc) {
ipc->expungeHandle(binderHandle(), this);
@@ -612,7 +641,9 @@ BpBinder::~BpBinder() {
}
void BpBinder::onFirstRef() {
- if (CC_UNLIKELY(isRpcBinder())) return;
+ if (isRpcBinder()) [[unlikely]] {
+ return;
+ }
if constexpr (!kEnableKernelIpc) {
LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
@@ -625,7 +656,7 @@ void BpBinder::onFirstRef() {
}
void BpBinder::onLastStrongRef(const void* /*id*/) {
- if (CC_UNLIKELY(isRpcBinder())) {
+ if (isRpcBinder()) [[unlikely]] {
(void)rpcSession()->sendDecStrong(this);
return;
}
@@ -666,7 +697,9 @@ void BpBinder::onLastStrongRef(const void* /*id*/) {
bool BpBinder::onIncStrongAttempted(uint32_t /*flags*/, const void* /*id*/)
{
// RPC binder doesn't currently support inc from weak binders
- if (CC_UNLIKELY(isRpcBinder())) return false;
+ if (isRpcBinder()) [[unlikely]] {
+ return false;
+ }
if constexpr (!kEnableKernelIpc) {
LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
@@ -680,7 +713,7 @@ bool BpBinder::onIncStrongAttempted(uint32_t /*flags*/, const void* /*id*/)
uint32_t BpBinder::getBinderProxyCount(uint32_t uid)
{
- AutoMutex _l(sTrackingLock);
+ RpcMutexUniqueLock _l(sTrackingLock);
auto it = sTrackingMap.find(uid);
if (it != sTrackingMap.end()) {
return it->second & COUNTING_VALUE_MASK;
@@ -688,9 +721,14 @@ uint32_t BpBinder::getBinderProxyCount(uint32_t uid)
return 0;
}
+uint32_t BpBinder::getBinderProxyCount()
+{
+ return sBinderProxyCount.load();
+}
+
void BpBinder::getCountByUid(Vector<uint32_t>& uids, Vector<uint32_t>& counts)
{
- AutoMutex _l(sTrackingLock);
+ RpcMutexUniqueLock _l(sTrackingLock);
uids.setCapacity(sTrackingMap.size());
counts.setCapacity(sTrackingMap.size());
for (const auto& it : sTrackingMap) {
@@ -703,15 +741,18 @@ void BpBinder::enableCountByUid() { sCountByUidEnabled.store(true); }
void BpBinder::disableCountByUid() { sCountByUidEnabled.store(false); }
void BpBinder::setCountByUidEnabled(bool enable) { sCountByUidEnabled.store(enable); }
-void BpBinder::setLimitCallback(binder_proxy_limit_callback cb) {
- AutoMutex _l(sTrackingLock);
- sLimitCallback = cb;
+void BpBinder::setBinderProxyCountEventCallback(binder_proxy_limit_callback cbl,
+ binder_proxy_warning_callback cbw) {
+ RpcMutexUniqueLock _l(sTrackingLock);
+ sLimitCallback = std::move(cbl);
+ sWarningCallback = std::move(cbw);
}
-void BpBinder::setBinderProxyCountWatermarks(int high, int low) {
- AutoMutex _l(sTrackingLock);
+void BpBinder::setBinderProxyCountWatermarks(int high, int low, int warning) {
+ RpcMutexUniqueLock _l(sTrackingLock);
sBinderProxyCountHighWatermark = high;
sBinderProxyCountLowWatermark = low;
+ sBinderProxyCountWarningWatermark = warning;
}
// ---------------------------------------------------------------------------
diff --git a/libs/binder/Debug.cpp b/libs/binder/Debug.cpp
index c6e4fb378d..7ae616e2b0 100644
--- a/libs/binder/Debug.cpp
+++ b/libs/binder/Debug.cpp
@@ -19,8 +19,6 @@
#include <binder/ProcessState.h>
-#include <utils/misc.h>
-
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
diff --git a/libs/binder/FdTrigger.cpp b/libs/binder/FdTrigger.cpp
index 8ee6cb0615..455a4338e5 100644
--- a/libs/binder/FdTrigger.cpp
+++ b/libs/binder/FdTrigger.cpp
@@ -21,16 +21,20 @@
#include <poll.h>
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
+#include <binder/Functional.h>
+#include "FdUtils.h"
#include "RpcState.h"
+#include "Utils.h"
+
namespace android {
+using namespace android::binder::impl;
+
std::unique_ptr<FdTrigger> FdTrigger::make() {
auto ret = std::make_unique<FdTrigger>();
#ifndef BINDER_RPC_SINGLE_THREADED
- if (!android::base::Pipe(&ret->mRead, &ret->mWrite)) {
+ if (!binder::Pipe(&ret->mRead, &ret->mWrite)) {
ALOGE("Could not create pipe %s", strerror(errno));
return nullptr;
}
@@ -50,7 +54,7 @@ bool FdTrigger::isTriggered() {
#ifdef BINDER_RPC_SINGLE_THREADED
return mTriggered;
#else
- return mWrite == -1;
+ return !mWrite.ok();
#endif
}
@@ -74,10 +78,9 @@ status_t FdTrigger::triggerablePoll(const android::RpcTransportFd& transportFd,
"Only one thread should be polling on Fd!");
transportFd.setPollingState(true);
- auto pollingStateGuard =
- android::base::make_scope_guard([&]() { transportFd.setPollingState(false); });
+ auto pollingStateGuard = make_scope_guard([&]() { transportFd.setPollingState(false); });
- int ret = TEMP_FAILURE_RETRY(poll(pfd, arraysize(pfd), -1));
+ int ret = TEMP_FAILURE_RETRY(poll(pfd, countof(pfd), -1));
if (ret < 0) {
return -errno;
}
diff --git a/libs/binder/FdTrigger.h b/libs/binder/FdTrigger.h
index 5fbf2908ad..e4a02839dc 100644
--- a/libs/binder/FdTrigger.h
+++ b/libs/binder/FdTrigger.h
@@ -17,11 +17,10 @@
#include <memory>
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
#include <utils/Errors.h>
#include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
namespace android {
@@ -62,8 +61,8 @@ private:
#ifdef BINDER_RPC_SINGLE_THREADED
bool mTriggered = false;
#else
- base::unique_fd mWrite;
- base::unique_fd mRead;
+ binder::unique_fd mWrite;
+ binder::unique_fd mRead;
#endif
};
} // namespace android
diff --git a/libs/binder/FdUtils.h b/libs/binder/FdUtils.h
new file mode 100644
index 0000000000..52ae48728f
--- /dev/null
+++ b/libs/binder/FdUtils.h
@@ -0,0 +1,99 @@
+/*
+ * 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 <binder/unique_fd.h>
+
+#if defined(_WIN32) || defined(__TRUSTY__)
+// Pipe and Socketpair are missing there
+#elif !defined(BINDER_NO_LIBBASE)
+
+namespace android::binder {
+using android::base::Pipe;
+using android::base::Socketpair;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <sys/socket.h>
+
+namespace android::binder {
+
+// Inline functions, so that they can be used header-only.
+
+// See pipe(2).
+// This helper hides the details of converting to unique_fd, and also hides the
+// fact that macOS doesn't support O_CLOEXEC or O_NONBLOCK directly.
+inline bool Pipe(unique_fd* read, unique_fd* write, int flags = O_CLOEXEC) {
+ int pipefd[2];
+
+#if defined(__APPLE__)
+ if (flags & ~(O_CLOEXEC | O_NONBLOCK)) {
+ return false;
+ }
+ if (pipe(pipefd) != 0) {
+ return false;
+ }
+
+ if (flags & O_CLOEXEC) {
+ if (fcntl(pipefd[0], F_SETFD, FD_CLOEXEC) != 0 ||
+ fcntl(pipefd[1], F_SETFD, FD_CLOEXEC) != 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return false;
+ }
+ }
+ if (flags & O_NONBLOCK) {
+ if (fcntl(pipefd[0], F_SETFL, O_NONBLOCK) != 0 ||
+ fcntl(pipefd[1], F_SETFL, O_NONBLOCK) != 0) {
+ close(pipefd[0]);
+ close(pipefd[1]);
+ return false;
+ }
+ }
+#else
+ if (pipe2(pipefd, flags) != 0) {
+ return false;
+ }
+#endif
+
+ read->reset(pipefd[0]);
+ write->reset(pipefd[1]);
+ return true;
+}
+
+// See socketpair(2).
+// This helper hides the details of converting to unique_fd.
+inline bool Socketpair(int domain, int type, int protocol, unique_fd* left, unique_fd* right) {
+ int sockfd[2];
+ if (socketpair(domain, type, protocol, sockfd) != 0) {
+ return false;
+ }
+ left->reset(sockfd[0]);
+ right->reset(sockfd[1]);
+ return true;
+}
+
+// See socketpair(2).
+// This helper hides the details of converting to unique_fd.
+inline bool Socketpair(int type, unique_fd* left, unique_fd* right) {
+ return Socketpair(AF_UNIX, type, 0, left, right);
+}
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/IActivityManager.cpp b/libs/binder/IActivityManager.cpp
index 08169f5538..152c815ec3 100644
--- a/libs/binder/IActivityManager.cpp
+++ b/libs/binder/IActivityManager.cpp
@@ -52,8 +52,8 @@ public:
}
} else {
// An exception was thrown back; fall through to return failure
- ALOGD("openContentUri(%s) caught exception %d\n",
- String8(stringUri).string(), exceptionCode);
+ ALOGD("openContentUri(%s) caught exception %d\n", String8(stringUri).c_str(),
+ exceptionCode);
}
}
return fd;
@@ -77,6 +77,30 @@ public:
return OK;
}
+ virtual status_t registerUidObserverForUids(const sp<IUidObserver>& observer,
+ const int32_t event, const int32_t cutpoint,
+ const String16& callingPackage,
+ const int32_t uids[], size_t nUids,
+ /*out*/ sp<IBinder>& 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<IUidObserver>& observer)
{
Parcel data, reply;
@@ -89,6 +113,34 @@ public:
return OK;
}
+ virtual status_t addUidToObserver(const sp<IBinder>& 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<IBinder>& 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,53 @@ 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("%s: FGS Logger Transaction failed, %d", __func__, 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("%s: FGS Logger Transaction failed, %d", __func__, 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_STATE_CHANGED_TRANSACTION, data, &reply,
+ IBinder::FLAG_ONEWAY);
+ if (err != NO_ERROR || ((err = reply.readExceptionCode()) != NO_ERROR)) {
+ ALOGD("%s: FGS Logger Transaction failed, %d", __func__, err);
+ return err;
+ }
+ return NO_ERROR;
+ }
};
// ------------------------------------------------------------------------------------
diff --git a/libs/binder/IBatteryStats.cpp b/libs/binder/IBatteryStats.cpp
index 0de804c3c2..69b11c0ee9 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/IInterface.cpp b/libs/binder/IInterface.cpp
index 2780bd4cd9..dea26038cf 100644
--- a/libs/binder/IInterface.cpp
+++ b/libs/binder/IInterface.cpp
@@ -15,7 +15,6 @@
*/
#define LOG_TAG "IInterface"
-#include <utils/Log.h>
#include <binder/IInterface.h>
namespace android {
diff --git a/libs/binder/IPCThreadState.cpp b/libs/binder/IPCThreadState.cpp
index da58251149..ef96f803c3 100644
--- a/libs/binder/IPCThreadState.cpp
+++ b/libs/binder/IPCThreadState.cpp
@@ -22,7 +22,6 @@
#include <binder/BpBinder.h>
#include <binder/TextOutput.h>
-#include <android-base/macros.h>
#include <cutils/sched_policy.h>
#include <utils/CallStack.h>
#include <utils/Log.h>
@@ -68,28 +67,28 @@ namespace android {
// Static const and functions will be optimized out if not used,
// when LOG_NDEBUG and references in IF_LOG_COMMANDS() are optimized out.
-static const char *kReturnStrings[] = {
- "BR_ERROR",
- "BR_OK",
- "BR_TRANSACTION",
- "BR_REPLY",
- "BR_ACQUIRE_RESULT",
- "BR_DEAD_REPLY",
- "BR_TRANSACTION_COMPLETE",
- "BR_INCREFS",
- "BR_ACQUIRE",
- "BR_RELEASE",
- "BR_DECREFS",
- "BR_ATTEMPT_ACQUIRE",
- "BR_NOOP",
- "BR_SPAWN_LOOPER",
- "BR_FINISHED",
- "BR_DEAD_BINDER",
- "BR_CLEAR_DEATH_NOTIFICATION_DONE",
- "BR_FAILED_REPLY",
- "BR_FROZEN_REPLY",
- "BR_ONEWAY_SPAM_SUSPECT",
- "BR_TRANSACTION_SEC_CTX",
+static const char* kReturnStrings[] = {
+ "BR_ERROR",
+ "BR_OK",
+ "BR_TRANSACTION/BR_TRANSACTION_SEC_CTX",
+ "BR_REPLY",
+ "BR_ACQUIRE_RESULT",
+ "BR_DEAD_REPLY",
+ "BR_TRANSACTION_COMPLETE",
+ "BR_INCREFS",
+ "BR_ACQUIRE",
+ "BR_RELEASE",
+ "BR_DECREFS",
+ "BR_ATTEMPT_ACQUIRE",
+ "BR_NOOP",
+ "BR_SPAWN_LOOPER",
+ "BR_FINISHED",
+ "BR_DEAD_BINDER",
+ "BR_CLEAR_DEATH_NOTIFICATION_DONE",
+ "BR_FAILED_REPLY",
+ "BR_FROZEN_REPLY",
+ "BR_ONEWAY_SPAM_SUSPECT",
+ "BR_TRANSACTION_PENDING_FROZEN",
};
static const char *kCommandStrings[] = {
@@ -395,7 +394,9 @@ void IPCThreadState::restoreGetCallingSpGuard(const SpGuard* guard) {
}
void IPCThreadState::checkContextIsBinderForUse(const char* use) const {
- if (LIKELY(mServingStackPointerGuard == nullptr)) return;
+ if (mServingStackPointerGuard == nullptr) [[likely]] {
+ return;
+ }
if (!mServingStackPointer || mServingStackPointerGuard->address < mServingStackPointer) {
LOG_ALWAYS_FATAL("In context %s, %s does not make sense (binder sp: %p, guard: %p).",
@@ -832,7 +833,7 @@ status_t IPCThreadState::transact(int32_t handle,
}
if ((flags & TF_ONE_WAY) == 0) {
- if (UNLIKELY(mCallRestriction != ProcessState::CallRestriction::NONE)) {
+ if (mCallRestriction != ProcessState::CallRestriction::NONE) [[unlikely]] {
if (mCallRestriction == ProcessState::CallRestriction::ERROR_IF_NOT_ONEWAY) {
ALOGE("Process making non-oneway call (code: %u) but is restricted.", code);
CallStack::logStack("non-oneway call", CallStack::getCurrent(10).get(),
@@ -842,13 +843,13 @@ status_t IPCThreadState::transact(int32_t handle,
}
}
- #if 0
+#if 0
if (code == 4) { // relayout
ALOGI(">>>>>> CALLING transaction 4");
} else {
ALOGI(">>>>>> CALLING transaction %d", code);
}
- #endif
+#endif
if (reply) {
err = waitForResponse(reply);
} else {
@@ -921,28 +922,10 @@ void IPCThreadState::decWeakHandle(int32_t handle)
flushIfNeeded();
}
-status_t IPCThreadState::attemptIncStrongHandle(int32_t handle)
-{
-#if HAS_BC_ATTEMPT_ACQUIRE
- LOG_REMOTEREFS("IPCThreadState::attemptIncStrongHandle(%d)\n", handle);
- mOut.writeInt32(BC_ATTEMPT_ACQUIRE);
- mOut.writeInt32(0); // xxx was thread priority
- mOut.writeInt32(handle);
- status_t result = UNKNOWN_ERROR;
-
- waitForResponse(NULL, &result);
-
-#if LOG_REFCOUNTS
- ALOGV("IPCThreadState::attemptIncStrongHandle(%ld) = %s\n",
- handle, result == NO_ERROR ? "SUCCESS" : "FAILURE");
-#endif
-
- return result;
-#else
+status_t IPCThreadState::attemptIncStrongHandle(int32_t handle) {
(void)handle;
ALOGE("%s(%d): Not supported\n", __func__, handle);
return INVALID_OPERATION;
-#endif
}
void IPCThreadState::expungeHandle(int32_t handle, IBinder* binder)
diff --git a/libs/binder/IResultReceiver.cpp b/libs/binder/IResultReceiver.cpp
index cd92217f84..60ece7269c 100644
--- a/libs/binder/IResultReceiver.cpp
+++ b/libs/binder/IResultReceiver.cpp
@@ -18,7 +18,6 @@
#include <binder/IResultReceiver.h>
-#include <utils/Log.h>
#include <binder/Parcel.h>
#include <utils/String8.h>
diff --git a/libs/binder/IServiceManager.cpp b/libs/binder/IServiceManager.cpp
index 2408307459..39573ec54d 100644
--- a/libs/binder/IServiceManager.cpp
+++ b/libs/binder/IServiceManager.cpp
@@ -40,6 +40,11 @@
#include "ServiceManagerHost.h"
#endif
+#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__) && !defined(__ANDROID_NATIVE_BRIDGE__)
+#include <android/apexsupport.h>
+#include <vndksupport/linker.h>
+#endif
+
#include "Static.h"
namespace android {
@@ -200,7 +205,7 @@ bool checkCallingPermission(const String16& permission, int32_t* outPid, int32_t
}
bool checkPermission(const String16& permission, pid_t pid, uid_t uid, bool logPermissionFailure) {
- static Mutex gPermissionControllerLock;
+ static std::mutex gPermissionControllerLock;
static sp<IPermissionController> gPermissionController;
sp<IPermissionController> pc;
@@ -216,8 +221,8 @@ bool checkPermission(const String16& permission, pid_t pid, uid_t uid, bool logP
if (res) {
if (startTime != 0) {
ALOGI("Check passed after %d seconds for %s from uid=%d pid=%d",
- (int)((uptimeMillis()-startTime)/1000),
- String8(permission).string(), uid, pid);
+ (int)((uptimeMillis() - startTime) / 1000), String8(permission).c_str(),
+ uid, pid);
}
return res;
}
@@ -225,7 +230,7 @@ bool checkPermission(const String16& permission, pid_t pid, uid_t uid, bool logP
// Is this a permission failure, or did the controller go away?
if (IInterface::asBinder(pc)->isBinderAlive()) {
if (logPermissionFailure) {
- ALOGW("Permission failure: %s from uid=%d pid=%d", String8(permission).string(),
+ ALOGW("Permission failure: %s from uid=%d pid=%d", String8(permission).c_str(),
uid, pid);
}
return false;
@@ -246,7 +251,7 @@ bool checkPermission(const String16& permission, pid_t pid, uid_t uid, bool logP
if (startTime == 0) {
startTime = uptimeMillis();
ALOGI("Waiting to check permission %s from uid=%d pid=%d",
- String8(permission).string(), uid, pid);
+ String8(permission).c_str(), uid, pid);
}
sleep(1);
} else {
@@ -259,6 +264,27 @@ bool checkPermission(const String16& permission, pid_t pid, uid_t uid, bool logP
}
}
+void* openDeclaredPassthroughHal(const String16& interface, const String16& instance, int flag) {
+#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__) && !defined(__ANDROID_NATIVE_BRIDGE__)
+ sp<IServiceManager> sm = defaultServiceManager();
+ String16 name = interface + String16("/") + instance;
+ if (!sm->isDeclared(name)) {
+ return nullptr;
+ }
+ String16 libraryName = interface + String16(".") + instance + String16(".so");
+ if (auto updatableViaApex = sm->updatableViaApex(name); updatableViaApex.has_value()) {
+ return AApexSupport_loadLibrary(String8(libraryName).c_str(),
+ String8(*updatableViaApex).c_str(), flag);
+ }
+ return android_load_sphal_library(String8(libraryName).c_str(), flag);
+#else
+ (void)interface;
+ (void)instance;
+ (void)flag;
+ return nullptr;
+#endif
+}
+
#endif //__ANDROID_VNDK__
// ----------------------------------------------------------------------
@@ -295,7 +321,7 @@ sp<IBinder> ServiceManagerShim::getService(const String16& name) const
// retry interval in millisecond; note that vendor services stay at 100ms
const useconds_t sleepTime = gSystemBootCompleted ? 1000 : 100;
- ALOGI("Waiting for service '%s' on '%s'...", String8(name).string(),
+ ALOGI("Waiting for service '%s' on '%s'...", String8(name).c_str(),
ProcessState::self()->getDriverName().c_str());
int n = 0;
@@ -306,12 +332,12 @@ sp<IBinder> ServiceManagerShim::getService(const String16& name) const
sp<IBinder> svc = checkService(name);
if (svc != nullptr) {
ALOGI("Waiting for service '%s' on '%s' successful after waiting %" PRIi64 "ms",
- String8(name).string(), ProcessState::self()->getDriverName().c_str(),
+ String8(name).c_str(), ProcessState::self()->getDriverName().c_str(),
uptimeMillis() - startTime);
return svc;
}
}
- ALOGW("Service %s didn't start. Returning NULL", String8(name).string());
+ ALOGW("Service %s didn't start. Returning NULL", String8(name).c_str());
return nullptr;
}
diff --git a/libs/binder/IUidObserver.cpp b/libs/binder/IUidObserver.cpp
index d952dc71f9..1c35f5359b 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/LazyServiceRegistrar.cpp b/libs/binder/LazyServiceRegistrar.cpp
index f66993f926..7644806e2b 100644
--- a/libs/binder/LazyServiceRegistrar.cpp
+++ b/libs/binder/LazyServiceRegistrar.cpp
@@ -324,6 +324,10 @@ LazyServiceRegistrar& LazyServiceRegistrar::getInstance() {
return *registrarInstance;
}
+LazyServiceRegistrar LazyServiceRegistrar::createExtraTestInstance() {
+ return LazyServiceRegistrar();
+}
+
status_t LazyServiceRegistrar::registerService(const sp<IBinder>& service, const std::string& name,
bool allowIsolated, int dumpFlags) {
if (!mClientCC->registerService(service, name, allowIsolated, dumpFlags)) {
diff --git a/libs/binder/MemoryDealer.cpp b/libs/binder/MemoryDealer.cpp
index 03553f36e9..95bdbb4b65 100644
--- a/libs/binder/MemoryDealer.cpp
+++ b/libs/binder/MemoryDealer.cpp
@@ -155,7 +155,7 @@ private:
void dump_l(String8& res, const char* what) const;
static const int kMemoryAlign;
- mutable Mutex mLock;
+ mutable std::mutex mLock;
LinkedList<chunk_t> mList;
size_t mHeapSize;
};
@@ -305,14 +305,14 @@ size_t SimpleBestFitAllocator::size() const
size_t SimpleBestFitAllocator::allocate(size_t size, uint32_t flags)
{
- Mutex::Autolock _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
ssize_t offset = alloc(size, flags);
return offset;
}
status_t SimpleBestFitAllocator::deallocate(size_t offset)
{
- Mutex::Autolock _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
chunk_t const * const freed = dealloc(offset);
if (freed) {
return NO_ERROR;
@@ -420,7 +420,7 @@ SimpleBestFitAllocator::chunk_t* SimpleBestFitAllocator::dealloc(size_t start)
void SimpleBestFitAllocator::dump(const char* what) const
{
- Mutex::Autolock _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
dump_l(what);
}
@@ -428,13 +428,13 @@ void SimpleBestFitAllocator::dump_l(const char* what) const
{
String8 result;
dump_l(result, what);
- ALOGD("%s", result.string());
+ ALOGD("%s", result.c_str());
}
void SimpleBestFitAllocator::dump(String8& result,
const char* what) const
{
- Mutex::Autolock _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
dump_l(result, what);
}
diff --git a/libs/binder/MemoryHeapBase.cpp b/libs/binder/MemoryHeapBase.cpp
index 8fe1d2bb3d..fc273e0367 100644
--- a/libs/binder/MemoryHeapBase.cpp
+++ b/libs/binder/MemoryHeapBase.cpp
@@ -73,12 +73,12 @@ MemoryHeapBase::MemoryHeapBase(size_t size, uint32_t flags, char const * name)
ALOGV("MemoryHeapBase: Attempting to force MemFD");
fd = memfd_create_region(name ? name : "MemoryHeapBase", size);
if (fd < 0 || (mapfd(fd, true, size) != NO_ERROR)) return;
- const int SEAL_FLAGS = ((mFlags & READ_ONLY) ? F_SEAL_FUTURE_WRITE : 0) |
- ((mFlags & MEMFD_ALLOW_SEALING_FLAG) ? 0 : F_SEAL_SEAL);
+ const int SEAL_FLAGS = ((mFlags & READ_ONLY) ? F_SEAL_FUTURE_WRITE : 0) | F_SEAL_GROW |
+ F_SEAL_SHRINK | ((mFlags & MEMFD_ALLOW_SEALING_FLAG) ? 0 : F_SEAL_SEAL);
if (SEAL_FLAGS && (fcntl(fd, F_ADD_SEALS, SEAL_FLAGS) == -1)) {
ALOGE("MemoryHeapBase: MemFD %s sealing with flags %x failed with error %s", name,
SEAL_FLAGS, strerror(errno));
- munmap(mBase, mSize);
+ if (mNeedUnmap) munmap(mBase, mSize);
mBase = nullptr;
mSize = 0;
close(fd);
diff --git a/libs/binder/OS.h b/libs/binder/OS.h
index fecae31763..0035aeb205 100644
--- a/libs/binder/OS.h
+++ b/libs/binder/OS.h
@@ -18,14 +18,16 @@
#include <stddef.h>
#include <cstdint>
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
#include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
#include <utils/Errors.h>
-namespace android {
+namespace android::binder::os {
-android::base::Result<void> setNonBlocking(android::base::borrowed_fd fd);
+void trace_begin(uint64_t tag, const char* name);
+void trace_end(uint64_t tag);
+
+status_t setNonBlocking(borrowed_fd fd);
status_t getRandomBytes(uint8_t* data, size_t size);
@@ -33,12 +35,14 @@ status_t dupFileDescriptor(int oldFd, int* newFd);
std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory();
-ssize_t sendMessageOnSocket(
- const RpcTransportFd& socket, iovec* iovs, int niovs,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
+ssize_t sendMessageOnSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds);
+
+ssize_t receiveMessageFromSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds);
+
+uint64_t GetThreadId();
-ssize_t receiveMessageFromSocket(
- const RpcTransportFd& socket, iovec* iovs, int niovs,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds);
+bool report_sysprop_change();
-} // namespace android
+} // namespace android::binder::os
diff --git a/libs/binder/OS_android.cpp b/libs/binder/OS_android.cpp
new file mode 100644
index 0000000000..1eace857fb
--- /dev/null
+++ b/libs/binder/OS_android.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "OS.h"
+
+#include <android-base/threads.h>
+#include <cutils/trace.h>
+#include <utils/misc.h>
+
+namespace android::binder {
+namespace os {
+
+uint64_t GetThreadId() {
+#ifdef BINDER_RPC_SINGLE_THREADED
+ return 0;
+#else
+ return base::GetThreadId();
+#endif
+}
+
+bool report_sysprop_change() {
+ android::report_sysprop_change();
+ return true;
+}
+
+void trace_begin(uint64_t tag, const char* name) {
+ atrace_begin(tag, name);
+}
+
+void trace_end(uint64_t tag) {
+ atrace_end(tag);
+}
+
+} // namespace os
+
+// Legacy trace symbol. To be removed once all of downstream rebuilds.
+void atrace_begin(uint64_t tag, const char* name) {
+ os::trace_begin(tag, name);
+}
+
+// Legacy trace symbol. To be removed once all of downstream rebuilds.
+void atrace_end(uint64_t tag) {
+ os::trace_end(tag);
+}
+
+} // namespace android::binder
diff --git a/libs/binder/OS_non_android_linux.cpp b/libs/binder/OS_non_android_linux.cpp
new file mode 100644
index 0000000000..b525d1ac34
--- /dev/null
+++ b/libs/binder/OS_non_android_linux.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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 "OS.h"
+
+#include <log/log.h>
+
+#include <syscall.h>
+#include <cstdarg>
+
+#ifdef __ANDROID__
+#error "This module is not intended for Android, just bare Linux"
+#endif
+#ifdef __APPLE__
+#error "This module is not intended for MacOS"
+#endif
+#ifdef _WIN32
+#error "This module is not intended for Windows"
+#endif
+
+namespace android::binder::os {
+
+void trace_begin(uint64_t, const char*) {}
+
+void trace_end(uint64_t) {}
+
+uint64_t GetThreadId() {
+ return syscall(__NR_gettid);
+}
+
+bool report_sysprop_change() {
+ return false;
+}
+
+} // namespace android::binder::os
+
+int __android_log_print(int /*prio*/, const char* /*tag*/, const char* fmt, ...) {
+ va_list args;
+ va_start(args, fmt);
+ vfprintf(stderr, fmt, args);
+ va_end(args);
+
+ return 1;
+}
diff --git a/libs/binder/OS.cpp b/libs/binder/OS_unix_base.cpp
index ce60e33ba7..ca998d46d3 100644
--- a/libs/binder/OS.cpp
+++ b/libs/binder/OS_unix_base.cpp
@@ -15,39 +15,41 @@
*/
#include "OS.h"
+#include "Utils.h"
+#include "file.h"
-#include <android-base/file.h>
#include <binder/RpcTransportRaw.h>
#include <log/log.h>
#include <string.h>
+#include <sys/socket.h>
-using android::base::ErrnoError;
-using android::base::Result;
+using android::binder::ReadFully;
-namespace android {
+namespace android::binder::os {
// Linux kernel supports up to 253 (from SCM_MAX_FD) for unix sockets.
constexpr size_t kMaxFdsPerMsg = 253;
-Result<void> setNonBlocking(android::base::borrowed_fd fd) {
+status_t setNonBlocking(borrowed_fd fd) {
int flags = TEMP_FAILURE_RETRY(fcntl(fd.get(), F_GETFL));
if (flags == -1) {
- return ErrnoError() << "Could not get flags for fd";
+ PLOGE("Failed setNonBlocking: Could not get flags for fd");
+ return -errno;
}
if (int ret = TEMP_FAILURE_RETRY(fcntl(fd.get(), F_SETFL, flags | O_NONBLOCK)); ret == -1) {
- return ErrnoError() << "Could not set non-blocking flag for fd";
+ PLOGE("Failed setNonBlocking: Could not set non-blocking flag for fd");
+ return -errno;
}
- return {};
+ return OK;
}
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) {
+ unique_fd fd(TEMP_FAILURE_RETRY(open("/dev/urandom", O_RDONLY | O_CLOEXEC | O_NOFOLLOW)));
+ if (!fd.ok()) {
return -errno;
}
- base::unique_fd fd(ret);
- if (!base::ReadFully(fd, data, size)) {
+ if (!ReadFully(fd, data, size)) {
return -errno;
}
return OK;
@@ -67,9 +69,8 @@ std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory() {
return RpcTransportCtxFactoryRaw::make();
}
-ssize_t sendMessageOnSocket(
- const RpcTransportFd& socket, iovec* iovs, int niovs,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+ssize_t sendMessageOnSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
if (ancillaryFds != nullptr && !ancillaryFds->empty()) {
if (ancillaryFds->size() > kMaxFdsPerMsg) {
errno = EINVAL;
@@ -112,9 +113,8 @@ ssize_t sendMessageOnSocket(
return TEMP_FAILURE_RETRY(sendmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
}
-ssize_t receiveMessageFromSocket(
- const RpcTransportFd& socket, iovec* iovs, int niovs,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+ssize_t receiveMessageFromSocket(const RpcTransportFd& socket, iovec* iovs, int niovs,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
if (ancillaryFds != nullptr) {
int fdBuffer[kMaxFdsPerMsg];
alignas(struct cmsghdr) char msgControlBuf[CMSG_SPACE(sizeof(fdBuffer))];
@@ -140,7 +140,7 @@ ssize_t receiveMessageFromSocket(
size_t fdCount = dataLen / sizeof(int);
ancillaryFds->reserve(ancillaryFds->size() + fdCount);
for (size_t i = 0; i < fdCount; i++) {
- ancillaryFds->emplace_back(base::unique_fd(fdBuffer[i]));
+ ancillaryFds->emplace_back(unique_fd(fdBuffer[i]));
}
break;
}
@@ -162,4 +162,4 @@ ssize_t receiveMessageFromSocket(
return TEMP_FAILURE_RETRY(recvmsg(socket.fd.get(), &msg, MSG_NOSIGNAL));
}
-} // namespace android
+} // namespace android::binder::os
diff --git a/libs/binder/OWNERS b/libs/binder/OWNERS
index bb17683a23..a70b55826f 100644
--- a/libs/binder/OWNERS
+++ b/libs/binder/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 32456
-maco@google.com
+
smoreland@google.com
-tkjos@google.com
+tkjos@google.com # kernel
+maco@google.com # historical
diff --git a/libs/binder/Parcel.cpp b/libs/binder/Parcel.cpp
index 0aca163eab..84ef489a6c 100644
--- a/libs/binder/Parcel.cpp
+++ b/libs/binder/Parcel.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "Parcel"
//#define LOG_NDEBUG 0
+#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
@@ -29,9 +30,11 @@
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
+#include <algorithm>
#include <binder/Binder.h>
#include <binder/BpBinder.h>
+#include <binder/Functional.h>
#include <binder/IPCThreadState.h>
#include <binder/Parcel.h>
#include <binder/ProcessState.h>
@@ -39,14 +42,11 @@
#include <binder/Status.h>
#include <binder/TextOutput.h>
-#include <android-base/scopeguard.h>
+#ifndef BINDER_DISABLE_BLOB
#include <cutils/ashmem.h>
-#include <cutils/compiler.h>
-#include <utils/Flattenable.h>
-#include <utils/Log.h>
+#endif
#include <utils/String16.h>
#include <utils/String8.h>
-#include <utils/misc.h>
#include "OS.h"
#include "RpcState.h"
@@ -69,6 +69,10 @@
typedef uintptr_t binder_uintptr_t;
#endif // BINDER_WITH_KERNEL_IPC
+#ifdef __BIONIC__
+#include <android/fdsan.h>
+#endif
+
#define LOG_REFS(...)
// #define LOG_REFS(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOG_ALLOC(...)
@@ -93,6 +97,10 @@ static size_t pad_size(size_t s) {
namespace android {
+using namespace android::binder::impl;
+using binder::borrowed_fd;
+using binder::unique_fd;
+
// many things compile this into prebuilts on the stack
#ifdef __LP64__
static_assert(sizeof(Parcel) == 120);
@@ -107,7 +115,38 @@ static std::atomic<size_t> gParcelGlobalAllocSize;
constexpr size_t kMaxFds = 1024;
// Maximum size of a blob to transfer in-place.
-static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;
+[[maybe_unused]] static const size_t BLOB_INPLACE_LIMIT = 16 * 1024;
+
+#if defined(__BIONIC__)
+static void FdTag(int fd, const void* old_addr, const void* new_addr) {
+ if (android_fdsan_exchange_owner_tag) {
+ uint64_t old_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+ reinterpret_cast<uint64_t>(old_addr));
+ uint64_t new_tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+ reinterpret_cast<uint64_t>(new_addr));
+ android_fdsan_exchange_owner_tag(fd, old_tag, new_tag);
+ }
+}
+static void FdTagClose(int fd, const void* addr) {
+ if (android_fdsan_close_with_tag) {
+ uint64_t tag = android_fdsan_create_owner_tag(ANDROID_FDSAN_OWNER_TYPE_PARCEL,
+ reinterpret_cast<uint64_t>(addr));
+ android_fdsan_close_with_tag(fd, tag);
+ } else {
+ close(fd);
+ }
+}
+#else
+static void FdTag(int fd, const void* old_addr, const void* new_addr) {
+ (void)fd;
+ (void)old_addr;
+ (void)new_addr;
+}
+static void FdTagClose(int fd, const void* addr) {
+ (void)addr;
+ close(fd);
+}
+#endif
enum {
BLOB_INPLACE = 0,
@@ -134,6 +173,9 @@ static void acquire_object(const sp<ProcessState>& proc, const flat_binder_objec
return;
}
case BINDER_TYPE_FD: {
+ if (obj.cookie != 0) { // owned
+ FdTag(obj.handle, nullptr, who);
+ }
return;
}
}
@@ -159,8 +201,10 @@ static void release_object(const sp<ProcessState>& proc, const flat_binder_objec
return;
}
case BINDER_TYPE_FD: {
+ // note: this path is not used when mOwner, so the tag is also released
+ // in 'closeFileDescriptors'
if (obj.cookie != 0) { // owned
- close(obj.handle);
+ FdTagClose(obj.handle, who);
}
return;
}
@@ -170,7 +214,7 @@ static void release_object(const sp<ProcessState>& proc, const flat_binder_objec
}
#endif // BINDER_WITH_KERNEL_IPC
-static int toRawFd(const std::variant<base::unique_fd, base::borrowed_fd>& v) {
+static int toRawFd(const std::variant<unique_fd, borrowed_fd>& v) {
return std::visit([](const auto& fd) { return fd.get(); }, v);
}
@@ -213,7 +257,7 @@ status_t Parcel::flattenBinder(const sp<IBinder>& binder) {
if (const auto* rpcFields = maybeRpcFields()) {
if (binder) {
- status_t status = writeInt32(1); // non-null
+ status_t status = writeInt32(RpcFields::TYPE_BINDER); // non-null
if (status != OK) return status;
uint64_t address;
// TODO(b/167966510): need to undo this if the Parcel is not sent
@@ -223,7 +267,7 @@ status_t Parcel::flattenBinder(const sp<IBinder>& binder) {
status = writeUint64(address);
if (status != OK) return status;
} else {
- status_t status = writeInt32(0); // null
+ status_t status = writeInt32(RpcFields::TYPE_BINDER_NULL); // null
if (status != OK) return status;
}
return finishFlattenBinder(binder);
@@ -554,7 +598,6 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
kernelFields->mObjectsSize++;
flat_binder_object* flat = reinterpret_cast<flat_binder_object*>(mData + off);
- acquire_object(proc, *flat, this);
if (flat->hdr.type == BINDER_TYPE_FD) {
// If this is a file descriptor, we need to dup it so the
@@ -567,6 +610,8 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
err = FDS_NOT_ALLOWED;
}
}
+
+ acquire_object(proc, *flat, this);
}
}
#else
@@ -585,7 +630,7 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
}
const size_t savedDataPos = mDataPos;
- base::ScopeGuard scopeGuard = [&]() { mDataPos = savedDataPos; };
+ auto scopeGuard = make_scope_guard([&]() { mDataPos = savedDataPos; });
rpcFields->mObjectPositions.reserve(otherRpcFields->mObjectPositions.size());
if (otherRpcFields->mFds != nullptr) {
@@ -622,10 +667,10 @@ status_t Parcel::appendFrom(const Parcel* parcel, size_t offset, size_t len) {
// To match kernel binder behavior, we always dup, even if the
// FD was unowned in the source parcel.
int newFd = -1;
- if (status_t status = dupFileDescriptor(oldFd, &newFd); status != OK) {
+ if (status_t status = binder::os::dupFileDescriptor(oldFd, &newFd); status != OK) {
ALOGW("Failed to duplicate file descriptor %d: %s", oldFd, strerror(-status));
}
- rpcFields->mFds->emplace_back(base::unique_fd(newFd));
+ rpcFields->mFds->emplace_back(unique_fd(newFd));
// Fixup the index in the data.
mDataPos = newDataPos + 4;
if (status_t status = writeInt32(rpcFields->mFds->size() - 1); status != OK) {
@@ -696,6 +741,12 @@ bool Parcel::hasFileDescriptors() const
return kernelFields->mHasFds;
}
+status_t Parcel::hasBinders(bool* result) const {
+ status_t status = hasBindersInRange(0, dataSize(), result);
+ ALOGE_IF(status != NO_ERROR, "Error %d calling hasBindersInRange()", status);
+ return status;
+}
+
std::vector<sp<IBinder>> Parcel::debugReadAllStrongBinders() const {
std::vector<sp<IBinder>> ret;
@@ -755,6 +806,46 @@ std::vector<int> Parcel::debugReadAllFileDescriptors() const {
return ret;
}
+status_t Parcel::hasBindersInRange(size_t offset, size_t len, bool* result) const {
+ if (len > INT32_MAX || offset > INT32_MAX) {
+ // Don't accept size_t values which may have come from an inadvertent conversion from a
+ // negative int.
+ return BAD_VALUE;
+ }
+ size_t limit;
+ if (__builtin_add_overflow(offset, len, &limit) || limit > mDataSize) {
+ return BAD_VALUE;
+ }
+ *result = false;
+ if (const auto* kernelFields = maybeKernelFields()) {
+#ifdef BINDER_WITH_KERNEL_IPC
+ for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
+ size_t pos = kernelFields->mObjects[i];
+ if (pos < offset) continue;
+ if (pos + sizeof(flat_binder_object) > offset + len) {
+ if (kernelFields->mObjectsSorted) {
+ break;
+ } else {
+ continue;
+ }
+ }
+ const flat_binder_object* flat =
+ reinterpret_cast<const flat_binder_object*>(mData + pos);
+ if (flat->hdr.type == BINDER_TYPE_BINDER || flat->hdr.type == BINDER_TYPE_HANDLE) {
+ *result = true;
+ break;
+ }
+ }
+#else
+ LOG_ALWAYS_FATAL("Binder kernel driver disabled at build time");
+ return INVALID_OPERATION;
+#endif // BINDER_WITH_KERNEL_IPC
+ } else if (const auto* rpcFields = maybeRpcFields()) {
+ return INVALID_OPERATION;
+ }
+ return NO_ERROR;
+}
+
status_t Parcel::hasFileDescriptorsInRange(size_t offset, size_t len, bool* result) const {
if (len > INT32_MAX || offset > INT32_MAX) {
// Don't accept size_t values which may have come from an inadvertent conversion from a
@@ -842,6 +933,9 @@ void Parcel::updateWorkSourceRequestHeaderPosition() const {
}
#ifdef BINDER_WITH_KERNEL_IPC
+
+#if defined(__ANDROID__)
+
#if defined(__ANDROID_VNDK__)
constexpr int32_t kHeader = B_PACK_CHARS('V', 'N', 'D', 'R');
#elif defined(__ANDROID_RECOVERY__)
@@ -849,12 +943,20 @@ constexpr int32_t kHeader = B_PACK_CHARS('R', 'E', 'C', 'O');
#else
constexpr int32_t kHeader = B_PACK_CHARS('S', 'Y', 'S', 'T');
#endif
+
+#else // ANDROID not defined
+
+// If kernel binder is used in new environments, we need to make sure it's separated
+// out and has a separate header.
+constexpr int32_t kHeader = B_PACK_CHARS('U', 'N', 'K', 'N');
+#endif
+
#endif // BINDER_WITH_KERNEL_IPC
// Write RPC headers. (previously just the interface token)
status_t Parcel::writeInterfaceToken(const String16& interface)
{
- return writeInterfaceToken(interface.string(), interface.size());
+ return writeInterfaceToken(interface.c_str(), interface.size());
}
status_t Parcel::writeInterfaceToken(const char16_t* str, size_t len) {
@@ -918,7 +1020,7 @@ bool Parcel::checkInterface(IBinder* binder) const
bool Parcel::enforceInterface(const String16& interface,
IPCThreadState* threadState) const
{
- return enforceInterface(interface.string(), interface.size(), threadState);
+ return enforceInterface(interface.c_str(), interface.size(), threadState);
}
bool Parcel::enforceInterface(const char16_t* interface,
@@ -947,7 +1049,10 @@ bool Parcel::enforceInterface(const char16_t* interface,
threadState->setCallingWorkSourceUidWithoutPropagation(workSource);
// vendor header
int32_t header = readInt32();
- if (header != kHeader) {
+
+ // fuzzers skip this check, because it is for protecting the underlying ABI, but
+ // we don't want it to reduce our coverage
+ if (header != kHeader && !mServiceFuzzing) {
ALOGE("Expecting header 0x%x but found 0x%x. Mixing copies of libbinder?", kHeader,
header);
return false;
@@ -966,10 +1071,18 @@ bool Parcel::enforceInterface(const char16_t* interface,
(!len || !memcmp(parcel_interface, interface, len * sizeof (char16_t)))) {
return true;
} else {
- ALOGW("**** enforceInterface() expected '%s' but read '%s'",
- String8(interface, len).string(),
- String8(parcel_interface, parcel_interface_len).string());
- return false;
+ if (mServiceFuzzing) {
+ // ignore. Theoretically, this could cause a few false positives, because
+ // people could assume things about getInterfaceDescriptor if they pass
+ // this point, but it would be extremely fragile. It's more important that
+ // we fuzz with the above things read from the Parcel.
+ return true;
+ } else {
+ ALOGW("**** enforceInterface() expected '%s' but read '%s'",
+ String8(interface, len).c_str(),
+ String8(parcel_interface, parcel_interface_len).c_str());
+ return false;
+ }
}
}
@@ -977,6 +1090,14 @@ void Parcel::setEnforceNoDataAvail(bool enforceNoDataAvail) {
mEnforceNoDataAvail = enforceNoDataAvail;
}
+void Parcel::setServiceFuzzing() {
+ mServiceFuzzing = true;
+}
+
+bool Parcel::isServiceFuzzing() const {
+ return mServiceFuzzing;
+}
+
binder::Status Parcel::enforceNoDataAvail() const {
if (!mEnforceNoDataAvail) {
return binder::Status::ok();
@@ -1186,9 +1307,16 @@ status_t Parcel::writeUtf8VectorAsUtf16Vector(
const std::unique_ptr<std::vector<std::unique_ptr<std::string>>>& val) { return writeData(val); }
status_t Parcel::writeUtf8VectorAsUtf16Vector(const std::vector<std::string>& val) { return writeData(val); }
-status_t Parcel::writeUniqueFileDescriptorVector(const std::vector<base::unique_fd>& val) { return writeData(val); }
-status_t Parcel::writeUniqueFileDescriptorVector(const std::optional<std::vector<base::unique_fd>>& val) { return writeData(val); }
-status_t Parcel::writeUniqueFileDescriptorVector(const std::unique_ptr<std::vector<base::unique_fd>>& val) { return writeData(val); }
+status_t Parcel::writeUniqueFileDescriptorVector(const std::vector<unique_fd>& val) {
+ return writeData(val);
+}
+status_t Parcel::writeUniqueFileDescriptorVector(const std::optional<std::vector<unique_fd>>& val) {
+ return writeData(val);
+}
+status_t Parcel::writeUniqueFileDescriptorVector(
+ const std::unique_ptr<std::vector<unique_fd>>& val) {
+ return writeData(val);
+}
status_t Parcel::writeStrongBinderVector(const std::vector<sp<IBinder>>& val) { return writeData(val); }
status_t Parcel::writeStrongBinderVector(const std::optional<std::vector<sp<IBinder>>>& val) { return writeData(val); }
@@ -1241,9 +1369,16 @@ status_t Parcel::readUtf8VectorFromUtf16Vector(
std::unique_ptr<std::vector<std::unique_ptr<std::string>>>* val) const { return readData(val); }
status_t Parcel::readUtf8VectorFromUtf16Vector(std::vector<std::string>* val) const { return readData(val); }
-status_t Parcel::readUniqueFileDescriptorVector(std::optional<std::vector<base::unique_fd>>* val) const { return readData(val); }
-status_t Parcel::readUniqueFileDescriptorVector(std::unique_ptr<std::vector<base::unique_fd>>* val) const { return readData(val); }
-status_t Parcel::readUniqueFileDescriptorVector(std::vector<base::unique_fd>* val) const { return readData(val); }
+status_t Parcel::readUniqueFileDescriptorVector(std::optional<std::vector<unique_fd>>* val) const {
+ return readData(val);
+}
+status_t Parcel::readUniqueFileDescriptorVector(
+ std::unique_ptr<std::vector<unique_fd>>* val) const {
+ return readData(val);
+}
+status_t Parcel::readUniqueFileDescriptorVector(std::vector<unique_fd>* val) const {
+ return readData(val);
+}
status_t Parcel::readStrongBinderVector(std::optional<std::vector<sp<IBinder>>>* val) const { return readData(val); }
status_t Parcel::readStrongBinderVector(std::unique_ptr<std::vector<sp<IBinder>>>* val) const { return readData(val); }
@@ -1357,7 +1492,7 @@ status_t Parcel::writeCString(const char* str)
status_t Parcel::writeString8(const String8& str)
{
- return writeString8(str.string(), str.size());
+ return writeString8(str.c_str(), str.size());
}
status_t Parcel::writeString8(const char* str, size_t len)
@@ -1380,7 +1515,7 @@ status_t Parcel::writeString8(const char* str, size_t len)
status_t Parcel::writeString16(const String16& str)
{
- return writeString16(str.string(), str.size());
+ return writeString16(str.c_str(), str.size());
}
status_t Parcel::writeString16(const char16_t* str, size_t len)
@@ -1416,6 +1551,7 @@ status_t Parcel::writeRawNullableParcelable(const Parcelable* parcelable) {
return writeParcelable(*parcelable);
}
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
status_t Parcel::writeNativeHandle(const native_handle* handle)
{
if (!handle || handle->version != sizeof(native_handle))
@@ -1438,14 +1574,15 @@ status_t Parcel::writeNativeHandle(const native_handle* handle)
err = write(handle->data + handle->numFds, sizeof(int)*handle->numInts);
return err;
}
+#endif
status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
if (auto* rpcFields = maybeRpcFields()) {
- std::variant<base::unique_fd, base::borrowed_fd> fdVariant;
+ std::variant<unique_fd, borrowed_fd> fdVariant;
if (takeOwnership) {
- fdVariant = base::unique_fd(fd);
+ fdVariant = unique_fd(fd);
} else {
- fdVariant = base::borrowed_fd(fd);
+ fdVariant = borrowed_fd(fd);
}
if (!mAllowFds) {
return FDS_NOT_ALLOWED;
@@ -1495,7 +1632,7 @@ status_t Parcel::writeFileDescriptor(int fd, bool takeOwnership) {
status_t Parcel::writeDupFileDescriptor(int fd)
{
int dupFd;
- if (status_t err = dupFileDescriptor(fd, &dupFd); err != OK) {
+ if (status_t err = binder::os::dupFileDescriptor(fd, &dupFd); err != OK) {
return err;
}
status_t err = writeFileDescriptor(dupFd, true /*takeOwnership*/);
@@ -1514,7 +1651,7 @@ status_t Parcel::writeParcelFileDescriptor(int fd, bool takeOwnership)
status_t Parcel::writeDupParcelFileDescriptor(int fd)
{
int dupFd;
- if (status_t err = dupFileDescriptor(fd, &dupFd); err != OK) {
+ if (status_t err = binder::os::dupFileDescriptor(fd, &dupFd); err != OK) {
return err;
}
status_t err = writeParcelFileDescriptor(dupFd, true /*takeOwnership*/);
@@ -1524,12 +1661,18 @@ status_t Parcel::writeDupParcelFileDescriptor(int fd)
return err;
}
-status_t Parcel::writeUniqueFileDescriptor(const base::unique_fd& fd) {
+status_t Parcel::writeUniqueFileDescriptor(const unique_fd& fd) {
return writeDupFileDescriptor(fd.get());
}
status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
{
+#ifdef BINDER_DISABLE_BLOB
+ (void)len;
+ (void)mutableCopy;
+ (void)outBlob;
+ return INVALID_OPERATION;
+#else
if (len > INT32_MAX) {
// don't accept size_t values which may have come from an
// inadvertent conversion from a negative int.
@@ -1581,6 +1724,7 @@ status_t Parcel::writeBlob(size_t len, bool mutableCopy, WritableBlob* outBlob)
}
::close(fd);
return status;
+#endif
}
status_t Parcel::writeDupImmutableBlobFileDescriptor(int fd)
@@ -1722,7 +1866,9 @@ status_t Parcel::validateReadData(size_t upperBound) const
do {
if (mDataPos < kernelFields->mObjects[nextObject] + sizeof(flat_binder_object)) {
// Requested info overlaps with an object
- ALOGE("Attempt to read from protected data in Parcel %p", this);
+ if (!mServiceFuzzing) {
+ ALOGE("Attempt to read from protected data in Parcel %p", this);
+ }
return PERMISSION_DENIED;
}
nextObject++;
@@ -2092,7 +2238,11 @@ String8 Parcel::readString8() const
size_t len;
const char* str = readString8Inplace(&len);
if (str) return String8(str, len);
- ALOGE("Reading a NULL string not supported here.");
+
+ if (!mServiceFuzzing) {
+ ALOGE("Reading a NULL string not supported here.");
+ }
+
return String8();
}
@@ -2132,7 +2282,11 @@ String16 Parcel::readString16() const
size_t len;
const char16_t* str = readString16Inplace(&len);
if (str) return String16(str, len);
- ALOGE("Reading a NULL string not supported here.");
+
+ if (!mServiceFuzzing) {
+ ALOGE("Reading a NULL string not supported here.");
+ }
+
return String16();
}
@@ -2172,7 +2326,9 @@ status_t Parcel::readStrongBinder(sp<IBinder>* val) const
{
status_t status = readNullableStrongBinder(val);
if (status == OK && !val->get()) {
- ALOGW("Expecting binder but got null!");
+ if (!mServiceFuzzing) {
+ ALOGW("Expecting binder but got null!");
+ }
status = UNEXPECTED_NULL;
}
return status;
@@ -2200,6 +2356,7 @@ int32_t Parcel::readExceptionCode() const
return status.exceptionCode();
}
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
native_handle* Parcel::readNativeHandle() const
{
int numFds, numInts;
@@ -2232,14 +2389,17 @@ native_handle* Parcel::readNativeHandle() const
}
return h;
}
+#endif
int Parcel::readFileDescriptor() const {
if (const auto* rpcFields = maybeRpcFields()) {
if (!std::binary_search(rpcFields->mObjectPositions.begin(),
rpcFields->mObjectPositions.end(), mDataPos)) {
- ALOGW("Attempt to read file descriptor from Parcel %p at offset %zu that is not in the "
- "object list",
- this, mDataPos);
+ if (!mServiceFuzzing) {
+ ALOGW("Attempt to read file descriptor from Parcel %p at offset %zu that is not in "
+ "the object list",
+ this, mDataPos);
+ }
return BAD_TYPE;
}
@@ -2304,8 +2464,7 @@ int Parcel::readParcelFileDescriptor() const {
return fd;
}
-status_t Parcel::readUniqueFileDescriptor(base::unique_fd* val) const
-{
+status_t Parcel::readUniqueFileDescriptor(unique_fd* val) const {
int got = readFileDescriptor();
if (got == BAD_TYPE) {
@@ -2313,7 +2472,7 @@ status_t Parcel::readUniqueFileDescriptor(base::unique_fd* val) const
}
int dupFd;
- if (status_t err = dupFileDescriptor(got, &dupFd); err != OK) {
+ if (status_t err = binder::os::dupFileDescriptor(got, &dupFd); err != OK) {
return BAD_VALUE;
}
@@ -2326,8 +2485,7 @@ status_t Parcel::readUniqueFileDescriptor(base::unique_fd* val) const
return OK;
}
-status_t Parcel::readUniqueParcelFileDescriptor(base::unique_fd* val) const
-{
+status_t Parcel::readUniqueParcelFileDescriptor(unique_fd* val) const {
int got = readParcelFileDescriptor();
if (got == BAD_TYPE) {
@@ -2335,7 +2493,7 @@ status_t Parcel::readUniqueParcelFileDescriptor(base::unique_fd* val) const
}
int dupFd;
- if (status_t err = dupFileDescriptor(got, &dupFd); err != OK) {
+ if (status_t err = binder::os::dupFileDescriptor(got, &dupFd); err != OK) {
return BAD_VALUE;
}
@@ -2350,6 +2508,11 @@ status_t Parcel::readUniqueParcelFileDescriptor(base::unique_fd* val) const
status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
{
+#ifdef BINDER_DISABLE_BLOB
+ (void)len;
+ (void)outBlob;
+ return INVALID_OPERATION;
+#else
int32_t blobType;
status_t status = readInt32(&blobType);
if (status) return status;
@@ -2383,6 +2546,7 @@ status_t Parcel::readBlob(size_t len, ReadableBlob* outBlob) const
outBlob->init(fd, ptr, len, isMutable);
return NO_ERROR;
+#endif
}
status_t Parcel::read(FlattenableHelperInterface& val) const
@@ -2497,8 +2661,11 @@ const flat_binder_object* Parcel::readObject(bool nullMetaData) const
return obj;
}
}
- ALOGW("Attempt to read object from Parcel %p at offset %zu that is not in the object list",
- this, DPOS);
+ if (!mServiceFuzzing) {
+ ALOGW("Attempt to read object from Parcel %p at offset %zu that is not in the object "
+ "list",
+ this, DPOS);
+ }
}
return nullptr;
}
@@ -2517,7 +2684,8 @@ void Parcel::closeFileDescriptors() {
reinterpret_cast<flat_binder_object*>(mData + kernelFields->mObjects[i]);
if (flat->hdr.type == BINDER_TYPE_FD) {
// ALOGI("Closing fd: %ld", flat->handle);
- close(flat->handle);
+ // FDs from the kernel are always owned
+ FdTagClose(flat->handle, this);
}
}
#else // BINDER_WITH_KERNEL_IPC
@@ -2598,6 +2766,10 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const bin
kernelFields->mObjectsSize = 0;
break;
}
+ if (type == BINDER_TYPE_FD) {
+ // FDs from the kernel are always owned
+ FdTag(flat->handle, 0, this);
+ }
minOffset = offset + sizeof(flat_binder_object);
}
scanForFds();
@@ -2610,8 +2782,7 @@ void Parcel::ipcSetDataReference(const uint8_t* data, size_t dataSize, const bin
status_t Parcel::rpcSetDataReference(
const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
const uint32_t* objectTable, size_t objectTableSize,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds,
- release_func relFunc) {
+ std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds, release_func relFunc) {
// this code uses 'mOwner == nullptr' to understand whether it owns memory
LOG_ALWAYS_FATAL_IF(relFunc == nullptr, "must provide cleanup function");
@@ -3093,6 +3264,7 @@ void Parcel::initState()
mDeallocZero = false;
mOwner = nullptr;
mEnforceNoDataAvail = true;
+ mServiceFuzzing = false;
}
void Parcel::scanForFds() const {
@@ -3122,6 +3294,7 @@ size_t Parcel::getOpenAshmemSize() const
}
size_t openAshmemSize = 0;
+#ifndef BINDER_DISABLE_BLOB
for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
const flat_binder_object* flat =
reinterpret_cast<const flat_binder_object*>(mData + kernelFields->mObjects[i]);
@@ -3136,6 +3309,7 @@ size_t Parcel::getOpenAshmemSize() const
}
}
}
+#endif
return openAshmemSize;
}
#endif // BINDER_WITH_KERNEL_IPC
diff --git a/libs/binder/ParcelFileDescriptor.cpp b/libs/binder/ParcelFileDescriptor.cpp
index 4f8b76f7d9..c3c487442b 100644
--- a/libs/binder/ParcelFileDescriptor.cpp
+++ b/libs/binder/ParcelFileDescriptor.cpp
@@ -19,9 +19,11 @@
namespace android {
namespace os {
+using android::binder::unique_fd;
+
ParcelFileDescriptor::ParcelFileDescriptor() = default;
-ParcelFileDescriptor::ParcelFileDescriptor(android::base::unique_fd fd) : mFd(std::move(fd)) {}
+ParcelFileDescriptor::ParcelFileDescriptor(unique_fd fd) : mFd(std::move(fd)) {}
ParcelFileDescriptor::~ParcelFileDescriptor() = default;
diff --git a/libs/binder/PermissionCache.cpp b/libs/binder/PermissionCache.cpp
index 670fd55da3..658686d2db 100644
--- a/libs/binder/PermissionCache.cpp
+++ b/libs/binder/PermissionCache.cpp
@@ -101,9 +101,8 @@ bool PermissionCache::checkPermission(
nsecs_t t = -systemTime();
granted = android::checkPermission(permission, pid, uid);
t += systemTime();
- ALOGD("checking %s for uid=%d => %s (%d us)",
- String8(permission).string(), uid,
- granted?"granted":"denied", (int)ns2us(t));
+ ALOGD("checking %s for uid=%d => %s (%d us)", String8(permission).c_str(), uid,
+ granted ? "granted" : "denied", (int)ns2us(t));
pc.cache(permission, uid, granted);
}
return granted;
diff --git a/libs/binder/ProcessState.cpp b/libs/binder/ProcessState.cpp
index 5f1f50672a..fb2781be59 100644
--- a/libs/binder/ProcessState.cpp
+++ b/libs/binder/ProcessState.cpp
@@ -18,10 +18,9 @@
#include <binder/ProcessState.h>
-#include <android-base/result.h>
-#include <android-base/scopeguard.h>
#include <android-base/strings.h>
#include <binder/BpBinder.h>
+#include <binder/Functional.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/Stability.h>
@@ -32,6 +31,7 @@
#include <utils/Thread.h>
#include "Static.h"
+#include "Utils.h"
#include "binder_module.h"
#include <errno.h>
@@ -60,6 +60,9 @@ const char* kDefaultDriver = "/dev/binder";
namespace android {
+using namespace android::binder::impl;
+using android::binder::unique_fd;
+
class PoolThread : public Thread
{
public:
@@ -104,14 +107,7 @@ bool ProcessState::isVndservicemanagerEnabled() {
return access("/vendor/bin/vndservicemanager", R_OK) == 0;
}
-sp<ProcessState> ProcessState::init(const char *driver, bool requireDefault)
-{
-#ifdef BINDER_IPC_32BIT
- LOG_ALWAYS_FATAL("32-bit binder IPC is not supported for new devices starting in Android P. If "
- "you do need to use this mode, please see b/232423610 or file an issue with "
- "AOSP upstream as otherwise this will be removed soon.");
-#endif
-
+sp<ProcessState> ProcessState::init(const char* driver, bool requireDefault) {
if (driver == nullptr) {
std::lock_guard<std::mutex> l(gProcessMutex);
if (gProcess) {
@@ -196,9 +192,10 @@ void ProcessState::childPostFork() {
void ProcessState::startThreadPool()
{
- AutoMutex _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
if (!mThreadPoolStarted) {
if (mMaxThreads == 0) {
+ // see also getThreadPoolMaxTotalThreadCount
ALOGW("Extra binder thread started, but 0 threads requested. Do not use "
"*startThreadPool when zero threads are requested.");
}
@@ -209,7 +206,7 @@ void ProcessState::startThreadPool()
bool ProcessState::becomeContextManager()
{
- AutoMutex _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
flat_binder_object obj {
.flags = FLAT_BINDER_FLAG_TXN_SECURITY_CTX,
@@ -316,7 +313,7 @@ sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
{
sp<IBinder> result;
- AutoMutex _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
if (handle == 0 && the_context_object != nullptr) return the_context_object;
@@ -380,7 +377,7 @@ sp<IBinder> ProcessState::getStrongProxyForHandle(int32_t handle)
void ProcessState::expungeHandle(int32_t handle, IBinder* binder)
{
- AutoMutex _l(mLock);
+ std::unique_lock<std::mutex> _l(mLock);
handle_entry* e = lookupHandleLocked(handle);
@@ -407,13 +404,18 @@ void ProcessState::spawnPooledThread(bool isMain)
{
if (mThreadPoolStarted) {
String8 name = makeBinderThreadName();
- ALOGV("Spawning new pooled thread, name=%s\n", name.string());
+ ALOGV("Spawning new pooled thread, name=%s\n", name.c_str());
sp<Thread> t = sp<PoolThread>::make(isMain);
- t->run(name.string());
+ t->run(name.c_str());
pthread_mutex_lock(&mThreadCountLock);
mKernelStartedThreads++;
pthread_mutex_unlock(&mThreadCountLock);
}
+ // TODO: if startThreadPool is called on another thread after the process
+ // starts up, the kernel might think that it already requested those
+ // binder threads, and additional won't be started. This is likely to
+ // cause deadlocks, and it will also cause getThreadPoolMaxTotalThreadCount
+ // to return too high of a value.
}
status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
@@ -431,14 +433,34 @@ status_t ProcessState::setThreadPoolMaxThreadCount(size_t maxThreads) {
size_t ProcessState::getThreadPoolMaxTotalThreadCount() const {
pthread_mutex_lock(&mThreadCountLock);
- base::ScopeGuard detachGuard = [&]() { pthread_mutex_unlock(&mThreadCountLock); };
+ auto detachGuard = make_scope_guard([&]() { pthread_mutex_unlock(&mThreadCountLock); });
- // may actually be one more than this, if join is called
if (mThreadPoolStarted) {
- return mCurrentThreads < mKernelStartedThreads
- ? mMaxThreads
- : mMaxThreads + mCurrentThreads - mKernelStartedThreads;
+ LOG_ALWAYS_FATAL_IF(mKernelStartedThreads > mMaxThreads + 1,
+ "too many kernel-started threads: %zu > %zu + 1", mKernelStartedThreads,
+ mMaxThreads);
+
+ // calling startThreadPool starts a thread
+ size_t threads = 1;
+
+ // the kernel is configured to start up to mMaxThreads more threads
+ threads += mMaxThreads;
+
+ // Users may call IPCThreadState::joinThreadPool directly. We don't
+ // currently have a way to count this directly (it could be added by
+ // adding a separate private joinKernelThread method in IPCThreadState).
+ // So, if we are in a race between the kernel thread variable being
+ // incremented in this file and mCurrentThreads being incremented
+ // in IPCThreadState, temporarily forget about the extra join threads.
+ // This is okay, because most callers of this method only care about
+ // having 0, 1, or more threads.
+ if (mCurrentThreads > mKernelStartedThreads) {
+ threads += mCurrentThreads - mKernelStartedThreads;
+ }
+
+ return threads;
}
+
// must not be initialized or maybe has poll thread setup, we
// currently don't track this in libbinder
LOG_ALWAYS_FATAL_IF(mKernelStartedThreads != 0,
@@ -470,6 +492,7 @@ bool ProcessState::isDriverFeatureEnabled(const DriverFeature feature) {
if (read(fd, &on, sizeof(on)) == -1) {
ALOGE("%s: error reading to %s: %s", __func__,
names[static_cast<int>(feature)], strerror(errno));
+ close(fd);
return false;
}
close(fd);
@@ -486,38 +509,38 @@ status_t ProcessState::enableOnewaySpamDetection(bool enable) {
}
void ProcessState::giveThreadPoolName() {
- androidSetThreadName( makeBinderThreadName().string() );
+ androidSetThreadName(makeBinderThreadName().c_str());
}
String8 ProcessState::getDriverName() {
return mDriverName;
}
-static base::Result<int> open_driver(const char* driver) {
- int fd = open(driver, O_RDWR | O_CLOEXEC);
- if (fd < 0) {
- return base::ErrnoError() << "Opening '" << driver << "' failed";
+static unique_fd open_driver(const char* driver) {
+ auto fd = unique_fd(open(driver, O_RDWR | O_CLOEXEC));
+ if (!fd.ok()) {
+ PLOGE("Opening '%s' failed", driver);
+ return {};
}
int vers = 0;
- status_t result = ioctl(fd, BINDER_VERSION, &vers);
+ int result = ioctl(fd.get(), BINDER_VERSION, &vers);
if (result == -1) {
- close(fd);
- return base::ErrnoError() << "Binder ioctl to obtain version failed";
+ PLOGE("Binder ioctl to obtain version failed");
+ return {};
}
if (result != 0 || vers != BINDER_CURRENT_PROTOCOL_VERSION) {
- close(fd);
- return base::Error() << "Binder driver protocol(" << vers
- << ") does not match user space protocol("
- << BINDER_CURRENT_PROTOCOL_VERSION
- << ")! ioctl() return value: " << result;
+ ALOGE("Binder driver protocol(%d) does not match user space protocol(%d)! "
+ "ioctl() return value: %d",
+ vers, BINDER_CURRENT_PROTOCOL_VERSION, result);
+ return {};
}
size_t maxThreads = DEFAULT_MAX_BINDER_THREADS;
- result = ioctl(fd, BINDER_SET_MAX_THREADS, &maxThreads);
+ result = ioctl(fd.get(), BINDER_SET_MAX_THREADS, &maxThreads);
if (result == -1) {
ALOGE("Binder ioctl to set max threads failed: %s", strerror(errno));
}
uint32_t enable = DEFAULT_ENABLE_ONEWAY_SPAM_DETECTION;
- result = ioctl(fd, BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
+ result = ioctl(fd.get(), BINDER_ENABLE_ONEWAY_SPAM_DETECTION, &enable);
if (result == -1) {
ALOGE_IF(ProcessState::isDriverFeatureEnabled(
ProcessState::DriverFeature::ONEWAY_SPAM_DETECTION),
@@ -542,28 +565,27 @@ ProcessState::ProcessState(const char* driver)
mThreadPoolStarted(false),
mThreadPoolSeq(1),
mCallRestriction(CallRestriction::NONE) {
- base::Result<int> opened = open_driver(driver);
+ unique_fd opened = open_driver(driver);
if (opened.ok()) {
// mmap the binder, providing a chunk of virtual address space to receive transactions.
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
- opened.value(), 0);
+ opened.get(), 0);
if (mVMStart == MAP_FAILED) {
- close(opened.value());
// *sigh*
- opened = base::Error()
- << "Using " << driver << " failed: unable to mmap transaction memory.";
+ ALOGE("Using %s failed: unable to mmap transaction memory.", driver);
+ opened.reset();
mDriverName.clear();
}
}
#ifdef __ANDROID__
- LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating: %s",
- driver, opened.error().message().c_str());
+ LOG_ALWAYS_FATAL_IF(!opened.ok(), "Binder driver '%s' could not be opened. Terminating.",
+ driver);
#endif
if (opened.ok()) {
- mDriverFD = opened.value();
+ mDriverFD = opened.release();
}
}
diff --git a/libs/binder/RecordedTransaction.cpp b/libs/binder/RecordedTransaction.cpp
index 2e7030437c..de2a69f7cf 100644
--- a/libs/binder/RecordedTransaction.cpp
+++ b/libs/binder/RecordedTransaction.cpp
@@ -14,16 +14,23 @@
* limitations under the License.
*/
-#include <android-base/file.h>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
+#include "file.h"
+
+#include <binder/Functional.h>
#include <binder/RecordedTransaction.h>
+#include <binder/unique_fd.h>
+
+#include <inttypes.h>
#include <sys/mman.h>
+#include <sys/stat.h>
#include <algorithm>
+using namespace android::binder::impl;
using android::Parcel;
-using android::base::borrowed_fd;
-using android::base::unique_fd;
+using android::binder::borrowed_fd;
+using android::binder::ReadFully;
+using android::binder::unique_fd;
+using android::binder::WriteFully;
using android::binder::debug::RecordedTransaction;
#define PADDING8(s) ((8 - (s) % 8) % 8)
@@ -107,8 +114,8 @@ static_assert(PADDING8(8) == 0);
RecordedTransaction::RecordedTransaction(RecordedTransaction&& t) noexcept {
mData = t.mData;
- mSent.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
- mReply.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
+ mSentDataOnly.setData(t.getDataParcel().data(), t.getDataParcel().dataSize());
+ mReplyDataOnly.setData(t.getReplyParcel().data(), t.getReplyParcel().dataSize());
}
std::optional<RecordedTransaction> RecordedTransaction::fromDetails(
@@ -123,21 +130,28 @@ std::optional<RecordedTransaction> RecordedTransaction::fromDetails(
static_cast<int32_t>(timestamp.tv_nsec),
0};
- t.mData.mInterfaceName = std::string(String8(interfaceName).string());
+ t.mData.mInterfaceName = std::string(String8(interfaceName).c_str());
if (interfaceName.size() != t.mData.mInterfaceName.size()) {
- LOG(ERROR) << "Interface Name is not valid. Contains characters that aren't single byte "
- "utf-8: "
- << interfaceName;
+ ALOGE("Interface Name is not valid. Contains characters that aren't single byte utf-8.");
return std::nullopt;
}
- if (t.mSent.setData(dataParcel.data(), dataParcel.dataSize()) != android::NO_ERROR) {
- LOG(ERROR) << "Failed to set sent parcel data.";
+ if (const auto* kernelFields = dataParcel.maybeKernelFields()) {
+ for (size_t i = 0; i < kernelFields->mObjectsSize; i++) {
+ uint64_t offset = kernelFields->mObjects[i];
+ t.mData.mSentObjectData.push_back(offset);
+ }
+ }
+
+ if (t.mSentDataOnly.setData(dataParcel.data(), dataParcel.dataBufferSize()) !=
+ android::NO_ERROR) {
+ ALOGE("Failed to set sent parcel data.");
return std::nullopt;
}
- if (t.mReply.setData(replyParcel.data(), replyParcel.dataSize()) != android::NO_ERROR) {
- LOG(ERROR) << "Failed to set reply parcel data.";
+ if (t.mReplyDataOnly.setData(replyParcel.data(), replyParcel.dataBufferSize()) !=
+ android::NO_ERROR) {
+ ALOGE("Failed to set reply parcel data.");
return std::nullopt;
}
@@ -149,6 +163,7 @@ enum {
DATA_PARCEL_CHUNK = 2,
REPLY_PARCEL_CHUNK = 3,
INTERFACE_NAME_CHUNK = 4,
+ DATA_PARCEL_OBJECT_CHUNK = 5,
END_CHUNK = 0x00ffffff,
};
@@ -161,53 +176,72 @@ static_assert(sizeof(ChunkDescriptor) % 8 == 0);
constexpr uint32_t kMaxChunkDataSize = 0xfffffff0;
typedef uint64_t transaction_checksum_t;
-static android::status_t readChunkDescriptor(borrowed_fd fd, ChunkDescriptor* chunkOut,
- transaction_checksum_t* sum) {
- if (!android::base::ReadFully(fd, chunkOut, sizeof(ChunkDescriptor))) {
- LOG(ERROR) << "Failed to read Chunk Descriptor from fd " << fd.get();
- return android::UNKNOWN_ERROR;
- }
-
- *sum ^= *reinterpret_cast<transaction_checksum_t*>(chunkOut);
- return android::NO_ERROR;
-}
-
std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd& fd) {
RecordedTransaction t;
ChunkDescriptor chunk;
const long pageSize = sysconf(_SC_PAGE_SIZE);
+ struct stat fileStat;
+ if (fstat(fd.get(), &fileStat) != 0) {
+ ALOGE("Unable to get file information");
+ return std::nullopt;
+ }
+
+ off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+ if (fdCurrentPosition == -1) {
+ ALOGE("Invalid offset in file descriptor.");
+ return std::nullopt;
+ }
do {
- transaction_checksum_t checksum = 0;
- if (NO_ERROR != readChunkDescriptor(fd, &chunk, &checksum)) {
- LOG(ERROR) << "Failed to read chunk descriptor.";
+ if (fileStat.st_size < (fdCurrentPosition + (off_t)sizeof(ChunkDescriptor))) {
+ ALOGE("Not enough file remains to contain expected chunk descriptor");
+ return std::nullopt;
+ }
+
+ if (!ReadFully(fd, &chunk, sizeof(ChunkDescriptor))) {
+ ALOGE("Failed to read ChunkDescriptor from fd %d. %s", fd.get(), strerror(errno));
+ return std::nullopt;
+ }
+ transaction_checksum_t checksum = *reinterpret_cast<transaction_checksum_t*>(&chunk);
+
+ fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
+ if (fdCurrentPosition == -1) {
+ ALOGE("Invalid offset in file descriptor.");
return std::nullopt;
}
- off_t fdCurrentPosition = lseek(fd.get(), 0, SEEK_CUR);
off_t mmapPageAlignedStart = (fdCurrentPosition / pageSize) * pageSize;
off_t mmapPayloadStartOffset = fdCurrentPosition - mmapPageAlignedStart;
if (chunk.dataSize > kMaxChunkDataSize) {
- LOG(ERROR) << "Chunk data exceeds maximum size.";
+ ALOGE("Chunk data exceeds maximum size.");
return std::nullopt;
}
size_t chunkPayloadSize =
chunk.dataSize + PADDING8(chunk.dataSize) + sizeof(transaction_checksum_t);
+ if (chunkPayloadSize > (size_t)(fileStat.st_size - fdCurrentPosition)) {
+ ALOGE("Chunk payload exceeds remaining file size.");
+ return std::nullopt;
+ }
+
if (PADDING8(chunkPayloadSize) != 0) {
- LOG(ERROR) << "Invalid chunk size, not aligned " << chunkPayloadSize;
+ ALOGE("Invalid chunk size, not aligned %zu", chunkPayloadSize);
return std::nullopt;
}
- transaction_checksum_t* payloadMap = reinterpret_cast<transaction_checksum_t*>(
- mmap(NULL, chunkPayloadSize + mmapPayloadStartOffset, PROT_READ, MAP_SHARED,
- fd.get(), mmapPageAlignedStart));
+ size_t memoryMappedSize = chunkPayloadSize + mmapPayloadStartOffset;
+ void* mappedMemory =
+ mmap(NULL, memoryMappedSize, PROT_READ, MAP_SHARED, fd.get(), mmapPageAlignedStart);
+ auto mmap_guard = make_scope_guard(
+ [mappedMemory, memoryMappedSize] { munmap(mappedMemory, memoryMappedSize); });
+
+ transaction_checksum_t* payloadMap =
+ reinterpret_cast<transaction_checksum_t*>(mappedMemory);
payloadMap += mmapPayloadStartOffset /
sizeof(transaction_checksum_t); // Skip chunk descriptor and required mmap
// page-alignment
if (payloadMap == MAP_FAILED) {
- LOG(ERROR) << "Memory mapping failed for fd " << fd.get() << ": " << errno << " "
- << strerror(errno);
+ ALOGE("Memory mapping failed for fd %d: %d %s", fd.get(), errno, strerror(errno));
return std::nullopt;
}
for (size_t checksumIndex = 0;
@@ -215,16 +249,21 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd
checksum ^= payloadMap[checksumIndex];
}
if (checksum != 0) {
- LOG(ERROR) << "Checksum failed.";
+ ALOGE("Checksum failed.");
+ return std::nullopt;
+ }
+
+ fdCurrentPosition = lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
+ if (fdCurrentPosition == -1) {
+ ALOGE("Invalid offset in file descriptor.");
return std::nullopt;
}
- lseek(fd.get(), chunkPayloadSize, SEEK_CUR);
switch (chunk.chunkType) {
case HEADER_CHUNK: {
if (chunk.dataSize != static_cast<uint32_t>(sizeof(TransactionHeader))) {
- LOG(ERROR) << "Header Chunk indicated size " << chunk.dataSize << "; Expected "
- << sizeof(TransactionHeader) << ".";
+ ALOGE("Header Chunk indicated size %" PRIu32 "; Expected %zu.", chunk.dataSize,
+ sizeof(TransactionHeader));
return std::nullopt;
}
t.mData.mHeader = *reinterpret_cast<TransactionHeader*>(payloadMap);
@@ -236,26 +275,35 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd
break;
}
case DATA_PARCEL_CHUNK: {
- if (t.mSent.setData(reinterpret_cast<const unsigned char*>(payloadMap),
- chunk.dataSize) != android::NO_ERROR) {
- LOG(ERROR) << "Failed to set sent parcel data.";
+ if (t.mSentDataOnly.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+ chunk.dataSize) != android::NO_ERROR) {
+ ALOGE("Failed to set sent parcel data.");
return std::nullopt;
}
break;
}
case REPLY_PARCEL_CHUNK: {
- if (t.mReply.setData(reinterpret_cast<const unsigned char*>(payloadMap),
- chunk.dataSize) != android::NO_ERROR) {
- LOG(ERROR) << "Failed to set reply parcel data.";
+ if (t.mReplyDataOnly.setData(reinterpret_cast<const unsigned char*>(payloadMap),
+ chunk.dataSize) != android::NO_ERROR) {
+ ALOGE("Failed to set reply parcel data.");
return std::nullopt;
}
break;
}
+ case DATA_PARCEL_OBJECT_CHUNK: {
+ const uint64_t* objects = reinterpret_cast<const uint64_t*>(payloadMap);
+ size_t metaDataSize = (chunk.dataSize / sizeof(uint64_t));
+ ALOGI("Total objects found in saved parcel %zu", metaDataSize);
+ for (size_t index = 0; index < metaDataSize; ++index) {
+ t.mData.mSentObjectData.push_back(objects[index]);
+ }
+ break;
+ }
case END_CHUNK:
break;
default:
- LOG(INFO) << "Unrecognized chunk.";
- continue;
+ ALOGI("Unrecognized chunk.");
+ break;
}
} while (chunk.chunkType != END_CHUNK);
@@ -265,7 +313,7 @@ std::optional<RecordedTransaction> RecordedTransaction::fromFile(const unique_fd
android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunkType,
size_t byteCount, const uint8_t* data) const {
if (byteCount > kMaxChunkDataSize) {
- LOG(ERROR) << "Chunk data exceeds maximum size";
+ ALOGE("Chunk data exceeds maximum size");
return BAD_VALUE;
}
ChunkDescriptor descriptor = {.chunkType = chunkType,
@@ -293,8 +341,8 @@ android::status_t RecordedTransaction::writeChunk(borrowed_fd fd, uint32_t chunk
buffer.insert(buffer.end(), checksumBytes, checksumBytes + sizeof(transaction_checksum_t));
// Write buffer to file
- if (!android::base::WriteFully(fd, buffer.data(), buffer.size())) {
- LOG(ERROR) << "Failed to write chunk fd " << fd.get();
+ if (!WriteFully(fd, buffer.data(), buffer.size())) {
+ ALOGE("Failed to write chunk fd %d", fd.get());
return UNKNOWN_ERROR;
}
return NO_ERROR;
@@ -304,26 +352,38 @@ android::status_t RecordedTransaction::dumpToFile(const unique_fd& fd) const {
if (NO_ERROR !=
writeChunk(fd, HEADER_CHUNK, sizeof(TransactionHeader),
reinterpret_cast<const uint8_t*>(&(mData.mHeader)))) {
- LOG(ERROR) << "Failed to write transactionHeader to fd " << fd.get();
+ ALOGE("Failed to write transactionHeader to fd %d", fd.get());
return UNKNOWN_ERROR;
}
if (NO_ERROR !=
writeChunk(fd, INTERFACE_NAME_CHUNK, mData.mInterfaceName.size() * sizeof(uint8_t),
reinterpret_cast<const uint8_t*>(mData.mInterfaceName.c_str()))) {
- LOG(INFO) << "Failed to write Interface Name Chunk to fd " << fd.get();
+ ALOGI("Failed to write Interface Name Chunk to fd %d", fd.get());
return UNKNOWN_ERROR;
}
- if (NO_ERROR != writeChunk(fd, DATA_PARCEL_CHUNK, mSent.dataSize(), mSent.data())) {
- LOG(ERROR) << "Failed to write sent Parcel to fd " << fd.get();
+ if (NO_ERROR !=
+ writeChunk(fd, DATA_PARCEL_CHUNK, mSentDataOnly.dataBufferSize(), mSentDataOnly.data())) {
+ ALOGE("Failed to write sent Parcel to fd %d", fd.get());
+ return UNKNOWN_ERROR;
+ }
+
+ if (NO_ERROR !=
+ writeChunk(fd, REPLY_PARCEL_CHUNK, mReplyDataOnly.dataBufferSize(),
+ mReplyDataOnly.data())) {
+ ALOGE("Failed to write reply Parcel to fd %d", fd.get());
return UNKNOWN_ERROR;
}
- if (NO_ERROR != writeChunk(fd, REPLY_PARCEL_CHUNK, mReply.dataSize(), mReply.data())) {
- LOG(ERROR) << "Failed to write reply Parcel to fd " << fd.get();
+
+ if (NO_ERROR !=
+ writeChunk(fd, DATA_PARCEL_OBJECT_CHUNK, mData.mSentObjectData.size() * sizeof(uint64_t),
+ reinterpret_cast<const uint8_t*>(mData.mSentObjectData.data()))) {
+ ALOGE("Failed to write sent parcel object metadata to fd %d", fd.get());
return UNKNOWN_ERROR;
}
+
if (NO_ERROR != writeChunk(fd, END_CHUNK, 0, NULL)) {
- LOG(ERROR) << "Failed to write end chunk to fd " << fd.get();
+ ALOGE("Failed to write end chunk to fd %d", fd.get());
return UNKNOWN_ERROR;
}
return NO_ERROR;
@@ -355,10 +415,14 @@ uint32_t RecordedTransaction::getVersion() const {
return mData.mHeader.version;
}
+const std::vector<uint64_t>& RecordedTransaction::getObjectOffsets() const {
+ return mData.mSentObjectData;
+}
+
const Parcel& RecordedTransaction::getDataParcel() const {
- return mSent;
+ return mSentDataOnly;
}
const Parcel& RecordedTransaction::getReplyParcel() const {
- return mReply;
+ return mReplyDataOnly;
}
diff --git a/libs/binder/RpcServer.cpp b/libs/binder/RpcServer.cpp
index 0d06e9e83b..d9e926a9d5 100644
--- a/libs/binder/RpcServer.cpp
+++ b/libs/binder/RpcServer.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "RpcServer"
#include <inttypes.h>
+#include <netinet/tcp.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/un.h>
@@ -24,13 +25,11 @@
#include <thread>
#include <vector>
-#include <android-base/hex.h>
-#include <android-base/scopeguard.h>
+#include <binder/Functional.h>
#include <binder/Parcel.h>
#include <binder/RpcServer.h>
#include <binder/RpcTransportRaw.h>
#include <log/log.h>
-#include <utils/Compat.h>
#include "BuildFlags.h"
#include "FdTrigger.h"
@@ -45,8 +44,9 @@ namespace android {
constexpr size_t kSessionIdBytes = 32;
-using base::ScopeGuard;
-using base::unique_fd;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
RpcServer::RpcServer(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {}
RpcServer::~RpcServer() {
@@ -57,7 +57,7 @@ RpcServer::~RpcServer() {
sp<RpcServer> RpcServer::make(std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
// Default is without TLS.
if (rpcTransportCtxFactory == nullptr)
- rpcTransportCtxFactory = makeDefaultRpcTransportCtxFactory();
+ rpcTransportCtxFactory = binder::os::makeDefaultRpcTransportCtxFactory();
auto ctx = rpcTransportCtxFactory->newServerCtx();
if (ctx == nullptr) return nullptr;
return sp<RpcServer>::make(std::move(ctx));
@@ -81,6 +81,7 @@ status_t RpcServer::setupInetServer(const char* address, unsigned int port,
auto aiStart = InetSocketAddress::getAddrInfo(address, port);
if (aiStart == nullptr) return UNKNOWN_ERROR;
for (auto ai = aiStart.get(); ai != nullptr; ai = ai->ai_next) {
+ if (ai->ai_addr == nullptr) continue;
InetSocketAddress socketAddress(ai->ai_addr, ai->ai_addrlen, address, port);
if (status_t status = setupSocketServer(socketAddress); status != OK) {
continue;
@@ -123,8 +124,13 @@ size_t RpcServer::getMaxThreads() {
return mMaxThreads;
}
-void RpcServer::setProtocolVersion(uint32_t version) {
+bool RpcServer::setProtocolVersion(uint32_t version) {
+ if (!RpcState::validateProtocolVersion(version)) {
+ return false;
+ }
+
mProtocolVersion = version;
+ return true;
}
void RpcServer::setSupportedFileDescriptorTransportModes(
@@ -148,7 +154,7 @@ void RpcServer::setRootObjectWeak(const wp<IBinder>& binder) {
mRootObjectWeak = binder;
}
void RpcServer::setPerSessionRootObject(
- std::function<sp<IBinder>(const void*, size_t)>&& makeObject) {
+ std::function<sp<IBinder>(wp<RpcSession> session, const void*, size_t)>&& makeObject) {
RpcMutexLockGuard _l(mLock);
mRootObject.clear();
mRootObjectWeak.clear();
@@ -161,6 +167,12 @@ void RpcServer::setConnectionFilter(std::function<bool(const void*, size_t)>&& f
mConnectionFilter = std::move(filter);
}
+void RpcServer::setServerSocketModifier(std::function<void(borrowed_fd)>&& modifier) {
+ RpcMutexLockGuard _l(mLock);
+ LOG_ALWAYS_FATAL_IF(mServer.fd.ok(), "Already started");
+ mServerSocketModifier = std::move(modifier);
+}
+
sp<IBinder> RpcServer::getRootObject() {
RpcMutexLockGuard _l(mLock);
bool hasWeak = mRootObjectWeak.unsafe_get();
@@ -189,7 +201,7 @@ void RpcServer::start() {
status_t RpcServer::acceptSocketConnection(const RpcServer& server, RpcTransportFd* out) {
RpcTransportFd clientSocket(unique_fd(TEMP_FAILURE_RETRY(
accept4(server.mServer.fd.get(), nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK))));
- if (clientSocket.fd < 0) {
+ if (!clientSocket.fd.ok()) {
int savedErrno = errno;
ALOGE("Could not accept4 socket: %s", strerror(savedErrno));
return -savedErrno;
@@ -202,9 +214,9 @@ status_t RpcServer::acceptSocketConnection(const RpcServer& server, RpcTransport
status_t RpcServer::recvmsgSocketConnection(const RpcServer& server, RpcTransportFd* out) {
int zero = 0;
iovec iov{&zero, sizeof(zero)};
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+ std::vector<std::variant<unique_fd, borrowed_fd>> fds;
- ssize_t num_bytes = receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
+ ssize_t num_bytes = binder::os::receiveMessageFromSocket(server.mServer, &iov, 1, &fds);
if (num_bytes < 0) {
int savedErrno = errno;
ALOGE("Failed recvmsg: %s", strerror(savedErrno));
@@ -219,10 +231,7 @@ status_t RpcServer::recvmsgSocketConnection(const RpcServer& server, RpcTranspor
}
unique_fd fd(std::move(std::get<unique_fd>(fds.back())));
- if (auto res = setNonBlocking(fd); !res.ok()) {
- ALOGE("Failed setNonBlocking: %s", res.error().message().c_str());
- return res.error().code() == 0 ? UNKNOWN_ERROR : -res.error().code();
- }
+ if (status_t res = binder::os::setNonBlocking(fd); res != OK) return res;
*out = RpcTransportFd(std::move(fd));
return OK;
@@ -295,7 +304,8 @@ void RpcServer::join() {
bool RpcServer::shutdown() {
RpcMutexUniqueLock _l(mLock);
if (mShutdownTrigger == nullptr) {
- LOG_RPC_DETAIL("Cannot shutdown. No shutdown trigger installed (already shutdown?)");
+ LOG_RPC_DETAIL("Cannot shutdown. No shutdown trigger installed (already shutdown, or not "
+ "joined yet?)");
return false;
}
@@ -334,6 +344,8 @@ bool RpcServer::shutdown() {
mJoinThread.reset();
}
+ mServer = RpcTransportFd();
+
LOG_RPC_DETAIL("Finished waiting on shutdown.");
mShutdownTrigger = nullptr;
@@ -443,11 +455,12 @@ void RpcServer::establishConnection(
LOG_ALWAYS_FATAL_IF(threadId == server->mConnectingThreads.end(),
"Must establish connection on owned thread");
thisThread = std::move(threadId->second);
- ScopeGuard detachGuard = [&]() {
+ auto detachGuardLambda = [&]() {
thisThread.detach();
_l.unlock();
server->mShutdownCv.notify_all();
};
+ auto detachGuard = make_scope_guard(std::ref(detachGuardLambda));
server->mConnectingThreads.erase(threadId);
if (status != OK || server->mShutdownTrigger->isTriggered()) {
@@ -469,11 +482,11 @@ void RpcServer::establishConnection(
// don't block if there is some entropy issue
if (tries++ > 5) {
ALOGE("Cannot find new address: %s",
- base::HexString(sessionId.data(), sessionId.size()).c_str());
+ HexString(sessionId.data(), sessionId.size()).c_str());
return;
}
- auto status = getRandomBytes(sessionId.data(), sessionId.size());
+ auto status = binder::os::getRandomBytes(sessionId.data(), sessionId.size());
if (status != OK) {
ALOGE("Failed to read random session ID: %s", strerror(-status));
return;
@@ -500,7 +513,8 @@ void RpcServer::establishConnection(
// if null, falls back to server root
sp<IBinder> sessionSpecificRoot;
if (server->mRootObjectFactory != nullptr) {
- sessionSpecificRoot = server->mRootObjectFactory(addr.data(), addrLen);
+ sessionSpecificRoot =
+ server->mRootObjectFactory(wp<RpcSession>(session), addr.data(), addrLen);
if (sessionSpecificRoot == nullptr) {
ALOGE("Warning: server returned null from root object factory");
}
@@ -520,7 +534,7 @@ void RpcServer::establishConnection(
auto it = server->mSessions.find(sessionId);
if (it == server->mSessions.end()) {
ALOGE("Cannot add thread, no record of session with ID %s",
- base::HexString(sessionId.data(), sessionId.size()).c_str());
+ HexString(sessionId.data(), sessionId.size()).c_str());
return;
}
session = it->second;
@@ -532,7 +546,7 @@ void RpcServer::establishConnection(
return;
}
- detachGuard.Disable();
+ detachGuard.release();
session->preJoinThreadOwnership(std::move(thisThread));
}
@@ -552,9 +566,28 @@ status_t RpcServer::setupSocketServer(const RpcSocketAddress& addr) {
socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
if (!socket_fd.ok()) {
int savedErrno = errno;
- ALOGE("Could not create socket: %s", strerror(savedErrno));
+ ALOGE("Could not create socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
return -savedErrno;
}
+
+ if (addr.addr()->sa_family == AF_INET || addr.addr()->sa_family == AF_INET6) {
+ int noDelay = 1;
+ int result =
+ setsockopt(socket_fd.get(), IPPROTO_TCP, TCP_NODELAY, &noDelay, sizeof(noDelay));
+ if (result < 0) {
+ int savedErrno = errno;
+ ALOGE("Could not set TCP_NODELAY on %s", strerror(savedErrno));
+ return -savedErrno;
+ }
+ }
+
+ {
+ RpcMutexLockGuard _l(mLock);
+ if (mServerSocketModifier != nullptr) {
+ mServerSocketModifier(socket_fd);
+ }
+ }
+
if (0 != TEMP_FAILURE_RETRY(bind(socket_fd.get(), addr.addr(), addr.addrSize()))) {
int savedErrno = errno;
ALOGE("Could not bind socket at %s: %s", addr.toString().c_str(), strerror(savedErrno));
@@ -586,15 +619,14 @@ status_t RpcServer::setupRawSocketServer(unique_fd socket_fd) {
void RpcServer::onSessionAllIncomingThreadsEnded(const sp<RpcSession>& session) {
const std::vector<uint8_t>& id = session->mId;
LOG_ALWAYS_FATAL_IF(id.empty(), "Server sessions must be initialized with ID");
- LOG_RPC_DETAIL("Dropping session with address %s",
- base::HexString(id.data(), id.size()).c_str());
+ LOG_RPC_DETAIL("Dropping session with address %s", HexString(id.data(), id.size()).c_str());
RpcMutexLockGuard _l(mLock);
auto it = mSessions.find(id);
LOG_ALWAYS_FATAL_IF(it == mSessions.end(), "Bad state, unknown session id %s",
- base::HexString(id.data(), id.size()).c_str());
+ HexString(id.data(), id.size()).c_str());
LOG_ALWAYS_FATAL_IF(it->second != session, "Bad state, session has id mismatch %s",
- base::HexString(id.data(), id.size()).c_str());
+ HexString(id.data(), id.size()).c_str());
(void)mSessions.erase(it);
}
@@ -613,8 +645,7 @@ unique_fd RpcServer::releaseServer() {
}
status_t RpcServer::setupExternalServer(
- base::unique_fd serverFd,
- std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn) {
+ unique_fd serverFd, std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn) {
RpcMutexLockGuard _l(mLock);
if (mServer.fd.ok()) {
ALOGE("Each RpcServer can only have one server.");
@@ -625,7 +656,7 @@ status_t RpcServer::setupExternalServer(
return OK;
}
-status_t RpcServer::setupExternalServer(base::unique_fd serverFd) {
+status_t RpcServer::setupExternalServer(unique_fd serverFd) {
return setupExternalServer(std::move(serverFd), &RpcServer::acceptSocketConnection);
}
diff --git a/libs/binder/RpcSession.cpp b/libs/binder/RpcSession.cpp
index ce6ef2becf..16a7f9fd48 100644
--- a/libs/binder/RpcSession.cpp
+++ b/libs/binder/RpcSession.cpp
@@ -20,20 +20,18 @@
#include <dlfcn.h>
#include <inttypes.h>
+#include <netinet/tcp.h>
#include <poll.h>
#include <unistd.h>
#include <string_view>
-#include <android-base/hex.h>
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
#include <binder/BpBinder.h>
+#include <binder/Functional.h>
#include <binder/Parcel.h>
#include <binder/RpcServer.h>
#include <binder/RpcTransportRaw.h>
#include <binder/Stability.h>
-#include <utils/Compat.h>
#include <utils/String8.h>
#include "BuildFlags.h"
@@ -52,7 +50,9 @@ extern "C" JavaVM* AndroidRuntimeGetJavaVM();
namespace android {
-using base::unique_fd;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
RpcSession::RpcSession(std::unique_ptr<RpcTransportCtx> ctx) : mCtx(std::move(ctx)) {
LOG_RPC_DETAIL("RpcSession created %p", this);
@@ -69,7 +69,7 @@ RpcSession::~RpcSession() {
sp<RpcSession> RpcSession::make() {
// Default is without TLS.
- return make(makeDefaultRpcTransportCtxFactory());
+ return make(binder::os::makeDefaultRpcTransportCtxFactory());
}
sp<RpcSession> RpcSession::make(std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
@@ -90,24 +90,20 @@ size_t RpcSession::getMaxIncomingThreads() {
return mMaxIncomingThreads;
}
-void RpcSession::setMaxOutgoingThreads(size_t threads) {
+void RpcSession::setMaxOutgoingConnections(size_t connections) {
RpcMutexLockGuard _l(mMutex);
LOG_ALWAYS_FATAL_IF(mStartedSetup,
"Must set max outgoing threads before setting up connections");
- mMaxOutgoingThreads = threads;
+ mMaxOutgoingConnections = connections;
}
size_t RpcSession::getMaxOutgoingThreads() {
RpcMutexLockGuard _l(mMutex);
- return mMaxOutgoingThreads;
+ return mMaxOutgoingConnections;
}
bool RpcSession::setProtocolVersionInternal(uint32_t version, bool checkStarted) {
- if (version >= RPC_WIRE_PROTOCOL_VERSION_NEXT &&
- version != RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
- ALOGE("Cannot start RPC session with version %u which is unknown (current protocol version "
- "is %u).",
- version, RPC_WIRE_PROTOCOL_VERSION);
+ if (!RpcState::validateProtocolVersion(version)) {
return false;
}
@@ -162,7 +158,7 @@ status_t RpcSession::setupUnixDomainSocketBootstrapClient(unique_fd bootstrapFd)
int zero = 0;
iovec iov{&zero, sizeof(zero)};
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+ std::vector<std::variant<unique_fd, borrowed_fd>> fds;
fds.push_back(std::move(serverFd));
status_t status = mBootstrapTransport->interruptableWriteFully(mShutdownTrigger.get(), &iov,
@@ -191,17 +187,13 @@ status_t RpcSession::setupInetClient(const char* addr, unsigned int port) {
return NAME_NOT_FOUND;
}
-status_t RpcSession::setupPreconnectedClient(base::unique_fd fd,
- std::function<unique_fd()>&& request) {
+status_t RpcSession::setupPreconnectedClient(unique_fd fd, std::function<unique_fd()>&& request) {
return setupClient([&](const std::vector<uint8_t>& sessionId, bool incoming) -> status_t {
if (!fd.ok()) {
fd = request();
if (!fd.ok()) return BAD_VALUE;
}
- if (auto res = setNonBlocking(fd); !res.ok()) {
- ALOGE("setupPreconnectedClient: %s", res.error().message().c_str());
- return res.error().code() == 0 ? UNKNOWN_ERROR : -res.error().code();
- }
+ if (status_t res = binder::os::setNonBlocking(fd); res != OK) return res;
RpcTransportFd transportFd(std::move(fd));
status_t status = initAndAddConnection(std::move(transportFd), sessionId, incoming);
@@ -216,7 +208,7 @@ status_t RpcSession::addNullDebuggingClient() {
unique_fd serverFd(TEMP_FAILURE_RETRY(open("/dev/null", O_WRONLY | O_CLOEXEC)));
- if (serverFd == -1) {
+ if (!serverFd.ok()) {
int savedErrno = errno;
ALOGE("Could not connect to /dev/null: %s", strerror(savedErrno));
return -savedErrno;
@@ -313,8 +305,7 @@ status_t RpcSession::readId() {
status = state()->getSessionId(connection.get(), sp<RpcSession>::fromExisting(this), &mId);
if (status != OK) return status;
- LOG_RPC_DETAIL("RpcSession %p has id %s", this,
- base::HexString(mId.data(), mId.size()).c_str());
+ LOG_RPC_DETAIL("RpcSession %p has id %s", this, HexString(mId.data(), mId.size()).c_str());
return OK;
}
@@ -417,7 +408,9 @@ public:
}
private:
- DISALLOW_COPY_AND_ASSIGN(JavaThreadAttacher);
+ JavaThreadAttacher(const JavaThreadAttacher&) = delete;
+ void operator=(const JavaThreadAttacher&) = delete;
+
bool mAttached = false;
static JavaVM* getJavaVM() {
@@ -502,7 +495,7 @@ status_t RpcSession::setupClient(const std::function<status_t(const std::vector<
if (auto status = initShutdownTrigger(); status != OK) return status;
auto oldProtocolVersion = mProtocolVersion;
- auto cleanup = base::ScopeGuard([&] {
+ auto cleanup = make_scope_guard([&] {
// if any threads are started, shut them down
(void)shutdownAndWait(true);
@@ -558,11 +551,11 @@ status_t RpcSession::setupClient(const std::function<status_t(const std::vector<
return status;
}
- size_t outgoingThreads = std::min(numThreadsAvailable, mMaxOutgoingThreads);
- ALOGI_IF(outgoingThreads != numThreadsAvailable,
+ size_t outgoingConnections = std::min(numThreadsAvailable, mMaxOutgoingConnections);
+ ALOGI_IF(outgoingConnections != numThreadsAvailable,
"Server hints client to start %zu outgoing threads, but client will only start %zu "
"because it is preconfigured to start at most %zu outgoing threads.",
- numThreadsAvailable, outgoingThreads, mMaxOutgoingThreads);
+ numThreadsAvailable, outgoingConnections, mMaxOutgoingConnections);
// TODO(b/189955605): we should add additional sessions dynamically
// instead of all at once - the other side should be responsible for setting
@@ -571,10 +564,10 @@ status_t RpcSession::setupClient(const std::function<status_t(const std::vector<
// any requests at all.
// we've already setup one client
- LOG_RPC_DETAIL("RpcSession::setupClient() instantiating %zu outgoing (server max: %zu) and %zu "
- "incoming threads",
- outgoingThreads, numThreadsAvailable, mMaxIncomingThreads);
- for (size_t i = 0; i + 1 < outgoingThreads; i++) {
+ LOG_RPC_DETAIL("RpcSession::setupClient() instantiating %zu outgoing connections (server max: "
+ "%zu) and %zu incoming threads",
+ outgoingConnections, numThreadsAvailable, mMaxIncomingThreads);
+ for (size_t i = 0; i + 1 < outgoingConnections; i++) {
if (status_t status = connectAndInit(mId, false /*incoming*/); status != OK) return status;
}
@@ -582,7 +575,7 @@ status_t RpcSession::setupClient(const std::function<status_t(const std::vector<
if (status_t status = connectAndInit(mId, true /*incoming*/); status != OK) return status;
}
- cleanup.Disable();
+ cleanup.release();
return OK;
}
@@ -601,13 +594,25 @@ status_t RpcSession::setupOneSocketConnection(const RpcSocketAddress& addr,
unique_fd serverFd(TEMP_FAILURE_RETRY(
socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
- if (serverFd == -1) {
+ if (!serverFd.ok()) {
int savedErrno = errno;
ALOGE("Could not create socket at %s: %s", addr.toString().c_str(),
strerror(savedErrno));
return -savedErrno;
}
+ if (addr.addr()->sa_family == AF_INET || addr.addr()->sa_family == AF_INET6) {
+ int noDelay = 1;
+ int result =
+ setsockopt(serverFd.get(), IPPROTO_TCP, TCP_NODELAY, &noDelay, sizeof(noDelay));
+ if (result < 0) {
+ int savedErrno = errno;
+ ALOGE("Could not set TCP_NODELAY on %s: %s", addr.toString().c_str(),
+ strerror(savedErrno));
+ return -savedErrno;
+ }
+ }
+
RpcTransportFd transportFd(std::move(serverFd));
if (0 != TEMP_FAILURE_RETRY(connect(transportFd.fd.get(), addr.addr(), addr.addrSize()))) {
@@ -700,7 +705,7 @@ status_t RpcSession::initAndAddConnection(RpcTransportFd fd, const std::vector<u
std::nullopt, nullptr);
if (sendSessionIdStatus != OK) {
ALOGE("Could not write session ID ('%s') to socket: %s",
- base::HexString(sessionId.data(), sessionId.size()).c_str(),
+ HexString(sessionId.data(), sessionId.size()).c_str(),
statusToString(sendSessionIdStatus).c_str());
return sendSessionIdStatus;
}
@@ -761,7 +766,7 @@ status_t RpcSession::addOutgoingConnection(std::unique_ptr<RpcTransport> rpcTran
{
RpcMutexLockGuard _l(mMutex);
connection->rpcTransport = std::move(rpcTransport);
- connection->exclusiveTid = rpcGetThreadId();
+ connection->exclusiveTid = binder::os::GetThreadId();
mConnections.mOutgoing.push_back(connection);
}
@@ -816,7 +821,7 @@ sp<RpcSession::RpcConnection> RpcSession::assignIncomingConnectionToThisThread(
sp<RpcConnection> session = sp<RpcConnection>::make();
session->rpcTransport = std::move(rpcTransport);
- session->exclusiveTid = rpcGetThreadId();
+ session->exclusiveTid = binder::os::GetThreadId();
mConnections.mIncoming.push_back(session);
mConnections.mMaxIncoming = mConnections.mIncoming.size();
@@ -861,7 +866,7 @@ status_t RpcSession::ExclusiveConnection::find(const sp<RpcSession>& session, Co
connection->mConnection = nullptr;
connection->mReentrant = false;
- uint64_t tid = rpcGetThreadId();
+ uint64_t tid = binder::os::GetThreadId();
RpcMutexUniqueLock _l(session->mMutex);
session->mConnections.mWaitingThreads++;
@@ -932,7 +937,8 @@ status_t RpcSession::ExclusiveConnection::find(const sp<RpcSession>& session, Co
(session->server()
? "This is a server session, so see RpcSession::setMaxIncomingThreads "
"for the corresponding client"
- : "This is a client session, so see RpcSession::setMaxOutgoingThreads "
+ : "This is a client session, so see "
+ "RpcSession::setMaxOutgoingConnections "
"for this client or RpcServer::setMaxThreads for the corresponding "
"server"));
return WOULD_BLOCK;
diff --git a/libs/binder/RpcState.cpp b/libs/binder/RpcState.cpp
index 2b0e5bae0d..fe6e1a3318 100644
--- a/libs/binder/RpcState.cpp
+++ b/libs/binder/RpcState.cpp
@@ -18,11 +18,8 @@
#include "RpcState.h"
-#include <android-base/hex.h>
-#include <android-base/macros.h>
-#include <android-base/scopeguard.h>
-#include <android-base/stringprintf.h>
#include <binder/BpBinder.h>
+#include <binder/Functional.h>
#include <binder/IPCThreadState.h>
#include <binder/RpcServer.h>
@@ -31,12 +28,19 @@
#include "Utils.h"
#include <random>
+#include <sstream>
#include <inttypes.h>
+#ifdef __ANDROID__
+#include <cutils/properties.h>
+#endif
+
namespace android {
-using base::StringPrintf;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
#if RPC_FLAKE_PRONE
void rpcMaybeWaitToFlake() {
@@ -59,6 +63,7 @@ static bool enableAncillaryFds(RpcSession::FileDescriptorTransportMode mode) {
case RpcSession::FileDescriptorTransportMode::TRUSTY:
return true;
}
+ LOG_ALWAYS_FATAL("Invalid FileDescriptorTransportMode: %d", static_cast<int>(mode));
}
RpcState::RpcState() {}
@@ -262,8 +267,10 @@ void RpcState::dump() {
}
void RpcState::clear() {
- RpcMutexUniqueLock _l(mNodeMutex);
+ return clear(RpcMutexUniqueLock(mNodeMutex));
+}
+void RpcState::clear(RpcMutexUniqueLock nodeLock) {
if (mTerminated) {
LOG_ALWAYS_FATAL_IF(!mNodeForAddress.empty(),
"New state should be impossible after terminating!");
@@ -292,7 +299,7 @@ void RpcState::clear() {
auto temp = std::move(mNodeForAddress);
mNodeForAddress.clear(); // RpcState isn't reusable, but for future/explicit
- _l.unlock();
+ nodeLock.unlock();
temp.clear(); // explicit
}
@@ -323,8 +330,10 @@ std::string RpcState::BinderNode::toString() const {
desc = "(not promotable)";
}
- return StringPrintf("node{%p times sent: %zu times recd: %zu type: %s}",
- this->binder.unsafe_get(), this->timesSent, this->timesRecd, desc);
+ std::stringstream ss;
+ ss << "node{" << intptr_t(this->binder.unsafe_get()) << " times sent: " << this->timesSent
+ << " times recd: " << this->timesRecd << " type: " << desc << "}";
+ return ss.str();
}
RpcState::CommandData::CommandData(size_t size) : mSize(size) {
@@ -348,15 +357,14 @@ RpcState::CommandData::CommandData(size_t size) : mSize(size) {
mData.reset(new (std::nothrow) uint8_t[size]);
}
-status_t RpcState::rpcSend(
- const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
- const char* what, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+status_t RpcState::rpcSend(const sp<RpcSession::RpcConnection>& connection,
+ const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs,
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
for (int i = 0; i < niovs; i++) {
LOG_RPC_DETAIL("Sending %s (part %d of %d) on RpcTransport %p: %s",
what, i + 1, niovs, connection->rpcTransport.get(),
- android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
+ HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
}
if (status_t status =
@@ -373,10 +381,9 @@ status_t RpcState::rpcSend(
return OK;
}
-status_t RpcState::rpcRec(
- const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
- const char* what, iovec* iovs, int niovs,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+status_t RpcState::rpcRec(const sp<RpcSession::RpcConnection>& connection,
+ const sp<RpcSession>& session, const char* what, iovec* iovs, int niovs,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
if (status_t status =
connection->rpcTransport->interruptableReadFully(session->mShutdownTrigger.get(),
iovs, niovs, std::nullopt,
@@ -391,11 +398,35 @@ status_t RpcState::rpcRec(
for (int i = 0; i < niovs; i++) {
LOG_RPC_DETAIL("Received %s (part %d of %d) on RpcTransport %p: %s",
what, i + 1, niovs, connection->rpcTransport.get(),
- android::base::HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
+ HexString(iovs[i].iov_base, iovs[i].iov_len).c_str());
}
return OK;
}
+bool RpcState::validateProtocolVersion(uint32_t version) {
+ if (version == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
+#if defined(__ANDROID__)
+ char codename[PROPERTY_VALUE_MAX];
+ property_get("ro.build.version.codename", codename, "");
+ if (!strcmp(codename, "REL")) {
+ ALOGE("Cannot use experimental RPC binder protocol in a release configuration.");
+ return false;
+ }
+#else
+ ALOGE("Cannot use experimental RPC binder protocol outside of Android.");
+ return false;
+#endif
+ } else if (version >= RPC_WIRE_PROTOCOL_VERSION_NEXT) {
+ ALOGE("Cannot use RPC binder protocol version %u which is unknown (current protocol "
+ "version "
+ "is %u).",
+ version, RPC_WIRE_PROTOCOL_VERSION);
+ return false;
+ }
+
+ return true;
+}
+
status_t RpcState::readNewSessionResponse(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session, uint32_t* version) {
RpcNewSessionResponse response;
@@ -570,25 +601,24 @@ status_t RpcState::transactAddress(const sp<RpcSession::RpcConnection>& connecti
{const_cast<uint8_t*>(data.data()), data.dataSize()},
objectTableSpan.toIovec(),
};
- if (status_t status = rpcSend(
- connection, session, "transaction", iovs, arraysize(iovs),
- [&] {
- if (waitUs > kWaitLogUs) {
- ALOGE("Cannot send command, trying to process pending refcounts. Waiting "
- "%zuus. Too many oneway calls?",
- waitUs);
- }
+ auto altPoll = [&] {
+ if (waitUs > kWaitLogUs) {
+ ALOGE("Cannot send command, trying to process pending refcounts. Waiting "
+ "%zuus. Too many oneway calls?",
+ waitUs);
+ }
- if (waitUs > 0) {
- usleep(waitUs);
- waitUs = std::min(kWaitMaxUs, waitUs * 2);
- } else {
- waitUs = 1;
- }
+ if (waitUs > 0) {
+ usleep(waitUs);
+ waitUs = std::min(kWaitMaxUs, waitUs * 2);
+ } else {
+ waitUs = 1;
+ }
- return drainCommands(connection, session, CommandType::CONTROL_ONLY);
- },
- rpcFields->mFds.get());
+ return drainCommands(connection, session, CommandType::CONTROL_ONLY);
+ };
+ if (status_t status = rpcSend(connection, session, "transaction", iovs, countof(iovs),
+ std::ref(altPoll), rpcFields->mFds.get());
status != OK) {
// rpcSend calls shutdownAndWait, so all refcounts should be reset. If we ever tolerate
// errors here, then we may need to undo the binder-sent counts for the transaction as
@@ -619,7 +649,7 @@ static void cleanup_reply_data(const uint8_t* data, size_t dataSize, const binde
status_t RpcState::waitForReply(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session, Parcel* reply) {
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> ancillaryFds;
+ std::vector<std::variant<unique_fd, borrowed_fd>> ancillaryFds;
RpcWireHeader command;
while (true) {
iovec iov{&command, sizeof(command)};
@@ -660,7 +690,7 @@ status_t RpcState::waitForReply(const sp<RpcSession::RpcConnection>& connection,
{&rpcReply, rpcReplyWireSize},
{data.data(), data.size()},
};
- if (status_t status = rpcRec(connection, session, "reply body", iovs, arraysize(iovs), nullptr);
+ if (status_t status = rpcRec(connection, session, "reply body", iovs, countof(iovs), nullptr);
status != OK)
return status;
@@ -704,7 +734,7 @@ status_t RpcState::sendDecStrongToTarget(const sp<RpcSession::RpcConnection>& co
};
{
- RpcMutexLockGuard _l(mNodeMutex);
+ RpcMutexUniqueLock _l(mNodeMutex);
if (mTerminated) return DEAD_OBJECT; // avoid fatal only, otherwise races
auto it = mNodeForAddress.find(addr);
LOG_ALWAYS_FATAL_IF(it == mNodeForAddress.end(),
@@ -720,8 +750,9 @@ status_t RpcState::sendDecStrongToTarget(const sp<RpcSession::RpcConnection>& co
body.amount = it->second.timesRecd - target;
it->second.timesRecd = target;
- LOG_ALWAYS_FATAL_IF(nullptr != tryEraseNode(it),
+ LOG_ALWAYS_FATAL_IF(nullptr != tryEraseNode(session, std::move(_l), it),
"Bad state. RpcState shouldn't own received binder");
+ // LOCK ALREADY RELEASED
}
RpcWireHeader cmd = {
@@ -729,14 +760,14 @@ status_t RpcState::sendDecStrongToTarget(const sp<RpcSession::RpcConnection>& co
.bodySize = sizeof(RpcDecStrong),
};
iovec iovs[]{{&cmd, sizeof(cmd)}, {&body, sizeof(body)}};
- return rpcSend(connection, session, "dec ref", iovs, arraysize(iovs), std::nullopt);
+ return rpcSend(connection, session, "dec ref", iovs, countof(iovs), std::nullopt);
}
status_t RpcState::getAndExecuteCommand(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session, CommandType type) {
LOG_RPC_DETAIL("getAndExecuteCommand on RpcTransport %p", connection->rpcTransport.get());
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> ancillaryFds;
+ std::vector<std::variant<unique_fd, borrowed_fd>> ancillaryFds;
RpcWireHeader command;
iovec iov{&command, sizeof(command)};
if (status_t status =
@@ -765,7 +796,7 @@ status_t RpcState::drainCommands(const sp<RpcSession::RpcConnection>& connection
status_t RpcState::processCommand(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
const RpcWireHeader& command, CommandType type,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds) {
+ std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds) {
#ifdef BINDER_WITH_KERNEL_IPC
IPCThreadState* kernelBinderState = IPCThreadState::selfOrNull();
IPCThreadState::SpGuard spGuard{
@@ -778,11 +809,11 @@ status_t RpcState::processCommand(
origGuard = kernelBinderState->pushGetCallingSpGuard(&spGuard);
}
- base::ScopeGuard guardUnguard = [&]() {
+ auto guardUnguard = make_scope_guard([&]() {
if (kernelBinderState != nullptr) {
kernelBinderState->restoreGetCallingSpGuard(origGuard);
}
- };
+ });
#endif // BINDER_WITH_KERNEL_IPC
switch (command.command) {
@@ -805,7 +836,7 @@ status_t RpcState::processCommand(
status_t RpcState::processTransact(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
const RpcWireHeader& command,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds) {
+ std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds) {
LOG_ALWAYS_FATAL_IF(command.command != RPC_COMMAND_TRANSACT, "command: %d", command.command);
CommandData transactionData(command.bodySize);
@@ -832,7 +863,7 @@ static void do_nothing_to_transact_data(const uint8_t* data, size_t dataSize,
status_t RpcState::processTransactInternal(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
CommandData transactionData,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds) {
+ std::vector<std::variant<unique_fd, borrowed_fd>>&& ancillaryFds) {
// for 'recursive' calls to this, we have already read and processed the
// binder from the transaction data and taken reference counts into account,
// so it is cached here.
@@ -925,7 +956,7 @@ processTransactInternalTailCall:
transactionData.size() -
offsetof(RpcWireTransaction, data)};
Span<const uint32_t> objectTableSpan;
- if (session->getProtocolVersion().value() >
+ if (session->getProtocolVersion().value() >=
RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE) {
std::optional<Span<const uint8_t>> objectTableBytes =
parcelSpan.splitOff(transaction->parcelDataSize);
@@ -1036,8 +1067,8 @@ processTransactInternalTailCall:
return DEAD_OBJECT;
}
- if (it->second.asyncTodo.size() == 0) return OK;
- if (it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) {
+ if (it->second.asyncTodo.size() != 0 &&
+ it->second.asyncTodo.top().asyncNumber == it->second.asyncNumber) {
LOG_RPC_DETAIL("Found next async transaction %" PRIu64 " on %" PRIu64,
it->second.asyncNumber, addr);
@@ -1112,7 +1143,7 @@ processTransactInternalTailCall:
{const_cast<uint8_t*>(reply.data()), reply.dataSize()},
objectTableSpan.toIovec(),
};
- return rpcSend(connection, session, "reply", iovs, arraysize(iovs), std::nullopt,
+ return rpcSend(connection, session, "reply", iovs, countof(iovs), std::nullopt,
rpcFields->mFds.get());
}
@@ -1164,8 +1195,8 @@ status_t RpcState::processDecStrong(const sp<RpcSession::RpcConnection>& connect
it->second.timesSent);
it->second.timesSent -= body.amount;
- sp<IBinder> tempHold = tryEraseNode(it);
- _l.unlock();
+ sp<IBinder> tempHold = tryEraseNode(session, std::move(_l), it);
+ // LOCK ALREADY RELEASED
tempHold = nullptr; // destructor may make binder calls on this session
return OK;
@@ -1187,10 +1218,11 @@ status_t RpcState::validateParcel(const sp<RpcSession>& session, const Parcel& p
uint32_t protocolVersion = session->getProtocolVersion().value();
if (protocolVersion < RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE &&
!rpcFields->mObjectPositions.empty()) {
- *errorMsg = StringPrintf("Parcel has attached objects but the session's protocol version "
- "(%" PRIu32 ") is too old, must be at least %" PRIu32,
- protocolVersion,
- RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE);
+ std::stringstream ss;
+ ss << "Parcel has attached objects but the session's protocol version (" << protocolVersion
+ << ") is too old, must be at least "
+ << RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_SIZE;
+ *errorMsg = ss.str();
return BAD_VALUE;
}
@@ -1203,9 +1235,10 @@ status_t RpcState::validateParcel(const sp<RpcSession>& session, const Parcel& p
case RpcSession::FileDescriptorTransportMode::UNIX: {
constexpr size_t kMaxFdsPerMsg = 253;
if (rpcFields->mFds->size() > kMaxFdsPerMsg) {
- *errorMsg = StringPrintf("Too many file descriptors in Parcel for unix "
- "domain socket: %zu (max is %zu)",
- rpcFields->mFds->size(), kMaxFdsPerMsg);
+ std::stringstream ss;
+ ss << "Too many file descriptors in Parcel for unix domain socket: "
+ << rpcFields->mFds->size() << " (max is " << kMaxFdsPerMsg << ")";
+ *errorMsg = ss.str();
return BAD_VALUE;
}
break;
@@ -1216,9 +1249,10 @@ status_t RpcState::validateParcel(const sp<RpcSession>& session, const Parcel& p
// available on Android
constexpr size_t kMaxFdsPerMsg = 8;
if (rpcFields->mFds->size() > kMaxFdsPerMsg) {
- *errorMsg = StringPrintf("Too many file descriptors in Parcel for Trusty "
- "IPC connection: %zu (max is %zu)",
- rpcFields->mFds->size(), kMaxFdsPerMsg);
+ std::stringstream ss;
+ ss << "Too many file descriptors in Parcel for Trusty IPC connection: "
+ << rpcFields->mFds->size() << " (max is " << kMaxFdsPerMsg << ")";
+ *errorMsg = ss.str();
return BAD_VALUE;
}
break;
@@ -1229,7 +1263,10 @@ status_t RpcState::validateParcel(const sp<RpcSession>& session, const Parcel& p
return OK;
}
-sp<IBinder> RpcState::tryEraseNode(std::map<uint64_t, BinderNode>::iterator& it) {
+sp<IBinder> RpcState::tryEraseNode(const sp<RpcSession>& session, RpcMutexUniqueLock nodeLock,
+ std::map<uint64_t, BinderNode>::iterator& it) {
+ bool shouldShutdown = false;
+
sp<IBinder> ref;
if (it->second.timesSent == 0) {
@@ -1239,9 +1276,27 @@ sp<IBinder> RpcState::tryEraseNode(std::map<uint64_t, BinderNode>::iterator& it)
LOG_ALWAYS_FATAL_IF(!it->second.asyncTodo.empty(),
"Can't delete binder w/ pending async transactions");
mNodeForAddress.erase(it);
+
+ if (mNodeForAddress.size() == 0) {
+ shouldShutdown = true;
+ }
}
}
+ // If we shutdown, prevent RpcState from being re-used. This prevents another
+ // thread from getting the root object again.
+ if (shouldShutdown) {
+ clear(std::move(nodeLock));
+ } else {
+ nodeLock.unlock(); // explicit
+ }
+ // LOCK IS RELEASED
+
+ if (shouldShutdown) {
+ ALOGI("RpcState has no binders left, so triggering shutdown...");
+ (void)session->shutdownAndWait(false);
+ }
+
return ref;
}
diff --git a/libs/binder/RpcState.h b/libs/binder/RpcState.h
index ac8658540e..8b84602fee 100644
--- a/libs/binder/RpcState.h
+++ b/libs/binder/RpcState.h
@@ -15,11 +15,12 @@
*/
#pragma once
-#include <android-base/unique_fd.h>
+#include <binder/Functional.h>
#include <binder/IBinder.h>
#include <binder/Parcel.h>
#include <binder/RpcSession.h>
#include <binder/RpcThreads.h>
+#include <binder/unique_fd.h>
#include <map>
#include <optional>
@@ -63,6 +64,8 @@ public:
RpcState();
~RpcState();
+ [[nodiscard]] static bool validateProtocolVersion(uint32_t version);
+
[[nodiscard]] status_t readNewSessionResponse(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session, uint32_t* version);
[[nodiscard]] status_t sendConnectionInit(const sp<RpcSession::RpcConnection>& connection,
@@ -168,6 +171,7 @@ public:
void clear();
private:
+ void clear(RpcMutexUniqueLock nodeLock);
void dumpLocked();
// Alternative to std::vector<uint8_t> that doesn't abort on allocation failure and caps
@@ -187,28 +191,29 @@ private:
[[nodiscard]] status_t rpcSend(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
const char* what, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds =
+ const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds =
nullptr);
- [[nodiscard]] status_t rpcRec(
- const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
- const char* what, iovec* iovs, int niovs,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds = nullptr);
+ [[nodiscard]] status_t rpcRec(const sp<RpcSession::RpcConnection>& connection,
+ const sp<RpcSession>& session, const char* what, iovec* iovs,
+ int niovs,
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
+ ancillaryFds = nullptr);
[[nodiscard]] status_t waitForReply(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session, Parcel* reply);
[[nodiscard]] status_t processCommand(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
const RpcWireHeader& command, CommandType type,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds);
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds);
[[nodiscard]] status_t processTransact(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
const RpcWireHeader& command,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds);
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds);
[[nodiscard]] status_t processTransactInternal(
const sp<RpcSession::RpcConnection>& connection, const sp<RpcSession>& session,
CommandData transactionData,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds);
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds);
[[nodiscard]] status_t processDecStrong(const sp<RpcSession::RpcConnection>& connection,
const sp<RpcSession>& session,
const RpcWireHeader& command);
@@ -250,7 +255,7 @@ private:
struct AsyncTodo {
sp<IBinder> ref;
CommandData data;
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> ancillaryFds;
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>> ancillaryFds;
uint64_t asyncNumber = 0;
bool operator<(const AsyncTodo& o) const {
@@ -268,11 +273,20 @@ private:
std::string toString() const;
};
- // checks if there is any reference left to a node and erases it. If erase
- // happens, and there is a strong reference to the binder kept by
- // binderNode, this returns that strong reference, so that it can be
- // dropped after any locks are removed.
- sp<IBinder> tryEraseNode(std::map<uint64_t, BinderNode>::iterator& it);
+ // Checks if there is any reference left to a node and erases it. If this
+ // is the last node, shuts down the session.
+ //
+ // Node lock is passed here for convenience, so that we can release it
+ // and terminate the session, but we could leave it up to the caller
+ // by returning a continuation if we needed to erase multiple specific
+ // nodes. It may be tempting to allow the client to keep on holding the
+ // lock and instead just return whether or not we should shutdown, but
+ // this introduces the posssibility that another thread calls
+ // getRootBinder and thinks it is valid, rather than immediately getting
+ // an error.
+ sp<IBinder> tryEraseNode(const sp<RpcSession>& session, RpcMutexUniqueLock nodeLock,
+ std::map<uint64_t, BinderNode>::iterator& it);
+
// true - success
// false - session shutdown, halt
[[nodiscard]] bool nodeProgressAsyncNumber(BinderNode* node);
diff --git a/libs/binder/RpcTlsUtils.cpp b/libs/binder/RpcTlsUtils.cpp
index f3ca02a3bd..d5c86d7227 100644
--- a/libs/binder/RpcTlsUtils.cpp
+++ b/libs/binder/RpcTlsUtils.cpp
@@ -21,6 +21,8 @@
#include "Utils.h"
+#include <limits>
+
namespace android {
namespace {
diff --git a/libs/binder/RpcTransportRaw.cpp b/libs/binder/RpcTransportRaw.cpp
index cd067bfee7..aa3a6e506d 100644
--- a/libs/binder/RpcTransportRaw.cpp
+++ b/libs/binder/RpcTransportRaw.cpp
@@ -19,6 +19,7 @@
#include <poll.h>
#include <stddef.h>
+#include <sys/socket.h>
#include <binder/RpcTransportRaw.h>
@@ -29,7 +30,9 @@
namespace android {
-namespace {
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
// RpcTransport with TLS disabled.
class RpcTransportRaw : public RpcTransport {
@@ -56,13 +59,12 @@ public:
status_t interruptableWriteFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
- override {
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
bool sentFds = false;
auto send = [&](iovec* iovs, int niovs) -> ssize_t {
- ssize_t ret =
- sendMessageOnSocket(mSocket, iovs, niovs, sentFds ? nullptr : ancillaryFds);
+ ssize_t ret = binder::os::sendMessageOnSocket(mSocket, iovs, niovs,
+ sentFds ? nullptr : ancillaryFds);
sentFds |= ret > 0;
return ret;
};
@@ -72,16 +74,16 @@ public:
status_t interruptableReadFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
auto recv = [&](iovec* iovs, int niovs) -> ssize_t {
- return receiveMessageFromSocket(mSocket, iovs, niovs, ancillaryFds);
+ return binder::os::receiveMessageFromSocket(mSocket, iovs, niovs, ancillaryFds);
};
return interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, recv, "recvmsg", POLLIN,
altPoll);
}
- virtual bool isWaiting() { return mSocket.isInPollingState(); }
+ bool isWaiting() override { return mSocket.isInPollingState(); }
private:
android::RpcTransportFd mSocket;
@@ -90,14 +92,13 @@ private:
// RpcTransportCtx with TLS disabled.
class RpcTransportCtxRaw : public RpcTransportCtx {
public:
- std::unique_ptr<RpcTransport> newTransport(android::RpcTransportFd socket, FdTrigger*) const {
+ std::unique_ptr<RpcTransport> newTransport(android::RpcTransportFd socket,
+ FdTrigger*) const override {
return std::make_unique<RpcTransportRaw>(std::move(socket));
}
std::vector<uint8_t> getCertificate(RpcCertificateFormat) const override { return {}; }
};
-} // namespace
-
std::unique_ptr<RpcTransportCtx> RpcTransportCtxFactoryRaw::newServerCtx() const {
return std::make_unique<RpcTransportCtxRaw>();
}
diff --git a/libs/binder/RpcTransportTipcAndroid.cpp b/libs/binder/RpcTransportTipcAndroid.cpp
index 8b3ddfbd6f..3819fb6472 100644
--- a/libs/binder/RpcTransportTipcAndroid.cpp
+++ b/libs/binder/RpcTransportTipcAndroid.cpp
@@ -26,13 +26,12 @@
#include "RpcState.h"
#include "RpcTransportUtils.h"
-using android::base::Error;
-using android::base::Result;
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
namespace android {
-namespace {
-
// RpcTransport for writing Trusty IPC clients in Android.
class RpcTransportTipcAndroid : public RpcTransport {
public:
@@ -54,7 +53,7 @@ public:
}
LOG_RPC_DETAIL("RpcTransport poll(): %s", strerror(savedErrno));
- return -savedErrno;
+ return adjustStatus(-savedErrno);
}
if (pfd.revents & POLLNVAL) {
@@ -77,9 +76,8 @@ public:
status_t interruptableWriteFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
- override {
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
auto writeFn = [&](iovec* iovs, size_t niovs) -> ssize_t {
// TODO: send ancillaryFds. For now, we just abort if anyone tries
// to send any.
@@ -87,15 +85,16 @@ public:
"File descriptors are not supported on Trusty yet");
return TEMP_FAILURE_RETRY(tipc_send(mSocket.fd.get(), iovs, niovs, nullptr, 0));
};
- return interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, writeFn, "tipc_send",
- POLLOUT, altPoll);
+
+ status_t status = interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, writeFn,
+ "tipc_send", POLLOUT, altPoll);
+ return adjustStatus(status);
}
status_t interruptableReadFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /*ancillaryFds*/)
- override {
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* /*ancillaryFds*/) override {
auto readFn = [&](iovec* iovs, size_t niovs) -> ssize_t {
// Fill the read buffer at most once per readFn call, then try to
// return as much of it as possible. If the input iovecs are spread
@@ -121,13 +120,26 @@ public:
return processSize;
};
- return interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, readFn, "read", POLLIN,
- altPoll);
+
+ status_t status = interruptableReadOrWrite(mSocket, fdTrigger, iovs, niovs, readFn, "read",
+ POLLIN, altPoll);
+ return adjustStatus(status);
}
bool isWaiting() override { return mSocket.isInPollingState(); }
private:
+ status_t adjustStatus(status_t status) {
+ if (status == -ENOTCONN) {
+ // TIPC returns ENOTCONN on disconnect, but that's basically
+ // the same as DEAD_OBJECT and the latter is the common libbinder
+ // error code for dead connections
+ return DEAD_OBJECT;
+ }
+
+ return status;
+ }
+
status_t fillReadBuffer() {
if (mReadBufferPos < mReadBufferSize) {
return OK;
@@ -167,7 +179,7 @@ private:
continue;
} else {
LOG_RPC_DETAIL("RpcTransport fillBuffer(): %s", strerror(savedErrno));
- return -savedErrno;
+ return adjustStatus(-savedErrno);
}
} else {
mReadBufferSize = static_cast<size_t>(processSize);
@@ -202,8 +214,6 @@ public:
std::vector<uint8_t> getCertificate(RpcCertificateFormat) const override { return {}; }
};
-} // namespace
-
std::unique_ptr<RpcTransportCtx> RpcTransportCtxFactoryTipcAndroid::newServerCtx() const {
return std::make_unique<RpcTransportCtxTipcAndroid>();
}
diff --git a/libs/binder/RpcTransportTls.cpp b/libs/binder/RpcTransportTls.cpp
index 3e98ecca9b..579694c321 100644
--- a/libs/binder/RpcTransportTls.cpp
+++ b/libs/binder/RpcTransportTls.cpp
@@ -18,6 +18,7 @@
#include <log/log.h>
#include <poll.h>
+#include <sys/socket.h>
#include <openssl/bn.h>
#include <openssl/ssl.h>
@@ -29,6 +30,8 @@
#include "RpcState.h"
#include "Utils.h"
+#include <sstream>
+
#define SHOULD_LOG_TLS_DETAIL false
#if SHOULD_LOG_TLS_DETAIL
@@ -38,6 +41,11 @@
#endif
namespace android {
+
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
+
namespace {
// Implement BIO for socket that ignores SIGPIPE.
@@ -51,7 +59,7 @@ int socketFree(BIO* bio) {
return 1;
}
int socketRead(BIO* bio, char* buf, int size) {
- android::base::borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
+ borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
int ret = TEMP_FAILURE_RETRY(::recv(fd.get(), buf, size, MSG_NOSIGNAL));
BIO_clear_retry_flags(bio);
if (errno == EAGAIN || errno == EWOULDBLOCK) {
@@ -61,7 +69,7 @@ int socketRead(BIO* bio, char* buf, int size) {
}
int socketWrite(BIO* bio, const char* buf, int size) {
- android::base::borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
+ borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
int ret = TEMP_FAILURE_RETRY(::send(fd.get(), buf, size, MSG_NOSIGNAL));
BIO_clear_retry_flags(bio);
if (errno == EAGAIN || errno == EWOULDBLOCK) {
@@ -71,13 +79,13 @@ int socketWrite(BIO* bio, const char* buf, int size) {
}
long socketCtrl(BIO* bio, int cmd, long num, void*) { // NOLINT
- android::base::borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
+ borrowed_fd fd(static_cast<int>(reinterpret_cast<intptr_t>(BIO_get_data(bio))));
if (cmd == BIO_CTRL_FLUSH) return 1;
LOG_ALWAYS_FATAL("sockCtrl(fd=%d, %d, %ld)", fd.get(), cmd, num);
return 0;
}
-bssl::UniquePtr<BIO> newSocketBio(android::base::borrowed_fd fd) {
+bssl::UniquePtr<BIO> newSocketBio(borrowed_fd fd) {
static const BIO_METHOD* gMethods = ([] {
auto methods = BIO_meth_new(BIO_get_new_index(), "socket_no_signal");
LOG_ALWAYS_FATAL_IF(0 == BIO_meth_set_write(methods, socketWrite), "BIO_meth_set_write");
@@ -181,10 +189,9 @@ public:
// |sslError| should be from Ssl::getError().
// If |sslError| is WANT_READ / WANT_WRITE, poll for POLLIN / POLLOUT respectively. Otherwise
// return error. Also return error if |fdTrigger| is triggered before or during poll().
- status_t pollForSslError(
- const android::RpcTransportFd& fd, int sslError, FdTrigger* fdTrigger,
- const char* fnString, int additionalEvent,
- const std::optional<android::base::function_ref<status_t()>>& altPoll) {
+ status_t pollForSslError(const android::RpcTransportFd& fd, int sslError, FdTrigger* fdTrigger,
+ const char* fnString, int additionalEvent,
+ const std::optional<SmallFunction<status_t()>>& altPoll) {
switch (sslError) {
case SSL_ERROR_WANT_READ:
return handlePoll(POLLIN | additionalEvent, fd, fdTrigger, fnString, altPoll);
@@ -200,7 +207,7 @@ private:
status_t handlePoll(int event, const android::RpcTransportFd& fd, FdTrigger* fdTrigger,
const char* fnString,
- const std::optional<android::base::function_ref<status_t()>>& altPoll) {
+ const std::optional<SmallFunction<status_t()>>& altPoll) {
status_t ret;
if (altPoll) {
ret = (*altPoll)();
@@ -275,6 +282,8 @@ private:
bssl::UniquePtr<SSL> mSsl;
};
+} // namespace
+
class RpcTransportTls : public RpcTransport {
public:
RpcTransportTls(RpcTransportFd socket, Ssl ssl)
@@ -282,15 +291,14 @@ public:
status_t pollRead(void) override;
status_t interruptableWriteFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
- override;
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override;
status_t interruptableReadFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override;
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override;
- bool isWaiting() { return mSocket.isInPollingState(); };
+ bool isWaiting() override { return mSocket.isInPollingState(); };
private:
android::RpcTransportFd mSocket;
@@ -318,8 +326,8 @@ status_t RpcTransportTls::pollRead(void) {
status_t RpcTransportTls::interruptableWriteFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
(void)ancillaryFds;
MAYBE_WAIT_IN_FLAKE_MODE;
@@ -364,8 +372,8 @@ status_t RpcTransportTls::interruptableWriteFully(
status_t RpcTransportTls::interruptableReadFully(
FdTrigger* fdTrigger, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& altPoll,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) {
+ const std::optional<SmallFunction<status_t()>>& altPoll,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) {
(void)ancillaryFds;
MAYBE_WAIT_IN_FLAKE_MODE;
@@ -411,7 +419,8 @@ status_t RpcTransportTls::interruptableReadFully(
}
// For |ssl|, set internal FD to |fd|, and do handshake. Handshake is triggerable by |fdTrigger|.
-bool setFdAndDoHandshake(Ssl* ssl, const android::RpcTransportFd& socket, FdTrigger* fdTrigger) {
+static bool setFdAndDoHandshake(Ssl* ssl, const android::RpcTransportFd& socket,
+ FdTrigger* fdTrigger) {
bssl::UniquePtr<BIO> bio = newSocketBio(socket.fd);
TEST_AND_RETURN(false, bio != nullptr);
auto [_, errorQueue] = ssl->call(SSL_set_bio, bio.get(), bio.get());
@@ -540,8 +549,6 @@ protected:
}
};
-} // namespace
-
std::unique_ptr<RpcTransportCtx> RpcTransportCtxFactoryTls::newServerCtx() const {
return android::RpcTransportCtxTls::create<RpcTransportCtxTlsServer>(mCertVerifier,
mAuth.get());
diff --git a/libs/binder/RpcTransportUtils.h b/libs/binder/RpcTransportUtils.h
index 32f0db805d..fcf6675588 100644
--- a/libs/binder/RpcTransportUtils.h
+++ b/libs/binder/RpcTransportUtils.h
@@ -15,7 +15,7 @@
*/
#pragma once
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
#include <poll.h>
#include "FdTrigger.h"
@@ -27,7 +27,7 @@ template <typename SendOrReceive>
status_t interruptableReadOrWrite(
const android::RpcTransportFd& socket, FdTrigger* fdTrigger, iovec* iovs, int niovs,
SendOrReceive sendOrReceiveFun, const char* funName, int16_t event,
- const std::optional<android::base::function_ref<status_t()>>& altPoll) {
+ const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll) {
MAYBE_WAIT_IN_FLAKE_MODE;
if (niovs < 0) {
diff --git a/libs/binder/RpcTrusty.cpp b/libs/binder/RpcTrusty.cpp
index 3b53b05991..a445196ca9 100644
--- a/libs/binder/RpcTrusty.cpp
+++ b/libs/binder/RpcTrusty.cpp
@@ -16,15 +16,14 @@
#define LOG_TAG "RpcTrusty"
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
#include <binder/RpcSession.h>
#include <binder/RpcTransportTipcAndroid.h>
+#include <binder/unique_fd.h>
#include <trusty/tipc.h>
namespace android {
-using android::base::unique_fd;
+using android::binder::unique_fd;
sp<RpcSession> RpcTrustyConnectWithSessionInitializer(
const char* device, const char* port,
@@ -35,13 +34,13 @@ sp<RpcSession> RpcTrustyConnectWithSessionInitializer(
auto request = [=] {
int tipcFd = tipc_connect(device, port);
if (tipcFd < 0) {
- LOG(ERROR) << "Failed to connect to Trusty service. Error code: " << tipcFd;
+ ALOGE("Failed to connect to Trusty service. Error code: %d", tipcFd);
return unique_fd();
}
return unique_fd(tipcFd);
};
if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
- LOG(ERROR) << "Failed to set up Trusty client. Error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up Trusty client. Error: %s", statusToString(status).c_str());
return nullptr;
}
return session;
diff --git a/libs/binder/ServiceManagerHost.cpp b/libs/binder/ServiceManagerHost.cpp
index 194254ac69..9482e3e118 100644
--- a/libs/binder/ServiceManagerHost.cpp
+++ b/libs/binder/ServiceManagerHost.cpp
@@ -56,16 +56,16 @@ public:
[[nodiscard]] const std::optional<unsigned int>& hostPort() const { return mPort; }
private:
- DISALLOW_COPY_AND_ASSIGN(AdbForwarder);
+ AdbForwarder(const AdbForwarder&) = delete;
+ void operator=(const AdbForwarder&) = delete;
explicit AdbForwarder(unsigned int port) : mPort(port) {}
std::optional<unsigned int> mPort;
};
std::optional<AdbForwarder> AdbForwarder::forward(unsigned int devicePort) {
auto result =
execute({"adb", "forward", "tcp:0", "tcp:" + std::to_string(devicePort)}, nullptr);
- if (!result.ok()) {
- ALOGE("Unable to run `adb forward tcp:0 tcp:%d`: %s", devicePort,
- result.error().message().c_str());
+ if (!result.has_value()) {
+ ALOGE("Unable to run `adb forward tcp:0 tcp:%d`", devicePort);
return std::nullopt;
}
// Must end with exit code 0 (`has_value() && value() == 0`)
@@ -94,9 +94,8 @@ AdbForwarder::~AdbForwarder() {
if (!mPort.has_value()) return;
auto result = execute({"adb", "forward", "--remove", "tcp:" + std::to_string(*mPort)}, nullptr);
- if (!result.ok()) {
- ALOGE("Unable to run `adb forward --remove tcp:%d`: %s", *mPort,
- result.error().message().c_str());
+ if (!result.has_value()) {
+ ALOGE("Unable to run `adb forward --remove tcp:%d`", *mPort);
return;
}
// Must end with exit code 0 (`has_value() && value() == 0`)
@@ -130,8 +129,7 @@ sp<IBinder> getDeviceService(std::vector<std::string>&& serviceDispatcherArgs,
serviceDispatcherArgs.insert(serviceDispatcherArgs.begin(), prefix.begin(), prefix.end());
auto result = execute(std::move(serviceDispatcherArgs), &CommandResult::stdoutEndsWithNewLine);
- if (!result.ok()) {
- ALOGE("%s", result.error().message().c_str());
+ if (!result.has_value()) {
return nullptr;
}
@@ -159,8 +157,8 @@ sp<IBinder> getDeviceService(std::vector<std::string>&& serviceDispatcherArgs,
LOG_ALWAYS_FATAL_IF(!forwardResult->hostPort().has_value());
auto rpcSession = RpcSession::make();
- if (options.maxOutgoingThreads.has_value()) {
- rpcSession->setMaxOutgoingThreads(*options.maxOutgoingThreads);
+ if (options.maxOutgoingConnections.has_value()) {
+ rpcSession->setMaxOutgoingConnections(*options.maxOutgoingConnections);
}
if (status_t status = rpcSession->setupInetClient("127.0.0.1", *forwardResult->hostPort());
diff --git a/libs/binder/ServiceManagerHost.h b/libs/binder/ServiceManagerHost.h
index c5310dac20..941ba3a757 100644
--- a/libs/binder/ServiceManagerHost.h
+++ b/libs/binder/ServiceManagerHost.h
@@ -16,7 +16,6 @@
#pragma once
-#include <android-base/macros.h>
#include <android/os/IServiceManager.h>
namespace android {
diff --git a/libs/binder/Stability.cpp b/libs/binder/Stability.cpp
index 2d05fb2505..665dfea456 100644
--- a/libs/binder/Stability.cpp
+++ b/libs/binder/Stability.cpp
@@ -73,9 +73,17 @@ void Stability::tryMarkCompilationUnit(IBinder* binder) {
(void)setRepr(binder, getLocalLevel(), REPR_NONE);
}
+// after deprecation of the VNDK, these should be aliases. At some point
+// all references to __ANDROID_VNDK__ should be replaced by __ANDROID_VENDOR__
+// but for right now, check that this condition holds because some
+// places check __ANDROID_VNDK__ and some places check __ANDROID_VENDOR__
+#if defined(__ANDROID_VNDK__) != defined(__ANDROID_VENDOR__)
+#error "__ANDROID_VNDK__ and __ANDROID_VENDOR__ should be aliases"
+#endif
+
Stability::Level Stability::getLocalLevel() {
#ifdef __ANDROID_APEX__
-#error APEX can't use libbinder (must use libbinder_ndk)
+#error "APEX can't use libbinder (must use libbinder_ndk)"
#endif
#ifdef __ANDROID_VNDK__
diff --git a/libs/binder/TEST_MAPPING b/libs/binder/TEST_MAPPING
index 1488400007..2b3ff4407a 100644
--- a/libs/binder/TEST_MAPPING
+++ b/libs/binder/TEST_MAPPING
@@ -16,9 +16,15 @@
"name": "binderDriverInterfaceTest"
},
{
+ "name": "binderRecordReplayTest"
+ },
+ {
"name": "binderHostDeviceTest"
},
{
+ "name": "binderParcelBenchmark"
+ },
+ {
"name": "binderTextOutputTest"
},
{
@@ -58,6 +64,9 @@
"name": "libbinderthreadstateutils_test"
},
{
+ "name": "fuzz_service_test"
+ },
+ {
"name": "CtsOsTestCases",
"options": [
{
@@ -79,6 +88,12 @@
},
{
"name": "rustBinderSerializationTest"
+ },
+ {
+ "name": "libbinder_ndk_bindgen_test"
+ },
+ {
+ "name": "libbinder_rpc_unstable_bindgen_test"
}
],
"presubmit-large": [
@@ -86,12 +101,6 @@
"name": "binderRpcTest"
},
{
- "name": "CtsRootRollbackManagerHostTestCases"
- },
- {
- "name": "StagedRollbackTest"
- },
- {
"name": "binderRpcTestNoKernel"
},
{
@@ -119,5 +128,10 @@
{
"name": "memunreachable_binder_test"
}
+ ],
+ "imports": [
+ {
+ "path": "packages/modules/Virtualization"
+ }
]
}
diff --git a/libs/binder/Utils.cpp b/libs/binder/Utils.cpp
index 0314b0fea7..d9a96af80a 100644
--- a/libs/binder/Utils.cpp
+++ b/libs/binder/Utils.cpp
@@ -24,4 +24,22 @@ void zeroMemory(uint8_t* data, size_t size) {
memset(data, 0, size);
}
+std::string HexString(const void* bytes, size_t len) {
+ LOG_ALWAYS_FATAL_IF(len > 0 && bytes == nullptr, "%p %zu", bytes, len);
+
+ // b/132916539: Doing this the 'C way', std::setfill triggers ubsan implicit conversion
+ const uint8_t* bytes8 = static_cast<const uint8_t*>(bytes);
+ const char chars[] = "0123456789abcdef";
+ std::string result;
+ result.resize(len * 2);
+
+ for (size_t i = 0; i < len; i++) {
+ const auto c = bytes8[i];
+ result[2 * i] = chars[c >> 4];
+ result[2 * i + 1] = chars[c & 0xf];
+ }
+
+ return result;
+}
+
} // namespace android
diff --git a/libs/binder/Utils.h b/libs/binder/Utils.h
index e04199c75a..eec09eb859 100644
--- a/libs/binder/Utils.h
+++ b/libs/binder/Utils.h
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#pragma once
+
#include <stddef.h>
#include <sys/uio.h>
#include <cstdint>
@@ -22,6 +24,30 @@
#include <log/log.h>
#include <utils/Errors.h>
+#define PLOGE(fmt, ...) \
+ do { \
+ auto savedErrno = errno; \
+ ALOGE(fmt ": %s" __VA_OPT__(, ) __VA_ARGS__, strerror(savedErrno)); \
+ } while (0)
+#define PLOGF(fmt, ...) \
+ do { \
+ auto savedErrno = errno; \
+ LOG_ALWAYS_FATAL(fmt ": %s" __VA_OPT__(, ) __VA_ARGS__, strerror(savedErrno)); \
+ } while (0)
+
+/* TEMP_FAILURE_RETRY is not available on macOS and Trusty. */
+#ifndef TEMP_FAILURE_RETRY
+/* Used to retry syscalls that can return EINTR. */
+#define TEMP_FAILURE_RETRY(exp) \
+ ({ \
+ __typeof__(exp) _rc; \
+ do { \
+ _rc = (exp); \
+ } while (_rc == -1 && errno == EINTR); \
+ _rc; \
+ })
+#endif
+
#define TEST_AND_RETURN(value, expr) \
do { \
if (!(expr)) { \
@@ -32,6 +58,17 @@
namespace android {
+/**
+ * Get the size of a statically initialized array.
+ *
+ * \param N the array to get the size of.
+ * \return the size of the array.
+ */
+template <typename T, size_t N>
+constexpr size_t countof(T (&)[N]) {
+ return N;
+}
+
// avoid optimizations
void zeroMemory(uint8_t* data, size_t size);
@@ -70,4 +107,10 @@ struct Span {
}
};
+// Converts binary data into a hexString.
+//
+// Hex values are printed in order, e.g. 0xDEAD will result in 'adde' because
+// Android is little-endian.
+std::string HexString(const void* bytes, size_t len);
+
} // namespace android
diff --git a/libs/binder/UtilsHost.cpp b/libs/binder/UtilsHost.cpp
index 52b8f69d36..ae1a6c44d7 100644
--- a/libs/binder/UtilsHost.cpp
+++ b/libs/binder/UtilsHost.cpp
@@ -25,8 +25,13 @@
#include <log/log.h>
+#include "FdUtils.h"
+#include "Utils.h"
+
namespace android {
+using android::binder::unique_fd;
+
CommandResult::~CommandResult() {
if (!pid.has_value()) return;
if (*pid == 0) {
@@ -72,8 +77,8 @@ std::string CommandResult::toString() const {
return ss.str();
}
-android::base::Result<CommandResult> execute(std::vector<std::string> argStringVec,
- const std::function<bool(const CommandResult&)>& end) {
+std::optional<CommandResult> execute(std::vector<std::string> argStringVec,
+ const std::function<bool(const CommandResult&)>& end) {
// turn vector<string> into null-terminated char* vector.
std::vector<char*> argv;
argv.reserve(argStringVec.size() + 1);
@@ -81,15 +86,22 @@ android::base::Result<CommandResult> execute(std::vector<std::string> argStringV
argv.push_back(nullptr);
CommandResult ret;
- android::base::unique_fd outWrite;
- if (!android::base::Pipe(&ret.outPipe, &outWrite))
- return android::base::ErrnoError() << "pipe() for outPipe";
- android::base::unique_fd errWrite;
- if (!android::base::Pipe(&ret.errPipe, &errWrite))
- return android::base::ErrnoError() << "pipe() for errPipe";
+ unique_fd outWrite;
+ if (!binder::Pipe(&ret.outPipe, &outWrite)) {
+ PLOGE("pipe() for outPipe");
+ return {};
+ }
+ unique_fd errWrite;
+ if (!binder::Pipe(&ret.errPipe, &errWrite)) {
+ PLOGE("pipe() for errPipe");
+ return {};
+ }
int pid = fork();
- if (pid == -1) return android::base::ErrnoError() << "fork()";
+ if (pid == -1) {
+ PLOGE("fork()");
+ return {};
+ }
if (pid == 0) {
// child
ret.outPipe.reset();
@@ -111,7 +123,7 @@ android::base::Result<CommandResult> execute(std::vector<std::string> argStringV
errWrite.reset();
ret.pid = pid;
- auto handlePoll = [](android::base::unique_fd* fd, const pollfd* pfd, std::string* s) {
+ auto handlePoll = [](unique_fd* fd, const pollfd* pfd, std::string* s) {
if (!fd->ok()) return true;
if (pfd->revents & POLLIN) {
char buf[1024];
@@ -140,12 +152,19 @@ android::base::Result<CommandResult> execute(std::vector<std::string> argStringV
*errPollFd = {.fd = ret.errPipe.get(), .events = POLLIN};
}
int pollRet = poll(fds, nfds, 1000 /* ms timeout */);
- if (pollRet == -1) return android::base::ErrnoError() << "poll()";
+ if (pollRet == -1) {
+ PLOGE("poll()");
+ return {};
+ }
- if (!handlePoll(&ret.outPipe, outPollFd, &ret.stdoutStr))
- return android::base::ErrnoError() << "read(stdout)";
- if (!handlePoll(&ret.errPipe, errPollFd, &ret.stderrStr))
- return android::base::ErrnoError() << "read(stderr)";
+ if (!handlePoll(&ret.outPipe, outPollFd, &ret.stdoutStr)) {
+ PLOGE("read(stdout)");
+ return {};
+ }
+ if (!handlePoll(&ret.errPipe, errPollFd, &ret.stderrStr)) {
+ PLOGE("read(stderr)");
+ return {};
+ }
if (end && end(ret)) return ret;
}
@@ -154,7 +173,10 @@ android::base::Result<CommandResult> execute(std::vector<std::string> argStringV
while (ret.pid.has_value()) {
int status;
auto exitPid = waitpid(pid, &status, 0);
- if (exitPid == -1) return android::base::ErrnoError() << "waitpid(" << pid << ")";
+ if (exitPid == -1) {
+ PLOGE("waitpid(%d)", pid);
+ return {};
+ }
if (exitPid == pid) {
if (WIFEXITED(status)) {
ret.pid = std::nullopt;
diff --git a/libs/binder/UtilsHost.h b/libs/binder/UtilsHost.h
index 98ac4e0c48..d6fe9facde 100644
--- a/libs/binder/UtilsHost.h
+++ b/libs/binder/UtilsHost.h
@@ -16,6 +16,7 @@
#pragma once
+#include <functional>
#include <optional>
#include <ostream>
#include <string>
@@ -23,8 +24,8 @@
#include <vector>
#include <android-base/macros.h>
-#include <android-base/result.h>
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
+#include <utils/Errors.h>
/**
* Log a lot more information about host-device binder communication, when debugging issues.
@@ -46,8 +47,8 @@ struct CommandResult {
std::string stdoutStr;
std::string stderrStr;
- android::base::unique_fd outPipe;
- android::base::unique_fd errPipe;
+ binder::unique_fd outPipe;
+ binder::unique_fd errPipe;
CommandResult() = default;
CommandResult(CommandResult&& other) noexcept { (*this) = std::move(other); }
@@ -67,7 +68,8 @@ struct CommandResult {
}
private:
- DISALLOW_COPY_AND_ASSIGN(CommandResult);
+ CommandResult(const CommandResult&) = delete;
+ void operator=(const CommandResult&) = delete;
};
std::ostream& operator<<(std::ostream& os, const CommandResult& res);
@@ -94,6 +96,6 @@ std::ostream& operator<<(std::ostream& os, const CommandResult& res);
//
// If the parent process has encountered any errors for system calls, return ExecuteError with
// the proper errno set.
-android::base::Result<CommandResult> execute(std::vector<std::string> argStringVec,
- const std::function<bool(const CommandResult&)>& end);
+std::optional<CommandResult> execute(std::vector<std::string> argStringVec,
+ const std::function<bool(const CommandResult&)>& end);
} // namespace android
diff --git a/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl b/libs/binder/aidl/android/content/pm/IPackageManagerNative.aidl
index 7c99f76ec6..3ddfefa311 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;
@@ -44,6 +43,18 @@ interface IPackageManagerNative {
@utf8InCpp String[] getNamesForUids(in int[] uids);
/**
+ * Return the UID associated with the given package name.
+ * Note that the same package will have different UIDs under different UserHandle on
+ * the same device.
+ * @param packageName The full name (i.e. com.google.apps.contacts) of the desired package.
+ * @param flags Additional option flags to modify the data returned.
+ * @param userId The user handle identifier to look up the package under.
+ * @return Returns an integer UID who owns the given package name, or -1 if no such package is
+ * available to the caller.
+ */
+ int getPackageUid(in @utf8InCpp String packageName, in long flags, in int userId);
+
+ /**
* Returns the name of the installer (a package) which installed the named
* package. Preloaded packages return the string "preload". Sideloaded packages
* return an empty string. Unknown or unknowable are returned as empty strings.
@@ -92,18 +103,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/binder_module.h b/libs/binder/binder_module.h
index eef07aef2d..b3a2d9ec28 100644
--- a/libs/binder/binder_module.h
+++ b/libs/binder/binder_module.h
@@ -32,77 +32,4 @@
#include <linux/android/binder.h>
#include <sys/ioctl.h>
-#ifndef BR_FROZEN_REPLY
-// Temporary definition of BR_FROZEN_REPLY. For production
-// this will come from UAPI binder.h
-#define BR_FROZEN_REPLY _IO('r', 18)
-#endif // BR_FROZEN_REPLY
-
-#ifndef BINDER_FREEZE
-/*
- * Temporary definitions for freeze support. For the final version
- * these will be defined in the UAPI binder.h file from upstream kernel.
- */
-#define BINDER_FREEZE _IOW('b', 14, struct binder_freeze_info)
-
-struct binder_freeze_info {
- //
- // Group-leader PID of process to be frozen
- //
- uint32_t pid;
- //
- // Enable(1) / Disable(0) freeze for given PID
- //
- uint32_t enable;
- //
- // Timeout to wait for transactions to drain.
- // 0: don't wait (ioctl will return EAGAIN if not drained)
- // N: number of ms to wait
- uint32_t timeout_ms;
-};
-#endif // BINDER_FREEZE
-
-#ifndef BINDER_GET_FROZEN_INFO
-
-#define BINDER_GET_FROZEN_INFO _IOWR('b', 15, struct binder_frozen_status_info)
-
-struct binder_frozen_status_info {
- //
- // Group-leader PID of process to be queried
- //
- __u32 pid;
- //
- // Indicates whether the process has received any sync calls since last
- // freeze (cleared at freeze/unfreeze)
- // bit 0: received sync transaction after being frozen
- // bit 1: new pending sync transaction during freezing
- //
- __u32 sync_recv;
- //
- // Indicates whether the process has received any async calls since last
- // freeze (cleared at freeze/unfreeze)
- //
- __u32 async_recv;
-};
-#endif // BINDER_GET_FROZEN_INFO
-
-#ifndef BR_ONEWAY_SPAM_SUSPECT
-// Temporary definition of BR_ONEWAY_SPAM_SUSPECT. For production
-// this will come from UAPI binder.h
-#define BR_ONEWAY_SPAM_SUSPECT _IO('r', 19)
-#endif // BR_ONEWAY_SPAM_SUSPECT
-
-#ifndef BINDER_ENABLE_ONEWAY_SPAM_DETECTION
-/*
- * Temporary definitions for oneway spam detection support. For the final version
- * these will be defined in the UAPI binder.h file from upstream kernel.
- */
-#define BINDER_ENABLE_ONEWAY_SPAM_DETECTION _IOW('b', 16, __u32)
-#endif // BINDER_ENABLE_ONEWAY_SPAM_DETECTION
-
-#ifndef BR_TRANSACTION_PENDING_FROZEN
-// Temporary definition of BR_TRANSACTION_PENDING_FROZEN until UAPI binder.h includes it.
-#define BR_TRANSACTION_PENDING_FROZEN _IO('r', 20)
-#endif // BR_TRANSACTION_PENDING_FROZEN
-
#endif // _BINDER_MODULE_H_
diff --git a/libs/binder/file.cpp b/libs/binder/file.cpp
new file mode 100644
index 0000000000..6e450b90b7
--- /dev/null
+++ b/libs/binder/file.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "file.h"
+
+#ifdef BINDER_NO_LIBBASE
+
+#include "Utils.h"
+
+#include <stdint.h>
+
+// clang-format off
+
+namespace android::binder {
+
+bool ReadFully(borrowed_fd fd, void* data, size_t byte_count) {
+ uint8_t* p = reinterpret_cast<uint8_t*>(data);
+ size_t remaining = byte_count;
+ while (remaining > 0) {
+ ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), p, remaining));
+ if (n == 0) { // EOF
+ errno = ENODATA;
+ return false;
+ }
+ if (n == -1) return false;
+ p += n;
+ remaining -= n;
+ }
+ return true;
+}
+
+bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count) {
+ const uint8_t* p = reinterpret_cast<const uint8_t*>(data);
+ size_t remaining = byte_count;
+ while (remaining > 0) {
+ ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, remaining));
+ if (n == -1) return false;
+ p += n;
+ remaining -= n;
+ }
+ return true;
+}
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/file.h b/libs/binder/file.h
new file mode 100644
index 0000000000..bcbfa31836
--- /dev/null
+++ b/libs/binder/file.h
@@ -0,0 +1,41 @@
+/*
+ * 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
+
+#ifndef BINDER_NO_LIBBASE
+
+#include <android-base/file.h>
+
+namespace android::binder {
+using android::base::ReadFully;
+using android::base::WriteFully;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <binder/unique_fd.h>
+
+#include <string_view>
+
+namespace android::binder {
+
+bool ReadFully(borrowed_fd fd, void* data, size_t byte_count);
+bool WriteFully(borrowed_fd fd, const void* data, size_t byte_count);
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/include/binder/Binder.h b/libs/binder/include/binder/Binder.h
index d960a0b894..7a65ff4e45 100644
--- a/libs/binder/include/binder/Binder.h
+++ b/libs/binder/include/binder/Binder.h
@@ -102,15 +102,9 @@ public:
// to another process.
void setParceled();
- [[nodiscard]] status_t setRpcClientDebug(android::base::unique_fd clientFd,
+ [[nodiscard]] status_t setRpcClientDebug(binder::unique_fd clientFd,
const sp<IBinder>& keepAliveBinder);
- // Start recording transactions to the unique_fd in data.
- // See RecordedTransaction.h for more details.
- [[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
- // Stop the current recording.
- [[nodiscard]] status_t stopRecordingTransactions();
-
protected:
virtual ~BBinder();
@@ -131,6 +125,8 @@ private:
[[nodiscard]] status_t setRpcClientDebug(const Parcel& data);
void removeRpcServerLink(const sp<RpcServerLink>& link);
+ [[nodiscard]] status_t startRecordingTransactions(const Parcel& data);
+ [[nodiscard]] status_t stopRecordingTransactions();
std::atomic<Extras*> mExtras;
diff --git a/libs/binder/include/binder/BpBinder.h b/libs/binder/include/binder/BpBinder.h
index 5496d61b70..9f0390769b 100644
--- a/libs/binder/include/binder/BpBinder.h
+++ b/libs/binder/include/binder/BpBinder.h
@@ -16,11 +16,12 @@
#pragma once
-#include <android-base/unique_fd.h>
#include <binder/IBinder.h>
-#include <utils/Mutex.h>
+#include <binder/RpcThreads.h>
+#include <binder/unique_fd.h>
#include <map>
+#include <optional>
#include <unordered_map>
#include <variant>
@@ -34,7 +35,8 @@ class Stability;
}
class ProcessState;
-using binder_proxy_limit_callback = void(*)(int);
+using binder_proxy_limit_callback = std::function<void(int)>;
+using binder_proxy_warning_callback = std::function<void(int)>;
class BpBinder : public IBinder
{
@@ -85,14 +87,16 @@ public:
static void enableCountByUid();
static void disableCountByUid();
static void setCountByUidEnabled(bool enable);
- static void setLimitCallback(binder_proxy_limit_callback cb);
- static void setBinderProxyCountWatermarks(int high, int low);
+ static void setBinderProxyCountEventCallback(binder_proxy_limit_callback cbl,
+ binder_proxy_warning_callback cbw);
+ static void setBinderProxyCountWatermarks(int high, int low, int warning);
+ static uint32_t getBinderProxyCount();
std::optional<int32_t> getDebugBinderHandle() const;
// Start recording transactions to the unique_fd.
// See RecordedTransaction.h for more details.
- status_t startRecordingBinder(const android::base::unique_fd& fd);
+ status_t startRecordingBinder(const binder::unique_fd& fd);
// Stop the current recording.
status_t stopRecordingBinder();
@@ -191,7 +195,7 @@ private:
void reportOneDeath(const Obituary& obit);
bool isDescriptorCached() const;
- mutable Mutex mLock;
+ mutable RpcMutex mLock;
volatile int32_t mAlive;
volatile int32_t mObitsSent;
Vector<Obituary>* mObituaries;
@@ -199,7 +203,7 @@ private:
mutable String16 mDescriptorCache;
int32_t mTrackedUid;
- static Mutex sTrackingLock;
+ static RpcMutex sTrackingLock;
static std::unordered_map<int32_t,uint32_t> sTrackingMap;
static int sNumTrackedUids;
static std::atomic_bool sCountByUidEnabled;
@@ -208,6 +212,10 @@ private:
static uint32_t sBinderProxyCountLowWatermark;
static bool sBinderProxyThrottleCreate;
static std::unordered_map<int32_t,uint32_t> sLastLimitCallbackMap;
+ static std::atomic<uint32_t> sBinderProxyCount;
+ static std::atomic<uint32_t> sBinderProxyCountWarned;
+ static binder_proxy_warning_callback sWarningCallback;
+ static uint32_t sBinderProxyCountWarningWatermark;
};
} // namespace android
diff --git a/libs/binder/include/binder/Delegate.h b/libs/binder/include/binder/Delegate.h
index 8b3fc1cc10..ad5a6a3e88 100644
--- a/libs/binder/include/binder/Delegate.h
+++ b/libs/binder/include/binder/Delegate.h
@@ -18,6 +18,11 @@
#include <binder/IBinder.h>
+#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
+#include <log/log.h>
+#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
+#endif
+
#ifndef __BIONIC__
#ifndef __assert
diff --git a/libs/binder/include/binder/Functional.h b/libs/binder/include/binder/Functional.h
new file mode 100644
index 0000000000..08e3b214da
--- /dev/null
+++ b/libs/binder/include/binder/Functional.h
@@ -0,0 +1,50 @@
+/*
+ * 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 <functional>
+#include <memory>
+
+namespace android::binder::impl {
+
+template <typename F>
+constexpr void assert_small_callable() {
+ // While this buffer (std::function::__func::__buf_) is an implementation detail generally not
+ // accessible to users, it's a good bet to assume its size to be around 3 pointers.
+ constexpr size_t kFunctionBufferSize = 3 * sizeof(void*);
+
+ static_assert(sizeof(F) <= kFunctionBufferSize,
+ "Supplied callable is larger than std::function optimization buffer. "
+ "Try using std::ref, but make sure lambda lives long enough to be called.");
+}
+
+template <typename F>
+std::unique_ptr<void, std::function<void(void*)>> make_scope_guard(F&& f) {
+ assert_small_callable<decltype(std::bind(f))>();
+ return {reinterpret_cast<void*>(true), std::bind(f)};
+}
+
+template <typename T>
+class SmallFunction : public std::function<T> {
+public:
+ template <typename F>
+ SmallFunction(F&& f) : std::function<T>(f) {
+ assert_small_callable<F>();
+ }
+};
+
+} // namespace android::binder::impl
diff --git a/libs/binder/include/binder/IBinder.h b/libs/binder/include/binder/IBinder.h
index e75d548bd8..dad9a1782d 100644
--- a/libs/binder/include/binder/IBinder.h
+++ b/libs/binder/include/binder/IBinder.h
@@ -16,7 +16,7 @@
#pragma once
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <utils/String16.h>
@@ -175,7 +175,7 @@ public:
*
* On death of @a keepAliveBinder, the RpcServer shuts down.
*/
- [[nodiscard]] status_t setRpcClientDebug(android::base::unique_fd socketFd,
+ [[nodiscard]] status_t setRpcClientDebug(binder::unique_fd socketFd,
const sp<IBinder>& keepAliveBinder);
// NOLINTNEXTLINE(google-default-arguments)
diff --git a/libs/binder/include/binder/IInterface.h b/libs/binder/include/binder/IInterface.h
index 9f7e2c87b8..ac845bc003 100644
--- a/libs/binder/include/binder/IInterface.h
+++ b/libs/binder/include/binder/IInterface.h
@@ -119,8 +119,8 @@ public:
"The preferred way to add interfaces is to define " \
"an .aidl file to auto-generate the interface. If " \
"an interface must be manually written, add its " \
- "name to the whitelist."); \
- DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME) \
+ "name to the allowlist."); \
+ DO_NOT_DIRECTLY_USE_ME_IMPLEMENT_META_INTERFACE(INTERFACE, NAME)
#else
@@ -219,80 +219,79 @@ inline IBinder* BpInterface<INTERFACE>::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[] = {
@@ -306,10 +305,10 @@ constexpr bool equals(const char* a, const char* b) {
return equals(a + 1, b + 1);
}
-constexpr bool inList(const char* a, const char* const* whitelist) {
- if (*whitelist == nullptr) return false;
- if (equals(a, *whitelist)) return true;
- return inList(a, whitelist + 1);
+constexpr bool inList(const char* a, const char* const* allowlist) {
+ if (*allowlist == nullptr) return false;
+ if (equals(a, *allowlist)) return true;
+ return inList(a, allowlist + 1);
}
constexpr bool allowedManualInterface(const char* name) {
diff --git a/libs/binder/include/binder/IPCThreadState.h b/libs/binder/include/binder/IPCThreadState.h
index d261c2143c..dc5b1a1712 100644
--- a/libs/binder/include/binder/IPCThreadState.h
+++ b/libs/binder/include/binder/IPCThreadState.h
@@ -62,8 +62,13 @@ public:
/**
* Returns the PID of the process which has made the current binder
- * call. If not in a binder call, this will return getpid. If the
- * call is oneway, this will return 0.
+ * call. If not in a binder call, this will return getpid.
+ *
+ * Warning: oneway transactions do not receive PID. Even if you expect
+ * a transaction to be synchronous, a misbehaving client could send it
+ * as an asynchronous call and result in a 0 PID here. Additionally, if
+ * there is a race and the calling process dies, the PID may still be
+ * 0 for a synchronous call.
*/
[[nodiscard]] pid_t getCallingPid() const;
@@ -147,7 +152,12 @@ public:
void flushCommands();
bool flushIfNeeded();
- // For main functions - dangerous for libraries to use
+ // Adds the current thread into the binder threadpool.
+ //
+ // This is in addition to any threads which are started
+ // with startThreadPool. Libraries should not call this
+ // function, as they may be loaded into processes which
+ // try to configure the threadpool differently.
void joinThreadPool(bool isMain = true);
// Stop the local process.
diff --git a/libs/binder/include/binder/IServiceManager.h b/libs/binder/include/binder/IServiceManager.h
index c78f870153..486bdfba78 100644
--- a/libs/binder/include/binder/IServiceManager.h
+++ b/libs/binder/include/binder/IServiceManager.h
@@ -207,6 +207,8 @@ status_t getService(const String16& name, sp<INTERFACE>* outService)
return NAME_NOT_FOUND;
}
+void* openDeclaredPassthroughHal(const String16& interface, const String16& instance, int flag);
+
bool checkCallingPermission(const String16& permission);
bool checkCallingPermission(const String16& permission,
int32_t* outPid, int32_t* outUid);
@@ -224,12 +226,12 @@ bool checkPermission(const String16& permission, pid_t pid, uid_t uid,
// }
// Resources are cleaned up when the object is destroyed.
//
-// For each returned binder object, at most |maxOutgoingThreads| outgoing threads are instantiated.
-// Hence, only |maxOutgoingThreads| calls can be made simultaneously. Additional calls are blocked
-// if there are |maxOutgoingThreads| ongoing calls. See RpcSession::setMaxOutgoingThreads.
-// If |maxOutgoingThreads| is not set, default is |RpcSession::kDefaultMaxOutgoingThreads|.
+// For each returned binder object, at most |maxOutgoingConnections| outgoing connections are
+// instantiated, depending on how many the service on the device is configured with.
+// Hence, only |maxOutgoingConnections| calls can be made simultaneously.
+// See also RpcSession::setMaxOutgoingConnections.
struct RpcDelegateServiceManagerOptions {
- std::optional<size_t> maxOutgoingThreads;
+ std::optional<size_t> maxOutgoingConnections;
};
sp<IServiceManager> createRpcDelegateServiceManager(
const RpcDelegateServiceManagerOptions& options);
diff --git a/libs/binder/include/binder/LazyServiceRegistrar.h b/libs/binder/include/binder/LazyServiceRegistrar.h
index 2e22b84ff0..bda3d19ee1 100644
--- a/libs/binder/include/binder/LazyServiceRegistrar.h
+++ b/libs/binder/include/binder/LazyServiceRegistrar.h
@@ -93,7 +93,17 @@ class LazyServiceRegistrar {
*/
void reRegister();
- private:
+ /**
+ * Create a second instance of lazy service registrar.
+ *
+ * WARNING: dangerous! DO NOT USE THIS - LazyServiceRegistrar
+ * should be single-instanced, so that the service will only
+ * shut down when all services are unused. A separate instance
+ * is only used to test race conditions.
+ */
+ static LazyServiceRegistrar createExtraTestInstance();
+
+ private:
std::shared_ptr<internal::ClientCounterCallback> mClientCC;
LazyServiceRegistrar();
};
diff --git a/libs/binder/include/binder/Parcel.h b/libs/binder/include/binder/Parcel.h
index 162cd406dc..5e18b9197d 100644
--- a/libs/binder/include/binder/Parcel.h
+++ b/libs/binder/include/binder/Parcel.h
@@ -17,30 +17,28 @@
#pragma once
#include <array>
+#include <limits>
#include <map> // for legacy reasons
+#include <optional>
#include <string>
#include <type_traits>
#include <variant>
#include <vector>
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
#include <cutils/native_handle.h>
+#endif
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <utils/String16.h>
#include <utils/Vector.h>
-#include <utils/Flattenable.h>
#include <binder/IInterface.h>
#include <binder/Parcelable.h>
-#ifdef BINDER_IPC_32BIT
-//NOLINTNEXTLINE(google-runtime-int) b/173188702
-typedef unsigned int binder_size_t;
-#else
//NOLINTNEXTLINE(google-runtime-int) b/173188702
typedef unsigned long long binder_size_t;
-#endif
struct flat_binder_object;
@@ -57,6 +55,9 @@ class String8;
class TextOutput;
namespace binder {
class Status;
+namespace debug {
+class RecordedTransaction;
+}
}
class Parcel {
@@ -100,7 +101,9 @@ public:
void restoreAllowFds(bool lastValue);
bool hasFileDescriptors() const;
+ status_t hasBinders(bool* result) const;
status_t hasFileDescriptorsInRange(size_t offset, size_t length, bool* result) const;
+ status_t hasBindersInRange(size_t offset, size_t length, bool* result) const;
// returns all binder objects in the Parcel
std::vector<sp<IBinder>> debugReadAllStrongBinders() const;
@@ -154,6 +157,11 @@ public:
// This Api is used by fuzzers to skip dataAvail checks.
void setEnforceNoDataAvail(bool enforceNoDataAvail);
+ // When fuzzing, we want to remove certain ABI checks that cause significant
+ // lost coverage, and we also want to avoid logs that cost too much to write.
+ void setServiceFuzzing();
+ bool isServiceFuzzing() const;
+
void freeData();
size_t objectsCount() const;
@@ -266,7 +274,8 @@ public:
status_t writeEnumVector(const std::optional<std::vector<T>>& val)
{ return writeData(val); }
template<typename T, std::enable_if_t<std::is_enum_v<T> && std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
- status_t writeEnumVector(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")))
+ [[deprecated("use std::optional version instead")]] //
+ status_t writeEnumVector(const std::unique_ptr<std::vector<T>>& val)
{ return writeData(val); }
// Write an Enum vector with underlying type != int8_t.
template<typename T, std::enable_if_t<std::is_enum_v<T> && !std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
@@ -276,17 +285,20 @@ public:
status_t writeEnumVector(const std::optional<std::vector<T>>& val)
{ return writeData(val); }
template<typename T, std::enable_if_t<std::is_enum_v<T> && !std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
- status_t writeEnumVector(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")))
+ [[deprecated("use std::optional version instead")]] //
+ status_t writeEnumVector(const std::unique_ptr<std::vector<T>>& val)
{ return writeData(val); }
template<typename T>
status_t writeParcelableVector(const std::optional<std::vector<std::optional<T>>>& val)
{ return writeData(val); }
template<typename T>
- status_t writeParcelableVector(const std::unique_ptr<std::vector<std::unique_ptr<T>>>& val) __attribute__((deprecated("use std::optional version instead")))
+ [[deprecated("use std::optional version instead")]] //
+ status_t writeParcelableVector(const std::unique_ptr<std::vector<std::unique_ptr<T>>>& val)
{ return writeData(val); }
template<typename T>
- status_t writeParcelableVector(const std::shared_ptr<std::vector<std::unique_ptr<T>>>& val) __attribute__((deprecated("use std::optional version instead")))
+ [[deprecated("use std::optional version instead")]] //
+ status_t writeParcelableVector(const std::shared_ptr<std::vector<std::unique_ptr<T>>>& val)
{ return writeData(val); }
template<typename T>
status_t writeParcelableVector(const std::shared_ptr<std::vector<std::optional<T>>>& val)
@@ -318,11 +330,13 @@ public:
template<typename T>
status_t writeVectorSize(const std::unique_ptr<std::vector<T>>& val) __attribute__((deprecated("use std::optional version instead")));
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
// Place a native_handle into the parcel (the native_handle's file-
// descriptors are dup'ed, so it is safe to delete the native_handle
// when this function returns).
// Doesn't take ownership of the native_handle.
status_t writeNativeHandle(const native_handle* handle);
+#endif
// Place a file descriptor into the parcel. The given fd must remain
// valid for the lifetime of the parcel.
@@ -345,17 +359,16 @@ public:
// Place a file descriptor into the parcel. This will not affect the
// semantics of the smart file descriptor. A new descriptor will be
// created, and will be closed when the parcel is destroyed.
- status_t writeUniqueFileDescriptor(
- const base::unique_fd& fd);
+ status_t writeUniqueFileDescriptor(const binder::unique_fd& fd);
// Place a vector of file desciptors into the parcel. Each descriptor is
// dup'd as in writeDupFileDescriptor
- status_t writeUniqueFileDescriptorVector(
- const std::optional<std::vector<base::unique_fd>>& val);
- status_t writeUniqueFileDescriptorVector(
- const std::unique_ptr<std::vector<base::unique_fd>>& val) __attribute__((deprecated("use std::optional version instead")));
- status_t writeUniqueFileDescriptorVector(
- const std::vector<base::unique_fd>& val);
+ status_t writeUniqueFileDescriptorVector(
+ const std::optional<std::vector<binder::unique_fd>>& val);
+ status_t writeUniqueFileDescriptorVector(
+ const std::unique_ptr<std::vector<binder::unique_fd>>& val)
+ __attribute__((deprecated("use std::optional version instead")));
+ status_t writeUniqueFileDescriptorVector(const std::vector<binder::unique_fd>& val);
// Writes a blob to the parcel.
// If the blob is small, then it is stored in-place, otherwise it is
@@ -422,7 +435,8 @@ public:
status_t readEnumVector(std::vector<T>* val) const
{ return readData(val); }
template<typename T, std::enable_if_t<std::is_enum_v<T> && std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
- status_t readEnumVector(std::unique_ptr<std::vector<T>>* val) const __attribute__((deprecated("use std::optional version instead")))
+ [[deprecated("use std::optional version instead")]] //
+ status_t readEnumVector(std::unique_ptr<std::vector<T>>* val) const
{ return readData(val); }
template<typename T, std::enable_if_t<std::is_enum_v<T> && std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
status_t readEnumVector(std::optional<std::vector<T>>* val) const
@@ -432,7 +446,8 @@ public:
status_t readEnumVector(std::vector<T>* val) const
{ return readData(val); }
template<typename T, std::enable_if_t<std::is_enum_v<T> && !std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
- status_t readEnumVector(std::unique_ptr<std::vector<T>>* val) const __attribute__((deprecated("use std::optional version instead")))
+ [[deprecated("use std::optional version instead")]] //
+ status_t readEnumVector(std::unique_ptr<std::vector<T>>* val) const
{ return readData(val); }
template<typename T, std::enable_if_t<std::is_enum_v<T> && !std::is_same_v<typename std::underlying_type_t<T>,int8_t>, bool> = 0>
status_t readEnumVector(std::optional<std::vector<T>>* val) const
@@ -443,8 +458,9 @@ public:
std::optional<std::vector<std::optional<T>>>* val) const
{ return readData(val); }
template<typename T>
+ [[deprecated("use std::optional version instead")]] //
status_t readParcelableVector(
- std::unique_ptr<std::vector<std::unique_ptr<T>>>* val) const __attribute__((deprecated("use std::optional version instead")))
+ std::unique_ptr<std::vector<std::unique_ptr<T>>>* val) const
{ return readData(val); }
template<typename T>
status_t readParcelableVector(std::vector<T>* val) const
@@ -550,13 +566,14 @@ public:
// response headers rather than doing it by hand.
int32_t readExceptionCode() const;
+#ifndef BINDER_DISABLE_NATIVE_HANDLE
// Retrieve native_handle from the parcel. This returns a copy of the
// parcel's native_handle (the caller takes ownership). The caller
- // must free the native_handle with native_handle_close() and
+ // must free the native_handle with native_handle_close() and
// native_handle_delete().
native_handle* readNativeHandle() const;
+#endif
-
// Retrieve a file descriptor from the parcel. This returns the raw fd
// in the parcel, which you do not own -- use dup() to get your own copy.
int readFileDescriptor() const;
@@ -566,20 +583,17 @@ public:
int readParcelFileDescriptor() const;
// Retrieve a smart file descriptor from the parcel.
- status_t readUniqueFileDescriptor(
- base::unique_fd* val) const;
+ status_t readUniqueFileDescriptor(binder::unique_fd* val) const;
// Retrieve a Java "parcel file descriptor" from the parcel.
- status_t readUniqueParcelFileDescriptor(base::unique_fd* val) const;
-
+ status_t readUniqueParcelFileDescriptor(binder::unique_fd* val) const;
// Retrieve a vector of smart file descriptors from the parcel.
- status_t readUniqueFileDescriptorVector(
- std::optional<std::vector<base::unique_fd>>* val) const;
- status_t readUniqueFileDescriptorVector(
- std::unique_ptr<std::vector<base::unique_fd>>* val) const __attribute__((deprecated("use std::optional version instead")));
- status_t readUniqueFileDescriptorVector(
- std::vector<base::unique_fd>* val) const;
+ status_t readUniqueFileDescriptorVector(
+ std::optional<std::vector<binder::unique_fd>>* val) const;
+ status_t readUniqueFileDescriptorVector(std::unique_ptr<std::vector<binder::unique_fd>>* val)
+ const __attribute__((deprecated("use std::optional version instead")));
+ status_t readUniqueFileDescriptorVector(std::vector<binder::unique_fd>* val) const;
// Reads a blob from the parcel.
// The caller should call release() on the blob after reading its contents.
@@ -616,7 +630,7 @@ private:
status_t rpcSetDataReference(
const sp<RpcSession>& session, const uint8_t* data, size_t dataSize,
const uint32_t* objectTable, size_t objectTableSize,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>&& ancillaryFds,
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>&& ancillaryFds,
release_func relFunc);
status_t finishWrite(size_t len);
@@ -635,6 +649,8 @@ private:
void freeDataNoInit();
void initState();
void scanForFds() const;
+ status_t scanForBinders(bool* result) const;
+
status_t validateReadData(size_t len) const;
void updateWorkSourceRequestHeaderPosition() const;
@@ -693,7 +709,7 @@ private:
// 5) Nullable objects contained in std::optional, std::unique_ptr, or std::shared_ptr.
//
// And active objects from the Android ecosystem such as:
- // 6) File descriptors, base::unique_fd (kernel object handles)
+ // 6) File descriptors, unique_fd (kernel object handles)
// 7) Binder objects, sp<IBinder> (active Android RPC handles)
//
// Objects from (1) through (5) serialize into the mData buffer.
@@ -944,9 +960,7 @@ private:
return writeUtf8AsUtf16(t);
}
- status_t writeData(const base::unique_fd& t) {
- return writeUniqueFileDescriptor(t);
- }
+ status_t writeData(const binder::unique_fd& t) { return writeUniqueFileDescriptor(t); }
status_t writeData(const Parcelable& t) { // std::is_base_of_v<Parcelable, T>
// implemented here. writeParcelable() calls this.
@@ -1093,9 +1107,7 @@ private:
return readUtf8FromUtf16(t);
}
- status_t readData(base::unique_fd* t) const {
- return readUniqueFileDescriptor(t);
- }
+ status_t readData(binder::unique_fd* t) const { return readUniqueFileDescriptor(t); }
status_t readData(Parcelable* t) const { // std::is_base_of_v<Parcelable, T>
// implemented here. readParcelable() calls this.
@@ -1280,6 +1292,7 @@ private:
// Fields only needed when parcelling for "kernel Binder".
struct KernelFields {
+ KernelFields() {}
binder_size_t* mObjects = nullptr;
size_t mObjectsSize = 0;
size_t mObjectsCapacity = 0;
@@ -1314,7 +1327,7 @@ private:
// same order as `mObjectPositions`.
//
// Boxed to save space. Lazy allocated.
- std::unique_ptr<std::vector<std::variant<base::unique_fd, base::borrowed_fd>>> mFds;
+ std::unique_ptr<std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>> mFds;
};
std::variant<KernelFields, RpcFields> mVariantFields;
@@ -1335,6 +1348,7 @@ private:
// Set this to false to skip dataAvail checks.
bool mEnforceNoDataAvail;
+ bool mServiceFuzzing;
release_func mOwner;
@@ -1436,6 +1450,9 @@ private:
// TODO(b/202029388): Remove 'getBlobAshmemSize' once no prebuilts reference
// this
size_t getBlobAshmemSize() const;
+
+ // Needed so that we can save object metadata to the disk
+ friend class android::binder::debug::RecordedTransaction;
};
// ---------------------------------------------------------------------------
diff --git a/libs/binder/include/binder/ParcelFileDescriptor.h b/libs/binder/include/binder/ParcelFileDescriptor.h
index 08d8e43106..c4ef3547e9 100644
--- a/libs/binder/include/binder/ParcelFileDescriptor.h
+++ b/libs/binder/include/binder/ParcelFileDescriptor.h
@@ -16,9 +16,9 @@
#pragma once
-#include <android-base/unique_fd.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
+#include <binder/unique_fd.h>
namespace android {
namespace os {
@@ -29,14 +29,14 @@ namespace os {
class ParcelFileDescriptor : public android::Parcelable {
public:
ParcelFileDescriptor();
- explicit ParcelFileDescriptor(android::base::unique_fd fd);
+ explicit ParcelFileDescriptor(binder::unique_fd fd);
ParcelFileDescriptor(ParcelFileDescriptor&& other) noexcept : mFd(std::move(other.mFd)) { }
ParcelFileDescriptor& operator=(ParcelFileDescriptor&& other) noexcept = default;
~ParcelFileDescriptor() override;
int get() const { return mFd.get(); }
- android::base::unique_fd release() { return std::move(mFd); }
- void reset(android::base::unique_fd fd = android::base::unique_fd()) { mFd = std::move(fd); }
+ binder::unique_fd release() { return std::move(mFd); }
+ void reset(binder::unique_fd fd = binder::unique_fd()) { mFd = std::move(fd); }
// android::Parcelable override:
android::status_t writeToParcel(android::Parcel* parcel) const override;
@@ -62,7 +62,7 @@ public:
return mFd.get() >= rhs.mFd.get();
}
private:
- android::base::unique_fd mFd;
+ binder::unique_fd mFd;
};
} // namespace os
diff --git a/libs/binder/include/binder/ProcessState.h b/libs/binder/include/binder/ProcessState.h
index ce578e3f5c..3672702fe1 100644
--- a/libs/binder/include/binder/ProcessState.h
+++ b/libs/binder/include/binder/ProcessState.h
@@ -17,13 +17,13 @@
#pragma once
#include <binder/IBinder.h>
-#include <utils/KeyedVector.h>
-#include <utils/Mutex.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <pthread.h>
+#include <mutex>
+
// ---------------------------------------------------------------------------
namespace android {
@@ -52,10 +52,29 @@ public:
sp<IBinder> getContextObject(const sp<IBinder>& caller);
- // For main functions - dangerous for libraries to use
+ // This should be called before startThreadPool at the beginning
+ // of a program, and libraries should never call it because programs
+ // should configure their own threadpools. The threadpool size can
+ // never be decreased.
+ //
+ // The 'maxThreads' value refers to the total number of threads
+ // that will be started by the kernel. This is in addition to any
+ // threads started by 'startThreadPool' or 'joinRpcThreadpool'.
+ status_t setThreadPoolMaxThreadCount(size_t maxThreads);
+
+ // Libraries should not call this, as processes should configure
+ // threadpools themselves. Should be called in the main function
+ // directly before any code executes or joins the threadpool.
+ //
+ // Starts one thread, PLUS those requested in setThreadPoolMaxThreadCount,
+ // PLUS those manually requested in joinThreadPool.
+ //
+ // For instance, if setThreadPoolMaxCount(3) is called and
+ // startThreadpPool (+1 thread) and joinThreadPool (+1 thread)
+ // are all called, then up to 5 threads can be started.
void startThreadPool();
- bool becomeContextManager();
+ [[nodiscard]] bool becomeContextManager();
sp<IBinder> getStrongProxyForHandle(int32_t handle);
void expungeHandle(int32_t handle, IBinder* binder);
@@ -63,8 +82,6 @@ public:
// TODO: deprecate.
void spawnPooledThread(bool isMain);
- // For main functions - dangerous for libraries to use
- status_t setThreadPoolMaxThreadCount(size_t maxThreads);
status_t enableOnewaySpamDetection(bool enable);
// Set the name of the current thread to look like a threadpool
@@ -161,7 +178,7 @@ private:
// Time when thread pool was emptied
int64_t mStarvationStartTimeMs;
- mutable Mutex mLock; // protects everything below.
+ mutable std::mutex mLock; // protects everything below.
Vector<handle_entry> mHandleToObject;
diff --git a/libs/binder/include/binder/RecordedTransaction.h b/libs/binder/include/binder/RecordedTransaction.h
index eb765fe8ec..f0bee7f39e 100644
--- a/libs/binder/include/binder/RecordedTransaction.h
+++ b/libs/binder/include/binder/RecordedTransaction.h
@@ -16,8 +16,8 @@
#pragma once
-#include <android-base/unique_fd.h>
#include <binder/Parcel.h>
+#include <binder/unique_fd.h>
#include <mutex>
namespace android {
@@ -31,7 +31,8 @@ namespace binder::debug {
class RecordedTransaction {
public:
// Filled with the first transaction from fd.
- static std::optional<RecordedTransaction> fromFile(const android::base::unique_fd& fd);
+
+ static std::optional<RecordedTransaction> fromFile(const binder::unique_fd& fd);
// Filled with the arguments.
static std::optional<RecordedTransaction> fromDetails(const String16& interfaceName,
uint32_t code, uint32_t flags,
@@ -39,7 +40,7 @@ public:
const Parcel& reply, status_t err);
RecordedTransaction(RecordedTransaction&& t) noexcept;
- [[nodiscard]] status_t dumpToFile(const android::base::unique_fd& fd) const;
+ [[nodiscard]] status_t dumpToFile(const binder::unique_fd& fd) const;
const std::string& getInterfaceName() const;
uint32_t getCode() const;
@@ -49,12 +50,13 @@ public:
uint32_t getVersion() const;
const Parcel& getDataParcel() const;
const Parcel& getReplyParcel() const;
+ const std::vector<uint64_t>& getObjectOffsets() const;
private:
RecordedTransaction() = default;
- android::status_t writeChunk(const android::base::borrowed_fd, uint32_t chunkType,
- size_t byteCount, const uint8_t* data) const;
+ android::status_t writeChunk(const binder::borrowed_fd, uint32_t chunkType, size_t byteCount,
+ const uint8_t* data) const;
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wpadded"
@@ -74,10 +76,11 @@ private:
struct MovableData { // movable
TransactionHeader mHeader;
std::string mInterfaceName;
+ std::vector<uint64_t> mSentObjectData; /* Object Offsets */
};
MovableData mData;
- Parcel mSent;
- Parcel mReply;
+ Parcel mSentDataOnly;
+ Parcel mReplyDataOnly;
};
} // namespace binder::debug
diff --git a/libs/binder/include/binder/RpcServer.h b/libs/binder/include/binder/RpcServer.h
index 25193a3428..a07880dd65 100644
--- a/libs/binder/include/binder/RpcServer.h
+++ b/libs/binder/include/binder/RpcServer.h
@@ -15,14 +15,15 @@
*/
#pragma once
-#include <android-base/unique_fd.h>
#include <binder/IBinder.h>
#include <binder/RpcSession.h>
#include <binder/RpcThreads.h>
#include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
+#include <bitset>
#include <mutex>
#include <thread>
@@ -58,7 +59,7 @@ public:
* to RpcSession::setupUnixDomainSocketBootstrapClient. Multiple client
* session can be created from the client end of the pair.
*/
- [[nodiscard]] status_t setupUnixDomainSocketBootstrapServer(base::unique_fd serverFd);
+ [[nodiscard]] status_t setupUnixDomainSocketBootstrapServer(binder::unique_fd serverFd);
/**
* This represents a session for responses, e.g.:
@@ -78,7 +79,7 @@ public:
* This method is used in the libbinder_rpc_unstable API
* RunInitUnixDomainRpcServer().
*/
- [[nodiscard]] status_t setupRawSocketServer(base::unique_fd socket_fd);
+ [[nodiscard]] status_t setupRawSocketServer(binder::unique_fd socket_fd);
/**
* Creates an RPC server binding to the given CID at the given port.
@@ -110,16 +111,19 @@ public:
/**
* If hasServer(), return the server FD. Otherwise return invalid FD.
*/
- [[nodiscard]] base::unique_fd releaseServer();
+ [[nodiscard]] binder::unique_fd releaseServer();
/**
* Set up server using an external FD previously set up by releaseServer().
* Return false if there's already a server.
*/
- [[nodiscard]] status_t setupExternalServer(base::unique_fd serverFd);
+ [[nodiscard]] status_t setupExternalServer(binder::unique_fd serverFd);
/**
- * This must be called before adding a client session.
+ * This must be called before adding a client session. This corresponds
+ * to the number of incoming connections to RpcSession objects in the
+ * server, which will correspond to the number of outgoing connections
+ * in client RpcSession objects.
*
* If this is not specified, this will be a single-threaded server.
*
@@ -134,7 +138,7 @@ public:
* used. However, this can be used in order to prevent newer protocol
* versions from ever being used. This is expected to be useful for testing.
*/
- void setProtocolVersion(uint32_t version);
+ [[nodiscard]] bool setProtocolVersion(uint32_t version);
/**
* Set the supported transports for sending and receiving file descriptors.
@@ -160,14 +164,18 @@ public:
* Allows a root object to be created for each session.
*
* Takes one argument: a callable that is invoked once per new session.
- * The callable takes two arguments: a type-erased pointer to an OS- and
- * transport-specific address structure, e.g., sockaddr_vm for vsock, and
- * an integer representing the size in bytes of that structure. The
- * callable should validate the size, then cast the type-erased pointer
- * to a pointer to the actual type of the address, e.g., const void* to
- * const sockaddr_vm*.
+ * The callable takes three arguments:
+ * - a weak pointer to the session. If you want to hold onto this in the root object, then
+ * you should keep a weak pointer, and promote it when needed. For instance, if you refer
+ * to this from the root object, then you could get ahold of transport-specific information.
+ * - a type-erased pointer to an OS- and transport-specific address structure, e.g.,
+ * sockaddr_vm for vsock
+ * - an integer representing the size in bytes of that structure. The callable should
+ * validate the size, then cast the type-erased pointer to a pointer to the actual type of the
+ * address, e.g., const void* to const sockaddr_vm*.
*/
- void setPerSessionRootObject(std::function<sp<IBinder>(const void*, size_t)>&& object);
+ void setPerSessionRootObject(
+ std::function<sp<IBinder>(wp<RpcSession> session, const void*, size_t)>&& object);
sp<IBinder> getRootObject();
/**
@@ -181,6 +189,13 @@ public:
void setConnectionFilter(std::function<bool(const void*, size_t)>&& filter);
/**
+ * Set optional modifier of each newly created server socket.
+ *
+ * The only argument is a successfully created file descriptor, not bound to an address yet.
+ */
+ void setServerSocketModifier(std::function<void(binder::borrowed_fd)>&& modifier);
+
+ /**
* See RpcTransportCtx::getCertificate
*/
std::vector<uint8_t> getCertificate(RpcCertificateFormat);
@@ -234,7 +249,7 @@ private:
void onSessionIncomingThreadEnded() override;
status_t setupExternalServer(
- base::unique_fd serverFd,
+ binder::unique_fd serverFd,
std::function<status_t(const RpcServer&, RpcTransportFd*)>&& acceptFn);
static constexpr size_t kRpcAddressSize = 128;
@@ -262,8 +277,9 @@ private:
sp<IBinder> mRootObject;
wp<IBinder> mRootObjectWeak;
- std::function<sp<IBinder>(const void*, size_t)> mRootObjectFactory;
+ std::function<sp<IBinder>(wp<RpcSession>, const void*, size_t)> mRootObjectFactory;
std::function<bool(const void*, size_t)> mConnectionFilter;
+ std::function<void(binder::borrowed_fd)> mServerSocketModifier;
std::map<std::vector<uint8_t>, sp<RpcSession>> mSessions;
std::unique_ptr<FdTrigger> mShutdownTrigger;
RpcConditionVariable mShutdownCv;
diff --git a/libs/binder/include/binder/RpcSession.h b/libs/binder/include/binder/RpcSession.h
index 40faf2c3b1..11fbde9ace 100644
--- a/libs/binder/include/binder/RpcSession.h
+++ b/libs/binder/include/binder/RpcSession.h
@@ -15,11 +15,10 @@
*/
#pragma once
-#include <android-base/threads.h>
-#include <android-base/unique_fd.h>
#include <binder/IBinder.h>
#include <binder/RpcThreads.h>
#include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
@@ -37,9 +36,9 @@ class RpcState;
class RpcTransport;
class FdTrigger;
-constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 1;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_NEXT = 2;
constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL = 0xF0000000;
-constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL;
+constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION = 1;
// Starting with this version:
//
@@ -51,11 +50,12 @@ constexpr uint32_t RPC_WIRE_PROTOCOL_VERSION_RPC_HEADER_FEATURE_EXPLICIT_PARCEL_
* This represents a session (group of connections) between a client
* and a server. Multiple connections are needed for multiple parallel "binder"
* calls which may also have nested calls.
+ *
+ * Once a binder exists in the session, if all references to all binders are dropped,
+ * the session shuts down.
*/
class RpcSession final : public virtual RefBase {
public:
- static constexpr size_t kDefaultMaxOutgoingThreads = 10;
-
// Create an RpcSession with default configuration (raw sockets).
static sp<RpcSession> make();
@@ -67,26 +67,30 @@ public:
/**
* Set the maximum number of incoming threads allowed to be made (for things like callbacks).
* By default, this is 0. This must be called before setting up this connection as a client.
- * Server sessions will inherits this value from RpcServer.
+ * Server sessions will inherits this value from RpcServer. Each thread will serve a
+ * connection to the remote RpcSession.
*
* If this is called, 'shutdown' on this session must also be called.
* Otherwise, a threadpool will leak.
*
- * TODO(b/189955605): start these dynamically
+ * TODO(b/189955605): start these lazily - currently all are started
*/
void setMaxIncomingThreads(size_t threads);
size_t getMaxIncomingThreads();
/**
- * Set the maximum number of outgoing threads allowed to be made.
- * By default, this is |kDefaultMaxOutgoingThreads|. This must be called before setting up this
- * connection as a client.
+ * Set the maximum number of outgoing connections allowed to be made.
+ * By default, this is |kDefaultMaxOutgoingConnections|. This must be called before setting up
+ * this connection as a client.
*
- * This limits the number of outgoing threads on top of the remote peer setting. This RpcSession
- * will only instantiate |min(maxOutgoingThreads, remoteMaxThreads)| outgoing threads, where
- * |remoteMaxThreads| can be retrieved from the remote peer via |getRemoteMaxThreads()|.
+ * For an RpcSession client, if you are connecting to a server which starts N threads,
+ * then this must be set to >= N. If you set the maximum number of outgoing connections
+ * to 1, but the server requests 10, then it would be considered an error. If you set a
+ * maximum number of connections to 10, and the server requests 1, then only 1 will be
+ * created. This API is used to limit the amount of resources a server can request you
+ * create.
*/
- void setMaxOutgoingThreads(size_t threads);
+ void setMaxOutgoingConnections(size_t connections);
size_t getMaxOutgoingThreads();
/**
@@ -119,7 +123,7 @@ public:
/**
* Connects to an RPC server over a nameless Unix domain socket pair.
*/
- [[nodiscard]] status_t setupUnixDomainSocketBootstrapClient(base::unique_fd bootstrap);
+ [[nodiscard]] status_t setupUnixDomainSocketBootstrapClient(binder::unique_fd bootstrap);
/**
* Connects to an RPC server at the CVD & port.
@@ -141,8 +145,8 @@ public:
*
* For future compatibility, 'request' should not reference any stack data.
*/
- [[nodiscard]] status_t setupPreconnectedClient(base::unique_fd fd,
- std::function<base::unique_fd()>&& request);
+ [[nodiscard]] status_t setupPreconnectedClient(binder::unique_fd fd,
+ std::function<binder::unique_fd()>&& request);
/**
* For debugging!
@@ -219,6 +223,8 @@ private:
friend RpcState;
explicit RpcSession(std::unique_ptr<RpcTransportCtx> ctx);
+ static constexpr size_t kDefaultMaxOutgoingConnections = 10;
+
// internal version of setProtocolVersion that
// optionally skips the mStartedSetup check
[[nodiscard]] bool setProtocolVersionInternal(uint32_t version, bool checkStarted);
@@ -368,7 +374,7 @@ private:
bool mStartedSetup = false;
size_t mMaxIncomingThreads = 0;
- size_t mMaxOutgoingThreads = kDefaultMaxOutgoingThreads;
+ size_t mMaxOutgoingConnections = kDefaultMaxOutgoingConnections;
std::optional<uint32_t> mProtocolVersion;
FileDescriptorTransportMode mFileDescriptorTransportMode = FileDescriptorTransportMode::NONE;
diff --git a/libs/binder/include/binder/RpcThreads.h b/libs/binder/include/binder/RpcThreads.h
index 8abf04eaf0..d25f29277c 100644
--- a/libs/binder/include/binder/RpcThreads.h
+++ b/libs/binder/include/binder/RpcThreads.h
@@ -17,8 +17,7 @@
#include <pthread.h>
-#include <android-base/threads.h>
-
+#include <condition_variable>
#include <functional>
#include <memory>
#include <thread>
@@ -120,10 +119,6 @@ static inline RpcMaybeThread::id get_id() {
}
} // namespace rpc_this_thread
-static inline uint64_t rpcGetThreadId() {
- return 0;
-}
-
static inline void rpcJoinIfSingleThreaded(RpcMaybeThread& t) {
t.join();
}
@@ -135,10 +130,6 @@ using RpcConditionVariable = std::condition_variable;
using RpcMaybeThread = std::thread;
namespace rpc_this_thread = std::this_thread;
-static inline uint64_t rpcGetThreadId() {
- return base::GetThreadId();
-}
-
static inline void rpcJoinIfSingleThreaded(RpcMaybeThread&) {}
#endif // BINDER_RPC_SINGLE_THREADED
diff --git a/libs/binder/include/binder/RpcTransport.h b/libs/binder/include/binder/RpcTransport.h
index fd52a3a1a9..a50cdc1db0 100644
--- a/libs/binder/include/binder/RpcTransport.h
+++ b/libs/binder/include/binder/RpcTransport.h
@@ -25,12 +25,12 @@
#include <variant>
#include <vector>
-#include <android-base/function_ref.h>
-#include <android-base/unique_fd.h>
#include <utils/Errors.h>
+#include <binder/Functional.h>
#include <binder/RpcCertificateFormat.h>
#include <binder/RpcThreads.h>
+#include <binder/unique_fd.h>
#include <sys/uio.h>
@@ -39,6 +39,16 @@ namespace android {
class FdTrigger;
struct RpcTransportFd;
+// for 'friend'
+class RpcTransportRaw;
+class RpcTransportTls;
+class RpcTransportTipcAndroid;
+class RpcTransportTipcTrusty;
+class RpcTransportCtxRaw;
+class RpcTransportCtxTls;
+class RpcTransportCtxTipcAndroid;
+class RpcTransportCtxTipcTrusty;
+
// Represents a socket connection.
// No thread-safety is guaranteed for these APIs.
class RpcTransport {
@@ -75,13 +85,14 @@ public:
* error - interrupted (failure or trigger)
*/
[[nodiscard]] virtual status_t interruptableWriteFully(
- FdTrigger *fdTrigger, iovec *iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>> &altPoll,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0;
+ FdTrigger* fdTrigger, iovec* iovs, int niovs,
+ const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
+ const std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>*
+ ancillaryFds) = 0;
[[nodiscard]] virtual status_t interruptableReadFully(
- FdTrigger *fdTrigger, iovec *iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>> &altPoll,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> *ancillaryFds) = 0;
+ FdTrigger* fdTrigger, iovec* iovs, int niovs,
+ const std::optional<binder::impl::SmallFunction<status_t()>>& altPoll,
+ std::vector<std::variant<binder::unique_fd, binder::borrowed_fd>>* ancillaryFds) = 0;
/**
* Check whether any threads are blocked while polling the transport
@@ -92,7 +103,21 @@ public:
*/
[[nodiscard]] virtual bool isWaiting() = 0;
-protected:
+private:
+ // limit the classes which can implement RpcTransport. Being able to change this
+ // interface is important to allow development of RPC binder. In the past, we
+ // changed this interface to use iovec for efficiency, and we added FDs to the
+ // interface. If another transport is needed, it should be added directly here.
+ // non-socket FDs likely also need changes in RpcSession in order to get
+ // connected, and similarly to how addrinfo was type-erased from RPC binder
+ // interfaces when RpcTransportTipc* was added, other changes may be needed
+ // to add more transports.
+
+ friend class ::android::RpcTransportRaw;
+ friend class ::android::RpcTransportTls;
+ friend class ::android::RpcTransportTipcAndroid;
+ friend class ::android::RpcTransportTipcTrusty;
+
RpcTransport() = default;
};
@@ -117,7 +142,13 @@ public:
[[nodiscard]] virtual std::vector<uint8_t> getCertificate(
RpcCertificateFormat format) const = 0;
-protected:
+private:
+ // see comment on RpcTransport
+ friend class ::android::RpcTransportCtxRaw;
+ friend class ::android::RpcTransportCtxTls;
+ friend class ::android::RpcTransportCtxTipcAndroid;
+ friend class ::android::RpcTransportCtxTipcTrusty;
+
RpcTransportCtx() = default;
};
@@ -140,17 +171,17 @@ protected:
RpcTransportCtxFactory() = default;
};
-struct RpcTransportFd {
+struct RpcTransportFd final {
private:
mutable bool isPolling{false};
void setPollingState(bool state) const { isPolling = state; }
public:
- base::unique_fd fd;
+ binder::unique_fd fd;
RpcTransportFd() = default;
- explicit RpcTransportFd(base::unique_fd &&descriptor)
+ explicit RpcTransportFd(binder::unique_fd&& descriptor)
: isPolling(false), fd(std::move(descriptor)) {}
RpcTransportFd(RpcTransportFd &&transportFd) noexcept
@@ -162,7 +193,7 @@ public:
return *this;
}
- RpcTransportFd &operator=(base::unique_fd &&descriptor) noexcept {
+ RpcTransportFd& operator=(binder::unique_fd&& descriptor) noexcept {
fd = std::move(descriptor);
isPolling = false;
return *this;
diff --git a/libs/binder/include/binder/SafeInterface.h b/libs/binder/include/binder/SafeInterface.h
index 5fa2ff6a87..96b9733c94 100644
--- a/libs/binder/include/binder/SafeInterface.h
+++ b/libs/binder/include/binder/SafeInterface.h
@@ -18,7 +18,6 @@
#include <binder/IInterface.h>
#include <binder/Parcel.h>
-#include <cutils/compiler.h>
// Set to 1 to enable CallStacks when logging errors
#define SI_DUMP_CALLSTACKS 0
@@ -218,7 +217,7 @@ private:
template <typename Function>
status_t callParcel(const char* name, Function f) const {
status_t error = f();
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to %s, (%d: %s)", name, error, strerror(-error));
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
@@ -265,7 +264,7 @@ protected:
data.writeInterfaceToken(this->getInterfaceDescriptor());
status_t error = writeInputs(&data, std::forward<Args>(args)...);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeInputs
return error;
}
@@ -273,7 +272,7 @@ protected:
// Send the data Parcel to the remote and retrieve the reply parcel
Parcel reply;
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
@@ -283,7 +282,7 @@ protected:
// Read the outputs from the reply Parcel into the output arguments
error = readOutputs(reply, std::forward<Args>(args)...);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by readOutputs
return error;
}
@@ -291,7 +290,7 @@ protected:
// Retrieve the result code from the reply Parcel
status_t result = NO_ERROR;
error = reply.readInt32(&result);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to obtain result");
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
@@ -315,7 +314,7 @@ protected:
Parcel data;
data.writeInterfaceToken(this->getInterfaceDescriptor());
status_t error = writeInputs(&data, std::forward<Args>(args)...);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeInputs
return;
}
@@ -324,7 +323,7 @@ protected:
Parcel reply;
error = this->remote()->transact(static_cast<uint32_t>(tag), data, &reply,
IBinder::FLAG_ONEWAY);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to transact (%d)", error);
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
@@ -406,7 +405,7 @@ private:
template <typename T, typename... Remaining>
status_t writeInputs(Parcel* data, T&& t, Remaining&&... remaining) const {
status_t error = writeIfInput(data, std::forward<T>(t));
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by writeIfInput
return error;
}
@@ -429,7 +428,7 @@ private:
template <typename T, typename... Remaining>
status_t readOutputs(const Parcel& reply, T&& t, Remaining&&... remaining) const {
status_t error = readIfOutput(reply, std::forward<T>(t));
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by readIfOutput
return error;
}
@@ -458,7 +457,7 @@ protected:
// Read the inputs from the data Parcel into the argument tuple
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by read
return error;
}
@@ -468,14 +467,14 @@ protected:
// Extract the outputs from the argument tuple and write them into the reply Parcel
error = OutputWriter<ParamTuple>{mLogTag}.writeOutputs(reply, &rawArgs);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by write
return error;
}
// Return the result code in the reply Parcel
error = reply->writeInt32(result);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
ALOG(LOG_ERROR, mLogTag, "Failed to write result");
#if SI_DUMP_CALLSTACKS
CallStack callStack(mLogTag);
@@ -500,7 +499,7 @@ protected:
// Read the inputs from the data Parcel into the argument tuple
status_t error = InputReader<ParamTuple>{mLogTag}.readInputs(data, &rawArgs);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged by read
return error;
}
@@ -596,7 +595,7 @@ private:
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
const Parcel& data, RawTuple* args) {
status_t error = readIfInput<I>(data, args);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged in read
return error;
}
@@ -694,7 +693,7 @@ private:
typename std::enable_if<(I < sizeof...(Params)), status_t>::type dispatchArg(
Parcel* reply, RawTuple* args) {
status_t error = writeIfOutput<I>(reply, args);
- if (CC_UNLIKELY(error != NO_ERROR)) {
+ if (error != NO_ERROR) [[unlikely]] {
// A message will have been logged in read
return error;
}
diff --git a/libs/binder/include/binder/TextOutput.h b/libs/binder/include/binder/TextOutput.h
index eb98042c42..50158c3072 100644
--- a/libs/binder/include/binder/TextOutput.h
+++ b/libs/binder/include/binder/TextOutput.h
@@ -147,7 +147,7 @@ inline TextOutput& operator<<(TextOutput& to, const bool &val)
inline TextOutput& operator<<(TextOutput& to, const String16& val)
{
- to << String8(val).string();
+ to << String8(val).c_str();
return to;
}
diff --git a/libs/binder/include/binder/Trace.h b/libs/binder/include/binder/Trace.h
index 99378428ad..95318b2bf6 100644
--- a/libs/binder/include/binder/Trace.h
+++ b/libs/binder/include/binder/Trace.h
@@ -16,22 +16,36 @@
#pragma once
-#include <cutils/trace.h>
#include <stdint.h>
+#if __has_include(<cutils/trace.h>)
+#include <cutils/trace.h>
+#endif
+
+#ifdef ATRACE_TAG_AIDL
+#if ATRACE_TAG_AIDL != (1 << 24)
+#error "Mismatched ATRACE_TAG_AIDL definitions"
+#endif
+#else
+#define ATRACE_TAG_AIDL (1 << 24)
+#endif
+
namespace android {
namespace binder {
+// Forward declarations from internal OS.h
+namespace os {
// Trampoline functions allowing generated aidls to trace binder transactions without depending on
// libcutils/libutils
-void atrace_begin(uint64_t tag, const char* name);
-void atrace_end(uint64_t tag);
+void trace_begin(uint64_t tag, const char* name);
+void trace_end(uint64_t tag);
+} // namespace os
class ScopedTrace {
public:
- inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { atrace_begin(mTag, name); }
+ inline ScopedTrace(uint64_t tag, const char* name) : mTag(tag) { os::trace_begin(mTag, name); }
- inline ~ScopedTrace() { atrace_end(mTag); }
+ inline ~ScopedTrace() { os::trace_end(mTag); }
private:
uint64_t mTag;
diff --git a/libs/binder/include/binder/unique_fd.h b/libs/binder/include/binder/unique_fd.h
new file mode 100644
index 0000000000..439b8a2e4e
--- /dev/null
+++ b/libs/binder/include/binder/unique_fd.h
@@ -0,0 +1,114 @@
+/*
+ * 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
+
+#ifndef BINDER_NO_LIBBASE
+
+#include <android-base/unique_fd.h>
+
+namespace android::binder {
+using android::base::borrowed_fd;
+using android::base::unique_fd;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <errno.h>
+#include <fcntl.h> // not needed for unique_fd, but a lot of users depend on open(3)
+#include <unistd.h>
+
+namespace android::binder {
+
+// Container for a file descriptor that automatically closes the descriptor as
+// it goes out of scope.
+//
+// unique_fd ufd(open("/some/path", "r"));
+// if (!ufd.ok()) return error;
+//
+// // Do something useful with ufd.get(), possibly including early 'return'.
+//
+// return 0; // Descriptor is closed for you.
+//
+class unique_fd final {
+public:
+ unique_fd() {}
+
+ explicit unique_fd(int fd) { reset(fd); }
+ ~unique_fd() { reset(); }
+
+ unique_fd(const unique_fd&) = delete;
+ void operator=(const unique_fd&) = delete;
+ unique_fd(unique_fd&& other) noexcept { reset(other.release()); }
+ unique_fd& operator=(unique_fd&& s) noexcept {
+ int fd = s.fd_;
+ s.fd_ = -1;
+ reset(fd);
+ return *this;
+ }
+
+ [[clang::reinitializes]] void reset(int new_value = -1) {
+ int previous_errno = errno;
+
+ if (fd_ != -1) {
+ ::close(fd_);
+ }
+
+ fd_ = new_value;
+ errno = previous_errno;
+ }
+
+ int get() const { return fd_; }
+
+ bool ok() const { return get() >= 0; }
+
+ [[nodiscard]] int release() {
+ int ret = fd_;
+ fd_ = -1;
+ return ret;
+ }
+
+private:
+ int fd_ = -1;
+};
+
+// A wrapper type that can be implicitly constructed from either int or
+// unique_fd. This supports cases where you don't actually own the file
+// descriptor, and can't take ownership, but are temporarily acting as if
+// you're the owner.
+//
+// One example would be a function that needs to also allow
+// STDERR_FILENO, not just a newly-opened fd. Another example would be JNI code
+// that's using a file descriptor that's actually owned by a
+// ParcelFileDescriptor or whatever on the Java side, but where the JNI code
+// would like to enforce this weaker sense of "temporary ownership".
+//
+// If you think of unique_fd as being like std::string in that represents
+// ownership, borrowed_fd is like std::string_view (and int is like const
+// char*).
+struct borrowed_fd {
+ /* implicit */ borrowed_fd(int fd) : fd_(fd) {} // NOLINT
+ /* implicit */ borrowed_fd(const unique_fd& ufd) : fd_(ufd.get()) {} // NOLINT
+
+ int get() const { return fd_; }
+
+private:
+ int fd_ = -1;
+};
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/include_activitymanager/binder/ActivityManager.h b/libs/binder/include_activitymanager/binder/ActivityManager.h
index 5dfbd44dc4..9c634c7238 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<IUidObserver>& observer, const int32_t event,
+ const int32_t cutpoint, const String16& callingPackage,
+ const int32_t uids[], size_t nUids,
+ /*out*/ sp<IBinder>& observerToken);
status_t unregisterUidObserver(const sp<IUidObserver>& observer);
+ status_t addUidToObserver(const sp<IBinder>& observerToken, const String16& callingPackage,
+ int32_t uid);
+ status_t removeUidFromObserver(const sp<IBinder>& 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 4632b2eb0f..07450c6af9 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<IUidObserver>& observer,
+ const int32_t event, const int32_t cutpoint,
+ const String16& callingPackage,
+ const int32_t uids[], size_t nUids,
+ /*out*/ sp<IBinder>& observerToken) = 0;
virtual status_t unregisterUidObserver(const sp<IUidObserver>& observer) = 0;
+ virtual status_t addUidToObserver(const sp<IBinder>& observerToken,
+ const String16& callingPackage, int32_t uid) = 0;
+ virtual status_t removeUidFromObserver(const sp<IBinder>& 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 17f03a9201..5ea7447ef2 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 6defc7fb0b..5bb01dd86b 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 <binder/IInterface.h>
+#include <binder/Status.h>
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/binder/include_rpc_unstable/binder_rpc_unstable.hpp b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
index 42d226b805..392ebb5b0a 100644
--- a/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
+++ b/libs/binder/include_rpc_unstable/binder_rpc_unstable.hpp
@@ -40,12 +40,13 @@ enum class ARpcSession_FileDescriptorTransportMode {
[[nodiscard]] ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid,
unsigned int port);
-// Starts a Unix domain RPC server with a given init-managed Unix domain `name`
+// Starts a Unix domain RPC server with an open raw socket file descriptor
// and a given root IBinder object.
-// The socket should be created in init.rc with the same `name`.
+// The socket should be created and bound to an address.
// Returns an opaque handle to the running server instance, or null if the server
// could not be started.
-[[nodiscard]] ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name);
+// The socket will be closed by the server once the server goes out of scope.
+[[nodiscard]] ARpcServer* ARpcServer_newBoundSocket(AIBinder* service, int socketFd);
// Starts an RPC server that bootstraps sessions using an existing Unix domain
// socket pair, with a given root IBinder object.
@@ -72,6 +73,17 @@ void ARpcServer_setSupportedFileDescriptorTransportModes(
const ARpcSession_FileDescriptorTransportMode modes[],
size_t modes_len);
+// Sets the maximum number of threads that the Server will use for
+// incoming client connections.
+//
+// This must be called before adding a client session. This corresponds
+// to the number of incoming connections to RpcSession objects in the
+// server, which will correspond to the number of outgoing connections
+// in client RpcSession objects.
+//
+// If this is not specified, this will be a single-threaded server.
+void ARpcServer_setMaxThreads(ARpcServer* server, size_t threads);
+
// Runs ARpcServer_join() in a background thread. Immediately returns.
void ARpcServer_start(ARpcServer* server);
@@ -126,11 +138,11 @@ AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* session,
void ARpcSession_setFileDescriptorTransportMode(ARpcSession* session,
ARpcSession_FileDescriptorTransportMode mode);
-// Sets the maximum number of incoming threads.
+// Sets the maximum number of incoming threads, to service connections.
void ARpcSession_setMaxIncomingThreads(ARpcSession* session, size_t threads);
-// Sets the maximum number of outgoing threads.
-void ARpcSession_setMaxOutgoingThreads(ARpcSession* session, size_t threads);
+// Sets the maximum number of outgoing connections.
+void ARpcSession_setMaxOutgoingConnections(ARpcSession* session, size_t connections);
// Decrements the refcount of the underlying RpcSession object.
void ARpcSession_free(ARpcSession* session);
diff --git a/libs/binder/libbinder_rpc_unstable.cpp b/libs/binder/libbinder_rpc_unstable.cpp
index daff8c1e16..21537fc50d 100644
--- a/libs/binder/libbinder_rpc_unstable.cpp
+++ b/libs/binder/libbinder_rpc_unstable.cpp
@@ -16,13 +16,18 @@
#include <binder_rpc_unstable.hpp>
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
#include <android/binder_libbinder.h>
#include <binder/RpcServer.h>
#include <binder/RpcSession.h>
+#include <binder/unique_fd.h>
+
+#ifndef __TRUSTY__
#include <cutils/sockets.h>
+#endif
+
+#ifdef __linux__
#include <linux/vm_sockets.h>
+#endif // __linux__
using android::OK;
using android::RpcServer;
@@ -30,7 +35,7 @@ using android::RpcSession;
using android::sp;
using android::status_t;
using android::statusToString;
-using android::base::unique_fd;
+using android::binder::unique_fd;
// Opaque handle for RpcServer.
struct ARpcServer {};
@@ -75,6 +80,7 @@ RpcSession::FileDescriptorTransportMode toTransportMode(
extern "C" {
+#ifndef __TRUSTY__
ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned int port) {
auto server = RpcServer::make();
@@ -85,8 +91,8 @@ ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned in
}
if (status_t status = server->setupVsockServer(bindCid, port); status != OK) {
- LOG(ERROR) << "Failed to set up vsock server with port " << port
- << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up vsock server with port %u error: %s", port,
+ statusToString(status).c_str());
return nullptr;
}
if (cid != VMADDR_CID_ANY) {
@@ -95,7 +101,7 @@ ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned in
const sockaddr_vm* vaddr = reinterpret_cast<const sockaddr_vm*>(addr);
LOG_ALWAYS_FATAL_IF(vaddr->svm_family != AF_VSOCK, "address is not a vsock");
if (cid != vaddr->svm_cid) {
- LOG(ERROR) << "Rejected vsock connection from CID " << vaddr->svm_cid;
+ ALOGE("Rejected vsock connection from CID %u", vaddr->svm_cid);
return false;
}
return true;
@@ -105,23 +111,16 @@ ARpcServer* ARpcServer_newVsock(AIBinder* service, unsigned int cid, unsigned in
return createObjectHandle<ARpcServer>(server);
}
-ARpcServer* ARpcServer_newInitUnixDomain(AIBinder* service, const char* name) {
+ARpcServer* ARpcServer_newBoundSocket(AIBinder* service, int socketFd) {
auto server = RpcServer::make();
- auto fd = unique_fd(android_get_control_socket(name));
+ auto fd = unique_fd(socketFd);
if (!fd.ok()) {
- LOG(ERROR) << "Failed to get fd for the socket:" << name;
+ ALOGE("Invalid socket fd %d", socketFd);
return nullptr;
}
- // Control socket fds are inherited from init, so they don't have O_CLOEXEC set.
- // But we don't want any child processes to inherit the socket we are running
- // the server on, so attempt to set the flag now.
- if (fcntl(fd, F_SETFD, FD_CLOEXEC) != 0) {
- LOG(WARNING) << "Failed to set CLOEXEC on control socket with name " << name
- << " error: " << errno;
- }
if (status_t status = server->setupRawSocketServer(std::move(fd)); status != OK) {
- LOG(ERROR) << "Failed to set up Unix Domain RPC server with name " << name
- << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up RPC server with fd %d error: %s", socketFd,
+ statusToString(status).c_str());
return nullptr;
}
server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -132,13 +131,13 @@ ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd
auto server = RpcServer::make();
auto fd = unique_fd(bootstrapFd);
if (!fd.ok()) {
- LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+ ALOGE("Invalid bootstrap fd %d", bootstrapFd);
return nullptr;
}
if (status_t status = server->setupUnixDomainSocketBootstrapServer(std::move(fd));
status != OK) {
- LOG(ERROR) << "Failed to set up Unix Domain RPC server with bootstrap fd " << bootstrapFd
- << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up Unix Domain RPC server with bootstrap fd %d error: %s", bootstrapFd,
+ statusToString(status).c_str());
return nullptr;
}
server->setRootObject(AIBinder_toPlatformBinder(service));
@@ -148,13 +147,14 @@ ARpcServer* ARpcServer_newUnixDomainBootstrap(AIBinder* service, int bootstrapFd
ARpcServer* ARpcServer_newInet(AIBinder* service, const char* address, unsigned int port) {
auto server = RpcServer::make();
if (status_t status = server->setupInetServer(address, port, nullptr); status != OK) {
- LOG(ERROR) << "Failed to set up inet RPC server with address " << address << " and port "
- << port << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up inet RPC server with address %s and port %u error: %s", address,
+ port, statusToString(status).c_str());
return nullptr;
}
server->setRootObject(AIBinder_toPlatformBinder(service));
return createObjectHandle<ARpcServer>(server);
}
+#endif // __TRUSTY__
void ARpcServer_setSupportedFileDescriptorTransportModes(
ARpcServer* handle, const ARpcSession_FileDescriptorTransportMode modes[],
@@ -167,6 +167,10 @@ void ARpcServer_setSupportedFileDescriptorTransportModes(
server->setSupportedFileDescriptorTransportModes(modevec);
}
+void ARpcServer_setMaxThreads(ARpcServer* handle, size_t threads) {
+ handleToStrongPointer<RpcServer>(handle)->setMaxThreads(threads);
+}
+
void ARpcServer_start(ARpcServer* handle) {
handleToStrongPointer<RpcServer>(handle)->start();
}
@@ -195,11 +199,12 @@ void ARpcSession_free(ARpcSession* handle) {
freeObjectHandle<RpcSession>(handle);
}
+#ifndef __TRUSTY__
AIBinder* ARpcSession_setupVsockClient(ARpcSession* handle, unsigned int cid, unsigned int port) {
auto session = handleToStrongPointer<RpcSession>(handle);
if (status_t status = session->setupVsockClient(cid, port); status != OK) {
- LOG(ERROR) << "Failed to set up vsock client with CID " << cid << " and port " << port
- << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up vsock client with CID %u and port %u error: %s", cid, port,
+ statusToString(status).c_str());
return nullptr;
}
return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -210,8 +215,8 @@ AIBinder* ARpcSession_setupUnixDomainClient(ARpcSession* handle, const char* nam
pathname = ANDROID_SOCKET_DIR "/" + pathname;
auto session = handleToStrongPointer<RpcSession>(handle);
if (status_t status = session->setupUnixDomainClient(pathname.c_str()); status != OK) {
- LOG(ERROR) << "Failed to set up Unix Domain RPC client with path: " << pathname
- << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up Unix Domain RPC client with path: %s error: %s", pathname.c_str(),
+ statusToString(status).c_str());
return nullptr;
}
return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -221,13 +226,13 @@ AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* handle, int bo
auto session = handleToStrongPointer<RpcSession>(handle);
auto fd = unique_fd(dup(bootstrapFd));
if (!fd.ok()) {
- LOG(ERROR) << "Invalid bootstrap fd " << bootstrapFd;
+ ALOGE("Invalid bootstrap fd %d", bootstrapFd);
return nullptr;
}
if (status_t status = session->setupUnixDomainSocketBootstrapClient(std::move(fd));
status != OK) {
- LOG(ERROR) << "Failed to set up Unix Domain RPC client with bootstrap fd: " << bootstrapFd
- << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up Unix Domain RPC client with bootstrap fd: %d error: %s",
+ bootstrapFd, statusToString(status).c_str());
return nullptr;
}
return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -236,19 +241,20 @@ AIBinder* ARpcSession_setupUnixDomainBootstrapClient(ARpcSession* handle, int bo
AIBinder* ARpcSession_setupInet(ARpcSession* handle, const char* address, unsigned int port) {
auto session = handleToStrongPointer<RpcSession>(handle);
if (status_t status = session->setupInetClient(address, port); status != OK) {
- LOG(ERROR) << "Failed to set up inet RPC client with address " << address << " and port "
- << port << " error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up inet RPC client with address %s and port %u error: %s", address,
+ port, statusToString(status).c_str());
return nullptr;
}
return AIBinder_fromPlatformBinder(session->getRootObject());
}
+#endif // __TRUSTY__
AIBinder* ARpcSession_setupPreconnectedClient(ARpcSession* handle, int (*requestFd)(void* param),
void* param) {
auto session = handleToStrongPointer<RpcSession>(handle);
auto request = [=] { return unique_fd{requestFd(param)}; };
if (status_t status = session->setupPreconnectedClient(unique_fd{}, request); status != OK) {
- LOG(ERROR) << "Failed to set up vsock client. error: " << statusToString(status).c_str();
+ ALOGE("Failed to set up preconnected client. error: %s", statusToString(status).c_str());
return nullptr;
}
return AIBinder_fromPlatformBinder(session->getRootObject());
@@ -265,8 +271,8 @@ void ARpcSession_setMaxIncomingThreads(ARpcSession* handle, size_t threads) {
session->setMaxIncomingThreads(threads);
}
-void ARpcSession_setMaxOutgoingThreads(ARpcSession* handle, size_t threads) {
+void ARpcSession_setMaxOutgoingConnections(ARpcSession* handle, size_t connections) {
auto session = handleToStrongPointer<RpcSession>(handle);
- session->setMaxOutgoingThreads(threads);
+ session->setMaxOutgoingConnections(connections);
}
}
diff --git a/libs/binder/libbinder_rpc_unstable.map.txt b/libs/binder/libbinder_rpc_unstable.map.txt
index 63679c28d0..50f7deb7d9 100644
--- a/libs/binder/libbinder_rpc_unstable.map.txt
+++ b/libs/binder/libbinder_rpc_unstable.map.txt
@@ -3,7 +3,7 @@ LIBBINDER_RPC_UNSTABLE_SHIM { # platform-only
ARpcServer_free;
ARpcServer_join;
ARpcServer_newInet;
- ARpcServer_newInitUnixDomain;
+ ARpcServer_newBoundSocket;
ARpcServer_newVsock;
ARpcServer_shutdown;
ARpcServer_start;
diff --git a/libs/binder/liblog_stub/Android.bp b/libs/binder/liblog_stub/Android.bp
new file mode 100644
index 0000000000..f2ca22fdb0
--- /dev/null
+++ b/libs/binder/liblog_stub/Android.bp
@@ -0,0 +1,44 @@
+// 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_library_headers {
+ name: "liblog_stub",
+ export_include_dirs: ["include"],
+
+ host_supported: true,
+ native_bridge_supported: true,
+ product_available: true,
+ recovery_available: true,
+ vendor_available: true,
+
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
+
+ visibility: [
+ "//frameworks/native/libs/binder:__subpackages__",
+ "//system/core/libutils/binder",
+ ],
+}
diff --git a/libs/binder/liblog_stub/include/android/log.h b/libs/binder/liblog_stub/include/android/log.h
new file mode 100644
index 0000000000..9dcd9262db
--- /dev/null
+++ b/libs/binder/liblog_stub/include/android/log.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
+
+extern "C" {
+
+/**
+ * Android log priority values, in increasing order of priority.
+ */
+typedef enum android_LogPriority {
+ /** For internal use only. */
+ ANDROID_LOG_UNKNOWN = 0,
+ /** The default priority, for internal use only. */
+ ANDROID_LOG_DEFAULT, /* only for SetMinPriority() */
+ /** Verbose logging. Should typically be disabled for a release apk. */
+ ANDROID_LOG_VERBOSE,
+ /** Debug logging. Should typically be disabled for a release apk. */
+ ANDROID_LOG_DEBUG,
+ /** Informational logging. Should typically be disabled for a release apk. */
+ ANDROID_LOG_INFO,
+ /** Warning logging. For use with recoverable failures. */
+ ANDROID_LOG_WARN,
+ /** Error logging. For use with unrecoverable failures. */
+ ANDROID_LOG_ERROR,
+ /** Fatal logging. For use when aborting. */
+ ANDROID_LOG_FATAL,
+ /** For internal use only. */
+ ANDROID_LOG_SILENT, /* only for SetMinPriority(); must be last */
+} android_LogPriority;
+
+typedef void (*__android_logger_function)(const struct __android_log_message* log_message);
+inline void __android_log_set_logger(__android_logger_function) {}
+inline void __android_log_stderr_logger(const struct __android_log_message*) {}
+
+} // extern "C"
diff --git a/libs/binder/liblog_stub/include/log/log.h b/libs/binder/liblog_stub/include/log/log.h
new file mode 100644
index 0000000000..dad0020ace
--- /dev/null
+++ b/libs/binder/liblog_stub/include/log/log.h
@@ -0,0 +1,93 @@
+/*
+ * 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 <cstdio>
+#include <cstdlib>
+
+#include <android/log.h>
+
+extern "C" {
+
+#ifndef ANDROID_LOG_STUB_MIN_PRIORITY
+#define ANDROID_LOG_STUB_MIN_PRIORITY ANDROID_LOG_INFO
+#endif
+
+#ifndef LOG_TAG
+#define LOG_TAG ""
+#endif
+
+constexpr bool __android_log_stub_is_loggable(android_LogPriority priority) {
+ return ANDROID_LOG_STUB_MIN_PRIORITY <= priority;
+}
+
+#ifdef ANDROID_LOG_STUB_WEAK_PRINT
+#define __ANDROID_LOG_STUB_IS_PRINT_PRESENT __android_log_print
+#define __ANDROID_LOG_STUB_PRINT_ATTR __attribute__((weak))
+#else
+#define __ANDROID_LOG_STUB_IS_PRINT_PRESENT true
+#define __ANDROID_LOG_STUB_PRINT_ATTR
+#endif
+
+int __android_log_print(int prio, const char* tag, const char* fmt, ...)
+ __attribute__((format(printf, 3, 4))) __ANDROID_LOG_STUB_PRINT_ATTR;
+
+#define IF_ALOG(priority, tag) \
+ if (__android_log_stub_is_loggable(ANDROID_##priority) && __ANDROID_LOG_STUB_IS_PRINT_PRESENT)
+#define IF_ALOGV() IF_ALOG(LOG_VERBOSE, LOG_TAG)
+#define IF_ALOGD() IF_ALOG(LOG_DEBUG, LOG_TAG)
+#define IF_ALOGI() IF_ALOG(LOG_INFO, LOG_TAG)
+#define IF_ALOGW() IF_ALOG(LOG_WARN, LOG_TAG)
+#define IF_ALOGE() IF_ALOG(LOG_ERROR, LOG_TAG)
+
+#define ALOG(priority, tag, fmt, ...) \
+ do { \
+ if (false)[[/*VERY*/ unlikely]] { /* ignore unused __VA_ARGS__ */ \
+ std::fprintf(stderr, fmt __VA_OPT__(, ) __VA_ARGS__); \
+ } \
+ IF_ALOG(priority, tag) { \
+ __android_log_print(ANDROID_##priority, tag, "%s: " fmt "\n", \
+ (tag)__VA_OPT__(, ) __VA_ARGS__); \
+ } \
+ if constexpr (ANDROID_##priority == ANDROID_LOG_FATAL) std::abort(); \
+ } while (false)
+#define ALOGV(...) ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ALOGD(...) ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI(...) ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW(...) ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE(...) ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOG_FATAL(...) ALOG(LOG_FATAL, LOG_TAG, __VA_ARGS__)
+#define LOG_ALWAYS_FATAL LOG_FATAL
+
+#define ALOG_IF(cond, priority, tag, ...) \
+ if (cond) [[unlikely]] \
+ ALOG(priority, tag, #cond ": " __VA_ARGS__)
+#define ALOGV_IF(cond, ...) ALOG_IF(cond, LOG_VERBOSE, LOG_TAG, __VA_ARGS__)
+#define ALOGD_IF(cond, ...) ALOG_IF(cond, LOG_DEBUG, LOG_TAG, __VA_ARGS__)
+#define ALOGI_IF(cond, ...) ALOG_IF(cond, LOG_INFO, LOG_TAG, __VA_ARGS__)
+#define ALOGW_IF(cond, ...) ALOG_IF(cond, LOG_WARN, LOG_TAG, __VA_ARGS__)
+#define ALOGE_IF(cond, ...) ALOG_IF(cond, LOG_ERROR, LOG_TAG, __VA_ARGS__)
+#define LOG_FATAL_IF(cond, ...) ALOG_IF(cond, LOG_FATAL, LOG_TAG, __VA_ARGS__)
+#define LOG_ALWAYS_FATAL_IF LOG_FATAL_IF
+#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
+
+inline int android_errorWriteLog(int tag, const char* subTag) {
+ ALOGE("android_errorWriteLog(%x, %s)", tag, subTag);
+ return 0;
+}
+
+} // extern "C"
diff --git a/libs/binder/ndk/.clang-format b/libs/binder/ndk/.clang-format
index 9a9d936f15..60774143ee 100644
--- a/libs/binder/ndk/.clang-format
+++ b/libs/binder/ndk/.clang-format
@@ -2,9 +2,7 @@ BasedOnStyle: Google
ColumnLimit: 100
IndentWidth: 4
ContinuationIndentWidth: 8
-PointerAlignment: Left
TabWidth: 4
AllowShortFunctionsOnASingleLine: Inline
PointerAlignment: Left
-TabWidth: 4
UseTab: Never
diff --git a/libs/binder/ndk/Android.bp b/libs/binder/ndk/Android.bp
index 58ed4188e2..9a2d14af32 100644
--- a/libs/binder/ndk/Android.bp
+++ b/libs/binder/ndk/Android.bp
@@ -40,6 +40,7 @@ cc_library {
llndk: {
symbol_file: "libbinder_ndk.map.txt",
+ export_llndk_headers: ["libvendorsupport_llndk_headers"],
},
export_include_dirs: [
@@ -49,8 +50,10 @@ cc_library {
],
cflags: [
+ "-DBINDER_WITH_KERNEL_IPC",
"-Wall",
"-Wextra",
+ "-Wextra-semi",
"-Werror",
],
@@ -60,6 +63,7 @@ cc_library {
"libbinder.cpp",
"parcel.cpp",
"parcel_jni.cpp",
+ "persistable_bundle.cpp",
"process.cpp",
"stability.cpp",
"status.cpp",
@@ -78,9 +82,11 @@ cc_library {
],
header_libs: [
+ "libvendorsupport_llndk_headers",
"jni_headers",
],
export_header_lib_headers: [
+ "libvendorsupport_llndk_headers",
"jni_headers",
],
@@ -138,6 +144,47 @@ cc_library {
"performance*",
"portability*",
],
+ afdo: true,
+}
+
+cc_library {
+ name: "libbinder_ndk_on_trusty_mock",
+ defaults: [
+ "trusty_mock_defaults",
+ ],
+
+ export_include_dirs: [
+ "include_cpp",
+ "include_ndk",
+ "include_platform",
+ ],
+
+ srcs: [
+ "ibinder.cpp",
+ "libbinder.cpp",
+ "parcel.cpp",
+ "stability.cpp",
+ "status.cpp",
+ ],
+
+ shared_libs: [
+ "libbinder_on_trusty_mock",
+ ],
+
+ header_libs: [
+ "libbinder_trusty_ndk_headers",
+ ],
+ export_header_lib_headers: [
+ "libbinder_trusty_ndk_headers",
+ ],
+
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+
+ visibility: ["//frameworks/native/libs/binder:__subpackages__"],
}
cc_library_headers {
diff --git a/libs/binder/ndk/ibinder.cpp b/libs/binder/ndk/ibinder.cpp
index d0de7b96b5..cf5942059a 100644
--- a/libs/binder/ndk/ibinder.cpp
+++ b/libs/binder/ndk/ibinder.cpp
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-#include <android-base/logging.h>
#include <android/binder_ibinder.h>
#include <android/binder_ibinder_platform.h>
#include <android/binder_stability.h>
#include <android/binder_status.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
+#if __has_include(<private/android_filesystem_config.h>)
#include <private/android_filesystem_config.h>
+#endif
+#include "../BuildFlags.h"
#include "ibinder_internal.h"
#include "parcel_internal.h"
#include "status_internal.h"
@@ -43,11 +45,13 @@ namespace ABBinderTag {
static const void* kId = "ABBinder";
static void* kValue = static_cast<void*>(new bool{true});
-void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/){/* do nothing */};
+void clean(const void* /*id*/, void* /*obj*/, void* /*cookie*/) {
+ /* do nothing */
+}
static void attach(const sp<IBinder>& binder) {
- // can only attach once
- CHECK_EQ(nullptr, binder->attachObject(kId, kValue, nullptr /*cookie*/, clean));
+ auto alreadyAttached = binder->attachObject(kId, kValue, nullptr /*cookie*/, clean);
+ LOG_ALWAYS_FATAL_IF(alreadyAttached != nullptr, "can only attach once");
}
static bool has(const sp<IBinder>& binder) {
return binder != nullptr && binder->findObject(kId) == kValue;
@@ -63,12 +67,12 @@ struct Value {
};
void clean(const void* id, void* obj, void* cookie) {
// be weary of leaks!
- // LOG(INFO) << "Deleting an ABpBinder";
+ // ALOGI("Deleting an ABpBinder");
- CHECK(id == kId) << id << " " << obj << " " << cookie;
+ LOG_ALWAYS_FATAL_IF(id != kId, "%p %p %p", id, obj, cookie);
delete static_cast<Value*>(obj);
-};
+}
} // namespace ABpBinderTag
@@ -119,14 +123,13 @@ bool AIBinder::associateClass(const AIBinder_Class* clazz) {
if (mClazz != nullptr && !asABpBinder()) {
const String16& currentDescriptor = mClazz->getInterfaceDescriptor();
if (newDescriptor == currentDescriptor) {
- LOG(ERROR) << __func__ << ": Class descriptors '" << currentDescriptor
- << "' match during associateClass, but they are different class objects ("
- << clazz << " vs " << mClazz << "). Class descriptor collision?";
+ ALOGE("Class descriptors '%s' match during associateClass, but they are different class"
+ " objects (%p vs %p). Class descriptor collision?",
+ String8(currentDescriptor).c_str(), clazz, mClazz);
} else {
- LOG(ERROR) << __func__
- << ": Class cannot be associated on object which already has a class. "
- "Trying to associate to '"
- << newDescriptor << "' but already set to '" << currentDescriptor << "'.";
+ ALOGE("%s: Class cannot be associated on object which already has a class. "
+ "Trying to associate to '%s' but already set to '%s'.",
+ __func__, String8(newDescriptor).c_str(), String8(currentDescriptor).c_str());
}
// always a failure because we know mClazz != clazz
@@ -137,15 +140,14 @@ bool AIBinder::associateClass(const AIBinder_Class* clazz) {
// since it's an error condition. Do the comparison after we take the lock and
// check the pointer equality fast path. By always taking the lock, it's also
// more flake-proof. However, the check is not dependent on the lock.
- if (descriptor != newDescriptor) {
+ if (descriptor != newDescriptor && !(asABpBinder() && asABpBinder()->isServiceFuzzing())) {
if (getBinder()->isBinderAlive()) {
- LOG(ERROR) << __func__ << ": Expecting binder to have class '" << newDescriptor
- << "' but descriptor is actually '" << SanitizeString(descriptor) << "'.";
+ ALOGE("%s: Expecting binder to have class '%s' but descriptor is actually '%s'.",
+ __func__, String8(newDescriptor).c_str(), SanitizeString(descriptor).c_str());
} else {
// b/155793159
- LOG(ERROR) << __func__ << ": Cannot associate class '" << newDescriptor
- << "' to dead binder with cached descriptor '" << SanitizeString(descriptor)
- << "'.";
+ ALOGE("%s: Cannot associate class '%s' to dead binder with cached descriptor '%s'.",
+ __func__, String8(newDescriptor).c_str(), SanitizeString(descriptor).c_str());
}
return false;
}
@@ -162,7 +164,7 @@ bool AIBinder::associateClass(const AIBinder_Class* clazz) {
ABBinder::ABBinder(const AIBinder_Class* clazz, void* userData)
: AIBinder(clazz), BBinder(), mUserData(userData) {
- CHECK(clazz != nullptr);
+ LOG_ALWAYS_FATAL_IF(clazz == nullptr, "clazz == nullptr");
}
ABBinder::~ABBinder() {
getClass()->onDestroy(mUserData);
@@ -182,7 +184,7 @@ status_t ABBinder::dump(int fd, const ::android::Vector<String16>& args) {
// technically UINT32_MAX would be okay here, but INT32_MAX is expected since this may be
// null in Java
if (args.size() > INT32_MAX) {
- LOG(ERROR) << "ABBinder::dump received too many arguments: " << args.size();
+ ALOGE("ABBinder::dump received too many arguments: %zu", args.size());
return STATUS_BAD_VALUE;
}
@@ -212,6 +214,12 @@ status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parce
binder_status_t status = getClass()->onTransact(this, code, &in, &out);
return PruneStatusT(status);
} else if (code == SHELL_COMMAND_TRANSACTION && getClass()->handleShellCommand != nullptr) {
+ if constexpr (!android::kEnableKernelIpc) {
+ // Non-IPC builds do not have getCallingUid(),
+ // so we have no way of authenticating the caller
+ return STATUS_PERMISSION_DENIED;
+ }
+
int in = data.readFileDescriptor();
int out = data.readFileDescriptor();
int err = data.readFileDescriptor();
@@ -229,7 +237,11 @@ status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parce
// Shell commands should only be callable by ADB.
uid_t uid = AIBinder_getCallingUid();
- if (uid != AID_ROOT && uid != AID_SHELL) {
+ if (uid != 0 /* root */
+#ifdef AID_SHELL
+ && uid != AID_SHELL
+#endif
+ ) {
if (resultReceiver != nullptr) {
resultReceiver->send(-1);
}
@@ -257,7 +269,7 @@ status_t ABBinder::onTransact(transaction_code_t code, const Parcel& data, Parce
ABpBinder::ABpBinder(const ::android::sp<::android::IBinder>& binder)
: AIBinder(nullptr /*clazz*/), mRemote(binder) {
- CHECK(binder != nullptr);
+ LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
}
ABpBinder::~ABpBinder() {}
@@ -367,27 +379,27 @@ AIBinder_Class* AIBinder_Class_define(const char* interfaceDescriptor,
}
void AIBinder_Class_setOnDump(AIBinder_Class* clazz, AIBinder_onDump onDump) {
- CHECK(clazz != nullptr) << "setOnDump requires non-null clazz";
+ LOG_ALWAYS_FATAL_IF(clazz == nullptr, "setOnDump requires non-null clazz");
// this is required to be called before instances are instantiated
clazz->onDump = onDump;
}
void AIBinder_Class_disableInterfaceTokenHeader(AIBinder_Class* clazz) {
- CHECK(clazz != nullptr) << "disableInterfaceTokenHeader requires non-null clazz";
+ LOG_ALWAYS_FATAL_IF(clazz == nullptr, "disableInterfaceTokenHeader requires non-null clazz");
clazz->writeHeader = false;
}
void AIBinder_Class_setHandleShellCommand(AIBinder_Class* clazz,
AIBinder_handleShellCommand handleShellCommand) {
- CHECK(clazz != nullptr) << "setHandleShellCommand requires non-null clazz";
+ LOG_ALWAYS_FATAL_IF(clazz == nullptr, "setHandleShellCommand requires non-null clazz");
clazz->handleShellCommand = handleShellCommand;
}
const char* AIBinder_Class_getDescriptor(const AIBinder_Class* clazz) {
- CHECK(clazz != nullptr) << "getDescriptor requires non-null clazz";
+ LOG_ALWAYS_FATAL_IF(clazz == nullptr, "getDescriptor requires non-null clazz");
return clazz->getInterfaceDescriptorUtf8();
}
@@ -399,8 +411,8 @@ AIBinder_DeathRecipient::TransferDeathRecipient::~TransferDeathRecipient() {
}
void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinder>& who) {
- CHECK(who == mWho) << who.unsafe_get() << "(" << who.get_refs() << ") vs " << mWho.unsafe_get()
- << " (" << mWho.get_refs() << ")";
+ LOG_ALWAYS_FATAL_IF(who != mWho, "%p (%p) vs %p (%p)", who.unsafe_get(), who.get_refs(),
+ mWho.unsafe_get(), mWho.get_refs());
mOnDied(mCookie);
@@ -411,7 +423,7 @@ void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinde
if (recipient != nullptr && strongWho != nullptr) {
status_t result = recipient->unlinkToDeath(strongWho, mCookie);
if (result != ::android::DEAD_OBJECT) {
- LOG(WARNING) << "Unlinking to dead binder resulted in: " << result;
+ ALOGW("Unlinking to dead binder resulted in: %d", result);
}
}
@@ -420,7 +432,7 @@ void AIBinder_DeathRecipient::TransferDeathRecipient::binderDied(const wp<IBinde
AIBinder_DeathRecipient::AIBinder_DeathRecipient(AIBinder_DeathRecipient_onBinderDied onDied)
: mOnDied(onDied), mOnUnlinked(nullptr) {
- CHECK(onDied != nullptr);
+ LOG_ALWAYS_FATAL_IF(onDied == nullptr, "onDied == nullptr");
}
void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
@@ -432,7 +444,7 @@ void AIBinder_DeathRecipient::pruneDeadTransferEntriesLocked() {
}
binder_status_t AIBinder_DeathRecipient::linkToDeath(const sp<IBinder>& binder, void* cookie) {
- CHECK(binder != nullptr);
+ LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
@@ -453,7 +465,7 @@ binder_status_t AIBinder_DeathRecipient::linkToDeath(const sp<IBinder>& binder,
}
binder_status_t AIBinder_DeathRecipient::unlinkToDeath(const sp<IBinder>& binder, void* cookie) {
- CHECK(binder != nullptr);
+ LOG_ALWAYS_FATAL_IF(binder == nullptr, "binder == nullptr");
std::lock_guard<std::mutex> l(mDeathRecipientsMutex);
@@ -465,9 +477,8 @@ binder_status_t AIBinder_DeathRecipient::unlinkToDeath(const sp<IBinder>& binder
status_t status = binder->unlinkToDeath(recipient, cookie, 0 /*flags*/);
if (status != ::android::OK) {
- LOG(ERROR) << __func__
- << ": removed reference to death recipient but unlink failed: "
- << statusToString(status);
+ ALOGE("%s: removed reference to death recipient but unlink failed: %s", __func__,
+ statusToString(status).c_str());
}
return PruneStatusT(status);
}
@@ -484,7 +495,7 @@ void AIBinder_DeathRecipient::setOnUnlinked(AIBinder_DeathRecipient_onBinderUnli
AIBinder* AIBinder_new(const AIBinder_Class* clazz, void* args) {
if (clazz == nullptr) {
- LOG(ERROR) << __func__ << ": Must provide class to construct local binder.";
+ ALOGE("%s: Must provide class to construct local binder.", __func__);
return nullptr;
}
@@ -548,8 +559,7 @@ binder_status_t AIBinder_dump(AIBinder* binder, int fd, const char** args, uint3
binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
void* cookie) {
if (binder == nullptr || recipient == nullptr) {
- LOG(ERROR) << __func__ << ": Must provide binder (" << binder << ") and recipient ("
- << recipient << ")";
+ ALOGE("%s: Must provide binder (%p) and recipient (%p)", __func__, binder, recipient);
return STATUS_UNEXPECTED_NULL;
}
@@ -560,8 +570,7 @@ binder_status_t AIBinder_linkToDeath(AIBinder* binder, AIBinder_DeathRecipient*
binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient* recipient,
void* cookie) {
if (binder == nullptr || recipient == nullptr) {
- LOG(ERROR) << __func__ << ": Must provide binder (" << binder << ") and recipient ("
- << recipient << ")";
+ ALOGE("%s: Must provide binder (%p) and recipient (%p)", __func__, binder, recipient);
return STATUS_UNEXPECTED_NULL;
}
@@ -569,6 +578,7 @@ binder_status_t AIBinder_unlinkToDeath(AIBinder* binder, AIBinder_DeathRecipient
return recipient->unlinkToDeath(binder->getBinder(), cookie);
}
+#ifdef BINDER_WITH_KERNEL_IPC
uid_t AIBinder_getCallingUid() {
return ::android::IPCThreadState::self()->getCallingUid();
}
@@ -580,6 +590,7 @@ pid_t AIBinder_getCallingPid() {
bool AIBinder_isHandlingTransaction() {
return ::android::IPCThreadState::self()->getServingStackPointer() != nullptr;
}
+#endif
void AIBinder_incStrong(AIBinder* binder) {
if (binder == nullptr) {
@@ -590,7 +601,7 @@ void AIBinder_incStrong(AIBinder* binder) {
}
void AIBinder_decStrong(AIBinder* binder) {
if (binder == nullptr) {
- LOG(ERROR) << __func__ << ": on null binder";
+ ALOGE("%s: on null binder", __func__);
return;
}
@@ -598,7 +609,7 @@ void AIBinder_decStrong(AIBinder* binder) {
}
int32_t AIBinder_debugGetRefCount(AIBinder* binder) {
if (binder == nullptr) {
- LOG(ERROR) << __func__ << ": on null binder";
+ ALOGE("%s: on null binder", __func__);
return -1;
}
@@ -636,15 +647,14 @@ void* AIBinder_getUserData(AIBinder* binder) {
binder_status_t AIBinder_prepareTransaction(AIBinder* binder, AParcel** in) {
if (binder == nullptr || in == nullptr) {
- LOG(ERROR) << __func__ << ": requires non-null parameters binder (" << binder
- << ") and in (" << in << ").";
+ ALOGE("%s: requires non-null parameters binder (%p) and in (%p).", __func__, binder, in);
return STATUS_UNEXPECTED_NULL;
}
const AIBinder_Class* clazz = binder->getClass();
if (clazz == nullptr) {
- LOG(ERROR) << __func__
- << ": Class must be defined for a remote binder transaction. See "
- "AIBinder_associateClass.";
+ ALOGE("%s: Class must be defined for a remote binder transaction. See "
+ "AIBinder_associateClass.",
+ __func__);
return STATUS_INVALID_OPERATION;
}
@@ -677,7 +687,7 @@ static void DestroyParcel(AParcel** parcel) {
binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, AParcel** in,
AParcel** out, binder_flags_t flags) {
if (in == nullptr) {
- LOG(ERROR) << __func__ << ": requires non-null in parameter";
+ ALOGE("%s: requires non-null in parameter", __func__);
return STATUS_UNEXPECTED_NULL;
}
@@ -687,27 +697,26 @@ binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, APa
AutoParcelDestroyer forIn(in, DestroyParcel);
if (!isUserCommand(code)) {
- LOG(ERROR) << __func__
- << ": Only user-defined transactions can be made from the NDK, but requested: "
- << code;
+ ALOGE("%s: Only user-defined transactions can be made from the NDK, but requested: %d",
+ __func__, code);
return STATUS_UNKNOWN_TRANSACTION;
}
constexpr binder_flags_t kAllFlags = FLAG_PRIVATE_VENDOR | FLAG_ONEWAY | FLAG_CLEAR_BUF;
if ((flags & ~kAllFlags) != 0) {
- LOG(ERROR) << __func__ << ": Unrecognized flags sent: " << flags;
+ ALOGE("%s: Unrecognized flags sent: %d", __func__, flags);
return STATUS_BAD_VALUE;
}
if (binder == nullptr || *in == nullptr || out == nullptr) {
- LOG(ERROR) << __func__ << ": requires non-null parameters binder (" << binder << "), in ("
- << in << "), and out (" << out << ").";
+ ALOGE("%s: requires non-null parameters binder (%p), in (%p), and out (%p).", __func__,
+ binder, in, out);
return STATUS_UNEXPECTED_NULL;
}
if ((*in)->getBinder() != binder) {
- LOG(ERROR) << __func__ << ": parcel is associated with binder object " << binder
- << " but called with " << (*in)->getBinder();
+ ALOGE("%s: parcel is associated with binder object %p but called with %p", __func__, binder,
+ (*in)->getBinder());
return STATUS_BAD_VALUE;
}
@@ -727,7 +736,7 @@ binder_status_t AIBinder_transact(AIBinder* binder, transaction_code_t code, APa
AIBinder_DeathRecipient* AIBinder_DeathRecipient_new(
AIBinder_DeathRecipient_onBinderDied onBinderDied) {
if (onBinderDied == nullptr) {
- LOG(ERROR) << __func__ << ": requires non-null onBinderDied parameter.";
+ ALOGE("%s: requires non-null onBinderDied parameter.", __func__);
return nullptr;
}
auto ret = new AIBinder_DeathRecipient(onBinderDied);
@@ -793,16 +802,17 @@ binder_status_t AIBinder_setExtension(AIBinder* binder, AIBinder* ext) {
void AIBinder_setRequestingSid(AIBinder* binder, bool requestingSid) {
ABBinder* localBinder = binder->asABBinder();
- if (localBinder == nullptr) {
- LOG(FATAL) << "AIBinder_setRequestingSid must be called on a local binder";
- }
+ LOG_ALWAYS_FATAL_IF(localBinder == nullptr,
+ "AIBinder_setRequestingSid must be called on a local binder");
localBinder->setRequestingSid(requestingSid);
}
+#ifdef BINDER_WITH_KERNEL_IPC
const char* AIBinder_getCallingSid() {
return ::android::IPCThreadState::self()->getCallingSid();
}
+#endif
void AIBinder_setMinSchedulerPolicy(AIBinder* binder, int policy, int priority) {
binder->asABBinder()->setMinSchedulerPolicy(policy, priority);
@@ -810,9 +820,8 @@ void AIBinder_setMinSchedulerPolicy(AIBinder* binder, int policy, int priority)
void AIBinder_setInheritRt(AIBinder* binder, bool inheritRt) {
ABBinder* localBinder = binder->asABBinder();
- if (localBinder == nullptr) {
- LOG(FATAL) << "AIBinder_setInheritRt must be called on a local binder";
- }
+ LOG_ALWAYS_FATAL_IF(localBinder == nullptr,
+ "AIBinder_setInheritRt must be called on a local binder");
localBinder->setInheritRt(inheritRt);
}
diff --git a/libs/binder/ndk/ibinder_internal.h b/libs/binder/ndk/ibinder_internal.h
index 67bb092f0f..9d5368f674 100644
--- a/libs/binder/ndk/ibinder_internal.h
+++ b/libs/binder/ndk/ibinder_internal.h
@@ -104,10 +104,14 @@ struct ABpBinder : public AIBinder {
::android::sp<::android::IBinder> getBinder() override { return mRemote; }
ABpBinder* asABpBinder() override { return this; }
+ bool isServiceFuzzing() const { return mServiceFuzzing; }
+ void setServiceFuzzing() { mServiceFuzzing = true; }
+
private:
friend android::sp<ABpBinder>;
explicit ABpBinder(const ::android::sp<::android::IBinder>& binder);
::android::sp<::android::IBinder> mRemote;
+ bool mServiceFuzzing = false;
};
struct AIBinder_Class {
diff --git a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
index d6937c2c52..18769b1454 100644
--- a/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_auto_utils.h
@@ -31,6 +31,7 @@
#include <android/binder_parcel.h>
#include <android/binder_status.h>
#include <assert.h>
+#include <string.h>
#include <unistd.h>
#include <cstddef>
@@ -115,17 +116,29 @@ class SpAIBinder {
*/
AIBinder** getR() { return &mBinder; }
- bool operator!=(const SpAIBinder& rhs) const { return get() != rhs.get(); }
- bool operator<(const SpAIBinder& rhs) const { return get() < rhs.get(); }
- bool operator<=(const SpAIBinder& rhs) const { return get() <= rhs.get(); }
- bool operator==(const SpAIBinder& rhs) const { return get() == rhs.get(); }
- bool operator>(const SpAIBinder& rhs) const { return get() > rhs.get(); }
- bool operator>=(const SpAIBinder& rhs) const { return get() >= rhs.get(); }
-
private:
AIBinder* mBinder = nullptr;
};
+#define SP_AIBINDER_COMPARE(_op_) \
+ static inline bool operator _op_(const SpAIBinder& lhs, const SpAIBinder& rhs) { \
+ return lhs.get() _op_ rhs.get(); \
+ } \
+ static inline bool operator _op_(const SpAIBinder& lhs, const AIBinder* rhs) { \
+ return lhs.get() _op_ rhs; \
+ } \
+ static inline bool operator _op_(const AIBinder* lhs, const SpAIBinder& rhs) { \
+ return lhs _op_ rhs.get(); \
+ }
+
+SP_AIBINDER_COMPARE(!=)
+SP_AIBINDER_COMPARE(<)
+SP_AIBINDER_COMPARE(<=)
+SP_AIBINDER_COMPARE(==)
+SP_AIBINDER_COMPARE(>)
+SP_AIBINDER_COMPARE(>=)
+#undef SP_AIBINDER_COMPARE
+
namespace impl {
/**
diff --git a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
index 9949de2aac..62738041ba 100644
--- a/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
+++ b/libs/binder/ndk/include_cpp/android/binder_interface_utils.h
@@ -138,6 +138,8 @@ class ICInterface : public SharedRefBase {
/**
* Dumps information about the interface. By default, dumps nothing.
+ *
+ * This method is not given ownership of the FD.
*/
virtual inline binder_status_t dump(int fd, const char** args, uint32_t numArgs);
diff --git a/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
new file mode 100644
index 0000000000..d570eab976
--- /dev/null
+++ b/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
@@ -0,0 +1,528 @@
+/*
+ * 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 <android/binder_parcel.h>
+#include <android/persistable_bundle.h>
+#include <sys/cdefs.h>
+
+#include <set>
+#include <sstream>
+
+// Include llndk-versioning.h only for vendor build as it is not available for NDK headers.
+#if defined(__ANDROID_VENDOR__)
+#include <android/llndk-versioning.h>
+#else // __ANDROID_VENDOR__
+#if defined(API_LEVEL_AT_LEAST)
+// Redefine API_LEVEL_AT_LEAST here to replace the version to __ANDROID_API_FUTURE__ as a workaround
+#undef API_LEVEL_AT_LEAST
+#endif
+// TODO(b/322384429) switch this __ANDROID_API_FUTURE__ to sdk_api_level when V is finalized
+#define API_LEVEL_AT_LEAST(sdk_api_level, vendor_api_level) \
+ (__builtin_available(android __ANDROID_API_FUTURE__, *))
+#endif // __ANDROID_VENDOR__
+
+namespace aidl::android::os {
+
+/**
+ * Wrapper class that enables interop with AIDL NDK generation
+ * Takes ownership of the APersistableBundle* given to it in reset() and will automatically
+ * destroy it in the destructor, similar to a smart pointer container
+ */
+class PersistableBundle {
+ public:
+ PersistableBundle() noexcept {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ mPBundle = APersistableBundle_new();
+ }
+ }
+ // takes ownership of the APersistableBundle*
+ PersistableBundle(APersistableBundle* _Nonnull bundle) noexcept : mPBundle(bundle) {}
+ // takes ownership of the APersistableBundle*
+ PersistableBundle(PersistableBundle&& other) noexcept : mPBundle(other.release()) {}
+ // duplicates, does not take ownership of the APersistableBundle*
+ PersistableBundle(const PersistableBundle& other) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ mPBundle = APersistableBundle_dup(other.mPBundle);
+ }
+ }
+ // duplicates, does not take ownership of the APersistableBundle*
+ PersistableBundle& operator=(const PersistableBundle& other) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ mPBundle = APersistableBundle_dup(other.mPBundle);
+ }
+ return *this;
+ }
+
+ ~PersistableBundle() { reset(); }
+
+ binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
+ reset();
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_readFromParcel(parcel, &mPBundle);
+ } else {
+ return STATUS_INVALID_OPERATION;
+ }
+ }
+
+ binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
+ if (!mPBundle) {
+ return STATUS_BAD_VALUE;
+ }
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_writeToParcel(mPBundle, parcel);
+ } else {
+ return STATUS_INVALID_OPERATION;
+ }
+ }
+
+ /**
+ * Destroys any currently owned APersistableBundle* and takes ownership of the given
+ * APersistableBundle*
+ *
+ * @param pBundle The APersistableBundle to take ownership of
+ */
+ void reset(APersistableBundle* _Nullable pBundle = nullptr) noexcept {
+ if (mPBundle) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_delete(mPBundle);
+ }
+ mPBundle = nullptr;
+ }
+ mPBundle = pBundle;
+ }
+
+ /**
+ * Check the actual contents of the bundle for equality. This is typically
+ * what should be used to check for equality.
+ */
+ bool deepEquals(const PersistableBundle& rhs) const {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_isEqual(get(), rhs.get());
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * NOTE: This does NOT check the contents of the PersistableBundle. This is
+ * implemented for ordering. Use deepEquals() to check for equality between
+ * two different PersistableBundle objects.
+ */
+ inline bool operator==(const PersistableBundle& rhs) const { return get() == rhs.get(); }
+ inline bool operator!=(const PersistableBundle& rhs) const { return get() != rhs.get(); }
+
+ inline bool operator<(const PersistableBundle& rhs) const { return get() < rhs.get(); }
+ inline bool operator>(const PersistableBundle& rhs) const { return get() > rhs.get(); }
+ inline bool operator>=(const PersistableBundle& rhs) const { return !(*this < rhs); }
+ inline bool operator<=(const PersistableBundle& rhs) const { return !(*this > rhs); }
+
+ PersistableBundle& operator=(PersistableBundle&& other) noexcept {
+ reset(other.release());
+ return *this;
+ }
+
+ /**
+ * Stops managing any contained APersistableBundle*, returning it to the caller. Ownership
+ * is released.
+ * @return APersistableBundle* or null if this was empty
+ */
+ [[nodiscard]] APersistableBundle* _Nullable release() noexcept {
+ APersistableBundle* _Nullable ret = mPBundle;
+ mPBundle = nullptr;
+ return ret;
+ }
+
+ inline std::string toString() const {
+ if (!mPBundle) {
+ return "<PersistableBundle: null>";
+ } else if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ std::ostringstream os;
+ os << "<PersistableBundle: ";
+ os << "size: " << std::to_string(APersistableBundle_size(mPBundle));
+ os << " >";
+ return os.str();
+ }
+ return "<PersistableBundle (unknown)>";
+ }
+
+ int32_t size() const {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_size(mPBundle);
+ } else {
+ return 0;
+ }
+ }
+
+ int32_t erase(const std::string& key) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_erase(mPBundle, key.c_str());
+ } else {
+ return 0;
+ }
+ }
+
+ void putBoolean(const std::string& key, bool val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_putBoolean(mPBundle, key.c_str(), val);
+ }
+ }
+
+ void putInt(const std::string& key, int32_t val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_putInt(mPBundle, key.c_str(), val);
+ }
+ }
+
+ void putLong(const std::string& key, int64_t val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_putLong(mPBundle, key.c_str(), val);
+ }
+ }
+
+ void putDouble(const std::string& key, double val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_putDouble(mPBundle, key.c_str(), val);
+ }
+ }
+
+ void putString(const std::string& key, const std::string& val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_putString(mPBundle, key.c_str(), val.c_str());
+ }
+ }
+
+ void putBooleanVector(const std::string& key, const std::vector<bool>& vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ // std::vector<bool> has no ::data().
+ int32_t num = vec.size();
+ if (num > 0) {
+ bool* newVec = (bool*)malloc(num * sizeof(bool));
+ if (newVec) {
+ for (int32_t i = 0; i < num; i++) {
+ newVec[i] = vec[i];
+ }
+ APersistableBundle_putBooleanVector(mPBundle, key.c_str(), newVec, num);
+ free(newVec);
+ }
+ }
+ }
+ }
+
+ void putIntVector(const std::string& key, const std::vector<int32_t>& vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ int32_t num = vec.size();
+ if (num > 0) {
+ APersistableBundle_putIntVector(mPBundle, key.c_str(), vec.data(), num);
+ }
+ }
+ }
+ void putLongVector(const std::string& key, const std::vector<int64_t>& vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ int32_t num = vec.size();
+ if (num > 0) {
+ APersistableBundle_putLongVector(mPBundle, key.c_str(), vec.data(), num);
+ }
+ }
+ }
+ void putDoubleVector(const std::string& key, const std::vector<double>& vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ int32_t num = vec.size();
+ if (num > 0) {
+ APersistableBundle_putDoubleVector(mPBundle, key.c_str(), vec.data(), num);
+ }
+ }
+ }
+ void putStringVector(const std::string& key, const std::vector<std::string>& vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ int32_t num = vec.size();
+ if (num > 0) {
+ char** inVec = (char**)malloc(num * sizeof(char*));
+ if (inVec) {
+ for (int32_t i = 0; i < num; i++) {
+ inVec[i] = strdup(vec[i].c_str());
+ }
+ APersistableBundle_putStringVector(mPBundle, key.c_str(), inVec, num);
+ free(inVec);
+ }
+ }
+ }
+ }
+ void putPersistableBundle(const std::string& key, const PersistableBundle& pBundle) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle_putPersistableBundle(mPBundle, key.c_str(), pBundle.mPBundle);
+ }
+ }
+
+ bool getBoolean(const std::string& key, bool* _Nonnull val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_getBoolean(mPBundle, key.c_str(), val);
+ } else {
+ return false;
+ }
+ }
+
+ bool getInt(const std::string& key, int32_t* _Nonnull val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_getInt(mPBundle, key.c_str(), val);
+ } else {
+ return false;
+ }
+ }
+
+ bool getLong(const std::string& key, int64_t* _Nonnull val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_getLong(mPBundle, key.c_str(), val);
+ } else {
+ return false;
+ }
+ }
+
+ bool getDouble(const std::string& key, double* _Nonnull val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return APersistableBundle_getDouble(mPBundle, key.c_str(), val);
+ } else {
+ return false;
+ }
+ }
+
+ static char* _Nullable stringAllocator(int32_t bufferSizeBytes, void* _Nullable) {
+ return (char*)malloc(bufferSizeBytes);
+ }
+
+ bool getString(const std::string& key, std::string* _Nonnull val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ char* outString = nullptr;
+ bool ret = APersistableBundle_getString(mPBundle, key.c_str(), &outString,
+ &stringAllocator, nullptr);
+ if (ret && outString) {
+ *val = std::string(outString);
+ }
+ return ret;
+ } else {
+ return false;
+ }
+ }
+
+ template <typename T>
+ bool getVecInternal(int32_t (*_Nonnull getVec)(const APersistableBundle* _Nonnull,
+ const char* _Nonnull, T* _Nullable, int32_t),
+ const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ std::vector<T>* _Nonnull vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ int32_t bytes = 0;
+ // call first with nullptr to get required size in bytes
+ bytes = getVec(pBundle, key, nullptr, 0);
+ if (bytes > 0) {
+ T* newVec = (T*)malloc(bytes);
+ if (newVec) {
+ bytes = getVec(pBundle, key, newVec, bytes);
+ int32_t elements = bytes / sizeof(T);
+ vec->clear();
+ for (int32_t i = 0; i < elements; i++) {
+ vec->push_back(newVec[i]);
+ }
+ free(newVec);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ bool getBooleanVector(const std::string& key, std::vector<bool>* _Nonnull vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getVecInternal<bool>(&APersistableBundle_getBooleanVector, mPBundle, key.c_str(),
+ vec);
+ }
+ return false;
+ }
+ bool getIntVector(const std::string& key, std::vector<int32_t>* _Nonnull vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getVecInternal<int32_t>(&APersistableBundle_getIntVector, mPBundle, key.c_str(),
+ vec);
+ }
+ return false;
+ }
+ bool getLongVector(const std::string& key, std::vector<int64_t>* _Nonnull vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getVecInternal<int64_t>(&APersistableBundle_getLongVector, mPBundle, key.c_str(),
+ vec);
+ }
+ return false;
+ }
+ bool getDoubleVector(const std::string& key, std::vector<double>* _Nonnull vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getVecInternal<double>(&APersistableBundle_getDoubleVector, mPBundle,
+ key.c_str(), vec);
+ }
+ return false;
+ }
+
+ // Takes ownership of and frees the char** and its elements.
+ // Creates a new set or vector based on the array of char*.
+ template <typename T>
+ T moveStringsInternal(char* _Nullable* _Nonnull strings, int32_t bufferSizeBytes) {
+ if (strings && bufferSizeBytes > 0) {
+ int32_t num = bufferSizeBytes / sizeof(char*);
+ T ret;
+ for (int32_t i = 0; i < num; i++) {
+ ret.insert(ret.end(), std::string(strings[i]));
+ free(strings[i]);
+ }
+ free(strings);
+ return ret;
+ }
+ return T();
+ }
+
+ bool getStringVector(const std::string& key, std::vector<std::string>* _Nonnull vec) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ int32_t bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), nullptr, 0,
+ &stringAllocator, nullptr);
+ if (bytes > 0) {
+ char** strings = (char**)malloc(bytes);
+ if (strings) {
+ bytes = APersistableBundle_getStringVector(mPBundle, key.c_str(), strings,
+ bytes, &stringAllocator, nullptr);
+ *vec = moveStringsInternal<std::vector<std::string>>(strings, bytes);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ bool getPersistableBundle(const std::string& key, PersistableBundle* _Nonnull val) {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ APersistableBundle* bundle = nullptr;
+ bool ret = APersistableBundle_getPersistableBundle(mPBundle, key.c_str(), &bundle);
+ if (ret) {
+ *val = PersistableBundle(bundle);
+ }
+ return ret;
+ } else {
+ return false;
+ }
+ }
+
+ std::set<std::string> getKeys(
+ int32_t (*_Nonnull getTypedKeys)(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable),
+ const APersistableBundle* _Nonnull pBundle) {
+ // call first with nullptr to get required size in bytes
+ int32_t bytes = getTypedKeys(pBundle, nullptr, 0, &stringAllocator, nullptr);
+ if (bytes > 0) {
+ char** keys = (char**)malloc(bytes);
+ if (keys) {
+ bytes = getTypedKeys(pBundle, keys, bytes, &stringAllocator, nullptr);
+ return moveStringsInternal<std::set<std::string>>(keys, bytes);
+ }
+ }
+ return {};
+ }
+
+ std::set<std::string> getBooleanKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getBooleanKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getIntKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getIntKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getLongKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getLongKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getDoubleKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getDoubleKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getStringKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getStringKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getBooleanVectorKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getBooleanVectorKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getIntVectorKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getIntVectorKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getLongVectorKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getLongVectorKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getDoubleVectorKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getDoubleVectorKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getStringVectorKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getStringVectorKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getPersistableBundleKeys() {
+ if API_LEVEL_AT_LEAST(__ANDROID_API_V__, 202404) {
+ return getKeys(&APersistableBundle_getPersistableBundleKeys, mPBundle);
+ } else {
+ return {};
+ }
+ }
+ std::set<std::string> getMonKeys() {
+ // :P
+ return {"c(o,o)b", "c(o,o)b"};
+ }
+
+ private:
+ inline APersistableBundle* _Nullable get() const { return mPBundle; }
+ APersistableBundle* _Nullable mPBundle = nullptr;
+};
+
+} // namespace aidl::android::os
diff --git a/libs/binder/ndk/include_ndk/android/binder_ibinder.h b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
index db2d2c1b09..b1ab7b0f9a 100644
--- a/libs/binder/ndk/include_ndk/android/binder_ibinder.h
+++ b/libs/binder/ndk/include_ndk/android/binder_ibinder.h
@@ -390,6 +390,12 @@ uid_t AIBinder_getCallingUid() __INTRODUCED_IN(29);
* calling process dies and is replaced with another process with elevated permissions and the same
* PID.
*
+ * Warning: oneway transactions do not receive PID. Even if you expect
+ * a transaction to be synchronous, a misbehaving client could send it
+ * as a synchronous call and result in a 0 PID here. Additionally, if
+ * there is a race and the calling process dies, the PID may still be
+ * 0 for a synchronous call.
+ *
* Available since API level 29.
*
* \return calling pid or the current process's PID if this thread isn't processing a transaction.
diff --git a/libs/binder/ndk/include_ndk/android/binder_parcel.h b/libs/binder/ndk/include_ndk/android/binder_parcel.h
index f68612c3ba..d833b837d0 100644
--- a/libs/binder/ndk/include_ndk/android/binder_parcel.h
+++ b/libs/binder/ndk/include_ndk/android/binder_parcel.h
@@ -26,11 +26,11 @@
#pragma once
+#include <android/binder_status.h>
#include <stdbool.h>
#include <stddef.h>
#include <sys/cdefs.h>
-
-#include <android/binder_status.h>
+#include <uchar.h>
struct AIBinder;
typedef struct AIBinder AIBinder;
diff --git a/libs/binder/ndk/include_ndk/android/binder_status.h b/libs/binder/ndk/include_ndk/android/binder_status.h
index 76c7aacb7c..14edf2bfb6 100644
--- a/libs/binder/ndk/include_ndk/android/binder_status.h
+++ b/libs/binder/ndk/include_ndk/android/binder_status.h
@@ -25,11 +25,17 @@
#pragma once
+#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdint.h>
#include <sys/cdefs.h>
+#if !defined(__BIONIC__) && defined(BINDER_ENABLE_LIBLOG_ASSERT)
+#include <log/log.h>
+#define __assert(file, line, message) LOG_ALWAYS_FATAL(file ":" #line ": " message)
+#endif
+
__BEGIN_DECLS
#ifndef __BIONIC__
diff --git a/libs/binder/ndk/include_ndk/android/persistable_bundle.h b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
new file mode 100644
index 0000000000..42ae15ae61
--- /dev/null
+++ b/libs/binder/ndk/include_ndk/android/persistable_bundle.h
@@ -0,0 +1,963 @@
+/*
+ * 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 <android/binder_parcel.h>
+#if defined(__ANDROID_VENDOR__)
+#include <android/llndk-versioning.h>
+#else
+#if !defined(__INTRODUCED_IN_LLNDK)
+#define __INTRODUCED_IN_LLNDK(level) __attribute__((annotate("introduced_in_llndk=" #level)))
+#endif
+#endif // __ANDROID_VENDOR__
+#include <stdbool.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+#include <sys/types.h>
+
+__BEGIN_DECLS
+
+/*
+ * A mapping from string keys to values of various types.
+ * See frameworks/base/core/java/android/os/PersistableBundle.java
+ * for the Java type than can be used in SDK APIs.
+ * APersistableBundle exists to be used in AIDL interfaces and seamlessly
+ * interact with framework services.
+ * frameworks/native/libs/binder/ndk/include_cpp/android/persistable_bundle_aidl.h
+ * contains the AIDL type used in the ndk backend of AIDL interfaces.
+ */
+struct APersistableBundle;
+typedef struct APersistableBundle APersistableBundle;
+
+enum {
+ /**
+ * This can be returned from functions that need to distinguish between an empty
+ * value and a non-existent key.
+ */
+ APERSISTABLEBUNDLE_KEY_NOT_FOUND = -1,
+
+ /**
+ * This can be returned from functions that take a APersistableBundle_stringAllocator.
+ * This means the allocator has failed and returned a nullptr.
+ */
+ APERSISTABLEBUNDLE_ALLOCATOR_FAILED = -2,
+};
+
+/**
+ * This is a user supplied allocator that allocates a buffer for the
+ * APersistableBundle APIs to fill in with a UTF-8 string.
+ * The caller that supplies this function is responsible for freeing the
+ * returned data.
+ *
+ * \param the required size in bytes for the allocated buffer
+ * \param context pointer if needed by the callback
+ *
+ * \return allocated buffer of sizeBytes for a UTF-8 string. Null if allocation failed.
+ */
+typedef char* _Nullable (*_Nonnull APersistableBundle_stringAllocator)(int32_t sizeBytes,
+ void* _Nullable context);
+
+/**
+ * Create a new APersistableBundle.
+ *
+ * Available since API level 202404.
+ *
+ * \return Pointer to a new APersistableBundle
+ */
+APersistableBundle* _Nullable APersistableBundle_new() __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Create a new APersistableBundle based off an existing APersistableBundle.
+ * This is a deep copy, so the new APersistableBundle has its own values from
+ * copying the original underlying PersistableBundle.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to duplicate
+ *
+ * \return Pointer to a new APersistableBundle
+ */
+APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* _Nonnull pBundle)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Delete an APersistableBundle. This must always be called when finished using
+ * the object.
+ *
+ * \param pBundle to delete. No-op if null.
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_delete(APersistableBundle* _Nullable pBundle)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Check for equality of APersistableBundles.
+ *
+ * Available since API level 202404.
+ *
+ * \param lhs bundle to compare against the other param
+ * \param rhs bundle to compare against the other param
+ *
+ * \return true when equal, false when not
+ */
+bool APersistableBundle_isEqual(const APersistableBundle* _Nonnull lhs,
+ const APersistableBundle* _Nonnull rhs)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Read an APersistableBundle from an AParcel.
+ *
+ * Available since API level 202404.
+ *
+ * \param parcel to read from
+ * \param outPBundle bundle to write to
+ *
+ * \return STATUS_OK on success
+ * STATUS_BAD_VALUE if the parcel or outBuffer is null, or if there's an
+ * issue deserializing (eg, corrupted parcel)
+ * STATUS_BAD_TYPE if the parcel's current data position is not that of
+ * an APersistableBundle type
+ * STATUS_NO_MEMORY if an allocation fails
+ */
+binder_status_t APersistableBundle_readFromParcel(
+ const AParcel* _Nonnull parcel, APersistableBundle* _Nullable* _Nonnull outPBundle)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Write an APersistableBundle to an AParcel.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle bundle to write to the parcel
+ * \param parcel to write to
+ *
+ * \return STATUS_OK on success.
+ * STATUS_BAD_VALUE if either pBundle or parcel is null, or if the
+ * APersistableBundle*
+ * fails to serialize (eg, internally corrupted)
+ * STATUS_NO_MEMORY if the parcel runs out of space to store the pBundle & is
+ * unable to allocate more
+ * STATUS_FDS_NOT_ALLOWED if the parcel does not allow storing FDs
+ */
+binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* _Nonnull pBundle,
+ AParcel* _Nonnull parcel)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get the size of an APersistableBundle. This is the number of mappings in the
+ * object.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to get the size of (number of mappings)
+ *
+ * \return number of mappings in the object
+ */
+int32_t APersistableBundle_size(const APersistableBundle* _Nonnull pBundle)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Erase any entries added with the provided key.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8 to erase
+ *
+ * \return number of entries erased. Either 0 or 1.
+ */
+int32_t APersistableBundle_erase(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put a boolean associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param value to put for the mapping
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putBoolean(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ bool val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put an int32_t associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putInt(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ int32_t val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put an int64_t associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putLong(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ int64_t val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put a double associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putDouble(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ double val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put a string associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The value is copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putString(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ const char* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put a boolean vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The values are copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putBooleanVector(APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, const bool* _Nonnull vec,
+ int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put an int32_t vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The values are copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putIntVector(APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ const int32_t* _Nonnull vec, int32_t num)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put an int64_t vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The values are copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putLongVector(APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, const int64_t* _Nonnull vec,
+ int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put a double vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The values are copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putDoubleVector(APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, const double* _Nonnull vec,
+ int32_t num) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put a string vector associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The values are copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param vec vector to put for the mapping
+ * \param num number of elements in the vector
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putStringVector(APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key,
+ const char* _Nullable const* _Nullable vec, int32_t num)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Put an APersistableBundle associated with the provided key.
+ * New values with the same key will overwrite existing values.
+ * The value is deep-copied.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val value to put for the mapping
+ *
+ * Available since API level 202404.
+ */
+void APersistableBundle_putPersistableBundle(APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key,
+ const APersistableBundle* _Nonnull val)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get a boolean associated with the provided key.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getBoolean(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, bool* _Nonnull val)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get an int32_t associated with the provided key.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getInt(const APersistableBundle* _Nonnull pBundle, const char* _Nonnull key,
+ int32_t* _Nonnull val) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get an int64_t associated with the provided key.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getLong(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, int64_t* _Nonnull val)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get a double associated with the provided key.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getDouble(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, double* _Nonnull val)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get a string associated with the provided key.
+ * The caller is responsible for freeing the returned data.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to write the value to in UTF-8
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of string in bytes associated with the provided key on success
+ * APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getString(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, char* _Nullable* _Nonnull val,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get a boolean vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ */
+int32_t APersistableBundle_getBooleanVector(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, bool* _Nullable buffer,
+ int32_t bufferSizeBytes)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get an int32_t vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ */
+int32_t APersistableBundle_getIntVector(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, int32_t* _Nullable buffer,
+ int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get an int64_t vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ */
+int32_t APersistableBundle_getLongVector(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, int64_t* _Nullable buffer,
+ int32_t bufferSizeBytes) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get a double vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the values to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ */
+int32_t APersistableBundle_getDoubleVector(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key, double* _Nullable buffer,
+ int32_t bufferSizeBytes)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get a string vector associated with the provided key and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes of stored vector.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param buffer pointer to a pre-allocated buffer to write the string pointers to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the stored vector in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * 0 if no string vector exists for the provided key
+ * APERSISTABLEBUNDLE_KEY_NOT_FOUND if the key was not found
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getStringVector(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key,
+ char* _Nullable* _Nullable buffer,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get an APersistableBundle* associated with the provided key.
+ *
+ * Available since API level 202404.
+ *
+ * \param pBundle to operate on
+ * \param key for the mapping in UTF-8
+ * \param val pointer to an APersistableBundle pointer to write to point to
+ * a new copy of the stored APersistableBundle. The caller takes ownership of
+ * the new APersistableBundle and must be deleted with
+ * APersistableBundle_delete.
+ *
+ * \return true if a value exists for the provided key
+ */
+bool APersistableBundle_getPersistableBundle(const APersistableBundle* _Nonnull pBundle,
+ const char* _Nonnull key,
+ APersistableBundle* _Nullable* _Nonnull outBundle)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getIntKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getLongKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys, int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getStringKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ */
+int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * false
+ */
+int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* _Nonnull pBundle,
+ char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context)
+ __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+/**
+ * Get all of the keys associated with this specific type and place it in the
+ * provided pre-allocated buffer from the user. The user must provide an
+ * APersistableBundle_stringAllocator for the individual strings to be
+ * allocated.
+ * The caller is responsible for freeing the returned data in bytes.
+ *
+ * This function returns the size in bytes required to fit the fill list of keys.
+ * The supplied buffer will be filled in based on the smaller of the supplied
+ * bufferSizeBytes or the actual size of the stored data.
+ * If the buffer is null or if the supplied bufferSizeBytes is smaller than the
+ * actual stored data, then not all of the stored data will be returned.
+ *
+ * Users can call this function with null buffer and 0 bufferSizeBytes to get
+ * the required size of the buffer to use on a subsequent call.
+ *
+ * \param pBundle to operate on
+ * \param outKeys pointer to a pre-allocated buffer to write the UTF-8 keys to
+ * \param bufferSizeBytes size of the pre-allocated buffer
+ * \param stringAllocator function pointer to the string allocator
+ * \param context pointer that will be passed to the stringAllocator
+ *
+ * \return size of the buffer of keys in bytes. This is the required size of the
+ * pre-allocated user supplied buffer if all of the stored contents are desired.
+ * APERSISTABLEBUNDLE_ALLOCATOR_FAILED if the provided allocator fails
+ */
+int32_t APersistableBundle_getPersistableBundleKeys(
+ const APersistableBundle* _Nonnull pBundle, char* _Nullable* _Nullable outKeys,
+ int32_t bufferSizeBytes, APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) __INTRODUCED_IN(__ANDROID_API_V__) __INTRODUCED_IN_LLNDK(202404);
+
+__END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_manager.h b/libs/binder/ndk/include_platform/android/binder_manager.h
index 43159d8ba2..52edae4a38 100644
--- a/libs/binder/ndk/include_platform/android/binder_manager.h
+++ b/libs/binder/ndk/include_platform/android/binder_manager.h
@@ -18,6 +18,7 @@
#include <android/binder_ibinder.h>
#include <android/binder_status.h>
+#include <android/llndk-versioning.h>
#include <sys/cdefs.h>
__BEGIN_DECLS
@@ -56,12 +57,12 @@ __attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServic
*
* \param binder object to register globally with the service manager.
* \param instance identifier of the service. This will be used to lookup the service.
- * \param flag an AServiceManager_AddServiceFlag enum to denote how the service should be added.
+ * \param flags an AServiceManager_AddServiceFlag enum to denote how the service should be added.
*
* \return EX_NONE on success.
*/
-__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlag(
- AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flag)
+__attribute__((warn_unused_result)) binder_exception_t AServiceManager_addServiceWithFlags(
+ AIBinder* binder, const char* instance, const AServiceManager_AddServiceFlag flags)
__INTRODUCED_IN(34);
/**
@@ -120,7 +121,7 @@ binder_status_t AServiceManager_registerLazyService(AIBinder* binder, const char
/**
* Gets a binder object with this specific instance name. Efficiently waits for the service.
- * If the service is not declared, it will wait indefinitely. Requires the threadpool
+ * If the service is not ever registered, it will wait indefinitely. Requires the threadpool
* to be started in the service.
* This also implicitly calls AIBinder_incStrong (so the caller of this function is responsible
* for calling AIBinder_decStrong).
@@ -243,6 +244,19 @@ void AServiceManager_getUpdatableApexName(const char* instance, void* context,
__INTRODUCED_IN(__ANDROID_API_U__);
/**
+ * Opens a declared passthrough HAL.
+ *
+ * \param instance identifier of the passthrough service (e.g. "mapper")
+ * \param instance identifier of the implemenatation (e.g. "default")
+ * \param flag passed to dlopen()
+ *
+ * \return the result of dlopen of the specified HAL
+ */
+void* AServiceManager_openDeclaredPassthroughHal(const char* interface, const char* instance,
+ int flag) __INTRODUCED_IN(__ANDROID_API_V__)
+ __INTRODUCED_IN_LLNDK(202404);
+
+/**
* Prevent lazy services without client from shutting down their process
*
* This should only be used if it is every eventually set to false. If a
diff --git a/libs/binder/ndk/include_platform/android/binder_process.h b/libs/binder/ndk/include_platform/android/binder_process.h
index ffcad559f6..68528e1004 100644
--- a/libs/binder/ndk/include_platform/android/binder_process.h
+++ b/libs/binder/ndk/include_platform/android/binder_process.h
@@ -24,7 +24,14 @@
__BEGIN_DECLS
/**
- * This creates a threadpool for incoming binder transactions if it has not already been created.
+ * This creates a threadpool for incoming binder transactions if it has not already been created,
+ * spawning one thread, and allowing the kernel to lazily start threads according to the count
+ * that is specified in ABinderProcess_setThreadPoolMaxThreadCount.
+ *
+ * For instance, if ABinderProcess_setThreadPoolMaxThreadCount(3) is called,
+ * ABinderProcess_startThreadPool() is called (+1 thread) then the main thread calls
+ * ABinderProcess_joinThreadPool() (+1 thread), up to *5* total threads will be started
+ * (2 directly, and 3 more if the kernel starts them lazily).
*
* When using this, it is expected that ABinderProcess_setupPolling and
* ABinderProcess_handlePolledCommands are not used.
@@ -32,11 +39,16 @@ __BEGIN_DECLS
* Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
* function should be responsible for configuring the threadpool for the entire application.
*/
-void ABinderProcess_startThreadPool();
+void ABinderProcess_startThreadPool(void);
/**
* This sets the maximum number of threads that can be started in the threadpool. By default, after
* startThreadPool is called, this is 15. If it is called additional times, it will only prevent
- * the kernel from starting new threads and will not delete already existing threads.
+ * the kernel from starting new threads and will not delete already existing threads. This should
+ * be called once before startThreadPool. The number of threads can never decrease.
+ *
+ * This count refers to the number of threads that will be created lazily by the kernel, in
+ * addition to the threads created by ABinderProcess_startThreadPool or
+ * ABinderProcess_joinThreadPool.
*
* Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
* function should be responsible for configuring the threadpool for the entire application.
@@ -48,15 +60,16 @@ bool ABinderProcess_setThreadPoolMaxThreadCount(uint32_t numThreads);
* you should use this in a library to abort if the threadpool is not started.
* Programs should configure binder threadpools once at the beginning.
*/
-bool ABinderProcess_isThreadPoolStarted();
+bool ABinderProcess_isThreadPoolStarted(void);
/**
- * This adds the current thread to the threadpool. This may cause the threadpool to exceed the
- * maximum size.
+ * This adds the current thread to the threadpool. This thread will be in addition to the thread
+ * started by ABinderProcess_startThreadPool and the lazy kernel-started threads specified by
+ * ABinderProcess_setThreadPoolMaxThreadCount.
*
* Do not use this from a library. Apps setup their own threadpools, and otherwise, the main
* function should be responsible for configuring the threadpool for the entire application.
*/
-void ABinderProcess_joinThreadPool();
+void ABinderProcess_joinThreadPool(void);
/**
* This gives you an fd to wait on. Whenever data is available on the fd,
@@ -79,6 +92,6 @@ __attribute__((weak)) binder_status_t ABinderProcess_setupPolling(int* fd) __INT
*
* \return STATUS_OK on success
*/
-__attribute__((weak)) binder_status_t ABinderProcess_handlePolledCommands() __INTRODUCED_IN(31);
+__attribute__((weak)) binder_status_t ABinderProcess_handlePolledCommands(void) __INTRODUCED_IN(31);
__END_DECLS
diff --git a/libs/binder/ndk/include_platform/android/binder_stability.h b/libs/binder/ndk/include_platform/android/binder_stability.h
index c1f62e58e9..089c775eca 100644
--- a/libs/binder/ndk/include_platform/android/binder_stability.h
+++ b/libs/binder/ndk/include_platform/android/binder_stability.h
@@ -21,17 +21,15 @@
__BEGIN_DECLS
/**
- * Private addition to binder_flag_t.
+ * Indicates that this transaction is coupled w/ vendor.img
*/
-enum {
- /**
- * Indicates that this transaction is coupled w/ vendor.img
- */
- FLAG_PRIVATE_VENDOR = 0x10000000,
-};
+constexpr binder_flags_t FLAG_PRIVATE_VENDOR = 0x10000000;
#if defined(__ANDROID_VENDOR__)
+/**
+ * Private addition to binder_flag_t.
+ */
enum {
FLAG_PRIVATE_LOCAL = FLAG_PRIVATE_VENDOR,
};
diff --git a/libs/binder/ndk/libbinder_ndk.map.txt b/libs/binder/ndk/libbinder_ndk.map.txt
index 1078fb2b16..826e199093 100644
--- a/libs/binder/ndk/libbinder_ndk.map.txt
+++ b/libs/binder/ndk/libbinder_ndk.map.txt
@@ -158,7 +158,94 @@ LIBBINDER_NDK34 { # introduced=UpsideDownCake
AServiceManager_getUpdatableApexName; # systemapi
AServiceManager_registerForServiceNotifications; # systemapi llndk
AServiceManager_NotificationRegistration_delete; # systemapi llndk
- AServiceManager_addServiceWithFlag; # systemapi llndk
+ AServiceManager_addServiceWithFlags; # systemapi llndk
+};
+
+LIBBINDER_NDK35 { # introduced=VanillaIceCream
+ global:
+ APersistableBundle_readFromParcel;
+ APersistableBundle_readFromParcel; # llndk=202404
+ APersistableBundle_writeToParcel;
+ APersistableBundle_writeToParcel; # llndk=202404
+ APersistableBundle_new;
+ APersistableBundle_new; # llndk=202404
+ APersistableBundle_dup;
+ APersistableBundle_dup; # llndk=202404
+ APersistableBundle_delete;
+ APersistableBundle_delete; # llndk=202404
+ APersistableBundle_isEqual;
+ APersistableBundle_isEqual; # llndk=202404
+ APersistableBundle_size;
+ APersistableBundle_size; # llndk=202404
+ APersistableBundle_erase;
+ APersistableBundle_erase; # llndk=202404
+ APersistableBundle_putBoolean;
+ APersistableBundle_putBoolean; # llndk=202404
+ APersistableBundle_putInt;
+ APersistableBundle_putInt; # llndk=202404
+ APersistableBundle_putLong;
+ APersistableBundle_putLong; # llndk=202404
+ APersistableBundle_putDouble;
+ APersistableBundle_putDouble; # llndk=202404
+ APersistableBundle_putString;
+ APersistableBundle_putString; # llndk=202404
+ APersistableBundle_putBooleanVector;
+ APersistableBundle_putBooleanVector; # llndk=202404
+ APersistableBundle_putIntVector;
+ APersistableBundle_putIntVector; # llndk=202404
+ APersistableBundle_putLongVector;
+ APersistableBundle_putLongVector; # llndk=202404
+ APersistableBundle_putDoubleVector;
+ APersistableBundle_putDoubleVector; # llndk=202404
+ APersistableBundle_putStringVector;
+ APersistableBundle_putStringVector; # llndk=202404
+ APersistableBundle_putPersistableBundle;
+ APersistableBundle_putPersistableBundle; # llndk=202404
+ APersistableBundle_getBoolean;
+ APersistableBundle_getBoolean; # llndk=202404
+ APersistableBundle_getInt;
+ APersistableBundle_getInt; # llndk=202404
+ APersistableBundle_getLong;
+ APersistableBundle_getLong; # llndk=202404
+ APersistableBundle_getDouble;
+ APersistableBundle_getDouble; # llndk=202404
+ APersistableBundle_getString;
+ APersistableBundle_getString; # llndk=202404
+ APersistableBundle_getBooleanVector;
+ APersistableBundle_getBooleanVector; # llndk=202404
+ APersistableBundle_getIntVector;
+ APersistableBundle_getIntVector; # llndk=202404
+ APersistableBundle_getLongVector;
+ APersistableBundle_getLongVector; # llndk=202404
+ APersistableBundle_getDoubleVector;
+ APersistableBundle_getDoubleVector; # llndk=202404
+ APersistableBundle_getStringVector;
+ APersistableBundle_getStringVector; # llndk=202404
+ APersistableBundle_getPersistableBundle;
+ APersistableBundle_getPersistableBundle; # llndk=202404
+ APersistableBundle_getBooleanKeys;
+ APersistableBundle_getBooleanKeys; # llndk=202404
+ APersistableBundle_getIntKeys;
+ APersistableBundle_getIntKeys; # llndk=202404
+ APersistableBundle_getLongKeys;
+ APersistableBundle_getLongKeys; # llndk=202404
+ APersistableBundle_getDoubleKeys;
+ APersistableBundle_getDoubleKeys; # llndk=202404
+ APersistableBundle_getStringKeys;
+ APersistableBundle_getStringKeys; # llndk=202404
+ APersistableBundle_getBooleanVectorKeys;
+ APersistableBundle_getBooleanVectorKeys; # llndk=202404
+ APersistableBundle_getIntVectorKeys;
+ APersistableBundle_getIntVectorKeys; # llndk=202404
+ APersistableBundle_getLongVectorKeys;
+ APersistableBundle_getLongVectorKeys; # llndk=202404
+ APersistableBundle_getDoubleVectorKeys;
+ APersistableBundle_getDoubleVectorKeys; # llndk=202404
+ APersistableBundle_getStringVectorKeys;
+ APersistableBundle_getStringVectorKeys; # llndk=202404
+ APersistableBundle_getPersistableBundleKeys;
+ APersistableBundle_getPersistableBundleKeys; # llndk=202404
+ AServiceManager_openDeclaredPassthroughHal; # systemapi llndk=202404
};
LIBBINDER_NDK_PLATFORM {
diff --git a/libs/binder/ndk/parcel.cpp b/libs/binder/ndk/parcel.cpp
index b5a2e2ff0b..88ce5f4d9b 100644
--- a/libs/binder/ndk/parcel.cpp
+++ b/libs/binder/ndk/parcel.cpp
@@ -16,24 +16,23 @@
#include <android/binder_parcel.h>
#include <android/binder_parcel_platform.h>
-#include "parcel_internal.h"
-
-#include "ibinder_internal.h"
-#include "status_internal.h"
-
-#include <limits>
-
-#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
#include <binder/Parcel.h>
#include <binder/ParcelFileDescriptor.h>
+#include <binder/unique_fd.h>
+#include <inttypes.h>
#include <utils/Unicode.h>
+#include <limits>
+
+#include "ibinder_internal.h"
+#include "parcel_internal.h"
+#include "status_internal.h"
+
using ::android::IBinder;
using ::android::Parcel;
using ::android::sp;
using ::android::status_t;
-using ::android::base::unique_fd;
+using ::android::binder::unique_fd;
using ::android::os::ParcelFileDescriptor;
template <typename T>
@@ -52,11 +51,11 @@ static binder_status_t WriteAndValidateArraySize(AParcel* parcel, bool isNullArr
if (length < -1) return STATUS_BAD_VALUE;
if (!isNullArray && length < 0) {
- LOG(ERROR) << __func__ << ": non-null array but length is " << length;
+ ALOGE("non-null array but length is %" PRIi32, length);
return STATUS_BAD_VALUE;
}
if (isNullArray && length > 0) {
- LOG(ERROR) << __func__ << ": null buffer cannot be for size " << length << " array.";
+ ALOGE("null buffer cannot be for size %" PRIi32 " array.", length);
return STATUS_BAD_VALUE;
}
@@ -270,6 +269,13 @@ binder_status_t AParcel_readStrongBinder(const AParcel* parcel, AIBinder** binde
}
sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(readBinder);
AIBinder_incStrong(ret.get());
+
+ if (ret.get() != nullptr && parcel->get()->isServiceFuzzing()) {
+ if (auto bp = ret->asABpBinder(); bp != nullptr) {
+ bp->setServiceFuzzing();
+ }
+ }
+
*binder = ret.get();
return PruneStatusT(status);
}
@@ -318,7 +324,7 @@ binder_status_t AParcel_readStatusHeader(const AParcel* parcel, AStatus** status
binder_status_t AParcel_writeString(AParcel* parcel, const char* string, int32_t length) {
if (string == nullptr) {
if (length != -1) {
- LOG(WARNING) << __func__ << ": null string must be used with length == -1.";
+ ALOGW("null string must be used with length == -1.");
return STATUS_BAD_VALUE;
}
@@ -327,7 +333,7 @@ binder_status_t AParcel_writeString(AParcel* parcel, const char* string, int32_t
}
if (length < 0) {
- LOG(WARNING) << __func__ << ": Negative string length: " << length;
+ ALOGW("Negative string length: %" PRIi32, length);
return STATUS_BAD_VALUE;
}
@@ -335,7 +341,7 @@ binder_status_t AParcel_writeString(AParcel* parcel, const char* string, int32_t
const ssize_t len16 = utf8_to_utf16_length(str8, length);
if (len16 < 0 || len16 >= std::numeric_limits<int32_t>::max()) {
- LOG(WARNING) << __func__ << ": Invalid string length: " << len16;
+ ALOGW("Invalid string length: %zd", len16);
return STATUS_BAD_VALUE;
}
@@ -376,7 +382,7 @@ binder_status_t AParcel_readString(const AParcel* parcel, void* stringData,
}
if (len8 <= 0 || len8 > std::numeric_limits<int32_t>::max()) {
- LOG(WARNING) << __func__ << ": Invalid string length: " << len8;
+ ALOGW("Invalid string length: %zd", len8);
return STATUS_BAD_VALUE;
}
@@ -384,7 +390,7 @@ binder_status_t AParcel_readString(const AParcel* parcel, void* stringData,
bool success = allocator(stringData, len8, &str8);
if (!success || str8 == nullptr) {
- LOG(WARNING) << __func__ << ": AParcel_stringAllocator failed to allocate.";
+ ALOGW("AParcel_stringAllocator failed to allocate.");
return STATUS_NO_MEMORY;
}
diff --git a/libs/binder/ndk/persistable_bundle.cpp b/libs/binder/ndk/persistable_bundle.cpp
new file mode 100644
index 0000000000..9b6877daed
--- /dev/null
+++ b/libs/binder/ndk/persistable_bundle.cpp
@@ -0,0 +1,316 @@
+/*
+ * 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 <android/binder_libbinder.h>
+#include <android/persistable_bundle.h>
+#include <binder/PersistableBundle.h>
+#include <log/log.h>
+#include <persistable_bundle_internal.h>
+#include <string.h>
+
+#include <set>
+
+__BEGIN_DECLS
+
+struct APersistableBundle {
+ APersistableBundle(const APersistableBundle& pBundle) : mPBundle(pBundle.mPBundle) {}
+ APersistableBundle(const android::os::PersistableBundle& pBundle) : mPBundle(pBundle) {}
+ APersistableBundle() = default;
+ android::os::PersistableBundle mPBundle;
+};
+
+APersistableBundle* _Nullable APersistableBundle_new() {
+ return new (std::nothrow) APersistableBundle();
+}
+
+APersistableBundle* _Nullable APersistableBundle_dup(const APersistableBundle* pBundle) {
+ if (pBundle) {
+ return new APersistableBundle(*pBundle);
+ } else {
+ return new APersistableBundle();
+ }
+}
+
+void APersistableBundle_delete(APersistableBundle* pBundle) {
+ free(pBundle);
+}
+
+bool APersistableBundle_isEqual(const APersistableBundle* lhs, const APersistableBundle* rhs) {
+ if (lhs && rhs) {
+ return lhs->mPBundle == rhs->mPBundle;
+ } else if (lhs == rhs) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+binder_status_t APersistableBundle_readFromParcel(const AParcel* parcel,
+ APersistableBundle* _Nullable* outPBundle) {
+ if (!parcel || !outPBundle) return STATUS_BAD_VALUE;
+ APersistableBundle* newPBundle = APersistableBundle_new();
+ if (newPBundle == nullptr) return STATUS_NO_MEMORY;
+ binder_status_t status =
+ newPBundle->mPBundle.readFromParcel(AParcel_viewPlatformParcel(parcel));
+ if (status == STATUS_OK) {
+ *outPBundle = newPBundle;
+ }
+ return status;
+}
+
+binder_status_t APersistableBundle_writeToParcel(const APersistableBundle* pBundle,
+ AParcel* parcel) {
+ if (!parcel || !pBundle) return STATUS_BAD_VALUE;
+ return pBundle->mPBundle.writeToParcel(AParcel_viewPlatformParcel(parcel));
+}
+
+int32_t APersistableBundle_size(const APersistableBundle* pBundle) {
+ size_t size = pBundle->mPBundle.size();
+ LOG_ALWAYS_FATAL_IF(size > INT32_MAX,
+ "The APersistableBundle has gotten too large! There will be an overflow in "
+ "the reported size.");
+ return pBundle->mPBundle.size();
+}
+int32_t APersistableBundle_erase(APersistableBundle* pBundle, const char* key) {
+ return pBundle->mPBundle.erase(android::String16(key));
+}
+void APersistableBundle_putBoolean(APersistableBundle* pBundle, const char* key, bool val) {
+ pBundle->mPBundle.putBoolean(android::String16(key), val);
+}
+void APersistableBundle_putInt(APersistableBundle* pBundle, const char* key, int32_t val) {
+ pBundle->mPBundle.putInt(android::String16(key), val);
+}
+void APersistableBundle_putLong(APersistableBundle* pBundle, const char* key, int64_t val) {
+ pBundle->mPBundle.putLong(android::String16(key), val);
+}
+void APersistableBundle_putDouble(APersistableBundle* pBundle, const char* key, double val) {
+ pBundle->mPBundle.putDouble(android::String16(key), val);
+}
+void APersistableBundle_putString(APersistableBundle* pBundle, const char* key, const char* val) {
+ pBundle->mPBundle.putString(android::String16(key), android::String16(val));
+}
+void APersistableBundle_putBooleanVector(APersistableBundle* pBundle, const char* key,
+ const bool* vec, int32_t num) {
+ LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+ std::vector<bool> newVec(num);
+ for (int32_t i = 0; i < num; i++) {
+ newVec[i] = vec[i];
+ }
+ pBundle->mPBundle.putBooleanVector(android::String16(key), newVec);
+}
+void APersistableBundle_putIntVector(APersistableBundle* pBundle, const char* key,
+ const int32_t* vec, int32_t num) {
+ LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+ std::vector<int32_t> newVec(num);
+ for (int32_t i = 0; i < num; i++) {
+ newVec[i] = vec[i];
+ }
+ pBundle->mPBundle.putIntVector(android::String16(key), newVec);
+}
+void APersistableBundle_putLongVector(APersistableBundle* pBundle, const char* key,
+ const int64_t* vec, int32_t num) {
+ LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+ std::vector<int64_t> newVec(num);
+ for (int32_t i = 0; i < num; i++) {
+ newVec[i] = vec[i];
+ }
+ pBundle->mPBundle.putLongVector(android::String16(key), newVec);
+}
+void APersistableBundle_putDoubleVector(APersistableBundle* pBundle, const char* key,
+ const double* vec, int32_t num) {
+ LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+ std::vector<double> newVec(num);
+ for (int32_t i = 0; i < num; i++) {
+ newVec[i] = vec[i];
+ }
+ pBundle->mPBundle.putDoubleVector(android::String16(key), newVec);
+}
+void APersistableBundle_putStringVector(APersistableBundle* pBundle, const char* key,
+ const char* const* vec, int32_t num) {
+ LOG_ALWAYS_FATAL_IF(num < 0, "Negative number of elements is invalid.");
+ std::vector<android::String16> newVec(num);
+ for (int32_t i = 0; i < num; i++) {
+ newVec[i] = android::String16(vec[i]);
+ }
+ pBundle->mPBundle.putStringVector(android::String16(key), newVec);
+}
+void APersistableBundle_putPersistableBundle(APersistableBundle* pBundle, const char* key,
+ const APersistableBundle* val) {
+ pBundle->mPBundle.putPersistableBundle(android::String16(key), val->mPBundle);
+}
+bool APersistableBundle_getBoolean(const APersistableBundle* pBundle, const char* key, bool* val) {
+ return pBundle->mPBundle.getBoolean(android::String16(key), val);
+}
+bool APersistableBundle_getInt(const APersistableBundle* pBundle, const char* key, int32_t* val) {
+ return pBundle->mPBundle.getInt(android::String16(key), val);
+}
+bool APersistableBundle_getLong(const APersistableBundle* pBundle, const char* key, int64_t* val) {
+ return pBundle->mPBundle.getLong(android::String16(key), val);
+}
+bool APersistableBundle_getDouble(const APersistableBundle* pBundle, const char* key, double* val) {
+ return pBundle->mPBundle.getDouble(android::String16(key), val);
+}
+int32_t APersistableBundle_getString(const APersistableBundle* pBundle, const char* key, char** val,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ android::String16 outVal;
+ bool ret = pBundle->mPBundle.getString(android::String16(key), &outVal);
+ if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+ android::String8 tmp8(outVal);
+ *val = stringAllocator(tmp8.bytes() + 1, context);
+ if (*val) {
+ strncpy(*val, tmp8.c_str(), tmp8.bytes() + 1);
+ return tmp8.bytes();
+ } else {
+ return APERSISTABLEBUNDLE_ALLOCATOR_FAILED;
+ }
+}
+int32_t APersistableBundle_getBooleanVector(const APersistableBundle* pBundle, const char* key,
+ bool* buffer, int32_t bufferSizeBytes) {
+ std::vector<bool> newVec;
+ bool ret = pBundle->mPBundle.getBooleanVector(android::String16(key), &newVec);
+ if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+ return getVecInternal<bool>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getIntVector(const APersistableBundle* pBundle, const char* key,
+ int32_t* buffer, int32_t bufferSizeBytes) {
+ std::vector<int32_t> newVec;
+ bool ret = pBundle->mPBundle.getIntVector(android::String16(key), &newVec);
+ if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+ return getVecInternal<int32_t>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getLongVector(const APersistableBundle* pBundle, const char* key,
+ int64_t* buffer, int32_t bufferSizeBytes) {
+ std::vector<int64_t> newVec;
+ bool ret = pBundle->mPBundle.getLongVector(android::String16(key), &newVec);
+ if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+ return getVecInternal<int64_t>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getDoubleVector(const APersistableBundle* pBundle, const char* key,
+ double* buffer, int32_t bufferSizeBytes) {
+ std::vector<double> newVec;
+ bool ret = pBundle->mPBundle.getDoubleVector(android::String16(key), &newVec);
+ if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+ return getVecInternal<double>(newVec, buffer, bufferSizeBytes);
+}
+int32_t APersistableBundle_getStringVector(const APersistableBundle* pBundle, const char* key,
+ char** vec, int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::vector<android::String16> newVec;
+ bool ret = pBundle->mPBundle.getStringVector(android::String16(key), &newVec);
+ if (!ret) return APERSISTABLEBUNDLE_KEY_NOT_FOUND;
+ return getStringsInternal<std::vector<android::String16>>(newVec, vec, bufferSizeBytes,
+ stringAllocator, context);
+}
+bool APersistableBundle_getPersistableBundle(const APersistableBundle* pBundle, const char* key,
+ APersistableBundle** outBundle) {
+ APersistableBundle* bundle = APersistableBundle_new();
+ bool ret = pBundle->mPBundle.getPersistableBundle(android::String16(key), &bundle->mPBundle);
+ if (ret) {
+ *outBundle = bundle;
+ return true;
+ }
+ return false;
+}
+int32_t APersistableBundle_getBooleanKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getBooleanKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getIntKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getIntKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getLongKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getLongKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getDoubleKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getDoubleKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getStringKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getStringKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getBooleanVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getBooleanVectorKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getIntVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getIntVectorKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getLongVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getLongVectorKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getDoubleVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getDoubleVectorKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getStringVectorKeys(const APersistableBundle* pBundle, char** outKeys,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getStringVectorKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+int32_t APersistableBundle_getPersistableBundleKeys(
+ const APersistableBundle* pBundle, char** outKeys, int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator, void* context) {
+ std::set<android::String16> ret = pBundle->mPBundle.getPersistableBundleKeys();
+ return getStringsInternal<std::set<android::String16>>(ret, outKeys, bufferSizeBytes,
+ stringAllocator, context);
+}
+
+__END_DECLS
diff --git a/libs/binder/ndk/persistable_bundle_internal.h b/libs/binder/ndk/persistable_bundle_internal.h
new file mode 100644
index 0000000000..bee10fda78
--- /dev/null
+++ b/libs/binder/ndk/persistable_bundle_internal.h
@@ -0,0 +1,85 @@
+/*
+ * 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 <android/persistable_bundle.h>
+#include <log/log.h>
+#include <utils/String8.h>
+
+// take a vector and put the contents into a buffer.
+// return the size of the contents.
+// This may not put all of the contents into the buffer if the buffer is not
+// large enough.
+template <typename T>
+int32_t getVecInternal(const std::vector<T>& inVec, T* _Nullable buffer, int32_t bufferSizeBytes) {
+ LOG_ALWAYS_FATAL_IF(inVec.size() > INT32_MAX,
+ "The size of the APersistableBundle has gotten too large!");
+ LOG_ALWAYS_FATAL_IF(
+ bufferSizeBytes < 0,
+ "The buffer size in bytes can not be larger than INT32_MAX and can not be negative.");
+ int32_t num = inVec.size();
+ int32_t numAvailable = bufferSizeBytes / sizeof(T);
+ int32_t numFill = numAvailable < num ? numAvailable : num;
+
+ if (numFill > 0 && buffer) {
+ for (int32_t i = 0; i < numFill; i++) {
+ buffer[i] = inVec[i];
+ }
+ }
+ return num * sizeof(T);
+}
+
+// take a vector or a set of String16 and put the contents into a char** buffer.
+// return the size of the contents.
+// This may not put all of the contents into the buffer if the buffer is not
+// large enough.
+// The strings are duped with a user supplied callback
+template <typename T>
+int32_t getStringsInternal(const T& strings, char* _Nullable* _Nullable buffer,
+ int32_t bufferSizeBytes,
+ APersistableBundle_stringAllocator stringAllocator,
+ void* _Nullable context) {
+ LOG_ALWAYS_FATAL_IF(strings.size() > INT32_MAX,
+ "The size of the APersistableBundle has gotten too large!");
+ LOG_ALWAYS_FATAL_IF(
+ bufferSizeBytes < 0,
+ "The buffer size in bytes can not be larger than INT32_MAX and can not be negative.");
+ int32_t num = strings.size();
+ int32_t numAvailable = bufferSizeBytes / sizeof(char*);
+ int32_t numFill = numAvailable < num ? numAvailable : num;
+ if (!stringAllocator) {
+ return APERSISTABLEBUNDLE_ALLOCATOR_FAILED;
+ }
+
+ if (numFill > 0 && buffer) {
+ int32_t i = 0;
+ for (const auto& val : strings) {
+ android::String8 tmp8 = android::String8(val);
+ buffer[i] = stringAllocator(tmp8.bytes() + 1, context);
+ if (buffer[i] == nullptr) {
+ return APERSISTABLEBUNDLE_ALLOCATOR_FAILED;
+ }
+ strncpy(buffer[i], tmp8.c_str(), tmp8.bytes() + 1);
+ i++;
+ if (i > numFill - 1) {
+ // buffer is too small to keep going or this is the end of the
+ // set
+ break;
+ }
+ }
+ }
+ return num * sizeof(char*);
+}
diff --git a/libs/binder/ndk/process.cpp b/libs/binder/ndk/process.cpp
index bc6610e3b9..0072ac3d3e 100644
--- a/libs/binder/ndk/process.cpp
+++ b/libs/binder/ndk/process.cpp
@@ -15,26 +15,24 @@
*/
#include <android/binder_process.h>
+#include <binder/IPCThreadState.h>
#include <mutex>
-#include <android-base/logging.h>
-#include <binder/IPCThreadState.h>
-
using ::android::IPCThreadState;
using ::android::ProcessState;
-void ABinderProcess_startThreadPool() {
+void ABinderProcess_startThreadPool(void) {
ProcessState::self()->startThreadPool();
ProcessState::self()->giveThreadPoolName();
}
bool ABinderProcess_setThreadPoolMaxThreadCount(uint32_t numThreads) {
return ProcessState::self()->setThreadPoolMaxThreadCount(numThreads) == 0;
}
-bool ABinderProcess_isThreadPoolStarted() {
+bool ABinderProcess_isThreadPoolStarted(void) {
return ProcessState::self()->isThreadPoolStarted();
}
-void ABinderProcess_joinThreadPool() {
+void ABinderProcess_joinThreadPool(void) {
IPCThreadState::self()->joinThreadPool();
}
@@ -42,6 +40,6 @@ binder_status_t ABinderProcess_setupPolling(int* fd) {
return IPCThreadState::self()->setupPolling(fd);
}
-binder_status_t ABinderProcess_handlePolledCommands() {
+binder_status_t ABinderProcess_handlePolledCommands(void) {
return IPCThreadState::self()->handlePolledCommands();
}
diff --git a/libs/binder/ndk/service_manager.cpp b/libs/binder/ndk/service_manager.cpp
index 84da459454..5529455cc6 100644
--- a/libs/binder/ndk/service_manager.cpp
+++ b/libs/binder/ndk/service_manager.cpp
@@ -15,14 +15,12 @@
*/
#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <binder/LazyServiceRegistrar.h>
#include "ibinder_internal.h"
#include "status_internal.h"
-#include <android-base/logging.h>
-#include <binder/IServiceManager.h>
-#include <binder/LazyServiceRegistrar.h>
-
using ::android::defaultServiceManager;
using ::android::IBinder;
using ::android::IServiceManager;
@@ -42,15 +40,15 @@ binder_exception_t AServiceManager_addService(AIBinder* binder, const char* inst
return PruneException(exception);
}
-binder_exception_t AServiceManager_addServiceWithFlag(AIBinder* binder, const char* instance,
- const AServiceManager_AddServiceFlag flag) {
+binder_exception_t AServiceManager_addServiceWithFlags(AIBinder* binder, const char* instance,
+ const AServiceManager_AddServiceFlag flags) {
if (binder == nullptr || instance == nullptr) {
return EX_ILLEGAL_ARGUMENT;
}
sp<IServiceManager> sm = defaultServiceManager();
- bool allowIsolated = flag & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
+ bool allowIsolated = flags & AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
status_t exception = sm->addService(String16(instance), binder->getBinder(), allowIsolated);
return PruneException(exception);
}
@@ -115,7 +113,8 @@ struct AServiceManager_NotificationRegistration
std::lock_guard<std::mutex> l(m);
if (onRegister == nullptr) return;
- CHECK_EQ(String8(smInstance), instance);
+ LOG_ALWAYS_FATAL_IF(String8(smInstance) != instance, "onServiceRegistration: %s != %s",
+ String8(smInstance).c_str(), instance);
sp<AIBinder> ret = ABpBinder::lookupOrCreateFromBinder(binder);
AIBinder_incStrong(ret.get());
@@ -135,8 +134,8 @@ __attribute__((warn_unused_result)) AServiceManager_NotificationRegistration*
AServiceManager_registerForServiceNotifications(const char* instance,
AServiceManager_onRegister onRegister,
void* cookie) {
- CHECK_NE(instance, nullptr);
- CHECK_NE(onRegister, nullptr) << instance;
+ LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
+ LOG_ALWAYS_FATAL_IF(onRegister == nullptr, "onRegister == nullptr for %s", instance);
// cookie can be nullptr
auto cb = sp<AServiceManager_NotificationRegistration>::make();
@@ -146,8 +145,8 @@ AServiceManager_registerForServiceNotifications(const char* instance,
sp<IServiceManager> sm = defaultServiceManager();
if (status_t res = sm->registerForNotifications(String16(instance), cb); res != STATUS_OK) {
- LOG(ERROR) << "Failed to register for service notifications for " << instance << ": "
- << statusToString(res);
+ ALOGE("Failed to register for service notifications for %s: %s", instance,
+ statusToString(res).c_str());
return nullptr;
}
@@ -157,7 +156,7 @@ AServiceManager_registerForServiceNotifications(const char* instance,
void AServiceManager_NotificationRegistration_delete(
AServiceManager_NotificationRegistration* notification) {
- CHECK_NE(notification, nullptr);
+ LOG_ALWAYS_FATAL_IF(notification == nullptr, "notification == nullptr");
notification->clear();
notification->decStrong(nullptr);
}
@@ -172,9 +171,9 @@ bool AServiceManager_isDeclared(const char* instance) {
}
void AServiceManager_forEachDeclaredInstance(const char* interface, void* context,
void (*callback)(const char*, void*)) {
- CHECK(interface != nullptr);
+ LOG_ALWAYS_FATAL_IF(interface == nullptr, "interface == nullptr");
// context may be nullptr
- CHECK(callback != nullptr);
+ LOG_ALWAYS_FATAL_IF(callback == nullptr, "callback == nullptr");
sp<IServiceManager> sm = defaultServiceManager();
for (const String16& instance : sm->getDeclaredInstances(String16(interface))) {
@@ -191,9 +190,9 @@ bool AServiceManager_isUpdatableViaApex(const char* instance) {
}
void AServiceManager_getUpdatableApexName(const char* instance, void* context,
void (*callback)(const char*, void*)) {
- CHECK_NE(instance, nullptr);
+ LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
// context may be nullptr
- CHECK_NE(callback, nullptr);
+ LOG_ALWAYS_FATAL_IF(callback == nullptr, "callback == nullptr");
sp<IServiceManager> sm = defaultServiceManager();
std::optional<String16> updatableViaApex = sm->updatableViaApex(String16(instance));
@@ -201,6 +200,13 @@ void AServiceManager_getUpdatableApexName(const char* instance, void* context,
callback(String8(updatableViaApex.value()).c_str(), context);
}
}
+void* AServiceManager_openDeclaredPassthroughHal(const char* interface, const char* instance,
+ int flag) {
+ LOG_ALWAYS_FATAL_IF(interface == nullptr, "interface == nullptr");
+ LOG_ALWAYS_FATAL_IF(instance == nullptr, "instance == nullptr");
+
+ return openDeclaredPassthroughHal(String16(interface), String16(instance), flag);
+}
void AServiceManager_forceLazyServicesPersist(bool persist) {
auto serviceRegistrar = android::binder::LazyServiceRegistrar::getInstance();
serviceRegistrar.forcePersist(persist);
diff --git a/libs/binder/ndk/stability.cpp b/libs/binder/ndk/stability.cpp
index 7eafb9c025..39cf1c4124 100644
--- a/libs/binder/ndk/stability.cpp
+++ b/libs/binder/ndk/stability.cpp
@@ -23,7 +23,11 @@
using ::android::internal::Stability;
-#ifdef __ANDROID_VNDK__
+#if defined(__ANDROID_VNDK__) && !defined(__TRUSTY__)
+#error libbinder_ndk should only be built in a system context
+#endif
+
+#if defined(__ANDROID_VENDOR__) && !defined(__TRUSTY__)
#error libbinder_ndk should only be built in a system context
#endif
diff --git a/libs/binder/ndk/status.cpp b/libs/binder/ndk/status.cpp
index 8ed91a5314..3aac3c0545 100644
--- a/libs/binder/ndk/status.cpp
+++ b/libs/binder/ndk/status.cpp
@@ -17,8 +17,6 @@
#include <android/binder_status.h>
#include "status_internal.h"
-#include <android-base/logging.h>
-
using ::android::status_t;
using ::android::statusToString;
using ::android::binder::Status;
@@ -127,8 +125,8 @@ binder_status_t PruneStatusT(status_t status) {
return STATUS_UNKNOWN_ERROR;
default:
- LOG(WARNING) << __func__ << ": Unknown status_t (" << statusToString(status)
- << ") pruned into STATUS_UNKNOWN_ERROR";
+ ALOGW("%s: Unknown status_t (%s) pruned into STATUS_UNKNOWN_ERROR", __func__,
+ statusToString(status).c_str());
return STATUS_UNKNOWN_ERROR;
}
}
@@ -159,8 +157,8 @@ binder_exception_t PruneException(int32_t exception) {
return EX_TRANSACTION_FAILED;
default:
- LOG(WARNING) << __func__ << ": Unknown binder exception (" << exception
- << ") pruned into EX_TRANSACTION_FAILED";
+ ALOGW("%s: Unknown binder exception (%d) pruned into EX_TRANSACTION_FAILED", __func__,
+ exception);
return EX_TRANSACTION_FAILED;
}
}
diff --git a/libs/binder/ndk/tests/Android.bp b/libs/binder/ndk/tests/Android.bp
index 8ee396e256..8fb755cdac 100644
--- a/libs/binder/ndk/tests/Android.bp
+++ b/libs/binder/ndk/tests/Android.bp
@@ -80,6 +80,28 @@ cc_test {
require_root: true,
}
+cc_test_host {
+ name: "libbinder_ndk_unit_test_host",
+ defaults: ["test_libbinder_ndk_defaults"],
+ srcs: ["libbinder_ndk_unit_test_host.cpp"],
+ test_suites: [
+ "general-tests",
+ ],
+ test_options: {
+ unit_test: true,
+ },
+ static_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libbinder",
+ "libcutils",
+ "libfakeservicemanager",
+ "libgmock",
+ "liblog",
+ "libutils",
+ ],
+}
+
cc_test {
name: "binderVendorDoubleLoadTest",
vendor: true,
diff --git a/libs/binder/ndk/tests/iface.cpp b/libs/binder/ndk/tests/iface.cpp
index 76acff50bf..3ee36cd8c3 100644
--- a/libs/binder/ndk/tests/iface.cpp
+++ b/libs/binder/ndk/tests/iface.cpp
@@ -156,7 +156,10 @@ binder_status_t IFoo::addService(const char* instance) {
}
sp<IFoo> IFoo::getService(const char* instance, AIBinder** outBinder) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AIBinder* binder = AServiceManager_getService(instance); // maybe nullptr
+#pragma clang diagnostic pop
if (binder == nullptr) {
return nullptr;
}
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
index 5b2532ab4e..966ec959b6 100644
--- a/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test.cpp
@@ -39,7 +39,6 @@
#include <condition_variable>
#include <iostream>
#include <mutex>
-#include <optional>
#include <thread>
#include "android/binder_ibinder.h"
@@ -51,8 +50,9 @@ constexpr char kBinderNdkUnitTestService[] = "BinderNdkUnitTest";
constexpr char kLazyBinderNdkUnitTestService[] = "LazyBinderNdkUnitTest";
constexpr char kForcePersistNdkUnitTestService[] = "ForcePersistNdkUnitTestService";
constexpr char kActiveServicesNdkUnitTestService[] = "ActiveServicesNdkUnitTestService";
+constexpr char kBinderNdkUnitTestServiceFlagged[] = "BinderNdkUnitTestFlagged";
-constexpr unsigned int kShutdownWaitTime = 10;
+constexpr unsigned int kShutdownWaitTime = 11;
constexpr uint64_t kContextTestValue = 0xb4e42fb4d9a1d715;
class MyTestFoo : public IFoo {
@@ -106,11 +106,13 @@ class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest {
}
static bool activeServicesCallback(bool hasClients, void* context) {
if (hasClients) {
+ LOG(INFO) << "hasClients, so not unregistering.";
return false;
}
// Unregister all services
if (!AServiceManager_tryUnregister()) {
+ LOG(INFO) << "Could not unregister service the first time.";
// Prevent shutdown (test will fail)
return false;
}
@@ -120,6 +122,7 @@ class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest {
// Unregister again before shutdown
if (!AServiceManager_tryUnregister()) {
+ LOG(INFO) << "Could not unregister service the second time.";
// Prevent shutdown (test will fail)
return false;
}
@@ -127,6 +130,7 @@ class MyBinderNdkUnitTest : public aidl::BnBinderNdkUnitTest {
// Check if the context was passed correctly
MyBinderNdkUnitTest* service = static_cast<MyBinderNdkUnitTest*>(context);
if (service->contextTestValue != kContextTestValue) {
+ LOG(INFO) << "Incorrect context value.";
// Prevent shutdown (test will fail)
return false;
}
@@ -158,6 +162,24 @@ int generatedService() {
return 1; // should not return
}
+int generatedFlaggedService(const AServiceManager_AddServiceFlag flags, const char* instance) {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+ auto service = ndk::SharedRefBase::make<MyBinderNdkUnitTest>();
+ auto binder = service->asBinder();
+
+ binder_exception_t exception =
+ AServiceManager_addServiceWithFlags(binder.get(), instance, flags);
+
+ if (exception != EX_NONE) {
+ LOG(FATAL) << "Could not register: " << exception << " " << instance;
+ }
+
+ ABinderProcess_joinThreadPool();
+
+ return 1; // should not return
+}
+
// manually-written parceling class considered bad practice
class MyFoo : public IFoo {
binder_status_t doubleNumber(int32_t in, int32_t* out) override {
@@ -260,8 +282,8 @@ TEST(NdkBinder, CheckServiceThatDoesntExist) {
TEST(NdkBinder, CheckServiceThatDoesExist) {
AIBinder* binder = AServiceManager_checkService(kExistingNonNdkService);
- EXPECT_NE(nullptr, binder);
- EXPECT_EQ(STATUS_OK, AIBinder_ping(binder));
+ ASSERT_NE(nullptr, binder) << "Could not get " << kExistingNonNdkService;
+ EXPECT_EQ(STATUS_OK, AIBinder_ping(binder)) << "Could not ping " << kExistingNonNdkService;
AIBinder_decStrong(binder);
}
@@ -319,7 +341,10 @@ TEST(NdkBinder, UnimplementedShell) {
// libbinder across processes to the NDK service which doesn't implement
// shell
static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> testService = sm->getService(String16(IFoo::kSomeInstanceName));
+#pragma clang diagnostic pop
Vector<String16> argsVec;
EXPECT_EQ(OK, IBinder::shellCommand(testService, STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO,
@@ -354,18 +379,27 @@ TEST(NdkBinder, CantHaveTwoLocalBinderClassesWithSameDescriptor) {
}
TEST(NdkBinder, GetTestServiceStressTest) {
- // libbinder has some complicated logic to make sure only one instance of
- // ABpBinder is associated with each binder.
-
constexpr size_t kNumThreads = 10;
constexpr size_t kNumCalls = 1000;
std::vector<std::thread> threads;
+ // this is not a lazy service, but we must make sure that it's started before calling
+ // checkService on it, since the other process serving it might not be started yet.
+ {
+ // getService, not waitForService, to take advantage of timeout
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ auto binder = ndk::SpAIBinder(AServiceManager_getService(IFoo::kSomeInstanceName));
+#pragma clang diagnostic pop
+ ASSERT_NE(nullptr, binder.get());
+ }
+
for (size_t i = 0; i < kNumThreads; i++) {
threads.push_back(std::thread([&]() {
for (size_t j = 0; j < kNumCalls; j++) {
auto binder =
ndk::SpAIBinder(AServiceManager_checkService(IFoo::kSomeInstanceName));
+ ASSERT_NE(nullptr, binder.get());
EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
}
}));
@@ -404,21 +438,6 @@ TEST(NdkBinder, GetLazyService) {
EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
}
-TEST(NdkBinder, IsUpdatable) {
- bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.light.ILights/default");
- EXPECT_EQ(isUpdatable, false);
-}
-
-TEST(NdkBinder, GetUpdatableViaApex) {
- std::optional<std::string> updatableViaApex;
- AServiceManager_getUpdatableApexName(
- "android.hardware.light.ILights/default", &updatableViaApex,
- [](const char* apexName, void* context) {
- *static_cast<std::optional<std::string>*>(context) = apexName;
- });
- EXPECT_EQ(updatableViaApex, std::nullopt) << *updatableViaApex;
-}
-
// This is too slow
TEST(NdkBinder, CheckLazyServiceShutDown) {
ndk::SpAIBinder binder(AServiceManager_waitForService(kLazyBinderNdkUnitTestService));
@@ -460,6 +479,8 @@ TEST(NdkBinder, ForcedPersistenceTest) {
}
TEST(NdkBinder, ActiveServicesCallbackTest) {
+ LOG(INFO) << "ActiveServicesCallbackTest starting";
+
ndk::SpAIBinder binder(AServiceManager_waitForService(kActiveServicesNdkUnitTestService));
std::shared_ptr<aidl::IBinderNdkUnitTest> service =
aidl::IBinderNdkUnitTest::fromBinder(binder);
@@ -470,6 +491,7 @@ TEST(NdkBinder, ActiveServicesCallbackTest) {
service = nullptr;
IPCThreadState::self()->flushCommands();
+ LOG(INFO) << "ActiveServicesCallbackTest about to sleep";
sleep(kShutdownWaitTime);
ASSERT_FALSE(isServiceRunning(kActiveServicesNdkUnitTestService))
@@ -478,14 +500,28 @@ TEST(NdkBinder, ActiveServicesCallbackTest) {
struct DeathRecipientCookie {
std::function<void(void)>*onDeath, *onUnlink;
+
+ // may contain additional data
+ // - if it contains AIBinder, then you must call AIBinder_unlinkToDeath manually,
+ // because it would form a strong reference cycle
+ // - if it points to a data member of another structure, this should have a weak
+ // promotable reference or a strong reference, in case that object is deleted
+ // while the death recipient is firing
};
void LambdaOnDeath(void* cookie) {
auto funcs = static_cast<DeathRecipientCookie*>(cookie);
+
+ // may reference other cookie members
+
(*funcs->onDeath)();
};
void LambdaOnUnlink(void* cookie) {
auto funcs = static_cast<DeathRecipientCookie*>(cookie);
(*funcs->onUnlink)();
+
+ // may reference other cookie members
+
+ delete funcs;
};
TEST(NdkBinder, DeathRecipient) {
using namespace std::chrono_literals;
@@ -517,12 +553,12 @@ TEST(NdkBinder, DeathRecipient) {
unlinkCv.notify_one();
};
- DeathRecipientCookie cookie = {&onDeath, &onUnlink};
+ DeathRecipientCookie* cookie = new DeathRecipientCookie{&onDeath, &onUnlink};
AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(LambdaOnDeath);
AIBinder_DeathRecipient_setOnUnlinked(recipient, LambdaOnUnlink);
- EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(&cookie)));
+ EXPECT_EQ(STATUS_OK, AIBinder_linkToDeath(binder, recipient, static_cast<void*>(cookie)));
// the binder driver should return this if the service dies during the transaction
EXPECT_EQ(STATUS_DEAD_OBJECT, foo->die());
@@ -544,7 +580,10 @@ TEST(NdkBinder, DeathRecipient) {
}
TEST(NdkBinder, RetrieveNonNdkService) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
ASSERT_NE(nullptr, binder);
EXPECT_TRUE(AIBinder_isRemote(binder));
EXPECT_TRUE(AIBinder_isAlive(binder));
@@ -558,7 +597,10 @@ void OnBinderDeath(void* cookie) {
}
TEST(NdkBinder, LinkToDeath) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
ASSERT_NE(nullptr, binder);
AIBinder_DeathRecipient* recipient = AIBinder_DeathRecipient_new(OnBinderDeath);
@@ -588,7 +630,10 @@ TEST(NdkBinder, SetInheritRt) {
}
TEST(NdkBinder, SetInheritRtNonLocal) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
ASSERT_NE(binder, nullptr);
ASSERT_TRUE(AIBinder_isRemote(binder));
@@ -624,11 +669,14 @@ TEST(NdkBinder, GetServiceInProcess) {
}
TEST(NdkBinder, EqualityOfRemoteBinderPointer) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AIBinder* binderA = AServiceManager_getService(kExistingNonNdkService);
ASSERT_NE(nullptr, binderA);
AIBinder* binderB = AServiceManager_getService(kExistingNonNdkService);
ASSERT_NE(nullptr, binderB);
+#pragma clang diagnostic pop
EXPECT_EQ(binderA, binderB);
@@ -642,7 +690,10 @@ TEST(NdkBinder, ToFromJavaNullptr) {
}
TEST(NdkBinder, ABpBinderRefCount) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
AIBinder* binder = AServiceManager_getService(kExistingNonNdkService);
+#pragma clang diagnostic pop
AIBinder_Weak* wBinder = AIBinder_Weak_new(binder);
ASSERT_NE(nullptr, binder);
@@ -665,7 +716,10 @@ TEST(NdkBinder, AddServiceMultipleTimes) {
}
TEST(NdkBinder, RequestedSidWorks) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ndk::SpAIBinder binder(AServiceManager_getService(kBinderNdkUnitTestService));
+#pragma clang diagnostic pop
std::shared_ptr<aidl::IBinderNdkUnitTest> service =
aidl::IBinderNdkUnitTest::fromBinder(binder);
@@ -688,7 +742,10 @@ TEST(NdkBinder, SentAidlBinderCanBeDestroyed) {
std::shared_ptr<MyEmpty> empty = ndk::SharedRefBase::make<MyEmpty>();
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ndk::SpAIBinder binder(AServiceManager_getService(kBinderNdkUnitTestService));
+#pragma clang diagnostic pop
std::shared_ptr<aidl::IBinderNdkUnitTest> service =
aidl::IBinderNdkUnitTest::fromBinder(binder);
@@ -711,13 +768,16 @@ TEST(NdkBinder, SentAidlBinderCanBeDestroyed) {
TEST(NdkBinder, ConvertToPlatformBinder) {
for (const ndk::SpAIBinder& binder :
{// remote
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)),
+#pragma clang diagnostic pop
// local
ndk::SharedRefBase::make<MyBinderNdkUnitTest>()->asBinder()}) {
// convert to platform binder
- EXPECT_NE(binder.get(), nullptr);
+ EXPECT_NE(binder, nullptr);
sp<IBinder> platformBinder = AIBinder_toPlatformBinder(binder.get());
- EXPECT_NE(platformBinder.get(), nullptr);
+ EXPECT_NE(platformBinder, nullptr);
auto proxy = interface_cast<IBinderNdkUnitTest>(platformBinder);
EXPECT_NE(proxy, nullptr);
@@ -728,7 +788,7 @@ TEST(NdkBinder, ConvertToPlatformBinder) {
// convert back
ndk::SpAIBinder backBinder = ndk::SpAIBinder(AIBinder_fromPlatformBinder(platformBinder));
- EXPECT_EQ(backBinder.get(), binder.get());
+ EXPECT_EQ(backBinder, binder);
}
}
@@ -744,7 +804,10 @@ TEST(NdkBinder, ConvertToPlatformParcel) {
TEST(NdkBinder, GetAndVerifyScopedAIBinder_Weak) {
for (const ndk::SpAIBinder& binder :
{// remote
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ndk::SpAIBinder(AServiceManager_getService(kBinderNdkUnitTestService)),
+#pragma clang diagnostic pop
// local
ndk::SharedRefBase::make<MyBinderNdkUnitTest>()->asBinder()}) {
// get a const ScopedAIBinder_Weak and verify promote
@@ -839,7 +902,10 @@ std::string shellCmdToString(sp<IBinder> unitTestService, const std::vector<cons
TEST(NdkBinder, UseHandleShellCommand) {
static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> testService = sm->getService(String16(kBinderNdkUnitTestService));
+#pragma clang diagnostic pop
EXPECT_EQ("", shellCmdToString(testService, {}));
EXPECT_EQ("", shellCmdToString(testService, {"", ""}));
@@ -847,6 +913,15 @@ TEST(NdkBinder, UseHandleShellCommand) {
EXPECT_EQ("CMD", shellCmdToString(testService, {"C", "M", "D"}));
}
+TEST(NdkBinder, FlaggedServiceAccessible) {
+ static const sp<android::IServiceManager> sm(android::defaultServiceManager());
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ sp<IBinder> testService = sm->getService(String16(kBinderNdkUnitTestServiceFlagged));
+#pragma clang diagnostic pop
+ ASSERT_NE(nullptr, testService);
+}
+
TEST(NdkBinder, GetClassInterfaceDescriptor) {
ASSERT_STREQ(IFoo::kIFooDescriptor, AIBinder_Class_getDescriptor(IFoo::kClass));
}
@@ -901,8 +976,15 @@ int main(int argc, char* argv[]) {
prctl(PR_SET_PDEATHSIG, SIGHUP);
return generatedService();
}
+ if (fork() == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+ // We may want to change this flag to be more generic ones for the future
+ AServiceManager_AddServiceFlag test_flags =
+ AServiceManager_AddServiceFlag::ADD_SERVICE_ALLOW_ISOLATED;
+ return generatedFlaggedService(test_flags, kBinderNdkUnitTestServiceFlagged);
+ }
- ABinderProcess_setThreadPoolMaxThreadCount(1); // to receive death notifications/callbacks
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp
new file mode 100644
index 0000000000..0a3021d0b0
--- /dev/null
+++ b/libs/binder/ndk/tests/libbinder_ndk_unit_test_host.cpp
@@ -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.
+ */
+
+#include <android/binder_manager.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <utils/String16.h>
+#include <utils/String8.h>
+#include <utils/StrongPointer.h>
+
+#include <optional>
+
+#include "fakeservicemanager/FakeServiceManager.h"
+
+using android::FakeServiceManager;
+using android::setDefaultServiceManager;
+using android::sp;
+using android::String16;
+using android::String8;
+using testing::_;
+using testing::Eq;
+using testing::Mock;
+using testing::NiceMock;
+using testing::Optional;
+using testing::Return;
+
+struct MockServiceManager : FakeServiceManager {
+ MOCK_METHOD1(updatableViaApex, std::optional<String16>(const String16&));
+};
+
+struct AServiceManager : testing::Test {
+ static sp<MockServiceManager> mockSM;
+
+ static void InitMock() {
+ mockSM = new NiceMock<MockServiceManager>;
+ setDefaultServiceManager(mockSM);
+ }
+
+ void TearDown() override { Mock::VerifyAndClear(mockSM.get()); }
+
+ void ExpectUpdatableViaApexReturns(std::optional<String16> apexName) {
+ EXPECT_CALL(*mockSM, updatableViaApex(_)).WillRepeatedly(Return(apexName));
+ }
+};
+
+sp<MockServiceManager> AServiceManager::mockSM;
+
+TEST_F(AServiceManager, isUpdatableViaApex) {
+ auto apexFoo = String16("com.android.hardware.foo");
+ ExpectUpdatableViaApexReturns(apexFoo);
+
+ bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default");
+ EXPECT_EQ(isUpdatable, true);
+}
+
+TEST_F(AServiceManager, isUpdatableViaApex_Not) {
+ ExpectUpdatableViaApexReturns(std::nullopt);
+
+ bool isUpdatable = AServiceManager_isUpdatableViaApex("android.hardware.foo.IFoo/default");
+ EXPECT_EQ(isUpdatable, false);
+}
+
+void getUpdatableApexNameCallback(const char* apexName, void* context) {
+ *(static_cast<std::optional<std::string>*>(context)) = apexName;
+}
+
+TEST_F(AServiceManager, getUpdatableApexName) {
+ auto apexFoo = String16("com.android.hardware.foo");
+ ExpectUpdatableViaApexReturns(apexFoo);
+
+ std::optional<std::string> result;
+ AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result,
+ getUpdatableApexNameCallback);
+ EXPECT_THAT(result, Optional(std::string(String8(apexFoo))));
+}
+
+TEST_F(AServiceManager, getUpdatableApexName_Null) {
+ ExpectUpdatableViaApexReturns(std::nullopt);
+
+ std::optional<std::string> result;
+ AServiceManager_getUpdatableApexName("android.hardware.foo.IFoo/default", &result,
+ getUpdatableApexNameCallback);
+ EXPECT_THAT(result, Eq(std::nullopt));
+}
+
+int main(int argc, char* argv[]) {
+ ::testing::InitGoogleTest(&argc, argv);
+ AServiceManager::InitMock();
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/rust/Android.bp b/libs/binder/rust/Android.bp
index d36ebac109..ef556d7f5f 100644
--- a/libs/binder/rust/Android.bp
+++ b/libs/binder/rust/Android.bp
@@ -11,9 +11,6 @@ rust_library {
name: "libbinder_rs",
crate_name: "binder",
srcs: ["src/lib.rs"],
- shared_libs: [
- "libutils",
- ],
rustlibs: [
"libbinder_ndk_sys",
"libdowncast_rs",
@@ -38,6 +35,21 @@ rust_library {
}
rust_library {
+ name: "libbinder_rs_on_trusty_mock",
+ crate_name: "binder",
+ srcs: ["src/lib.rs"],
+ cfgs: [
+ "trusty",
+ ],
+ rustlibs: [
+ "libbinder_ndk_sys_on_trusty_mock",
+ "libdowncast_rs",
+ "liblibc",
+ ],
+ vendor: true,
+}
+
+rust_library {
name: "libbinder_tokio_rs",
crate_name: "binder_tokio",
srcs: ["binder_tokio/lib.rs"],
@@ -92,39 +104,37 @@ rust_library {
visibility: [":__subpackages__"],
}
+rust_library {
+ name: "libbinder_ndk_sys_on_trusty_mock",
+ crate_name: "binder_ndk_sys",
+ srcs: [
+ "sys/lib.rs",
+ ":libbinder_ndk_bindgen_on_trusty_mock",
+ ],
+ cfgs: [
+ "trusty",
+ ],
+ shared_libs: [
+ "libbinder_ndk_on_trusty_mock",
+ ],
+ vendor: true,
+ // Lints are checked separately for libbinder_ndk_sys.
+ // The Trusty mock copy pulls in extra headers that
+ // don't pass the lints for the bindgen output.
+ lints: "none",
+}
+
rust_bindgen {
name: "libbinder_ndk_bindgen",
crate_name: "binder_ndk_bindgen",
wrapper_src: "sys/BinderBindings.hpp",
source_stem: "bindings",
- bindgen_flags: [
+ bindgen_flag_files: [
// Unfortunately the only way to specify the rust_non_exhaustive enum
// style for a type is to make it the default
- "--default-enum-style",
- "rust_non_exhaustive",
// and then specify constified enums for the enums we don't want
// rustified
- "--constified-enum",
- "android::c_interface::consts::.*",
-
- "--allowlist-type",
- "android::c_interface::.*",
- "--allowlist-type",
- "AStatus",
- "--allowlist-type",
- "AIBinder_Class",
- "--allowlist-type",
- "AIBinder",
- "--allowlist-type",
- "AIBinder_Weak",
- "--allowlist-type",
- "AIBinder_DeathRecipient",
- "--allowlist-type",
- "AParcel",
- "--allowlist-type",
- "binder_status_t",
- "--allowlist-function",
- ".*",
+ "libbinder_ndk_bindgen_flags.txt",
],
shared_libs: [
"libbinder_ndk",
@@ -150,6 +160,28 @@ rust_bindgen {
min_sdk_version: "Tiramisu",
}
+rust_bindgen {
+ name: "libbinder_ndk_bindgen_on_trusty_mock",
+ crate_name: "binder_ndk_bindgen",
+ wrapper_src: "sys/BinderBindings.hpp",
+ source_stem: "bindings",
+ defaults: [
+ "trusty_mock_defaults",
+ ],
+
+ bindgen_flag_files: [
+ // Unfortunately the only way to specify the rust_non_exhaustive enum
+ // style for a type is to make it the default
+ // and then specify constified enums for the enums we don't want
+ // rustified
+ "libbinder_ndk_bindgen_flags.txt",
+ ],
+ shared_libs: [
+ "libbinder_ndk_on_trusty_mock",
+ "libc++",
+ ],
+}
+
rust_test {
name: "libbinder_rs-internal_test",
crate_name: "binder",
diff --git a/libs/binder/rust/binder_tokio/lib.rs b/libs/binder/rust/binder_tokio/lib.rs
index 2d2bf7c582..1dc0b2471d 100644
--- a/libs/binder/rust/binder_tokio/lib.rs
+++ b/libs/binder/rust/binder_tokio/lib.rs
@@ -103,7 +103,12 @@ impl BinderAsyncPool for Tokio {
//
// This shouldn't cause issues with blocking the thread as only one task will run in a
// call to `block_on`, so there aren't other tasks to block.
- let result = spawn_me();
+ //
+ // If the `block_in_place` call fails, then you are driving a current-thread runtime on
+ // the binder threadpool. Instead, it is recommended to use `TokioRuntime<Handle>` when
+ // the runtime is a current-thread runtime, as the current-thread runtime can be driven
+ // only by `Runtime::block_on` calls and not by `Handle::block_on`.
+ let result = tokio::task::block_in_place(spawn_me);
Box::pin(after_spawn(result))
} else {
let handle = tokio::task::spawn_blocking(spawn_me);
diff --git a/libs/binder/rust/libbinder_ndk_bindgen_flags.txt b/libs/binder/rust/libbinder_ndk_bindgen_flags.txt
new file mode 100644
index 0000000000..cb6993e922
--- /dev/null
+++ b/libs/binder/rust/libbinder_ndk_bindgen_flags.txt
@@ -0,0 +1,24 @@
+--default-enum-style=rust_non_exhaustive
+--constified-enum=android::c_interface::consts::.*
+--allowlist-type=android::c_interface::.*
+--allowlist-type=AStatus
+--allowlist-type=AIBinder_Class
+--allowlist-type=AIBinder
+--allowlist-type=AIBinder_Weak
+--allowlist-type=AIBinder_DeathRecipient
+--allowlist-type=AParcel
+--allowlist-type=binder_status_t
+--blocklist-function="vprintf"
+--blocklist-function="strtold"
+--blocklist-function="_vtlog"
+--blocklist-function="vscanf"
+--blocklist-function="vfprintf_worker"
+--blocklist-function="vsprintf"
+--blocklist-function="vsnprintf"
+--blocklist-function="vsnprintf_filtered"
+--blocklist-function="vfscanf"
+--blocklist-function="vsscanf"
+--blocklist-function="vdprintf"
+--blocklist-function="vasprintf"
+--blocklist-function="strtold_l"
+--allowlist-function=.*
diff --git a/libs/binder/rust/rpcbinder/Android.bp b/libs/binder/rust/rpcbinder/Android.bp
index 0067a20484..2e463451f9 100644
--- a/libs/binder/rust/rpcbinder/Android.bp
+++ b/libs/binder/rust/rpcbinder/Android.bp
@@ -18,6 +18,7 @@ rust_library {
"libbinder_ndk_sys",
"libbinder_rpc_unstable_bindgen_sys",
"libbinder_rs",
+ "libcfg_if",
"libdowncast_rs",
"libforeign_types",
"liblibc",
@@ -70,12 +71,11 @@ rust_library {
// TODO(b/184872979): remove once the RPC Binder API is stabilised.
rust_bindgen {
name: "libbinder_rpc_unstable_bindgen",
- wrapper_src: ":libbinder_rpc_unstable_header",
+ wrapper_src: "BinderBindings.hpp",
crate_name: "binder_rpc_unstable_bindgen",
visibility: [":__subpackages__"],
source_stem: "bindings",
bindgen_flags: [
- "--size_t-is-usize",
"--blocklist-type",
"AIBinder",
"--raw-line",
diff --git a/libs/binder/rust/rpcbinder/BinderBindings.hpp b/libs/binder/rust/rpcbinder/BinderBindings.hpp
new file mode 100644
index 0000000000..7feb9650fd
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/BinderBindings.hpp
@@ -0,0 +1 @@
+#include <binder_rpc_unstable.hpp>
diff --git a/libs/binder/rust/rpcbinder/src/lib.rs b/libs/binder/rust/rpcbinder/src/lib.rs
index a9573850f1..7e5c9ddc35 100644
--- a/libs/binder/rust/rpcbinder/src/lib.rs
+++ b/libs/binder/rust/rpcbinder/src/lib.rs
@@ -19,5 +19,7 @@
mod server;
mod session;
-pub use server::{RpcServer, RpcServerRef};
+pub use server::RpcServer;
+#[cfg(not(target_os = "trusty"))]
+pub use server::RpcServerRef;
pub use session::{FileDescriptorTransportMode, RpcSession, RpcSessionRef};
diff --git a/libs/binder/rust/rpcbinder/src/server.rs b/libs/binder/rust/rpcbinder/src/server.rs
index c87876ac15..d6bdbd831f 100644
--- a/libs/binder/rust/rpcbinder/src/server.rs
+++ b/libs/binder/rust/rpcbinder/src/server.rs
@@ -14,160 +14,12 @@
* limitations under the License.
*/
-use crate::session::FileDescriptorTransportMode;
-use binder::{unstable_api::AsNative, SpIBinder};
-use binder_rpc_unstable_bindgen::ARpcServer;
-use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
-use std::ffi::CString;
-use std::io::{Error, ErrorKind};
-use std::os::unix::io::{IntoRawFd, OwnedFd};
-
-foreign_type! {
- type CType = binder_rpc_unstable_bindgen::ARpcServer;
- fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
-
- /// A type that represents a foreign instance of RpcServer.
- #[derive(Debug)]
- pub struct RpcServer;
- /// A borrowed RpcServer.
- pub struct RpcServerRef;
-}
-
-/// SAFETY - The opaque handle can be cloned freely.
-unsafe impl Send for RpcServer {}
-/// SAFETY - The underlying C++ RpcServer class is thread-safe.
-unsafe impl Sync for RpcServer {}
-
-impl RpcServer {
- /// Creates a binder RPC server, serving the supplied binder service implementation on the given
- /// vsock port. Only connections from the given CID are accepted.
- ///
- // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
- // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
- pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
- service, cid, port,
- ))
- }
- }
-
- /// Creates a binder RPC server, serving the supplied binder service implementation on the given
- /// socket file name. The socket should be initialized in init.rc with the same name.
- pub fn new_init_unix_domain(
- mut service: SpIBinder,
- socket_name: &str,
- ) -> Result<RpcServer, Error> {
- let socket_name = match CString::new(socket_name) {
- Ok(s) => s,
- Err(e) => {
- log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
- return Err(Error::from(ErrorKind::InvalidInput));
- }
- };
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInitUnixDomain(
- service,
- socket_name.as_ptr(),
- ))
- }
- }
-
- /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
- /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
- /// domain sockets, pass one to the server and the other to the client. Multiple client session
- /// can be created from the client end of the pair.
- pub fn new_unix_domain_bootstrap(
- mut service: SpIBinder,
- bootstrap_fd: OwnedFd,
- ) -> Result<RpcServer, Error> {
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- // The server takes ownership of the bootstrap FD.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
- service,
- bootstrap_fd.into_raw_fd(),
- ))
- }
- }
-
- /// Creates a binder RPC server, serving the supplied binder service implementation on the given
- /// IP address and port.
- pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
- let address = match CString::new(address) {
- Ok(s) => s,
- Err(e) => {
- log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
- return Err(Error::from(ErrorKind::InvalidInput));
- }
- };
- let service = service.as_native_mut();
-
- // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
- // Plus the binder objects are threadsafe.
- unsafe {
- Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
- service,
- address.as_ptr(),
- port,
- ))
- }
- }
-
- unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
- if ptr.is_null() {
- return Err(Error::new(ErrorKind::Other, "Failed to start server"));
- }
- Ok(RpcServer::from_ptr(ptr))
- }
-}
-
-impl RpcServerRef {
- /// Sets the list of file descriptor transport modes supported by this server.
- pub fn set_supported_file_descriptor_transport_modes(
- &self,
- modes: &[FileDescriptorTransportMode],
- ) {
- // SAFETY - Does not keep the pointer after returning does, nor does it
- // read past its boundary. Only passes the 'self' pointer as an opaque handle.
- unsafe {
- binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
- self.as_ptr(),
- modes.as_ptr(),
- modes.len(),
- )
- }
- }
-
- /// Starts a new background thread and calls join(). Returns immediately.
- pub fn start(&self) {
- unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
- }
-
- /// Joins the RpcServer thread. The call blocks until the server terminates.
- /// This must be called from exactly one thread.
- pub fn join(&self) {
- unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
- }
-
- /// Shuts down the running RpcServer. Can be called multiple times and from
- /// multiple threads. Called automatically during drop().
- pub fn shutdown(&self) -> Result<(), Error> {
- if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
- Ok(())
- } else {
- Err(Error::from(ErrorKind::UnexpectedEof))
- }
+cfg_if::cfg_if! {
+ if #[cfg(target_os = "trusty")] {
+ mod trusty;
+ pub use trusty::*;
+ } else {
+ mod android;
+ pub use android::*;
}
}
diff --git a/libs/binder/rust/rpcbinder/src/server/android.rs b/libs/binder/rust/rpcbinder/src/server/android.rs
new file mode 100644
index 0000000000..2ab34472a9
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/android.rs
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::session::FileDescriptorTransportMode;
+use binder::{unstable_api::AsNative, SpIBinder};
+use binder_rpc_unstable_bindgen::ARpcServer;
+use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
+use std::ffi::CString;
+use std::io::{Error, ErrorKind};
+use std::os::unix::io::{IntoRawFd, OwnedFd};
+
+foreign_type! {
+ type CType = binder_rpc_unstable_bindgen::ARpcServer;
+ fn drop = binder_rpc_unstable_bindgen::ARpcServer_free;
+
+ /// A type that represents a foreign instance of RpcServer.
+ #[derive(Debug)]
+ pub struct RpcServer;
+ /// A borrowed RpcServer.
+ pub struct RpcServerRef;
+}
+
+/// SAFETY: The opaque handle can be cloned freely.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl RpcServer {
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// vsock port. Only connections from the given CID are accepted.
+ ///
+ // Set `cid` to libc::VMADDR_CID_ANY to accept connections from any client.
+ // Set `cid` to libc::VMADDR_CID_LOCAL to only bind to the local vsock interface.
+ pub fn new_vsock(mut service: SpIBinder, cid: u32, port: u32) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newVsock(
+ service, cid, port,
+ ))
+ }
+ }
+
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// socket file descriptor. The socket should be bound to an address before calling this
+ /// function.
+ pub fn new_bound_socket(
+ mut service: SpIBinder,
+ socket_fd: OwnedFd,
+ ) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ // The server takes ownership of the socket FD.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newBoundSocket(
+ service,
+ socket_fd.into_raw_fd(),
+ ))
+ }
+ }
+
+ /// Creates a binder RPC server that bootstraps sessions using an existing Unix domain socket
+ /// pair, with a given root IBinder object. Callers should create a pair of SOCK_STREAM Unix
+ /// domain sockets, pass one to the server and the other to the client. Multiple client session
+ /// can be created from the client end of the pair.
+ pub fn new_unix_domain_bootstrap(
+ mut service: SpIBinder,
+ bootstrap_fd: OwnedFd,
+ ) -> Result<RpcServer, Error> {
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ // The server takes ownership of the bootstrap FD.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newUnixDomainBootstrap(
+ service,
+ bootstrap_fd.into_raw_fd(),
+ ))
+ }
+ }
+
+ /// Creates a binder RPC server, serving the supplied binder service implementation on the given
+ /// IP address and port.
+ pub fn new_inet(mut service: SpIBinder, address: &str, port: u32) -> Result<RpcServer, Error> {
+ let address = match CString::new(address) {
+ Ok(s) => s,
+ Err(e) => {
+ log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
+ return Err(Error::from(ErrorKind::InvalidInput));
+ }
+ };
+ let service = service.as_native_mut();
+
+ // SAFETY: Service ownership is transferring to the server and won't be valid afterward.
+ // Plus the binder objects are threadsafe.
+ unsafe {
+ Self::checked_from_ptr(binder_rpc_unstable_bindgen::ARpcServer_newInet(
+ service,
+ address.as_ptr(),
+ port,
+ ))
+ }
+ }
+
+ unsafe fn checked_from_ptr(ptr: *mut ARpcServer) -> Result<RpcServer, Error> {
+ if ptr.is_null() {
+ return Err(Error::new(ErrorKind::Other, "Failed to start server"));
+ }
+ // SAFETY: Our caller must pass us a valid or null pointer, and we've checked that it's not
+ // null.
+ Ok(unsafe { RpcServer::from_ptr(ptr) })
+ }
+}
+
+impl RpcServerRef {
+ /// Sets the list of file descriptor transport modes supported by this server.
+ pub fn set_supported_file_descriptor_transport_modes(
+ &self,
+ modes: &[FileDescriptorTransportMode],
+ ) {
+ // SAFETY: Does not keep the pointer after returning does, nor does it
+ // read past its boundary. Only passes the 'self' pointer as an opaque handle.
+ unsafe {
+ binder_rpc_unstable_bindgen::ARpcServer_setSupportedFileDescriptorTransportModes(
+ self.as_ptr(),
+ modes.as_ptr(),
+ modes.len(),
+ )
+ }
+ }
+
+ /// Sets the max number of threads this Server uses for incoming client connections.
+ ///
+ /// This must be called before adding a client session. This corresponds
+ /// to the number of incoming connections to RpcSession objects in the
+ /// server, which will correspond to the number of outgoing connections
+ /// in client RpcSession objects. Specifically this is useful for handling
+ /// client-side callback connections.
+ ///
+ /// If this is not specified, this will be a single-threaded server.
+ pub fn set_max_threads(&self, count: usize) {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_setMaxThreads(self.as_ptr(), count) };
+ }
+
+ /// Starts a new background thread and calls join(). Returns immediately.
+ pub fn start(&self) {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_start(self.as_ptr()) };
+ }
+
+ /// Joins the RpcServer thread. The call blocks until the server terminates.
+ /// This must be called from exactly one thread.
+ pub fn join(&self) {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ unsafe { binder_rpc_unstable_bindgen::ARpcServer_join(self.as_ptr()) };
+ }
+
+ /// Shuts down the running RpcServer. Can be called multiple times and from
+ /// multiple threads. Called automatically during drop().
+ pub fn shutdown(&self) -> Result<(), Error> {
+ // SAFETY: RpcServerRef wraps a valid pointer to an ARpcServer.
+ if unsafe { binder_rpc_unstable_bindgen::ARpcServer_shutdown(self.as_ptr()) } {
+ Ok(())
+ } else {
+ Err(Error::from(ErrorKind::UnexpectedEof))
+ }
+ }
+}
diff --git a/libs/binder/rust/rpcbinder/src/server/trusty.rs b/libs/binder/rust/rpcbinder/src/server/trusty.rs
new file mode 100644
index 0000000000..fe45decf25
--- /dev/null
+++ b/libs/binder/rust/rpcbinder/src/server/trusty.rs
@@ -0,0 +1,161 @@
+/*
+ * 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.
+ */
+
+use binder::{unstable_api::AsNative, SpIBinder};
+use libc::size_t;
+use std::ffi::{c_char, c_void};
+use std::ptr;
+use tipc::{ConnectResult, Handle, MessageResult, PortCfg, TipcError, UnbufferedService, Uuid};
+
+pub trait PerSessionCallback: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+impl<T> PerSessionCallback for T where T: Fn(Uuid) -> Option<SpIBinder> + Send + Sync + 'static {}
+
+pub struct RpcServer {
+ inner: *mut binder_rpc_server_bindgen::ARpcServerTrusty,
+}
+
+/// SAFETY: The opaque handle points to a heap allocation
+/// that should be process-wide and not tied to the current thread.
+unsafe impl Send for RpcServer {}
+/// SAFETY: The underlying C++ RpcServer class is thread-safe.
+unsafe impl Sync for RpcServer {}
+
+impl Drop for RpcServer {
+ fn drop(&mut self) {
+ // SAFETY: `ARpcServerTrusty_delete` is the correct destructor to call
+ // on pointers returned by `ARpcServerTrusty_new`.
+ unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_delete(self.inner);
+ }
+ }
+}
+
+impl RpcServer {
+ /// Allocates a new RpcServer object.
+ pub fn new(service: SpIBinder) -> RpcServer {
+ Self::new_per_session(move |_uuid| Some(service.clone()))
+ }
+
+ /// Allocates a new per-session RpcServer object.
+ ///
+ /// Per-session objects take a closure that gets called once
+ /// for every new connection. The closure gets the UUID of
+ /// the peer and can accept or reject that connection.
+ pub fn new_per_session<F: PerSessionCallback>(f: F) -> RpcServer {
+ // SAFETY: Takes ownership of the returned handle, which has correct refcount.
+ let inner = unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_newPerSession(
+ Some(per_session_callback_wrapper::<F>),
+ Box::into_raw(Box::new(f)).cast(),
+ Some(per_session_callback_deleter::<F>),
+ )
+ };
+ RpcServer { inner }
+ }
+}
+
+unsafe extern "C" fn per_session_callback_wrapper<F: PerSessionCallback>(
+ uuid_ptr: *const c_void,
+ len: size_t,
+ cb_ptr: *mut c_char,
+) -> *mut binder_rpc_server_bindgen::AIBinder {
+ // SAFETY: This callback should only get called while the RpcServer is alive.
+ let cb = unsafe { &mut *cb_ptr.cast::<F>() };
+
+ if len != std::mem::size_of::<Uuid>() {
+ return ptr::null_mut();
+ }
+
+ // SAFETY: On the previous lines we check that we got exactly the right amount of bytes.
+ let uuid = unsafe {
+ let mut uuid = std::mem::MaybeUninit::<Uuid>::uninit();
+ uuid.as_mut_ptr().copy_from(uuid_ptr.cast(), 1);
+ uuid.assume_init()
+ };
+
+ cb(uuid).map_or_else(ptr::null_mut, |b| {
+ // Prevent AIBinder_decStrong from being called before AIBinder_toPlatformBinder.
+ // The per-session callback in C++ is supposed to call AIBinder_decStrong on the
+ // pointer we return here.
+ std::mem::ManuallyDrop::new(b).as_native_mut().cast()
+ })
+}
+
+unsafe extern "C" fn per_session_callback_deleter<F: PerSessionCallback>(cb: *mut c_char) {
+ // SAFETY: shared_ptr calls this to delete the pointer we gave it.
+ // It should only get called once the last shared reference goes away.
+ unsafe {
+ drop(Box::<F>::from_raw(cb.cast()));
+ }
+}
+
+pub struct RpcServerConnection {
+ ctx: *mut c_void,
+}
+
+impl Drop for RpcServerConnection {
+ fn drop(&mut self) {
+ // We do not need to close handle_fd since we do not own it.
+ unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_handleChannelCleanup(self.ctx);
+ }
+ }
+}
+
+impl UnbufferedService for RpcServer {
+ type Connection = RpcServerConnection;
+
+ fn on_connect(
+ &self,
+ _port: &PortCfg,
+ handle: &Handle,
+ peer: &Uuid,
+ ) -> tipc::Result<ConnectResult<Self::Connection>> {
+ let mut conn = RpcServerConnection { ctx: std::ptr::null_mut() };
+ let rc = unsafe {
+ binder_rpc_server_bindgen::ARpcServerTrusty_handleConnect(
+ self.inner,
+ handle.as_raw_fd(),
+ peer.as_ptr().cast(),
+ &mut conn.ctx,
+ )
+ };
+ if rc < 0 {
+ Err(TipcError::from_uapi(rc.into()))
+ } else {
+ Ok(ConnectResult::Accept(conn))
+ }
+ }
+
+ fn on_message(
+ &self,
+ conn: &Self::Connection,
+ _handle: &Handle,
+ buffer: &mut [u8],
+ ) -> tipc::Result<MessageResult> {
+ assert!(buffer.is_empty());
+ let rc = unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleMessage(conn.ctx) };
+ if rc < 0 {
+ Err(TipcError::from_uapi(rc.into()))
+ } else {
+ Ok(MessageResult::MaintainConnection)
+ }
+ }
+
+ fn on_disconnect(&self, conn: &Self::Connection) {
+ unsafe { binder_rpc_server_bindgen::ARpcServerTrusty_handleDisconnect(conn.ctx) };
+ }
+}
diff --git a/libs/binder/rust/rpcbinder/src/session.rs b/libs/binder/rust/rpcbinder/src/session.rs
index 0b517cf613..09688a21a7 100644
--- a/libs/binder/rust/rpcbinder/src/session.rs
+++ b/libs/binder/rust/rpcbinder/src/session.rs
@@ -17,11 +17,8 @@
use binder::unstable_api::new_spibinder;
use binder::{FromIBinder, SpIBinder, StatusCode, Strong};
use foreign_types::{foreign_type, ForeignType, ForeignTypeRef};
-use std::ffi::CString;
-use std::os::{
- raw::{c_int, c_void},
- unix::io::{AsRawFd, BorrowedFd, RawFd},
-};
+use std::os::fd::RawFd;
+use std::os::raw::{c_int, c_void};
pub use binder_rpc_unstable_bindgen::ARpcSession_FileDescriptorTransportMode as FileDescriptorTransportMode;
@@ -36,15 +33,15 @@ foreign_type! {
pub struct RpcSessionRef;
}
-/// SAFETY - The opaque handle can be cloned freely.
+/// SAFETY: The opaque handle can be cloned freely.
unsafe impl Send for RpcSession {}
-/// SAFETY - The underlying C++ RpcSession class is thread-safe.
+/// SAFETY: The underlying C++ RpcSession class is thread-safe.
unsafe impl Sync for RpcSession {}
impl RpcSession {
/// Allocates a new RpcSession object.
pub fn new() -> RpcSession {
- // SAFETY - Takes ownership of the returned handle, which has correct refcount.
+ // SAFETY: Takes ownership of the returned handle, which has correct refcount.
unsafe { RpcSession::from_ptr(binder_rpc_unstable_bindgen::ARpcSession_new()) }
}
}
@@ -58,7 +55,7 @@ impl Default for RpcSession {
impl RpcSessionRef {
/// Sets the file descriptor transport mode for this session.
pub fn set_file_descriptor_transport_mode(&self, mode: FileDescriptorTransportMode) {
- // SAFETY - Only passes the 'self' pointer as an opaque handle.
+ // SAFETY: Only passes the 'self' pointer as an opaque handle.
unsafe {
binder_rpc_unstable_bindgen::ARpcSession_setFileDescriptorTransportMode(
self.as_ptr(),
@@ -69,21 +66,25 @@ impl RpcSessionRef {
/// Sets the maximum number of incoming threads.
pub fn set_max_incoming_threads(&self, threads: usize) {
- // SAFETY - Only passes the 'self' pointer as an opaque handle.
+ // SAFETY: Only passes the 'self' pointer as an opaque handle.
unsafe {
binder_rpc_unstable_bindgen::ARpcSession_setMaxIncomingThreads(self.as_ptr(), threads)
};
}
- /// Sets the maximum number of outgoing threads.
- pub fn set_max_outgoing_threads(&self, threads: usize) {
- // SAFETY - Only passes the 'self' pointer as an opaque handle.
+ /// Sets the maximum number of outgoing connections.
+ pub fn set_max_outgoing_connections(&self, connections: usize) {
+ // SAFETY: Only passes the 'self' pointer as an opaque handle.
unsafe {
- binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingThreads(self.as_ptr(), threads)
+ binder_rpc_unstable_bindgen::ARpcSession_setMaxOutgoingConnections(
+ self.as_ptr(),
+ connections,
+ )
};
}
/// Connects to an RPC Binder server over vsock for a particular interface.
+ #[cfg(not(target_os = "trusty"))]
pub fn setup_vsock_client<T: FromIBinder + ?Sized>(
&self,
cid: u32,
@@ -103,11 +104,12 @@ impl RpcSessionRef {
/// Connects to an RPC Binder server over a names Unix Domain Socket for
/// a particular interface.
+ #[cfg(not(target_os = "trusty"))]
pub fn setup_unix_domain_client<T: FromIBinder + ?Sized>(
&self,
socket_name: &str,
) -> Result<Strong<T>, StatusCode> {
- let socket_name = match CString::new(socket_name) {
+ let socket_name = match std::ffi::CString::new(socket_name) {
Ok(s) => s,
Err(e) => {
log::error!("Cannot convert {} to CString. Error: {:?}", socket_name, e);
@@ -128,10 +130,12 @@ impl RpcSessionRef {
/// Connects to an RPC Binder server over a bootstrap Unix Domain Socket
/// for a particular interface.
+ #[cfg(not(target_os = "trusty"))]
pub fn setup_unix_domain_bootstrap_client<T: FromIBinder + ?Sized>(
&self,
- bootstrap_fd: BorrowedFd,
+ bootstrap_fd: std::os::fd::BorrowedFd,
) -> Result<Strong<T>, StatusCode> {
+ use std::os::fd::AsRawFd;
// SAFETY: ARpcSession_setupUnixDomainBootstrapClient does not take
// ownership of bootstrap_fd. The returned AIBinder has correct
// reference count, and the ownership can safely be taken by new_spibinder.
@@ -145,12 +149,13 @@ impl RpcSessionRef {
}
/// Connects to an RPC Binder server over inet socket at the given address and port.
+ #[cfg(not(target_os = "trusty"))]
pub fn setup_inet_client<T: FromIBinder + ?Sized>(
&self,
address: &str,
port: u32,
) -> Result<Strong<T>, StatusCode> {
- let address = match CString::new(address) {
+ let address = match std::ffi::CString::new(address) {
Ok(s) => s,
Err(e) => {
log::error!("Cannot convert {} to CString. Error: {:?}", address, e);
@@ -170,6 +175,22 @@ impl RpcSessionRef {
Self::get_interface(service)
}
+ #[cfg(target_os = "trusty")]
+ pub fn setup_trusty_client<T: FromIBinder + ?Sized>(
+ &self,
+ port: &std::ffi::CStr,
+ ) -> Result<Strong<T>, StatusCode> {
+ self.setup_preconnected_client(|| {
+ let h = tipc::Handle::connect(port)
+ .expect("Failed to connect to service port {SERVICE_PORT}");
+
+ // Do not close the handle at the end of the scope
+ let fd = h.as_raw_fd();
+ core::mem::forget(h);
+ Some(fd)
+ })
+ }
+
/// Connects to an RPC Binder server, using the given callback to get (and
/// take ownership of) file descriptors already connected to it.
pub fn setup_preconnected_client<T: FromIBinder + ?Sized>(
@@ -207,10 +228,10 @@ impl RpcSessionRef {
type RequestFd<'a> = &'a mut dyn FnMut() -> Option<RawFd>;
unsafe extern "C" fn request_fd_wrapper(param: *mut c_void) -> c_int {
+ let request_fd_ptr = param as *mut RequestFd;
// SAFETY: This is only ever called by RpcPreconnectedClient, within the lifetime of the
// BinderFdFactory reference, with param being a properly aligned non-null pointer to an
// initialized instance.
- let request_fd_ptr = param as *mut RequestFd;
- let request_fd = request_fd_ptr.as_mut().unwrap();
+ let request_fd = unsafe { request_fd_ptr.as_mut().unwrap() };
request_fd().unwrap_or(-1)
}
diff --git a/libs/binder/rust/src/binder.rs b/libs/binder/rust/src/binder.rs
index 976f54d36f..e34d31e58f 100644
--- a/libs/binder/rust/src/binder.rs
+++ b/libs/binder/rust/src/binder.rs
@@ -21,16 +21,17 @@ use crate::parcel::{BorrowedParcel, Parcel};
use crate::proxy::{DeathRecipient, SpIBinder, WpIBinder};
use crate::sys;
+use downcast_rs::{impl_downcast, DowncastSync};
use std::borrow::Borrow;
use std::cmp::Ordering;
use std::convert::TryFrom;
use std::ffi::{c_void, CStr, CString};
use std::fmt;
-use std::fs::File;
+use std::io::Write;
use std::marker::PhantomData;
use std::ops::Deref;
+use std::os::fd::AsRawFd;
use std::os::raw::c_char;
-use std::os::unix::io::AsRawFd;
use std::ptr;
/// Binder action to perform.
@@ -51,7 +52,7 @@ pub type TransactionFlags = u32;
/// interfaces) must implement this trait.
///
/// This is equivalent `IInterface` in C++.
-pub trait Interface: Send + Sync {
+pub trait Interface: Send + Sync + DowncastSync {
/// Convert this binder object into a generic [`SpIBinder`] reference.
fn as_binder(&self) -> SpIBinder {
panic!("This object was not a Binder object and cannot be converted into an SpIBinder.")
@@ -61,11 +62,13 @@ pub trait Interface: Send + Sync {
///
/// This handler is a no-op by default and should be implemented for each
/// Binder service struct that wishes to respond to dump transactions.
- fn dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+ fn dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
Ok(())
}
}
+impl_downcast!(sync Interface);
+
/// Implemented by sync interfaces to specify what the associated async interface is.
/// Generic to handle the fact that async interfaces are generic over a thread pool.
///
@@ -97,25 +100,20 @@ where
/// Interface stability promise
///
-/// An interface can promise to be a stable vendor interface ([`Vintf`]), or
-/// makes no stability guarantees ([`Local`]). [`Local`] is
+/// An interface can promise to be a stable vendor interface ([`Stability::Vintf`]),
+/// or makes no stability guarantees ([`Stability::Local`]). [`Stability::Local`] is
/// currently the default stability.
-#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Stability {
/// Default stability, visible to other modules in the same compilation
/// context (e.g. modules on system.img)
+ #[default]
Local,
/// A Vendor Interface Object, which promises to be stable
Vintf,
}
-impl Default for Stability {
- fn default() -> Self {
- Stability::Local
- }
-}
-
impl From<Stability> for i32 {
fn from(stability: Stability) -> i32 {
use Stability::*;
@@ -144,11 +142,11 @@ impl TryFrom<i32> for Stability {
/// via `Binder::new(object)`.
///
/// This is a low-level interface that should normally be automatically
-/// generated from AIDL via the [`declare_binder_interface!`] macro. When using
-/// the AIDL backend, users need only implement the high-level AIDL-defined
+/// generated from AIDL via the [`crate::declare_binder_interface!`] macro.
+/// When using the AIDL backend, users need only implement the high-level AIDL-defined
/// interface. The AIDL compiler then generates a container struct that wraps
/// the user-defined service and implements `Remotable`.
-pub trait Remotable: Send + Sync {
+pub trait Remotable: Send + Sync + 'static {
/// The Binder interface descriptor string.
///
/// This string is a unique identifier for a Binder interface, and should be
@@ -167,7 +165,7 @@ pub trait Remotable: Send + Sync {
/// Handle a request to invoke the dump transaction on this
/// object.
- fn on_dump(&self, file: &File, args: &[&CStr]) -> Result<()>;
+ fn on_dump(&self, file: &mut dyn Write, args: &[&CStr]) -> Result<()>;
/// Retrieve the class of this remote object.
///
@@ -265,7 +263,14 @@ pub trait IBinder {
/// Trying to use this function on a local binder will result in an
/// INVALID_OPERATION code being returned and nothing happening.
///
- /// This link always holds a weak reference to its recipient.
+ /// This link only holds a weak reference to its recipient. If the
+ /// `DeathRecipient` is dropped then it will be unlinked.
+ ///
+ /// Note that the notifications won't work if you don't first start at least
+ /// one Binder thread by calling
+ /// [`ProcessState::start_thread_pool`](crate::ProcessState::start_thread_pool)
+ /// or
+ /// [`ProcessState::join_thread_pool`](crate::ProcessState::join_thread_pool).
fn link_to_death(&mut self, recipient: &mut DeathRecipient) -> Result<()>;
/// Remove a previously registered death notification.
@@ -295,18 +300,17 @@ impl InterfaceClass {
/// Note: the returned pointer will not be constant. Calling this method
/// multiple times for the same type will result in distinct class
/// pointers. A static getter for this value is implemented in
- /// [`declare_binder_interface!`].
+ /// [`crate::declare_binder_interface!`].
pub fn new<I: InterfaceClassMethods>() -> InterfaceClass {
let descriptor = CString::new(I::get_descriptor()).unwrap();
+ // Safety: `AIBinder_Class_define` expects a valid C string, and three
+ // valid callback functions, all non-null pointers. The C string is
+ // copied and need not be valid for longer than the call, so we can drop
+ // it after the call. We can safely assign null to the onDump and
+ // handleShellCommand callbacks as long as the class pointer was
+ // non-null. Rust None for a Option<fn> is guaranteed to be a NULL
+ // pointer. Rust retains ownership of the pointer after it is defined.
let ptr = unsafe {
- // Safety: `AIBinder_Class_define` expects a valid C string, and
- // three valid callback functions, all non-null pointers. The C
- // string is copied and need not be valid for longer than the call,
- // so we can drop it after the call. We can safely assign null to
- // the onDump and handleShellCommand callbacks as long as the class
- // pointer was non-null. Rust None for a Option<fn> is guaranteed to
- // be a NULL pointer. Rust retains ownership of the pointer after it
- // is defined.
let class = sys::AIBinder_Class_define(
descriptor.as_ptr(),
Some(I::on_create),
@@ -336,13 +340,12 @@ impl InterfaceClass {
/// Get the interface descriptor string of this class.
pub fn get_descriptor(&self) -> String {
+ // SAFETY: The descriptor returned by AIBinder_Class_getDescriptor is
+ // always a two-byte null terminated sequence of u16s. Thus, we can
+ // continue reading from the pointer until we hit a null value, and this
+ // pointer can be a valid slice if the slice length is <= the number of
+ // u16 elements before the null terminator.
unsafe {
- // SAFETY: The descriptor returned by AIBinder_Class_getDescriptor
- // is always a two-byte null terminated sequence of u16s. Thus, we
- // can continue reading from the pointer until we hit a null value,
- // and this pointer can be a valid slice if the slice length is <=
- // the number of u16 elements before the null terminator.
-
let raw_descriptor: *const c_char = sys::AIBinder_Class_getDescriptor(self.0);
CStr::from_ptr(raw_descriptor)
.to_str()
@@ -436,7 +439,7 @@ impl<I: FromIBinder + ?Sized> Ord for Strong<I> {
impl<I: FromIBinder + ?Sized> PartialOrd for Strong<I> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- self.0.as_binder().partial_cmp(&other.0.as_binder())
+ Some(self.cmp(other))
}
}
@@ -483,7 +486,7 @@ impl<I: FromIBinder + ?Sized> Ord for Weak<I> {
impl<I: FromIBinder + ?Sized> PartialOrd for Weak<I> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
- self.weak_binder.partial_cmp(&other.weak_binder)
+ Some(self.cmp(other))
}
}
@@ -540,17 +543,15 @@ macro_rules! binder_fn_get_class {
static CLASS_INIT: std::sync::Once = std::sync::Once::new();
static mut CLASS: Option<$crate::binder_impl::InterfaceClass> = None;
+ // Safety: This assignment is guarded by the `CLASS_INIT` `Once`
+ // variable, and therefore is thread-safe, as it can only occur
+ // once.
CLASS_INIT.call_once(|| unsafe {
- // Safety: This assignment is guarded by the `CLASS_INIT` `Once`
- // variable, and therefore is thread-safe, as it can only occur
- // once.
CLASS = Some($constructor);
});
- unsafe {
- // Safety: The `CLASS` variable can only be mutated once, above,
- // and is subsequently safe to read from any thread.
- CLASS.unwrap()
- }
+ // Safety: The `CLASS` variable can only be mutated once, above, and
+ // is subsequently safe to read from any thread.
+ unsafe { CLASS.unwrap() }
}
};
}
@@ -662,6 +663,8 @@ pub unsafe trait AsNative<T> {
fn as_native_mut(&mut self) -> *mut T;
}
+// Safety: If V is a valid Android C++ type then we can either use that or a
+// null pointer.
unsafe impl<T, V: AsNative<T>> AsNative<T> for Option<V> {
fn as_native(&self) -> *const T {
self.as_ref().map_or(ptr::null(), |v| v.as_native())
@@ -893,6 +896,23 @@ macro_rules! declare_binder_interface {
$crate::binder_impl::IBinderInternal::set_requesting_sid(&mut binder, features.set_requesting_sid);
$crate::Strong::new(Box::new(binder))
}
+
+ /// Tries to downcast the interface to another type.
+ /// When receiving this object from a binder call, make sure that the object received is
+ /// a binder native object and that is of the right type for the Downcast:
+ ///
+ /// let binder = received_object.as_binder();
+ /// if !binder.is_remote() {
+ /// let binder_native: Binder<BnFoo> = binder.try_into()?;
+ /// let original_object = binder_native.downcast_binder::<MyFoo>();
+ /// // Check that returned type is not None before using it
+ /// }
+ ///
+ /// Handle the error cases instead of just calling `unwrap` or `expect` to prevent a
+ /// malicious caller to mount a Denial of Service attack.
+ pub fn downcast_binder<T: $interface>(&self) -> Option<&T> {
+ self.0.as_any().downcast_ref::<T>()
+ }
}
impl $crate::binder_impl::Remotable for $native {
@@ -914,23 +934,23 @@ macro_rules! declare_binder_interface {
}
}
- fn on_dump(&self, file: &std::fs::File, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
- self.0.dump(file, args)
+ fn on_dump(&self, writer: &mut dyn std::io::Write, args: &[&std::ffi::CStr]) -> std::result::Result<(), $crate::StatusCode> {
+ self.0.dump(writer, args)
}
fn get_class() -> $crate::binder_impl::InterfaceClass {
static CLASS_INIT: std::sync::Once = std::sync::Once::new();
static mut CLASS: Option<$crate::binder_impl::InterfaceClass> = None;
+ // Safety: This assignment is guarded by the `CLASS_INIT` `Once`
+ // variable, and therefore is thread-safe, as it can only occur
+ // once.
CLASS_INIT.call_once(|| unsafe {
- // Safety: This assignment is guarded by the `CLASS_INIT` `Once`
- // variable, and therefore is thread-safe, as it can only occur
- // once.
CLASS = Some($crate::binder_impl::InterfaceClass::new::<$crate::binder_impl::Binder<$native>>());
});
+ // Safety: The `CLASS` variable can only be mutated once, above,
+ // and is subsequently safe to read from any thread.
unsafe {
- // Safety: The `CLASS` variable can only be mutated once, above,
- // and is subsequently safe to read from any thread.
CLASS.unwrap()
}
}
@@ -1004,7 +1024,7 @@ macro_rules! declare_binder_interface {
$(
// Async interface trait implementations.
- impl<P: $crate::BinderAsyncPool> $crate::FromIBinder for dyn $async_interface<P> {
+ impl<P: $crate::BinderAsyncPool + 'static> $crate::FromIBinder for dyn $async_interface<P> {
fn try_from(mut ibinder: $crate::SpIBinder) -> std::result::Result<$crate::Strong<dyn $async_interface<P>>, $crate::StatusCode> {
use $crate::binder_impl::AssociateClass;
@@ -1023,44 +1043,34 @@ macro_rules! declare_binder_interface {
}
if ibinder.associate_class(<$native as $crate::binder_impl::Remotable>::get_class()) {
- let service: std::result::Result<$crate::binder_impl::Binder<$native>, $crate::StatusCode> =
- std::convert::TryFrom::try_from(ibinder.clone());
- if let Ok(service) = service {
- // We were able to associate with our expected class and
- // the service is local.
- todo!()
- //return Ok($crate::Strong::new(Box::new(service)));
- } else {
- // Service is remote
- return Ok($crate::Strong::new(Box::new(<$proxy as $crate::binder_impl::Proxy>::from_binder(ibinder)?)));
- }
+ return Ok($crate::Strong::new(Box::new(<$proxy as $crate::binder_impl::Proxy>::from_binder(ibinder)?)));
}
Err($crate::StatusCode::BAD_TYPE.into())
}
}
- impl<P: $crate::BinderAsyncPool> $crate::binder_impl::Serialize for dyn $async_interface<P> + '_ {
+ impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::Serialize for dyn $async_interface<P> + '_ {
fn serialize(&self, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
let binder = $crate::Interface::as_binder(self);
parcel.write(&binder)
}
}
- impl<P: $crate::BinderAsyncPool> $crate::binder_impl::SerializeOption for dyn $async_interface<P> + '_ {
+ impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::SerializeOption for dyn $async_interface<P> + '_ {
fn serialize_option(this: Option<&Self>, parcel: &mut $crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<(), $crate::StatusCode> {
parcel.write(&this.map($crate::Interface::as_binder))
}
}
- impl<P: $crate::BinderAsyncPool> std::fmt::Debug for dyn $async_interface<P> + '_ {
+ impl<P: $crate::BinderAsyncPool + 'static> std::fmt::Debug for dyn $async_interface<P> + '_ {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.pad(stringify!($async_interface))
}
}
/// Convert a &dyn $async_interface to Strong<dyn $async_interface>
- impl<P: $crate::BinderAsyncPool> std::borrow::ToOwned for dyn $async_interface<P> {
+ impl<P: $crate::BinderAsyncPool + 'static> std::borrow::ToOwned for dyn $async_interface<P> {
type Owned = $crate::Strong<dyn $async_interface<P>>;
fn to_owned(&self) -> Self::Owned {
self.as_binder().into_interface()
@@ -1068,11 +1078,11 @@ macro_rules! declare_binder_interface {
}
}
- impl<P: $crate::BinderAsyncPool> $crate::binder_impl::ToAsyncInterface<P> for dyn $interface {
+ impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::ToAsyncInterface<P> for dyn $interface {
type Target = dyn $async_interface<P>;
}
- impl<P: $crate::BinderAsyncPool> $crate::binder_impl::ToSyncInterface for dyn $async_interface<P> {
+ impl<P: $crate::BinderAsyncPool + 'static> $crate::binder_impl::ToSyncInterface for dyn $async_interface<P> {
type Target = dyn $interface;
}
)?
@@ -1127,6 +1137,10 @@ macro_rules! declare_binder_enum {
}
impl $crate::binder_impl::Deserialize for $enum {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType { Self::UninitType::default() }
+ fn from_init(value: Self) -> Self::UninitType { value }
+
fn deserialize(parcel: &$crate::binder_impl::BorrowedParcel<'_>) -> std::result::Result<Self, $crate::StatusCode> {
parcel.read().map(Self)
}
diff --git a/libs/binder/rust/src/error.rs b/libs/binder/rust/src/error.rs
index f6b09ed8fe..eb04cc3153 100644
--- a/libs/binder/rust/src/error.rs
+++ b/libs/binder/rust/src/error.rs
@@ -20,6 +20,7 @@ use crate::sys;
use std::error;
use std::ffi::{CStr, CString};
use std::fmt::{Debug, Display, Formatter, Result as FmtResult};
+use std::ptr;
use std::result;
pub use sys::binder_status_t as status_t;
@@ -92,7 +93,7 @@ fn parse_exception_code(code: i32) -> ExceptionCode {
/// track of and chain binder errors along with service specific errors.
///
/// Used in AIDL transactions to represent failed transactions.
-pub struct Status(*mut sys::AStatus);
+pub struct Status(ptr::NonNull<sys::AStatus>);
// Safety: The `AStatus` that the `Status` points to must have an entirely thread-safe API for the
// duration of the `Status` object's lifetime. We ensure this by not allowing mutation of a `Status`
@@ -111,43 +112,37 @@ fn to_cstring<T: AsRef<str>>(message: T) -> Option<CString> {
impl Status {
/// Create a status object representing a successful transaction.
pub fn ok() -> Self {
- let ptr = unsafe {
- // Safety: `AStatus_newOk` always returns a new, heap allocated
- // pointer to an `ASTatus` object, so we know this pointer will be
- // valid.
- //
- // Rust takes ownership of the returned pointer.
- sys::AStatus_newOk()
- };
- Self(ptr)
+ // Safety: `AStatus_newOk` always returns a new, heap allocated
+ // pointer to an `ASTatus` object, so we know this pointer will be
+ // valid.
+ //
+ // Rust takes ownership of the returned pointer.
+ let ptr = unsafe { sys::AStatus_newOk() };
+ Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
}
/// Create a status object from a service specific error
pub fn new_service_specific_error(err: i32, message: Option<&CStr>) -> Status {
let ptr = if let Some(message) = message {
- unsafe {
- // Safety: Any i32 is a valid service specific error for the
- // error code parameter. We construct a valid, null-terminated
- // `CString` from the message, which must be a valid C-style
- // string to pass as the message. This function always returns a
- // new, heap allocated pointer to an `AStatus` object, so we
- // know the returned pointer will be valid.
- //
- // Rust takes ownership of the returned pointer.
- sys::AStatus_fromServiceSpecificErrorWithMessage(err, message.as_ptr())
- }
+ // Safety: Any i32 is a valid service specific error for the
+ // error code parameter. We construct a valid, null-terminated
+ // `CString` from the message, which must be a valid C-style
+ // string to pass as the message. This function always returns a
+ // new, heap allocated pointer to an `AStatus` object, so we
+ // know the returned pointer will be valid.
+ //
+ // Rust takes ownership of the returned pointer.
+ unsafe { sys::AStatus_fromServiceSpecificErrorWithMessage(err, message.as_ptr()) }
} else {
- unsafe {
- // Safety: Any i32 is a valid service specific error for the
- // error code parameter. This function always returns a new,
- // heap allocated pointer to an `AStatus` object, so we know the
- // returned pointer will be valid.
- //
- // Rust takes ownership of the returned pointer.
- sys::AStatus_fromServiceSpecificError(err)
- }
+ // Safety: Any i32 is a valid service specific error for the
+ // error code parameter. This function always returns a new,
+ // heap allocated pointer to an `AStatus` object, so we know the
+ // returned pointer will be valid.
+ //
+ // Rust takes ownership of the returned pointer.
+ unsafe { sys::AStatus_fromServiceSpecificError(err) }
};
- Self(ptr)
+ Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
}
/// Creates a status object from a service specific error.
@@ -158,10 +153,12 @@ impl Status {
/// Create a status object from an exception code
pub fn new_exception(exception: ExceptionCode, message: Option<&CStr>) -> Status {
if let Some(message) = message {
+ // Safety: the C string pointer is valid and not retained by the
+ // function.
let ptr = unsafe {
sys::AStatus_fromExceptionCodeWithMessage(exception as i32, message.as_ptr())
};
- Self(ptr)
+ Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
} else {
exception.into()
}
@@ -181,42 +178,36 @@ impl Status {
///
/// This constructor is safe iff `ptr` is a valid pointer to an `AStatus`.
pub(crate) unsafe fn from_ptr(ptr: *mut sys::AStatus) -> Self {
- Self(ptr)
+ Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
}
/// Returns `true` if this status represents a successful transaction.
pub fn is_ok(&self) -> bool {
- unsafe {
- // Safety: `Status` always contains a valid `AStatus` pointer, so we
- // are always passing a valid pointer to `AStatus_isOk` here.
- sys::AStatus_isOk(self.as_native())
- }
+ // Safety: `Status` always contains a valid `AStatus` pointer, so we
+ // are always passing a valid pointer to `AStatus_isOk` here.
+ unsafe { sys::AStatus_isOk(self.as_native()) }
}
/// Returns a description of the status.
pub fn get_description(&self) -> String {
- let description_ptr = unsafe {
- // Safety: `Status` always contains a valid `AStatus` pointer, so we
- // are always passing a valid pointer to `AStatus_getDescription`
- // here.
- //
- // `AStatus_getDescription` always returns a valid pointer to a null
- // terminated C string. Rust is responsible for freeing this pointer
- // via `AStatus_deleteDescription`.
- sys::AStatus_getDescription(self.as_native())
- };
- let description = unsafe {
- // Safety: `AStatus_getDescription` always returns a valid C string,
- // which can be safely converted to a `CStr`.
- CStr::from_ptr(description_ptr)
- };
+ // Safety: `Status` always contains a valid `AStatus` pointer, so we
+ // are always passing a valid pointer to `AStatus_getDescription`
+ // here.
+ //
+ // `AStatus_getDescription` always returns a valid pointer to a null
+ // terminated C string. Rust is responsible for freeing this pointer
+ // via `AStatus_deleteDescription`.
+ let description_ptr = unsafe { sys::AStatus_getDescription(self.as_native()) };
+ // Safety: `AStatus_getDescription` always returns a valid C string,
+ // which can be safely converted to a `CStr`.
+ let description = unsafe { CStr::from_ptr(description_ptr) };
let description = description.to_string_lossy().to_string();
+ // Safety: `description_ptr` was returned from
+ // `AStatus_getDescription` above, and must be freed via
+ // `AStatus_deleteDescription`. We must not access the pointer after
+ // this call, so we copy it into an owned string above and return
+ // that string.
unsafe {
- // Safety: `description_ptr` was returned from
- // `AStatus_getDescription` above, and must be freed via
- // `AStatus_deleteDescription`. We must not access the pointer after
- // this call, so we copy it into an owned string above and return
- // that string.
sys::AStatus_deleteDescription(description_ptr);
}
description
@@ -224,12 +215,10 @@ impl Status {
/// Returns the exception code of the status.
pub fn exception_code(&self) -> ExceptionCode {
- let code = unsafe {
- // Safety: `Status` always contains a valid `AStatus` pointer, so we
- // are always passing a valid pointer to `AStatus_getExceptionCode`
- // here.
- sys::AStatus_getExceptionCode(self.as_native())
- };
+ // Safety: `Status` always contains a valid `AStatus` pointer, so we
+ // are always passing a valid pointer to `AStatus_getExceptionCode`
+ // here.
+ let code = unsafe { sys::AStatus_getExceptionCode(self.as_native()) };
parse_exception_code(code)
}
@@ -240,11 +229,9 @@ impl Status {
/// exception or a service specific error. To find out if this transaction
/// as a whole is okay, use [`is_ok`](Self::is_ok) instead.
pub fn transaction_error(&self) -> StatusCode {
- let code = unsafe {
- // Safety: `Status` always contains a valid `AStatus` pointer, so we
- // are always passing a valid pointer to `AStatus_getStatus` here.
- sys::AStatus_getStatus(self.as_native())
- };
+ // Safety: `Status` always contains a valid `AStatus` pointer, so we
+ // are always passing a valid pointer to `AStatus_getStatus` here.
+ let code = unsafe { sys::AStatus_getStatus(self.as_native()) };
parse_status_code(code)
}
@@ -257,12 +244,10 @@ impl Status {
/// find out if this transaction as a whole is okay, use
/// [`is_ok`](Self::is_ok) instead.
pub fn service_specific_error(&self) -> i32 {
- unsafe {
- // Safety: `Status` always contains a valid `AStatus` pointer, so we
- // are always passing a valid pointer to
- // `AStatus_getServiceSpecificError` here.
- sys::AStatus_getServiceSpecificError(self.as_native())
- }
+ // Safety: `Status` always contains a valid `AStatus` pointer, so we
+ // are always passing a valid pointer to
+ // `AStatus_getServiceSpecificError` here.
+ unsafe { sys::AStatus_getServiceSpecificError(self.as_native()) }
}
/// Calls `op` if the status was ok, otherwise returns an `Err` value of
@@ -320,25 +305,21 @@ impl From<StatusCode> for Status {
impl From<status_t> for Status {
fn from(status: status_t) -> Status {
- let ptr = unsafe {
- // Safety: `AStatus_fromStatus` expects any `status_t` integer, so
- // this is a safe FFI call. Unknown values will be coerced into
- // UNKNOWN_ERROR.
- sys::AStatus_fromStatus(status)
- };
- Self(ptr)
+ // Safety: `AStatus_fromStatus` expects any `status_t` integer, so
+ // this is a safe FFI call. Unknown values will be coerced into
+ // UNKNOWN_ERROR.
+ let ptr = unsafe { sys::AStatus_fromStatus(status) };
+ Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
}
}
impl From<ExceptionCode> for Status {
fn from(code: ExceptionCode) -> Status {
- let ptr = unsafe {
- // Safety: `AStatus_fromExceptionCode` expects any
- // `binder_exception_t` (i32) integer, so this is a safe FFI call.
- // Unknown values will be coerced into EX_TRANSACTION_FAILED.
- sys::AStatus_fromExceptionCode(code as i32)
- };
- Self(ptr)
+ // Safety: `AStatus_fromExceptionCode` expects any
+ // `binder_exception_t` (i32) integer, so this is a safe FFI call.
+ // Unknown values will be coerced into EX_TRANSACTION_FAILED.
+ let ptr = unsafe { sys::AStatus_fromExceptionCode(code as i32) };
+ Self(ptr::NonNull::new(ptr).expect("Unexpected null AStatus pointer"))
}
}
@@ -362,30 +343,118 @@ impl From<Status> for status_t {
impl Drop for Status {
fn drop(&mut self) {
+ // Safety: `Status` manages the lifetime of its inner `AStatus`
+ // pointee, so we need to delete it here. We know that the pointer
+ // will be valid here since `Status` always contains a valid pointer
+ // while it is alive.
unsafe {
- // Safety: `Status` manages the lifetime of its inner `AStatus`
- // pointee, so we need to delete it here. We know that the pointer
- // will be valid here since `Status` always contains a valid pointer
- // while it is alive.
- sys::AStatus_delete(self.0);
+ sys::AStatus_delete(self.0.as_mut());
}
}
}
-/// # Safety
-///
-/// `Status` always contains a valid pointer to an `AStatus` object, so we can
-/// trivially convert it to a correctly-typed raw pointer.
+/// Safety: `Status` always contains a valid pointer to an `AStatus` object, so
+/// we can trivially convert it to a correctly-typed raw pointer.
///
/// Care must be taken that the returned pointer is only dereferenced while the
/// `Status` object is still alive.
unsafe impl AsNative<sys::AStatus> for Status {
fn as_native(&self) -> *const sys::AStatus {
- self.0
+ self.0.as_ptr()
}
fn as_native_mut(&mut self) -> *mut sys::AStatus {
- self.0
+ // Safety: The pointer will be valid here since `Status` always contains
+ // a valid and initialized pointer while it is alive.
+ unsafe { self.0.as_mut() }
+ }
+}
+
+/// A conversion from `std::result::Result<T, E>` to `binder::Result<T>`. If this type is `Ok(T)`,
+/// it's returned as is. If this type is `Err(E)`, `E` is converted into `Status` which can be
+/// either a general binder exception, or a service-specific exception.
+///
+/// # Examples
+///
+/// ```
+/// // std::io::Error is formatted as the exception's message
+/// fn file_exists(name: &str) -> binder::Result<bool> {
+/// std::fs::metadata(name)
+/// .or_service_specific_exception(NOT_FOUND)?
+/// }
+///
+/// // A custom function is used to create the exception's message
+/// fn file_exists(name: &str) -> binder::Result<bool> {
+/// std::fs::metadata(name)
+/// .or_service_specific_exception_with(NOT_FOUND,
+/// |e| format!("file {} not found: {:?}", name, e))?
+/// }
+///
+/// // anyhow::Error is formatted as the exception's message
+/// use anyhow::{Context, Result};
+/// fn file_exists(name: &str) -> binder::Result<bool> {
+/// std::fs::metadata(name)
+/// .context("file {} not found")
+/// .or_service_specific_exception(NOT_FOUND)?
+/// }
+///
+/// // General binder exceptions can be created similarly
+/// fn file_exists(name: &str) -> binder::Result<bool> {
+/// std::fs::metadata(name)
+/// .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
+/// }
+/// ```
+pub trait IntoBinderResult<T, E> {
+ /// Converts the embedded error into a general binder exception of code `exception`. The
+ /// message of the exception is set by formatting the error for debugging.
+ fn or_binder_exception(self, exception: ExceptionCode) -> result::Result<T, Status>;
+
+ /// Converts the embedded error into a general binder exception of code `exception`. The
+ /// message of the exception is set by lazily evaluating the `op` function.
+ fn or_binder_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
+ self,
+ exception: ExceptionCode,
+ op: O,
+ ) -> result::Result<T, Status>;
+
+ /// Converts the embedded error into a service-specific binder exception. `error_code` is used
+ /// to distinguish different service-specific binder exceptions. The message of the exception
+ /// is set by formatting the error for debugging.
+ fn or_service_specific_exception(self, error_code: i32) -> result::Result<T, Status>;
+
+ /// Converts the embedded error into a service-specific binder exception. `error_code` is used
+ /// to distinguish different service-specific binder exceptions. The message of the exception
+ /// is set by lazily evaluating the `op` function.
+ fn or_service_specific_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
+ self,
+ error_code: i32,
+ op: O,
+ ) -> result::Result<T, Status>;
+}
+
+impl<T, E: std::fmt::Debug> IntoBinderResult<T, E> for result::Result<T, E> {
+ fn or_binder_exception(self, exception: ExceptionCode) -> result::Result<T, Status> {
+ self.or_binder_exception_with(exception, |e| format!("{:?}", e))
+ }
+
+ fn or_binder_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
+ self,
+ exception: ExceptionCode,
+ op: O,
+ ) -> result::Result<T, Status> {
+ self.map_err(|e| Status::new_exception_str(exception, Some(op(e))))
+ }
+
+ fn or_service_specific_exception(self, error_code: i32) -> result::Result<T, Status> {
+ self.or_service_specific_exception_with(error_code, |e| format!("{:?}", e))
+ }
+
+ fn or_service_specific_exception_with<M: AsRef<str>, O: FnOnce(E) -> M>(
+ self,
+ error_code: i32,
+ op: O,
+ ) -> result::Result<T, Status> {
+ self.map_err(|e| Status::new_service_specific_error_str(error_code, Some(op(e))))
}
}
@@ -425,4 +494,66 @@ mod tests {
assert_eq!(status.service_specific_error(), 0);
assert_eq!(status.get_description(), "Status(-5, EX_ILLEGAL_STATE): ''".to_string());
}
+
+ #[test]
+ fn convert_to_service_specific_exception() {
+ let res: std::result::Result<(), Status> =
+ Err("message").or_service_specific_exception(-42);
+
+ assert!(res.is_err());
+ let status = res.unwrap_err();
+ assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC);
+ assert_eq!(status.service_specific_error(), -42);
+ assert_eq!(
+ status.get_description(),
+ "Status(-8, EX_SERVICE_SPECIFIC): '-42: \"message\"'".to_string()
+ );
+ }
+
+ #[test]
+ fn convert_to_service_specific_exception_with() {
+ let res: std::result::Result<(), Status> = Err("message")
+ .or_service_specific_exception_with(-42, |e| format!("outer message: {:?}", e));
+
+ assert!(res.is_err());
+ let status = res.unwrap_err();
+ assert_eq!(status.exception_code(), ExceptionCode::SERVICE_SPECIFIC);
+ assert_eq!(status.service_specific_error(), -42);
+ assert_eq!(
+ status.get_description(),
+ "Status(-8, EX_SERVICE_SPECIFIC): '-42: outer message: \"message\"'".to_string()
+ );
+ }
+
+ #[test]
+ fn convert_to_binder_exception() {
+ let res: std::result::Result<(), Status> =
+ Err("message").or_binder_exception(ExceptionCode::ILLEGAL_STATE);
+
+ assert!(res.is_err());
+ let status = res.unwrap_err();
+ assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE);
+ assert_eq!(status.service_specific_error(), 0);
+ assert_eq!(
+ status.get_description(),
+ "Status(-5, EX_ILLEGAL_STATE): '\"message\"'".to_string()
+ );
+ }
+
+ #[test]
+ fn convert_to_binder_exception_with() {
+ let res: std::result::Result<(), Status> = Err("message")
+ .or_binder_exception_with(ExceptionCode::ILLEGAL_STATE, |e| {
+ format!("outer message: {:?}", e)
+ });
+
+ assert!(res.is_err());
+ let status = res.unwrap_err();
+ assert_eq!(status.exception_code(), ExceptionCode::ILLEGAL_STATE);
+ assert_eq!(status.service_specific_error(), 0);
+ assert_eq!(
+ status.get_description(),
+ "Status(-5, EX_ILLEGAL_STATE): 'outer message: \"message\"'".to_string()
+ );
+ }
}
diff --git a/libs/binder/rust/src/lib.rs b/libs/binder/rust/src/lib.rs
index 0c8b48f1f5..0f9c58c0a2 100644
--- a/libs/binder/rust/src/lib.rs
+++ b/libs/binder/rust/src/lib.rs
@@ -100,22 +100,25 @@ mod error;
mod native;
mod parcel;
mod proxy;
+#[cfg(not(trusty))]
+mod service;
+#[cfg(not(trusty))]
mod state;
use binder_ndk_sys as sys;
pub use crate::binder_async::{BinderAsyncPool, BoxFuture};
pub use binder::{BinderFeatures, FromIBinder, IBinder, Interface, Strong, Weak};
-pub use error::{ExceptionCode, Status, StatusCode};
-pub use native::{
- add_service, force_lazy_services_persist, is_handling_transaction, register_lazy_service,
- LazyServiceGuard,
-};
+pub use error::{ExceptionCode, IntoBinderResult, Status, StatusCode};
pub use parcel::{ParcelFileDescriptor, Parcelable, ParcelableHolder};
-pub use proxy::{
- get_declared_instances, get_interface, get_service, is_declared, wait_for_interface,
- wait_for_service, DeathRecipient, SpIBinder, WpIBinder,
+pub use proxy::{DeathRecipient, SpIBinder, WpIBinder};
+#[cfg(not(trusty))]
+pub use service::{
+ add_service, force_lazy_services_persist, get_declared_instances, get_interface, get_service,
+ is_declared, is_handling_transaction, register_lazy_service, wait_for_interface,
+ wait_for_service, LazyServiceGuard,
};
+#[cfg(not(trusty))]
pub use state::{ProcessState, ThreadState};
/// Binder result containing a [`Status`] on error.
@@ -134,8 +137,8 @@ pub mod binder_impl {
pub use crate::native::Binder;
pub use crate::parcel::{
BorrowedParcel, Deserialize, DeserializeArray, DeserializeOption, Parcel,
- ParcelableMetadata, Serialize, SerializeArray, SerializeOption, NON_NULL_PARCELABLE_FLAG,
- NULL_PARCELABLE_FLAG,
+ ParcelableMetadata, Serialize, SerializeArray, SerializeOption, UnstructuredParcelable,
+ NON_NULL_PARCELABLE_FLAG, NULL_PARCELABLE_FLAG,
};
pub use crate::proxy::{AssociateClass, Proxy};
}
@@ -144,6 +147,7 @@ pub mod binder_impl {
#[doc(hidden)]
pub mod unstable_api {
pub use crate::binder::AsNative;
+ pub use crate::error::status_result;
pub use crate::proxy::unstable_api::new_spibinder;
pub use crate::sys::AIBinder;
pub use crate::sys::AParcel;
diff --git a/libs/binder/rust/src/native.rs b/libs/binder/rust/src/native.rs
index 5557168055..c87cc94973 100644
--- a/libs/binder/rust/src/native.rs
+++ b/libs/binder/rust/src/native.rs
@@ -23,14 +23,11 @@ use crate::proxy::SpIBinder;
use crate::sys;
use std::convert::TryFrom;
-use std::ffi::{c_void, CStr, CString};
-use std::fs::File;
+use std::ffi::{c_void, CStr};
+use std::io::Write;
use std::mem::ManuallyDrop;
use std::ops::Deref;
use std::os::raw::c_char;
-use std::os::unix::io::FromRawFd;
-use std::slice;
-use std::sync::Mutex;
/// Rust wrapper around Binder remotable objects.
///
@@ -42,7 +39,7 @@ pub struct Binder<T: Remotable> {
rust_object: *mut T,
}
-/// # Safety
+/// Safety:
///
/// A `Binder<T>` is a pair of unique owning pointers to two values:
/// * a C++ ABBinder which the C++ API guarantees can be passed between threads
@@ -54,7 +51,7 @@ pub struct Binder<T: Remotable> {
/// to how `Box<T>` is `Send` if `T` is `Send`.
unsafe impl<T: Remotable> Send for Binder<T> {}
-/// # Safety
+/// Safety:
///
/// A `Binder<T>` is a pair of unique owning pointers to two values:
/// * a C++ ABBinder which is thread-safe, i.e. `Send + Sync`
@@ -89,15 +86,13 @@ impl<T: Remotable> Binder<T> {
pub fn new_with_stability(rust_object: T, stability: Stability) -> Binder<T> {
let class = T::get_class();
let rust_object = Box::into_raw(Box::new(rust_object));
- let ibinder = unsafe {
- // Safety: `AIBinder_new` expects a valid class pointer (which we
- // initialize via `get_class`), and an arbitrary pointer
- // argument. The caller owns the returned `AIBinder` pointer, which
- // is a strong reference to a `BBinder`. This reference should be
- // decremented via `AIBinder_decStrong` when the reference lifetime
- // ends.
- sys::AIBinder_new(class.into(), rust_object as *mut c_void)
- };
+ // Safety: `AIBinder_new` expects a valid class pointer (which we
+ // initialize via `get_class`), and an arbitrary pointer
+ // argument. The caller owns the returned `AIBinder` pointer, which
+ // is a strong reference to a `BBinder`. This reference should be
+ // decremented via `AIBinder_decStrong` when the reference lifetime
+ // ends.
+ let ibinder = unsafe { sys::AIBinder_new(class.into(), rust_object as *mut c_void) };
let mut binder = Binder { ibinder, rust_object };
binder.mark_stability(stability);
binder
@@ -176,15 +171,14 @@ impl<T: Remotable> Binder<T> {
/// }
/// # }
pub fn set_extension(&mut self, extension: &mut SpIBinder) -> Result<()> {
- let status = unsafe {
- // Safety: `AIBinder_setExtension` expects two valid, mutable
- // `AIBinder` pointers. We are guaranteed that both `self` and
- // `extension` contain valid `AIBinder` pointers, because they
- // cannot be initialized without a valid
- // pointer. `AIBinder_setExtension` does not take ownership of
- // either parameter.
- sys::AIBinder_setExtension(self.as_native_mut(), extension.as_native_mut())
- };
+ let status =
+ // Safety: `AIBinder_setExtension` expects two valid, mutable
+ // `AIBinder` pointers. We are guaranteed that both `self` and
+ // `extension` contain valid `AIBinder` pointers, because they
+ // cannot be initialized without a valid
+ // pointer. `AIBinder_setExtension` does not take ownership of
+ // either parameter.
+ unsafe { sys::AIBinder_setExtension(self.as_native_mut(), extension.as_native_mut()) };
status_result(status)
}
@@ -199,9 +193,9 @@ impl<T: Remotable> Binder<T> {
match stability {
Stability::Local => self.mark_local_stability(),
Stability::Vintf => {
+ // Safety: Self always contains a valid `AIBinder` pointer, so
+ // we can always call this C API safely.
unsafe {
- // Safety: Self always contains a valid `AIBinder` pointer, so
- // we can always call this C API safely.
sys::AIBinder_markVintfStability(self.as_native_mut());
}
}
@@ -212,9 +206,9 @@ impl<T: Remotable> Binder<T> {
/// building for android_vendor and system otherwise.
#[cfg(android_vendor)]
fn mark_local_stability(&mut self) {
+ // Safety: Self always contains a valid `AIBinder` pointer, so we can
+ // always call this C API safely.
unsafe {
- // Safety: Self always contains a valid `AIBinder` pointer, so
- // we can always call this C API safely.
sys::AIBinder_markVendorStability(self.as_native_mut());
}
}
@@ -223,9 +217,9 @@ impl<T: Remotable> Binder<T> {
/// building for android_vendor and system otherwise.
#[cfg(not(android_vendor))]
fn mark_local_stability(&mut self) {
+ // Safety: Self always contains a valid `AIBinder` pointer, so we can
+ // always call this C API safely.
unsafe {
- // Safety: Self always contains a valid `AIBinder` pointer, so
- // we can always call this C API safely.
sys::AIBinder_markSystemStability(self.as_native_mut());
}
}
@@ -239,13 +233,13 @@ impl<T: Remotable> Interface for Binder<T> {
/// remotable object, which will prevent the object from being dropped while
/// the `SpIBinder` is alive.
fn as_binder(&self) -> SpIBinder {
+ // Safety: `self.ibinder` is guaranteed to always be a valid pointer
+ // to an `AIBinder` by the `Binder` constructor. We are creating a
+ // copy of the `self.ibinder` strong reference, but
+ // `SpIBinder::from_raw` assumes it receives an owned pointer with
+ // its own strong reference. We first increment the reference count,
+ // so that the new `SpIBinder` will be tracked as a new reference.
unsafe {
- // Safety: `self.ibinder` is guaranteed to always be a valid pointer
- // to an `AIBinder` by the `Binder` constructor. We are creating a
- // copy of the `self.ibinder` strong reference, but
- // `SpIBinder::from_raw` assumes it receives an owned pointer with
- // its own strong reference. We first increment the reference count,
- // so that the new `SpIBinder` will be tracked as a new reference.
sys::AIBinder_incStrong(self.ibinder);
SpIBinder::from_raw(self.ibinder).unwrap()
}
@@ -275,10 +269,20 @@ impl<T: Remotable> InterfaceClassMethods for Binder<T> {
reply: *mut sys::AParcel,
) -> status_t {
let res = {
- let mut reply = BorrowedParcel::from_raw(reply).unwrap();
- let data = BorrowedParcel::from_raw(data as *mut sys::AParcel).unwrap();
- let object = sys::AIBinder_getUserData(binder);
- let binder: &T = &*(object as *const T);
+ // Safety: The caller must give us a parcel pointer which is either
+ // null or valid at least for the duration of this function call. We
+ // don't keep the resulting value beyond the function.
+ let mut reply = unsafe { BorrowedParcel::from_raw(reply).unwrap() };
+ // Safety: The caller must give us a parcel pointer which is either
+ // null or valid at least for the duration of this function call. We
+ // don't keep the resulting value beyond the function.
+ let data = unsafe { BorrowedParcel::from_raw(data as *mut sys::AParcel).unwrap() };
+ // Safety: Our caller promised that `binder` is a non-null, valid
+ // pointer to a local `AIBinder`.
+ let object = unsafe { sys::AIBinder_getUserData(binder) };
+ // Safety: Our caller promised that the binder has a `T` pointer in
+ // its user data.
+ let binder: &T = unsafe { &*(object as *const T) };
binder.on_transact(code, &data, &mut reply)
};
match res {
@@ -295,7 +299,9 @@ impl<T: Remotable> InterfaceClassMethods for Binder<T> {
/// Must be called with a valid pointer to a `T` object. After this call,
/// the pointer will be invalid and should not be dereferenced.
unsafe extern "C" fn on_destroy(object: *mut c_void) {
- drop(Box::from_raw(object as *mut T));
+ // Safety: Our caller promised that `object` is a valid pointer to a
+ // `T`.
+ drop(unsafe { Box::from_raw(object as *mut T) });
}
/// Called whenever a new, local `AIBinder` object is needed of a specific
@@ -320,7 +326,8 @@ impl<T: Remotable> InterfaceClassMethods for Binder<T> {
/// Must be called with a non-null, valid pointer to a local `AIBinder` that
/// contains a `T` pointer in its user data. fd should be a non-owned file
/// descriptor, and args must be an array of null-terminated string
- /// poiinters with length num_args.
+ /// pointers with length num_args.
+ #[cfg(not(trusty))]
unsafe extern "C" fn on_dump(
binder: *mut sys::AIBinder,
fd: i32,
@@ -330,8 +337,10 @@ impl<T: Remotable> InterfaceClassMethods for Binder<T> {
if fd < 0 {
return StatusCode::UNEXPECTED_NULL as status_t;
}
- // We don't own this file, so we need to be careful not to drop it.
- let file = ManuallyDrop::new(File::from_raw_fd(fd));
+ use std::os::fd::FromRawFd;
+ // Safety: Our caller promised that fd is a file descriptor. We don't
+ // own this file descriptor, so we need to be careful not to drop it.
+ let mut file = unsafe { ManuallyDrop::new(std::fs::File::from_raw_fd(fd)) };
if args.is_null() && num_args != 0 {
return StatusCode::UNEXPECTED_NULL as status_t;
@@ -340,21 +349,42 @@ impl<T: Remotable> InterfaceClassMethods for Binder<T> {
let args = if args.is_null() || num_args == 0 {
vec![]
} else {
- slice::from_raw_parts(args, num_args as usize)
- .iter()
- .map(|s| CStr::from_ptr(*s))
- .collect()
+ // Safety: Our caller promised that `args` is an array of
+ // null-terminated string pointers with length `num_args`.
+ unsafe {
+ std::slice::from_raw_parts(args, num_args as usize)
+ .iter()
+ .map(|s| CStr::from_ptr(*s))
+ .collect()
+ }
};
- let object = sys::AIBinder_getUserData(binder);
- let binder: &T = &*(object as *const T);
- let res = binder.on_dump(&file, &args);
+ // Safety: Our caller promised that `binder` is a non-null, valid
+ // pointer to a local `AIBinder`.
+ let object = unsafe { sys::AIBinder_getUserData(binder) };
+ // Safety: Our caller promised that the binder has a `T` pointer in its
+ // user data.
+ let binder: &T = unsafe { &*(object as *const T) };
+ let res = binder.on_dump(&mut *file, &args);
match res {
Ok(()) => 0,
Err(e) => e as status_t,
}
}
+
+ /// Called to handle the `dump` transaction.
+ #[cfg(trusty)]
+ unsafe extern "C" fn on_dump(
+ _binder: *mut sys::AIBinder,
+ _fd: i32,
+ _args: *mut *const c_char,
+ _num_args: u32,
+ ) -> status_t {
+ // This operation is not supported on Trusty right now
+ // because we do not have a uniform way of writing to handles
+ StatusCode::INVALID_OPERATION as status_t
+ }
}
impl<T: Remotable> Drop for Binder<T> {
@@ -363,11 +393,11 @@ impl<T: Remotable> Drop for Binder<T> {
// actually destroys the object, it calls `on_destroy` and we can drop the
// `rust_object` then.
fn drop(&mut self) {
+ // Safety: When `self` is dropped, we can no longer access the
+ // reference, so can decrement the reference count. `self.ibinder` is
+ // always a valid `AIBinder` pointer, so is valid to pass to
+ // `AIBinder_decStrong`.
unsafe {
- // Safety: When `self` is dropped, we can no longer access the
- // reference, so can decrement the reference count. `self.ibinder`
- // is always a valid `AIBinder` pointer, so is valid to pass to
- // `AIBinder_decStrong`.
sys::AIBinder_decStrong(self.ibinder);
}
}
@@ -377,14 +407,11 @@ impl<T: Remotable> Deref for Binder<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
- unsafe {
- // Safety: While `self` is alive, the reference count of the
- // underlying object is > 0 and therefore `on_destroy` cannot be
- // called. Therefore while `self` is alive, we know that
- // `rust_object` is still a valid pointer to a heap allocated object
- // of type `T`.
- &*self.rust_object
- }
+ // Safety: While `self` is alive, the reference count of the underlying
+ // object is > 0 and therefore `on_destroy` cannot be called. Therefore
+ // while `self` is alive, we know that `rust_object` is still a valid
+ // pointer to a heap allocated object of type `T`.
+ unsafe { &*self.rust_object }
}
}
@@ -405,13 +432,10 @@ impl<B: Remotable> TryFrom<SpIBinder> for Binder<B> {
if Some(class) != ibinder.get_class() {
return Err(StatusCode::BAD_TYPE);
}
- let userdata = unsafe {
- // Safety: `SpIBinder` always holds a valid pointer pointer to an
- // `AIBinder`, which we can safely pass to
- // `AIBinder_getUserData`. `ibinder` retains ownership of the
- // returned pointer.
- sys::AIBinder_getUserData(ibinder.as_native_mut())
- };
+ // Safety: `SpIBinder` always holds a valid pointer pointer to an
+ // `AIBinder`, which we can safely pass to `AIBinder_getUserData`.
+ // `ibinder` retains ownership of the returned pointer.
+ let userdata = unsafe { sys::AIBinder_getUserData(ibinder.as_native_mut()) };
if userdata.is_null() {
return Err(StatusCode::UNEXPECTED_NULL);
}
@@ -422,12 +446,10 @@ impl<B: Remotable> TryFrom<SpIBinder> for Binder<B> {
}
}
-/// # Safety
-///
-/// The constructor for `Binder` guarantees that `self.ibinder` will contain a
-/// valid, non-null pointer to an `AIBinder`, so this implementation is type
-/// safe. `self.ibinder` will remain valid for the entire lifetime of `self`
-/// because we hold a strong reference to the `AIBinder` until `self` is
+/// Safety: The constructor for `Binder` guarantees that `self.ibinder` will
+/// contain a valid, non-null pointer to an `AIBinder`, so this implementation
+/// is type safe. `self.ibinder` will remain valid for the entire lifetime of
+/// `self` because we hold a strong reference to the `AIBinder` until `self` is
/// dropped.
unsafe impl<B: Remotable> AsNative<sys::AIBinder> for Binder<B> {
fn as_native(&self) -> *const sys::AIBinder {
@@ -439,115 +461,6 @@ unsafe impl<B: Remotable> AsNative<sys::AIBinder> for Binder<B> {
}
}
-/// Register a new service with the default service manager.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
- let instance = CString::new(identifier).unwrap();
- let status = unsafe {
- // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
- // string pointers. Caller retains ownership of both
- // pointers. `AServiceManager_addService` creates a new strong reference
- // and copies the string, so both pointers need only be valid until the
- // call returns.
- sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr())
- };
- status_result(status)
-}
-
-/// Register a dynamic service via the LazyServiceRegistrar.
-///
-/// Registers the given binder object with the given identifier. If successful,
-/// this service can then be retrieved using that identifier. The service process
-/// will be shut down once all registered services are no longer in use.
-///
-/// If any service in the process is registered as lazy, all should be, otherwise
-/// the process may be shut down while a service is in use.
-///
-/// This function will panic if the identifier contains a 0 byte (NUL).
-pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
- let instance = CString::new(identifier).unwrap();
- let status = unsafe {
- // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
- // string pointers. Caller retains ownership of both
- // pointers. `AServiceManager_registerLazyService` creates a new strong reference
- // and copies the string, so both pointers need only be valid until the
- // call returns.
-
- sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
- };
- status_result(status)
-}
-
-/// Prevent a process which registers lazy services from being shut down even when none
-/// of the services is in use.
-///
-/// If persist is true then shut down will be blocked until this function is called again with
-/// persist false. If this is to be the initial state, call this function before calling
-/// register_lazy_service.
-///
-/// Consider using [`LazyServiceGuard`] rather than calling this directly.
-pub fn force_lazy_services_persist(persist: bool) {
- unsafe {
- // Safety: No borrowing or transfer of ownership occurs here.
- sys::AServiceManager_forceLazyServicesPersist(persist)
- }
-}
-
-/// An RAII object to ensure a process which registers lazy services is not killed. During the
-/// lifetime of any of these objects the service manager will not not kill the process even if none
-/// of its lazy services are in use.
-#[must_use]
-#[derive(Debug)]
-pub struct LazyServiceGuard {
- // Prevent construction outside this module.
- _private: (),
-}
-
-// Count of how many LazyServiceGuard objects are in existence.
-static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
-
-impl LazyServiceGuard {
- /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
- /// process.
- pub fn new() -> Self {
- let mut count = GUARD_COUNT.lock().unwrap();
- *count += 1;
- if *count == 1 {
- // It's important that we make this call with the mutex held, to make sure
- // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
- // sequenced. (That also means we can't just use an AtomicU64.)
- force_lazy_services_persist(true);
- }
- Self { _private: () }
- }
-}
-
-impl Drop for LazyServiceGuard {
- fn drop(&mut self) {
- let mut count = GUARD_COUNT.lock().unwrap();
- *count -= 1;
- if *count == 0 {
- force_lazy_services_persist(false);
- }
- }
-}
-
-impl Clone for LazyServiceGuard {
- fn clone(&self) -> Self {
- Self::new()
- }
-}
-
-impl Default for LazyServiceGuard {
- fn default() -> Self {
- Self::new()
- }
-}
-
/// Tests often create a base BBinder instance; so allowing the unit
/// type to be remotable translates nicely to Binder::new(()).
impl Remotable for () {
@@ -564,7 +477,7 @@ impl Remotable for () {
Ok(())
}
- fn on_dump(&self, _file: &File, _args: &[&CStr]) -> Result<()> {
+ fn on_dump(&self, _writer: &mut dyn Write, _args: &[&CStr]) -> Result<()> {
Ok(())
}
@@ -572,12 +485,3 @@ impl Remotable for () {
}
impl Interface for () {}
-
-/// Determine whether the current thread is currently executing an incoming
-/// transaction.
-pub fn is_handling_transaction() -> bool {
- unsafe {
- // Safety: This method is always safe to call.
- sys::AIBinder_isHandlingTransaction()
- }
-}
diff --git a/libs/binder/rust/src/parcel.rs b/libs/binder/rust/src/parcel.rs
index e4c568eab2..3bfc425ee3 100644
--- a/libs/binder/rust/src/parcel.rs
+++ b/libs/binder/rust/src/parcel.rs
@@ -34,7 +34,7 @@ mod parcelable_holder;
pub use self::file_descriptor::ParcelFileDescriptor;
pub use self::parcelable::{
Deserialize, DeserializeArray, DeserializeOption, Parcelable, Serialize, SerializeArray,
- SerializeOption, NON_NULL_PARCELABLE_FLAG, NULL_PARCELABLE_FLAG,
+ SerializeOption, UnstructuredParcelable, NON_NULL_PARCELABLE_FLAG, NULL_PARCELABLE_FLAG,
};
pub use self::parcelable_holder::{ParcelableHolder, ParcelableMetadata};
@@ -52,11 +52,12 @@ pub struct Parcel {
ptr: NonNull<sys::AParcel>,
}
-/// # Safety
+/// Safety: This type guarantees that it owns the AParcel and that all access to
+/// the AParcel happens through the Parcel, so it is ok to send across threads.
///
-/// This type guarantees that it owns the AParcel and that all access to
-/// the AParcel happens through the Parcel, so it is ok to send across
-/// threads.
+/// It would not be okay to implement Sync, because that would allow you to call
+/// the reading methods from several threads in parallel, which would be a data
+/// race on the cursor position inside the AParcel.
unsafe impl Send for Parcel {}
/// Container for a message (data and object references) that can be sent
@@ -73,11 +74,9 @@ pub struct BorrowedParcel<'a> {
impl Parcel {
/// Create a new empty `Parcel`.
pub fn new() -> Parcel {
- let ptr = unsafe {
- // Safety: If `AParcel_create` succeeds, it always returns
- // a valid pointer. If it fails, the process will crash.
- sys::AParcel_create()
- };
+ // Safety: If `AParcel_create` succeeds, it always returns
+ // a valid pointer. If it fails, the process will crash.
+ let ptr = unsafe { sys::AParcel_create() };
Self { ptr: NonNull::new(ptr).expect("AParcel_create returned null pointer") }
}
@@ -171,10 +170,8 @@ impl<'a> BorrowedParcel<'a> {
}
}
-/// # Safety
-///
-/// The `Parcel` constructors guarantee that a `Parcel` object will always
-/// contain a valid pointer to an `AParcel`.
+/// Safety: The `Parcel` constructors guarantee that a `Parcel` object will
+/// always contain a valid pointer to an `AParcel`.
unsafe impl AsNative<sys::AParcel> for Parcel {
fn as_native(&self) -> *const sys::AParcel {
self.ptr.as_ptr()
@@ -185,10 +182,8 @@ unsafe impl AsNative<sys::AParcel> for Parcel {
}
}
-/// # Safety
-///
-/// The `BorrowedParcel` constructors guarantee that a `BorrowedParcel` object
-/// will always contain a valid pointer to an `AParcel`.
+/// Safety: The `BorrowedParcel` constructors guarantee that a `BorrowedParcel`
+/// object will always contain a valid pointer to an `AParcel`.
unsafe impl<'a> AsNative<sys::AParcel> for BorrowedParcel<'a> {
fn as_native(&self) -> *const sys::AParcel {
self.ptr.as_ptr()
@@ -203,10 +198,8 @@ unsafe impl<'a> AsNative<sys::AParcel> for BorrowedParcel<'a> {
impl<'a> BorrowedParcel<'a> {
/// Data written to parcelable is zero'd before being deleted or reallocated.
pub fn mark_sensitive(&mut self) {
- unsafe {
- // Safety: guaranteed to have a parcel object, and this method never fails
- sys::AParcel_markSensitive(self.as_native())
- }
+ // Safety: guaranteed to have a parcel object, and this method never fails
+ unsafe { sys::AParcel_markSensitive(self.as_native()) }
}
/// Write a type that implements [`Serialize`] to the parcel.
@@ -265,11 +258,15 @@ impl<'a> BorrowedParcel<'a> {
f(&mut subparcel)?;
}
let end = self.get_data_position();
+ // Safety: start is less than the current size of the parcel data
+ // buffer, because we just got it with `get_data_position`.
unsafe {
self.set_data_position(start)?;
}
assert!(end >= start);
self.write(&(end - start))?;
+ // Safety: end is less than the current size of the parcel data
+ // buffer, because we just got it with `get_data_position`.
unsafe {
self.set_data_position(end)?;
}
@@ -278,20 +275,16 @@ impl<'a> BorrowedParcel<'a> {
/// Returns the current position in the parcel data.
pub fn get_data_position(&self) -> i32 {
- unsafe {
- // Safety: `BorrowedParcel` always contains a valid pointer to an
- // `AParcel`, and this call is otherwise safe.
- sys::AParcel_getDataPosition(self.as_native())
- }
+ // Safety: `BorrowedParcel` always contains a valid pointer to an
+ // `AParcel`, and this call is otherwise safe.
+ unsafe { sys::AParcel_getDataPosition(self.as_native()) }
}
/// Returns the total size of the parcel.
pub fn get_data_size(&self) -> i32 {
- unsafe {
- // Safety: `BorrowedParcel` always contains a valid pointer to an
- // `AParcel`, and this call is otherwise safe.
- sys::AParcel_getDataSize(self.as_native())
- }
+ // Safety: `BorrowedParcel` always contains a valid pointer to an
+ // `AParcel`, and this call is otherwise safe.
+ unsafe { sys::AParcel_getDataSize(self.as_native()) }
}
/// Move the current read/write position in the parcel.
@@ -304,7 +297,9 @@ impl<'a> BorrowedParcel<'a> {
/// accesses are bounds checked, this call is still safe, but we can't rely
/// on that.
pub unsafe fn set_data_position(&self, pos: i32) -> Result<()> {
- status_result(sys::AParcel_setDataPosition(self.as_native(), pos))
+ // Safety: `BorrowedParcel` always contains a valid pointer to an
+ // `AParcel`, and the caller guarantees that `pos` is within bounds.
+ status_result(unsafe { sys::AParcel_setDataPosition(self.as_native(), pos) })
}
/// Append a subset of another parcel.
@@ -317,10 +312,10 @@ impl<'a> BorrowedParcel<'a> {
start: i32,
size: i32,
) -> Result<()> {
+ // Safety: `Parcel::appendFrom` from C++ checks that `start`
+ // and `size` are in bounds, and returns an error otherwise.
+ // Both `self` and `other` always contain valid pointers.
let status = unsafe {
- // Safety: `Parcel::appendFrom` from C++ checks that `start`
- // and `size` are in bounds, and returns an error otherwise.
- // Both `self` and `other` always contain valid pointers.
sys::AParcel_appendFrom(other.as_native(), self.as_native_mut(), start, size)
};
status_result(status)
@@ -418,7 +413,9 @@ impl Parcel {
/// accesses are bounds checked, this call is still safe, but we can't rely
/// on that.
pub unsafe fn set_data_position(&self, pos: i32) -> Result<()> {
- self.borrowed_ref().set_data_position(pos)
+ // Safety: We have the same safety requirements as
+ // `BorrowedParcel::set_data_position`.
+ unsafe { self.borrowed_ref().set_data_position(pos) }
}
/// Append a subset of another parcel.
@@ -461,7 +458,7 @@ impl<'a> BorrowedParcel<'a> {
/// and call a closure with the sub-parcel as its parameter.
/// The closure can keep reading data from the sub-parcel
/// until it runs out of input data. The closure is responsible
- /// for calling [`ReadableSubParcel::has_more_data`] to check for
+ /// for calling `ReadableSubParcel::has_more_data` to check for
/// more data before every read, at least until Rust generators
/// are stabilized.
/// After the closure returns, skip to the end of the current
@@ -504,7 +501,10 @@ impl<'a> BorrowedParcel<'a> {
f(subparcel)?;
// Advance the data position to the actual end,
- // in case the closure read less data than was available
+ // in case the closure read less data than was available.
+ //
+ // Safety: end must be less than the current size of the parcel, because
+ // we checked above against `get_data_size`.
unsafe {
self.set_data_position(end)?;
}
@@ -595,7 +595,7 @@ impl Parcel {
/// and call a closure with the sub-parcel as its parameter.
/// The closure can keep reading data from the sub-parcel
/// until it runs out of input data. The closure is responsible
- /// for calling [`ReadableSubParcel::has_more_data`] to check for
+ /// for calling `ReadableSubParcel::has_more_data` to check for
/// more data before every read, at least until Rust generators
/// are stabilized.
/// After the closure returns, skip to the end of the current
@@ -649,17 +649,17 @@ impl Parcel {
// Internal APIs
impl<'a> BorrowedParcel<'a> {
pub(crate) fn write_binder(&mut self, binder: Option<&SpIBinder>) -> Result<()> {
+ // Safety: `BorrowedParcel` always contains a valid pointer to an
+ // `AParcel`. `AsNative` for `Option<SpIBinder`> will either return
+ // null or a valid pointer to an `AIBinder`, both of which are
+ // valid, safe inputs to `AParcel_writeStrongBinder`.
+ //
+ // This call does not take ownership of the binder. However, it does
+ // require a mutable pointer, which we cannot extract from an
+ // immutable reference, so we clone the binder, incrementing the
+ // refcount before the call. The refcount will be immediately
+ // decremented when this temporary is dropped.
unsafe {
- // Safety: `BorrowedParcel` always contains a valid pointer to an
- // `AParcel`. `AsNative` for `Option<SpIBinder`> will either return
- // null or a valid pointer to an `AIBinder`, both of which are
- // valid, safe inputs to `AParcel_writeStrongBinder`.
- //
- // This call does not take ownership of the binder. However, it does
- // require a mutable pointer, which we cannot extract from an
- // immutable reference, so we clone the binder, incrementing the
- // refcount before the call. The refcount will be immediately
- // decremented when this temporary is dropped.
status_result(sys::AParcel_writeStrongBinder(
self.as_native_mut(),
binder.cloned().as_native_mut(),
@@ -669,33 +669,28 @@ impl<'a> BorrowedParcel<'a> {
pub(crate) fn read_binder(&self) -> Result<Option<SpIBinder>> {
let mut binder = ptr::null_mut();
- let status = unsafe {
- // Safety: `BorrowedParcel` always contains a valid pointer to an
- // `AParcel`. We pass a valid, mutable out pointer to the `binder`
- // parameter. After this call, `binder` will be either null or a
- // valid pointer to an `AIBinder` owned by the caller.
- sys::AParcel_readStrongBinder(self.as_native(), &mut binder)
- };
+ // Safety: `BorrowedParcel` always contains a valid pointer to an
+ // `AParcel`. We pass a valid, mutable out pointer to the `binder`
+ // parameter. After this call, `binder` will be either null or a
+ // valid pointer to an `AIBinder` owned by the caller.
+ let status = unsafe { sys::AParcel_readStrongBinder(self.as_native(), &mut binder) };
status_result(status)?;
- Ok(unsafe {
- // Safety: `binder` is either null or a valid, owned pointer at this
- // point, so can be safely passed to `SpIBinder::from_raw`.
- SpIBinder::from_raw(binder)
- })
+ // Safety: `binder` is either null or a valid, owned pointer at this
+ // point, so can be safely passed to `SpIBinder::from_raw`.
+ Ok(unsafe { SpIBinder::from_raw(binder) })
}
}
impl Drop for Parcel {
fn drop(&mut self) {
// Run the C++ Parcel complete object destructor
- unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. Since we own the parcel, we can safely delete it
- // here.
- sys::AParcel_delete(self.ptr.as_ptr())
- }
+ //
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. Since we own the parcel, we can safely delete it
+ // here.
+ unsafe { sys::AParcel_delete(self.ptr.as_ptr()) }
}
}
@@ -732,6 +727,8 @@ fn test_read_write() {
parcel.write(&1i32).unwrap();
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
parcel.set_data_position(start).unwrap();
}
@@ -748,6 +745,8 @@ fn test_read_data() {
parcel.write(&b"Hello, Binder!\0"[..]).unwrap();
// Skip over string length
+ // SAFETY: str_start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(str_start).is_ok());
}
@@ -756,42 +755,56 @@ fn test_read_data() {
assert!(parcel.read::<bool>().unwrap());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<i8>().unwrap(), 72i8);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<u16>().unwrap(), 25928);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<i32>().unwrap(), 1819043144);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<u32>().unwrap(), 1819043144);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<i64>().unwrap(), 4764857262830019912);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<u64>().unwrap(), 4764857262830019912);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -799,6 +812,8 @@ fn test_read_data() {
assert_eq!(parcel.read::<f32>().unwrap(), 1143139100000000000000000000.0);
assert_eq!(parcel.read::<f32>().unwrap(), 40.043392);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -806,6 +821,8 @@ fn test_read_data() {
assert_eq!(parcel.read::<f64>().unwrap(), 34732488246.197815);
// Skip back to before the string length
+ // SAFETY: str_start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(str_start).is_ok());
}
@@ -819,15 +836,21 @@ fn test_utf8_utf16_conversions() {
let start = parcel.get_data_position();
assert!(parcel.write("Hello, Binder!").is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<Option<String>>().unwrap().unwrap(), "Hello, Binder!",);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(parcel.write("Embedded null \0 inside a string").is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -835,6 +858,8 @@ fn test_utf8_utf16_conversions() {
parcel.read::<Option<String>>().unwrap().unwrap(),
"Embedded null \0 inside a string",
);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -849,6 +874,8 @@ fn test_utf8_utf16_conversions() {
let s3 = "Some more text here.";
assert!(parcel.write(&[s1, s2, s3][..]).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -874,6 +901,8 @@ fn test_sized_write() {
assert_eq!(parcel.get_data_position(), start + expected_len);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
parcel.set_data_position(start).unwrap();
}
@@ -893,6 +922,8 @@ fn test_append_from() {
assert_eq!(4, parcel2.get_data_size());
assert_eq!(Ok(()), parcel2.append_all_from(&parcel1));
assert_eq!(8, parcel2.get_data_size());
+ // SAFETY: 0 is less than the current size of the parcel data buffer, because the parcel is not
+ // empty.
unsafe {
parcel2.set_data_position(0).unwrap();
}
@@ -903,6 +934,8 @@ fn test_append_from() {
assert_eq!(Ok(()), parcel2.append_from(&parcel1, 0, 2));
assert_eq!(Ok(()), parcel2.append_from(&parcel1, 2, 2));
assert_eq!(4, parcel2.get_data_size());
+ // SAFETY: 0 is less than the current size of the parcel data buffer, because the parcel is not
+ // empty.
unsafe {
parcel2.set_data_position(0).unwrap();
}
@@ -911,6 +944,8 @@ fn test_append_from() {
let mut parcel2 = Parcel::new();
assert_eq!(Ok(()), parcel2.append_from(&parcel1, 0, 2));
assert_eq!(2, parcel2.get_data_size());
+ // SAFETY: 0 is less than the current size of the parcel data buffer, because the parcel is not
+ // empty.
unsafe {
parcel2.set_data_position(0).unwrap();
}
diff --git a/libs/binder/rust/src/parcel/file_descriptor.rs b/libs/binder/rust/src/parcel/file_descriptor.rs
index de6d64934a..6afe5ab42b 100644
--- a/libs/binder/rust/src/parcel/file_descriptor.rs
+++ b/libs/binder/rust/src/parcel/file_descriptor.rs
@@ -22,29 +22,28 @@ use crate::binder::AsNative;
use crate::error::{status_result, Result, StatusCode};
use crate::sys;
-use std::fs::File;
-use std::os::unix::io::{AsRawFd, FromRawFd, IntoRawFd, RawFd};
+use std::os::fd::{AsRawFd, FromRawFd, IntoRawFd, OwnedFd, RawFd};
/// Rust version of the Java class android.os.ParcelFileDescriptor
#[derive(Debug)]
-pub struct ParcelFileDescriptor(File);
+pub struct ParcelFileDescriptor(OwnedFd);
impl ParcelFileDescriptor {
/// Create a new `ParcelFileDescriptor`
- pub fn new(file: File) -> Self {
- Self(file)
+ pub fn new<F: Into<OwnedFd>>(fd: F) -> Self {
+ Self(fd.into())
}
}
-impl AsRef<File> for ParcelFileDescriptor {
- fn as_ref(&self) -> &File {
+impl AsRef<OwnedFd> for ParcelFileDescriptor {
+ fn as_ref(&self) -> &OwnedFd {
&self.0
}
}
-impl From<ParcelFileDescriptor> for File {
- fn from(file: ParcelFileDescriptor) -> File {
- file.0
+impl From<ParcelFileDescriptor> for OwnedFd {
+ fn from(fd: ParcelFileDescriptor) -> OwnedFd {
+ fd.0
}
}
@@ -73,14 +72,12 @@ impl Eq for ParcelFileDescriptor {}
impl Serialize for ParcelFileDescriptor {
fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()> {
let fd = self.0.as_raw_fd();
- let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. Likewise, `ParcelFileDescriptor` always contains a
- // valid file, so we can borrow a valid file
- // descriptor. `AParcel_writeParcelFileDescriptor` does NOT take
- // ownership of the fd, so we need not duplicate it first.
- sys::AParcel_writeParcelFileDescriptor(parcel.as_native_mut(), fd)
- };
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. Likewise, `ParcelFileDescriptor` always contains a
+ // valid file, so we can borrow a valid file
+ // descriptor. `AParcel_writeParcelFileDescriptor` does NOT take
+ // ownership of the fd, so we need not duplicate it first.
+ let status = unsafe { sys::AParcel_writeParcelFileDescriptor(parcel.as_native_mut(), fd) };
status_result(status)
}
}
@@ -92,13 +89,12 @@ impl SerializeOption for ParcelFileDescriptor {
if let Some(f) = this {
f.serialize(parcel)
} else {
- let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. `AParcel_writeParcelFileDescriptor` accepts the
- // value `-1` as the file descriptor to signify serializing a
- // null file descriptor.
- sys::AParcel_writeParcelFileDescriptor(parcel.as_native_mut(), -1i32)
- };
+ let status =
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. `AParcel_writeParcelFileDescriptor` accepts the
+ // value `-1` as the file descriptor to signify serializing a
+ // null file descriptor.
+ unsafe { sys::AParcel_writeParcelFileDescriptor(parcel.as_native_mut(), -1i32) };
status_result(status)
}
}
@@ -107,31 +103,37 @@ impl SerializeOption for ParcelFileDescriptor {
impl DeserializeOption for ParcelFileDescriptor {
fn deserialize_option(parcel: &BorrowedParcel<'_>) -> Result<Option<Self>> {
let mut fd = -1i32;
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. We pass a valid mutable pointer to an i32, which
+ // `AParcel_readParcelFileDescriptor` assigns the valid file
+ // descriptor into, or `-1` if deserializing a null file
+ // descriptor. The read function passes ownership of the file
+ // descriptor to its caller if it was non-null, so we must take
+ // ownership of the file and ensure that it is eventually closed.
unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. We pass a valid mutable pointer to an i32, which
- // `AParcel_readParcelFileDescriptor` assigns the valid file
- // descriptor into, or `-1` if deserializing a null file
- // descriptor. The read function passes ownership of the file
- // descriptor to its caller if it was non-null, so we must take
- // ownership of the file and ensure that it is eventually closed.
status_result(sys::AParcel_readParcelFileDescriptor(parcel.as_native(), &mut fd))?;
}
if fd < 0 {
Ok(None)
} else {
- let file = unsafe {
- // Safety: At this point, we know that the file descriptor was
- // not -1, so must be a valid, owned file descriptor which we
- // can safely turn into a `File`.
- File::from_raw_fd(fd)
- };
+ // Safety: At this point, we know that the file descriptor was
+ // not -1, so must be a valid, owned file descriptor which we
+ // can safely turn into a `File`.
+ let file = unsafe { OwnedFd::from_raw_fd(fd) };
Ok(Some(ParcelFileDescriptor::new(file)))
}
}
}
impl Deserialize for ParcelFileDescriptor {
+ type UninitType = Option<Self>;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ Some(value)
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
Deserialize::deserialize(parcel).transpose().unwrap_or(Err(StatusCode::UNEXPECTED_NULL))
}
diff --git a/libs/binder/rust/src/parcel/parcelable.rs b/libs/binder/rust/src/parcel/parcelable.rs
index 6f4c37530f..33dfe19fe9 100644
--- a/libs/binder/rust/src/parcel/parcelable.rs
+++ b/libs/binder/rust/src/parcel/parcelable.rs
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-use crate::binder::{AsNative, FromIBinder, Stability, Strong};
+use crate::binder::{AsNative, FromIBinder, Interface, Stability, Strong};
use crate::error::{status_result, status_t, Result, Status, StatusCode};
use crate::parcel::BorrowedParcel;
use crate::proxy::SpIBinder;
@@ -22,12 +22,12 @@ use crate::sys;
use std::convert::{TryFrom, TryInto};
use std::ffi::c_void;
-use std::mem::{self, ManuallyDrop, MaybeUninit};
+use std::mem::{self, ManuallyDrop};
use std::os::raw::c_char;
use std::ptr;
use std::slice;
-/// Super-trait for Binder parcelables.
+/// Super-trait for structured Binder parcelables, i.e. those generated from AIDL.
///
/// This trait is equivalent `android::Parcelable` in C++,
/// and defines a common interface that all parcelables need
@@ -50,20 +50,69 @@ pub trait Parcelable {
fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<()>;
}
-/// A struct whose instances can be written to a [`Parcel`].
+/// Super-trait for unstructured Binder parcelables, i.e. those implemented manually.
+///
+/// These differ from structured parcelables in that they may not have a reasonable default value
+/// and so aren't required to implement `Default`.
+pub trait UnstructuredParcelable: Sized {
+ /// Internal serialization function for parcelables.
+ ///
+ /// This method is mainly for internal use. `Serialize::serialize` and its variants are
+ /// generally preferred over calling this function, since the former also prepend a header.
+ fn write_to_parcel(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()>;
+
+ /// Internal deserialization function for parcelables.
+ ///
+ /// This method is mainly for internal use. `Deserialize::deserialize` and its variants are
+ /// generally preferred over calling this function, since the former also parse the additional
+ /// header.
+ fn from_parcel(parcel: &BorrowedParcel<'_>) -> Result<Self>;
+
+ /// Internal deserialization function for parcelables.
+ ///
+ /// This method is mainly for internal use. `Deserialize::deserialize_from` and its variants are
+ /// generally preferred over calling this function, since the former also parse the additional
+ /// header.
+ fn read_from_parcel(&mut self, parcel: &BorrowedParcel<'_>) -> Result<()> {
+ *self = Self::from_parcel(parcel)?;
+ Ok(())
+ }
+}
+
+/// A struct whose instances can be written to a [`crate::parcel::Parcel`].
// Might be able to hook this up as a serde backend in the future?
pub trait Serialize {
- /// Serialize this instance into the given [`Parcel`].
+ /// Serialize this instance into the given [`crate::parcel::Parcel`].
fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()>;
}
-/// A struct whose instances can be restored from a [`Parcel`].
+/// A struct whose instances can be restored from a [`crate::parcel::Parcel`].
// Might be able to hook this up as a serde backend in the future?
pub trait Deserialize: Sized {
- /// Deserialize an instance from the given [`Parcel`].
+ /// Type for the uninitialized value of this type. Will be either `Self`
+ /// if the type implements `Default`, `Option<Self>` otherwise.
+ type UninitType;
+
+ /// Assert at compile-time that `Self` and `Self::UninitType` have the same
+ /// size and alignment. This will either fail to compile or evaluate to `true`.
+ /// The only two macros that work here are `panic!` and `assert!`, so we cannot
+ /// use `assert_eq!`.
+ const ASSERT_UNINIT_SIZE_AND_ALIGNMENT: bool = {
+ assert!(std::mem::size_of::<Self>() == std::mem::size_of::<Self::UninitType>());
+ assert!(std::mem::align_of::<Self>() == std::mem::align_of::<Self::UninitType>());
+ true
+ };
+
+ /// Return an uninitialized or default-initialized value for this type.
+ fn uninit() -> Self::UninitType;
+
+ /// Convert an initialized value of type `Self` into `Self::UninitType`.
+ fn from_init(value: Self) -> Self::UninitType;
+
+ /// Deserialize an instance from the given [`crate::parcel::Parcel`].
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self>;
- /// Deserialize an instance from the given [`Parcel`] onto the
+ /// Deserialize an instance from the given [`crate::parcel::Parcel`] onto the
/// current object. This operation will overwrite the old value
/// partially or completely, depending on how much data is available.
fn deserialize_from(&mut self, parcel: &BorrowedParcel<'_>) -> Result<()> {
@@ -82,8 +131,8 @@ pub trait Deserialize: Sized {
pub trait SerializeArray: Serialize + Sized {
/// Serialize an array of this type into the given parcel.
fn serialize_array(slice: &[Self], parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+ // Safety: Safe FFI, slice will always be a safe pointer to pass.
let res = unsafe {
- // Safety: Safe FFI, slice will always be a safe pointer to pass.
sys::AParcel_writeParcelableArray(
parcel.as_native_mut(),
slice.as_ptr() as *const c_void,
@@ -97,7 +146,9 @@ pub trait SerializeArray: Serialize + Sized {
/// Callback to serialize an element of a generic parcelable array.
///
-/// Safety: We are relying on binder_ndk to not overrun our slice. As long as it
+/// # Safety
+///
+/// We are relying on binder_ndk to not overrun our slice. As long as it
/// doesn't provide an index larger than the length of the original slice in
/// serialize_array, this operation is safe. The index provided is zero-based.
unsafe extern "C" fn serialize_element<T: Serialize>(
@@ -105,9 +156,14 @@ unsafe extern "C" fn serialize_element<T: Serialize>(
array: *const c_void,
index: usize,
) -> status_t {
- let slice: &[T] = slice::from_raw_parts(array.cast(), index + 1);
-
- let mut parcel = match BorrowedParcel::from_raw(parcel) {
+ // Safety: The caller guarantees that `array` is a valid pointer of the
+ // appropriate type.
+ let slice: &[T] = unsafe { slice::from_raw_parts(array.cast(), index + 1) };
+
+ // Safety: The caller must give us a parcel pointer which is either null or
+ // valid at least for the duration of this function call. We don't keep the
+ // resulting value beyond the function.
+ let mut parcel = match unsafe { BorrowedParcel::from_raw(parcel) } {
None => return StatusCode::UNEXPECTED_NULL as status_t,
Some(p) => p,
};
@@ -121,10 +177,10 @@ unsafe extern "C" fn serialize_element<T: Serialize>(
pub trait DeserializeArray: Deserialize {
/// Deserialize an array of type from the given parcel.
fn deserialize_array(parcel: &BorrowedParcel<'_>) -> Result<Option<Vec<Self>>> {
- let mut vec: Option<Vec<MaybeUninit<Self>>> = None;
+ let mut vec: Option<Vec<Self::UninitType>> = None;
+ // Safety: Safe FFI, vec is the correct opaque type expected by
+ // allocate_vec and deserialize_element.
let res = unsafe {
- // Safety: Safe FFI, vec is the correct opaque type expected by
- // allocate_vec and deserialize_element.
sys::AParcel_readParcelableArray(
parcel.as_native(),
&mut vec as *mut _ as *mut c_void,
@@ -133,36 +189,41 @@ pub trait DeserializeArray: Deserialize {
)
};
status_result(res)?;
- let vec: Option<Vec<Self>> = unsafe {
- // Safety: We are assuming that the NDK correctly initialized every
- // element of the vector by now, so we know that all the
- // MaybeUninits are now properly initialized. We can transmute from
- // Vec<MaybeUninit<T>> to Vec<T> because MaybeUninit<T> has the same
- // alignment and size as T, so the pointer to the vector allocation
- // will be compatible.
- mem::transmute(vec)
- };
+ // Safety: We are assuming that the NDK correctly initialized every
+ // element of the vector by now, so we know that all the
+ // UninitTypes are now properly initialized. We can transmute from
+ // Vec<T::UninitType> to Vec<T> because T::UninitType has the same
+ // alignment and size as T, so the pointer to the vector allocation
+ // will be compatible.
+ let vec: Option<Vec<Self>> = unsafe { mem::transmute(vec) };
Ok(vec)
}
}
/// Callback to deserialize a parcelable element.
///
+/// # Safety
+///
/// The opaque array data pointer must be a mutable pointer to an
-/// `Option<Vec<MaybeUninit<T>>>` with at least enough elements for `index` to be valid
+/// `Option<Vec<T::UninitType>>` with at least enough elements for `index` to be valid
/// (zero-based).
unsafe extern "C" fn deserialize_element<T: Deserialize>(
parcel: *const sys::AParcel,
array: *mut c_void,
index: usize,
) -> status_t {
- let vec = &mut *(array as *mut Option<Vec<MaybeUninit<T>>>);
+ // Safety: The caller guarantees that `array` is a valid pointer of the
+ // appropriate type.
+ let vec = unsafe { &mut *(array as *mut Option<Vec<T::UninitType>>) };
let vec = match vec {
Some(v) => v,
None => return StatusCode::BAD_INDEX as status_t,
};
- let parcel = match BorrowedParcel::from_raw(parcel as *mut _) {
+ // Safety: The caller must give us a parcel pointer which is either null or
+ // valid at least for the duration of this function call. We don't keep the
+ // resulting value beyond the function.
+ let parcel = match unsafe { BorrowedParcel::from_raw(parcel as *mut _) } {
None => return StatusCode::UNEXPECTED_NULL as status_t,
Some(p) => p,
};
@@ -170,7 +231,7 @@ unsafe extern "C" fn deserialize_element<T: Deserialize>(
Ok(e) => e,
Err(code) => return code as status_t,
};
- ptr::write(vec[index].as_mut_ptr(), element);
+ vec[index] = T::from_init(element);
StatusCode::OK as status_t
}
@@ -233,17 +294,22 @@ pub trait DeserializeOption: Deserialize {
/// # Safety
///
/// The opaque data pointer passed to the array read function must be a mutable
-/// pointer to an `Option<Vec<MaybeUninit<T>>>`. `buffer` will be assigned a mutable pointer
-/// to the allocated vector data if this function returns true.
-unsafe extern "C" fn allocate_vec_with_buffer<T>(
+/// pointer to an `Option<Vec<T::UninitType>>`. `buffer` will be assigned a mutable pointer
+/// to the allocated vector data if this function returns true. `buffer` must be a valid pointer.
+unsafe extern "C" fn allocate_vec_with_buffer<T: Deserialize>(
data: *mut c_void,
len: i32,
buffer: *mut *mut T,
) -> bool {
- let res = allocate_vec::<T>(data, len);
- let vec = &mut *(data as *mut Option<Vec<MaybeUninit<T>>>);
+ // Safety: We have the same safety requirements as `allocate_vec` for `data`.
+ let res = unsafe { allocate_vec::<T>(data, len) };
+ // Safety: The caller guarantees that `data` is a valid mutable pointer to the appropriate type.
+ let vec = unsafe { &mut *(data as *mut Option<Vec<T::UninitType>>) };
if let Some(new_vec) = vec {
- *buffer = new_vec.as_mut_ptr() as *mut T;
+ // Safety: The caller guarantees that `buffer` is a valid pointer.
+ unsafe {
+ *buffer = new_vec.as_mut_ptr() as *mut T;
+ }
}
res
}
@@ -253,22 +319,24 @@ unsafe extern "C" fn allocate_vec_with_buffer<T>(
/// # Safety
///
/// The opaque data pointer passed to the array read function must be a mutable
-/// pointer to an `Option<Vec<MaybeUninit<T>>>`.
-unsafe extern "C" fn allocate_vec<T>(data: *mut c_void, len: i32) -> bool {
- let vec = &mut *(data as *mut Option<Vec<MaybeUninit<T>>>);
+/// pointer to an `Option<Vec<T::UninitType>>`.
+unsafe extern "C" fn allocate_vec<T: Deserialize>(data: *mut c_void, len: i32) -> bool {
+ // Safety: The caller guarantees that `data` is a valid mutable pointer to the appropriate type.
+ let vec = unsafe { &mut *(data as *mut Option<Vec<T::UninitType>>) };
if len < 0 {
*vec = None;
return true;
}
- let mut new_vec: Vec<MaybeUninit<T>> = Vec::with_capacity(len as usize);
- // Safety: We are filling the vector with uninitialized data here, but this
- // is safe because the vector contains MaybeUninit elements which can be
- // uninitialized. We're putting off the actual unsafe bit, transmuting the
- // vector to a Vec<T> until the contents are initialized.
- new_vec.set_len(len as usize);
+ // Assert at compile time that `T` and `T::UninitType` have the same size and alignment.
+ let _ = T::ASSERT_UNINIT_SIZE_AND_ALIGNMENT;
+ let mut new_vec: Vec<T::UninitType> = Vec::with_capacity(len as usize);
+ new_vec.resize_with(len as usize, T::uninit);
- ptr::write(vec, Some(new_vec));
+ // Safety: The caller guarantees that vec is a valid mutable pointer to the appropriate type.
+ unsafe {
+ ptr::write(vec, Some(new_vec));
+ }
true
}
@@ -283,22 +351,25 @@ macro_rules! parcelable_primitives {
}
/// Safety: All elements in the vector must be properly initialized.
-unsafe fn vec_assume_init<T>(vec: Vec<MaybeUninit<T>>) -> Vec<T> {
- // We can convert from Vec<MaybeUninit<T>> to Vec<T> because MaybeUninit<T>
- // has the same alignment and size as T, so the pointer to the vector
- // allocation will be compatible.
+unsafe fn vec_assume_init<T: Deserialize>(vec: Vec<T::UninitType>) -> Vec<T> {
+ // Assert at compile time that `T` and `T::UninitType` have the same size and alignment.
+ let _ = T::ASSERT_UNINIT_SIZE_AND_ALIGNMENT;
+
let mut vec = ManuallyDrop::new(vec);
- Vec::from_raw_parts(vec.as_mut_ptr().cast(), vec.len(), vec.capacity())
+ // Safety: We can convert from Vec<T::UninitType> to Vec<T> because
+ // T::UninitType has the same alignment and size as T, so the pointer to the
+ // vector allocation will be compatible.
+ unsafe { Vec::from_raw_parts(vec.as_mut_ptr().cast(), vec.len(), vec.capacity()) }
}
macro_rules! impl_parcelable {
{Serialize, $ty:ty, $write_fn:path} => {
impl Serialize for $ty {
fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`, and any `$ty` literal value is safe to pass to
+ // `$write_fn`.
unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`, and any `$ty` literal value is safe to pass to
- // `$write_fn`.
status_result($write_fn(parcel.as_native_mut(), *self))
}
}
@@ -307,13 +378,16 @@ macro_rules! impl_parcelable {
{Deserialize, $ty:ty, $read_fn:path} => {
impl Deserialize for $ty {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType { Self::UninitType::default() }
+ fn from_init(value: Self) -> Self::UninitType { value }
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
let mut val = Self::default();
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. We pass a valid, mutable pointer to `val`, a
+ // literal of type `$ty`, and `$read_fn` will write the
+ // value read into `val` if successful
unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. We pass a valid, mutable pointer to `val`, a
- // literal of type `$ty`, and `$read_fn` will write the
- // value read into `val` if successful
status_result($read_fn(parcel.as_native(), &mut val))?
};
Ok(val)
@@ -324,13 +398,13 @@ macro_rules! impl_parcelable {
{SerializeArray, $ty:ty, $write_array_fn:path} => {
impl SerializeArray for $ty {
fn serialize_array(slice: &[Self], parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. If the slice is > 0 length, `slice.as_ptr()`
+ // will be a valid pointer to an array of elements of type
+ // `$ty`. If the slice length is 0, `slice.as_ptr()` may be
+ // dangling, but this is safe since the pointer is not
+ // dereferenced if the length parameter is 0.
let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. If the slice is > 0 length, `slice.as_ptr()`
- // will be a valid pointer to an array of elements of type
- // `$ty`. If the slice length is 0, `slice.as_ptr()` may be
- // dangling, but this is safe since the pointer is not
- // dereferenced if the length parameter is 0.
$write_array_fn(
parcel.as_native_mut(),
slice.as_ptr(),
@@ -348,12 +422,12 @@ macro_rules! impl_parcelable {
{DeserializeArray, $ty:ty, $read_array_fn:path} => {
impl DeserializeArray for $ty {
fn deserialize_array(parcel: &BorrowedParcel<'_>) -> Result<Option<Vec<Self>>> {
- let mut vec: Option<Vec<MaybeUninit<Self>>> = None;
+ let mut vec: Option<Vec<Self::UninitType>> = None;
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. `allocate_vec<T>` expects the opaque pointer to
+ // be of type `*mut Option<Vec<T::UninitType>>`, so `&mut vec` is
+ // correct for it.
let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. `allocate_vec<T>` expects the opaque pointer to
- // be of type `*mut Option<Vec<MaybeUninit<T>>>`, so `&mut vec` is
- // correct for it.
$read_array_fn(
parcel.as_native(),
&mut vec as *mut _ as *mut c_void,
@@ -361,11 +435,11 @@ macro_rules! impl_parcelable {
)
};
status_result(status)?;
+ // Safety: We are assuming that the NDK correctly
+ // initialized every element of the vector by now, so we
+ // know that all the UninitTypes are now properly
+ // initialized.
let vec: Option<Vec<Self>> = unsafe {
- // Safety: We are assuming that the NDK correctly
- // initialized every element of the vector by now, so we
- // know that all the MaybeUninits are now properly
- // initialized.
vec.map(|vec| vec_assume_init(vec))
};
Ok(vec)
@@ -440,6 +514,14 @@ impl Serialize for u8 {
}
impl Deserialize for u8 {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
i8::deserialize(parcel).map(|v| v as u8)
}
@@ -447,13 +529,13 @@ impl Deserialize for u8 {
impl SerializeArray for u8 {
fn serialize_array(slice: &[Self], parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. If the slice is > 0 length, `slice.as_ptr()` will be a
+ // valid pointer to an array of elements of type `$ty`. If the slice
+ // length is 0, `slice.as_ptr()` may be dangling, but this is safe
+ // since the pointer is not dereferenced if the length parameter is
+ // 0.
let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. If the slice is > 0 length, `slice.as_ptr()` will be a
- // valid pointer to an array of elements of type `$ty`. If the slice
- // length is 0, `slice.as_ptr()` may be dangling, but this is safe
- // since the pointer is not dereferenced if the length parameter is
- // 0.
sys::AParcel_writeByteArray(
parcel.as_native_mut(),
slice.as_ptr() as *const i8,
@@ -471,6 +553,14 @@ impl Serialize for i16 {
}
impl Deserialize for i16 {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
u16::deserialize(parcel).map(|v| v as i16)
}
@@ -478,13 +568,13 @@ impl Deserialize for i16 {
impl SerializeArray for i16 {
fn serialize_array(slice: &[Self], parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. If the slice is > 0 length, `slice.as_ptr()` will be a
+ // valid pointer to an array of elements of type `$ty`. If the slice
+ // length is 0, `slice.as_ptr()` may be dangling, but this is safe
+ // since the pointer is not dereferenced if the length parameter is
+ // 0.
let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. If the slice is > 0 length, `slice.as_ptr()` will be a
- // valid pointer to an array of elements of type `$ty`. If the slice
- // length is 0, `slice.as_ptr()` may be dangling, but this is safe
- // since the pointer is not dereferenced if the length parameter is
- // 0.
sys::AParcel_writeCharArray(
parcel.as_native_mut(),
slice.as_ptr() as *const u16,
@@ -498,22 +588,22 @@ impl SerializeArray for i16 {
impl SerializeOption for str {
fn serialize_option(this: Option<&Self>, parcel: &mut BorrowedParcel<'_>) -> Result<()> {
match this {
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. If the string pointer is null,
+ // `AParcel_writeString` requires that the length is -1 to
+ // indicate that we want to serialize a null string.
None => unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. If the string pointer is null,
- // `AParcel_writeString` requires that the length is -1 to
- // indicate that we want to serialize a null string.
status_result(sys::AParcel_writeString(parcel.as_native_mut(), ptr::null(), -1))
},
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. `AParcel_writeString` assumes that we pass a utf-8
+ // string pointer of `length` bytes, which is what str in Rust
+ // is. The docstring for `AParcel_writeString` says that the
+ // string input should be null-terminated, but it doesn't
+ // actually rely on that fact in the code. If this ever becomes
+ // necessary, we will need to null-terminate the str buffer
+ // before sending it.
Some(s) => unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. `AParcel_writeString` assumes that we pass a utf-8
- // string pointer of `length` bytes, which is what str in Rust
- // is. The docstring for `AParcel_writeString` says that the
- // string input should be null-terminated, but it doesn't
- // actually rely on that fact in the code. If this ever becomes
- // necessary, we will need to null-terminate the str buffer
- // before sending it.
status_result(sys::AParcel_writeString(
parcel.as_native_mut(),
s.as_ptr() as *const c_char,
@@ -547,13 +637,21 @@ impl SerializeOption for String {
}
impl Deserialize for Option<String> {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
let mut vec: Option<Vec<u8>> = None;
+ // Safety: `Parcel` always contains a valid pointer to an `AParcel`.
+ // `Option<Vec<u8>>` is equivalent to the expected `Option<Vec<i8>>`
+ // for `allocate_vec`, so `vec` is safe to pass as the opaque data
+ // pointer on platforms where char is signed.
let status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an `AParcel`.
- // `Option<Vec<u8>>` is equivalent to the expected `Option<Vec<i8>>`
- // for `allocate_vec`, so `vec` is safe to pass as the opaque data
- // pointer on platforms where char is signed.
sys::AParcel_readString(
parcel.as_native(),
&mut vec as *mut _ as *mut c_void,
@@ -575,6 +673,14 @@ impl Deserialize for Option<String> {
impl DeserializeArray for Option<String> {}
impl Deserialize for String {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
Deserialize::deserialize(parcel).transpose().unwrap_or(Err(StatusCode::UNEXPECTED_NULL))
}
@@ -611,6 +717,14 @@ impl<T: SerializeArray> SerializeOption for Vec<T> {
}
impl<T: DeserializeArray> Deserialize for Vec<T> {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
DeserializeArray::deserialize_array(parcel)
.transpose()
@@ -640,6 +754,14 @@ impl<T: SerializeArray, const N: usize> SerializeOption for [T; N] {
impl<T: SerializeArray, const N: usize> SerializeArray for [T; N] {}
impl<T: DeserializeArray, const N: usize> Deserialize for [T; N] {
+ type UninitType = [T::UninitType; N];
+ fn uninit() -> Self::UninitType {
+ [(); N].map(|_| T::uninit())
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value.map(T::from_init)
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
let vec = DeserializeArray::deserialize_array(parcel)
.transpose()
@@ -664,6 +786,14 @@ impl Serialize for Stability {
}
impl Deserialize for Stability {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
i32::deserialize(parcel).and_then(Stability::try_from)
}
@@ -671,34 +801,39 @@ impl Deserialize for Stability {
impl Serialize for Status {
fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()> {
+ // Safety: `Parcel` always contains a valid pointer to an `AParcel`
+ // and `Status` always contains a valid pointer to an `AStatus`, so
+ // both parameters are valid and safe. This call does not take
+ // ownership of either of its parameters.
unsafe {
- // Safety: `Parcel` always contains a valid pointer to an `AParcel`
- // and `Status` always contains a valid pointer to an `AStatus`, so
- // both parameters are valid and safe. This call does not take
- // ownership of either of its parameters.
status_result(sys::AParcel_writeStatusHeader(parcel.as_native_mut(), self.as_native()))
}
}
}
impl Deserialize for Status {
+ type UninitType = Option<Self>;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ Some(value)
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
let mut status_ptr = ptr::null_mut();
- let ret_status = unsafe {
- // Safety: `Parcel` always contains a valid pointer to an
- // `AParcel`. We pass a mutable out pointer which will be
- // assigned a valid `AStatus` pointer if the function returns
- // status OK. This function passes ownership of the status
- // pointer to the caller, if it was assigned.
- sys::AParcel_readStatusHeader(parcel.as_native(), &mut status_ptr)
- };
+ let ret_status =
+ // Safety: `Parcel` always contains a valid pointer to an
+ // `AParcel`. We pass a mutable out pointer which will be
+ // assigned a valid `AStatus` pointer if the function returns
+ // status OK. This function passes ownership of the status
+ // pointer to the caller, if it was assigned.
+ unsafe { sys::AParcel_readStatusHeader(parcel.as_native(), &mut status_ptr) };
status_result(ret_status)?;
- Ok(unsafe {
- // Safety: At this point, the return status of the read call was ok,
- // so we know that `status_ptr` is a valid, owned pointer to an
- // `AStatus`, from which we can safely construct a `Status` object.
- Status::from_ptr(status_ptr)
- })
+ // Safety: At this point, the return status of the read call was ok,
+ // so we know that `status_ptr` is a valid, owned pointer to an
+ // `AStatus`, from which we can safely construct a `Status` object.
+ Ok(unsafe { Status::from_ptr(status_ptr) })
}
}
@@ -717,12 +852,29 @@ impl<T: SerializeOption + FromIBinder + ?Sized> SerializeOption for Strong<T> {
impl<T: Serialize + FromIBinder + ?Sized> SerializeArray for Strong<T> {}
impl<T: FromIBinder + ?Sized> Deserialize for Strong<T> {
+ type UninitType = Option<Strong<T>>;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ Some(value)
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
let ibinder: SpIBinder = parcel.read()?;
FromIBinder::try_from(ibinder)
}
}
+struct AssertIBinder;
+impl Interface for AssertIBinder {}
+impl FromIBinder for AssertIBinder {
+ // This is only needed so we can assert on the size of Strong<AssertIBinder>
+ fn try_from(_: SpIBinder) -> Result<Strong<Self>> {
+ unimplemented!()
+ }
+}
+
impl<T: FromIBinder + ?Sized> DeserializeOption for Strong<T> {
fn deserialize_option(parcel: &BorrowedParcel<'_>) -> Result<Option<Self>> {
let ibinder: Option<SpIBinder> = parcel.read()?;
@@ -752,6 +904,14 @@ impl<T: SerializeOption> Serialize for Option<T> {
}
impl<T: DeserializeOption> Deserialize for Option<T> {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
DeserializeOption::deserialize_option(parcel)
}
@@ -767,11 +927,16 @@ impl<T: DeserializeOption> Deserialize for Option<T> {
/// `Serialize`, `SerializeArray` and `SerializeOption` for
/// structured parcelables. The target type must implement the
/// `Parcelable` trait.
-/// ```
#[macro_export]
macro_rules! impl_serialize_for_parcelable {
($parcelable:ident) => {
- impl $crate::binder_impl::Serialize for $parcelable {
+ $crate::impl_serialize_for_parcelable!($parcelable < >);
+ };
+ ($parcelable:ident < $( $param:ident ),* , >) => {
+ $crate::impl_serialize_for_parcelable!($parcelable < $($param),* >);
+ };
+ ($parcelable:ident < $( $param:ident ),* > ) => {
+ impl < $($param),* > $crate::binder_impl::Serialize for $parcelable < $($param),* > {
fn serialize(
&self,
parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
@@ -780,9 +945,9 @@ macro_rules! impl_serialize_for_parcelable {
}
}
- impl $crate::binder_impl::SerializeArray for $parcelable {}
+ impl < $($param),* > $crate::binder_impl::SerializeArray for $parcelable < $($param),* > {}
- impl $crate::binder_impl::SerializeOption for $parcelable {
+ impl < $($param),* > $crate::binder_impl::SerializeOption for $parcelable < $($param),* > {
fn serialize_option(
this: Option<&Self>,
parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
@@ -808,7 +973,16 @@ macro_rules! impl_serialize_for_parcelable {
#[macro_export]
macro_rules! impl_deserialize_for_parcelable {
($parcelable:ident) => {
- impl $crate::binder_impl::Deserialize for $parcelable {
+ $crate::impl_deserialize_for_parcelable!($parcelable < >);
+ };
+ ($parcelable:ident < $( $param:ident ),* , >) => {
+ $crate::impl_deserialize_for_parcelable!($parcelable < $($param),* >);
+ };
+ ($parcelable:ident < $( $param:ident ),* > ) => {
+ impl < $($param: Default),* > $crate::binder_impl::Deserialize for $parcelable < $($param),* > {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType { Self::UninitType::default() }
+ fn from_init(value: Self) -> Self::UninitType { value }
fn deserialize(
parcel: &$crate::binder_impl::BorrowedParcel<'_>,
) -> std::result::Result<Self, $crate::StatusCode> {
@@ -830,9 +1004,9 @@ macro_rules! impl_deserialize_for_parcelable {
}
}
- impl $crate::binder_impl::DeserializeArray for $parcelable {}
+ impl < $($param: Default),* > $crate::binder_impl::DeserializeArray for $parcelable < $($param),* > {}
- impl $crate::binder_impl::DeserializeOption for $parcelable {
+ impl < $($param: Default),* > $crate::binder_impl::DeserializeOption for $parcelable < $($param),* > {
fn deserialize_option(
parcel: &$crate::binder_impl::BorrowedParcel<'_>,
) -> std::result::Result<Option<Self>, $crate::StatusCode> {
@@ -857,6 +1031,125 @@ macro_rules! impl_deserialize_for_parcelable {
};
}
+/// Implements `Serialize` trait and friends for an unstructured parcelable.
+///
+/// The target type must implement the `UnstructuredParcelable` trait.
+#[macro_export]
+macro_rules! impl_serialize_for_unstructured_parcelable {
+ ($parcelable:ident) => {
+ $crate::impl_serialize_for_unstructured_parcelable!($parcelable < >);
+ };
+ ($parcelable:ident < $( $param:ident ),* , >) => {
+ $crate::impl_serialize_for_unstructured_parcelable!($parcelable < $($param),* >);
+ };
+ ($parcelable:ident < $( $param:ident ),* > ) => {
+ impl < $($param),* > $crate::binder_impl::Serialize for $parcelable < $($param),* > {
+ fn serialize(
+ &self,
+ parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
+ ) -> std::result::Result<(), $crate::StatusCode> {
+ <Self as $crate::binder_impl::SerializeOption>::serialize_option(Some(self), parcel)
+ }
+ }
+
+ impl < $($param),* > $crate::binder_impl::SerializeArray for $parcelable < $($param),* > {}
+
+ impl < $($param),* > $crate::binder_impl::SerializeOption for $parcelable < $($param),* > {
+ fn serialize_option(
+ this: Option<&Self>,
+ parcel: &mut $crate::binder_impl::BorrowedParcel<'_>,
+ ) -> std::result::Result<(), $crate::StatusCode> {
+ if let Some(this) = this {
+ use $crate::binder_impl::UnstructuredParcelable;
+ parcel.write(&$crate::binder_impl::NON_NULL_PARCELABLE_FLAG)?;
+ this.write_to_parcel(parcel)
+ } else {
+ parcel.write(&$crate::binder_impl::NULL_PARCELABLE_FLAG)
+ }
+ }
+ }
+ };
+}
+
+/// Implement `Deserialize` trait and friends for an unstructured parcelable
+///
+/// The target type must implement the `UnstructuredParcelable` trait.
+#[macro_export]
+macro_rules! impl_deserialize_for_unstructured_parcelable {
+ ($parcelable:ident) => {
+ $crate::impl_deserialize_for_unstructured_parcelable!($parcelable < >);
+ };
+ ($parcelable:ident < $( $param:ident ),* , >) => {
+ $crate::impl_deserialize_for_unstructured_parcelable!($parcelable < $($param),* >);
+ };
+ ($parcelable:ident < $( $param:ident ),* > ) => {
+ impl < $($param: Default),* > $crate::binder_impl::Deserialize for $parcelable < $($param),* > {
+ type UninitType = Option<Self>;
+ fn uninit() -> Self::UninitType { None }
+ fn from_init(value: Self) -> Self::UninitType { Some(value) }
+ fn deserialize(
+ parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+ ) -> std::result::Result<Self, $crate::StatusCode> {
+ $crate::binder_impl::DeserializeOption::deserialize_option(parcel)
+ .transpose()
+ .unwrap_or(Err($crate::StatusCode::UNEXPECTED_NULL))
+ }
+ fn deserialize_from(
+ &mut self,
+ parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+ ) -> std::result::Result<(), $crate::StatusCode> {
+ let status: i32 = parcel.read()?;
+ if status == $crate::binder_impl::NULL_PARCELABLE_FLAG {
+ Err($crate::StatusCode::UNEXPECTED_NULL)
+ } else {
+ use $crate::binder_impl::UnstructuredParcelable;
+ self.read_from_parcel(parcel)
+ }
+ }
+ }
+
+ impl < $($param: Default),* > $crate::binder_impl::DeserializeArray for $parcelable < $($param),* > {}
+
+ impl < $($param: Default),* > $crate::binder_impl::DeserializeOption for $parcelable < $($param),* > {
+ fn deserialize_option(
+ parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+ ) -> std::result::Result<Option<Self>, $crate::StatusCode> {
+ let present: i32 = parcel.read()?;
+ match present {
+ $crate::binder_impl::NULL_PARCELABLE_FLAG => Ok(None),
+ $crate::binder_impl::NON_NULL_PARCELABLE_FLAG => {
+ use $crate::binder_impl::UnstructuredParcelable;
+ Ok(Some(Self::from_parcel(parcel)?))
+ }
+ _ => Err(StatusCode::BAD_VALUE),
+ }
+ }
+ fn deserialize_option_from(
+ this: &mut Option<Self>,
+ parcel: &$crate::binder_impl::BorrowedParcel<'_>,
+ ) -> std::result::Result<(), $crate::StatusCode> {
+ let present: i32 = parcel.read()?;
+ match present {
+ $crate::binder_impl::NULL_PARCELABLE_FLAG => {
+ *this = None;
+ Ok(())
+ }
+ $crate::binder_impl::NON_NULL_PARCELABLE_FLAG => {
+ use $crate::binder_impl::UnstructuredParcelable;
+ if let Some(this) = this {
+ this.read_from_parcel(parcel)?;
+ } else {
+ *this = Some(Self::from_parcel(parcel)?);
+ }
+ Ok(())
+ }
+ _ => Err(StatusCode::BAD_VALUE),
+ }
+ }
+ }
+ };
+}
+
impl<T: Serialize> Serialize for Box<T> {
fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<()> {
Serialize::serialize(&**self, parcel)
@@ -864,6 +1157,14 @@ impl<T: Serialize> Serialize for Box<T> {
}
impl<T: Deserialize> Deserialize for Box<T> {
+ type UninitType = Option<Self>;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ Some(value)
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
Deserialize::deserialize(parcel).map(Box::new)
}
@@ -888,6 +1189,7 @@ mod tests {
#[test]
fn test_custom_parcelable() {
+ #[derive(Default)]
struct Custom(u32, bool, String, Vec<String>);
impl Serialize for Custom {
@@ -900,6 +1202,14 @@ mod tests {
}
impl Deserialize for Custom {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self> {
Ok(Custom(
parcel.read()?,
@@ -925,6 +1235,8 @@ mod tests {
assert!(custom.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -947,6 +1259,8 @@ mod tests {
assert!(bools.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -956,6 +1270,8 @@ mod tests {
assert_eq!(parcel.read::<u32>().unwrap(), 0);
assert_eq!(parcel.read::<u32>().unwrap(), 0);
assert_eq!(parcel.read::<u32>().unwrap(), 1);
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -971,12 +1287,17 @@ mod tests {
assert!(parcel.write(&u8s[..]).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
assert_eq!(parcel.read::<u32>().unwrap(), 0x752aff65); // bytes
+
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -986,18 +1307,25 @@ mod tests {
let i8s = [-128i8, 127, 42, -117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(parcel.write(&i8s[..]).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert_eq!(parcel.read::<u32>().unwrap(), 4); // 4 items
assert_eq!(parcel.read::<u32>().unwrap(), 0x8b2a7f80); // bytes
+
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1007,10 +1335,14 @@ mod tests {
let u16s = [u16::max_value(), 12_345, 42, 117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(u16s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1020,6 +1352,9 @@ mod tests {
assert_eq!(parcel.read::<u32>().unwrap(), 12345); // 12,345
assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
assert_eq!(parcel.read::<u32>().unwrap(), 117); // 117
+
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1030,10 +1365,14 @@ mod tests {
let i16s = [i16::max_value(), i16::min_value(), 42, -117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(i16s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1043,6 +1382,9 @@ mod tests {
assert_eq!(parcel.read::<u32>().unwrap(), 0x8000); // i16::min_value()
assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
assert_eq!(parcel.read::<u32>().unwrap(), 0xff8b); // -117
+
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1053,10 +1395,14 @@ mod tests {
let u32s = [u32::max_value(), 12_345, 42, 117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(u32s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1066,6 +1412,9 @@ mod tests {
assert_eq!(parcel.read::<u32>().unwrap(), 12345); // 12,345
assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
assert_eq!(parcel.read::<u32>().unwrap(), 117); // 117
+
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1076,10 +1425,14 @@ mod tests {
let i32s = [i32::max_value(), i32::min_value(), 42, -117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(i32s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1089,6 +1442,9 @@ mod tests {
assert_eq!(parcel.read::<u32>().unwrap(), 0x80000000); // i32::min_value()
assert_eq!(parcel.read::<u32>().unwrap(), 42); // 42
assert_eq!(parcel.read::<u32>().unwrap(), 0xffffff8b); // -117
+
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1099,10 +1455,14 @@ mod tests {
let u64s = [u64::max_value(), 12_345, 42, 117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(u64s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1113,10 +1473,14 @@ mod tests {
let i64s = [i64::max_value(), i64::min_value(), 42, -117];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(i64s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1127,10 +1491,14 @@ mod tests {
let f32s = [std::f32::NAN, std::f32::INFINITY, 1.23456789, std::f32::EPSILON];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(f32s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1143,10 +1511,14 @@ mod tests {
let f64s = [std::f64::NAN, std::f64::INFINITY, 1.234567890123456789, std::f64::EPSILON];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(f64s.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
@@ -1164,10 +1536,14 @@ mod tests {
let strs = [s1, s2, s3, s4];
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
assert!(strs.serialize(&mut parcel.borrowed()).is_ok());
+ // SAFETY: start is less than the current size of the parcel data buffer, because we haven't
+ // made it any shorter since we got the position.
unsafe {
assert!(parcel.set_data_position(start).is_ok());
}
diff --git a/libs/binder/rust/src/parcel/parcelable_holder.rs b/libs/binder/rust/src/parcel/parcelable_holder.rs
index c829d37dca..f90611361f 100644
--- a/libs/binder/rust/src/parcel/parcelable_holder.rs
+++ b/libs/binder/rust/src/parcel/parcelable_holder.rs
@@ -133,8 +133,8 @@ impl ParcelableHolder {
}
}
ParcelableHolderData::Parcel(ref mut parcel) => {
+ // Safety: 0 should always be a valid position.
unsafe {
- // Safety: 0 should always be a valid position.
parcel.set_data_position(0)?;
}
@@ -161,6 +161,15 @@ impl ParcelableHolder {
}
}
+impl Clone for ParcelableHolder {
+ fn clone(&self) -> ParcelableHolder {
+ ParcelableHolder {
+ data: Mutex::new(self.data.lock().unwrap().clone()),
+ stability: self.stability,
+ }
+ }
+}
+
impl Serialize for ParcelableHolder {
fn serialize(&self, parcel: &mut BorrowedParcel<'_>) -> Result<(), StatusCode> {
parcel.write(&NON_NULL_PARCELABLE_FLAG)?;
@@ -169,6 +178,14 @@ impl Serialize for ParcelableHolder {
}
impl Deserialize for ParcelableHolder {
+ type UninitType = Self;
+ fn uninit() -> Self::UninitType {
+ Self::new(Default::default())
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ value
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<Self, StatusCode> {
let status: i32 = parcel.read()?;
if status == NULL_PARCELABLE_FLAG {
@@ -197,15 +214,15 @@ impl Parcelable for ParcelableHolder {
parcelable.write_to_parcel(parcel)?;
let end = parcel.get_data_position();
+ // Safety: we got the position from `get_data_position`.
unsafe {
- // Safety: we got the position from `get_data_position`.
parcel.set_data_position(length_start)?;
}
assert!(end >= data_start);
parcel.write(&(end - data_start))?;
+ // Safety: we got the position from `get_data_position`.
unsafe {
- // Safety: we got the position from `get_data_position`.
parcel.set_data_position(end)?;
}
@@ -243,11 +260,11 @@ impl Parcelable for ParcelableHolder {
new_parcel.append_from(parcel, data_start, data_size)?;
*self.data.get_mut().unwrap() = ParcelableHolderData::Parcel(new_parcel);
+ // Safety: `append_from` checks if `data_size` overflows
+ // `parcel` and returns `BAD_VALUE` if that happens. We also
+ // explicitly check for negative and zero `data_size` above,
+ // so `data_end` is guaranteed to be greater than `data_start`.
unsafe {
- // Safety: `append_from` checks if `data_size` overflows
- // `parcel` and returns `BAD_VALUE` if that happens. We also
- // explicitly check for negative and zero `data_size` above,
- // so `data_end` is guaranteed to be greater than `data_start`.
parcel.set_data_position(data_end)?;
}
diff --git a/libs/binder/rust/src/proxy.rs b/libs/binder/rust/src/proxy.rs
index 254efaed9a..340014aeaa 100644
--- a/libs/binder/rust/src/proxy.rs
+++ b/libs/binder/rust/src/proxy.rs
@@ -29,11 +29,10 @@ use crate::sys;
use std::cmp::Ordering;
use std::convert::TryInto;
-use std::ffi::{c_void, CStr, CString};
+use std::ffi::{c_void, CString};
use std::fmt;
use std::mem;
-use std::os::raw::c_char;
-use std::os::unix::io::AsRawFd;
+use std::os::fd::AsRawFd;
use std::ptr;
use std::sync::Arc;
@@ -49,14 +48,12 @@ impl fmt::Debug for SpIBinder {
}
}
-/// # Safety
-///
-/// An `SpIBinder` is an immutable handle to a C++ IBinder, which is thread-safe
+/// Safety: An `SpIBinder` is an immutable handle to a C++ IBinder, which is
+/// thread-safe.
unsafe impl Send for SpIBinder {}
-/// # Safety
-///
-/// An `SpIBinder` is an immutable handle to a C++ IBinder, which is thread-safe
+/// Safety: An `SpIBinder` is an immutable handle to a C++ IBinder, which is
+/// thread-safe.
unsafe impl Sync for SpIBinder {}
impl SpIBinder {
@@ -97,11 +94,9 @@ impl SpIBinder {
/// Return true if this binder object is hosted in a different process than
/// the current one.
pub fn is_remote(&self) -> bool {
- unsafe {
- // Safety: `SpIBinder` guarantees that it always contains a valid
- // `AIBinder` pointer.
- sys::AIBinder_isRemote(self.as_native())
- }
+ // Safety: `SpIBinder` guarantees that it always contains a valid
+ // `AIBinder` pointer.
+ unsafe { sys::AIBinder_isRemote(self.as_native()) }
}
/// Try to convert this Binder object into a trait object for the given
@@ -116,12 +111,12 @@ impl SpIBinder {
/// Return the interface class of this binder object, if associated with
/// one.
pub fn get_class(&mut self) -> Option<InterfaceClass> {
+ // Safety: `SpIBinder` guarantees that it always contains a valid
+ // `AIBinder` pointer. `AIBinder_getClass` returns either a null
+ // pointer or a valid pointer to an `AIBinder_Class`. After mapping
+ // null to None, we can safely construct an `InterfaceClass` if the
+ // pointer was non-null.
unsafe {
- // Safety: `SpIBinder` guarantees that it always contains a valid
- // `AIBinder` pointer. `AIBinder_getClass` returns either a null
- // pointer or a valid pointer to an `AIBinder_Class`. After mapping
- // null to None, we can safely construct an `InterfaceClass` if the
- // pointer was non-null.
let class = sys::AIBinder_getClass(self.as_native_mut());
class.as_ref().map(|p| InterfaceClass::from_ptr(p))
}
@@ -133,14 +128,6 @@ impl SpIBinder {
}
}
-fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
- if let Some(service) = service {
- FromIBinder::try_from(service)
- } else {
- Err(StatusCode::NAME_NOT_FOUND)
- }
-}
-
pub mod unstable_api {
use super::{sys, SpIBinder};
@@ -152,7 +139,8 @@ pub mod unstable_api {
///
/// See `SpIBinder::from_raw`.
pub unsafe fn new_spibinder(ptr: *mut sys::AIBinder) -> Option<SpIBinder> {
- SpIBinder::from_raw(ptr)
+ // Safety: The caller makes the same guarantees as this requires.
+ unsafe { SpIBinder::from_raw(ptr) }
}
}
@@ -171,30 +159,24 @@ pub trait AssociateClass {
impl AssociateClass for SpIBinder {
fn associate_class(&mut self, class: InterfaceClass) -> bool {
- unsafe {
- // Safety: `SpIBinder` guarantees that it always contains a valid
- // `AIBinder` pointer. An `InterfaceClass` can always be converted
- // into a valid `AIBinder_Class` pointer, so these parameters are
- // always safe.
- sys::AIBinder_associateClass(self.as_native_mut(), class.into())
- }
+ // Safety: `SpIBinder` guarantees that it always contains a valid
+ // `AIBinder` pointer. An `InterfaceClass` can always be converted
+ // into a valid `AIBinder_Class` pointer, so these parameters are
+ // always safe.
+ unsafe { sys::AIBinder_associateClass(self.as_native_mut(), class.into()) }
}
}
impl Ord for SpIBinder {
fn cmp(&self, other: &Self) -> Ordering {
- let less_than = unsafe {
- // Safety: SpIBinder always holds a valid `AIBinder` pointer, so
- // this pointer is always safe to pass to `AIBinder_lt` (null is
- // also safe to pass to this function, but we should never do that).
- sys::AIBinder_lt(self.0.as_ptr(), other.0.as_ptr())
- };
- let greater_than = unsafe {
- // Safety: SpIBinder always holds a valid `AIBinder` pointer, so
- // this pointer is always safe to pass to `AIBinder_lt` (null is
- // also safe to pass to this function, but we should never do that).
- sys::AIBinder_lt(other.0.as_ptr(), self.0.as_ptr())
- };
+ // Safety: SpIBinder always holds a valid `AIBinder` pointer, so this
+ // pointer is always safe to pass to `AIBinder_lt` (null is also safe to
+ // pass to this function, but we should never do that).
+ let less_than = unsafe { sys::AIBinder_lt(self.0.as_ptr(), other.0.as_ptr()) };
+ // Safety: SpIBinder always holds a valid `AIBinder` pointer, so this
+ // pointer is always safe to pass to `AIBinder_lt` (null is also safe to
+ // pass to this function, but we should never do that).
+ let greater_than = unsafe { sys::AIBinder_lt(other.0.as_ptr(), self.0.as_ptr()) };
if !less_than && !greater_than {
Ordering::Equal
} else if less_than {
@@ -221,10 +203,10 @@ impl Eq for SpIBinder {}
impl Clone for SpIBinder {
fn clone(&self) -> Self {
+ // Safety: Cloning a strong reference must increment the reference
+ // count. We are guaranteed by the `SpIBinder` constructor
+ // invariants that `self.0` is always a valid `AIBinder` pointer.
unsafe {
- // Safety: Cloning a strong reference must increment the reference
- // count. We are guaranteed by the `SpIBinder` constructor
- // invariants that `self.0` is always a valid `AIBinder` pointer.
sys::AIBinder_incStrong(self.0.as_ptr());
}
Self(self.0)
@@ -235,9 +217,9 @@ impl Drop for SpIBinder {
// We hold a strong reference to the IBinder in SpIBinder and need to give up
// this reference on drop.
fn drop(&mut self) {
+ // Safety: SpIBinder always holds a valid `AIBinder` pointer, so we
+ // know this pointer is safe to pass to `AIBinder_decStrong` here.
unsafe {
- // Safety: SpIBinder always holds a valid `AIBinder` pointer, so we
- // know this pointer is safe to pass to `AIBinder_decStrong` here.
sys::AIBinder_decStrong(self.as_native_mut());
}
}
@@ -246,26 +228,24 @@ impl Drop for SpIBinder {
impl<T: AsNative<sys::AIBinder>> IBinderInternal for T {
fn prepare_transact(&self) -> Result<Parcel> {
let mut input = ptr::null_mut();
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`. It is safe to cast from an
+ // immutable pointer to a mutable pointer here, because
+ // `AIBinder_prepareTransaction` only calls immutable `AIBinder`
+ // methods but the parameter is unfortunately not marked as const.
+ //
+ // After the call, input will be either a valid, owned `AParcel`
+ // pointer, or null.
let status = unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`. It is safe to cast from an
- // immutable pointer to a mutable pointer here, because
- // `AIBinder_prepareTransaction` only calls immutable `AIBinder`
- // methods but the parameter is unfortunately not marked as const.
- //
- // After the call, input will be either a valid, owned `AParcel`
- // pointer, or null.
sys::AIBinder_prepareTransaction(self.as_native() as *mut sys::AIBinder, &mut input)
};
status_result(status)?;
- unsafe {
- // Safety: At this point, `input` is either a valid, owned `AParcel`
- // pointer, or null. `OwnedParcel::from_raw` safely handles both cases,
- // taking ownership of the parcel.
- Parcel::from_raw(input).ok_or(StatusCode::UNEXPECTED_NULL)
- }
+ // Safety: At this point, `input` is either a valid, owned `AParcel`
+ // pointer, or null. `OwnedParcel::from_raw` safely handles both cases,
+ // taking ownership of the parcel.
+ unsafe { Parcel::from_raw(input).ok_or(StatusCode::UNEXPECTED_NULL) }
}
fn submit_transact(
@@ -275,23 +255,23 @@ impl<T: AsNative<sys::AIBinder>> IBinderInternal for T {
flags: TransactionFlags,
) -> Result<Parcel> {
let mut reply = ptr::null_mut();
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`. Although `IBinder::transact` is
+ // not a const method, it is still safe to cast our immutable
+ // pointer to mutable for the call. First, `IBinder::transact` is
+ // thread-safe, so concurrency is not an issue. The only way that
+ // `transact` can affect any visible, mutable state in the current
+ // process is by calling `onTransact` for a local service. However,
+ // in order for transactions to be thread-safe, this method must
+ // dynamically lock its data before modifying it. We enforce this
+ // property in Rust by requiring `Sync` for remotable objects and
+ // only providing `on_transact` with an immutable reference to
+ // `self`.
+ //
+ // This call takes ownership of the `data` parcel pointer, and
+ // passes ownership of the `reply` out parameter to its caller. It
+ // does not affect ownership of the `binder` parameter.
let status = unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`. Although `IBinder::transact` is
- // not a const method, it is still safe to cast our immutable
- // pointer to mutable for the call. First, `IBinder::transact` is
- // thread-safe, so concurrency is not an issue. The only way that
- // `transact` can affect any visible, mutable state in the current
- // process is by calling `onTransact` for a local service. However,
- // in order for transactions to be thread-safe, this method must
- // dynamically lock its data before modifying it. We enforce this
- // property in Rust by requiring `Sync` for remotable objects and
- // only providing `on_transact` with an immutable reference to
- // `self`.
- //
- // This call takes ownership of the `data` parcel pointer, and
- // passes ownership of the `reply` out parameter to its caller. It
- // does not affect ownership of the `binder` parameter.
sys::AIBinder_transact(
self.as_native() as *mut sys::AIBinder,
code,
@@ -302,45 +282,45 @@ impl<T: AsNative<sys::AIBinder>> IBinderInternal for T {
};
status_result(status)?;
- unsafe {
- // Safety: `reply` is either a valid `AParcel` pointer or null
- // after the call to `AIBinder_transact` above, so we can
- // construct a `Parcel` out of it. `AIBinder_transact` passes
- // ownership of the `reply` parcel to Rust, so we need to
- // construct an owned variant.
- Parcel::from_raw(reply).ok_or(StatusCode::UNEXPECTED_NULL)
- }
+ // Safety: `reply` is either a valid `AParcel` pointer or null
+ // after the call to `AIBinder_transact` above, so we can
+ // construct a `Parcel` out of it. `AIBinder_transact` passes
+ // ownership of the `reply` parcel to Rust, so we need to
+ // construct an owned variant.
+ unsafe { Parcel::from_raw(reply).ok_or(StatusCode::UNEXPECTED_NULL) }
}
fn is_binder_alive(&self) -> bool {
- unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`.
- //
- // This call does not affect ownership of its pointer parameter.
- sys::AIBinder_isAlive(self.as_native())
- }
+ // Safety: `SpIBinder` guarantees that `self` always contains a valid
+ // pointer to an `AIBinder`.
+ //
+ // This call does not affect ownership of its pointer parameter.
+ unsafe { sys::AIBinder_isAlive(self.as_native()) }
}
#[cfg(not(android_vndk))]
fn set_requesting_sid(&mut self, enable: bool) {
+ // Safety: `SpIBinder` guarantees that `self` always contains a valid
+ // pointer to an `AIBinder`.
+ //
+ // This call does not affect ownership of its pointer parameter.
unsafe { sys::AIBinder_setRequestingSid(self.as_native_mut(), enable) };
}
fn dump<F: AsRawFd>(&mut self, fp: &F, args: &[&str]) -> Result<()> {
let args: Vec<_> = args.iter().map(|a| CString::new(*a).unwrap()).collect();
let mut arg_ptrs: Vec<_> = args.iter().map(|a| a.as_ptr()).collect();
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`. `AsRawFd` guarantees that the
+ // file descriptor parameter is always be a valid open file. The
+ // `args` pointer parameter is a valid pointer to an array of C
+ // strings that will outlive the call since `args` lives for the
+ // whole function scope.
+ //
+ // This call does not affect ownership of its binder pointer
+ // parameter and does not take ownership of the file or args array
+ // parameters.
let status = unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`. `AsRawFd` guarantees that the
- // file descriptor parameter is always be a valid open file. The
- // `args` pointer parameter is a valid pointer to an array of C
- // strings that will outlive the call since `args` lives for the
- // whole function scope.
- //
- // This call does not affect ownership of its binder pointer
- // parameter and does not take ownership of the file or args array
- // parameters.
sys::AIBinder_dump(
self.as_native_mut(),
fp.as_raw_fd(),
@@ -353,22 +333,18 @@ impl<T: AsNative<sys::AIBinder>> IBinderInternal for T {
fn get_extension(&mut self) -> Result<Option<SpIBinder>> {
let mut out = ptr::null_mut();
- let status = unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`. After this call, the `out`
- // parameter will be either null, or a valid pointer to an
- // `AIBinder`.
- //
- // This call passes ownership of the out pointer to its caller
- // (assuming it is set to a non-null value).
- sys::AIBinder_getExtension(self.as_native_mut(), &mut out)
- };
- let ibinder = unsafe {
- // Safety: The call above guarantees that `out` is either null or a
- // valid, owned pointer to an `AIBinder`, both of which are safe to
- // pass to `SpIBinder::from_raw`.
- SpIBinder::from_raw(out)
- };
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`. After this call, the `out`
+ // parameter will be either null, or a valid pointer to an
+ // `AIBinder`.
+ //
+ // This call passes ownership of the out pointer to its caller
+ // (assuming it is set to a non-null value).
+ let status = unsafe { sys::AIBinder_getExtension(self.as_native_mut(), &mut out) };
+ // Safety: The call above guarantees that `out` is either null or a
+ // valid, owned pointer to an `AIBinder`, both of which are safe to
+ // pass to `SpIBinder::from_raw`.
+ let ibinder = unsafe { SpIBinder::from_raw(out) };
status_result(status)?;
Ok(ibinder)
@@ -377,17 +353,17 @@ impl<T: AsNative<sys::AIBinder>> IBinderInternal for T {
impl<T: AsNative<sys::AIBinder>> IBinder for T {
fn link_to_death(&mut self, recipient: &mut DeathRecipient) -> Result<()> {
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`. `recipient` can always be
+ // converted into a valid pointer to an
+ // `AIBinder_DeathRecipient`.
+ //
+ // The cookie is also the correct pointer, and by calling new_cookie,
+ // we have created a new ref-count to the cookie, which linkToDeath
+ // takes ownership of. Once the DeathRecipient is unlinked for any
+ // reason (including if this call fails), the onUnlinked callback
+ // will consume that ref-count.
status_result(unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`. `recipient` can always be
- // converted into a valid pointer to an
- // `AIBinder_DeathRecipient`.
- //
- // The cookie is also the correct pointer, and by calling new_cookie,
- // we have created a new ref-count to the cookie, which linkToDeath
- // takes ownership of. Once the DeathRecipient is unlinked for any
- // reason (including if this call fails), the onUnlinked callback
- // will consume that ref-count.
sys::AIBinder_linkToDeath(
self.as_native_mut(),
recipient.as_native_mut(),
@@ -397,13 +373,13 @@ impl<T: AsNative<sys::AIBinder>> IBinder for T {
}
fn unlink_to_death(&mut self, recipient: &mut DeathRecipient) -> Result<()> {
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`. `recipient` can always be
+ // converted into a valid pointer to an
+ // `AIBinder_DeathRecipient`. Any value is safe to pass as the
+ // cookie, although we depend on this value being set by
+ // `get_cookie` when the death recipient callback is called.
status_result(unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`. `recipient` can always be
- // converted into a valid pointer to an
- // `AIBinder_DeathRecipient`. Any value is safe to pass as the
- // cookie, although we depend on this value being set by
- // `get_cookie` when the death recipient callback is called.
sys::AIBinder_unlinkToDeath(
self.as_native_mut(),
recipient.as_native_mut(),
@@ -413,13 +389,11 @@ impl<T: AsNative<sys::AIBinder>> IBinder for T {
}
fn ping_binder(&mut self) -> Result<()> {
- let status = unsafe {
- // Safety: `SpIBinder` guarantees that `self` always contains a
- // valid pointer to an `AIBinder`.
- //
- // This call does not affect ownership of its pointer parameter.
- sys::AIBinder_ping(self.as_native_mut())
- };
+ // Safety: `SpIBinder` guarantees that `self` always contains a
+ // valid pointer to an `AIBinder`.
+ //
+ // This call does not affect ownership of its pointer parameter.
+ let status = unsafe { sys::AIBinder_ping(self.as_native_mut()) };
status_result(status)
}
}
@@ -439,6 +413,14 @@ impl SerializeOption for SpIBinder {
impl SerializeArray for SpIBinder {}
impl Deserialize for SpIBinder {
+ type UninitType = Option<Self>;
+ fn uninit() -> Self::UninitType {
+ Self::UninitType::default()
+ }
+ fn from_init(value: Self) -> Self::UninitType {
+ Some(value)
+ }
+
fn deserialize(parcel: &BorrowedParcel<'_>) -> Result<SpIBinder> {
parcel.read_binder().transpose().unwrap_or(Err(StatusCode::UNEXPECTED_NULL))
}
@@ -464,35 +446,31 @@ impl fmt::Debug for WpIBinder {
}
}
-/// # Safety
-///
-/// A `WpIBinder` is an immutable handle to a C++ IBinder, which is thread-safe.
+/// Safety: A `WpIBinder` is an immutable handle to a C++ IBinder, which is
+/// thread-safe.
unsafe impl Send for WpIBinder {}
-/// # Safety
-///
-/// A `WpIBinder` is an immutable handle to a C++ IBinder, which is thread-safe.
+/// Safety: A `WpIBinder` is an immutable handle to a C++ IBinder, which is
+/// thread-safe.
unsafe impl Sync for WpIBinder {}
impl WpIBinder {
/// Create a new weak reference from an object that can be converted into a
/// raw `AIBinder` pointer.
fn new<B: AsNative<sys::AIBinder>>(binder: &mut B) -> WpIBinder {
- let ptr = unsafe {
- // Safety: `SpIBinder` guarantees that `binder` always contains a
- // valid pointer to an `AIBinder`.
- sys::AIBinder_Weak_new(binder.as_native_mut())
- };
+ // Safety: `SpIBinder` guarantees that `binder` always contains a valid
+ // pointer to an `AIBinder`.
+ let ptr = unsafe { sys::AIBinder_Weak_new(binder.as_native_mut()) };
Self(ptr::NonNull::new(ptr).expect("Unexpected null pointer from AIBinder_Weak_new"))
}
/// Promote this weak reference to a strong reference to the binder object.
pub fn promote(&self) -> Option<SpIBinder> {
+ // Safety: `WpIBinder` always contains a valid weak reference, so we can
+ // pass this pointer to `AIBinder_Weak_promote`. Returns either null or
+ // an AIBinder owned by the caller, both of which are valid to pass to
+ // `SpIBinder::from_raw`.
unsafe {
- // Safety: `WpIBinder` always contains a valid weak reference, so we
- // can pass this pointer to `AIBinder_Weak_promote`. Returns either
- // null or an AIBinder owned by the caller, both of which are valid
- // to pass to `SpIBinder::from_raw`.
let ptr = sys::AIBinder_Weak_promote(self.0.as_ptr());
SpIBinder::from_raw(ptr)
}
@@ -501,35 +479,27 @@ impl WpIBinder {
impl Clone for WpIBinder {
fn clone(&self) -> Self {
- let ptr = unsafe {
- // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer,
- // so this pointer is always safe to pass to `AIBinder_Weak_clone`
- // (although null is also a safe value to pass to this API).
- //
- // We get ownership of the returned pointer, so can construct a new
- // WpIBinder object from it.
- sys::AIBinder_Weak_clone(self.0.as_ptr())
- };
+ // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer, so
+ // this pointer is always safe to pass to `AIBinder_Weak_clone`
+ // (although null is also a safe value to pass to this API).
+ //
+ // We get ownership of the returned pointer, so can construct a new
+ // WpIBinder object from it.
+ let ptr = unsafe { sys::AIBinder_Weak_clone(self.0.as_ptr()) };
Self(ptr::NonNull::new(ptr).expect("Unexpected null pointer from AIBinder_Weak_clone"))
}
}
impl Ord for WpIBinder {
fn cmp(&self, other: &Self) -> Ordering {
- let less_than = unsafe {
- // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer,
- // so this pointer is always safe to pass to `AIBinder_Weak_lt`
- // (null is also safe to pass to this function, but we should never
- // do that).
- sys::AIBinder_Weak_lt(self.0.as_ptr(), other.0.as_ptr())
- };
- let greater_than = unsafe {
- // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer,
- // so this pointer is always safe to pass to `AIBinder_Weak_lt`
- // (null is also safe to pass to this function, but we should never
- // do that).
- sys::AIBinder_Weak_lt(other.0.as_ptr(), self.0.as_ptr())
- };
+ // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer, so
+ // this pointer is always safe to pass to `AIBinder_Weak_lt` (null is
+ // also safe to pass to this function, but we should never do that).
+ let less_than = unsafe { sys::AIBinder_Weak_lt(self.0.as_ptr(), other.0.as_ptr()) };
+ // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer, so
+ // this pointer is always safe to pass to `AIBinder_Weak_lt` (null is
+ // also safe to pass to this function, but we should never do that).
+ let greater_than = unsafe { sys::AIBinder_Weak_lt(other.0.as_ptr(), self.0.as_ptr()) };
if !less_than && !greater_than {
Ordering::Equal
} else if less_than {
@@ -556,9 +526,9 @@ impl Eq for WpIBinder {}
impl Drop for WpIBinder {
fn drop(&mut self) {
+ // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer, so we
+ // know this pointer is safe to pass to `AIBinder_Weak_delete` here.
unsafe {
- // Safety: WpIBinder always holds a valid `AIBinder_Weak` pointer, so we
- // know this pointer is safe to pass to `AIBinder_Weak_delete` here.
sys::AIBinder_Weak_delete(self.0.as_ptr());
}
}
@@ -566,7 +536,7 @@ impl Drop for WpIBinder {
/// Rust wrapper around DeathRecipient objects.
///
-/// The cookie in this struct represents an Arc<F> for the owned callback.
+/// The cookie in this struct represents an `Arc<F>` for the owned callback.
/// This struct owns a ref-count of it, and so does every binder that we
/// have been linked with.
///
@@ -584,17 +554,13 @@ struct DeathRecipientVtable {
cookie_decr_refcount: unsafe extern "C" fn(*mut c_void),
}
-/// # Safety
-///
-/// A `DeathRecipient` is a wrapper around `AIBinder_DeathRecipient` and a pointer
-/// to a `Fn` which is `Sync` and `Send` (the cookie field). As
+/// Safety: A `DeathRecipient` is a wrapper around `AIBinder_DeathRecipient` and
+/// a pointer to a `Fn` which is `Sync` and `Send` (the cookie field). As
/// `AIBinder_DeathRecipient` is threadsafe, this structure is too.
unsafe impl Send for DeathRecipient {}
-/// # Safety
-///
-/// A `DeathRecipient` is a wrapper around `AIBinder_DeathRecipient` and a pointer
-/// to a `Fn` which is `Sync` and `Send` (the cookie field). As
+/// Safety: A `DeathRecipient` is a wrapper around `AIBinder_DeathRecipient` and
+/// a pointer to a `Fn` which is `Sync` and `Send` (the cookie field). As
/// `AIBinder_DeathRecipient` is threadsafe, this structure is too.
unsafe impl Sync for DeathRecipient {}
@@ -606,19 +572,17 @@ impl DeathRecipient {
F: Fn() + Send + Sync + 'static,
{
let callback: *const F = Arc::into_raw(Arc::new(callback));
- let recipient = unsafe {
- // Safety: The function pointer is a valid death recipient callback.
- //
- // This call returns an owned `AIBinder_DeathRecipient` pointer
- // which must be destroyed via `AIBinder_DeathRecipient_delete` when
- // no longer needed.
- sys::AIBinder_DeathRecipient_new(Some(Self::binder_died::<F>))
- };
+ // Safety: The function pointer is a valid death recipient callback.
+ //
+ // This call returns an owned `AIBinder_DeathRecipient` pointer which
+ // must be destroyed via `AIBinder_DeathRecipient_delete` when no longer
+ // needed.
+ let recipient = unsafe { sys::AIBinder_DeathRecipient_new(Some(Self::binder_died::<F>)) };
+ // Safety: The function pointer is a valid onUnlinked callback.
+ //
+ // All uses of linkToDeath in this file correctly increment the
+ // ref-count that this onUnlinked callback will decrement.
unsafe {
- // Safety: The function pointer is a valid onUnlinked callback.
- //
- // All uses of linkToDeath in this file correctly increment the
- // ref-count that this onUnlinked callback will decrement.
sys::AIBinder_DeathRecipient_setOnUnlinked(
recipient,
Some(Self::cookie_decr_refcount::<F>),
@@ -640,7 +604,12 @@ impl DeathRecipient {
///
/// The caller must handle the returned ref-count correctly.
unsafe fn new_cookie(&self) -> *mut c_void {
- (self.vtable.cookie_incr_refcount)(self.cookie);
+ // Safety: `cookie_incr_refcount` points to
+ // `Self::cookie_incr_refcount`, and `self.cookie` is the cookie for an
+ // Arc<F>.
+ unsafe {
+ (self.vtable.cookie_incr_refcount)(self.cookie);
+ }
// Return a raw pointer with ownership of a ref-count
self.cookie
@@ -659,13 +628,14 @@ impl DeathRecipient {
///
/// # Safety
///
- /// The `cookie` parameter must be the cookie for an Arc<F> and
+ /// The `cookie` parameter must be the cookie for an `Arc<F>` and
/// the caller must hold a ref-count to it.
unsafe extern "C" fn binder_died<F>(cookie: *mut c_void)
where
F: Fn() + Send + Sync + 'static,
{
- let callback = (cookie as *const F).as_ref().unwrap();
+ // Safety: The caller promises that `cookie` is for an Arc<F>.
+ let callback = unsafe { (cookie as *const F).as_ref().unwrap() };
callback();
}
@@ -674,34 +644,34 @@ impl DeathRecipient {
///
/// # Safety
///
- /// The `cookie` parameter must be the cookie for an Arc<F> and
+ /// The `cookie` parameter must be the cookie for an `Arc<F>` and
/// the owner must give up a ref-count to it.
unsafe extern "C" fn cookie_decr_refcount<F>(cookie: *mut c_void)
where
F: Fn() + Send + Sync + 'static,
{
- drop(Arc::from_raw(cookie as *const F));
+ // Safety: The caller promises that `cookie` is for an Arc<F>.
+ drop(unsafe { Arc::from_raw(cookie as *const F) });
}
/// Callback that increments the ref-count.
///
/// # Safety
///
- /// The `cookie` parameter must be the cookie for an Arc<F> and
+ /// The `cookie` parameter must be the cookie for an `Arc<F>` and
/// the owner must handle the created ref-count properly.
unsafe extern "C" fn cookie_incr_refcount<F>(cookie: *mut c_void)
where
F: Fn() + Send + Sync + 'static,
{
- let arc = mem::ManuallyDrop::new(Arc::from_raw(cookie as *const F));
+ // Safety: The caller promises that `cookie` is for an Arc<F>.
+ let arc = mem::ManuallyDrop::new(unsafe { Arc::from_raw(cookie as *const F) });
mem::forget(Arc::clone(&arc));
}
}
-/// # Safety
-///
-/// A `DeathRecipient` is always constructed with a valid raw pointer to an
-/// `AIBinder_DeathRecipient`, so it is always type-safe to extract this
+/// Safety: A `DeathRecipient` is always constructed with a valid raw pointer to
+/// an `AIBinder_DeathRecipient`, so it is always type-safe to extract this
/// pointer.
unsafe impl AsNative<sys::AIBinder_DeathRecipient> for DeathRecipient {
fn as_native(&self) -> *const sys::AIBinder_DeathRecipient {
@@ -715,18 +685,19 @@ unsafe impl AsNative<sys::AIBinder_DeathRecipient> for DeathRecipient {
impl Drop for DeathRecipient {
fn drop(&mut self) {
+ // Safety: `self.recipient` is always a valid, owned
+ // `AIBinder_DeathRecipient` pointer returned by
+ // `AIBinder_DeathRecipient_new` when `self` was created. This delete
+ // method can only be called once when `self` is dropped.
unsafe {
- // Safety: `self.recipient` is always a valid, owned
- // `AIBinder_DeathRecipient` pointer returned by
- // `AIBinder_DeathRecipient_new` when `self` was created. This
- // delete method can only be called once when `self` is dropped.
sys::AIBinder_DeathRecipient_delete(self.recipient);
+ }
- // Safety: We own a ref-count to the cookie, and so does every
- // linked binder. This call gives up our ref-count. The linked
- // binders should already have given up their ref-count, or should
- // do so shortly.
- (self.vtable.cookie_decr_refcount)(self.cookie)
+ // Safety: We own a ref-count to the cookie, and so does every linked
+ // binder. This call gives up our ref-count. The linked binders should
+ // already have given up their ref-count, or should do so shortly.
+ unsafe {
+ (self.vtable.cookie_decr_refcount)(self.cookie);
}
}
}
@@ -746,11 +717,9 @@ pub trait Proxy: Sized + Interface {
fn from_binder(binder: SpIBinder) -> Result<Self>;
}
-/// # Safety
-///
-/// This is a convenience method that wraps `AsNative` for `SpIBinder` to allow
-/// invocation of `IBinder` methods directly from `Interface` objects. It shares
-/// the same safety as the implementation for `SpIBinder`.
+/// Safety: This is a convenience method that wraps `AsNative` for `SpIBinder`
+/// to allow invocation of `IBinder` methods directly from `Interface` objects.
+/// It shares the same safety as the implementation for `SpIBinder`.
unsafe impl<T: Proxy> AsNative<sys::AIBinder> for T {
fn as_native(&self) -> *const sys::AIBinder {
self.as_binder().as_native()
@@ -761,101 +730,8 @@ unsafe impl<T: Proxy> AsNative<sys::AIBinder> for T {
}
}
-/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
-/// exist.
-pub fn get_service(name: &str) -> Option<SpIBinder> {
- let name = CString::new(name).ok()?;
- unsafe {
- // Safety: `AServiceManager_getService` returns either a null pointer or
- // a valid pointer to an owned `AIBinder`. Either of these values is
- // safe to pass to `SpIBinder::from_raw`.
- SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr()))
- }
-}
-
-/// Retrieve an existing service, or start it if it is configured as a dynamic
-/// service and isn't yet started.
-pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
- let name = CString::new(name).ok()?;
- unsafe {
- // Safety: `AServiceManager_waitforService` returns either a null
- // pointer or a valid pointer to an owned `AIBinder`. Either of these
- // values is safe to pass to `SpIBinder::from_raw`.
- SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr()))
- }
-}
-
-/// Retrieve an existing service for a particular interface, blocking for a few
-/// seconds if it doesn't yet exist.
-pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
- interface_cast(get_service(name))
-}
-
-/// Retrieve an existing service for a particular interface, or start it if it
-/// is configured as a dynamic service and isn't yet started.
-pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
- interface_cast(wait_for_service(name))
-}
-
-/// Check if a service is declared (e.g. in a VINTF manifest)
-pub fn is_declared(interface: &str) -> Result<bool> {
- let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
-
- unsafe {
- // Safety: `interface` is a valid null-terminated C-style string and is
- // only borrowed for the lifetime of the call. The `interface` local
- // outlives this call as it lives for the function scope.
- Ok(sys::AServiceManager_isDeclared(interface.as_ptr()))
- }
-}
-
-/// Retrieve all declared instances for a particular interface
-///
-/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
-/// is passed here, then ["foo"] would be returned.
-pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
- unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
- // Safety: opaque was a mutable pointer created below from a Vec of
- // CString, and outlives this callback. The null handling here is just
- // to avoid the possibility of unwinding across C code if this crate is
- // ever compiled with panic=unwind.
- if let Some(instances) = opaque.cast::<Vec<CString>>().as_mut() {
- // Safety: instance is a valid null-terminated C string with a
- // lifetime at least as long as this function, and we immediately
- // copy it into an owned CString.
- instances.push(CStr::from_ptr(instance).to_owned());
- } else {
- eprintln!("Opaque pointer was null in get_declared_instances callback!");
- }
- }
-
- let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
- let mut instances: Vec<CString> = vec![];
- unsafe {
- // Safety: `interface` and `instances` are borrowed for the length of
- // this call and both outlive the call. `interface` is guaranteed to be
- // a valid null-terminated C-style string.
- sys::AServiceManager_forEachDeclaredInstance(
- interface.as_ptr(),
- &mut instances as *mut _ as *mut c_void,
- Some(callback),
- );
- }
-
- instances
- .into_iter()
- .map(CString::into_string)
- .collect::<std::result::Result<Vec<String>, _>>()
- .map_err(|e| {
- eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
- StatusCode::BAD_VALUE
- })
-}
-
-/// # Safety
-///
-/// `SpIBinder` guarantees that `binder` always contains a valid pointer to an
-/// `AIBinder`, so we can trivially extract this pointer here.
+/// Safety: `SpIBinder` guarantees that `binder` always contains a valid pointer
+/// to an `AIBinder`, so we can trivially extract this pointer here.
unsafe impl AsNative<sys::AIBinder> for SpIBinder {
fn as_native(&self) -> *const sys::AIBinder {
self.0.as_ptr()
diff --git a/libs/binder/rust/src/service.rs b/libs/binder/rust/src/service.rs
new file mode 100644
index 0000000000..3ca3b540c4
--- /dev/null
+++ b/libs/binder/rust/src/service.rs
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crate::binder::{AsNative, FromIBinder, Strong};
+use crate::error::{status_result, Result, StatusCode};
+use crate::proxy::SpIBinder;
+use crate::sys;
+
+use std::ffi::{c_void, CStr, CString};
+use std::os::raw::c_char;
+use std::sync::Mutex;
+
+/// Register a new service with the default service manager.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+ let instance = CString::new(identifier).unwrap();
+ let status =
+ // Safety: `AServiceManager_addService` expects valid `AIBinder` and C
+ // string pointers. Caller retains ownership of both pointers.
+ // `AServiceManager_addService` creates a new strong reference and copies
+ // the string, so both pointers need only be valid until the call returns.
+ unsafe { sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr()) };
+ status_result(status)
+}
+
+/// Register a dynamic service via the LazyServiceRegistrar.
+///
+/// Registers the given binder object with the given identifier. If successful,
+/// this service can then be retrieved using that identifier. The service process
+/// will be shut down once all registered services are no longer in use.
+///
+/// If any service in the process is registered as lazy, all should be, otherwise
+/// the process may be shut down while a service is in use.
+///
+/// This function will panic if the identifier contains a 0 byte (NUL).
+pub fn register_lazy_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
+ let instance = CString::new(identifier).unwrap();
+ // Safety: `AServiceManager_registerLazyService` expects valid `AIBinder` and C
+ // string pointers. Caller retains ownership of both
+ // pointers. `AServiceManager_registerLazyService` creates a new strong reference
+ // and copies the string, so both pointers need only be valid until the
+ // call returns.
+ let status = unsafe {
+ sys::AServiceManager_registerLazyService(binder.as_native_mut(), instance.as_ptr())
+ };
+ status_result(status)
+}
+
+/// Prevent a process which registers lazy services from being shut down even when none
+/// of the services is in use.
+///
+/// If persist is true then shut down will be blocked until this function is called again with
+/// persist false. If this is to be the initial state, call this function before calling
+/// register_lazy_service.
+///
+/// Consider using [`LazyServiceGuard`] rather than calling this directly.
+pub fn force_lazy_services_persist(persist: bool) {
+ // Safety: No borrowing or transfer of ownership occurs here.
+ unsafe { sys::AServiceManager_forceLazyServicesPersist(persist) }
+}
+
+/// An RAII object to ensure a process which registers lazy services is not killed. During the
+/// lifetime of any of these objects the service manager will not kill the process even if none
+/// of its lazy services are in use.
+#[must_use]
+#[derive(Debug)]
+pub struct LazyServiceGuard {
+ // Prevent construction outside this module.
+ _private: (),
+}
+
+// Count of how many LazyServiceGuard objects are in existence.
+static GUARD_COUNT: Mutex<u64> = Mutex::new(0);
+
+impl LazyServiceGuard {
+ /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
+ /// process.
+ pub fn new() -> Self {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count += 1;
+ if *count == 1 {
+ // It's important that we make this call with the mutex held, to make sure
+ // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
+ // sequenced. (That also means we can't just use an AtomicU64.)
+ force_lazy_services_persist(true);
+ }
+ Self { _private: () }
+ }
+}
+
+impl Drop for LazyServiceGuard {
+ fn drop(&mut self) {
+ let mut count = GUARD_COUNT.lock().unwrap();
+ *count -= 1;
+ if *count == 0 {
+ force_lazy_services_persist(false);
+ }
+ }
+}
+
+impl Clone for LazyServiceGuard {
+ fn clone(&self) -> Self {
+ Self::new()
+ }
+}
+
+impl Default for LazyServiceGuard {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+/// Determine whether the current thread is currently executing an incoming
+/// transaction.
+pub fn is_handling_transaction() -> bool {
+ // Safety: This method is always safe to call.
+ unsafe { sys::AIBinder_isHandlingTransaction() }
+}
+
+fn interface_cast<T: FromIBinder + ?Sized>(service: Option<SpIBinder>) -> Result<Strong<T>> {
+ if let Some(service) = service {
+ FromIBinder::try_from(service)
+ } else {
+ Err(StatusCode::NAME_NOT_FOUND)
+ }
+}
+
+/// Retrieve an existing service, blocking for a few seconds if it doesn't yet
+/// exist.
+pub fn get_service(name: &str) -> Option<SpIBinder> {
+ let name = CString::new(name).ok()?;
+ // Safety: `AServiceManager_getService` returns either a null pointer or a
+ // valid pointer to an owned `AIBinder`. Either of these values is safe to
+ // pass to `SpIBinder::from_raw`.
+ unsafe { SpIBinder::from_raw(sys::AServiceManager_getService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service, or start it if it is configured as a dynamic
+/// service and isn't yet started.
+pub fn wait_for_service(name: &str) -> Option<SpIBinder> {
+ let name = CString::new(name).ok()?;
+ // Safety: `AServiceManager_waitforService` returns either a null pointer or
+ // a valid pointer to an owned `AIBinder`. Either of these values is safe to
+ // pass to `SpIBinder::from_raw`.
+ unsafe { SpIBinder::from_raw(sys::AServiceManager_waitForService(name.as_ptr())) }
+}
+
+/// Retrieve an existing service for a particular interface, blocking for a few
+/// seconds if it doesn't yet exist.
+pub fn get_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+ interface_cast(get_service(name))
+}
+
+/// Retrieve an existing service for a particular interface, or start it if it
+/// is configured as a dynamic service and isn't yet started.
+pub fn wait_for_interface<T: FromIBinder + ?Sized>(name: &str) -> Result<Strong<T>> {
+ interface_cast(wait_for_service(name))
+}
+
+/// Check if a service is declared (e.g. in a VINTF manifest)
+pub fn is_declared(interface: &str) -> Result<bool> {
+ let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+
+ // Safety: `interface` is a valid null-terminated C-style string and is only
+ // borrowed for the lifetime of the call. The `interface` local outlives
+ // this call as it lives for the function scope.
+ unsafe { Ok(sys::AServiceManager_isDeclared(interface.as_ptr())) }
+}
+
+/// Retrieve all declared instances for a particular interface
+///
+/// For instance, if 'android.foo.IFoo/foo' is declared, and 'android.foo.IFoo'
+/// is passed here, then ["foo"] would be returned.
+pub fn get_declared_instances(interface: &str) -> Result<Vec<String>> {
+ unsafe extern "C" fn callback(instance: *const c_char, opaque: *mut c_void) {
+ // Safety: opaque was a mutable pointer created below from a Vec of
+ // CString, and outlives this callback. The null handling here is just
+ // to avoid the possibility of unwinding across C code if this crate is
+ // ever compiled with panic=unwind.
+ if let Some(instances) = unsafe { opaque.cast::<Vec<CString>>().as_mut() } {
+ // Safety: instance is a valid null-terminated C string with a
+ // lifetime at least as long as this function, and we immediately
+ // copy it into an owned CString.
+ unsafe {
+ instances.push(CStr::from_ptr(instance).to_owned());
+ }
+ } else {
+ eprintln!("Opaque pointer was null in get_declared_instances callback!");
+ }
+ }
+
+ let interface = CString::new(interface).or(Err(StatusCode::UNEXPECTED_NULL))?;
+ let mut instances: Vec<CString> = vec![];
+ // Safety: `interface` and `instances` are borrowed for the length of this
+ // call and both outlive the call. `interface` is guaranteed to be a valid
+ // null-terminated C-style string.
+ unsafe {
+ sys::AServiceManager_forEachDeclaredInstance(
+ interface.as_ptr(),
+ &mut instances as *mut _ as *mut c_void,
+ Some(callback),
+ );
+ }
+
+ instances
+ .into_iter()
+ .map(CString::into_string)
+ .collect::<std::result::Result<Vec<String>, _>>()
+ .map_err(|e| {
+ eprintln!("An interface instance name was not a valid UTF-8 string: {}", e);
+ StatusCode::BAD_VALUE
+ })
+}
diff --git a/libs/binder/rust/src/state.rs b/libs/binder/rust/src/state.rs
index cc18741a4e..8a06274e9c 100644
--- a/libs/binder/rust/src/state.rs
+++ b/libs/binder/rust/src/state.rs
@@ -22,30 +22,48 @@ use libc::{pid_t, uid_t};
pub struct ProcessState;
impl ProcessState {
- /// Start the Binder IPC thread pool
+ /// Starts the Binder IPC thread pool.
+ ///
+ /// Starts 1 thread, plus allows the kernel to lazily start up to
+ /// `num_threads` additional threads as specified by
+ /// [`set_thread_pool_max_thread_count`](Self::set_thread_pool_max_thread_count).
+ ///
+ /// This should be done before creating any Binder client or server. If
+ /// neither this nor [`join_thread_pool`](Self::join_thread_pool) are
+ /// called, then some things (such as callbacks and
+ /// [`IBinder::link_to_death`](crate::IBinder::link_to_death)) will silently
+ /// not work: the callbacks will be queued but never called as there is no
+ /// thread to call them on.
pub fn start_thread_pool() {
+ // Safety: Safe FFI
unsafe {
- // Safety: Safe FFI
sys::ABinderProcess_startThreadPool();
}
}
- /// Set the maximum number of threads that can be started in the threadpool.
+ /// Sets the maximum number of threads that can be started in the
+ /// threadpool.
///
- /// By default, after startThreadPool is called, this is 15. If it is called
- /// additional times, it will only prevent the kernel from starting new
- /// threads and will not delete already existing threads.
+ /// By default, after [`start_thread_pool`](Self::start_thread_pool) is
+ /// called, this is 15. If it is called additional times, the thread pool
+ /// size can only be increased.
pub fn set_thread_pool_max_thread_count(num_threads: u32) {
+ // Safety: Safe FFI
unsafe {
- // Safety: Safe FFI
sys::ABinderProcess_setThreadPoolMaxThreadCount(num_threads);
}
}
- /// Block on the Binder IPC thread pool
+ /// Blocks on the Binder IPC thread pool by adding the current thread to the
+ /// pool.
+ ///
+ /// Note that this adds the current thread in addition to those that are
+ /// created by
+ /// [`set_thread_pool_max_thread_count`](Self::set_thread_pool_max_thread_count)
+ /// and [`start_thread_pool`](Self::start_thread_pool).
pub fn join_thread_pool() {
+ // Safety: Safe FFI
unsafe {
- // Safety: Safe FFI
sys::ABinderProcess_joinThreadPool();
}
}
@@ -68,10 +86,8 @@ impl ThreadState {
/// \return calling uid or the current process's UID if this thread isn't
/// processing a transaction.
pub fn get_calling_uid() -> uid_t {
- unsafe {
- // Safety: Safe FFI
- sys::AIBinder_getCallingUid()
- }
+ // Safety: Safe FFI
+ unsafe { sys::AIBinder_getCallingUid() }
}
/// This returns the calling PID assuming that this thread is called from a
@@ -85,18 +101,19 @@ impl ThreadState {
/// dies and is replaced with another process with elevated permissions and
/// the same PID.
///
+ /// Warning: oneway transactions do not receive PID. Even if you expect
+ /// a transaction to be synchronous, a misbehaving client could send it
+ /// as a synchronous call and result in a 0 PID here. Additionally, if
+ /// there is a race and the calling process dies, the PID may still be
+ /// 0 for a synchronous call.
+ ///
/// Available since API level 29.
///
/// \return calling pid or the current process's PID if this thread isn't
/// processing a transaction.
- ///
- /// If the transaction being processed is a oneway transaction, then this
- /// method will return 0.
pub fn get_calling_pid() -> pid_t {
- unsafe {
- // Safety: Safe FFI
- sys::AIBinder_getCallingPid()
- }
+ // Safety: Safe FFI
+ unsafe { sys::AIBinder_getCallingPid() }
}
/// Determine whether the current thread is currently executing an incoming transaction.
@@ -104,10 +121,8 @@ impl ThreadState {
/// \return true if the current thread is currently executing an incoming transaction, and false
/// otherwise.
pub fn is_handling_transaction() -> bool {
- unsafe {
- // Safety: Safe FFI
- sys::AIBinder_isHandlingTransaction()
- }
+ // Safety: Safe FFI
+ unsafe { sys::AIBinder_isHandlingTransaction() }
}
/// This function makes the client's security context available to the
diff --git a/libs/binder/rust/sys/lib.rs b/libs/binder/rust/sys/lib.rs
index 1d1a295221..5352473272 100644
--- a/libs/binder/rust/sys/lib.rs
+++ b/libs/binder/rust/sys/lib.rs
@@ -19,7 +19,22 @@
use std::error::Error;
use std::fmt;
-include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+#[cfg(not(target_os = "trusty"))]
+mod bindings {
+ include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+}
+
+// Trusty puts the full path to the auto-generated file in BINDGEN_INC_FILE
+// and builds it with warnings-as-errors, so we need to use #[allow(bad_style)].
+// We need to use cfg(target_os) instead of cfg(trusty) here because of
+// the difference between the two build systems, which we cannot mock.
+#[cfg(target_os = "trusty")]
+#[allow(bad_style)]
+mod bindings {
+ include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use bindings::*;
impl Error for android_c_interface_StatusCode {}
diff --git a/libs/binder/rust/tests/binderRustNdkInteropTest.cpp b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
index 59ca6edf12..663b9bbe37 100644
--- a/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
+++ b/libs/binder/rust/tests/binderRustNdkInteropTest.cpp
@@ -54,14 +54,12 @@ TEST(RustNdkInterop, NdkCanCallRust) {
EXPECT_EQ(STATUS_OK, AIBinder_ping(binder.get()));
auto interface = aidl::IBinderRustNdkInteropTest::fromBinder(binder);
- // TODO(b/167723746): this test requires that fromBinder allow association
- // with an already associated local binder by treating it as remote.
- EXPECT_EQ(interface, nullptr);
+ EXPECT_NE(interface, nullptr);
- // std::string in("testing");
- // std::string out;
- // EXPECT_TRUE(interface->echo(in, &out).isOk());
- // EXPECT_EQ(in, out);
+ std::string in("testing");
+ std::string out;
+ EXPECT_TRUE(interface->echo(in, &out).isOk());
+ EXPECT_EQ(in, out);
}
int main(int argc, char** argv) {
diff --git a/libs/binder/rust/tests/integration.rs b/libs/binder/rust/tests/integration.rs
index ca2cedc19d..c87fa89756 100644
--- a/libs/binder/rust/tests/integration.rs
+++ b/libs/binder/rust/tests/integration.rs
@@ -26,7 +26,7 @@ use binder::binder_impl::{
use std::convert::{TryFrom, TryInto};
use std::ffi::CStr;
-use std::fs::File;
+use std::io::Write;
use std::sync::Mutex;
/// Name of service runner.
@@ -118,7 +118,7 @@ impl TryFrom<u32> for TestTransactionCode {
}
impl Interface for TestService {
- fn dump(&self, _file: &File, args: &[&CStr]) -> Result<(), StatusCode> {
+ fn dump(&self, _writer: &mut dyn Write, args: &[&CStr]) -> Result<(), StatusCode> {
let mut dump_args = self.dump_args.lock().unwrap();
dump_args.extend(args.iter().map(|s| s.to_str().unwrap().to_owned()));
Ok(())
@@ -545,6 +545,11 @@ mod tests {
}
fn get_expected_selinux_context() -> &'static str {
+ // SAFETY: The pointer we pass to `getcon` is valid because it comes from a reference, and
+ // `getcon` doesn't retain it after it returns. If `getcon` succeeds then `out_ptr` will
+ // point to a valid C string, otherwise it will remain null. We check for null, so the
+ // pointer we pass to `CStr::from_ptr` must be a valid pointer to a C string. There is a
+ // memory leak as we don't call `freecon`, but that's fine because this is just a test.
unsafe {
let mut out_ptr = ptr::null_mut();
assert_eq!(selinux_sys::getcon(&mut out_ptr), 0);
diff --git a/libs/binder/rust/tests/ndk_rust_interop.rs b/libs/binder/rust/tests/ndk_rust_interop.rs
index 415ede1a7b..fbedfee956 100644
--- a/libs/binder/rust/tests/ndk_rust_interop.rs
+++ b/libs/binder/rust/tests/ndk_rust_interop.rs
@@ -28,10 +28,11 @@ use std::os::raw::{c_char, c_int};
///
/// # Safety
///
-/// service_name must be a valid, non-null C-style string (null-terminated).
+/// service_name must be a valid, non-null C-style string (nul-terminated).
#[no_mangle]
pub unsafe extern "C" fn rust_call_ndk(service_name: *const c_char) -> c_int {
- let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+ // SAFETY: Our caller promises that service_name is a valid C string.
+ let service_name = unsafe { CStr::from_ptr(service_name) }.to_str().unwrap();
// The Rust class descriptor pointer will not match the NDK one, but the
// descriptor strings match so this needs to still associate.
@@ -57,7 +58,7 @@ pub unsafe extern "C" fn rust_call_ndk(service_name: *const c_char) -> c_int {
let wrong_service: Result<binder::Strong<dyn IBinderRustNdkInteropTestOther>, StatusCode> =
binder::get_interface(service_name);
match wrong_service {
- Err(e) if e == StatusCode::BAD_TYPE => {}
+ Err(StatusCode::BAD_TYPE) => {}
Err(e) => {
eprintln!("Trying to use a service via the wrong interface errored with unexpected error {:?}", e);
return e as c_int;
@@ -85,10 +86,11 @@ impl IBinderRustNdkInteropTest for Service {
///
/// # Safety
///
-/// service_name must be a valid, non-null C-style string (null-terminated).
+/// service_name must be a valid, non-null C-style string (nul-terminated).
#[no_mangle]
pub unsafe extern "C" fn rust_start_service(service_name: *const c_char) -> c_int {
- let service_name = CStr::from_ptr(service_name).to_str().unwrap();
+ // SAFETY: Our caller promises that service_name is a valid C string.
+ let service_name = unsafe { CStr::from_ptr(service_name) }.to_str().unwrap();
let service = BnBinderRustNdkInteropTest::new_binder(Service, BinderFeatures::default());
match binder::add_service(service_name, service.as_binder()) {
Ok(_) => StatusCode::OK as c_int,
diff --git a/libs/binder/rust/tests/parcel_fuzzer/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/Android.bp
index df8a2afb03..6eb707bcf7 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/rust/tests/parcel_fuzzer/Android.bp
@@ -3,25 +3,34 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-rust_fuzz {
- name: "parcel_fuzzer_rs",
- srcs: [
- "parcel_fuzzer.rs",
- ],
+rust_defaults {
+ name: "service_fuzzer_defaults_rs",
rustlibs: [
- "libarbitrary",
- "libnum_traits",
"libbinder_rs",
"libbinder_random_parcel_rs",
- "binderReadParcelIface-rust",
],
-
fuzz_config: {
cc: [
"waghpawan@google.com",
"smoreland@google.com",
],
+ triage_assignee: "waghpawan@google.com",
// hotlist "AIDL fuzzers bugs" on buganizer
hotlists: ["4637097"],
},
}
+
+rust_fuzz {
+ name: "parcel_fuzzer_rs",
+ srcs: [
+ "parcel_fuzzer.rs",
+ ],
+ defaults: [
+ "service_fuzzer_defaults_rs",
+ ],
+ rustlibs: [
+ "libarbitrary",
+ "libnum_traits",
+ "binderReadParcelIface-rust",
+ ],
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
index 29bf92cb97..ce0f742934 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/parcel_fuzzer.rs
@@ -105,9 +105,9 @@ fn do_read_fuzz(read_operations: Vec<ReadOperation>, data: &[u8]) {
for operation in read_operations {
match operation {
ReadOperation::SetDataPosition { pos } => {
+ // Safety: Safe if pos is less than current size of the parcel.
+ // It relies on C++ code for bound checks
unsafe {
- // Safety: Safe if pos is less than current size of the parcel.
- // It relies on C++ code for bound checks
match parcel.set_data_position(pos) {
Ok(result) => result,
Err(e) => println!("error occurred while setting data position: {:?}", e),
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
index 43a309409d..5cac6475cf 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/Android.bp
@@ -11,7 +11,6 @@ rust_bindgen {
source_stem: "bindings",
visibility: [":__subpackages__"],
bindgen_flags: [
- "--size_t-is-usize",
"--allowlist-function",
"createRandomParcel",
"--allowlist-function",
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
index 5cb406afc2..84130c17e1 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/fuzz_service_test/Android.bp
@@ -19,17 +19,10 @@ rust_fuzz {
srcs: [
"service_fuzzer.rs",
],
+ defaults: [
+ "service_fuzzer_defaults_rs",
+ ],
rustlibs: [
- "libbinder_rs",
- "libbinder_random_parcel_rs",
"testServiceInterface-rust",
],
- fuzz_config: {
- cc: [
- "waghpawan@google.com",
- "smoreland@google.com",
- ],
- // hotlist "AIDL fuzzers bugs" on buganizer
- hotlists: ["4637097"],
- },
}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
index 1bbd6742f2..896b78f488 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/src/lib.rs
@@ -35,10 +35,26 @@ pub fn create_random_parcel(fuzzer_data: &[u8]) -> Parcel {
/// This API automatically fuzzes provided service
pub fn fuzz_service(binder: &mut SpIBinder, fuzzer_data: &[u8]) {
- let ptr = binder.as_native_mut() as *mut c_void;
+ let mut binders = [binder];
+ fuzz_multiple_services(&mut binders, fuzzer_data);
+}
+
+/// This API automatically fuzzes provided services
+pub fn fuzz_multiple_services(binders: &mut [&mut SpIBinder], fuzzer_data: &[u8]) {
+ let mut cppBinders = vec![];
+ for binder in binders.iter_mut() {
+ let ptr = binder.as_native_mut() as *mut c_void;
+ cppBinders.push(ptr);
+ }
+
unsafe {
- // Safety: `SpIBinder::as_native_mut` and `slice::as_ptr` always
+ // Safety: `Vec::as_mut_ptr` and `slice::as_ptr` always
// return valid pointers.
- fuzzRustService(ptr, fuzzer_data.as_ptr(), fuzzer_data.len());
+ fuzzRustService(
+ cppBinders.as_mut_ptr(),
+ cppBinders.len(),
+ fuzzer_data.as_ptr(),
+ fuzzer_data.len(),
+ );
}
}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
index 831bd5660c..cfdd2abd05 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
+++ b/libs/binder/rust/tests/parcel_fuzzer/random_parcel/wrappers/RandomParcelWrapper.hpp
@@ -21,5 +21,5 @@ extern "C" {
void createRandomParcel(void* aParcel, const uint8_t* data, size_t len);
// This API is used by fuzzers to automatically fuzz aidl services
- void fuzzRustService(void* binder, const uint8_t* data, size_t len);
-} \ No newline at end of file
+ void fuzzRustService(void** binders, size_t numBinders, const uint8_t* data, size_t len);
+}
diff --git a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
index a2d48b6115..2c8d05facd 100644
--- a/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
+++ b/libs/binder/rust/tests/parcel_fuzzer/read_utils.rs
@@ -89,14 +89,17 @@ pub const READ_FUNCS: &[fn(&BorrowedParcel<'_>)] = &[
read_parcel_interface!(Option<Vec<u64>>),
read_parcel_interface!(Option<Vec<String>>),
read_parcel_interface!(ParcelFileDescriptor),
+ read_parcel_interface!(Vec<ParcelFileDescriptor>),
read_parcel_interface!(Vec<Option<ParcelFileDescriptor>>),
read_parcel_interface!(Option<Vec<ParcelFileDescriptor>>),
read_parcel_interface!(Option<Vec<Option<ParcelFileDescriptor>>>),
read_parcel_interface!(SpIBinder),
+ read_parcel_interface!(Vec<SpIBinder>),
read_parcel_interface!(Vec<Option<SpIBinder>>),
read_parcel_interface!(Option<Vec<SpIBinder>>),
read_parcel_interface!(Option<Vec<Option<SpIBinder>>>),
read_parcel_interface!(SomeParcelable),
+ read_parcel_interface!(Vec<SomeParcelable>),
read_parcel_interface!(Vec<Option<SomeParcelable>>),
read_parcel_interface!(Option<Vec<SomeParcelable>>),
read_parcel_interface!(Option<Vec<Option<SomeParcelable>>>),
diff --git a/libs/binder/rust/tests/serialization.cpp b/libs/binder/rust/tests/serialization.cpp
index 3f59dab3a9..0cdf8c56ab 100644
--- a/libs/binder/rust/tests/serialization.cpp
+++ b/libs/binder/rust/tests/serialization.cpp
@@ -14,6 +14,10 @@
* limitations under the License.
*/
+#include "serialization.hpp"
+#include "../../FdUtils.h"
+#include "../../tests/FileUtils.h"
+
#include <android/binder_ibinder_platform.h>
#include <android/binder_libbinder.h>
#include <binder/IServiceManager.h>
@@ -24,8 +28,6 @@
#include <gtest/gtest.h>
#include <utils/Errors.h>
#include <utils/String16.h>
-#include "android-base/file.h"
-#include "serialization.hpp"
#include <cmath>
#include <cstdint>
@@ -34,7 +36,7 @@
using namespace std;
using namespace android;
-using android::base::unique_fd;
+using android::binder::unique_fd;
using android::os::ParcelFileDescriptor;
// defined in Rust
@@ -349,7 +351,7 @@ TEST_F(SerializationTest, SerializeString) {
TEST_F(SerializationTest, SerializeFileDescriptor) {
unique_fd out_file, in_file;
- ASSERT_TRUE(base::Pipe(&out_file, &in_file));
+ ASSERT_TRUE(binder::Pipe(&out_file, &in_file));
vector<ParcelFileDescriptor> file_descriptors;
file_descriptors.push_back(ParcelFileDescriptor(std::move(out_file)));
diff --git a/libs/binder/rust/tests/serialization.hpp b/libs/binder/rust/tests/serialization.hpp
index 0041608ae0..9edcd6d9b6 100644
--- a/libs/binder/rust/tests/serialization.hpp
+++ b/libs/binder/rust/tests/serialization.hpp
@@ -14,7 +14,10 @@
* limitations under the License.
*/
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wpragma-once-outside-header"
#pragma once
+#pragma clang diagnostic pop
#include <binder/IBinder.h>
diff --git a/libs/binder/rust/tests/serialization.rs b/libs/binder/rust/tests/serialization.rs
index 6220db4b28..2b6c282530 100644
--- a/libs/binder/rust/tests/serialization.rs
+++ b/libs/binder/rust/tests/serialization.rs
@@ -26,7 +26,7 @@ use binder::{
use binder::binder_impl::{Binder, BorrowedParcel, TransactionCode};
use std::ffi::{c_void, CStr, CString};
-use std::sync::Once;
+use std::sync::OnceLock;
#[allow(
non_camel_case_types,
@@ -70,20 +70,18 @@ macro_rules! assert {
};
}
-static SERVICE_ONCE: Once = Once::new();
-static mut SERVICE: Option<SpIBinder> = None;
+static SERVICE: OnceLock<SpIBinder> = OnceLock::new();
/// Start binder service and return a raw AIBinder pointer to it.
///
/// Safe to call multiple times, only creates the service once.
#[no_mangle]
pub extern "C" fn rust_service() -> *mut c_void {
- unsafe {
- SERVICE_ONCE.call_once(|| {
- SERVICE = Some(BnReadParcelTest::new_binder((), BinderFeatures::default()).as_binder());
- });
- SERVICE.as_ref().unwrap().as_raw().cast()
- }
+ let service = SERVICE
+ .get_or_init(|| BnReadParcelTest::new_binder((), BinderFeatures::default()).as_binder());
+ // SAFETY: The SpIBinder will remain alive as long as the program is running because it is in
+ // the static SERVICE, so the pointer is valid forever.
+ unsafe { service.as_raw().cast() }
}
/// Empty interface just to use the declare_binder_interface macro
@@ -113,11 +111,13 @@ fn on_transact(
bindings::Transaction_TEST_BOOL => {
assert!(parcel.read::<bool>()?);
assert!(!parcel.read::<bool>()?);
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<bool>>()?, unsafe { bindings::TESTDATA_BOOL });
assert_eq!(parcel.read::<Option<Vec<bool>>>()?, None);
reply.write(&true)?;
reply.write(&false)?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_BOOL }[..])?;
reply.write(&(None as Option<Vec<bool>>))?;
}
@@ -125,14 +125,18 @@ fn on_transact(
assert_eq!(parcel.read::<i8>()?, 0);
assert_eq!(parcel.read::<i8>()?, 1);
assert_eq!(parcel.read::<i8>()?, i8::max_value());
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<i8>>()?, unsafe { bindings::TESTDATA_I8 });
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<u8>>()?, unsafe { bindings::TESTDATA_U8 });
assert_eq!(parcel.read::<Option<Vec<i8>>>()?, None);
reply.write(&0i8)?;
reply.write(&1i8)?;
reply.write(&i8::max_value())?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_I8 }[..])?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_U8 }[..])?;
reply.write(&(None as Option<Vec<i8>>))?;
}
@@ -140,12 +144,14 @@ fn on_transact(
assert_eq!(parcel.read::<u16>()?, 0);
assert_eq!(parcel.read::<u16>()?, 1);
assert_eq!(parcel.read::<u16>()?, u16::max_value());
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<u16>>()?, unsafe { bindings::TESTDATA_CHARS });
assert_eq!(parcel.read::<Option<Vec<u16>>>()?, None);
reply.write(&0u16)?;
reply.write(&1u16)?;
reply.write(&u16::max_value())?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_CHARS }[..])?;
reply.write(&(None as Option<Vec<u16>>))?;
}
@@ -153,12 +159,14 @@ fn on_transact(
assert_eq!(parcel.read::<i32>()?, 0);
assert_eq!(parcel.read::<i32>()?, 1);
assert_eq!(parcel.read::<i32>()?, i32::max_value());
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<i32>>()?, unsafe { bindings::TESTDATA_I32 });
assert_eq!(parcel.read::<Option<Vec<i32>>>()?, None);
reply.write(&0i32)?;
reply.write(&1i32)?;
reply.write(&i32::max_value())?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_I32 }[..])?;
reply.write(&(None as Option<Vec<i32>>))?;
}
@@ -166,12 +174,14 @@ fn on_transact(
assert_eq!(parcel.read::<i64>()?, 0);
assert_eq!(parcel.read::<i64>()?, 1);
assert_eq!(parcel.read::<i64>()?, i64::max_value());
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<i64>>()?, unsafe { bindings::TESTDATA_I64 });
assert_eq!(parcel.read::<Option<Vec<i64>>>()?, None);
reply.write(&0i64)?;
reply.write(&1i64)?;
reply.write(&i64::max_value())?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_I64 }[..])?;
reply.write(&(None as Option<Vec<i64>>))?;
}
@@ -179,12 +189,14 @@ fn on_transact(
assert_eq!(parcel.read::<u64>()?, 0);
assert_eq!(parcel.read::<u64>()?, 1);
assert_eq!(parcel.read::<u64>()?, u64::max_value());
+ // SAFETY: Just reading an extern constant.
assert_eq!(parcel.read::<Vec<u64>>()?, unsafe { bindings::TESTDATA_U64 });
assert_eq!(parcel.read::<Option<Vec<u64>>>()?, None);
reply.write(&0u64)?;
reply.write(&1u64)?;
reply.write(&u64::max_value())?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_U64 }[..])?;
reply.write(&(None as Option<Vec<u64>>))?;
}
@@ -192,10 +204,12 @@ fn on_transact(
assert_eq!(parcel.read::<f32>()?, 0f32);
let floats = parcel.read::<Vec<f32>>()?;
assert!(floats[0].is_nan());
+ // SAFETY: Just reading an extern constant.
assert_eq!(floats[1..], unsafe { bindings::TESTDATA_FLOAT }[1..]);
assert_eq!(parcel.read::<Option<Vec<f32>>>()?, None);
reply.write(&0f32)?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_FLOAT }[..])?;
reply.write(&(None as Option<Vec<f32>>))?;
}
@@ -203,10 +217,12 @@ fn on_transact(
assert_eq!(parcel.read::<f64>()?, 0f64);
let doubles = parcel.read::<Vec<f64>>()?;
assert!(doubles[0].is_nan());
+ // SAFETY: Just reading an extern constant.
assert_eq!(doubles[1..], unsafe { bindings::TESTDATA_DOUBLE }[1..]);
assert_eq!(parcel.read::<Option<Vec<f64>>>()?, None);
reply.write(&0f64)?;
+ // SAFETY: Just reading an extern constant.
reply.write(&unsafe { bindings::TESTDATA_DOUBLE }[..])?;
reply.write(&(None as Option<Vec<f64>>))?;
}
@@ -216,14 +232,17 @@ fn on_transact(
let s: Option<String> = parcel.read()?;
assert_eq!(s, None);
let s: Option<Vec<Option<String>>> = parcel.read()?;
+ // SAFETY: Just reading an extern constant.
for (s, expected) in s.unwrap().iter().zip(unsafe { bindings::TESTDATA_STRS }.iter()) {
let expected =
+ // SAFETY: Just reading an extern constant.
unsafe { expected.as_ref().and_then(|e| CStr::from_ptr(e).to_str().ok()) };
assert_eq!(s.as_deref(), expected);
}
let s: Option<Vec<Option<String>>> = parcel.read()?;
assert_eq!(s, None);
+ // SAFETY: Just reading an extern constant.
let strings: Vec<Option<String>> = unsafe {
bindings::TESTDATA_STRS
.iter()
@@ -258,8 +277,7 @@ fn on_transact(
assert!(ibinders[1].is_none());
assert!(parcel.read::<Option<Vec<Option<SpIBinder>>>>()?.is_none());
- let service =
- unsafe { SERVICE.as_ref().expect("Global binder service not initialized").clone() };
+ let service = SERVICE.get().expect("Global binder service not initialized").clone();
reply.write(&service)?;
reply.write(&(None as Option<&SpIBinder>))?;
reply.write(&[Some(&service), None][..])?;
diff --git a/libs/binder/servicedispatcher.cpp b/libs/binder/servicedispatcher.cpp
index 692cc95e3b..18b178b9b1 100644
--- a/libs/binder/servicedispatcher.cpp
+++ b/libs/binder/servicedispatcher.cpp
@@ -17,12 +17,11 @@
#include <sysexits.h>
#include <unistd.h>
+#include <filesystem>
#include <iostream>
-#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
#include <android/debug/BnAdbCallback.h>
#include <android/debug/IAdbManager.h>
#include <android/os/BnServiceManager.h>
@@ -31,6 +30,8 @@
#include <binder/ProcessState.h>
#include <binder/RpcServer.h>
+#include "file.h"
+
using android::BBinder;
using android::defaultServiceManager;
using android::OK;
@@ -39,14 +40,12 @@ using android::sp;
using android::status_t;
using android::statusToString;
using android::String16;
-using android::base::Basename;
using android::base::GetBoolProperty;
using android::base::InitLogging;
using android::base::LogdLogger;
using android::base::LogId;
using android::base::LogSeverity;
using android::base::StdioLogger;
-using android::base::StringPrintf;
using std::string_view_literals::operator""sv;
namespace {
@@ -55,26 +54,29 @@ const char* kLocalInetAddress = "127.0.0.1";
using ServiceRetriever = decltype(&android::IServiceManager::checkService);
using android::debug::IAdbManager;
-int Usage(const char* program) {
- auto basename = Basename(program);
- auto format = R"(dispatch calls to RPC service.
+int Usage(std::filesystem::path program) {
+ auto basename = program.filename();
+ // clang-format off
+ LOG(ERROR) << R"(dispatch calls to RPC service.
Usage:
- %s [-g] <service_name>
+ )" << basename << R"( [-g] [-i <ip_address>] <service_name>
<service_name>: the service to connect to.
- %s [-g] manager
+ )" << basename << R"( [-g] manager
Runs an RPC-friendly service that redirects calls to servicemanager.
-g: use getService() instead of checkService().
+ -i: use ip_address when setting up the server instead of '127.0.0.1'
If successful, writes port number and a new line character to stdout, and
blocks until killed.
Otherwise, writes error message to stderr and exits with non-zero code.
)";
- LOG(ERROR) << StringPrintf(format, basename.c_str(), basename.c_str());
+ // clang-format on
return EX_USAGE;
}
-int Dispatch(const char* name, const ServiceRetriever& serviceRetriever) {
+int Dispatch(const char* name, const ServiceRetriever& serviceRetriever,
+ const char* ip_address = kLocalInetAddress) {
auto sm = defaultServiceManager();
if (nullptr == sm) {
LOG(ERROR) << "No servicemanager";
@@ -91,7 +93,7 @@ int Dispatch(const char* name, const ServiceRetriever& serviceRetriever) {
return EX_SOFTWARE;
}
unsigned int port;
- if (status_t status = rpcServer->setupInetServer(kLocalInetAddress, 0, &port); status != OK) {
+ if (status_t status = rpcServer->setupInetServer(ip_address, 0, &port); status != OK) {
LOG(ERROR) << "setupInetServer failed: " << statusToString(status);
return EX_SOFTWARE;
}
@@ -188,7 +190,8 @@ private:
// Workaround for b/191059588.
// TODO(b/191059588): Once we can run RpcServer on single-threaded services,
// `servicedispatcher manager` should call Dispatch("manager") directly.
-int wrapServiceManager(const ServiceRetriever& serviceRetriever) {
+int wrapServiceManager(const ServiceRetriever& serviceRetriever,
+ const char* ip_address = kLocalInetAddress) {
auto sm = defaultServiceManager();
if (nullptr == sm) {
LOG(ERROR) << "No servicemanager";
@@ -212,7 +215,7 @@ int wrapServiceManager(const ServiceRetriever& serviceRetriever) {
auto rpcServer = RpcServer::make();
rpcServer->setRootObject(service);
unsigned int port;
- if (status_t status = rpcServer->setupInetServer(kLocalInetAddress, 0, &port); status != OK) {
+ if (status_t status = rpcServer->setupInetServer(ip_address, 0, &port); status != OK) {
LOG(ERROR) << "Unable to set up inet server: " << statusToString(status);
return EX_SOFTWARE;
}
@@ -251,7 +254,8 @@ public:
mLogdLogger(id, severity, tag, file, line, message);
if (severity >= LogSeverity::WARNING) {
std::cout << std::flush;
- std::cerr << Basename(getprogname()) << ": " << message << std::endl;
+ auto progname = std::filesystem::path(getprogname()).filename();
+ std::cerr << progname << ": " << message << std::endl;
}
}
@@ -272,10 +276,17 @@ int main(int argc, char* argv[]) {
int opt;
ServiceRetriever serviceRetriever = &android::IServiceManager::checkService;
- while (-1 != (opt = getopt(argc, argv, "g"))) {
+ char* ip_address = nullptr;
+ while (-1 != (opt = getopt(argc, argv, "gi:"))) {
switch (opt) {
case 'g': {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
serviceRetriever = &android::IServiceManager::getService;
+#pragma clang diagnostic pop
+ } break;
+ case 'i': {
+ ip_address = optarg;
} break;
default: {
return Usage(argv[0]);
@@ -291,7 +302,15 @@ int main(int argc, char* argv[]) {
auto name = argv[optind];
if (name == "manager"sv) {
- return wrapServiceManager(serviceRetriever);
+ if (ip_address) {
+ return wrapServiceManager(serviceRetriever, ip_address);
+ } else {
+ return wrapServiceManager(serviceRetriever);
+ }
+ }
+ if (ip_address) {
+ return Dispatch(name, serviceRetriever, ip_address);
+ } else {
+ return Dispatch(name, serviceRetriever);
}
- return Dispatch(name, serviceRetriever);
}
diff --git a/libs/binder/tests/Android.bp b/libs/binder/tests/Android.bp
index 0f0d64aea8..6800a8d36c 100644
--- a/libs/binder/tests/Android.bp
+++ b/libs/binder/tests/Android.bp
@@ -32,28 +32,8 @@ cc_defaults {
}
cc_test {
- name: "binderDriverInterfaceTest_IPC_32",
- defaults: ["binder_test_defaults"],
- srcs: ["binderDriverInterfaceTest.cpp"],
- header_libs: ["libbinder_headers"],
- compile_multilib: "32",
- multilib: {
- lib32: {
- suffix: "",
- },
- },
- cflags: ["-DBINDER_IPC_32BIT=1"],
- test_suites: ["vts"],
-}
-
-cc_test {
name: "binderDriverInterfaceTest",
defaults: ["binder_test_defaults"],
- product_variables: {
- binder32bit: {
- cflags: ["-DBINDER_IPC_32BIT=1"],
- },
- },
header_libs: ["libbinder_headers"],
srcs: ["binderDriverInterfaceTest.cpp"],
test_suites: [
@@ -62,30 +42,6 @@ cc_test {
],
}
-cc_test {
- name: "binderLibTest_IPC_32",
- defaults: ["binder_test_defaults"],
- srcs: ["binderLibTest.cpp"],
- shared_libs: [
- "libbase",
- "libbinder",
- "liblog",
- "libutils",
- ],
- static_libs: [
- "libgmock",
- ],
- compile_multilib: "32",
- multilib: {
- lib32: {
- suffix: "",
- },
- },
- cflags: ["-DBINDER_IPC_32BIT=1"],
- test_suites: ["vts"],
- require_root: true,
-}
-
// unit test only, which can run on host and doesn't use /dev/binder
cc_test {
name: "binderUnitTest",
@@ -111,13 +67,53 @@ cc_test {
}
cc_test {
- name: "binderLibTest",
- defaults: ["binder_test_defaults"],
- product_variables: {
- binder32bit: {
- cflags: ["-DBINDER_IPC_32BIT=1"],
+ name: "binderRecordReplayTest",
+ srcs: ["binderRecordReplayTest.cpp"],
+ cflags: [
+ "-DBINDER_WITH_KERNEL_IPC",
+ ],
+ shared_libs: [
+ "libbinder",
+ "libcutils",
+ "libutils",
+ "liblog",
+ ],
+ static_libs: [
+ "binderRecordReplayTestIface-cpp",
+ "binderReadParcelIface-cpp",
+ "libbinder_random_parcel_seeds",
+ "libbinder_random_parcel",
+ ],
+ test_suites: ["general-tests"],
+ require_root: true,
+}
+
+aidl_interface {
+ name: "binderRecordReplayTestIface",
+ unstable: true,
+ srcs: [
+ "IBinderRecordReplayTest.aidl",
+ ],
+ imports: ["binderReadParcelIface"],
+ backend: {
+ java: {
+ enabled: true,
+ platform_apis: true,
+ },
+
+ // TODO: switch from FileDescriptor to ParcelFileDescriptor
+ ndk: {
+ enabled: false,
+ },
+ rust: {
+ enabled: false,
},
},
+}
+
+cc_test {
+ name: "binderLibTest",
+ defaults: ["binder_test_defaults"],
srcs: ["binderLibTest.cpp"],
shared_libs: [
@@ -138,6 +134,7 @@ cc_test {
aidl_interface {
name: "binderRpcTestIface",
+ vendor_available: true,
host_supported: true,
unstable: true,
srcs: [
@@ -149,6 +146,10 @@ aidl_interface {
"IBinderRpcTest.aidl",
"ParcelableCertificateData.aidl",
],
+ flags: [
+ "-Werror",
+ "-Wno-mixed-oneway",
+ ],
backend: {
java: {
enabled: false,
@@ -159,6 +160,7 @@ aidl_interface {
cc_library_static {
name: "libbinder_tls_test_utils",
host_supported: true,
+ vendor_available: true,
target: {
darwin: {
enabled: false,
@@ -186,6 +188,32 @@ cc_library_static {
],
}
+cc_library_static {
+ name: "libbinder_test_utils",
+ host_supported: true,
+ vendor_available: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ defaults: [
+ "binder_test_defaults",
+ ],
+ header_libs: [
+ "libbinder_headers_base",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ srcs: [
+ "FileUtils.cpp",
+ ],
+ visibility: [
+ ":__subpackages__",
+ ],
+}
+
cc_defaults {
name: "binderRpcTest_common_defaults",
host_supported: true,
@@ -199,6 +227,7 @@ cc_defaults {
],
static_libs: [
+ "libbinder_test_utils",
"libbinder_tls_static",
"libbinder_tls_test_utils",
"binderRpcTestIface-cpp",
@@ -236,6 +265,19 @@ cc_defaults {
"binderRpcUniversalTests.cpp",
],
+ // This test uses a lot of resources and takes a long time. Due to
+ // design of several tests, it is also very sensitive to resource
+ // contention on the device. b/276820894
+ test_options: {
+ unit_test: false,
+ test_runner_options: [
+ {
+ name: "native-test-timeout",
+ value: "10m",
+ },
+ ],
+ },
+
test_suites: ["general-tests"],
require_root: true,
@@ -395,6 +437,8 @@ cc_test {
// Add the Trusty mock library as a fake dependency so it gets built
required: [
"libbinder_on_trusty_mock",
+ "libbinder_ndk_on_trusty_mock",
+ "libbinder_rs_on_trusty_mock",
"binderRpcTestService_on_trusty_mock",
"binderRpcTest_on_trusty_mock",
],
@@ -441,6 +485,37 @@ cc_test {
}
cc_test {
+ name: "binderRpcToTrustyTest",
+ vendor: true,
+ host_supported: false,
+ defaults: [
+ "binderRpcTest_common_defaults",
+ "binderRpcTest_static_defaults",
+ ],
+
+ srcs: [
+ "binderRpcTest.cpp",
+ "binderRpcTestCommon.cpp",
+ "binderRpcUniversalTests.cpp",
+ ],
+
+ cflags: [
+ "-DBINDER_RPC_TO_TRUSTY_TEST",
+ ],
+
+ static_libs: [
+ // We want to link libbinder statically so we can push the binary
+ // to the device for testing independently of the library
+ "libbinder_rpc_no_kernel",
+ "libbinder_trusty",
+ "libtrusty",
+ ],
+
+ test_suites: ["device-tests"],
+ require_root: true,
+}
+
+cc_test {
name: "RpcTlsUtilsTest",
host_supported: true,
target: {
@@ -676,6 +751,7 @@ cc_benchmark {
"liblog",
"libutils",
],
+ test_suites: ["general-tests"],
}
cc_test_host {
@@ -776,5 +852,18 @@ cc_defaults {
],
// Adds bugs to hotlist "AIDL fuzzers bugs" on buganizer
hotlists: ["4637097"],
+ use_for_presubmit: true,
+ },
+}
+
+cc_defaults {
+ name: "fuzzer_disable_leaks",
+ fuzz_config: {
+ asan_options: [
+ "detect_leaks=0",
+ ],
+ hwasan_options: [
+ "detect_leaks=0",
+ ],
},
}
diff --git a/libs/binder/tests/FileUtils.cpp b/libs/binder/tests/FileUtils.cpp
new file mode 100644
index 0000000000..61509fe18e
--- /dev/null
+++ b/libs/binder/tests/FileUtils.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "FileUtils.h"
+
+#ifdef BINDER_NO_LIBBASE
+
+#include <sys/stat.h>
+#include <filesystem>
+
+#if defined(__APPLE__)
+#include <mach-o/dyld.h>
+#endif
+#if defined(_WIN32)
+#include <direct.h>
+#include <windows.h>
+#endif
+
+namespace android::binder {
+
+bool ReadFdToString(borrowed_fd fd, std::string* content) {
+ content->clear();
+
+ // Although original we had small files in mind, this code gets used for
+ // very large files too, where the std::string growth heuristics might not
+ // be suitable. https://code.google.com/p/android/issues/detail?id=258500.
+ struct stat sb;
+ if (fstat(fd.get(), &sb) != -1 && sb.st_size > 0) {
+ content->reserve(sb.st_size);
+ }
+
+ char buf[4096] __attribute__((__uninitialized__));
+ ssize_t n;
+ while ((n = TEMP_FAILURE_RETRY(read(fd.get(), &buf[0], sizeof(buf)))) > 0) {
+ content->append(buf, n);
+ }
+ return (n == 0) ? true : false;
+}
+
+bool WriteStringToFd(std::string_view content, borrowed_fd fd) {
+ const char* p = content.data();
+ size_t left = content.size();
+ while (left > 0) {
+ ssize_t n = TEMP_FAILURE_RETRY(write(fd.get(), p, left));
+ if (n == -1) {
+ return false;
+ }
+ p += n;
+ left -= n;
+ }
+ return true;
+}
+
+static std::filesystem::path GetExecutablePath2() {
+#if defined(__linux__)
+ return std::filesystem::read_symlink("/proc/self/exe");
+#elif defined(__APPLE__)
+ char path[PATH_MAX + 1];
+ uint32_t path_len = sizeof(path);
+ int rc = _NSGetExecutablePath(path, &path_len);
+ if (rc < 0) {
+ std::unique_ptr<char> path_buf(new char[path_len]);
+ _NSGetExecutablePath(path_buf.get(), &path_len);
+ return path_buf.get();
+ }
+ return path;
+#elif defined(_WIN32)
+ char path[PATH_MAX + 1];
+ DWORD result = GetModuleFileName(NULL, path, sizeof(path) - 1);
+ if (result == 0 || result == sizeof(path) - 1) return "";
+ path[PATH_MAX - 1] = 0;
+ return path;
+#elif defined(__EMSCRIPTEN__)
+ abort();
+#else
+#error unknown OS
+#endif
+}
+
+std::string GetExecutableDirectory() {
+ return GetExecutablePath2().parent_path();
+}
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/tests/FileUtils.h b/libs/binder/tests/FileUtils.h
new file mode 100644
index 0000000000..2cbe5e7f25
--- /dev/null
+++ b/libs/binder/tests/FileUtils.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 "../file.h"
+
+#ifndef BINDER_NO_LIBBASE
+
+namespace android::binder {
+using android::base::GetExecutableDirectory;
+using android::base::ReadFdToString;
+using android::base::WriteStringToFd;
+} // namespace android::binder
+
+#else // BINDER_NO_LIBBASE
+
+#include <binder/unique_fd.h>
+
+#include <string_view>
+
+#if !defined(_WIN32) && !defined(O_BINARY)
+/** Windows needs O_BINARY, but Unix never mangles line endings. */
+#define O_BINARY 0
+#endif
+
+namespace android::binder {
+
+bool ReadFdToString(borrowed_fd fd, std::string* content);
+bool WriteStringToFd(std::string_view content, borrowed_fd fd);
+
+std::string GetExecutableDirectory();
+
+} // namespace android::binder
+
+#endif // BINDER_NO_LIBBASE
diff --git a/libs/binder/tests/IBinderRecordReplayTest.aidl b/libs/binder/tests/IBinderRecordReplayTest.aidl
new file mode 100644
index 0000000000..29267e91c6
--- /dev/null
+++ b/libs/binder/tests/IBinderRecordReplayTest.aidl
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+import parcelables.SingleDataParcelable;
+
+interface IBinderRecordReplayTest {
+ void setByte(byte input);
+ byte getByte();
+
+ void setChar(char input);
+ char getChar();
+
+ void setBoolean(boolean input);
+ boolean getBoolean();
+
+ void setInt(int input);
+ int getInt();
+
+ void setFloat(float input);
+ float getFloat();
+
+ void setLong(long input);
+ long getLong();
+
+ void setDouble(double input);
+ double getDouble();
+
+ void setString(String input);
+ String getString();
+
+ void setSingleDataParcelable(in SingleDataParcelable p);
+ SingleDataParcelable getSingleDataParcelable();
+
+ void setByteArray(in byte[] input);
+ byte[] getByteArray();
+
+ void setCharArray(in char[] input);
+ char[] getCharArray();
+
+ void setBooleanArray(in boolean[] input);
+ boolean[] getBooleanArray();
+
+ void setIntArray(in int[] input);
+ int[] getIntArray();
+
+ void setFloatArray(in float[] input);
+ float[] getFloatArray();
+
+ void setLongArray(in long[] input);
+ long[] getLongArray();
+
+ void setDoubleArray(in double[] input);
+ double[] getDoubleArray();
+
+ void setStringArray(in String[] input);
+ String[] getStringArray();
+
+ void setSingleDataParcelableArray(in SingleDataParcelable[] input);
+ SingleDataParcelable[] getSingleDataParcelableArray();
+
+ void setBinder(in IBinder binder);
+ IBinder getBinder();
+
+ void setFileDescriptor(in FileDescriptor fd);
+ FileDescriptor getFileDescriptor();
+}
diff --git a/libs/binder/tests/IBinderRpcBenchmark.aidl b/libs/binder/tests/IBinderRpcBenchmark.aidl
index 2baf68097a..100877847d 100644
--- a/libs/binder/tests/IBinderRpcBenchmark.aidl
+++ b/libs/binder/tests/IBinderRpcBenchmark.aidl
@@ -18,4 +18,7 @@ interface IBinderRpcBenchmark {
@utf8InCpp String repeatString(@utf8InCpp String str);
IBinder repeatBinder(IBinder binder);
byte[] repeatBytes(in byte[] bytes);
+
+ IBinder gimmeBinder();
+ void waitGimmesDestroyed();
}
diff --git a/libs/binder/tests/IBinderRpcTest.aidl b/libs/binder/tests/IBinderRpcTest.aidl
index a3ed571462..116476765a 100644
--- a/libs/binder/tests/IBinderRpcTest.aidl
+++ b/libs/binder/tests/IBinderRpcTest.aidl
@@ -80,4 +80,8 @@ interface IBinderRpcTest {
// get queued.
oneway void blockingSendFdOneway(in ParcelFileDescriptor fd);
ParcelFileDescriptor blockingRecvFd();
+
+ // Same as blockingSendFdOneway, but with integers.
+ oneway void blockingSendIntOneway(int n);
+ int blockingRecvInt();
}
diff --git a/libs/binder/tests/binderAbiHelper.h b/libs/binder/tests/binderAbiHelper.h
deleted file mode 100644
index 369b55dc22..0000000000
--- a/libs/binder/tests/binderAbiHelper.h
+++ /dev/null
@@ -1,52 +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.
- */
-
-#pragma once
-
-#include <stdlib.h>
-#include <iostream>
-
-#ifdef BINDER_IPC_32BIT
-static constexpr bool kBuild32Abi = true;
-#else
-static constexpr bool kBuild32Abi = false;
-#endif
-
-// TODO: remove when CONFIG_ANDROID_BINDER_IPC_32BIT is no longer supported
-static inline bool ReadKernelConfigIs32BitAbi() {
- // failure case implies we run with standard ABI
- return 0 == system("zcat /proc/config.gz | grep -E \"^CONFIG_ANDROID_BINDER_IPC_32BIT=y$\"");
-}
-
-static inline void ExitIfWrongAbi() {
- bool runtime32Abi = ReadKernelConfigIs32BitAbi();
-
- if (kBuild32Abi != runtime32Abi) {
- std::cout << "[==========] Running 1 test from 1 test suite." << std::endl;
- std::cout << "[----------] Global test environment set-up." << std::endl;
- std::cout << "[----------] 1 tests from BinderLibTest" << std::endl;
- std::cout << "[ RUN ] BinderTest.AbortForWrongAbi" << std::endl;
- std::cout << "[ INFO ] test build abi 32: " << kBuild32Abi << " runtime abi 32: " << runtime32Abi << " so, skipping tests " << std::endl;
- std::cout << "[ OK ] BinderTest.AbortForWrongAbi (0 ms) " << std::endl;
- std::cout << "[----------] 1 tests from BinderTest (0 ms total)" << std::endl;
- std::cout << "" << std::endl;
- std::cout << "[----------] Global test environment tear-down" << std::endl;
- std::cout << "[==========] 1 test from 1 test suite ran. (0 ms total)" << std::endl;
- std::cout << "[ PASSED ] 1 tests." << std::endl;
- exit(0);
- }
-}
-
diff --git a/libs/binder/tests/binderAllocationLimits.cpp b/libs/binder/tests/binderAllocationLimits.cpp
index bc40864020..7e0b59463a 100644
--- a/libs/binder/tests/binderAllocationLimits.cpp
+++ b/libs/binder/tests/binderAllocationLimits.cpp
@@ -16,6 +16,7 @@
#include <android-base/logging.h>
#include <binder/Binder.h>
+#include <binder/Functional.h>
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <binder/RpcServer.h>
@@ -28,6 +29,8 @@
#include <functional>
#include <vector>
+using namespace android::binder::impl;
+
static android::String8 gEmpty(""); // make sure first allocation from optimization runs
struct DestructionAction {
@@ -172,6 +175,18 @@ TEST(BinderAllocation, PingTransaction) {
a_binder->pingBinder();
}
+TEST(BinderAllocation, MakeScopeGuard) {
+ const auto m = ScopeDisallowMalloc();
+ {
+ auto guard1 = make_scope_guard([] {});
+ guard1.release();
+
+ auto guard2 = make_scope_guard([&guard1, ptr = imaginary_use] {
+ if (ptr == nullptr) guard1.release();
+ });
+ }
+}
+
TEST(BinderAllocation, InterfaceDescriptorTransaction) {
sp<IBinder> a_binder = GetRemoteBinder();
@@ -216,16 +231,16 @@ TEST(RpcBinderAllocation, SetupRpcServer) {
auto server = RpcServer::make();
server->setRootObject(sp<BBinder>::make());
- CHECK_EQ(OK, server->setupUnixDomainServer(addr.c_str()));
+ ASSERT_EQ(OK, server->setupUnixDomainServer(addr.c_str()));
std::thread([server]() { server->join(); }).detach();
- status_t status;
auto session = RpcSession::make();
- status = session->setupUnixDomainClient(addr.c_str());
- CHECK_EQ(status, OK) << "Could not connect: " << addr << ": " << statusToString(status).c_str();
+ status_t status = session->setupUnixDomainClient(addr.c_str());
+ ASSERT_EQ(status, OK) << "Could not connect: " << addr << ": " << statusToString(status).c_str();
auto remoteBinder = session->getRootObject();
+ ASSERT_NE(remoteBinder, nullptr);
size_t mallocs = 0, totalBytes = 0;
{
@@ -233,7 +248,7 @@ TEST(RpcBinderAllocation, SetupRpcServer) {
mallocs++;
totalBytes += bytes;
});
- CHECK_EQ(OK, remoteBinder->pingBinder());
+ ASSERT_EQ(OK, remoteBinder->pingBinder());
}
EXPECT_EQ(mallocs, 1);
EXPECT_EQ(totalBytes, 40);
diff --git a/libs/binder/tests/binderClearBufTest.cpp b/libs/binder/tests/binderClearBufTest.cpp
index 307151c7de..e43ee5fcf5 100644
--- a/libs/binder/tests/binderClearBufTest.cpp
+++ b/libs/binder/tests/binderClearBufTest.cpp
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-#include <android-base/hex.h>
#include <android-base/logging.h>
#include <binder/Binder.h>
#include <binder/IBinder.h>
@@ -24,6 +23,8 @@
#include <binder/Stability.h>
#include <gtest/gtest.h>
+#include "../Utils.h"
+
#include <sys/prctl.h>
#include <thread>
@@ -68,13 +69,16 @@ class FooBar : public BBinder {
lastReply = reply.data();
lastReplySize = reply.dataSize();
}
- *outBuffer = android::base::HexString(lastReply, lastReplySize);
+ *outBuffer = android::HexString(lastReply, lastReplySize);
return result;
}
};
TEST(BinderClearBuf, ClearKernelBuffer) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> binder = defaultServiceManager()->getService(kServerName);
+#pragma clang diagnostic pop
ASSERT_NE(nullptr, binder);
std::string replyBuffer;
diff --git a/libs/binder/tests/binderDriverInterfaceTest.cpp b/libs/binder/tests/binderDriverInterfaceTest.cpp
index 8cc3054f80..cf23a4658c 100644
--- a/libs/binder/tests/binderDriverInterfaceTest.cpp
+++ b/libs/binder/tests/binderDriverInterfaceTest.cpp
@@ -25,8 +25,6 @@
#include <sys/mman.h>
#include <poll.h>
-#include "binderAbiHelper.h"
-
#define BINDER_DEV_NAME "/dev/binder"
testing::Environment* binder_env;
@@ -362,8 +360,7 @@ TEST_F(BinderDriverInterfaceTest, RequestDeathNotification) {
binderTestReadEmpty();
}
-int main(int argc, char **argv) {
- ExitIfWrongAbi();
+int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
binder_env = AddGlobalTestEnvironment(new BinderDriverInterfaceTestEnv());
diff --git a/libs/binder/tests/binderHostDeviceTest.cpp b/libs/binder/tests/binderHostDeviceTest.cpp
index 464da60dde..0ae536c6bc 100644
--- a/libs/binder/tests/binderHostDeviceTest.cpp
+++ b/libs/binder/tests/binderHostDeviceTest.cpp
@@ -66,7 +66,7 @@ MATCHER_P(StatusEq, expected, (negation ? "not " : "") + statusToString(expected
void initHostRpcServiceManagerOnce() {
static std::once_flag gSmOnce;
std::call_once(gSmOnce, [] {
- setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingThreads = 1}));
+ setDefaultServiceManager(createRpcDelegateServiceManager({.maxOutgoingConnections = 1}));
});
}
@@ -75,7 +75,7 @@ class HostDeviceTest : public ::testing::Test {
public:
void SetUp() override {
auto debuggableResult = execute(Split("adb shell getprop ro.debuggable", " "), nullptr);
- ASSERT_THAT(debuggableResult, Ok());
+ ASSERT_TRUE(debuggableResult.has_value());
ASSERT_EQ(0, debuggableResult->exitCode) << *debuggableResult;
auto debuggableBool = ParseBool(Trim(debuggableResult->stdoutStr));
ASSERT_NE(ParseBoolResult::kError, debuggableBool) << Trim(debuggableResult->stdoutStr);
@@ -84,7 +84,7 @@ public:
}
auto lsResult = execute(Split("adb shell which servicedispatcher", " "), nullptr);
- ASSERT_THAT(lsResult, Ok());
+ ASSERT_TRUE(lsResult.has_value());
if (lsResult->exitCode != 0) {
GTEST_SKIP() << "b/182914638: until feature is fully enabled, skip test on devices "
"without servicedispatcher";
@@ -95,7 +95,7 @@ public:
auto service = execute({"adb", "shell", kServiceBinary, kServiceName, kDescriptor},
&CommandResult::stdoutEndsWithNewLine);
- ASSERT_THAT(service, Ok());
+ ASSERT_TRUE(service.has_value());
ASSERT_EQ(std::nullopt, service->exitCode) << *service;
mService = std::move(*service);
}
@@ -135,7 +135,10 @@ TEST_F(HostDeviceTest, CheckService) {
TEST_F(HostDeviceTest, GetService) {
auto sm = defaultServiceManager();
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
auto rpcBinder = sm->getService(String16(kServiceName));
+#pragma clang diagnostic pop
ASSERT_NE(nullptr, rpcBinder);
EXPECT_THAT(rpcBinder->pingBinder(), StatusEq(OK));
diff --git a/libs/binder/tests/binderLibTest.cpp b/libs/binder/tests/binderLibTest.cpp
index 09bff74231..1f61f1852a 100644
--- a/libs/binder/tests/binderLibTest.cpp
+++ b/libs/binder/tests/binderLibTest.cpp
@@ -29,17 +29,17 @@
#include <android-base/properties.h>
#include <android-base/result-gmock.h>
-#include <android-base/result.h>
-#include <android-base/scopeguard.h>
#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
#include <binder/Binder.h>
#include <binder/BpBinder.h>
+#include <binder/Functional.h>
#include <binder/IBinder.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/RpcServer.h>
#include <binder/RpcSession.h>
+#include <binder/unique_fd.h>
+#include <utils/Flattenable.h>
#include <linux/sched.h>
#include <sys/epoll.h>
@@ -48,15 +48,16 @@
#include <sys/un.h>
#include "../binder_module.h"
-#include "binderAbiHelper.h"
#define ARRAY_SIZE(array) (sizeof array / sizeof array[0])
using namespace android;
+using namespace android::binder::impl;
using namespace std::string_literals;
using namespace std::chrono_literals;
using android::base::testing::HasValue;
using android::base::testing::Ok;
+using android::binder::unique_fd;
using testing::ExplainMatchResult;
using testing::Matcher;
using testing::Not;
@@ -69,7 +70,7 @@ MATCHER_P(StatusEq, expected, (negation ? "not " : "") + statusToString(expected
}
static ::testing::AssertionResult IsPageAligned(void *buf) {
- if (((unsigned long)buf & ((unsigned long)PAGE_SIZE - 1)) == 0)
+ if (((unsigned long)buf & ((unsigned long)getpagesize() - 1)) == 0)
return ::testing::AssertionSuccess();
else
return ::testing::AssertionFailure() << buf << " is not page aligned";
@@ -83,7 +84,7 @@ static char binderserverarg[] = "--binderserver";
static constexpr int kSchedPolicy = SCHED_RR;
static constexpr int kSchedPriority = 7;
static constexpr int kSchedPriorityMore = 8;
-static constexpr int kKernelThreads = 15;
+static constexpr int kKernelThreads = 17; // anything different than the default
static String16 binderLibTestServiceName = String16("test.binderLib");
@@ -114,6 +115,7 @@ enum BinderLibTestTranscationCode {
BINDER_LIB_TEST_GET_SCHEDULING_POLICY,
BINDER_LIB_TEST_NOP_TRANSACTION_WAIT,
BINDER_LIB_TEST_GETPID,
+ BINDER_LIB_TEST_GETUID,
BINDER_LIB_TEST_ECHO_VECTOR,
BINDER_LIB_TEST_GET_NON_BLOCKING_FD,
BINDER_LIB_TEST_REJECT_OBJECTS,
@@ -214,7 +216,10 @@ class BinderLibTestEnv : public ::testing::Environment {
sp<IServiceManager> sm = defaultServiceManager();
//printf("%s: pid %d, get service\n", __func__, m_pid);
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
m_server = sm->getService(binderLibTestServiceName);
+#pragma clang diagnostic pop
ASSERT_TRUE(m_server != nullptr);
//printf("%s: pid %d, get service done\n", __func__, m_pid);
}
@@ -501,10 +506,11 @@ TEST_F(BinderLibTest, Freeze) {
// Pass test on devices where BINDER_FREEZE ioctl is not supported
int ret = IPCThreadState::self()->freeze(pid, false, 0);
- if (ret != 0) {
+ if (ret == -EINVAL) {
GTEST_SKIP();
return;
}
+ EXPECT_EQ(NO_ERROR, ret);
EXPECT_EQ(-EAGAIN, IPCThreadState::self()->freeze(pid, true, 0));
@@ -844,7 +850,7 @@ TEST_F(BinderLibTest, PassParcelFileDescriptor) {
writebuf[i] = i;
}
- android::base::unique_fd read_end, write_end;
+ unique_fd read_end, write_end;
{
int pipefd[2];
ASSERT_EQ(0, pipe2(pipefd, O_NONBLOCK));
@@ -1108,6 +1114,7 @@ TEST_F(BinderLibTest, WorkSourcePropagatedForAllFollowingBinderCalls)
status_t ret;
data.writeInterfaceToken(binderLibTestServiceName);
ret = m_server->transact(BINDER_LIB_TEST_GET_WORK_SOURCE_TRANSACTION, data, &reply);
+ EXPECT_EQ(NO_ERROR, ret);
Parcel data2, reply2;
status_t ret2;
@@ -1173,7 +1180,7 @@ TEST_F(BinderLibTest, FileDescriptorRemainsNonBlocking) {
Parcel reply;
EXPECT_THAT(server->transact(BINDER_LIB_TEST_GET_NON_BLOCKING_FD, {} /*data*/, &reply),
StatusEq(NO_ERROR));
- base::unique_fd fd;
+ unique_fd fd;
EXPECT_THAT(reply.readUniqueFileDescriptor(&fd), StatusEq(OK));
const int result = fcntl(fd.get(), F_GETFL);
@@ -1322,7 +1329,7 @@ TEST_F(BinderLibTest, TooManyFdsFlattenable) {
ASSERT_EQ(0, ret);
// Restore the original file limits when the test finishes
- base::ScopeGuard guardUnguard([&]() { setrlimit(RLIMIT_NOFILE, &origNofile); });
+ auto guardUnguard = make_scope_guard([&]() { setrlimit(RLIMIT_NOFILE, &origNofile); });
rlimit testNofile = {1024, 1024};
ret = setrlimit(RLIMIT_NOFILE, &testNofile);
@@ -1358,17 +1365,20 @@ TEST_F(BinderLibTest, ThreadPoolAvailableThreads) {
EXPECT_THAT(server->transact(BINDER_LIB_TEST_GET_MAX_THREAD_COUNT, data, &reply),
StatusEq(NO_ERROR));
int32_t replyi = reply.readInt32();
- // Expect 16 threads: kKernelThreads = 15 + Pool thread == 16
- EXPECT_TRUE(replyi == kKernelThreads || replyi == kKernelThreads + 1);
+ // see getThreadPoolMaxTotalThreadCount for why there is a race
+ EXPECT_TRUE(replyi == kKernelThreads + 1 || replyi == kKernelThreads + 2) << replyi;
+
EXPECT_THAT(server->transact(BINDER_LIB_TEST_PROCESS_LOCK, data, &reply), NO_ERROR);
/*
- * This will use all threads in the pool expect the main pool thread.
- * The service should run fine without locking, and the thread count should
- * not exceed 16 (15 Max + pool thread).
+ * This will use all threads in the pool but one. There are actually kKernelThreads+2
+ * available in the other process (startThreadPool, joinThreadPool, + the kernel-
+ * started threads from setThreadPoolMaxThreadCount
+ *
+ * Adding one more will cause it to deadlock.
*/
std::vector<std::thread> ts;
- for (size_t i = 0; i < kKernelThreads; i++) {
+ for (size_t i = 0; i < kKernelThreads + 1; i++) {
ts.push_back(std::thread([&] {
Parcel local_reply;
EXPECT_THAT(server->transact(BINDER_LIB_TEST_LOCK_UNLOCK, data, &local_reply),
@@ -1376,8 +1386,13 @@ TEST_F(BinderLibTest, ThreadPoolAvailableThreads) {
}));
}
- data.writeInt32(100);
- // Give a chance for all threads to be used
+ // make sure all of the above calls will be queued in parallel. Otherwise, most of
+ // the time, the below call will pre-empt them (presumably because we have the
+ // scheduler timeslice already + scheduler hint).
+ sleep(1);
+
+ data.writeInt32(1000);
+ // Give a chance for all threads to be used (kKernelThreads + 1 thread in use)
EXPECT_THAT(server->transact(BINDER_LIB_TEST_UNLOCK_AFTER_MS, data, &reply), NO_ERROR);
for (auto &t : ts) {
@@ -1387,7 +1402,7 @@ TEST_F(BinderLibTest, ThreadPoolAvailableThreads) {
EXPECT_THAT(server->transact(BINDER_LIB_TEST_GET_MAX_THREAD_COUNT, data, &reply),
StatusEq(NO_ERROR));
replyi = reply.readInt32();
- EXPECT_EQ(replyi, kKernelThreads + 1);
+ EXPECT_EQ(replyi, kKernelThreads + 2);
}
TEST_F(BinderLibTest, ThreadPoolStarted) {
@@ -1434,6 +1449,116 @@ TEST_F(BinderLibTest, HangingServices) {
EXPECT_GE(epochMsAfter, epochMsBefore + delay);
}
+TEST_F(BinderLibTest, BinderProxyCount) {
+ Parcel data, reply;
+ sp<IBinder> server = addServer();
+ ASSERT_NE(server, nullptr);
+
+ uint32_t initialCount = BpBinder::getBinderProxyCount();
+ size_t iterations = 100;
+ {
+ uint32_t count = initialCount;
+ std::vector<sp<IBinder> > proxies;
+ sp<IBinder> proxy;
+ // Create binder proxies and verify the count.
+ for (size_t i = 0; i < iterations; i++) {
+ ASSERT_THAT(server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION, data, &reply),
+ StatusEq(NO_ERROR));
+ proxies.push_back(reply.readStrongBinder());
+ EXPECT_EQ(BpBinder::getBinderProxyCount(), ++count);
+ }
+ // Remove every other one and verify the count.
+ auto it = proxies.begin();
+ for (size_t i = 0; it != proxies.end(); i++) {
+ if (i % 2 == 0) {
+ it = proxies.erase(it);
+ EXPECT_EQ(BpBinder::getBinderProxyCount(), --count);
+ }
+ }
+ }
+ EXPECT_EQ(BpBinder::getBinderProxyCount(), initialCount);
+}
+
+static constexpr int kBpCountHighWatermark = 20;
+static constexpr int kBpCountLowWatermark = 10;
+static constexpr int kBpCountWarningWatermark = 15;
+static constexpr int kInvalidUid = -1;
+
+TEST_F(BinderLibTest, BinderProxyCountCallback) {
+ Parcel data, reply;
+ sp<IBinder> server = addServer();
+ ASSERT_NE(server, nullptr);
+
+ BpBinder::enableCountByUid();
+ EXPECT_THAT(m_server->transact(BINDER_LIB_TEST_GETUID, data, &reply), StatusEq(NO_ERROR));
+ int32_t uid = reply.readInt32();
+ ASSERT_NE(uid, kInvalidUid);
+
+ uint32_t initialCount = BpBinder::getBinderProxyCount();
+ {
+ uint32_t count = initialCount;
+ BpBinder::setBinderProxyCountWatermarks(kBpCountHighWatermark,
+ kBpCountLowWatermark,
+ kBpCountWarningWatermark);
+ int limitCallbackUid = kInvalidUid;
+ int warningCallbackUid = kInvalidUid;
+ BpBinder::setBinderProxyCountEventCallback([&](int uid) { limitCallbackUid = uid; },
+ [&](int uid) { warningCallbackUid = uid; });
+
+ std::vector<sp<IBinder> > proxies;
+ auto createProxyOnce = [&](int expectedWarningCallbackUid, int expectedLimitCallbackUid) {
+ warningCallbackUid = limitCallbackUid = kInvalidUid;
+ ASSERT_THAT(server->transact(BINDER_LIB_TEST_CREATE_BINDER_TRANSACTION, data, &reply),
+ StatusEq(NO_ERROR));
+ proxies.push_back(reply.readStrongBinder());
+ EXPECT_EQ(BpBinder::getBinderProxyCount(), ++count);
+ EXPECT_EQ(warningCallbackUid, expectedWarningCallbackUid);
+ EXPECT_EQ(limitCallbackUid, expectedLimitCallbackUid);
+ };
+ auto removeProxyOnce = [&](int expectedWarningCallbackUid, int expectedLimitCallbackUid) {
+ warningCallbackUid = limitCallbackUid = kInvalidUid;
+ proxies.pop_back();
+ EXPECT_EQ(BpBinder::getBinderProxyCount(), --count);
+ EXPECT_EQ(warningCallbackUid, expectedWarningCallbackUid);
+ EXPECT_EQ(limitCallbackUid, expectedLimitCallbackUid);
+ };
+
+ // Test the increment/decrement of the binder proxies.
+ for (int i = 1; i <= kBpCountWarningWatermark; i++) {
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ createProxyOnce(uid, kInvalidUid); // Warning callback should have been triggered.
+ for (int i = kBpCountWarningWatermark + 2; i <= kBpCountHighWatermark; i++) {
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ createProxyOnce(kInvalidUid, uid); // Limit callback should have been triggered.
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ for (int i = kBpCountHighWatermark + 2; i >= kBpCountHighWatermark; i--) {
+ removeProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ createProxyOnce(kInvalidUid, kInvalidUid);
+
+ // Go down below the low watermark.
+ for (int i = kBpCountHighWatermark; i >= kBpCountLowWatermark; i--) {
+ removeProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ for (int i = kBpCountLowWatermark; i <= kBpCountWarningWatermark; i++) {
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ createProxyOnce(uid, kInvalidUid); // Warning callback should have been triggered.
+ for (int i = kBpCountWarningWatermark + 2; i <= kBpCountHighWatermark; i++) {
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ createProxyOnce(kInvalidUid, uid); // Limit callback should have been triggered.
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ for (int i = kBpCountHighWatermark + 2; i >= kBpCountHighWatermark; i--) {
+ removeProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ createProxyOnce(kInvalidUid, kInvalidUid);
+ }
+ EXPECT_EQ(BpBinder::getBinderProxyCount(), initialCount);
+}
+
class BinderLibRpcTestBase : public BinderLibTest {
public:
void SetUp() override {
@@ -1444,7 +1569,7 @@ public:
BinderLibTest::SetUp();
}
- std::tuple<android::base::unique_fd, unsigned int> CreateSocket() {
+ std::tuple<unique_fd, unsigned int> CreateSocket() {
auto rpcServer = RpcServer::make();
EXPECT_NE(nullptr, rpcServer);
if (rpcServer == nullptr) return {};
@@ -1511,7 +1636,7 @@ public:
TEST_P(BinderLibRpcTestP, SetRpcClientDebugNoFd) {
auto binder = GetService();
ASSERT_TRUE(binder != nullptr);
- EXPECT_THAT(binder->setRpcClientDebug(android::base::unique_fd(), sp<BBinder>::make()),
+ EXPECT_THAT(binder->setRpcClientDebug(unique_fd(), sp<BBinder>::make()),
Debuggable(StatusEq(BAD_VALUE)));
}
@@ -1558,9 +1683,8 @@ public:
}
switch (code) {
case BINDER_LIB_TEST_REGISTER_SERVER: {
- int32_t id;
sp<IBinder> binder;
- id = data.readInt32();
+ /*id =*/data.readInt32();
binder = data.readStrongBinder();
if (binder == nullptr) {
return BAD_VALUE;
@@ -1638,6 +1762,9 @@ public:
case BINDER_LIB_TEST_GETPID:
reply->writeInt32(getpid());
return NO_ERROR;
+ case BINDER_LIB_TEST_GETUID:
+ reply->writeInt32(getuid());
+ return NO_ERROR;
case BINDER_LIB_TEST_NOP_TRANSACTION_WAIT:
usleep(5000);
[[fallthrough]];
@@ -1782,7 +1909,7 @@ public:
int ret;
int32_t size;
const void *buf;
- android::base::unique_fd fd;
+ unique_fd fd;
ret = data.readUniqueParcelFileDescriptor(&fd);
if (ret != NO_ERROR) {
@@ -1847,7 +1974,7 @@ public:
ALOGE("Could not make socket non-blocking: %s", strerror(errno));
return UNKNOWN_ERROR;
}
- base::unique_fd out(sockets[0]);
+ unique_fd out(sockets[0]);
status_t writeResult = reply->writeUniqueFileDescriptor(out);
if (writeResult != NO_ERROR) {
ALOGE("Could not write unique_fd");
@@ -1956,7 +2083,10 @@ int run_server(int index, int readypipefd, bool usePoll)
if (index == 0) {
ret = sm->addService(binderLibTestServiceName, testService);
} else {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> server = sm->getService(binderLibTestServiceName);
+#pragma clang diagnostic pop
Parcel data, reply;
data.writeInt32(index);
data.writeStrongBinder(testService);
@@ -2022,9 +2152,7 @@ int run_server(int index, int readypipefd, bool usePoll)
return 1; /* joinThreadPool should not return */
}
-int main(int argc, char **argv) {
- ExitIfWrongAbi();
-
+int main(int argc, char** argv) {
if (argc == 4 && !strcmp(argv[1], "--servername")) {
binderservername = argv[2];
} else {
diff --git a/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp b/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
index 278dd2bf81..140270f5a1 100644
--- a/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
+++ b/libs/binder/tests/binderMemoryHeapBaseUnitTest.cpp
@@ -37,7 +37,8 @@ TEST(MemoryHeapBase, MemfdSealed) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_SEAL);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
TEST(MemoryHeapBase, MemfdUnsealed) {
@@ -48,7 +49,8 @@ TEST(MemoryHeapBase, MemfdUnsealed) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), 0);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
TEST(MemoryHeapBase, MemfdSealedProtected) {
@@ -59,7 +61,9 @@ TEST(MemoryHeapBase, MemfdSealedProtected) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_SEAL | F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS),
+ F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_SEAL | F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
TEST(MemoryHeapBase, MemfdUnsealedProtected) {
@@ -71,7 +75,8 @@ TEST(MemoryHeapBase, MemfdUnsealedProtected) {
ASSERT_NE(mHeap.get(), nullptr);
int fd = mHeap->getHeapID();
EXPECT_NE(fd, -1);
- EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(fcntl(fd, F_GET_SEALS), F_SEAL_GROW | F_SEAL_SHRINK | F_SEAL_FUTURE_WRITE);
+ EXPECT_EQ(ftruncate(fd, 4096), -1);
}
#else
diff --git a/libs/binder/tests/binderParcelUnitTest.cpp b/libs/binder/tests/binderParcelUnitTest.cpp
index 359c783de5..32a70e5b11 100644
--- a/libs/binder/tests/binderParcelUnitTest.cpp
+++ b/libs/binder/tests/binderParcelUnitTest.cpp
@@ -23,6 +23,7 @@
using android::BBinder;
using android::IBinder;
using android::IPCThreadState;
+using android::NO_ERROR;
using android::OK;
using android::Parcel;
using android::sp;
@@ -30,6 +31,7 @@ using android::status_t;
using android::String16;
using android::String8;
using android::binder::Status;
+using android::binder::unique_fd;
TEST(Parcel, NonNullTerminatedString8) {
String8 kTestString = String8("test-is-good");
@@ -112,6 +114,205 @@ TEST(Parcel, DebugReadAllFds) {
EXPECT_EQ(ret[1], STDIN_FILENO);
}
+TEST(Parcel, AppendFromEmpty) {
+ Parcel p1;
+ Parcel p2;
+ p2.writeInt32(2);
+
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, p2.dataSize()));
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(2, p1.readInt32());
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+}
+
+TEST(Parcel, AppendPlainData) {
+ Parcel p1;
+ p1.writeInt32(1);
+ Parcel p2;
+ p2.writeInt32(2);
+
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, p2.dataSize()));
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(1, p1.readInt32());
+ ASSERT_EQ(2, p1.readInt32());
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+}
+
+TEST(Parcel, AppendPlainDataPartial) {
+ Parcel p1;
+ p1.writeInt32(1);
+ Parcel p2;
+ p2.writeInt32(2);
+ p2.writeInt32(3);
+ p2.writeInt32(4);
+
+ // only copy 8 bytes (two int32's worth)
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, 8));
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(1, p1.readInt32());
+ ASSERT_EQ(2, p1.readInt32());
+ ASSERT_EQ(3, p1.readInt32());
+ ASSERT_EQ(0, p1.readInt32()); // not 4, end of Parcel
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+}
+
+TEST(Parcel, HasBinders) {
+ sp<IBinder> b1 = sp<BBinder>::make();
+
+ Parcel p1;
+ p1.writeInt32(1);
+ p1.writeStrongBinder(b1);
+
+ bool result = false;
+ ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+ ASSERT_EQ(true, result);
+
+ p1.setDataSize(0); // clear data
+ result = false;
+ ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+ ASSERT_EQ(false, result);
+ p1.writeStrongBinder(b1); // reset with binder data
+ result = false;
+ ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+ ASSERT_EQ(true, result);
+
+ Parcel p3;
+ p3.appendFrom(&p1, 0, p1.dataSize());
+ result = false;
+ ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+ ASSERT_EQ(true, result);
+}
+
+TEST(Parcel, HasBindersInRange) {
+ sp<IBinder> b1 = sp<BBinder>::make();
+ Parcel p1;
+ p1.writeStrongBinder(b1);
+ bool result = false;
+ ASSERT_EQ(NO_ERROR, p1.hasBindersInRange(0, p1.dataSize(), &result));
+ ASSERT_EQ(true, result);
+ result = false;
+ ASSERT_EQ(NO_ERROR, p1.hasBinders(&result));
+ ASSERT_EQ(true, result);
+}
+
+TEST(Parcel, AppendWithBinder) {
+ sp<IBinder> b1 = sp<BBinder>::make();
+ sp<IBinder> b2 = sp<BBinder>::make();
+
+ Parcel p1;
+ p1.writeInt32(1);
+ p1.writeStrongBinder(b1);
+ Parcel p2;
+ p2.writeInt32(2);
+ p2.writeStrongBinder(b2);
+
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, p2.dataSize()));
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(1, p1.readInt32());
+ ASSERT_EQ(b1, p1.readStrongBinder());
+ ASSERT_EQ(2, p1.readInt32());
+ ASSERT_EQ(b2, p1.readStrongBinder());
+ ASSERT_EQ(2, p1.objectsCount());
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+ ASSERT_EQ(b2, p2.readStrongBinder());
+}
+
+TEST(Parcel, AppendWithBinderPartial) {
+ sp<IBinder> b1 = sp<BBinder>::make();
+ sp<IBinder> b2 = sp<BBinder>::make();
+
+ Parcel p1;
+ p1.writeInt32(1);
+ p1.writeStrongBinder(b1);
+ Parcel p2;
+ p2.writeInt32(2);
+ p2.writeStrongBinder(b2);
+
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, 8)); // BAD: 4 bytes into strong binder
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(1, p1.readInt32());
+ ASSERT_EQ(b1, p1.readStrongBinder());
+ ASSERT_EQ(2, p1.readInt32());
+ ASSERT_EQ(1935813253, p1.readInt32()); // whatever garbage that is there (ABI)
+ ASSERT_EQ(1, p1.objectsCount());
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+ ASSERT_EQ(b2, p2.readStrongBinder());
+}
+
+TEST(Parcel, AppendWithFd) {
+ unique_fd fd1 = unique_fd(dup(0));
+ unique_fd fd2 = unique_fd(dup(0));
+
+ Parcel p1;
+ p1.writeInt32(1);
+ p1.writeDupFileDescriptor(0); // with ownership
+ p1.writeFileDescriptor(fd1.get()); // without ownership
+ Parcel p2;
+ p2.writeInt32(2);
+ p2.writeDupFileDescriptor(0); // with ownership
+ p2.writeFileDescriptor(fd2.get()); // without ownership
+
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, p2.dataSize()));
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(1, p1.readInt32());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_EQ(2, p1.readInt32());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_EQ(4, p1.objectsCount());
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+}
+
+TEST(Parcel, AppendWithFdPartial) {
+ unique_fd fd1 = unique_fd(dup(0));
+ unique_fd fd2 = unique_fd(dup(0));
+
+ Parcel p1;
+ p1.writeInt32(1);
+ p1.writeDupFileDescriptor(0); // with ownership
+ p1.writeFileDescriptor(fd1.get()); // without ownership
+ Parcel p2;
+ p2.writeInt32(2);
+ p2.writeDupFileDescriptor(0); // with ownership
+ p2.writeFileDescriptor(fd2.get()); // without ownership
+
+ ASSERT_EQ(OK, p1.appendFrom(&p2, 0, 8)); // BAD: 4 bytes into binder
+
+ p1.setDataPosition(0);
+ ASSERT_EQ(1, p1.readInt32());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_EQ(2, p1.readInt32());
+ ASSERT_EQ(1717840517, p1.readInt32()); // whatever garbage that is there (ABI)
+ ASSERT_EQ(2, p1.objectsCount());
+
+ p2.setDataPosition(0);
+ ASSERT_EQ(2, p2.readInt32());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+ ASSERT_NE(-1, p1.readFileDescriptor());
+}
+
// Tests a second operation results in a parcel at the same location as it
// started.
void parcelOpSameLength(const std::function<void(Parcel*)>& a, const std::function<void(Parcel*)>& b) {
diff --git a/libs/binder/tests/binderRecordReplayTest.cpp b/libs/binder/tests/binderRecordReplayTest.cpp
new file mode 100644
index 0000000000..b975fad2c9
--- /dev/null
+++ b/libs/binder/tests/binderRecordReplayTest.cpp
@@ -0,0 +1,411 @@
+/*
+ * 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 <BnBinderRecordReplayTest.h>
+#include <android-base/logging.h>
+#include <binder/Binder.h>
+#include <binder/BpBinder.h>
+#include <binder/IBinder.h>
+#include <binder/IPCThreadState.h>
+#include <binder/IServiceManager.h>
+#include <binder/RecordedTransaction.h>
+#include <binder/unique_fd.h>
+
+#include <cutils/ashmem.h>
+
+#include <fuzzbinder/libbinder_driver.h>
+#include <fuzzbinder/random_binder.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <fuzzseeds/random_parcel_seeds.h>
+
+#include <gtest/gtest.h>
+
+#include <sys/prctl.h>
+
+#include "../file.h"
+#include "parcelables/SingleDataParcelable.h"
+
+using namespace android;
+using android::generateSeedsFromRecording;
+using android::RandomBinder;
+using android::binder::borrowed_fd;
+using android::binder::Status;
+using android::binder::unique_fd;
+using android::binder::debug::RecordedTransaction;
+using parcelables::SingleDataParcelable;
+
+const String16 kServerName = String16("binderRecordReplay");
+extern std::string kRandomInterfaceName;
+
+#define GENERATE_GETTER_SETTER_PRIMITIVE(name, T) \
+ Status set##name(T input) { \
+ m##name = input; \
+ return Status::ok(); \
+ } \
+ \
+ Status get##name(T* output) { \
+ *output = m##name; \
+ return Status::ok(); \
+ } \
+ T m##name
+
+#define GENERATE_GETTER_SETTER(name, T) \
+ Status set##name(const T& input) { \
+ m##name = input; \
+ return Status::ok(); \
+ } \
+ \
+ Status get##name(T* output) { \
+ *output = m##name; \
+ return Status::ok(); \
+ } \
+ T m##name
+
+class MyRecordReplay : public BnBinderRecordReplayTest {
+public:
+ GENERATE_GETTER_SETTER_PRIMITIVE(Boolean, bool);
+ GENERATE_GETTER_SETTER_PRIMITIVE(Byte, int8_t);
+ GENERATE_GETTER_SETTER_PRIMITIVE(Int, int);
+ GENERATE_GETTER_SETTER_PRIMITIVE(Char, char16_t);
+ GENERATE_GETTER_SETTER_PRIMITIVE(Long, int64_t);
+ GENERATE_GETTER_SETTER_PRIMITIVE(Float, float);
+ GENERATE_GETTER_SETTER_PRIMITIVE(Double, double);
+
+ GENERATE_GETTER_SETTER(String, String16);
+ GENERATE_GETTER_SETTER(SingleDataParcelable, SingleDataParcelable);
+ GENERATE_GETTER_SETTER(Binder, sp<IBinder>);
+
+ GENERATE_GETTER_SETTER(BooleanArray, std::vector<bool>);
+ GENERATE_GETTER_SETTER(ByteArray, std::vector<uint8_t>);
+ GENERATE_GETTER_SETTER(IntArray, std::vector<int>);
+ GENERATE_GETTER_SETTER(CharArray, std::vector<char16_t>);
+ GENERATE_GETTER_SETTER(LongArray, std::vector<int64_t>);
+ GENERATE_GETTER_SETTER(FloatArray, std::vector<float>);
+ GENERATE_GETTER_SETTER(DoubleArray, std::vector<double>);
+ GENERATE_GETTER_SETTER(StringArray, std::vector<::android::String16>);
+ GENERATE_GETTER_SETTER(SingleDataParcelableArray, std::vector<SingleDataParcelable>);
+
+ Status setFileDescriptor(unique_fd input) {
+ mFd = std::move(unique_fd(dup(input)));
+ return Status::ok();
+ }
+
+ Status getFileDescriptor(unique_fd* output) {
+ *output = std::move(unique_fd(dup(mFd)));
+ return Status::ok();
+ }
+ unique_fd mFd;
+};
+
+std::vector<uint8_t> retrieveData(borrowed_fd fd) {
+ struct stat fdStat;
+ EXPECT_TRUE(fstat(fd.get(), &fdStat) != -1);
+
+ std::vector<uint8_t> buffer(fdStat.st_size);
+ auto readResult = android::base::ReadFully(fd, buffer.data(), fdStat.st_size);
+ EXPECT_TRUE(readResult != 0);
+ return std::move(buffer);
+}
+
+void replayFuzzService(const sp<BpBinder>& binder, const RecordedTransaction& transaction) {
+ unique_fd seedFd(open("/data/local/tmp/replayFuzzService",
+ O_RDWR | O_CREAT | O_CLOEXEC | O_TRUNC, 0666));
+ ASSERT_TRUE(seedFd.ok());
+
+ // generate corpus from this transaction.
+ generateSeedsFromRecording(seedFd, transaction);
+
+ // Read the data which has been written to seed corpus
+ ASSERT_EQ(0, lseek(seedFd.get(), 0, SEEK_SET));
+ std::vector<uint8_t> seedData = retrieveData(seedFd);
+ EXPECT_TRUE(seedData.size() != 0);
+
+ // use fuzzService to replay the corpus
+ FuzzedDataProvider provider(seedData.data(), seedData.size());
+ fuzzService(binder, std::move(provider));
+}
+
+void replayBinder(const sp<BpBinder>& binder, const RecordedTransaction& transaction) {
+ // TODO: move logic to replay RecordedTransaction into RecordedTransaction
+ Parcel data;
+ data.setData(transaction.getDataParcel().data(), transaction.getDataParcel().dataSize());
+ auto result = binder->transact(transaction.getCode(), data, nullptr, transaction.getFlags());
+
+ // make sure recording does the thing we expect it to do
+ EXPECT_EQ(OK, result);
+}
+
+class BinderRecordReplayTest : public ::testing::Test {
+public:
+ void SetUp() override {
+ // get the remote service
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ auto binder = defaultServiceManager()->getService(kServerName);
+#pragma clang diagnostic pop
+ ASSERT_NE(nullptr, binder);
+ mInterface = interface_cast<IBinderRecordReplayTest>(binder);
+ mBpBinder = binder->remoteBinder();
+ ASSERT_NE(nullptr, mBpBinder);
+ }
+
+ template <typename T, typename U>
+ void recordReplay(Status (IBinderRecordReplayTest::*set)(T), U recordedValue,
+ Status (IBinderRecordReplayTest::*get)(U*), U changedValue) {
+ using ReplayFunc = decltype(&replayFuzzService);
+ vector<ReplayFunc> replayFunctions = {&replayFuzzService};
+ if (!std::is_same_v<U, unique_fd> && !std::is_same_v<U, sp<IBinder>>) {
+ // Parcel retrieved from record replay doesn't have object information. use it for
+ // replaying primitive types only.
+ replayFunctions.push_back(&replayBinder);
+ }
+
+ for (auto replayFunc : replayFunctions) {
+ unique_fd fd(open("/data/local/tmp/binderRecordReplayTest.rec",
+ O_RDWR | O_CREAT | O_CLOEXEC, 0666));
+ ASSERT_TRUE(fd.ok());
+
+ // record a transaction
+ mBpBinder->startRecordingBinder(fd);
+ auto status = (*mInterface.*set)(std::move(recordedValue));
+ EXPECT_TRUE(status.isOk());
+ mBpBinder->stopRecordingBinder();
+
+ // test transaction does the thing we expect it to do
+ U output;
+ status = (*mInterface.*get)(&output);
+ EXPECT_TRUE(status.isOk());
+
+ // Expect this equal only if types are primitives
+ if (!std::is_same_v<U, unique_fd> && !std::is_same_v<U, sp<IBinder>>) {
+ EXPECT_EQ(output, recordedValue);
+ }
+
+ // write over the existing state
+ status = (*mInterface.*set)(std::move(changedValue));
+ EXPECT_TRUE(status.isOk());
+
+ status = (*mInterface.*get)(&output);
+ EXPECT_TRUE(status.isOk());
+
+ if (!std::is_same_v<U, unique_fd> && !std::is_same_v<U, sp<IBinder>>) {
+ EXPECT_EQ(output, changedValue);
+ }
+
+ // replay transaction
+ ASSERT_EQ(0, lseek(fd.get(), 0, SEEK_SET));
+ std::optional<RecordedTransaction> transaction = RecordedTransaction::fromFile(fd);
+ ASSERT_NE(transaction, std::nullopt);
+
+ const RecordedTransaction& recordedTransaction = *transaction;
+ // call replay function with recorded transaction
+ (*replayFunc)(mBpBinder, recordedTransaction);
+
+ status = (*mInterface.*get)(&output);
+ EXPECT_TRUE(status.isOk());
+
+ // FDs and binders will be replaced with random fd and random binders
+ if constexpr (std::is_same_v<U, unique_fd>) {
+ // verify that replayed fd is /dev/null. This is being replayed from random_fd.cpp
+ // and choosing /dav/null while generating seed in binder2corpus
+ std::string fdPath = "/proc/self/fd/" + std::to_string(output.get());
+ char path[PATH_MAX];
+ ASSERT_GT(readlink(fdPath.c_str(), path, sizeof(path)), 0);
+ EXPECT_EQ(strcmp("/dev/null", path), 0);
+ } else if constexpr (std::is_same_v<U, sp<IBinder>>) {
+ // This is binder is replayed from random_binder.cpp using seed data which writes
+ // this interface.
+ EXPECT_EQ(String16(kRandomInterfaceName.c_str(), kRandomInterfaceName.size()),
+ output->getInterfaceDescriptor());
+ } else {
+ ASSERT_EQ(recordedValue, output);
+ }
+ }
+ }
+
+private:
+ sp<BpBinder> mBpBinder;
+ sp<IBinderRecordReplayTest> mInterface;
+};
+
+TEST_F(BinderRecordReplayTest, ReplayByte) {
+ recordReplay(&IBinderRecordReplayTest::setByte, int8_t{122}, &IBinderRecordReplayTest::getByte,
+ int8_t{90});
+}
+
+TEST_F(BinderRecordReplayTest, ReplayBoolean) {
+ recordReplay(&IBinderRecordReplayTest::setBoolean, true, &IBinderRecordReplayTest::getBoolean,
+ false);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayChar) {
+ recordReplay(&IBinderRecordReplayTest::setChar, char16_t{'G'},
+ &IBinderRecordReplayTest::getChar, char16_t{'K'});
+}
+
+TEST_F(BinderRecordReplayTest, ReplayInt) {
+ recordReplay(&IBinderRecordReplayTest::setInt, 3, &IBinderRecordReplayTest::getInt, 5);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayFloat) {
+ recordReplay(&IBinderRecordReplayTest::setFloat, 1.1f, &IBinderRecordReplayTest::getFloat,
+ 22.0f);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayLong) {
+ recordReplay(&IBinderRecordReplayTest::setLong, int64_t{1LL << 55},
+ &IBinderRecordReplayTest::getLong, int64_t{1LL << 12});
+}
+
+TEST_F(BinderRecordReplayTest, ReplayDouble) {
+ recordReplay(&IBinderRecordReplayTest::setDouble, 0.00, &IBinderRecordReplayTest::getDouble,
+ 1.11);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayString) {
+ const ::android::String16& input1 = String16("This is saved string");
+ const ::android::String16& input2 = String16("This is changed string");
+ recordReplay(&IBinderRecordReplayTest::setString, input1, &IBinderRecordReplayTest::getString,
+ input2);
+}
+
+TEST_F(BinderRecordReplayTest, ReplaySingleDataParcelable) {
+ SingleDataParcelable saved, changed;
+ saved.data = 3;
+ changed.data = 5;
+ recordReplay(&IBinderRecordReplayTest::setSingleDataParcelable, saved,
+ &IBinderRecordReplayTest::getSingleDataParcelable, changed);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayByteArray) {
+ std::vector<uint8_t> savedArray = {uint8_t{255}, uint8_t{0}, uint8_t{127}};
+ std::vector<uint8_t> changedArray = {uint8_t{2}, uint8_t{7}, uint8_t{117}};
+ recordReplay(&IBinderRecordReplayTest::setByteArray, savedArray,
+ &IBinderRecordReplayTest::getByteArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayBooleanArray) {
+ std::vector<bool> savedArray = {true, false, true};
+ std::vector<bool> changedArray = {false, true, false};
+ recordReplay(&IBinderRecordReplayTest::setBooleanArray, savedArray,
+ &IBinderRecordReplayTest::getBooleanArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayCharArray) {
+ std::vector<char16_t> savedArray = {char16_t{'G'}, char16_t{'L'}, char16_t{'K'}, char16_t{'T'}};
+ std::vector<char16_t> changedArray = {char16_t{'X'}, char16_t{'Y'}, char16_t{'Z'}};
+ recordReplay(&IBinderRecordReplayTest::setCharArray, savedArray,
+ &IBinderRecordReplayTest::getCharArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayIntArray) {
+ std::vector<int> savedArray = {12, 45, 178};
+ std::vector<int> changedArray = {32, 14, 78, 1899};
+ recordReplay(&IBinderRecordReplayTest::setIntArray, savedArray,
+ &IBinderRecordReplayTest::getIntArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayFloatArray) {
+ std::vector<float> savedArray = {12.14f, 45.56f, 123.178f};
+ std::vector<float> changedArray = {0.00f, 14.0f, 718.1f, 1899.122f, 3268.123f};
+ recordReplay(&IBinderRecordReplayTest::setFloatArray, savedArray,
+ &IBinderRecordReplayTest::getFloatArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayLongArray) {
+ std::vector<int64_t> savedArray = {int64_t{1LL << 11}, int64_t{1LL << 55}, int64_t{1LL << 45}};
+ std::vector<int64_t> changedArray = {int64_t{1LL << 1}, int64_t{1LL << 21}, int64_t{1LL << 33},
+ int64_t{1LL << 62}};
+ recordReplay(&IBinderRecordReplayTest::setLongArray, savedArray,
+ &IBinderRecordReplayTest::getLongArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayDoubleArray) {
+ std::vector<double> savedArray = {12.1412313, 45.561232, 123.1781111};
+ std::vector<double> changedArray = {0.00111, 14.32130, 712312318.19, 1899212.122,
+ 322168.122123};
+ recordReplay(&IBinderRecordReplayTest::setDoubleArray, savedArray,
+ &IBinderRecordReplayTest::getDoubleArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayStringArray) {
+ std::vector<String16> savedArray = {String16("This is saved value"), String16(),
+ String16("\0\0", 2), String16("\xF3\x01\xAC\xAD\x21\xAF")};
+
+ std::vector<String16> changedArray = {String16("This is changed value"),
+ String16("\xF0\x90\x90\xB7\xE2\x82\xAC")};
+ recordReplay(&IBinderRecordReplayTest::setStringArray, savedArray,
+ &IBinderRecordReplayTest::getStringArray, changedArray);
+}
+
+TEST_F(BinderRecordReplayTest, ReplaySingleDataParcelableArray) {
+ SingleDataParcelable s1, s2, s3, s4, s5;
+ s1.data = 5213;
+ s2.data = 1512;
+ s3.data = 4233;
+ s4.data = 123124;
+ s5.data = 0;
+ std::vector<SingleDataParcelable> saved = {s1, s2, s3};
+ std::vector<SingleDataParcelable> changed = {s4, s5};
+
+ recordReplay(&IBinderRecordReplayTest::setSingleDataParcelableArray, saved,
+ &IBinderRecordReplayTest::getSingleDataParcelableArray, changed);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayBinder) {
+ vector<uint8_t> data = {0x8A, 0x19, 0x0D, 0x44, 0x37, 0x0D, 0x38, 0x5E, 0x9B, 0xAA, 0xF3, 0xDA};
+ sp<IBinder> saved = new RandomBinder(String16("random_interface"), std::move(data));
+ sp<IBinder> changed = IInterface::asBinder(defaultServiceManager());
+ recordReplay(&IBinderRecordReplayTest::setBinder, saved, &IBinderRecordReplayTest::getBinder,
+ changed);
+}
+
+TEST_F(BinderRecordReplayTest, ReplayFd) {
+ // Write something to both fds we are setting
+ unique_fd saved(open("/data/local/tmp/test_fd", O_RDWR | O_CREAT | O_CLOEXEC, 0666));
+ std::string contentSaved = "This will be never read again for recorded fd!";
+ CHECK(android::base::WriteFully(saved, contentSaved.data(), contentSaved.size()))
+ << saved.get();
+
+ unique_fd changed(open("/data/local/tmp/test_des", O_RDWR | O_CREAT | O_CLOEXEC, 0666));
+ std::string contentChanged = "This will be never read again from changed fd!";
+ CHECK(android::base::WriteFully(changed, contentChanged.data(), contentChanged.size()))
+ << changed.get();
+
+ // When fds are replayed, it will be replaced by /dev/null..reading from it should yield
+ // null data
+ recordReplay(&IBinderRecordReplayTest::setFileDescriptor, std::move(unique_fd(dup(saved))),
+ &IBinderRecordReplayTest::getFileDescriptor, std::move(unique_fd(dup(changed))));
+}
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+
+ if (fork() == 0) {
+ prctl(PR_SET_PDEATHSIG, SIGHUP);
+
+ auto server = sp<MyRecordReplay>::make();
+ android::defaultServiceManager()->addService(kServerName, server.get());
+
+ IPCThreadState::self()->joinThreadPool(true);
+ exit(1); // should not reach
+ }
+
+ // not racey, but getService sleeps for 1s
+ usleep(100000);
+
+ return RUN_ALL_TESTS();
+}
diff --git a/libs/binder/tests/binderRecordedTransactionTest.cpp b/libs/binder/tests/binderRecordedTransactionTest.cpp
index 30172cce87..6eb78d037d 100644
--- a/libs/binder/tests/binderRecordedTransactionTest.cpp
+++ b/libs/binder/tests/binderRecordedTransactionTest.cpp
@@ -20,7 +20,7 @@
using android::Parcel;
using android::status_t;
-using android::base::unique_fd;
+using android::binder::unique_fd;
using android::binder::debug::RecordedTransaction;
TEST(BinderRecordedTransaction, RoundTripEncoding) {
diff --git a/libs/binder/tests/binderRpcBenchmark.cpp b/libs/binder/tests/binderRpcBenchmark.cpp
index 52ba9b00fd..4f10d7417f 100644
--- a/libs/binder/tests/binderRpcBenchmark.cpp
+++ b/libs/binder/tests/binderRpcBenchmark.cpp
@@ -74,6 +74,44 @@ class MyBinderRpcBenchmark : public BnBinderRpcBenchmark {
*out = bytes;
return Status::ok();
}
+
+ class CountedBinder : public BBinder {
+ public:
+ CountedBinder(const sp<MyBinderRpcBenchmark>& parent) : mParent(parent) {
+ std::lock_guard<std::mutex> l(mParent->mCountMutex);
+ mParent->mBinderCount++;
+ // std::cout << "Count + is now " << mParent->mBinderCount << std::endl;
+ }
+ ~CountedBinder() {
+ {
+ std::lock_guard<std::mutex> l(mParent->mCountMutex);
+ mParent->mBinderCount--;
+ // std::cout << "Count - is now " << mParent->mBinderCount << std::endl;
+
+ // skip notify
+ if (mParent->mBinderCount != 0) return;
+ }
+ mParent->mCountCv.notify_one();
+ }
+
+ private:
+ sp<MyBinderRpcBenchmark> mParent;
+ };
+
+ Status gimmeBinder(sp<IBinder>* out) override {
+ *out = sp<CountedBinder>::make(sp<MyBinderRpcBenchmark>::fromExisting(this));
+ return Status::ok();
+ }
+ Status waitGimmesDestroyed() override {
+ std::unique_lock<std::mutex> l(mCountMutex);
+ mCountCv.wait(l, [&] { return mBinderCount == 0; });
+ return Status::ok();
+ }
+
+ friend class CountedBinder;
+ std::mutex mCountMutex;
+ std::condition_variable mCountCv;
+ size_t mBinderCount;
};
enum Transport {
@@ -102,9 +140,11 @@ std::unique_ptr<RpcTransportCtxFactory> makeFactoryTls() {
}
static sp<RpcSession> gSession = RpcSession::make();
+static sp<IBinder> gRpcBinder;
// Certificate validation happens during handshake and does not affect the result of benchmarks.
// Skip certificate validation to simplify the setup process.
static sp<RpcSession> gSessionTls = RpcSession::make(makeFactoryTls());
+static sp<IBinder> gRpcTlsBinder;
#ifdef __BIONIC__
static const String16 kKernelBinderInstance = String16(u"binderRpcBenchmark-control");
static sp<IBinder> gKernelBinder;
@@ -118,21 +158,42 @@ static sp<IBinder> getBinderForOptions(benchmark::State& state) {
return gKernelBinder;
#endif
case RPC:
- return gSession->getRootObject();
+ return gRpcBinder;
case RPC_TLS:
- return gSessionTls->getRootObject();
+ return gRpcTlsBinder;
default:
LOG(FATAL) << "Unknown transport value: " << transport;
return nullptr;
}
}
+static void SetLabel(benchmark::State& state) {
+ Transport transport = static_cast<Transport>(state.range(0));
+ switch (transport) {
+#ifdef __BIONIC__
+ case KERNEL:
+ state.SetLabel("kernel");
+ break;
+#endif
+ case RPC:
+ state.SetLabel("rpc");
+ break;
+ case RPC_TLS:
+ state.SetLabel("rpc_tls");
+ break;
+ default:
+ LOG(FATAL) << "Unknown transport value: " << transport;
+ }
+}
+
void BM_pingTransaction(benchmark::State& state) {
sp<IBinder> binder = getBinderForOptions(state);
while (state.KeepRunning()) {
CHECK_EQ(OK, binder->pingBinder());
}
+
+ SetLabel(state);
}
BENCHMARK(BM_pingTransaction)->ArgsProduct({kTransportList});
@@ -162,6 +223,8 @@ void BM_repeatTwoPageString(benchmark::State& state) {
Status ret = iface->repeatString(str, &out);
CHECK(ret.isOk()) << ret;
}
+
+ SetLabel(state);
}
BENCHMARK(BM_repeatTwoPageString)->ArgsProduct({kTransportList});
@@ -180,11 +243,45 @@ void BM_throughputForTransportAndBytes(benchmark::State& state) {
Status ret = iface->repeatBytes(bytes, &out);
CHECK(ret.isOk()) << ret;
}
+
+ SetLabel(state);
}
BENCHMARK(BM_throughputForTransportAndBytes)
->ArgsProduct({kTransportList,
{64, 1024, 2048, 4096, 8182, 16364, 32728, 65535, 65536, 65537}});
+void BM_collectProxies(benchmark::State& state) {
+ sp<IBinder> binder = getBinderForOptions(state);
+ sp<IBinderRpcBenchmark> iface = interface_cast<IBinderRpcBenchmark>(binder);
+ CHECK(iface != nullptr);
+
+ const size_t kNumIters = state.range(1);
+
+ while (state.KeepRunning()) {
+ std::vector<sp<IBinder>> out;
+ out.resize(kNumIters);
+
+ for (size_t i = 0; i < kNumIters; i++) {
+ Status ret = iface->gimmeBinder(&out[i]);
+ CHECK(ret.isOk()) << ret;
+ }
+
+ out.clear();
+
+ // we are using a thread up to wait, so make a call to
+ // force all refcounts to be updated first - current
+ // binder behavior means we really don't need to wait,
+ // so code which is waiting is really there to protect
+ // against any future changes that could delay destruction
+ android::IInterface::asBinder(iface)->pingBinder();
+
+ iface->waitGimmesDestroyed();
+ }
+
+ SetLabel(state);
+}
+BENCHMARK(BM_collectProxies)->ArgsProduct({kTransportList, {10, 100, 1000, 5000, 10000, 20000}});
+
void BM_repeatBinder(benchmark::State& state) {
sp<IBinder> binder = getBinderForOptions(state);
CHECK(binder != nullptr);
@@ -199,6 +296,8 @@ void BM_repeatBinder(benchmark::State& state) {
Status ret = iface->repeatBinder(binder, &out);
CHECK(ret.isOk()) << ret;
}
+
+ SetLabel(state);
}
BENCHMARK(BM_repeatBinder)->ArgsProduct({kTransportList});
@@ -226,11 +325,6 @@ int main(int argc, char** argv) {
::benchmark::Initialize(&argc, argv);
if (::benchmark::ReportUnrecognizedArguments(argc, argv)) return 1;
- std::cerr << "Tests suffixes:" << std::endl;
- std::cerr << "\t.../" << Transport::KERNEL << " is KERNEL" << std::endl;
- std::cerr << "\t.../" << Transport::RPC << " is RPC" << std::endl;
- std::cerr << "\t.../" << Transport::RPC_TLS << " is RPC with TLS" << std::endl;
-
#ifdef __BIONIC__
if (0 == fork()) {
prctl(PR_SET_PDEATHSIG, SIGHUP); // racey, okay
@@ -254,11 +348,13 @@ int main(int argc, char** argv) {
(void)unlink(addr.c_str());
forkRpcServer(addr.c_str(), RpcServer::make(RpcTransportCtxFactoryRaw::make()));
setupClient(gSession, addr.c_str());
+ gRpcBinder = gSession->getRootObject();
std::string tlsAddr = tmp + "/binderRpcTlsBenchmark";
(void)unlink(tlsAddr.c_str());
forkRpcServer(tlsAddr.c_str(), RpcServer::make(makeFactoryTls()));
setupClient(gSessionTls, tlsAddr.c_str());
+ gRpcTlsBinder = gSessionTls->getRootObject();
::benchmark::RunSpecifiedBenchmarks();
return 0;
diff --git a/libs/binder/tests/binderRpcTest.cpp b/libs/binder/tests/binderRpcTest.cpp
index dcea880ef9..2769a88342 100644
--- a/libs/binder/tests/binderRpcTest.cpp
+++ b/libs/binder/tests/binderRpcTest.cpp
@@ -14,8 +14,10 @@
* limitations under the License.
*/
+#ifndef __ANDROID_VENDOR__
+// only used on NDK tests outside of vendor
#include <aidl/IBinderRpcTest.h>
-#include <android-base/stringprintf.h>
+#endif
#include <chrono>
#include <cstdlib>
@@ -23,21 +25,27 @@
#include <thread>
#include <type_traits>
+#include <dirent.h>
#include <dlfcn.h>
#include <poll.h>
#include <sys/prctl.h>
#include <sys/socket.h>
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
#include <binder/RpcTransportTipcAndroid.h>
#include <trusty/tipc.h>
-#endif // __ANDROID_VENDOR__
+#endif // BINDER_RPC_TO_TRUSTY_TEST
+#include "../Utils.h"
#include "binderRpcTestCommon.h"
#include "binderRpcTestFixture.h"
using namespace std::chrono_literals;
using namespace std::placeholders;
+using android::binder::borrowed_fd;
+using android::binder::GetExecutableDirectory;
+using android::binder::ReadFdToString;
+using android::binder::unique_fd;
using testing::AssertionFailure;
using testing::AssertionResult;
using testing::AssertionSuccess;
@@ -50,18 +58,18 @@ constexpr bool kEnableSharedLibs = false;
constexpr bool kEnableSharedLibs = true;
#endif
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
constexpr char kTrustyIpcDevice[] = "/dev/trusty-ipc-dev0";
#endif
static std::string WaitStatusToString(int wstatus) {
if (WIFEXITED(wstatus)) {
- return base::StringPrintf("exit status %d", WEXITSTATUS(wstatus));
+ return std::format("exit status {}", WEXITSTATUS(wstatus));
}
if (WIFSIGNALED(wstatus)) {
- return base::StringPrintf("term signal %d", WTERMSIG(wstatus));
+ return std::format("term signal {}", WTERMSIG(wstatus));
}
- return base::StringPrintf("unexpected state %d", wstatus);
+ return std::format("unexpected state {}", wstatus);
}
static void debugBacktrace(pid_t pid) {
@@ -80,12 +88,11 @@ public:
mPid = other.mPid;
other.mPid = 0;
}
- Process(const std::function<void(android::base::borrowed_fd /* writeEnd */,
- android::base::borrowed_fd /* readEnd */)>& f) {
- android::base::unique_fd childWriteEnd;
- android::base::unique_fd childReadEnd;
- CHECK(android::base::Pipe(&mReadEnd, &childWriteEnd, 0)) << strerror(errno);
- CHECK(android::base::Pipe(&childReadEnd, &mWriteEnd, 0)) << strerror(errno);
+ Process(const std::function<void(borrowed_fd /* writeEnd */, borrowed_fd /* readEnd */)>& f) {
+ unique_fd childWriteEnd;
+ unique_fd childReadEnd;
+ if (!binder::Pipe(&mReadEnd, &childWriteEnd, 0)) PLOGF("child write pipe failed");
+ if (!binder::Pipe(&childReadEnd, &mWriteEnd, 0)) PLOGF("child read pipe failed");
if (0 == (mPid = fork())) {
// racey: assume parent doesn't crash before this is set
prctl(PR_SET_PDEATHSIG, SIGHUP);
@@ -107,8 +114,8 @@ public:
}
}
}
- android::base::borrowed_fd readEnd() { return mReadEnd; }
- android::base::borrowed_fd writeEnd() { return mWriteEnd; }
+ borrowed_fd readEnd() { return mReadEnd; }
+ borrowed_fd writeEnd() { return mWriteEnd; }
void setCustomExitStatusCheck(std::function<void(int wstatus)> f) {
mCustomExitStatusCheck = std::move(f);
@@ -122,14 +129,14 @@ public:
private:
std::function<void(int wstatus)> mCustomExitStatusCheck;
pid_t mPid = 0;
- android::base::unique_fd mReadEnd;
- android::base::unique_fd mWriteEnd;
+ unique_fd mReadEnd;
+ unique_fd mWriteEnd;
};
static std::string allocateSocketAddress() {
static size_t id = 0;
std::string temp = getenv("TMPDIR") ?: "/tmp";
- auto ret = temp + "/binderRpcTest_" + std::to_string(id++);
+ auto ret = temp + "/binderRpcTest_" + std::to_string(getpid()) + "_" + std::to_string(id++);
unlink(ret.c_str());
return ret;
};
@@ -139,12 +146,13 @@ static unsigned int allocateVsockPort() {
return vsockPort++;
}
-static base::unique_fd initUnixSocket(std::string addr) {
+static unique_fd initUnixSocket(std::string addr) {
auto socket_addr = UnixSocketAddress(addr.c_str());
- base::unique_fd fd(
- TEMP_FAILURE_RETRY(socket(socket_addr.addr()->sa_family, SOCK_STREAM, AF_UNIX)));
- CHECK(fd.ok());
- CHECK_EQ(0, TEMP_FAILURE_RETRY(bind(fd.get(), socket_addr.addr(), socket_addr.addrSize())));
+ unique_fd fd(TEMP_FAILURE_RETRY(socket(socket_addr.addr()->sa_family, SOCK_STREAM, AF_UNIX)));
+ if (!fd.ok()) PLOGF("initUnixSocket failed to create socket");
+ if (0 != TEMP_FAILURE_RETRY(bind(fd.get(), socket_addr.addr(), socket_addr.addrSize()))) {
+ PLOGF("initUnixSocket failed to bind");
+ }
return fd;
}
@@ -163,7 +171,8 @@ public:
session.root = nullptr;
}
- for (auto& info : sessions) {
+ for (size_t sessionNum = 0; sessionNum < sessions.size(); sessionNum++) {
+ auto& info = sessions.at(sessionNum);
sp<RpcSession>& session = info.session;
EXPECT_NE(nullptr, session);
@@ -179,6 +188,7 @@ public:
for (size_t i = 0; i < 3; i++) {
sp<RpcSession> strongSession = weakSession.promote();
EXPECT_EQ(nullptr, strongSession)
+ << "For session " << sessionNum << ". "
<< (debugBacktrace(host.getPid()), debugBacktrace(getpid()),
"Leaked sess: ")
<< strongSession->getStrongCount() << " checked time " << i;
@@ -197,72 +207,67 @@ public:
void terminate() override { host.terminate(); }
};
-static base::unique_fd connectTo(const RpcSocketAddress& addr) {
- base::unique_fd serverFd(
+static unique_fd connectTo(const RpcSocketAddress& addr) {
+ unique_fd serverFd(
TEMP_FAILURE_RETRY(socket(addr.addr()->sa_family, SOCK_STREAM | SOCK_CLOEXEC, 0)));
- int savedErrno = errno;
- CHECK(serverFd.ok()) << "Could not create socket " << addr.toString() << ": "
- << strerror(savedErrno);
+ if (!serverFd.ok()) {
+ PLOGF("Could not create socket %s", addr.toString().c_str());
+ }
if (0 != TEMP_FAILURE_RETRY(connect(serverFd.get(), addr.addr(), addr.addrSize()))) {
- int savedErrno = errno;
- LOG(FATAL) << "Could not connect to socket " << addr.toString() << ": "
- << strerror(savedErrno);
+ PLOGF("Could not connect to socket %s", addr.toString().c_str());
}
return serverFd;
}
-static base::unique_fd connectToUnixBootstrap(const RpcTransportFd& transportFd) {
- base::unique_fd sockClient, sockServer;
- if (!base::Socketpair(SOCK_STREAM, &sockClient, &sockServer)) {
- int savedErrno = errno;
- LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
+#ifndef BINDER_RPC_TO_TRUSTY_TEST
+static unique_fd connectToUnixBootstrap(const RpcTransportFd& transportFd) {
+ unique_fd sockClient, sockServer;
+ if (!binder::Socketpair(SOCK_STREAM, &sockClient, &sockServer)) {
+ PLOGF("Failed socketpair()");
}
int zero = 0;
iovec iov{&zero, sizeof(zero)};
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+ std::vector<std::variant<unique_fd, borrowed_fd>> fds;
fds.emplace_back(std::move(sockServer));
- if (sendMessageOnSocket(transportFd, &iov, 1, &fds) < 0) {
- int savedErrno = errno;
- LOG(FATAL) << "Failed sendMessageOnSocket: " << strerror(savedErrno);
+ if (binder::os::sendMessageOnSocket(transportFd, &iov, 1, &fds) < 0) {
+ PLOGF("Failed sendMessageOnSocket");
}
return std::move(sockClient);
}
+#endif // BINDER_RPC_TO_TRUSTY_TEST
-std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
- auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
- auto ret = PrintToString(type) + "_" + newFactory(security)->toCString() + "_clientV" +
- std::to_string(clientVersion) + "_serverV" + std::to_string(serverVersion);
- if (singleThreaded) {
- ret += "_single_threaded";
- }
- if (noKernel) {
- ret += "_no_kernel";
- }
- return ret;
+std::unique_ptr<RpcTransportCtxFactory> BinderRpc::newFactory(RpcSecurity rpcSecurity) {
+ return newTlsFactory(rpcSecurity);
}
// This creates a new process serving an interface on a certain number of
// threads.
std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
const BinderRpcOptions& options) {
- CHECK_GE(options.numSessions, 1) << "Must have at least one session to a server";
+ LOG_ALWAYS_FATAL_IF(options.numSessions < 1, "Must have at least one session to a server");
- SocketType socketType = std::get<0>(GetParam());
- RpcSecurity rpcSecurity = std::get<1>(GetParam());
- uint32_t clientVersion = std::get<2>(GetParam());
- uint32_t serverVersion = std::get<3>(GetParam());
- bool singleThreaded = std::get<4>(GetParam());
- bool noKernel = std::get<5>(GetParam());
+ if (options.numIncomingConnectionsBySession.size() != 0) {
+ LOG_ALWAYS_FATAL_IF(options.numIncomingConnectionsBySession.size() != options.numSessions,
+ "%s: %zu != %zu", __func__,
+ options.numIncomingConnectionsBySession.size(), options.numSessions);
+ }
- std::string path = android::base::GetExecutableDirectory();
- auto servicePath = android::base::StringPrintf("%s/binder_rpc_test_service%s%s", path.c_str(),
- singleThreaded ? "_single_threaded" : "",
- noKernel ? "_no_kernel" : "");
+ SocketType socketType = GetParam().type;
+ RpcSecurity rpcSecurity = GetParam().security;
+ uint32_t clientVersion = GetParam().clientVersion;
+ uint32_t serverVersion = GetParam().serverVersion;
+ bool singleThreaded = GetParam().singleThreaded;
+ bool noKernel = GetParam().noKernel;
- base::unique_fd bootstrapClientFd, socketFd;
+ std::string path = GetExecutableDirectory();
+ auto servicePath =
+ std::format("{}/binder_rpc_test_service{}{}", path,
+ singleThreaded ? "_single_threaded" : "", noKernel ? "_no_kernel" : "");
+
+ unique_fd bootstrapClientFd, socketFd;
auto addr = allocateSocketAddress();
// Initializes the socket before the fork/exec.
@@ -271,14 +276,13 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
} else if (socketType == SocketType::UNIX_BOOTSTRAP) {
// Do not set O_CLOEXEC, bootstrapServerFd needs to survive fork/exec.
// This is because we cannot pass ParcelFileDescriptor over a pipe.
- if (!base::Socketpair(SOCK_STREAM, &bootstrapClientFd, &socketFd)) {
- int savedErrno = errno;
- LOG(FATAL) << "Failed socketpair(): " << strerror(savedErrno);
+ if (!binder::Socketpair(SOCK_STREAM, &bootstrapClientFd, &socketFd)) {
+ PLOGF("Failed socketpair()");
}
}
auto ret = std::make_unique<LinuxProcessSession>(
- Process([=](android::base::borrowed_fd writeEnd, android::base::borrowed_fd readEnd) {
+ Process([=](borrowed_fd writeEnd, borrowed_fd readEnd) {
if (socketType == SocketType::TIPC) {
// Trusty has a single persistent service
return;
@@ -286,8 +290,10 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
auto writeFd = std::to_string(writeEnd.get());
auto readFd = std::to_string(readEnd.get());
- execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(), readFd.c_str(),
- NULL);
+ auto status = execl(servicePath.c_str(), servicePath.c_str(), writeFd.c_str(),
+ readFd.c_str(), NULL);
+ PLOGF("execl('%s', _, %s, %s) should not return at all, but it returned %d",
+ servicePath.c_str(), writeFd.c_str(), readFd.c_str(), status);
}));
BinderRpcTestServerConfig serverConfig;
@@ -311,13 +317,13 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
for (size_t i = 0; i < options.numSessions; i++) {
std::unique_ptr<RpcTransportCtxFactory> factory;
if (socketType == SocketType::TIPC) {
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
factory = RpcTransportCtxFactoryTipcAndroid::make();
#else
LOG_ALWAYS_FATAL("TIPC socket type only supported on vendor");
#endif
} else {
- factory = newFactory(rpcSecurity, certVerifier);
+ factory = newTlsFactory(rpcSecurity, certVerifier);
}
sessions.emplace_back(RpcSession::make(std::move(factory)));
}
@@ -332,25 +338,31 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
}
writeToFd(ret->host.writeEnd(), clientInfo);
- CHECK_LE(serverInfo.port, std::numeric_limits<unsigned int>::max());
+ LOG_ALWAYS_FATAL_IF(serverInfo.port > std::numeric_limits<unsigned int>::max());
if (socketType == SocketType::INET) {
- CHECK_NE(0, serverInfo.port);
+ LOG_ALWAYS_FATAL_IF(0 == serverInfo.port);
}
if (rpcSecurity == RpcSecurity::TLS) {
const auto& serverCert = serverInfo.cert.data;
- CHECK_EQ(OK,
- certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
- serverCert));
+ LOG_ALWAYS_FATAL_IF(
+ OK !=
+ certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM, serverCert));
}
}
status_t status;
- for (const auto& session : sessions) {
- CHECK(session->setProtocolVersion(clientVersion));
- session->setMaxIncomingThreads(options.numIncomingConnections);
- session->setMaxOutgoingThreads(options.numOutgoingConnections);
+ for (size_t i = 0; i < sessions.size(); i++) {
+ const auto& session = sessions.at(i);
+
+ size_t numIncoming = options.numIncomingConnectionsBySession.size() > 0
+ ? options.numIncomingConnectionsBySession.at(i)
+ : 0;
+
+ LOG_ALWAYS_FATAL_IF(!session->setProtocolVersion(clientVersion));
+ session->setMaxIncomingThreads(numIncoming);
+ session->setMaxOutgoingConnections(options.numOutgoingConnections);
session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
switch (socketType) {
@@ -365,7 +377,7 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
break;
case SocketType::UNIX_BOOTSTRAP:
status = session->setupUnixDomainSocketBootstrapClient(
- base::unique_fd(dup(bootstrapClientFd.get())));
+ unique_fd(dup(bootstrapClientFd.get())));
break;
case SocketType::VSOCK:
status = session->setupVsockClient(VMADDR_CID_LOCAL, serverConfig.vsockPort);
@@ -375,14 +387,21 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
break;
case SocketType::TIPC:
status = session->setupPreconnectedClient({}, [=]() {
-#ifdef __ANDROID_VENDOR__
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
auto port = trustyIpcPort(serverVersion);
- int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
- return tipcFd >= 0 ? android::base::unique_fd(tipcFd)
- : android::base::unique_fd();
+ for (size_t i = 0; i < 5; i++) {
+ // Try to connect several times,
+ // in case the service is slow to start
+ int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
+ if (tipcFd >= 0) {
+ return unique_fd(tipcFd);
+ }
+ usleep(50000);
+ }
+ return unique_fd();
#else
LOG_ALWAYS_FATAL("Tried to connect to Trusty outside of vendor");
- return android::base::unique_fd();
+ return unique_fd();
#endif
});
break;
@@ -393,7 +412,7 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
ret->sessions.clear();
break;
}
- CHECK_EQ(status, OK) << "Could not connect: " << statusToString(status);
+ LOG_ALWAYS_FATAL_IF(status != OK, "Could not connect: %s", statusToString(status).c_str());
ret->sessions.push_back({session, session->getRootObject()});
}
return ret;
@@ -435,8 +454,7 @@ TEST_P(BinderRpc, ThreadPoolGreaterThanEqualRequested) {
for (auto& t : ts) t.join();
}
-static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls,
- size_t sleepMs = 500) {
+static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCalls, size_t sleepMs) {
size_t epochMsBefore = epochMillis();
std::vector<std::thread> ts;
@@ -450,8 +468,11 @@ static void testThreadPoolOverSaturated(sp<IBinderRpcTest> iface, size_t numCall
EXPECT_GE(epochMsAfter, epochMsBefore + 2 * sleepMs);
- // Potential flake, but make sure calls are handled in parallel.
- EXPECT_LE(epochMsAfter, epochMsBefore + 3 * sleepMs);
+ // Potential flake, but make sure calls are handled in parallel. Due
+ // to past flakes, this only checks that the amount of time taken has
+ // some parallelism. Other tests such as ThreadPoolGreaterThanEqualRequested
+ // check this more exactly.
+ EXPECT_LE(epochMsAfter, epochMsBefore + (numCalls - 1) * sleepMs);
}
TEST_P(BinderRpc, ThreadPoolOverSaturated) {
@@ -462,7 +483,9 @@ TEST_P(BinderRpc, ThreadPoolOverSaturated) {
constexpr size_t kNumThreads = 10;
constexpr size_t kNumCalls = kNumThreads + 3;
auto proc = createRpcTestSocketServerProcess({.numThreads = kNumThreads});
- testThreadPoolOverSaturated(proc.rootIface, kNumCalls);
+
+ // b/272429574 - below 500ms, the test fails
+ testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
}
TEST_P(BinderRpc, ThreadPoolLimitOutgoing) {
@@ -475,7 +498,9 @@ TEST_P(BinderRpc, ThreadPoolLimitOutgoing) {
constexpr size_t kNumCalls = kNumOutgoingConnections + 3;
auto proc = createRpcTestSocketServerProcess(
{.numThreads = kNumThreads, .numOutgoingConnections = kNumOutgoingConnections});
- testThreadPoolOverSaturated(proc.rootIface, kNumCalls);
+
+ // b/272429574 - below 500ms, the test fails
+ testThreadPoolOverSaturated(proc.rootIface, kNumCalls, 500 /*ms*/);
}
TEST_P(BinderRpc, ThreadingStressTest) {
@@ -483,9 +508,9 @@ TEST_P(BinderRpc, ThreadingStressTest) {
GTEST_SKIP() << "This test requires multiple threads";
}
- constexpr size_t kNumClientThreads = 10;
- constexpr size_t kNumServerThreads = 10;
- constexpr size_t kNumCalls = 100;
+ constexpr size_t kNumClientThreads = 5;
+ constexpr size_t kNumServerThreads = 5;
+ constexpr size_t kNumCalls = 50;
auto proc = createRpcTestSocketServerProcess({.numThreads = kNumServerThreads});
@@ -568,12 +593,12 @@ TEST_P(BinderRpc, OnewayCallQueueingWithFds) {
android::os::ParcelFileDescriptor fdA;
EXPECT_OK(proc.rootIface->blockingRecvFd(&fdA));
std::string result;
- CHECK(android::base::ReadFdToString(fdA.get(), &result));
+ ASSERT_TRUE(ReadFdToString(fdA.get(), &result));
EXPECT_EQ(result, "a");
android::os::ParcelFileDescriptor fdB;
EXPECT_OK(proc.rootIface->blockingRecvFd(&fdB));
- CHECK(android::base::ReadFdToString(fdB.get(), &result));
+ ASSERT_TRUE(ReadFdToString(fdB.get(), &result));
EXPECT_EQ(result, "b");
saturateThreadPool(kNumServerThreads, proc.rootIface);
@@ -584,30 +609,22 @@ TEST_P(BinderRpc, OnewayCallQueueing) {
GTEST_SKIP() << "This test requires multiple threads";
}
- constexpr size_t kNumSleeps = 10;
+ constexpr size_t kNumQueued = 10;
constexpr size_t kNumExtraServerThreads = 4;
- constexpr size_t kSleepMs = 50;
// make sure calls to the same object happen on the same thread
auto proc = createRpcTestSocketServerProcess({.numThreads = 1 + kNumExtraServerThreads});
- EXPECT_OK(proc.rootIface->lock());
-
- size_t epochMsBefore = epochMillis();
-
- // all these *Async commands should be queued on the server sequentially,
+ // all these *Oneway commands should be queued on the server sequentially,
// even though there are multiple threads.
- for (size_t i = 0; i + 1 < kNumSleeps; i++) {
- proc.rootIface->sleepMsAsync(kSleepMs);
+ for (size_t i = 0; i + 1 < kNumQueued; i++) {
+ proc.rootIface->blockingSendIntOneway(i);
+ }
+ for (size_t i = 0; i + 1 < kNumQueued; i++) {
+ int n;
+ proc.rootIface->blockingRecvInt(&n);
+ EXPECT_EQ(n, i);
}
- EXPECT_OK(proc.rootIface->unlockInMsAsync(kSleepMs));
-
- // this can only return once the final async call has unlocked
- EXPECT_OK(proc.rootIface->lockUnlock());
-
- size_t epochMsAfter = epochMillis();
-
- EXPECT_GE(epochMsAfter, epochMsBefore + kSleepMs * kNumSleeps);
saturateThreadPool(1 + kNumExtraServerThreads, proc.rootIface);
}
@@ -656,6 +673,38 @@ TEST_P(BinderRpc, OnewayCallExhaustion) {
proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
}
+TEST_P(BinderRpc, SessionWithIncomingThreadpoolDoesntLeak) {
+ if (clientOrServerSingleThreaded()) {
+ GTEST_SKIP() << "This test requires multiple threads";
+ }
+
+ // session 0 - will check for leaks in destrutor of proc
+ // session 1 - we want to make sure it gets deleted when we drop all references to it
+ auto proc = createRpcTestSocketServerProcess(
+ {.numThreads = 1, .numSessions = 2, .numIncomingConnectionsBySession = {0, 1}});
+
+ wp<RpcSession> session = proc.proc->sessions.at(1).session;
+
+ // remove all references to the second session
+ proc.proc->sessions.at(1).root = nullptr;
+ proc.proc->sessions.erase(proc.proc->sessions.begin() + 1);
+
+ // TODO(b/271830568) more efficient way to wait for other incoming threadpool
+ // to drain commands.
+ for (size_t i = 0; i < 100; i++) {
+ usleep(10 * 1000);
+ if (session.promote() == nullptr) break;
+ }
+
+ EXPECT_EQ(nullptr, session.promote());
+
+ // now that it has died, wait for the remote session to shutdown
+ std::vector<int32_t> remoteCounts;
+ do {
+ EXPECT_OK(proc.rootIface->countBinders(&remoteCounts));
+ } while (remoteCounts.size() > 1);
+}
+
TEST_P(BinderRpc, SingleDeathRecipient) {
if (clientOrServerSingleThreaded()) {
GTEST_SKIP() << "This test requires multiple threads";
@@ -673,7 +722,7 @@ TEST_P(BinderRpc, SingleDeathRecipient) {
// Death recipient needs to have an incoming connection to be called
auto proc = createRpcTestSocketServerProcess(
- {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 1});
+ {.numThreads = 1, .numSessions = 1, .numIncomingConnectionsBySession = {1}});
auto dr = sp<MyDeathRec>::make();
ASSERT_EQ(OK, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
@@ -686,6 +735,10 @@ TEST_P(BinderRpc, SingleDeathRecipient) {
ASSERT_TRUE(dr->mCv.wait_for(lock, 100ms, [&]() { return dr->dead; }));
// need to wait for the session to shutdown so we don't "Leak session"
+ // can't do this before checking the death recipient by calling
+ // forceShutdown earlier, because shutdownAndWait will also trigger
+ // a death recipient, but if we had a way to wait for the service
+ // to gracefully shutdown, we could use that here.
EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
proc.expectAlreadyShutdown = true;
}
@@ -707,7 +760,7 @@ TEST_P(BinderRpc, SingleDeathRecipientOnShutdown) {
// Death recipient needs to have an incoming connection to be called
auto proc = createRpcTestSocketServerProcess(
- {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 1});
+ {.numThreads = 1, .numSessions = 1, .numIncomingConnectionsBySession = {1}});
auto dr = sp<MyDeathRec>::make();
EXPECT_EQ(OK, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
@@ -740,8 +793,7 @@ TEST_P(BinderRpc, DeathRecipientFailsWithoutIncoming) {
void binderDied(const wp<IBinder>& /* who */) override {}
};
- auto proc = createRpcTestSocketServerProcess(
- {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 0});
+ auto proc = createRpcTestSocketServerProcess({.numThreads = 1, .numSessions = 1});
auto dr = sp<MyDeathRec>::make();
EXPECT_EQ(INVALID_OPERATION, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
@@ -760,19 +812,13 @@ TEST_P(BinderRpc, UnlinkDeathRecipient) {
// Death recipient needs to have an incoming connection to be called
auto proc = createRpcTestSocketServerProcess(
- {.numThreads = 1, .numSessions = 1, .numIncomingConnections = 1});
+ {.numThreads = 1, .numSessions = 1, .numIncomingConnectionsBySession = {1}});
auto dr = sp<MyDeathRec>::make();
ASSERT_EQ(OK, proc.rootBinder->linkToDeath(dr, (void*)1, 0));
ASSERT_EQ(OK, proc.rootBinder->unlinkToDeath(dr, (void*)1, 0, nullptr));
- if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
- EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
- }
-
- // need to wait for the session to shutdown so we don't "Leak session"
- EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
- proc.expectAlreadyShutdown = true;
+ proc.forceShutdown();
}
TEST_P(BinderRpc, Die) {
@@ -909,8 +955,8 @@ TEST_P(BinderRpc, ReceiveFile) {
ASSERT_TRUE(status.isOk()) << status;
std::string result;
- CHECK(android::base::ReadFdToString(out.get(), &result));
- EXPECT_EQ(result, "hello");
+ ASSERT_TRUE(ReadFdToString(out.get(), &result));
+ ASSERT_EQ(result, "hello");
}
TEST_P(BinderRpc, SendFiles) {
@@ -939,7 +985,7 @@ TEST_P(BinderRpc, SendFiles) {
ASSERT_TRUE(status.isOk()) << status;
std::string result;
- CHECK(android::base::ReadFdToString(out.get(), &result));
+ EXPECT_TRUE(ReadFdToString(out.get(), &result));
EXPECT_EQ(result, "123abcd");
}
@@ -964,7 +1010,7 @@ TEST_P(BinderRpc, SendMaxFiles) {
ASSERT_TRUE(status.isOk()) << status;
std::string result;
- CHECK(android::base::ReadFdToString(out.get(), &result));
+ EXPECT_TRUE(ReadFdToString(out.get(), &result));
EXPECT_EQ(result, std::string(253, 'a'));
}
@@ -1081,13 +1127,42 @@ TEST_P(BinderRpc, Fds) {
ASSERT_EQ(beforeFds, countFds()) << (system("ls -l /proc/self/fd/"), "fd leak?");
}
-static bool testSupportVsockLoopback() {
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
+
+static std::vector<BinderRpc::ParamType> getTrustyBinderRpcParams() {
+ std::vector<BinderRpc::ParamType> ret;
+
+ for (const auto& clientVersion : testVersions()) {
+ for (const auto& serverVersion : testVersions()) {
+ ret.push_back(BinderRpc::ParamType{
+ .type = SocketType::TIPC,
+ .security = RpcSecurity::RAW,
+ .clientVersion = clientVersion,
+ .serverVersion = serverVersion,
+ .singleThreaded = true,
+ .noKernel = true,
+ });
+ }
+ }
+
+ return ret;
+}
+
+INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc, ::testing::ValuesIn(getTrustyBinderRpcParams()),
+ BinderRpc::PrintParamInfo);
+#else // BINDER_RPC_TO_TRUSTY_TEST
+bool testSupportVsockLoopback() {
// We don't need to enable TLS to know if vsock is supported.
unsigned int vsockPort = allocateVsockPort();
- android::base::unique_fd serverFd(
+ unique_fd serverFd(
TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
- LOG_ALWAYS_FATAL_IF(serverFd == -1, "Could not create socket: %s", strerror(errno));
+
+ if (errno == EAFNOSUPPORT) {
+ return false;
+ }
+
+ LOG_ALWAYS_FATAL_IF(!serverFd.ok(), "Could not create socket: %s", strerror(errno));
sockaddr_vm serverAddr{
.svm_family = AF_VSOCK,
@@ -1107,9 +1182,9 @@ static bool testSupportVsockLoopback() {
// to see if the kernel supports it. It's safe to use a blocking
// connect because vsock sockets have a 2 second connection timeout,
// and they return ETIMEDOUT after that.
- android::base::unique_fd connectFd(
+ unique_fd connectFd(
TEMP_FAILURE_RETRY(socket(AF_VSOCK, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0)));
- LOG_ALWAYS_FATAL_IF(connectFd == -1, "Could not create socket for port %u: %s", vsockPort,
+ LOG_ALWAYS_FATAL_IF(!connectFd.ok(), "Could not create socket for port %u: %s", vsockPort,
strerror(errno));
bool success = false;
@@ -1121,13 +1196,13 @@ static bool testSupportVsockLoopback() {
ret = TEMP_FAILURE_RETRY(connect(connectFd.get(), reinterpret_cast<sockaddr*>(&connectAddr),
sizeof(connectAddr)));
if (ret != 0 && (errno == EAGAIN || errno == EINPROGRESS)) {
- android::base::unique_fd acceptFd;
+ unique_fd acceptFd;
while (true) {
pollfd pfd[]{
{.fd = serverFd.get(), .events = POLLIN, .revents = 0},
{.fd = connectFd.get(), .events = POLLOUT, .revents = 0},
};
- ret = TEMP_FAILURE_RETRY(poll(pfd, arraysize(pfd), -1));
+ ret = TEMP_FAILURE_RETRY(poll(pfd, countof(pfd), -1));
LOG_ALWAYS_FATAL_IF(ret < 0, "Error polling: %s", strerror(errno));
if (pfd[0].revents & POLLIN) {
@@ -1176,7 +1251,15 @@ static std::vector<SocketType> testSocketTypes(bool hasPreconnected = true) {
if (hasPreconnected) ret.push_back(SocketType::PRECONNECTED);
+#ifdef __BIONIC__
+ // Devices may not have vsock support. AVF tests will verify whether they do, but
+ // we can't require it due to old kernels for the time being.
static bool hasVsockLoopback = testSupportVsockLoopback();
+#else
+ // On host machines, we always assume we have vsock loopback. If we don't, the
+ // subsequent failures will be more clear than showing one now.
+ static bool hasVsockLoopback = true;
+#endif
if (hasVsockLoopback) {
ret.push_back(SocketType::VSOCK);
@@ -1185,36 +1268,47 @@ static std::vector<SocketType> testSocketTypes(bool hasPreconnected = true) {
return ret;
}
-static std::vector<SocketType> testTipcSocketTypes() {
-#ifdef __ANDROID_VENDOR__
- auto port = trustyIpcPort(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
- int tipcFd = tipc_connect(kTrustyIpcDevice, port.c_str());
- if (tipcFd >= 0) {
- close(tipcFd);
- return {SocketType::TIPC};
+static std::vector<BinderRpc::ParamType> getBinderRpcParams() {
+ std::vector<BinderRpc::ParamType> ret;
+
+ constexpr bool full = false;
+
+ for (const auto& type : testSocketTypes()) {
+ if (full || type == SocketType::UNIX) {
+ for (const auto& security : RpcSecurityValues()) {
+ for (const auto& clientVersion : testVersions()) {
+ for (const auto& serverVersion : testVersions()) {
+ for (bool singleThreaded : {false, true}) {
+ for (bool noKernel : {false, true}) {
+ ret.push_back(BinderRpc::ParamType{
+ .type = type,
+ .security = security,
+ .clientVersion = clientVersion,
+ .serverVersion = serverVersion,
+ .singleThreaded = singleThreaded,
+ .noKernel = noKernel,
+ });
+ }
+ }
+ }
+ }
+ }
+ } else {
+ ret.push_back(BinderRpc::ParamType{
+ .type = type,
+ .security = RpcSecurity::RAW,
+ .clientVersion = RPC_WIRE_PROTOCOL_VERSION,
+ .serverVersion = RPC_WIRE_PROTOCOL_VERSION,
+ .singleThreaded = false,
+ .noKernel = false,
+ });
+ }
}
-#endif // __ANDROID_VENDOR__
- // TIPC is not supported on this device, most likely
- // because /dev/trusty-ipc-dev0 is missing
- return {};
+ return ret;
}
-INSTANTIATE_TEST_CASE_P(PerSocket, BinderRpc,
- ::testing::Combine(::testing::ValuesIn(testSocketTypes()),
- ::testing::ValuesIn(RpcSecurityValues()),
- ::testing::ValuesIn(testVersions()),
- ::testing::ValuesIn(testVersions()),
- ::testing::Values(false, true),
- ::testing::Values(false, true)),
- BinderRpc::PrintParamInfo);
-
-INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc,
- ::testing::Combine(::testing::ValuesIn(testTipcSocketTypes()),
- ::testing::Values(RpcSecurity::RAW),
- ::testing::ValuesIn(testVersions()),
- ::testing::ValuesIn(testVersions()),
- ::testing::Values(true), ::testing::Values(true)),
+INSTANTIATE_TEST_CASE_P(PerSocket, BinderRpc, ::testing::ValuesIn(getBinderRpcParams()),
BinderRpc::PrintParamInfo);
class BinderRpcServerRootObject
@@ -1227,7 +1321,7 @@ TEST_P(BinderRpcServerRootObject, WeakRootObject) {
};
auto [isStrong1, isStrong2, rpcSecurity] = GetParam();
- auto server = RpcServer::make(newFactory(rpcSecurity));
+ auto server = RpcServer::make(newTlsFactory(rpcSecurity));
auto binder1 = sp<BBinder>::make();
IBinder* binderRaw1 = binder1.get();
setRootObject(isStrong1)(server.get(), binder1);
@@ -1269,7 +1363,11 @@ private:
};
TEST(BinderRpc, Java) {
-#if !defined(__ANDROID__)
+ bool expectDebuggable = false;
+#if defined(__ANDROID__)
+ expectDebuggable = android::base::GetBoolProperty("ro.debuggable", false) &&
+ android::base::GetProperty("ro.build.type", "") != "user";
+#else
GTEST_SKIP() << "This test is only run on Android. Though it can technically run on host on"
"createRpcDelegateServiceManager() with a device attached, such test belongs "
"to binderHostDeviceTest. Hence, just disable this test on host.";
@@ -1297,8 +1395,7 @@ TEST(BinderRpc, Java) {
auto keepAlive = sp<BBinder>::make();
auto setRpcClientDebugStatus = binder->setRpcClientDebug(std::move(socket), keepAlive);
- if (!android::base::GetBoolProperty("ro.debuggable", false) ||
- android::base::GetProperty("ro.build.type", "") == "user") {
+ if (!expectDebuggable) {
ASSERT_EQ(INVALID_OPERATION, setRpcClientDebugStatus)
<< "setRpcClientDebug should return INVALID_OPERATION on non-debuggable or user "
"builds, but get "
@@ -1323,20 +1420,20 @@ TEST(BinderRpc, Java) {
class BinderRpcServerOnly : public ::testing::TestWithParam<std::tuple<RpcSecurity, uint32_t>> {
public:
static std::string PrintTestParam(const ::testing::TestParamInfo<ParamType>& info) {
- return std::string(newFactory(std::get<0>(info.param))->toCString()) + "_serverV" +
+ return std::string(newTlsFactory(std::get<0>(info.param))->toCString()) + "_serverV" +
std::to_string(std::get<1>(info.param));
}
};
TEST_P(BinderRpcServerOnly, SetExternalServerTest) {
- base::unique_fd sink(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)));
+ unique_fd sink(TEMP_FAILURE_RETRY(open("/dev/null", O_RDWR)));
int sinkFd = sink.get();
- auto server = RpcServer::make(newFactory(std::get<0>(GetParam())));
- server->setProtocolVersion(std::get<1>(GetParam()));
+ auto server = RpcServer::make(newTlsFactory(std::get<0>(GetParam())));
+ ASSERT_TRUE(server->setProtocolVersion(std::get<1>(GetParam())));
ASSERT_FALSE(server->hasServer());
ASSERT_EQ(OK, server->setupExternalServer(std::move(sink)));
ASSERT_TRUE(server->hasServer());
- base::unique_fd retrieved = server->releaseServer();
+ unique_fd retrieved = server->releaseServer();
ASSERT_FALSE(server->hasServer());
ASSERT_EQ(sinkFd, retrieved.get());
}
@@ -1347,8 +1444,8 @@ TEST_P(BinderRpcServerOnly, Shutdown) {
}
auto addr = allocateSocketAddress();
- auto server = RpcServer::make(newFactory(std::get<0>(GetParam())));
- server->setProtocolVersion(std::get<1>(GetParam()));
+ auto server = RpcServer::make(newTlsFactory(std::get<0>(GetParam())));
+ ASSERT_TRUE(server->setProtocolVersion(std::get<1>(GetParam())));
ASSERT_EQ(OK, server->setupUnixDomainServer(addr.c_str()));
auto joinEnds = std::make_shared<OneOffSignal>();
@@ -1382,12 +1479,12 @@ public:
// in the client half of the tests.
using Param =
std::tuple<SocketType, RpcSecurity, std::optional<RpcCertificateFormat>, uint32_t>;
- using ConnectToServer = std::function<base::unique_fd()>;
+ using ConnectToServer = std::function<unique_fd()>;
// A server that handles client socket connections.
class Server {
public:
- using AcceptConnection = std::function<base::unique_fd(Server*)>;
+ using AcceptConnection = std::function<unique_fd(Server*)>;
explicit Server() {}
Server(Server&&) = default;
@@ -1396,8 +1493,10 @@ public:
const Param& param,
std::unique_ptr<RpcAuth> auth = std::make_unique<RpcAuthSelfSigned>()) {
auto [socketType, rpcSecurity, certificateFormat, serverVersion] = param;
- auto rpcServer = RpcServer::make(newFactory(rpcSecurity));
- rpcServer->setProtocolVersion(serverVersion);
+ auto rpcServer = RpcServer::make(newTlsFactory(rpcSecurity));
+ if (!rpcServer->setProtocolVersion(serverVersion)) {
+ return AssertionFailure() << "Invalid protocol version: " << serverVersion;
+ }
switch (socketType) {
case SocketType::PRECONNECTED: {
return AssertionFailure() << "Not supported by this test";
@@ -1414,8 +1513,8 @@ public:
};
} break;
case SocketType::UNIX_BOOTSTRAP: {
- base::unique_fd bootstrapFdClient, bootstrapFdServer;
- if (!base::Socketpair(SOCK_STREAM, &bootstrapFdClient, &bootstrapFdServer)) {
+ unique_fd bootstrapFdClient, bootstrapFdServer;
+ if (!binder::Socketpair(SOCK_STREAM, &bootstrapFdClient, &bootstrapFdServer)) {
return AssertionFailure() << "Socketpair() failed";
}
auto status = rpcServer->setupUnixDomainSocketBootstrapServer(
@@ -1458,7 +1557,7 @@ public:
mConnectToServer = [port] {
const char* addr = kLocalInetAddress;
auto aiStart = InetSocketAddress::getAddrInfo(addr, port);
- if (aiStart == nullptr) return base::unique_fd{};
+ if (aiStart == nullptr) return unique_fd{};
for (auto ai = aiStart.get(); ai != nullptr; ai = ai->ai_next) {
auto fd = connectTo(
InetSocketAddress(ai->ai_addr, ai->ai_addrlen, addr, port));
@@ -1466,7 +1565,7 @@ public:
}
ALOGE("None of the socket address resolved for %s:%u can be connected",
addr, port);
- return base::unique_fd{};
+ return unique_fd{};
};
} break;
case SocketType::TIPC: {
@@ -1475,7 +1574,7 @@ public:
}
mFd = rpcServer->releaseServer();
if (!mFd.fd.ok()) return AssertionFailure() << "releaseServer returns invalid fd";
- mCtx = newFactory(rpcSecurity, mCertVerifier, std::move(auth))->newServerCtx();
+ mCtx = newTlsFactory(rpcSecurity, mCertVerifier, std::move(auth))->newServerCtx();
if (mCtx == nullptr) return AssertionFailure() << "newServerCtx";
mSetup = true;
return AssertionSuccess();
@@ -1490,24 +1589,22 @@ public:
mThread = std::make_unique<std::thread>(&Server::run, this);
}
- base::unique_fd acceptServerConnection() {
- return base::unique_fd(TEMP_FAILURE_RETRY(
+ unique_fd acceptServerConnection() {
+ return unique_fd(TEMP_FAILURE_RETRY(
accept4(mFd.fd.get(), nullptr, nullptr, SOCK_CLOEXEC | SOCK_NONBLOCK)));
}
- base::unique_fd recvmsgServerConnection() {
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>> fds;
+ unique_fd recvmsgServerConnection() {
+ std::vector<std::variant<unique_fd, borrowed_fd>> fds;
int buf;
iovec iov{&buf, sizeof(buf)};
- if (receiveMessageFromSocket(mFd, &iov, 1, &fds) < 0) {
- int savedErrno = errno;
- LOG(FATAL) << "Failed receiveMessage: " << strerror(savedErrno);
- }
- if (fds.size() != 1) {
- LOG(FATAL) << "Expected one FD from receiveMessage(), got " << fds.size();
+ if (binder::os::receiveMessageFromSocket(mFd, &iov, 1, &fds) < 0) {
+ PLOGF("Failed receiveMessage");
}
- return std::move(std::get<base::unique_fd>(fds[0]));
+ LOG_ALWAYS_FATAL_IF(fds.size() != 1, "Expected one FD from receiveMessage(), got %zu",
+ fds.size());
+ return std::move(std::get<unique_fd>(fds[0]));
}
void run() {
@@ -1515,13 +1612,13 @@ public:
std::vector<std::thread> threads;
while (OK == mFdTrigger->triggerablePoll(mFd, POLLIN)) {
- base::unique_fd acceptedFd = mAcceptConnection(this);
+ unique_fd acceptedFd = mAcceptConnection(this);
threads.emplace_back(&Server::handleOne, this, std::move(acceptedFd));
}
for (auto& thread : threads) thread.join();
}
- void handleOne(android::base::unique_fd acceptedFd) {
+ void handleOne(unique_fd acceptedFd) {
ASSERT_TRUE(acceptedFd.ok());
RpcTransportFd transportFd(std::move(acceptedFd));
auto serverTransport = mCtx->newTransport(std::move(transportFd), mFdTrigger.get());
@@ -1580,7 +1677,7 @@ public:
auto [socketType, rpcSecurity, certificateFormat, serverVersion] = param;
(void)serverVersion;
mFdTrigger = FdTrigger::make();
- mCtx = newFactory(rpcSecurity, mCertVerifier)->newClientCtx();
+ mCtx = newTlsFactory(rpcSecurity, mCertVerifier)->newClientCtx();
if (mCtx == nullptr) return AssertionFailure() << "newClientCtx";
return AssertionSuccess();
}
@@ -1652,7 +1749,7 @@ public:
using Client = RpcTransportTestUtils::Client;
static inline std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
auto [socketType, rpcSecurity, certificateFormat, serverVersion] = info.param;
- auto ret = PrintToString(socketType) + "_" + newFactory(rpcSecurity)->toCString();
+ auto ret = PrintToString(socketType) + "_" + newTlsFactory(rpcSecurity)->toCString();
if (certificateFormat.has_value()) ret += "_" + PrintToString(*certificateFormat);
ret += "_serverV" + std::to_string(serverVersion);
return ret;
@@ -1985,12 +2082,13 @@ INSTANTIATE_TEST_CASE_P(
testing::Values(RpcKeyFormat::PEM, RpcKeyFormat::DER),
testing::ValuesIn(testVersions())),
RpcTransportTlsKeyTest::PrintParamInfo);
+#endif // BINDER_RPC_TO_TRUSTY_TEST
} // namespace android
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
- android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+ __android_log_set_logger(__android_log_stderr_logger);
return RUN_ALL_TESTS();
}
diff --git a/libs/binder/tests/binderRpcTestCommon.h b/libs/binder/tests/binderRpcTestCommon.h
index a467ee389b..8832f1a6ba 100644
--- a/libs/binder/tests/binderRpcTestCommon.h
+++ b/libs/binder/tests/binderRpcTestCommon.h
@@ -22,7 +22,6 @@
#include <BnBinderRpcCallback.h>
#include <BnBinderRpcSession.h>
#include <BnBinderRpcTest.h>
-#include <android-base/stringprintf.h>
#include <binder/Binder.h>
#include <binder/BpBinder.h>
#include <binder/IPCThreadState.h>
@@ -37,10 +36,11 @@
#include <string>
#include <vector>
-#ifndef __TRUSTY__
-#include <android-base/file.h>
-#include <android-base/logging.h>
+#ifdef __ANDROID__
#include <android-base/properties.h>
+#endif
+
+#ifndef __TRUSTY__
#include <android/binder_auto_utils.h>
#include <android/binder_libbinder.h>
#include <binder/ProcessState.h>
@@ -57,7 +57,10 @@
#include "../BuildFlags.h"
#include "../FdTrigger.h"
+#include "../FdUtils.h"
#include "../RpcState.h" // for debugging
+#include "FileUtils.h"
+#include "format.h"
#include "utils/Errors.h"
namespace android {
@@ -70,17 +73,33 @@ static inline std::vector<RpcSecurity> RpcSecurityValues() {
return {RpcSecurity::RAW, RpcSecurity::TLS};
}
+static inline bool hasExperimentalRpc() {
+#ifdef BINDER_RPC_TO_TRUSTY_TEST
+ // Trusty services do not support the experimental version,
+ // so that we can update the prebuilts separately.
+ // This covers the binderRpcToTrustyTest case on Android.
+ return false;
+#endif
+#ifdef __ANDROID__
+ return base::GetProperty("ro.build.version.codename", "") != "REL";
+#else
+ return false;
+#endif
+}
+
static inline std::vector<uint32_t> testVersions() {
std::vector<uint32_t> versions;
for (size_t i = 0; i < RPC_WIRE_PROTOCOL_VERSION_NEXT; i++) {
versions.push_back(i);
}
- versions.push_back(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+ if (hasExperimentalRpc()) {
+ versions.push_back(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
+ }
return versions;
}
static inline std::string trustyIpcPort(uint32_t serverVersion) {
- return base::StringPrintf("com.android.trusty.binderRpcTestService.V%" PRIu32, serverVersion);
+ return std::format("com.android.trusty.binderRpcTestService.V{}", serverVersion);
}
enum class SocketType {
@@ -126,7 +145,11 @@ static inline size_t epochMillis() {
struct BinderRpcOptions {
size_t numThreads = 1;
size_t numSessions = 1;
- size_t numIncomingConnections = 0;
+ // right now, this can be empty, or length numSessions, where each value
+ // represents the info for the corresponding session, but we should
+ // probably switch this to be a list of sessions options so that other
+ // options can all be specified per session
+ std::vector<size_t> numIncomingConnectionsBySession = {};
size_t numOutgoingConnections = SIZE_MAX;
RpcSession::FileDescriptorTransportMode clientFileDescriptorTransportMode =
RpcSession::FileDescriptorTransportMode::NONE;
@@ -140,37 +163,38 @@ struct BinderRpcOptions {
};
#ifndef __TRUSTY__
-static inline void writeString(android::base::borrowed_fd fd, std::string_view str) {
+static inline void writeString(binder::borrowed_fd fd, std::string_view str) {
uint64_t length = str.length();
- CHECK(android::base::WriteFully(fd, &length, sizeof(length)));
- CHECK(android::base::WriteFully(fd, str.data(), str.length()));
+ LOG_ALWAYS_FATAL_IF(!android::binder::WriteFully(fd, &length, sizeof(length)));
+ LOG_ALWAYS_FATAL_IF(!android::binder::WriteFully(fd, str.data(), str.length()));
}
-static inline std::string readString(android::base::borrowed_fd fd) {
+static inline std::string readString(binder::borrowed_fd fd) {
uint64_t length;
- CHECK(android::base::ReadFully(fd, &length, sizeof(length)));
+ LOG_ALWAYS_FATAL_IF(!android::binder::ReadFully(fd, &length, sizeof(length)));
std::string ret(length, '\0');
- CHECK(android::base::ReadFully(fd, ret.data(), length));
+ LOG_ALWAYS_FATAL_IF(!android::binder::ReadFully(fd, ret.data(), length));
return ret;
}
-static inline void writeToFd(android::base::borrowed_fd fd, const Parcelable& parcelable) {
+static inline void writeToFd(binder::borrowed_fd fd, const Parcelable& parcelable) {
Parcel parcel;
- CHECK_EQ(OK, parcelable.writeToParcel(&parcel));
+ LOG_ALWAYS_FATAL_IF(OK != parcelable.writeToParcel(&parcel));
writeString(fd, std::string(reinterpret_cast<const char*>(parcel.data()), parcel.dataSize()));
}
template <typename T>
-static inline T readFromFd(android::base::borrowed_fd fd) {
+static inline T readFromFd(binder::borrowed_fd fd) {
std::string data = readString(fd);
Parcel parcel;
- CHECK_EQ(OK, parcel.setData(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
+ LOG_ALWAYS_FATAL_IF(OK !=
+ parcel.setData(reinterpret_cast<const uint8_t*>(data.data()), data.size()));
T object;
- CHECK_EQ(OK, object.readFromParcel(&parcel));
+ LOG_ALWAYS_FATAL_IF(OK != object.readFromParcel(&parcel));
return object;
}
-static inline std::unique_ptr<RpcTransportCtxFactory> newFactory(
+static inline std::unique_ptr<RpcTransportCtxFactory> newTlsFactory(
RpcSecurity rpcSecurity, std::shared_ptr<RpcCertificateVerifier> verifier = nullptr,
std::unique_ptr<RpcAuth> auth = nullptr) {
switch (rpcSecurity) {
@@ -191,12 +215,12 @@ static inline std::unique_ptr<RpcTransportCtxFactory> newFactory(
}
// Create an FD that returns `contents` when read.
-static inline base::unique_fd mockFileDescriptor(std::string contents) {
- android::base::unique_fd readFd, writeFd;
- CHECK(android::base::Pipe(&readFd, &writeFd)) << strerror(errno);
+static inline binder::unique_fd mockFileDescriptor(std::string contents) {
+ binder::unique_fd readFd, writeFd;
+ LOG_ALWAYS_FATAL_IF(!binder::Pipe(&readFd, &writeFd), "%s", strerror(errno));
RpcMaybeThread([writeFd = std::move(writeFd), contents = std::move(contents)]() {
signal(SIGPIPE, SIG_IGN); // ignore possible SIGPIPE from the write
- if (!WriteStringToFd(contents, writeFd)) {
+ if (!android::binder::WriteStringToFd(contents, writeFd)) {
int savedErrno = errno;
LOG_ALWAYS_FATAL_IF(EPIPE != savedErrno, "mockFileDescriptor write failed: %s",
strerror(savedErrno));
@@ -375,7 +399,7 @@ public:
}
if (delayed) {
- RpcMaybeThread([=]() {
+ RpcMaybeThread([=, this]() {
ALOGE("Executing delayed callback: '%s'", value.c_str());
Status status = doCallback(callback, oneway, false, value);
ALOGE("Delayed callback status: '%s'", status.toString8().c_str());
@@ -445,6 +469,12 @@ public:
Status blockingRecvFd(android::os::ParcelFileDescriptor* /*fd*/) override {
return Status::fromStatusT(UNKNOWN_TRANSACTION);
}
+
+ Status blockingSendIntOneway(int /*n*/) override {
+ return Status::fromStatusT(UNKNOWN_TRANSACTION);
+ }
+
+ Status blockingRecvInt(int* /*n*/) override { return Status::fromStatusT(UNKNOWN_TRANSACTION); }
};
} // namespace android
diff --git a/libs/binder/tests/binderRpcTestFixture.h b/libs/binder/tests/binderRpcTestFixture.h
index c99d68aacf..2c9646b30e 100644
--- a/libs/binder/tests/binderRpcTestFixture.h
+++ b/libs/binder/tests/binderRpcTestFixture.h
@@ -64,6 +64,22 @@ struct BinderRpcTestProcessSession {
// whether session should be invalidated by end of run
bool expectAlreadyShutdown = false;
+ // TODO(b/271830568): fix this in binderRpcTest, we always use the first session to cause the
+ // remote process to shutdown. Normally, when we shutdown, the default in the destructor is to
+ // check that there are no leaks and shutdown. However, when there are incoming threadpools,
+ // there will be a few extra binder threads there, so we can't shutdown the server. We should
+ // consider an alternative way of doing the test so that we don't need this, some ideas, such as
+ // program in understanding of incoming threadpool into the destructor so that (e.g.
+ // intelligently wait for sessions to shutdown now that they will do this)
+ void forceShutdown() {
+ if (auto status = rootIface->scheduleShutdown(); !status.isOk()) {
+ EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
+ }
+ EXPECT_TRUE(proc->sessions.at(0).session->shutdownAndWait(true));
+ expectAlreadyShutdown = true;
+ }
+
+ BinderRpcTestProcessSession(std::unique_ptr<ProcessSession> proc) : proc(std::move(proc)){};
BinderRpcTestProcessSession(BinderRpcTestProcessSession&&) = default;
~BinderRpcTestProcessSession() {
if (!expectAlreadyShutdown) {
@@ -90,15 +106,23 @@ struct BinderRpcTestProcessSession {
}
};
-class BinderRpc : public ::testing::TestWithParam<
- std::tuple<SocketType, RpcSecurity, uint32_t, uint32_t, bool, bool>> {
+struct BinderRpcParam {
+ SocketType type;
+ RpcSecurity security;
+ uint32_t clientVersion;
+ uint32_t serverVersion;
+ bool singleThreaded;
+ bool noKernel;
+};
+class BinderRpc : public ::testing::TestWithParam<BinderRpcParam> {
public:
- SocketType socketType() const { return std::get<0>(GetParam()); }
- RpcSecurity rpcSecurity() const { return std::get<1>(GetParam()); }
- uint32_t clientVersion() const { return std::get<2>(GetParam()); }
- uint32_t serverVersion() const { return std::get<3>(GetParam()); }
- bool serverSingleThreaded() const { return std::get<4>(GetParam()); }
- bool noKernel() const { return std::get<5>(GetParam()); }
+ // TODO: avoid unnecessary layer of indirection
+ SocketType socketType() const { return GetParam().type; }
+ RpcSecurity rpcSecurity() const { return GetParam().security; }
+ uint32_t clientVersion() const { return GetParam().clientVersion; }
+ uint32_t serverVersion() const { return GetParam().serverVersion; }
+ bool serverSingleThreaded() const { return GetParam().singleThreaded; }
+ bool noKernel() const { return GetParam().noKernel; }
bool clientOrServerSingleThreaded() const {
return !kEnableRpcThreads || serverSingleThreaded();
@@ -123,9 +147,7 @@ public:
}
BinderRpcTestProcessSession createRpcTestSocketServerProcess(const BinderRpcOptions& options) {
- BinderRpcTestProcessSession ret{
- .proc = createRpcTestSocketServerProcessEtc(options),
- };
+ BinderRpcTestProcessSession ret(createRpcTestSocketServerProcessEtc(options));
ret.rootBinder = ret.proc->sessions.empty() ? nullptr : ret.proc->sessions.at(0).root;
ret.rootIface = interface_cast<IBinderRpcTest>(ret.rootBinder);
@@ -133,9 +155,27 @@ public:
return ret;
}
- static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info);
+ static std::string PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
+ auto ret = PrintToString(info.param.type) + "_" +
+ newFactory(info.param.security)->toCString() + "_clientV" +
+ std::to_string(info.param.clientVersion) + "_serverV" +
+ std::to_string(info.param.serverVersion);
+ if (info.param.singleThreaded) {
+ ret += "_single_threaded";
+ } else {
+ ret += "_multi_threaded";
+ }
+ if (info.param.noKernel) {
+ ret += "_no_kernel";
+ } else {
+ ret += "_with_kernel";
+ }
+ return ret;
+ }
protected:
+ static std::unique_ptr<RpcTransportCtxFactory> newFactory(RpcSecurity rpcSecurity);
+
std::unique_ptr<ProcessSession> createRpcTestSocketServerProcessEtc(
const BinderRpcOptions& options);
};
diff --git a/libs/binder/tests/binderRpcTestService.cpp b/libs/binder/tests/binderRpcTestService.cpp
index a27bd2f2e6..28125f169c 100644
--- a/libs/binder/tests/binderRpcTestService.cpp
+++ b/libs/binder/tests/binderRpcTestService.cpp
@@ -17,6 +17,8 @@
#include "binderRpcTestCommon.h"
using namespace android;
+using android::binder::ReadFdToString;
+using android::binder::unique_fd;
class MyBinderRpcTestAndroid : public MyBinderRpcTestBase {
public:
@@ -65,17 +67,17 @@ public:
std::string acc;
for (const auto& file : files) {
std::string result;
- CHECK(android::base::ReadFdToString(file.get(), &result));
+ LOG_ALWAYS_FATAL_IF(!ReadFdToString(file.get(), &result));
acc.append(result);
}
out->reset(mockFileDescriptor(acc));
return Status::ok();
}
- HandoffChannel<android::base::unique_fd> mFdChannel;
+ HandoffChannel<unique_fd> mFdChannel;
Status blockingSendFdOneway(const android::os::ParcelFileDescriptor& fd) override {
- mFdChannel.write(android::base::unique_fd(fcntl(fd.get(), F_DUPFD_CLOEXEC, 0)));
+ mFdChannel.write(unique_fd(fcntl(fd.get(), F_DUPFD_CLOEXEC, 0)));
return Status::ok();
}
@@ -83,14 +85,26 @@ public:
fd->reset(mFdChannel.read());
return Status::ok();
}
+
+ HandoffChannel<int> mIntChannel;
+
+ Status blockingSendIntOneway(int n) override {
+ mIntChannel.write(n);
+ return Status::ok();
+ }
+
+ Status blockingRecvInt(int* n) override {
+ *n = mIntChannel.read();
+ return Status::ok();
+ }
};
int main(int argc, char* argv[]) {
- android::base::InitLogging(argv, android::base::StderrLogger, android::base::DefaultAborter);
+ __android_log_set_logger(__android_log_stderr_logger);
LOG_ALWAYS_FATAL_IF(argc != 3, "Invalid number of arguments: %d", argc);
- base::unique_fd writeEnd(atoi(argv[1]));
- base::unique_fd readEnd(atoi(argv[2]));
+ unique_fd writeEnd(atoi(argv[1]));
+ unique_fd readEnd(atoi(argv[2]));
auto serverConfig = readFromFd<BinderRpcTestServerConfig>(readEnd);
auto socketType = static_cast<SocketType>(serverConfig.socketType);
@@ -104,34 +118,38 @@ int main(int argc, char* argv[]) {
}
auto certVerifier = std::make_shared<RpcCertificateVerifierSimple>();
- sp<RpcServer> server = RpcServer::make(newFactory(rpcSecurity, certVerifier));
+ sp<RpcServer> server = RpcServer::make(newTlsFactory(rpcSecurity, certVerifier));
- server->setProtocolVersion(serverConfig.serverVersion);
+ LOG_ALWAYS_FATAL_IF(!server->setProtocolVersion(serverConfig.serverVersion));
server->setMaxThreads(serverConfig.numThreads);
server->setSupportedFileDescriptorTransportModes(serverSupportedFileDescriptorTransportModes);
unsigned int outPort = 0;
- base::unique_fd socketFd(serverConfig.socketFd);
+ unique_fd socketFd(serverConfig.socketFd);
switch (socketType) {
case SocketType::PRECONNECTED:
[[fallthrough]];
case SocketType::UNIX:
- CHECK_EQ(OK, server->setupUnixDomainServer(serverConfig.addr.c_str()))
- << serverConfig.addr;
+ LOG_ALWAYS_FATAL_IF(OK != server->setupUnixDomainServer(serverConfig.addr.c_str()),
+ "%s", serverConfig.addr.c_str());
break;
case SocketType::UNIX_BOOTSTRAP:
- CHECK_EQ(OK, server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
+ LOG_ALWAYS_FATAL_IF(OK !=
+ server->setupUnixDomainSocketBootstrapServer(std::move(socketFd)));
break;
case SocketType::UNIX_RAW:
- CHECK_EQ(OK, server->setupRawSocketServer(std::move(socketFd)));
+ LOG_ALWAYS_FATAL_IF(OK != server->setupRawSocketServer(std::move(socketFd)));
break;
case SocketType::VSOCK:
- CHECK_EQ(OK, server->setupVsockServer(VMADDR_CID_LOCAL, serverConfig.vsockPort));
+ LOG_ALWAYS_FATAL_IF(OK !=
+ server->setupVsockServer(VMADDR_CID_LOCAL,
+ serverConfig.vsockPort),
+ "Need `sudo modprobe vsock_loopback`?");
break;
case SocketType::INET: {
- CHECK_EQ(OK, server->setupInetServer(kLocalInetAddress, 0, &outPort));
- CHECK_NE(0, outPort);
+ LOG_ALWAYS_FATAL_IF(OK != server->setupInetServer(kLocalInetAddress, 0, &outPort));
+ LOG_ALWAYS_FATAL_IF(0 == outPort);
break;
}
default:
@@ -146,16 +164,21 @@ int main(int argc, char* argv[]) {
if (rpcSecurity == RpcSecurity::TLS) {
for (const auto& clientCert : clientInfo.certs) {
- CHECK_EQ(OK,
- certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
- clientCert.data));
+ LOG_ALWAYS_FATAL_IF(OK !=
+ certVerifier->addTrustedPeerCertificate(RpcCertificateFormat::PEM,
+ clientCert.data));
}
}
- server->setPerSessionRootObject([&](const void* addrPtr, size_t len) {
+ server->setPerSessionRootObject([&](wp<RpcSession> session, const void* addrPtr, size_t len) {
+ {
+ sp<RpcSession> spSession = session.promote();
+ LOG_ALWAYS_FATAL_IF(nullptr == spSession.get());
+ }
+
// UNIX sockets with abstract addresses return
// sizeof(sa_family_t)==2 in addrlen
- CHECK_GE(len, sizeof(sa_family_t));
+ LOG_ALWAYS_FATAL_IF(len < sizeof(sa_family_t));
const sockaddr* addr = reinterpret_cast<const sockaddr*>(addrPtr);
sp<MyBinderRpcTestAndroid> service = sp<MyBinderRpcTestAndroid>::make();
switch (addr->sa_family) {
@@ -163,15 +186,15 @@ int main(int argc, char* argv[]) {
// nothing to save
break;
case AF_VSOCK:
- CHECK_EQ(len, sizeof(sockaddr_vm));
+ LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_vm));
service->port = reinterpret_cast<const sockaddr_vm*>(addr)->svm_port;
break;
case AF_INET:
- CHECK_EQ(len, sizeof(sockaddr_in));
+ LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_in));
service->port = ntohs(reinterpret_cast<const sockaddr_in*>(addr)->sin_port);
break;
case AF_INET6:
- CHECK_EQ(len, sizeof(sockaddr_in));
+ LOG_ALWAYS_FATAL_IF(len != sizeof(sockaddr_in));
service->port = ntohs(reinterpret_cast<const sockaddr_in6*>(addr)->sin6_port);
break;
default:
diff --git a/libs/binder/tests/binderRpcTestServiceTrusty.cpp b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
index 85573895e9..8346b3691e 100644
--- a/libs/binder/tests/binderRpcTestServiceTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestServiceTrusty.cpp
@@ -16,7 +16,6 @@
#define TLOG_TAG "binderRpcTestService"
-#include <android-base/stringprintf.h>
#include <binder/RpcServerTrusty.h>
#include <inttypes.h>
#include <lib/tipc/tipc.h>
@@ -28,7 +27,6 @@
#include "binderRpcTestCommon.h"
using namespace android;
-using android::base::StringPrintf;
using binder::Status;
static int gConnectionCounter = 0;
@@ -79,26 +77,27 @@ int main(void) {
// Message size needs to be large enough to cover all messages sent by the
// tests: SendAndGetResultBackBig sends two large strings.
constexpr size_t max_msg_size = 4096;
- auto serverOrErr =
+ auto server =
RpcServerTrusty::make(hset, serverInfo.port->c_str(),
std::shared_ptr<const RpcServerTrusty::PortAcl>(&port_acl),
max_msg_size);
- if (!serverOrErr.ok()) {
- TLOGE("Failed to create RpcServer (%d)\n", serverOrErr.error());
+ if (server == nullptr) {
return EXIT_FAILURE;
}
- auto server = std::move(*serverOrErr);
serverInfo.server = server;
- serverInfo.server->setProtocolVersion(serverVersion);
- serverInfo.server->setPerSessionRootObject([=](const void* /*addrPtr*/, size_t /*len*/) {
- auto service = sp<MyBinderRpcTestTrusty>::make();
- // Assign a unique connection identifier to service->port so
- // getClientPort returns a unique value per connection
- service->port = ++gConnectionCounter;
- service->server = server;
- return service;
- });
+ if (!serverInfo.server->setProtocolVersion(serverVersion)) {
+ return EXIT_FAILURE;
+ }
+ serverInfo.server->setPerSessionRootObject(
+ [=](wp<RpcSession> /*session*/, const void* /*addrPtr*/, size_t /*len*/) {
+ auto service = sp<MyBinderRpcTestTrusty>::make();
+ // Assign a unique connection identifier to service->port so
+ // getClientPort returns a unique value per connection
+ service->port = ++gConnectionCounter;
+ service->server = server;
+ return service;
+ });
servers.push_back(std::move(serverInfo));
}
diff --git a/libs/binder/tests/binderRpcTestTrusty.cpp b/libs/binder/tests/binderRpcTestTrusty.cpp
index b3bb5ebda7..18751cc089 100644
--- a/libs/binder/tests/binderRpcTestTrusty.cpp
+++ b/libs/binder/tests/binderRpcTestTrusty.cpp
@@ -16,13 +16,14 @@
#define LOG_TAG "binderRpcTest"
-#include <android-base/stringprintf.h>
#include <binder/RpcTransportTipcTrusty.h>
#include <trusty-gtest.h>
#include <trusty_ipc.h>
#include "binderRpcTestFixture.h"
+using android::binder::unique_fd;
+
namespace android {
// Destructors need to be defined, even if pure virtual
@@ -39,46 +40,43 @@ public:
void terminate() override { LOG_ALWAYS_FATAL("terminate() not supported"); }
};
-std::string BinderRpc::PrintParamInfo(const testing::TestParamInfo<ParamType>& info) {
- auto [type, security, clientVersion, serverVersion, singleThreaded, noKernel] = info.param;
- auto ret = PrintToString(type) + "_clientV" + std::to_string(clientVersion) + "_serverV" +
- std::to_string(serverVersion);
- if (singleThreaded) {
- ret += "_single_threaded";
- }
- if (noKernel) {
- ret += "_no_kernel";
+std::unique_ptr<RpcTransportCtxFactory> BinderRpc::newFactory(RpcSecurity rpcSecurity) {
+ switch (rpcSecurity) {
+ case RpcSecurity::RAW:
+ return RpcTransportCtxFactoryTipcTrusty::make();
+ default:
+ LOG_ALWAYS_FATAL("Unknown RpcSecurity %d", rpcSecurity);
}
- return ret;
}
// This creates a new process serving an interface on a certain number of
// threads.
std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
const BinderRpcOptions& options) {
- LOG_ALWAYS_FATAL_IF(options.numIncomingConnections != 0,
- "Non-zero incoming connections %zu on Trusty",
- options.numIncomingConnections);
+ LOG_ALWAYS_FATAL_IF(std::any_of(options.numIncomingConnectionsBySession.begin(),
+ options.numIncomingConnectionsBySession.end(),
+ [](size_t n) { return n != 0; }),
+ "Non-zero incoming connections on Trusty");
- uint32_t clientVersion = std::get<2>(GetParam());
- uint32_t serverVersion = std::get<3>(GetParam());
+ RpcSecurity rpcSecurity = GetParam().security;
+ uint32_t clientVersion = GetParam().clientVersion;
+ uint32_t serverVersion = GetParam().serverVersion;
auto ret = std::make_unique<TrustyProcessSession>();
status_t status;
for (size_t i = 0; i < options.numSessions; i++) {
- auto factory = android::RpcTransportCtxFactoryTipcTrusty::make();
- auto session = android::RpcSession::make(std::move(factory));
+ auto session = android::RpcSession::make(newFactory(rpcSecurity));
EXPECT_TRUE(session->setProtocolVersion(clientVersion));
- session->setMaxOutgoingThreads(options.numOutgoingConnections);
+ session->setMaxOutgoingConnections(options.numOutgoingConnections);
session->setFileDescriptorTransportMode(options.clientFileDescriptorTransportMode);
status = session->setupPreconnectedClient({}, [&]() {
auto port = trustyIpcPort(serverVersion);
int rc = connect(port.c_str(), IPC_CONNECT_WAIT_FOR_PORT);
LOG_ALWAYS_FATAL_IF(rc < 0, "Failed to connect to service: %d", rc);
- return base::unique_fd(rc);
+ return unique_fd(rc);
});
if (options.allowConnectFailure && status != OK) {
ret->sessions.clear();
@@ -92,12 +90,27 @@ std::unique_ptr<ProcessSession> BinderRpc::createRpcTestSocketServerProcessEtc(
return ret;
}
-INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc,
- ::testing::Combine(::testing::Values(SocketType::TIPC),
- ::testing::Values(RpcSecurity::RAW),
- ::testing::ValuesIn(testVersions()),
- ::testing::ValuesIn(testVersions()),
- ::testing::Values(false), ::testing::Values(true)),
+static std::vector<BinderRpc::ParamType> getTrustyBinderRpcParams() {
+ std::vector<BinderRpc::ParamType> ret;
+
+ for (const auto& clientVersion : testVersions()) {
+ for (const auto& serverVersion : testVersions()) {
+ ret.push_back(BinderRpc::ParamType{
+ .type = SocketType::TIPC,
+ .security = RpcSecurity::RAW,
+ .clientVersion = clientVersion,
+ .serverVersion = serverVersion,
+ // TODO: should we test both versions here?
+ .singleThreaded = false,
+ .noKernel = true,
+ });
+ }
+ }
+
+ return ret;
+}
+
+INSTANTIATE_TEST_CASE_P(Trusty, BinderRpc, ::testing::ValuesIn(getTrustyBinderRpcParams()),
BinderRpc::PrintParamInfo);
} // namespace android
diff --git a/libs/binder/tests/binderRpcUniversalTests.cpp b/libs/binder/tests/binderRpcUniversalTests.cpp
index 11a22b0fb6..f4807806ad 100644
--- a/libs/binder/tests/binderRpcUniversalTests.cpp
+++ b/libs/binder/tests/binderRpcUniversalTests.cpp
@@ -48,10 +48,13 @@ TEST(BinderRpc, CannotUseNextWireVersion) {
EXPECT_FALSE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_NEXT + 15));
}
+#ifndef BINDER_RPC_TO_TRUSTY_TEST
TEST(BinderRpc, CanUseExperimentalWireVersion) {
auto session = RpcSession::make();
- EXPECT_TRUE(session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
+ EXPECT_EQ(hasExperimentalRpc(),
+ session->setProtocolVersion(RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL));
}
+#endif
TEST_P(BinderRpc, Ping) {
auto proc = createRpcTestSocketServerProcess({});
@@ -84,7 +87,7 @@ TEST_P(BinderRpc, SeparateRootObject) {
GTEST_SKIP() << "This test requires a multi-threaded service";
}
- SocketType type = std::get<0>(GetParam());
+ SocketType type = GetParam().type;
if (type == SocketType::PRECONNECTED || type == SocketType::UNIX ||
type == SocketType::UNIX_BOOTSTRAP || type == SocketType::UNIX_RAW) {
// we can't get port numbers for unix sockets
@@ -463,7 +466,7 @@ TEST_P(BinderRpc, Callbacks) {
auto proc = createRpcTestSocketServerProcess(
{.numThreads = 1,
.numSessions = 1,
- .numIncomingConnections = numIncomingConnections});
+ .numIncomingConnectionsBySession = {numIncomingConnections}});
auto cb = sp<MyBinderRpcCallback>::make();
if (callIsOneway) {
@@ -491,16 +494,7 @@ TEST_P(BinderRpc, Callbacks) {
<< "callIsOneway: " << callIsOneway
<< " callbackIsOneway: " << callbackIsOneway << " delayed: " << delayed;
- // since we are severing the connection, we need to go ahead and
- // tell the server to shutdown and exit so that waitpid won't hang
- if (auto status = proc.rootIface->scheduleShutdown(); !status.isOk()) {
- EXPECT_EQ(DEAD_OBJECT, status.transactionError()) << status;
- }
-
- // since this session has an incoming connection w/ a threadpool, we
- // need to manually shut it down
- EXPECT_TRUE(proc.proc->sessions.at(0).session->shutdownAndWait(true));
- proc.expectAlreadyShutdown = true;
+ proc.forceShutdown();
}
}
}
diff --git a/libs/binder/tests/binderRpcWireProtocolTest.cpp b/libs/binder/tests/binderRpcWireProtocolTest.cpp
index 3dab2c748b..e59dc82b2b 100644
--- a/libs/binder/tests/binderRpcWireProtocolTest.cpp
+++ b/libs/binder/tests/binderRpcWireProtocolTest.cpp
@@ -14,9 +14,7 @@
* limitations under the License.
*/
-#include <android-base/hex.h>
#include <android-base/logging.h>
-#include <android-base/macros.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <binder/Parcel.h>
@@ -25,6 +23,7 @@
#include <gtest/gtest.h>
#include "../Debug.h"
+#include "../Utils.h"
namespace android {
@@ -176,7 +175,7 @@ static std::string buildRepr(uint32_t version) {
setParcelForRpc(&p, version);
kFillFuns[i](&p);
- result += base::HexString(p.data(), p.dataSize());
+ result += HexString(p.data(), p.dataSize());
}
return result;
}
@@ -237,31 +236,30 @@ TEST(RpcWire, V0) {
checkRepr(kCurrentRepr, 0);
}
+TEST(RpcWire, V1) {
+ checkRepr(kCurrentRepr, 1);
+}
+
TEST(RpcWire, CurrentVersion) {
checkRepr(kCurrentRepr, RPC_WIRE_PROTOCOL_VERSION);
}
-static_assert(RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL,
+static_assert(RPC_WIRE_PROTOCOL_VERSION == 1,
"If the binder wire protocol is updated, this test should test additional versions. "
"The binder wire protocol should only be updated on upstream AOSP.");
-TEST(RpcWire, ReleaseBranchHasFrozenRpcWireProtocol) {
- if (RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
- EXPECT_FALSE(base::GetProperty("ro.build.version.codename", "") == "REL")
- << "Binder RPC wire protocol must be frozen on a release branch!";
+TEST(RpcWire, NextIsPlusOneReminder) {
+ if (RPC_WIRE_PROTOCOL_VERSION != RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
+ EXPECT_EQ(RPC_WIRE_PROTOCOL_VERSION + 1, RPC_WIRE_PROTOCOL_VERSION_NEXT)
+ << "Make sure to note what the next version should be.";
}
}
-TEST(RpcWire, IfNotExperimentalCodeHasNoExperimentalFeatures) {
+TEST(RpcWire, ReleaseBranchHasFrozenRpcWireProtocol) {
if (RPC_WIRE_PROTOCOL_VERSION == RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL) {
- GTEST_SKIP() << "Version is experimental, so experimental features are okay.";
+ EXPECT_FALSE(base::GetProperty("ro.build.version.codename", "") == "REL")
+ << "Binder RPC wire protocol must be frozen on a release branch!";
}
-
- // if we set the wire protocol version to experimental, none of the code
- // should introduce a difference (if this fails, it means we have features
- // which are enabled under experimental mode, but we aren't actually using
- // or testing them!)
- checkRepr(kCurrentRepr, RPC_WIRE_PROTOCOL_VERSION_EXPERIMENTAL);
}
} // namespace android
diff --git a/libs/binder/tests/binderSafeInterfaceTest.cpp b/libs/binder/tests/binderSafeInterfaceTest.cpp
index c857d62cb2..41cb552e4c 100644
--- a/libs/binder/tests/binderSafeInterfaceTest.cpp
+++ b/libs/binder/tests/binderSafeInterfaceTest.cpp
@@ -28,6 +28,7 @@
#include <gtest/gtest.h>
#pragma clang diagnostic pop
+#include <utils/Flattenable.h>
#include <utils/LightRefBase.h>
#include <utils/NativeHandle.h>
@@ -35,10 +36,12 @@
#include <optional>
+#include <inttypes.h>
#include <sys/eventfd.h>
#include <sys/prctl.h>
using namespace std::chrono_literals; // NOLINT - google-build-using-namespace
+using android::binder::unique_fd;
namespace android {
namespace tests {
@@ -604,7 +607,10 @@ private:
static constexpr const char* getLogTag() { return "SafeInterfaceTest"; }
sp<ISafeInterfaceTest> getRemoteService() {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> binder = defaultServiceManager()->getService(kServiceName);
+#pragma clang diagnostic pop
sp<ISafeInterfaceTest> iface = interface_cast<ISafeInterfaceTest>(binder);
EXPECT_TRUE(iface != nullptr);
@@ -680,16 +686,18 @@ bool fdsAreEquivalent(int a, int b) {
TEST_F(SafeInterfaceTest, TestIncrementNativeHandle) {
// Create an fd we can use to send and receive from the remote process
- base::unique_fd eventFd{eventfd(0 /*initval*/, 0 /*flags*/)};
+ unique_fd eventFd{eventfd(0 /*initval*/, 0 /*flags*/)};
ASSERT_NE(-1, eventFd);
// Determine the maximum number of fds this process can have open
struct rlimit limit {};
ASSERT_EQ(0, getrlimit(RLIMIT_NOFILE, &limit));
- uint32_t maxFds = static_cast<uint32_t>(limit.rlim_cur);
+ uint64_t maxFds = limit.rlim_cur;
+
+ ALOG(LOG_INFO, "SafeInterfaceTest", "%s max FDs: %" PRIu64, __PRETTY_FUNCTION__, maxFds);
// Perform this test enough times to rule out fd leaks
- for (uint32_t iter = 0; iter < (2 * maxFds); ++iter) {
+ for (uint32_t iter = 0; iter < (maxFds + 100); ++iter) {
native_handle* handle = native_handle_create(1 /*numFds*/, 1 /*numInts*/);
ASSERT_NE(nullptr, handle);
handle->data[0] = dup(eventFd.get());
diff --git a/libs/binder/tests/binderStabilityTest.cpp b/libs/binder/tests/binderStabilityTest.cpp
index 2398e1e1ef..3d993588a4 100644
--- a/libs/binder/tests/binderStabilityTest.cpp
+++ b/libs/binder/tests/binderStabilityTest.cpp
@@ -155,7 +155,10 @@ TEST(BinderStability, NdkForceDowngradeToLocalStability) {
}
TEST(BinderStability, ForceDowngradeToVendorStability) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> serverBinder = android::defaultServiceManager()->getService(kSystemStabilityServer);
+#pragma clang diagnostic pop
auto server = interface_cast<IBinderStabilityTest>(serverBinder);
ASSERT_NE(nullptr, server.get());
@@ -206,7 +209,10 @@ TEST(BinderStability, ConnectionInfoRequiresManifestEntries) {
EXPECT_EQ(connectionInfo, std::nullopt);
}
TEST(BinderStability, CantCallVendorBinderInSystemContext) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
sp<IBinder> serverBinder = android::defaultServiceManager()->getService(kSystemStabilityServer);
+#pragma clang diagnostic pop
auto server = interface_cast<IBinderStabilityTest>(serverBinder);
ASSERT_NE(nullptr, server.get());
@@ -310,8 +316,11 @@ static AIBinder_Class* kNdkBadStableBinder =
extern "C" void AIBinder_markVendorStability(AIBinder* binder); // <- BAD DO NOT COPY
TEST(BinderStability, NdkCantCallVendorBinderInSystemContext) {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
SpAIBinder binder = SpAIBinder(AServiceManager_getService(
String8(kSystemStabilityServer).c_str()));
+#pragma clang diagnostic pop
std::shared_ptr<aidl::IBinderStabilityTest> remoteServer =
aidl::IBinderStabilityTest::fromBinder(binder);
diff --git a/libs/binder/tests/binderTextOutputTest.cpp b/libs/binder/tests/binderTextOutputTest.cpp
index b37030e7d7..a648c0886d 100644
--- a/libs/binder/tests/binderTextOutputTest.cpp
+++ b/libs/binder/tests/binderTextOutputTest.cpp
@@ -20,13 +20,14 @@
#include <limits>
#include <cstddef>
-#include "android-base/file.h"
#include "android-base/test_utils.h"
#include <gtest/gtest.h>
#include <binder/Parcel.h>
#include <binder/TextOutput.h>
+#include "../file.h"
+
static void CheckMessage(CapturedStderr& cap,
const char* expected,
bool singleline) {
diff --git a/libs/binder/tests/binderThroughputTest.cpp b/libs/binder/tests/binderThroughputTest.cpp
index cfaf2a987f..f912348689 100644
--- a/libs/binder/tests/binderThroughputTest.cpp
+++ b/libs/binder/tests/binderThroughputTest.cpp
@@ -7,9 +7,10 @@
#include <cstdlib>
#include <cstdio>
+#include <fstream>
#include <iostream>
-#include <vector>
#include <tuple>
+#include <vector>
#include <unistd.h>
#include <sys/wait.h>
@@ -49,6 +50,75 @@ public:
}
};
+static uint64_t warn_latency = std::numeric_limits<uint64_t>::max();
+
+struct ProcResults {
+ vector<uint64_t> data;
+
+ ProcResults(size_t capacity) { data.reserve(capacity); }
+
+ void add_time(uint64_t time) { data.push_back(time); }
+ void combine_with(const ProcResults& append) {
+ data.insert(data.end(), append.data.begin(), append.data.end());
+ }
+ uint64_t worst() {
+ return *max_element(data.begin(), data.end());
+ }
+ void dump_to_file(string filename) {
+ ofstream output;
+ output.open(filename);
+ if (!output.is_open()) {
+ cerr << "Failed to open '" << filename << "'." << endl;
+ exit(EXIT_FAILURE);
+ }
+ for (uint64_t value : data) {
+ output << value << "\n";
+ }
+ output.close();
+ }
+ void dump() {
+ if (data.size() == 0) {
+ // This avoids index-out-of-bounds below.
+ cout << "error: no data\n" << endl;
+ return;
+ }
+
+ size_t num_long_transactions = 0;
+ for (uint64_t elem : data) {
+ if (elem > warn_latency) {
+ num_long_transactions += 1;
+ }
+ }
+
+ if (num_long_transactions > 0) {
+ cout << (double)num_long_transactions / data.size() << "% of transactions took longer "
+ "than estimated max latency. Consider setting -m to be higher than "
+ << worst() / 1000 << " microseconds" << endl;
+ }
+
+ sort(data.begin(), data.end());
+
+ uint64_t total_time = 0;
+ for (uint64_t elem : data) {
+ total_time += elem;
+ }
+
+ double best = (double)data[0] / 1.0E6;
+ double worst = (double)data.back() / 1.0E6;
+ double average = (double)total_time / data.size() / 1.0E6;
+ cout << "average:" << average << "ms worst:" << worst << "ms best:" << best << "ms" << endl;
+
+ double percentile_50 = data[(50 * data.size()) / 100] / 1.0E6;
+ double percentile_90 = data[(90 * data.size()) / 100] / 1.0E6;
+ double percentile_95 = data[(95 * data.size()) / 100] / 1.0E6;
+ double percentile_99 = data[(99 * data.size()) / 100] / 1.0E6;
+ cout << "50%: " << percentile_50 << " ";
+ cout << "90%: " << percentile_90 << " ";
+ cout << "95%: " << percentile_95 << " ";
+ cout << "99%: " << percentile_99 << endl;
+ }
+};
+
class Pipe {
int m_readFd;
int m_writeFd;
@@ -79,13 +149,37 @@ public:
int error = read(m_readFd, &val, sizeof(val));
ASSERT_TRUE(error >= 0);
}
- template <typename T> void send(const T& v) {
- int error = write(m_writeFd, &v, sizeof(T));
+ void send(const ProcResults& v) {
+ size_t num_elems = v.data.size();
+
+ int error = write(m_writeFd, &num_elems, sizeof(size_t));
ASSERT_TRUE(error >= 0);
+
+ char* to_write = (char*)v.data.data();
+ size_t num_bytes = sizeof(uint64_t) * num_elems;
+
+ while (num_bytes > 0) {
+ int ret = write(m_writeFd, to_write, num_bytes);
+ ASSERT_TRUE(ret >= 0);
+ num_bytes -= ret;
+ to_write += ret;
+ }
}
- template <typename T> void recv(T& v) {
- int error = read(m_readFd, &v, sizeof(T));
+ void recv(ProcResults& v) {
+ size_t num_elems = 0;
+ int error = read(m_readFd, &num_elems, sizeof(size_t));
ASSERT_TRUE(error >= 0);
+
+ v.data.resize(num_elems);
+ char* read_to = (char*)v.data.data();
+ size_t num_bytes = sizeof(uint64_t) * num_elems;
+
+ while (num_bytes > 0) {
+ int ret = read(m_readFd, read_to, num_bytes);
+ ASSERT_TRUE(ret >= 0);
+ num_bytes -= ret;
+ read_to += ret;
+ }
}
static tuple<Pipe, Pipe> createPipePair() {
int a[2];
@@ -100,74 +194,6 @@ public:
}
};
-static const uint32_t num_buckets = 128;
-static uint64_t max_time_bucket = 50ull * 1000000;
-static uint64_t time_per_bucket = max_time_bucket / num_buckets;
-
-struct ProcResults {
- uint64_t m_worst = 0;
- uint32_t m_buckets[num_buckets] = {0};
- uint64_t m_transactions = 0;
- uint64_t m_long_transactions = 0;
- uint64_t m_total_time = 0;
- uint64_t m_best = max_time_bucket;
-
- void add_time(uint64_t time) {
- if (time > max_time_bucket) {
- m_long_transactions++;
- }
- m_buckets[min((uint32_t)(time / time_per_bucket), num_buckets - 1)] += 1;
- m_best = min(time, m_best);
- m_worst = max(time, m_worst);
- m_transactions += 1;
- m_total_time += time;
- }
- static ProcResults combine(const ProcResults& a, const ProcResults& b) {
- ProcResults ret;
- for (int i = 0; i < num_buckets; i++) {
- ret.m_buckets[i] = a.m_buckets[i] + b.m_buckets[i];
- }
- ret.m_worst = max(a.m_worst, b.m_worst);
- ret.m_best = min(a.m_best, b.m_best);
- ret.m_transactions = a.m_transactions + b.m_transactions;
- ret.m_long_transactions = a.m_long_transactions + b.m_long_transactions;
- ret.m_total_time = a.m_total_time + b.m_total_time;
- return ret;
- }
- void dump() {
- if (m_long_transactions > 0) {
- cout << (double)m_long_transactions / m_transactions << "% of transactions took longer "
- "than estimated max latency. Consider setting -m to be higher than "
- << m_worst / 1000 << " microseconds" << endl;
- }
-
- double best = (double)m_best / 1.0E6;
- double worst = (double)m_worst / 1.0E6;
- double average = (double)m_total_time / m_transactions / 1.0E6;
- cout << "average:" << average << "ms worst:" << worst << "ms best:" << best << "ms" << endl;
-
- uint64_t cur_total = 0;
- float time_per_bucket_ms = time_per_bucket / 1.0E6;
- for (int i = 0; i < num_buckets; i++) {
- float cur_time = time_per_bucket_ms * i + 0.5f * time_per_bucket_ms;
- if ((cur_total < 0.5f * m_transactions) && (cur_total + m_buckets[i] >= 0.5f * m_transactions)) {
- cout << "50%: " << cur_time << " ";
- }
- if ((cur_total < 0.9f * m_transactions) && (cur_total + m_buckets[i] >= 0.9f * m_transactions)) {
- cout << "90%: " << cur_time << " ";
- }
- if ((cur_total < 0.95f * m_transactions) && (cur_total + m_buckets[i] >= 0.95f * m_transactions)) {
- cout << "95%: " << cur_time << " ";
- }
- if ((cur_total < 0.99f * m_transactions) && (cur_total + m_buckets[i] >= 0.99f * m_transactions)) {
- cout << "99%: " << cur_time << " ";
- }
- cur_total += m_buckets[i];
- }
- cout << endl;
- }
-};
-
String16 generateServiceName(int num)
{
char num_str[32];
@@ -204,31 +230,37 @@ void worker_fx(int num,
for (int i = 0; i < server_count; i++) {
if (num == i)
continue;
- workers.push_back(serviceMgr->getService(generateServiceName(i)));
+ workers.push_back(serviceMgr->waitForService(generateServiceName(i)));
}
- // Run the benchmark if client
- ProcResults results;
+ p.signal();
+ p.wait();
+
+ ProcResults results(iterations);
chrono::time_point<chrono::high_resolution_clock> start, end;
- for (int i = 0; (!cs_pair || num >= server_count) && i < iterations; i++) {
- Parcel data, reply;
- int target = cs_pair ? num % server_count : rand() % workers.size();
- int sz = payload_size;
-
- while (sz >= sizeof(uint32_t)) {
- data.writeInt32(0);
- sz -= sizeof(uint32_t);
- }
- start = chrono::high_resolution_clock::now();
- status_t ret = workers[target]->transact(BINDER_NOP, data, &reply);
- end = chrono::high_resolution_clock::now();
- uint64_t cur_time = uint64_t(chrono::duration_cast<chrono::nanoseconds>(end - start).count());
- results.add_time(cur_time);
+ // Skip the benchmark if server of a cs_pair.
+ if (!(cs_pair && num < server_count)) {
+ for (int i = 0; i < iterations; i++) {
+ Parcel data, reply;
+ int target = cs_pair ? num % server_count : rand() % workers.size();
+ int sz = payload_size;
- if (ret != NO_ERROR) {
- cout << "thread " << num << " failed " << ret << "i : " << i << endl;
- exit(EXIT_FAILURE);
+ while (sz >= sizeof(uint32_t)) {
+ data.writeInt32(0);
+ sz -= sizeof(uint32_t);
+ }
+ start = chrono::high_resolution_clock::now();
+ status_t ret = workers[target]->transact(BINDER_NOP, data, &reply);
+ end = chrono::high_resolution_clock::now();
+
+ uint64_t cur_time = uint64_t(chrono::duration_cast<chrono::nanoseconds>(end - start).count());
+ results.add_time(cur_time);
+
+ if (ret != NO_ERROR) {
+ cout << "thread " << num << " failed " << ret << "i : " << i << endl;
+ exit(EXIT_FAILURE);
+ }
}
}
@@ -274,20 +306,23 @@ void signal_all(vector<Pipe>& v)
}
}
-void run_main(int iterations,
- int workers,
- int payload_size,
- int cs_pair,
- bool training_round=false)
-{
+void run_main(int iterations, int workers, int payload_size, int cs_pair,
+ bool training_round = false, bool dump_to_file = false, string dump_filename = "") {
vector<Pipe> pipes;
// Create all the workers and wait for them to spawn.
for (int i = 0; i < workers; i++) {
pipes.push_back(make_worker(i, iterations, workers, payload_size, cs_pair));
}
wait_all(pipes);
+ // All workers have now been spawned and added themselves to service
+ // manager. Signal each worker to obtain a handle to the server workers from
+ // servicemanager.
+ signal_all(pipes);
+ // Wait for each worker to finish obtaining a handle to all server workers
+ // from servicemanager.
+ wait_all(pipes);
- // Run the workers and wait for completion.
+ // Run the benchmark and wait for completion.
chrono::time_point<chrono::high_resolution_clock> start, end;
cout << "waiting for workers to complete" << endl;
start = chrono::high_resolution_clock::now();
@@ -302,11 +337,10 @@ void run_main(int iterations,
// Collect all results from the workers.
cout << "collecting results" << endl;
signal_all(pipes);
- ProcResults tot_results;
+ ProcResults tot_results(0), tmp_results(0);
for (int i = 0; i < workers; i++) {
- ProcResults tmp_results;
pipes[i].recv(tmp_results);
- tot_results = ProcResults::combine(tot_results, tmp_results);
+ tot_results.combine_with(tmp_results);
}
// Kill all the workers.
@@ -320,13 +354,14 @@ void run_main(int iterations,
}
}
if (training_round) {
- // sets max_time_bucket to 2 * m_worst from the training round.
- // Also needs to adjust time_per_bucket accordingly.
- max_time_bucket = 2 * tot_results.m_worst;
- time_per_bucket = max_time_bucket / num_buckets;
- cout << "Max latency during training: " << tot_results.m_worst / 1.0E6 << "ms" << endl;
+ // Sets warn_latency to 2 * worst from the training round.
+ warn_latency = 2 * tot_results.worst();
+ cout << "Max latency during training: " << tot_results.worst() / 1.0E6 << "ms" << endl;
} else {
- tot_results.dump();
+ if (dump_to_file) {
+ tot_results.dump_to_file(dump_filename);
+ }
+ tot_results.dump();
}
}
@@ -337,8 +372,9 @@ int main(int argc, char *argv[])
int payload_size = 0;
bool cs_pair = false;
bool training_round = false;
- (void)argc;
- (void)argv;
+ int max_time_us;
+ bool dump_to_file = false;
+ string dump_filename;
// Parse arguments.
for (int i = 1; i < argc; i++) {
@@ -348,55 +384,85 @@ int main(int argc, char *argv[])
cout << "\t-m N : Specify expected max latency in microseconds." << endl;
cout << "\t-p : Split workers into client/server pairs." << endl;
cout << "\t-s N : Specify payload size." << endl;
- cout << "\t-t N : Run training round." << endl;
+ cout << "\t-t : Run training round." << endl;
cout << "\t-w N : Specify total number of workers." << endl;
+ cout << "\t-d FILE : Dump raw data to file." << endl;
return 0;
}
if (string(argv[i]) == "-w") {
+ if (i + 1 == argc) {
+ cout << "-w requires an argument\n" << endl;
+ exit(EXIT_FAILURE);
+ }
workers = atoi(argv[i+1]);
i++;
continue;
}
if (string(argv[i]) == "-i") {
+ if (i + 1 == argc) {
+ cout << "-i requires an argument\n" << endl;
+ exit(EXIT_FAILURE);
+ }
iterations = atoi(argv[i+1]);
i++;
continue;
}
if (string(argv[i]) == "-s") {
+ if (i + 1 == argc) {
+ cout << "-s requires an argument\n" << endl;
+ exit(EXIT_FAILURE);
+ }
payload_size = atoi(argv[i+1]);
i++;
+ continue;
}
if (string(argv[i]) == "-p") {
// client/server pairs instead of spreading
// requests to all workers. If true, half
// the workers become clients and half servers
cs_pair = true;
+ continue;
}
if (string(argv[i]) == "-t") {
// Run one training round before actually collecting data
// to get an approximation of max latency.
training_round = true;
+ continue;
}
if (string(argv[i]) == "-m") {
+ if (i + 1 == argc) {
+ cout << "-m requires an argument\n" << endl;
+ exit(EXIT_FAILURE);
+ }
// Caller specified the max latency in microseconds.
// No need to run training round in this case.
- if (atoi(argv[i+1]) > 0) {
- max_time_bucket = strtoull(argv[i+1], (char **)nullptr, 10) * 1000;
- time_per_bucket = max_time_bucket / num_buckets;
- i++;
- } else {
+ max_time_us = atoi(argv[i+1]);
+ if (max_time_us <= 0) {
cout << "Max latency -m must be positive." << endl;
exit(EXIT_FAILURE);
}
+ warn_latency = max_time_us * 1000ull;
+ i++;
+ continue;
+ }
+ if (string(argv[i]) == "-d") {
+ if (i + 1 == argc) {
+ cout << "-d requires an argument\n" << endl;
+ exit(EXIT_FAILURE);
+ }
+ dump_to_file = true;
+ dump_filename = argv[i + 1];
+ i++;
+ continue;
}
}
if (training_round) {
cout << "Start training round" << endl;
- run_main(iterations, workers, payload_size, cs_pair, training_round=true);
+ run_main(iterations, workers, payload_size, cs_pair, true);
cout << "Completed training round" << endl << endl;
}
- run_main(iterations, workers, payload_size, cs_pair);
+ run_main(iterations, workers, payload_size, cs_pair, false, dump_to_file, dump_filename);
return 0;
}
diff --git a/libs/binder/tests/binderUtilsHostTest.cpp b/libs/binder/tests/binderUtilsHostTest.cpp
index 25e286ce0c..6301c74842 100644
--- a/libs/binder/tests/binderUtilsHostTest.cpp
+++ b/libs/binder/tests/binderUtilsHostTest.cpp
@@ -32,7 +32,7 @@ namespace android {
TEST(UtilsHost, ExecuteImmediately) {
auto result = execute({"echo", "foo"}, nullptr);
- ASSERT_THAT(result, Ok());
+ ASSERT_TRUE(result.has_value());
EXPECT_THAT(result->exitCode, Optional(EX_OK));
EXPECT_EQ(result->stdoutStr, "foo\n");
}
@@ -58,7 +58,7 @@ TEST(UtilsHost, ExecuteLongRunning) {
EXPECT_GE(elapsedMs, 1000);
EXPECT_LT(elapsedMs, 2000);
- ASSERT_THAT(result, Ok());
+ ASSERT_TRUE(result.has_value());
EXPECT_EQ(std::nullopt, result->exitCode);
EXPECT_EQ(result->stdoutStr, "foo\n");
}
@@ -83,7 +83,7 @@ TEST(UtilsHost, ExecuteLongRunning2) {
EXPECT_GE(elapsedMs, 4000);
EXPECT_LT(elapsedMs, 6000);
- ASSERT_THAT(result, Ok());
+ ASSERT_TRUE(result.has_value());
EXPECT_EQ(std::nullopt, result->exitCode);
EXPECT_EQ(result->stdoutStr, "foo\n");
}
@@ -104,7 +104,7 @@ TEST(UtilsHost, KillWithSigKill) {
return false;
});
- ASSERT_THAT(result, Ok());
+ ASSERT_TRUE(result.has_value());
EXPECT_EQ(std::nullopt, result->exitCode);
EXPECT_THAT(result->signal, Optional(SIGKILL));
}
diff --git a/libs/binder/tests/format.h b/libs/binder/tests/format.h
new file mode 100644
index 0000000000..c588de70bd
--- /dev/null
+++ b/libs/binder/tests/format.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.
+ */
+
+// TODO(b/302723053): remove this header and replace with <format> once b/175635923 is done
+// ETA for this blocker is 2023-10-27~2023-11-10.
+// Also, remember to remove fmtlib's format.cc from trusty makefiles.
+
+#if __has_include(<format>) && !defined(_LIBCPP_HAS_NO_INCOMPLETE_FORMAT)
+#include <format>
+#else
+#include <fmt/format.h>
+
+namespace std {
+using fmt::format;
+}
+#endif \ No newline at end of file
diff --git a/libs/binder/tests/parcel_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/Android.bp
index 35866adf20..fbab8f08a9 100644
--- a/libs/binder/tests/parcel_fuzzer/Android.bp
+++ b/libs/binder/tests/parcel_fuzzer/Android.bp
@@ -16,6 +16,9 @@ aidl_interface {
"parcelables/SingleDataParcelable.aidl",
"parcelables/GenericDataParcelable.aidl",
],
+ flags: [
+ "-Werror",
+ ],
backend: {
java: {
enabled: true,
@@ -32,7 +35,11 @@ cc_fuzz {
host_supported: true,
fuzz_config: {
- cc: ["smoreland@google.com"],
+ cc: [
+ "smoreland@google.com",
+ "waghpawan@google.com",
+ ],
+ use_for_presubmit: true,
},
srcs: [
@@ -104,3 +111,55 @@ cc_library_static {
local_include_dirs: ["include_random_parcel"],
export_include_dirs: ["include_random_parcel"],
}
+
+cc_library {
+ name: "libbinder_random_parcel_seeds",
+ host_supported: true,
+ vendor_available: true,
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ srcs: [
+ "random_parcel_seeds.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libbinder_ndk",
+ "libcutils",
+ "libutils",
+ ],
+ static_libs: [
+ "libbinder_random_parcel",
+ ],
+ include_dirs: [
+ "bionic/libc/kernel/android/uapi/",
+ "bionic/libc/kernel/uapi/",
+ ],
+ local_include_dirs: [
+ "include_random_parcel_seeds",
+ ],
+ export_include_dirs: ["include_random_parcel_seeds"],
+}
+
+cc_binary_host {
+ name: "binder2corpus",
+ static_libs: [
+ "libbinder_random_parcel",
+ "libbinder_random_parcel_seeds",
+ ],
+ cflags: [
+ "-DBINDER_WITH_KERNEL_IPC",
+ ],
+ srcs: [
+ "binder2corpus/binder2corpus.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder",
+ "libutils",
+ "libcutils",
+ ],
+}
diff --git a/libs/binder/tests/parcel_fuzzer/binder.cpp b/libs/binder/tests/parcel_fuzzer/binder.cpp
index 768fbe118d..5c280f4b2c 100644
--- a/libs/binder/tests/parcel_fuzzer/binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder.cpp
@@ -21,14 +21,17 @@
#include "parcelables/SingleDataParcelable.h"
#include "util.h"
-#include <android-base/hex.h>
#include <android/os/IServiceManager.h>
#include <binder/ParcelableHolder.h>
#include <binder/PersistableBundle.h>
#include <binder/Status.h>
+#include <utils/Flattenable.h>
+#include "../../Utils.h"
+
+using ::android::HexString;
using ::android::status_t;
-using ::android::base::HexString;
+using ::android::binder::unique_fd;
enum ByteEnum : int8_t {};
enum IntEnum : int32_t {};
@@ -305,11 +308,12 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS {
},
PARCEL_READ_NO_STATUS(int, readFileDescriptor),
PARCEL_READ_NO_STATUS(int, readParcelFileDescriptor),
- PARCEL_READ_WITH_STATUS(android::base::unique_fd, readUniqueFileDescriptor),
+ PARCEL_READ_WITH_STATUS(unique_fd, readUniqueFileDescriptor),
- PARCEL_READ_WITH_STATUS(std::unique_ptr<std::vector<android::base::unique_fd>>, readUniqueFileDescriptorVector),
- PARCEL_READ_WITH_STATUS(std::optional<std::vector<android::base::unique_fd>>, readUniqueFileDescriptorVector),
- PARCEL_READ_WITH_STATUS(std::vector<android::base::unique_fd>, readUniqueFileDescriptorVector),
+ PARCEL_READ_WITH_STATUS(std::unique_ptr<std::vector<unique_fd>>,
+ readUniqueFileDescriptorVector),
+ PARCEL_READ_WITH_STATUS(std::optional<std::vector<unique_fd>>, readUniqueFileDescriptorVector),
+ PARCEL_READ_WITH_STATUS(std::vector<unique_fd>, readUniqueFileDescriptorVector),
[] (const ::android::Parcel& p, FuzzedDataProvider& provider) {
size_t len = provider.ConsumeIntegral<size_t>();
@@ -349,6 +353,20 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS {
FUZZ_LOG() << " status: " << status << " result: " << result;
},
[] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
+ FUZZ_LOG() << "about to call hasBinders() with status";
+ bool result;
+ status_t status = p.hasBinders(&result);
+ FUZZ_LOG() << " status: " << status << " result: " << result;
+ },
+ [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
+ FUZZ_LOG() << "about to call hasBindersInRange() with status";
+ size_t offset = p.readUint32();
+ size_t length = p.readUint32();
+ bool result;
+ status_t status = p.hasBindersInRange(offset, length, &result);
+ FUZZ_LOG() << " status: " << status << " result: " << result;
+ },
+ [] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
FUZZ_LOG() << "about to call compareDataInRange() with status";
size_t thisOffset = p.readUint32();
size_t otherOffset = p.readUint32();
@@ -367,13 +385,15 @@ std::vector<ParcelRead<::android::Parcel>> BINDER_PARCEL_READ_FUNCTIONS {
FUZZ_LOG() << "about to call readFromParcel() with status for SingleDataParcelable";
parcelables::SingleDataParcelable singleDataParcelable;
status_t status = singleDataParcelable.readFromParcel(&p);
- FUZZ_LOG() <<" status: " << status;
+ FUZZ_LOG() << " status: " << status;
},
[] (const ::android::Parcel& p, FuzzedDataProvider& /*provider*/) {
FUZZ_LOG() << "about to call readFromParcel() with status for GenericDataParcelable";
parcelables::GenericDataParcelable genericDataParcelable;
status_t status = genericDataParcelable.readFromParcel(&p);
- FUZZ_LOG() <<" status: " << status;
+ FUZZ_LOG() << " status: " << status;
+ std::string toString = genericDataParcelable.toString();
+ FUZZ_LOG() << " toString() result: " << toString;
},
};
// clang-format on
diff --git a/libs/binder/tests/parcel_fuzzer/binder2corpus/README.md b/libs/binder/tests/parcel_fuzzer/binder2corpus/README.md
new file mode 100644
index 0000000000..59bf9f31c0
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/binder2corpus/README.md
@@ -0,0 +1,31 @@
+# binder2corpus
+
+This tool converts recordings generated by record_binder tool to fuzzer seeds for fuzzService.
+
+# Steps to add corpus:
+
+## Start recording the service binder
+ex. record_binder start manager
+
+## Run test on device or keep device idle
+ex. atest servicemanager_test
+
+## Stop the recording
+record_binder stop manager
+
+## Pull the recording on host
+Recordings are present on device at /data/local/recordings/<service_name>. Use adb pull.
+Use inspect command of record_binder to check if there are some transactions captured.
+ex. record_binder inspect manager
+
+## run corpus generator tool
+binder2corpus <recording_path> <dir_to_write_corpus>
+
+## Build fuzzer and sync data directory
+ex. m servicemanager_fuzzer && adb sync data
+
+## Push corpus on device
+ex. adb push servicemanager_fuzzer_corpus/ /data/fuzz/x86_64/servicemanager_fuzzer/
+
+## Run fuzzer with corpus directory as argument
+ex. adb shell /data/fuzz/x86_64/servicemanager_fuzzer/servicemanager_fuzzer /data/fuzz/x86_64/servicemanager_fuzzer/servicemanager_fuzzer_corpus \ No newline at end of file
diff --git a/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp b/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp
new file mode 100644
index 0000000000..57521f4a73
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/binder2corpus/binder2corpus.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 "../../FileUtils.h"
+
+#include <android-base/logging.h>
+#include <binder/RecordedTransaction.h>
+#include <binder/unique_fd.h>
+
+#include <fuzzseeds/random_parcel_seeds.h>
+
+#include <sys/prctl.h>
+#include <sys/stat.h>
+
+using android::generateSeedsFromRecording;
+using android::status_t;
+using android::binder::unique_fd;
+using android::binder::debug::RecordedTransaction;
+
+status_t generateCorpus(const char* recordingPath, const char* corpusDir) {
+ unique_fd fd(open(recordingPath, O_RDONLY));
+ if (!fd.ok()) {
+ std::cerr << "Failed to open recording file at path " << recordingPath
+ << " with error: " << strerror(errno) << '\n';
+ return android::BAD_VALUE;
+ }
+
+ if (auto res = mkdir(corpusDir, 0766); res != 0) {
+ std::cerr
+ << "Failed to create corpus directory at path. Delete directory if already exists: "
+ << corpusDir << std::endl;
+ return android::BAD_VALUE;
+ }
+
+ int transactionNumber = 0;
+ while (auto transaction = RecordedTransaction::fromFile(fd)) {
+ ++transactionNumber;
+ std::string filePath = std::string(corpusDir) + std::string("transaction_") +
+ std::to_string(transactionNumber);
+ constexpr int openFlags = O_WRONLY | O_CREAT | O_BINARY | O_CLOEXEC;
+ unique_fd corpusFd(open(filePath.c_str(), openFlags, 0666));
+ if (!corpusFd.ok()) {
+ std::cerr << "Failed to open fd. Path " << filePath
+ << " with error: " << strerror(errno) << std::endl;
+ return android::UNKNOWN_ERROR;
+ }
+ generateSeedsFromRecording(corpusFd, transaction.value());
+ }
+
+ if (transactionNumber == 0) {
+ std::cerr << "No valid transaction has been found in recording file: " << recordingPath
+ << std::endl;
+ return android::BAD_VALUE;
+ }
+
+ return android::NO_ERROR;
+}
+
+void printHelp(const char* toolName) {
+ std::cout << "Usage: \n\n"
+ << toolName
+ << " <recording_path> <destination_directory> \n\n*Use "
+ "record_binder tool for recording binder transactions."
+ << std::endl;
+}
+
+int main(int argc, char** argv) {
+ if (argc != 3) {
+ printHelp(argv[0]);
+ return 1;
+ }
+ const char* sourcePath = argv[1];
+ const char* corpusDir = argv[2];
+ if (android::NO_ERROR != generateCorpus(sourcePath, corpusDir)) {
+ std::cerr << "Failed to generate fuzzer corpus." << std::endl;
+ return 1;
+ }
+ return 0;
+}
diff --git a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
index 08eb27a8a7..3a1471eabe 100644
--- a/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
+++ b/libs/binder/tests/parcel_fuzzer/binder_ndk.cpp
@@ -198,6 +198,8 @@ std::vector<ParcelRead<NdkParcelAdapter>> BINDER_NDK_PARCEL_READ_FUNCTIONS{
aidl::parcelables::GenericDataParcelable genericDataParcelable;
binder_status_t status = genericDataParcelable.readFromParcel(p.aParcel());
FUZZ_LOG() << "status: " << status;
+ std::string toString = genericDataParcelable.toString();
+ FUZZ_LOG() << "toString() result: " << toString;
},
[](const NdkParcelAdapter& p, FuzzedDataProvider& provider) {
FUZZ_LOG() << "about to marshal AParcel";
diff --git a/libs/binder/tests/parcel_fuzzer/hwbinder.cpp b/libs/binder/tests/parcel_fuzzer/hwbinder.cpp
index 438e8ae9b2..cdc8bccf0c 100644
--- a/libs/binder/tests/parcel_fuzzer/hwbinder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/hwbinder.cpp
@@ -18,12 +18,13 @@
#include "hwbinder.h"
#include "util.h"
-#include <android-base/hex.h>
#include <android-base/logging.h>
#include <hwbinder/Parcel.h>
+#include "../../Utils.h"
+
using ::android::status_t;
-using ::android::base::HexString;
+using ::android::HexString;
// TODO: support scatter-gather types
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_driver.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_driver.h
index a9a6197439..cb37cfaa27 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_driver.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_driver.h
@@ -19,7 +19,17 @@
#include <binder/IBinder.h>
#include <fuzzer/FuzzedDataProvider.h>
+#include <vector>
+
namespace android {
+
+/**
+ * See fuzzService, but fuzzes multiple services at the same time.
+ *
+ * Consumes providers.
+ */
+void fuzzService(const std::vector<sp<IBinder>>& binders, FuzzedDataProvider&& provider);
+
/**
* Based on the random data in provider, construct an arbitrary number of
* Parcel objects and send them to the service in serial.
@@ -34,4 +44,5 @@ namespace android {
* }
*/
void fuzzService(const sp<IBinder>& binder, FuzzedDataProvider&& provider);
+
} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_ndk_driver.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_ndk_driver.h
index f2b782337c..d8bf87a58c 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_ndk_driver.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/libbinder_ndk_driver.h
@@ -16,10 +16,21 @@
#pragma once
+#include <android/binder_auto_utils.h>
#include <android/binder_parcel.h>
#include <fuzzer/FuzzedDataProvider.h>
+#include <vector>
+
namespace android {
+
+/**
+ * See fuzzService, but fuzzes multiple services at the same time.
+ *
+ * Consumes providers.
+ */
+void fuzzService(const std::vector<ndk::SpAIBinder>& binders, FuzzedDataProvider&& provider);
+
/**
* Based on the random data in provider, construct an arbitrary number of
* Parcel objects and send them to the service in serial.
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h
index 8fc9263b98..7a1688b7b7 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_binder.h
@@ -16,11 +16,25 @@
#pragma once
+#include <binder/Binder.h>
#include <binder/IBinder.h>
#include <fuzzer/FuzzedDataProvider.h>
namespace android {
+class RandomBinder : public BBinder {
+public:
+ RandomBinder(const String16& descriptor, std::vector<uint8_t>&& bytes);
+ const String16& getInterfaceDescriptor() const override;
+ status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override;
+
+private:
+ String16 mDescriptor;
+ // note may not all be used
+ std::vector<uint8_t> mBytes;
+ FuzzedDataProvider mProvider;
+};
+
// Get a random binder object for use in fuzzing.
//
// May return nullptr.
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h
index 6ea970825b..8d1299d92a 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_fd.h
@@ -16,7 +16,7 @@
#pragma once
-#include <android-base/unique_fd.h>
+#include <binder/unique_fd.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <vector>
@@ -27,6 +27,6 @@ namespace android {
// get a random FD for use in fuzzing, of a few different specific types
//
// may return multiple FDs (e.g. pipe), but will always return at least one
-std::vector<base::unique_fd> getRandomFds(FuzzedDataProvider* provider);
+std::vector<binder::unique_fd> getRandomFds(FuzzedDataProvider* provider);
} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
index 27587a9162..2812da79fa 100644
--- a/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel/fuzzbinder/random_parcel.h
@@ -27,7 +27,7 @@ namespace android {
struct RandomParcelOptions {
std::function<void(Parcel* p, FuzzedDataProvider& provider)> writeHeader;
std::vector<sp<IBinder>> extraBinders;
- std::vector<base::unique_fd> extraFds;
+ std::vector<binder::unique_fd> extraFds;
};
/**
diff --git a/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h b/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h
new file mode 100644
index 0000000000..694b68d25e
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/include_random_parcel_seeds/fuzzseeds/random_parcel_seeds.h
@@ -0,0 +1,46 @@
+/*
+ * 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 "../../../../file.h"
+
+#include <android-base/logging.h>
+
+#include <binder/Binder.h>
+#include <binder/Parcel.h>
+#include <binder/RecordedTransaction.h>
+
+#include <private/android_filesystem_config.h>
+
+#include <vector>
+
+using android::Parcel;
+using std::vector;
+
+namespace android {
+namespace impl {
+// computes the bytes so that if they are passed to FuzzedDataProvider and
+// provider.ConsumeIntegralInRange<T>(min, max) is called, it will return val
+template <typename T>
+void writeReversedBuffer(std::vector<std::byte>& integralBuffer, T min, T max, T val);
+
+// Calls writeInBuffer method with min and max numeric limits of type T. This method
+// is reversal of ConsumeIntegral<T>() in FuzzedDataProvider
+template <typename T>
+void writeReversedBuffer(std::vector<std::byte>& integralBuffer, T val);
+} // namespace impl
+void generateSeedsFromRecording(binder::borrowed_fd fd,
+ const binder::debug::RecordedTransaction& transaction);
+} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
index 8bef33f2ca..02e69cc371 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_driver.cpp
@@ -21,29 +21,61 @@
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
+#include <private/android_filesystem_config.h>
+
+using android::binder::unique_fd;
+
namespace android {
void fuzzService(const sp<IBinder>& binder, FuzzedDataProvider&& provider) {
- sp<IBinder> target;
+ fuzzService(std::vector<sp<IBinder>>{binder}, std::move(provider));
+}
+void fuzzService(const std::vector<sp<IBinder>>& binders, FuzzedDataProvider&& provider) {
RandomParcelOptions options{
- .extraBinders = {binder},
+ .extraBinders = binders,
.extraFds = {},
};
+ // Reserved bytes so that we don't have to change fuzzers and seed corpus if
+ // we introduce anything new in fuzzService.
+ std::vector<uint8_t> reservedBytes = provider.ConsumeBytes<uint8_t>(8);
+ (void)reservedBytes;
+
+ // always refresh the calling identity, because we sometimes set it below, but also,
+ // the code we're fuzzing might reset it
+ IPCThreadState::self()->clearCallingIdentity();
+
+ // Always take so that a perturbation of just the one ConsumeBool byte will always
+ // take the same path, but with a different UID. Without this, the fuzzer needs to
+ // guess both the change in value and the shift at the same time.
+ int64_t maybeSetUid = provider.PickValueInArray<int64_t>(
+ {static_cast<int64_t>(AID_ROOT) << 32, static_cast<int64_t>(AID_SYSTEM) << 32,
+ provider.ConsumeIntegralInRange<int64_t>(static_cast<int64_t>(AID_ROOT) << 32,
+ static_cast<int64_t>(AID_USER) << 32),
+ provider.ConsumeIntegral<int64_t>()});
+
if (provider.ConsumeBool()) {
// set calling uid
- IPCThreadState::self()->restoreCallingIdentity(provider.ConsumeIntegral<int64_t>());
+ IPCThreadState::self()->restoreCallingIdentity(maybeSetUid);
}
while (provider.remaining_bytes() > 0) {
// Most of the AIDL services will have small set of transaction codes.
+ // TODO(b/295942369) : Add remaining transact codes from IBinder.h
uint32_t code = provider.ConsumeBool() ? provider.ConsumeIntegral<uint32_t>()
- : provider.ConsumeIntegralInRange<uint32_t>(0, 100);
+ : provider.ConsumeBool()
+ ? provider.ConsumeIntegralInRange<uint32_t>(0, 100)
+ : provider.PickValueInArray<uint32_t>(
+ {IBinder::DUMP_TRANSACTION, IBinder::PING_TRANSACTION,
+ IBinder::SHELL_COMMAND_TRANSACTION, IBinder::INTERFACE_TRANSACTION,
+ IBinder::SYSPROPS_TRANSACTION, IBinder::EXTENSION_TRANSACTION,
+ IBinder::TWEET_TRANSACTION, IBinder::LIKE_TRANSACTION});
uint32_t flags = provider.ConsumeIntegral<uint32_t>();
Parcel data;
// for increased fuzz coverage
- data.setEnforceNoDataAvail(provider.ConsumeBool());
+ data.setEnforceNoDataAvail(false);
+ data.setServiceFuzzing();
sp<IBinder> target = options.extraBinders.at(
provider.ConsumeIntegralInRange<size_t>(0, options.extraBinders.size() - 1));
@@ -61,7 +93,8 @@ void fuzzService(const sp<IBinder>& binder, FuzzedDataProvider&& provider) {
Parcel reply;
// for increased fuzz coverage
- reply.setEnforceNoDataAvail(provider.ConsumeBool());
+ reply.setEnforceNoDataAvail(false);
+ reply.setServiceFuzzing();
(void)target->transact(code, data, &reply, flags);
// feed back in binders and fds that are returned from the service, so that
@@ -72,12 +105,11 @@ void fuzzService(const sp<IBinder>& binder, FuzzedDataProvider&& provider) {
retBinders.end());
auto retFds = reply.debugReadAllFileDescriptors();
for (size_t i = 0; i < retFds.size(); i++) {
- options.extraFds.push_back(base::unique_fd(dup(retFds[i])));
+ options.extraFds.push_back(unique_fd(dup(retFds[i])));
}
}
// invariants
-
auto ps = ProcessState::selfOrNull();
if (ps) {
CHECK_EQ(0, ps->getThreadPoolMaxTotalThreadCount())
diff --git a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
index a1fb70131e..84b9ff684f 100644
--- a/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
+++ b/libs/binder/tests/parcel_fuzzer/libbinder_ndk_driver.cpp
@@ -22,8 +22,20 @@
// and APEX users, but we need access to it to fuzz.
#include "../../ndk/ibinder_internal.h"
+using android::IBinder;
+using android::sp;
+
namespace android {
+void fuzzService(const std::vector<ndk::SpAIBinder>& binders, FuzzedDataProvider&& provider) {
+ std::vector<sp<IBinder>> cppBinders;
+ for (const auto& binder : binders) {
+ cppBinders.push_back(binder.get()->getBinder());
+ }
+
+ fuzzService(cppBinders, std::move(provider));
+}
+
void fuzzService(AIBinder* binder, FuzzedDataProvider&& provider) {
fuzzService(binder->getBinder(), std::move(provider));
}
@@ -32,9 +44,14 @@ void fuzzService(AIBinder* binder, FuzzedDataProvider&& provider) {
extern "C" {
// This API is used by fuzzers to automatically fuzz aidl services
-void fuzzRustService(void* binder, const uint8_t* data, size_t len) {
- AIBinder* aiBinder = static_cast<AIBinder*>(binder);
+void fuzzRustService(void** binders, size_t numBinders, const uint8_t* data, size_t len) {
+ std::vector<sp<IBinder>> cppBinders;
+ for (size_t binderIndex = 0; binderIndex < numBinders; ++binderIndex) {
+ AIBinder* aiBinder = static_cast<AIBinder*>(binders[binderIndex]);
+ cppBinders.push_back(aiBinder->getBinder());
+ }
+
FuzzedDataProvider provider(data, len);
- android::fuzzService(aiBinder, std::move(provider));
+ android::fuzzService(cppBinders, std::move(provider));
}
} // extern "C"
diff --git a/libs/binder/tests/parcel_fuzzer/main.cpp b/libs/binder/tests/parcel_fuzzer/main.cpp
index bef4ab6e99..5b1e9eac23 100644
--- a/libs/binder/tests/parcel_fuzzer/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/main.cpp
@@ -22,7 +22,6 @@
#include <iostream>
-#include <android-base/hex.h>
#include <android-base/logging.h>
#include <android/binder_auto_utils.h>
#include <android/binder_libbinder.h>
@@ -34,10 +33,12 @@
#include <sys/resource.h>
#include <sys/time.h>
+#include "../../Utils.h"
+
using android::fillRandomParcel;
using android::RandomParcelOptions;
using android::sp;
-using android::base::HexString;
+using android::HexString;
void fillRandomParcel(::android::hardware::Parcel* p, FuzzedDataProvider&& provider,
RandomParcelOptions* options) {
diff --git a/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
index f1079e9047..9884dbb1af 100644
--- a/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
+++ b/libs/binder/tests/parcel_fuzzer/parcelables/GenericDataParcelable.aidl
@@ -16,10 +16,23 @@
package parcelables;
parcelable GenericDataParcelable {
+ enum JustSomeEnum {
+ ONE_ENUMERATOR,
+ ANOTHER_ENUMERATOR,
+ MAYBE_ONE_MORE_ENUMERATOR,
+ }
+
+ const int COOL_CONSTANT = 0x1234;
+
int data;
float majorVersion;
float minorVersion;
IBinder binder;
ParcelFileDescriptor fileDescriptor;
int[] array;
-} \ No newline at end of file
+ String greatString;
+ @utf8InCpp
+ String greaterString;
+ @nullable String nullableString;
+ JustSomeEnum gretEnum = JustSomeEnum.ANOTHER_ENUMERATOR;
+}
diff --git a/libs/binder/tests/parcel_fuzzer/random_binder.cpp b/libs/binder/tests/parcel_fuzzer/random_binder.cpp
index 8a1fecb212..f41c35bfbe 100644
--- a/libs/binder/tests/parcel_fuzzer/random_binder.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_binder.cpp
@@ -21,56 +21,52 @@
#include <binder/IInterface.h>
#include <binder/IServiceManager.h>
+size_t kRandomInterfaceLength = 50;
namespace android {
-class RandomBinder : public BBinder {
-public:
- RandomBinder(const String16& descriptor, std::vector<uint8_t>&& bytes)
- : mDescriptor(descriptor),
- mBytes(std::move(bytes)),
- mProvider(mBytes.data(), mBytes.size()) {}
- const String16& getInterfaceDescriptor() const override { return mDescriptor; }
+RandomBinder::RandomBinder(const String16& descriptor, std::vector<uint8_t>&& bytes)
+ : mDescriptor(descriptor),
+ mBytes(std::move(bytes)),
+ mProvider(mBytes.data(), mBytes.size()) {}
- status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override {
- (void)code;
- (void)data;
- (void)reply;
- (void)flags; // note - for maximum coverage even ignore if oneway
+const String16& RandomBinder::getInterfaceDescriptor() const {
+ return mDescriptor;
+}
- if (mProvider.ConsumeBool()) {
- return mProvider.ConsumeIntegral<status_t>();
- }
+status_t RandomBinder::onTransact(uint32_t code, const Parcel& data, Parcel* reply,
+ uint32_t flags) {
+ (void)code;
+ (void)data;
+ (void)reply;
+ (void)flags; // note - for maximum coverage even ignore if oneway
- if (reply == nullptr) return OK;
+ if (mProvider.ConsumeBool()) {
+ return mProvider.ConsumeIntegral<status_t>();
+ }
- // TODO: things we could do to increase state space
- // - also pull FDs and binders from 'data'
- // (optionally combine these into random parcel 'options')
- // - also pull FDs and binders from random parcel 'options'
- RandomParcelOptions options;
+ if (reply == nullptr) return OK;
- // random output
- std::vector<uint8_t> subData = mProvider.ConsumeBytes<uint8_t>(
- mProvider.ConsumeIntegralInRange<size_t>(0, mProvider.remaining_bytes()));
- fillRandomParcel(reply, FuzzedDataProvider(subData.data(), subData.size()), &options);
+ // TODO: things we could do to increase state space
+ // - also pull FDs and binders from 'data'
+ // (optionally combine these into random parcel 'options')
+ // - also pull FDs and binders from random parcel 'options'
+ RandomParcelOptions options;
- return OK;
- }
+ // random output
+ std::vector<uint8_t> subData = mProvider.ConsumeBytes<uint8_t>(
+ mProvider.ConsumeIntegralInRange<size_t>(0, mProvider.remaining_bytes()));
+ fillRandomParcel(reply, FuzzedDataProvider(subData.data(), subData.size()), &options);
-private:
- String16 mDescriptor;
-
- // note may not all be used
- std::vector<uint8_t> mBytes;
- FuzzedDataProvider mProvider;
-};
+ return OK;
+}
sp<IBinder> getRandomBinder(FuzzedDataProvider* provider) {
auto makeFunc = provider->PickValueInArray<const std::function<sp<IBinder>()>>({
[&]() {
// descriptor is the length of a class name, e.g.
// "some.package.Foo"
- std::string str = provider->ConsumeRandomLengthString(100 /*max length*/);
+ std::string str =
+ provider->ConsumeRandomLengthString(kRandomInterfaceLength /*max length*/);
// arbitrarily consume remaining data to create a binder that can return
// random results - coverage guided fuzzer should ensure all of the remaining
diff --git a/libs/binder/tests/parcel_fuzzer/random_fd.cpp b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
index e4dbb2d955..c7d15337b5 100644
--- a/libs/binder/tests/parcel_fuzzer/random_fd.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_fd.cpp
@@ -23,46 +23,73 @@
namespace android {
-using base::unique_fd;
+using binder::unique_fd;
std::vector<unique_fd> getRandomFds(FuzzedDataProvider* provider) {
const char* fdType;
std::vector<unique_fd> fds = provider->PickValueInArray<
- std::function<std::vector<unique_fd>()>>({
- [&]() {
- fdType = "ashmem";
- std::vector<unique_fd> ret;
- ret.push_back(unique_fd(
- ashmem_create_region("binder test region",
- provider->ConsumeIntegralInRange<size_t>(0, 4096))));
- return ret;
- },
- [&]() {
- fdType = "/dev/null";
- std::vector<unique_fd> ret;
- ret.push_back(unique_fd(open("/dev/null", O_RDWR)));
- return ret;
- },
- [&]() {
- fdType = "pipefd";
-
- int pipefds[2];
-
- int flags = O_CLOEXEC;
- if (provider->ConsumeBool()) flags |= O_DIRECT;
- if (provider->ConsumeBool()) flags |= O_NONBLOCK;
-
- CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
-
- if (provider->ConsumeBool()) std::swap(pipefds[0], pipefds[1]);
-
- std::vector<unique_fd> ret;
- ret.push_back(unique_fd(pipefds[0]));
- ret.push_back(unique_fd(pipefds[1]));
- return ret;
- },
- })();
+ std::function<std::vector<unique_fd>()>>(
+ {[&]() {
+ fdType = "ashmem";
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(
+ ashmem_create_region("binder test region",
+ provider->ConsumeIntegralInRange<size_t>(0, 4096))));
+ return ret;
+ },
+ [&]() {
+ fdType = "/dev/null";
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(open("/dev/null", O_RDWR)));
+ return ret;
+ },
+ [&]() {
+ fdType = "pipefd";
+
+ int pipefds[2];
+
+ int flags = O_CLOEXEC;
+ if (provider->ConsumeBool()) flags |= O_DIRECT;
+
+ // TODO(b/236812909): also test blocking
+ if (true) flags |= O_NONBLOCK;
+
+ CHECK_EQ(0, pipe2(pipefds, flags)) << flags;
+
+ if (provider->ConsumeBool()) std::swap(pipefds[0], pipefds[1]);
+
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(pipefds[0]));
+ ret.push_back(unique_fd(pipefds[1]));
+ return ret;
+ },
+ [&]() {
+ fdType = "tempfd";
+ char name[PATH_MAX];
+#if defined(__ANDROID__)
+ snprintf(name, sizeof(name), "/data/local/tmp/android-tempfd-test-%d-XXXXXX",
+ getpid());
+#else
+ snprintf(name, sizeof(name), "/tmp/android-tempfd-test-%d-XXXXXX", getpid());
+#endif
+ int fd = mkstemp(name);
+ CHECK_NE(fd, -1) << "Failed to create file " << name << ", errno: " << errno;
+ unlink(name);
+ if (provider->ConsumeBool()) {
+ CHECK_NE(TEMP_FAILURE_RETRY(
+ ftruncate(fd,
+ provider->ConsumeIntegralInRange<size_t>(0, 4096))),
+ -1)
+ << "Failed to truncate file, errno: " << errno;
+ }
+
+ std::vector<unique_fd> ret;
+ ret.push_back(unique_fd(fd));
+ return ret;
+ }
+
+ })();
for (const auto& fd : fds) CHECK(fd.ok()) << fd.get() << " " << fdType;
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
index f0beed234b..62b84330cd 100644
--- a/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel.cpp
@@ -23,6 +23,8 @@
#include <fuzzbinder/random_fd.h>
#include <utils/String16.h>
+using android::binder::unique_fd;
+
namespace android {
static void fillRandomParcelData(Parcel* p, FuzzedDataProvider&& provider) {
@@ -66,8 +68,13 @@ void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOpti
},
// write FD
[&]() {
- if (options->extraFds.size() > 0 && provider.ConsumeBool()) {
- const base::unique_fd& fd = options->extraFds.at(
+ // b/296516864 - Limit number of objects written to a parcel.
+ if (p->objectsCount() > 100) {
+ return;
+ }
+
+ if (provider.ConsumeBool() && options->extraFds.size() > 0) {
+ const unique_fd& fd = options->extraFds.at(
provider.ConsumeIntegralInRange<size_t>(0,
options->extraFds.size() -
1));
@@ -78,11 +85,10 @@ void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOpti
return;
}
- std::vector<base::unique_fd> fds = getRandomFds(&provider);
+ std::vector<unique_fd> fds = getRandomFds(&provider);
CHECK(OK ==
p->writeFileDescriptor(fds.begin()->release(),
true /*takeOwnership*/));
-
options->extraFds.insert(options->extraFds.end(),
std::make_move_iterator(fds.begin() + 1),
std::make_move_iterator(fds.end()));
@@ -90,8 +96,13 @@ void fillRandomParcel(Parcel* p, FuzzedDataProvider&& provider, RandomParcelOpti
},
// write binder
[&]() {
+ // b/296516864 - Limit number of objects written to a parcel.
+ if (p->objectsCount() > 100) {
+ return;
+ }
+
sp<IBinder> binder;
- if (options->extraBinders.size() > 0 && provider.ConsumeBool()) {
+ if (provider.ConsumeBool() && options->extraBinders.size() > 0) {
binder = options->extraBinders.at(
provider.ConsumeIntegralInRange<size_t>(0,
options->extraBinders
diff --git a/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
new file mode 100644
index 0000000000..fd9777a916
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/random_parcel_seeds.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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 <linux/android/binder.h>
+
+#include <android-base/logging.h>
+
+#include <binder/Parcel.h>
+#include <binder/RecordedTransaction.h>
+
+#include <fuzzseeds/random_parcel_seeds.h>
+
+#include <stack>
+#include <string>
+#include "../../file.h"
+
+using android::binder::borrowed_fd;
+using android::binder::WriteFully;
+using std::stack;
+
+extern size_t kRandomInterfaceLength;
+// Keep this in sync with max_length in random_binder.cpp while creating a RandomBinder
+std::string kRandomInterfaceName(kRandomInterfaceLength, 'i');
+
+namespace android {
+namespace impl {
+template <typename T>
+std::vector<uint8_t> reverseBytes(T min, T max, T val) {
+ uint64_t range = static_cast<uint64_t>(max) - min;
+ uint64_t result = val - min;
+ size_t offset = 0;
+
+ std::vector<uint8_t> reverseData;
+ uint8_t reversed = 0;
+ reversed |= result;
+
+ while (offset < sizeof(T) * CHAR_BIT && (range >> offset) > 0) {
+ reverseData.push_back(reversed);
+ reversed = 0;
+ reversed |= (result >> CHAR_BIT);
+ result = result >> CHAR_BIT;
+ offset += CHAR_BIT;
+ }
+
+ return std::move(reverseData);
+}
+
+template <typename T>
+void writeReversedBuffer(std::vector<uint8_t>& integralBuffer, T min, T max, T val) {
+ std::vector<uint8_t> reversedData = reverseBytes(min, max, val);
+ // ConsumeIntegral Calls read buffer from the end. Keep inserting at the front of the buffer
+ // so that we can align fuzzService operations with seed generation for readability.
+ integralBuffer.insert(integralBuffer.begin(), reversedData.begin(), reversedData.end());
+}
+
+template <typename T>
+void writeReversedBuffer(std::vector<uint8_t>& integralBuffer, T val) {
+ // For ConsumeIntegral<T>() calls, FuzzedDataProvider uses numeric limits min and max
+ // as range
+ writeReversedBuffer(integralBuffer, std::numeric_limits<T>::min(),
+ std::numeric_limits<T>::max(), val);
+}
+
+} // namespace impl
+
+struct ProviderMetadata {
+ size_t position;
+ size_t value;
+
+ ProviderMetadata() {
+ value = 0;
+ position = 0;
+ }
+};
+
+// Assuming current seed path is inside the fillRandomParcel function, start of the loop.
+void writeRandomBinder(borrowed_fd fd, vector<uint8_t>& fillParcelBuffer,
+ stack<ProviderMetadata>& remainingPositions) {
+ // Choose 2 index in array
+ size_t fillFuncIndex = 2;
+ impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2),
+ fillFuncIndex);
+
+ // navigate to getRandomBinder. provide consume bool false
+ bool flag = false;
+ impl::writeReversedBuffer(fillParcelBuffer, flag);
+
+ // selecting RandomBinder, other binders in the list are not recorded as KernelObjects
+ size_t randomBinderIndex = 0;
+ impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2),
+ randomBinderIndex);
+
+ // write random string of length 100 in actual buffer array.
+ CHECK(WriteFully(fd, kRandomInterfaceName.c_str(), kRandomInterfaceName.size())) << fd.get();
+
+ // These will be bytes which are used inside of RandomBinder
+ // simplest path for these bytes is going to be consume bool -> return random status
+ vector<uint8_t> randomBinderBuffer;
+
+ bool returnRandomInt = true;
+ impl::writeReversedBuffer(randomBinderBuffer, returnRandomInt);
+
+ status_t randomStatus = 0;
+ impl::writeReversedBuffer(randomBinderBuffer, randomStatus);
+
+ // write integral in range to consume bytes for random binder
+ ProviderMetadata providerData;
+ providerData.position = fillParcelBuffer.size();
+ providerData.value = randomBinderBuffer.size();
+ remainingPositions.push(providerData);
+
+ // Write to fd
+ CHECK(WriteFully(fd, randomBinderBuffer.data(), randomBinderBuffer.size())) << fd.get();
+}
+
+// Assuming current seed path is inside the fillRandomParcelFunction, start of the loop.
+void writeRandomFd(vector<uint8_t>& fillParcelBuffer) {
+ // path to random fd
+ size_t fillFuncIndex = 1;
+ impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2),
+ fillFuncIndex);
+
+ bool flag = false;
+ impl::writeReversedBuffer(fillParcelBuffer, flag);
+
+ // go for /dev/null index 1
+ size_t fdIndex = 1;
+ impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(3),
+ fdIndex);
+}
+
+void writeParcelData(borrowed_fd fd, vector<uint8_t>& fillParcelBuffer,
+ stack<ProviderMetadata>& remainingPositions, const uint8_t* data, size_t start,
+ size_t length) {
+ // need to write parcel data till next offset with instructions to pick random bytes till offset
+ size_t fillFuncIndex = 0;
+ impl::writeReversedBuffer(fillParcelBuffer, static_cast<size_t>(0), static_cast<size_t>(2),
+ fillFuncIndex);
+
+ // provide how much bytes to read in control buffer
+ ProviderMetadata providerData;
+ providerData.position = fillParcelBuffer.size();
+ providerData.value = length;
+ remainingPositions.push(providerData);
+
+ // provide actual bytes
+ CHECK(WriteFully(fd, data + start, length)) << fd.get();
+}
+
+/**
+ * Generate sequence of copy data, write fd and write binder instructions and required data.
+ * Data which will be read using consumeBytes is written to fd directly. Data which is read in
+ * form integer is consumed from rear end FuzzedDataProvider. So insert it in fillParcelBuffer and
+ * then write to fd
+ */
+size_t regenerateParcel(borrowed_fd fd, vector<uint8_t>& fillParcelBuffer, const Parcel& p,
+ size_t dataSize, const vector<uint64_t>& objectOffsets) {
+ stack<ProviderMetadata> remainingPositions;
+ size_t copiedDataPosition = 0;
+ const uint8_t* parcelData = p.data();
+ size_t numBinders = 0;
+ size_t numFds = 0;
+
+ for (auto offset : objectOffsets) {
+ // Check what type of object is present here
+ const flat_binder_object* flatObject =
+ reinterpret_cast<const flat_binder_object*>(parcelData + offset);
+ // Copy till the object offset
+ writeParcelData(fd, fillParcelBuffer, remainingPositions, parcelData, copiedDataPosition,
+ offset - copiedDataPosition);
+ copiedDataPosition = offset;
+ if (flatObject->hdr.type == BINDER_TYPE_BINDER ||
+ flatObject->hdr.type == BINDER_TYPE_HANDLE) {
+ writeRandomBinder(fd, fillParcelBuffer, remainingPositions);
+ numBinders++;
+ // In case of binder, stability is written after the binder object.
+ // We want to move the copiedDataPosition further to account for this stability field
+ copiedDataPosition += sizeof(int32_t) + sizeof(flat_binder_object);
+ } else if (flatObject->hdr.type == BINDER_TYPE_FD) {
+ writeRandomFd(fillParcelBuffer);
+ numFds++;
+ copiedDataPosition += sizeof(flat_binder_object);
+ }
+ }
+
+ if (copiedDataPosition < dataSize) {
+ // copy remaining data from recorded parcel -> last Object to end of the data
+ writeParcelData(fd, fillParcelBuffer, remainingPositions, parcelData, copiedDataPosition,
+ dataSize - copiedDataPosition);
+ }
+
+ // We need to write bytes for selecting integer within range of 0 to provide.remaining_bytes()
+ // is called.
+ size_t totalWrittenBytes = dataSize - (sizeof(flat_binder_object) * objectOffsets.size()) -
+ (sizeof(int32_t) * numBinders) +
+ (kRandomInterfaceName.size() /*Interface String*/ + sizeof(bool) + sizeof(status_t)) *
+ numBinders;
+
+ // Code in fuzzService relies on provider.remaining_bytes() to select random bytes using
+ // consume integer. use the calculated remaining_bytes to generate byte buffer which can
+ // generate required fds and binders in fillRandomParcel function.
+ while (!remainingPositions.empty()) {
+ auto meta = remainingPositions.top();
+ remainingPositions.pop();
+ size_t remainingBytes = totalWrittenBytes + fillParcelBuffer.size() - meta.position;
+
+ vector<uint8_t> remReversedBytes;
+ impl::writeReversedBuffer(remReversedBytes, static_cast<size_t>(0), remainingBytes,
+ meta.value);
+ // Check the order of buffer which is being written
+ fillParcelBuffer.insert(fillParcelBuffer.end() - meta.position, remReversedBytes.begin(),
+ remReversedBytes.end());
+ }
+
+ return totalWrittenBytes;
+}
+
+/**
+ * Current corpus format
+ * |Reserved bytes(8)|parcel data|fillParcelBuffer|integralBuffer|
+ */
+void generateSeedsFromRecording(borrowed_fd fd,
+ const binder::debug::RecordedTransaction& transaction) {
+ // Write Reserved bytes for future use
+ std::vector<uint8_t> reservedBytes(8);
+ CHECK(WriteFully(fd, reservedBytes.data(), reservedBytes.size())) << fd.get();
+
+ std::vector<uint8_t> integralBuffer;
+
+ // Write UID array : Array elements are initialized in the order that they are declared
+ // UID array index 2 element
+ // int64_t aidRoot = 0;
+ impl::writeReversedBuffer(integralBuffer, static_cast<int64_t>(AID_ROOT) << 32,
+ static_cast<int64_t>(AID_USER) << 32,
+ static_cast<int64_t>(AID_ROOT) << 32);
+
+ // UID array index 3 element
+ impl::writeReversedBuffer(integralBuffer, static_cast<int64_t>(AID_ROOT) << 32);
+
+ // always pick AID_ROOT -> index 0
+ size_t uidIndex = 0;
+ impl::writeReversedBuffer(integralBuffer, static_cast<size_t>(0), static_cast<size_t>(3),
+ uidIndex);
+
+ // Never set uid in seed corpus
+ uint8_t writeUid = 0;
+ impl::writeReversedBuffer(integralBuffer, writeUid);
+
+ // Read random code. this will be from recorded transaction
+ uint8_t selectCode = 1;
+ impl::writeReversedBuffer(integralBuffer, selectCode);
+
+ // Get from recorded transaction
+ uint32_t code = transaction.getCode();
+ impl::writeReversedBuffer(integralBuffer, code);
+
+ // Get from recorded transaction
+ uint32_t flags = transaction.getFlags();
+ impl::writeReversedBuffer(integralBuffer, flags);
+
+ // always fuzz primary binder
+ size_t extraBindersIndex = 0;
+ impl::writeReversedBuffer(integralBuffer, static_cast<size_t>(0), static_cast<size_t>(0),
+ extraBindersIndex);
+
+ const Parcel& dataParcel = transaction.getDataParcel();
+
+ // This buffer holds the bytes which will be used for fillRandomParcel API
+ std::vector<uint8_t> fillParcelBuffer;
+
+ // Don't take rpc path
+ uint8_t rpcBranch = 0;
+ impl::writeReversedBuffer(fillParcelBuffer, rpcBranch);
+
+ // Implicit branch on this path -> options->writeHeader(p, provider)
+ uint8_t writeHeaderInternal = 0;
+ impl::writeReversedBuffer(fillParcelBuffer, writeHeaderInternal);
+
+ auto objectMetadata = transaction.getObjectOffsets();
+ size_t toWrite = regenerateParcel(fd, fillParcelBuffer, dataParcel, dataParcel.dataBufferSize(),
+ objectMetadata);
+
+ // Write Fill Parcel buffer size in integralBuffer so that fuzzService knows size of data
+ size_t subDataSize = toWrite + fillParcelBuffer.size();
+ impl::writeReversedBuffer(integralBuffer, static_cast<size_t>(0), subDataSize, subDataSize);
+
+ // Write fill parcel buffer
+ CHECK(WriteFully(fd, fillParcelBuffer.data(), fillParcelBuffer.size())) << fd.get();
+
+ // Write the integralBuffer to data
+ CHECK(WriteFully(fd, integralBuffer.data(), integralBuffer.size())) << fd.get();
+}
+} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
new file mode 100644
index 0000000000..690c39afc9
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/Android.bp
@@ -0,0 +1,64 @@
+package {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+aidl_interface {
+ name: "testServiceIface",
+ host_supported: true,
+ unstable: true,
+ srcs: [
+ "ITestService.aidl",
+ ],
+ backend: {
+ java: {
+ enabled: true,
+ platform_apis: true,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+// Adding this fuzzer to test the fuzzService functionality
+cc_fuzz {
+ name: "test_service_fuzzer_should_crash",
+ defaults: [
+ "service_fuzzer_defaults",
+ ],
+ static_libs: [
+ "liblog",
+ "testServiceIface-cpp",
+ ],
+ host_supported: true,
+ srcs: ["TestServiceFuzzer.cpp"],
+ fuzz_config: {
+ triage_assignee: "waghpawan@google.com",
+
+ // This fuzzer should be used only test fuzzService locally
+ fuzz_on_haiku_host: false,
+ fuzz_on_haiku_device: false,
+ },
+}
+
+sh_test_host {
+ name: "fuzz_service_test",
+ src: "run_fuzz_service_test.sh",
+ filename: "run_fuzz_service_test.sh",
+ test_config: "fuzz_service_test_config.xml",
+ data_bins: [
+ "test_service_fuzzer_should_crash",
+ ],
+ required: [
+ "test_service_fuzzer_should_crash",
+ ],
+ target: {
+ linux_bionic: {
+ enabled: false,
+ },
+ darwin: {
+ enabled: false,
+ },
+ },
+ test_suites: ["general-tests"],
+}
diff --git a/services/inputflinger/host/main.cpp b/libs/binder/tests/parcel_fuzzer/test_fuzzer/ITestService.aidl
index 0a517cc5d9..5089ae5004 100644
--- a/services/inputflinger/host/main.cpp
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/ITestService.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2013 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,13 +14,13 @@
* limitations under the License.
*/
-#include <binder/BinderService.h>
-#include "InputFlinger.h"
+interface ITestService {
-using namespace android;
+ void setIntData(int input);
-int main(int, char**) {
- ProcessState::self()->setThreadPoolMaxThreadCount(4);
- BinderService<InputFlinger>::publishAndJoinThreadPool(true);
- return 0;
+ void setCharData(char input);
+
+ void setBooleanData(boolean input);
+
+ void setService(ITestService service);
}
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/TestServiceFuzzer.cpp b/libs/binder/tests/parcel_fuzzer/test_fuzzer/TestServiceFuzzer.cpp
new file mode 100644
index 0000000000..d2fa581822
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/TestServiceFuzzer.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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 <BnTestService.h>
+#include <fuzzbinder/libbinder_driver.h>
+
+#include <binder/IPCThreadState.h>
+#include <log/log.h>
+
+#include <private/android_filesystem_config.h>
+
+using android::binder::Status;
+
+namespace android {
+
+enum class CrashType {
+ NONE,
+ ON_PLAIN,
+ ON_BINDER,
+ ON_KNOWN_UID,
+ ON_SYSTEM_AID,
+ ON_ROOT_AID,
+ ON_DUMP_TRANSACT,
+ ON_SHELL_CMD_TRANSACT,
+ CRASH_ALWAYS,
+};
+
+// This service is to verify that fuzzService is functioning properly
+class TestService : public BnTestService {
+public:
+ TestService(CrashType crash) : mCrash(crash) {}
+
+ void onData() {
+ switch (mCrash) {
+ case CrashType::ON_PLAIN: {
+ LOG_ALWAYS_FATAL("Expected crash, PLAIN.");
+ break;
+ }
+ case CrashType::ON_KNOWN_UID: {
+ if (IPCThreadState::self()->getCallingUid() == getuid()) {
+ LOG_ALWAYS_FATAL("Expected crash, KNOWN_UID.");
+ }
+ break;
+ }
+ case CrashType::ON_SYSTEM_AID: {
+ if (IPCThreadState::self()->getCallingUid() == AID_SYSTEM) {
+ LOG_ALWAYS_FATAL("Expected crash, AID_SYSTEM.");
+ }
+ break;
+ }
+ case CrashType::ON_ROOT_AID: {
+ if (IPCThreadState::self()->getCallingUid() == AID_ROOT) {
+ LOG_ALWAYS_FATAL("Expected crash, AID_ROOT.");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ Status setIntData(int /*input*/) override {
+ onData();
+ return Status::ok();
+ }
+
+ Status setCharData(char16_t /*input*/) override {
+ onData();
+ return Status::ok();
+ }
+
+ Status setBooleanData(bool /*input*/) override {
+ onData();
+ return Status::ok();
+ }
+
+ Status setService(const sp<ITestService>& service) override {
+ onData();
+ if (mCrash == CrashType::ON_BINDER && service != nullptr) {
+ LOG_ALWAYS_FATAL("Expected crash, BINDER.");
+ }
+ return Status::ok();
+ }
+
+ status_t onTransact(uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags) override {
+ if (mCrash == CrashType::ON_DUMP_TRANSACT && code == DUMP_TRANSACTION) {
+ LOG_ALWAYS_FATAL("Expected crash, DUMP.");
+ } else if (mCrash == CrashType::ON_SHELL_CMD_TRANSACT &&
+ code == SHELL_COMMAND_TRANSACTION) {
+ LOG_ALWAYS_FATAL("Expected crash, SHELL_CMD.");
+ }
+ return BnTestService::onTransact(code, data, reply, flags);
+ }
+
+private:
+ CrashType mCrash;
+};
+
+CrashType gCrashType = CrashType::NONE;
+
+extern "C" int LLVMFuzzerInitialize(int* argc, char*** argv) {
+ if (*argc < 2) {
+ // This fuzzer is also used as test fuzzer to check infra pipeline.
+ // It should always run and find a crash in TestService.
+ gCrashType = CrashType::CRASH_ALWAYS;
+ return 0;
+ }
+
+ std::string arg = std::string((*argv)[1]);
+
+ // ignore first argument, because we consume it
+ (*argv)[1] = (*argv[0]);
+ (*argc)--;
+ (*argv)++;
+
+ if (arg == "PLAIN") {
+ gCrashType = CrashType::ON_PLAIN;
+ } else if (arg == "KNOWN_UID") {
+ gCrashType = CrashType::ON_KNOWN_UID;
+ } else if (arg == "AID_SYSTEM") {
+ gCrashType = CrashType::ON_SYSTEM_AID;
+ } else if (arg == "AID_ROOT") {
+ gCrashType = CrashType::ON_ROOT_AID;
+ } else if (arg == "BINDER") {
+ gCrashType = CrashType::ON_BINDER;
+ } else if (arg == "DUMP") {
+ gCrashType = CrashType::ON_DUMP_TRANSACT;
+ } else if (arg == "SHELL_CMD") {
+ gCrashType = CrashType::ON_SHELL_CMD_TRANSACT;
+ } else {
+ printf("INVALID ARG\n");
+ exit(0); // success because this is a crash test
+ }
+
+ return 0;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ if (gCrashType == CrashType::CRASH_ALWAYS) {
+ LOG_ALWAYS_FATAL("Expected crash, This fuzzer will always crash.");
+ }
+ auto service = sp<TestService>::make(gCrashType);
+ fuzzService(service, FuzzedDataProvider(data, size));
+ return 0;
+}
+
+} // namespace android
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/fuzz_service_test_config.xml b/libs/binder/tests/parcel_fuzzer/test_fuzzer/fuzz_service_test_config.xml
new file mode 100644
index 0000000000..19eb33a635
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/fuzz_service_test_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs fuzzService test">
+ <option name="null-device" value="true" />
+ <test class="com.android.tradefed.testtype.binary.ExecutableHostTest" >
+ <option name="binary" value="run_fuzz_service_test.sh"/>
+ <option name="relative-path-execution" value="true" />
+ </test>
+</configuration>
diff --git a/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
new file mode 100755
index 0000000000..5d68fe172e
--- /dev/null
+++ b/libs/binder/tests/parcel_fuzzer/test_fuzzer/run_fuzz_service_test.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+# 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.
+
+color_success=$'\E'"[0;32m"
+color_failed=$'\E'"[0;31m"
+color_reset=$'\E'"[00m"
+
+FUZZER_NAME=test_service_fuzzer_should_crash
+FUZZER_OUT=fuzzer-output
+
+if [ ! -f "$FUZZER_NAME" ]
+then
+ echo -e "${color_failed}Binary $FUZZER_NAME does not exist"
+ echo "${color_reset}"
+ exit 1
+fi
+
+for CRASH_TYPE in PLAIN KNOWN_UID AID_SYSTEM AID_ROOT BINDER DUMP SHELL_CMD; do
+ echo "INFO: Running fuzzer : test_service_fuzzer_should_crash $CRASH_TYPE"
+
+ ./test_service_fuzzer_should_crash "$CRASH_TYPE" -max_total_time=60 &>"$FUZZER_OUT"
+
+ echo "INFO: Searching fuzzer output for expected crashes"
+ if grep -q "Expected crash, $CRASH_TYPE." "$FUZZER_OUT"
+ then
+ echo -e "${color_success}Success: Found expected crash. fuzzService test successful!"
+ else
+ echo -e "${color_failed}Failed: Unable to find successful fuzzing output from test_service_fuzzer_should_crash"
+ echo "${color_reset}"
+ exit 1
+ fi
+done
diff --git a/libs/binder/tests/rpc_fuzzer/Android.bp b/libs/binder/tests/rpc_fuzzer/Android.bp
index 71e847fd1e..ab72bfd2b5 100644
--- a/libs/binder/tests/rpc_fuzzer/Android.bp
+++ b/libs/binder/tests/rpc_fuzzer/Android.bp
@@ -25,13 +25,14 @@ cc_fuzz {
"libbase",
"libcutils",
"liblog",
+ "libbinder_test_utils",
"libbinder_tls_static",
"libbinder_tls_test_utils",
"libssl_fuzz_unsafe",
"libcrypto_fuzz_unsafe",
],
cflags: [
- "-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE" // for RAND_reset_for_fuzzing
+ "-DBORINGSSL_UNSAFE_DETERMINISTIC_MODE", // for RAND_reset_for_fuzzing
],
target: {
android: {
diff --git a/libs/binder/tests/rpc_fuzzer/main.cpp b/libs/binder/tests/rpc_fuzzer/main.cpp
index b8ae84d5c0..50fc2f21f1 100644
--- a/libs/binder/tests/rpc_fuzzer/main.cpp
+++ b/libs/binder/tests/rpc_fuzzer/main.cpp
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#include <android-base/file.h>
+#include "../FileUtils.h"
+
#include <android-base/logging.h>
-#include <android-base/unique_fd.h>
#include <binder/Binder.h>
#include <binder/Parcel.h>
#include <binder/RpcServer.h>
@@ -24,13 +24,18 @@
#include <binder/RpcTransport.h>
#include <binder/RpcTransportRaw.h>
#include <binder/RpcTransportTls.h>
+#include <binder/unique_fd.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <openssl/rand.h>
#include <openssl/ssl.h>
#include <sys/resource.h>
+#include <sys/socket.h>
#include <sys/un.h>
+using android::binder::GetExecutableDirectory;
+using android::binder::unique_fd;
+
namespace android {
static const std::string kSock = std::string(getenv("TMPDIR") ?: "/tmp") +
@@ -76,12 +81,12 @@ struct ServerAuth {
ServerAuth readServerKeyAndCert() {
ServerAuth ret;
- auto keyPath = android::base::GetExecutableDirectory() + "/data/server.key";
+ auto keyPath = GetExecutableDirectory() + "/data/server.key";
bssl::UniquePtr<BIO> keyBio(BIO_new_file(keyPath.c_str(), "r"));
ret.pkey.reset(PEM_read_bio_PrivateKey(keyBio.get(), nullptr, passwordCallback, nullptr));
CHECK_NE(ret.pkey.get(), nullptr);
- auto certPath = android::base::GetExecutableDirectory() + "/data/server.crt";
+ auto certPath = GetExecutableDirectory() + "/data/server.crt";
bssl::UniquePtr<BIO> certBio(BIO_new_file(certPath.c_str(), "r"));
ret.cert.reset(PEM_read_bio_X509(certBio.get(), nullptr, nullptr, nullptr));
CHECK_NE(ret.cert.get(), nullptr);
@@ -129,18 +134,18 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
CHECK_LT(kSock.size(), sizeof(addr.sun_path));
memcpy(&addr.sun_path, kSock.c_str(), kSock.size());
- std::vector<base::unique_fd> connections;
+ std::vector<unique_fd> connections;
bool hangupBeforeShutdown = provider.ConsumeBool();
// b/260736889 - limit arbitrarily, due to thread resource exhaustion, which currently
// aborts. Servers should consider RpcServer::setConnectionFilter instead.
- constexpr size_t kMaxConnections = 1000;
+ constexpr size_t kMaxConnections = 10;
while (provider.remaining_bytes() > 0) {
if (connections.empty() ||
(connections.size() < kMaxConnections && provider.ConsumeBool())) {
- base::unique_fd fd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
+ unique_fd fd(TEMP_FAILURE_RETRY(socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)));
CHECK_NE(fd.get(), -1);
CHECK_EQ(0,
TEMP_FAILURE_RETRY(
diff --git a/libs/binder/tests/schd-dbg.cpp b/libs/binder/tests/schd-dbg.cpp
index 0035e4ee5a..d3cd528e26 100644
--- a/libs/binder/tests/schd-dbg.cpp
+++ b/libs/binder/tests/schd-dbg.cpp
@@ -340,7 +340,10 @@ void worker_fx(int num, int no_process, int iterations, int payload_size,
for (int i = 0; i < server_count; i++) {
// self service is in-process so just skip
if (num == i) continue;
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
workers.push_back(serviceMgr->getService(generateServiceName(i)));
+#pragma clang diagnostic pop
}
// Client for each pair iterates here
diff --git a/libs/binder/tests/unit_fuzzers/Android.bp b/libs/binder/tests/unit_fuzzers/Android.bp
index 8ea948cc15..6871cca3c1 100644
--- a/libs/binder/tests/unit_fuzzers/Android.bp
+++ b/libs/binder/tests/unit_fuzzers/Android.bp
@@ -52,6 +52,18 @@ cc_defaults {
enabled: false,
},
},
+ fuzz_config: {
+ cc: [
+ "smoreland@google.com",
+ "waghpawan@google.com",
+ ],
+ componentid: 32456,
+ description: "The fuzzer targets the APIs of libbinder",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
+ },
}
cc_fuzz {
@@ -104,3 +116,42 @@ cc_fuzz {
defaults: ["binder_fuzz_defaults"],
srcs: ["MemoryDealerFuzz.cpp"],
}
+
+cc_fuzz {
+ name: "binder_recordedTransactionFileFuzz",
+ defaults: ["binder_fuzz_defaults"],
+ srcs: ["RecordedTransactionFileFuzz.cpp"],
+ corpus: [
+ "recorded_transaction_corpus/*",
+ ],
+}
+
+cc_fuzz {
+ name: "binder_recordedTransactionFuzz",
+ defaults: ["binder_fuzz_defaults"],
+ srcs: ["RecordedTransactionFuzz.cpp"],
+ target: {
+ android: {
+ shared_libs: [
+ "libcutils",
+ "libutils",
+ "libbase",
+ "libbinder",
+ ],
+ static_libs: ["libbinder_random_parcel"],
+ },
+ host: {
+ static_libs: [
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libbase",
+ "libbinder",
+ "libbinder_random_parcel",
+ ],
+ },
+ darwin: {
+ enabled: false,
+ },
+ },
+}
diff --git a/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h b/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
index 8d2b714b5c..993418adcc 100644
--- a/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
+++ b/libs/binder/tests/unit_fuzzers/BinderFuzzFunctions.h
@@ -74,7 +74,7 @@ static const std::vector<std::function<void(FuzzedDataProvider*, const sp<BBinde
bbinder->getDebugPid();
},
[](FuzzedDataProvider*, const sp<BBinder>& bbinder) -> void {
- (void)bbinder->setRpcClientDebug(android::base::unique_fd(),
+ (void)bbinder->setRpcClientDebug(binder::unique_fd(),
sp<BBinder>::make());
}};
diff --git a/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp b/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
index 910c9dc25c..a6fd487fe5 100644
--- a/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/BpBinderFuzz.cpp
@@ -51,8 +51,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
sp<RpcSession> session = RpcSession::make();
session->setMaxIncomingThreads(1);
status_t status;
- for (size_t tries = 0; tries < 5; tries++) {
- usleep(10000);
+
+ // b/274084938 - ASAN may be slow, wait a while
+ for (size_t tries = 0; tries < 50; tries++) {
+ usleep(100000);
status = session->setupUnixDomainClient(addr.c_str());
if (status == OK) break;
}
diff --git a/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h b/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h
index 5079431058..83d0ca745f 100644
--- a/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h
+++ b/libs/binder/tests/unit_fuzzers/BpBinderFuzzFunctions.h
@@ -26,7 +26,6 @@
#include <binder/Parcel.h>
#include <binder/Stability.h>
-#include <cutils/compiler.h>
#include <utils/KeyedVector.h>
#include <utils/Log.h>
#include <utils/Mutex.h>
@@ -96,14 +95,16 @@ static const std::vector<std::function<void(FuzzedDataProvider*, const sp<BpBind
},
[](FuzzedDataProvider*, const sp<BpBinder>& bpbinder,
const sp<IBinder::DeathRecipient>&) -> void {
- binder_proxy_limit_callback cb = binder_proxy_limit_callback();
- bpbinder->setLimitCallback(cb);
+ binder_proxy_limit_callback cbl = binder_proxy_limit_callback();
+ binder_proxy_warning_callback cbw = binder_proxy_warning_callback();
+ bpbinder->setBinderProxyCountEventCallback(cbl, cbw);
},
[](FuzzedDataProvider* fdp, const sp<BpBinder>& bpbinder,
const sp<IBinder::DeathRecipient>&) -> void {
int high = fdp->ConsumeIntegral<int>();
int low = fdp->ConsumeIntegral<int>();
- bpbinder->setBinderProxyCountWatermarks(high, low);
+ int warning = fdp->ConsumeIntegral<int>();
+ bpbinder->setBinderProxyCountWatermarks(high, low, warning);
}};
} // namespace android
diff --git a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
index 09cb2162f7..b80ac53ba2 100644
--- a/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/BufferedTextOutputFuzz.cpp
@@ -16,6 +16,7 @@
#include <commonFuzzHelpers.h>
#include <fuzzer/FuzzedDataProvider.h>
+#include <functional>
#include <string>
#include <vector>
#include "BufferedTextOutput.h"
diff --git a/libs/binder/tests/unit_fuzzers/IBinderFuzzFunctions.h b/libs/binder/tests/unit_fuzzers/IBinderFuzzFunctions.h
index bf7c61347f..a6dc182588 100644
--- a/libs/binder/tests/unit_fuzzers/IBinderFuzzFunctions.h
+++ b/libs/binder/tests/unit_fuzzers/IBinderFuzzFunctions.h
@@ -23,7 +23,6 @@
#include <binder/IResultReceiver.h>
#include <binder/Parcel.h>
#include <binder/Stability.h>
-#include <cutils/compiler.h>
#include <utils/KeyedVector.h>
#include <utils/Log.h>
#include <utils/Mutex.h>
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
new file mode 100644
index 0000000000..87b0fb6662
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFileFuzz.cpp
@@ -0,0 +1,46 @@
+/*
+ * 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 <binder/RecordedTransaction.h>
+#include <filesystem>
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+using android::binder::unique_fd;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ std::FILE* intermediateFile = std::tmpfile();
+ fwrite(data, sizeof(uint8_t), size, intermediateFile);
+ rewind(intermediateFile);
+ int fileNumber = fileno(intermediateFile);
+
+ unique_fd fd(dup(fileNumber));
+
+ auto transaction = android::binder::debug::RecordedTransaction::fromFile(fd);
+
+ std::fclose(intermediateFile);
+
+ if (transaction.has_value()) {
+ intermediateFile = std::tmpfile();
+
+ unique_fd fdForWriting(dup(fileno(intermediateFile)));
+ auto writeStatus [[maybe_unused]] = transaction.value().dumpToFile(fdForWriting);
+
+ std::fclose(intermediateFile);
+ }
+
+ return 0;
+}
diff --git a/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
new file mode 100644
index 0000000000..fa939e68e4
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/RecordedTransactionFuzz.cpp
@@ -0,0 +1,64 @@
+/*
+ * 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 <binder/RecordedTransaction.h>
+#include <fuzzbinder/random_parcel.h>
+#include <filesystem>
+#include <string>
+
+#include "fuzzer/FuzzedDataProvider.h"
+
+using android::fillRandomParcel;
+using android::binder::unique_fd;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider provider = FuzzedDataProvider(data, size);
+
+ android::String16 interfaceName =
+ android::String16(provider.ConsumeRandomLengthString().c_str());
+
+ uint32_t code = provider.ConsumeIntegral<uint32_t>();
+ uint32_t flags = provider.ConsumeIntegral<uint32_t>();
+ time_t sec = provider.ConsumeIntegral<time_t>();
+ long nsec = provider.ConsumeIntegral<long>();
+ timespec timestamp = {.tv_sec = sec, .tv_nsec = nsec};
+ android::status_t transactionStatus = provider.ConsumeIntegral<android::status_t>();
+
+ std::vector<uint8_t> bytes = provider.ConsumeBytes<uint8_t>(
+ provider.ConsumeIntegralInRange<size_t>(0, provider.remaining_bytes()));
+
+ // same options so that FDs and binders could be shared in both Parcels
+ android::RandomParcelOptions options;
+
+ android::Parcel p0, p1;
+ fillRandomParcel(&p0, FuzzedDataProvider(bytes.data(), bytes.size()), &options);
+ fillRandomParcel(&p1, std::move(provider), &options);
+
+ auto transaction =
+ android::binder::debug::RecordedTransaction::fromDetails(interfaceName, code, flags,
+ timestamp, p0, p1,
+ transactionStatus);
+
+ if (transaction.has_value()) {
+ std::FILE* intermediateFile = std::tmpfile();
+ unique_fd fdForWriting(dup(fileno(intermediateFile)));
+ auto writeStatus [[maybe_unused]] = transaction.value().dumpToFile(fdForWriting);
+
+ std::fclose(intermediateFile);
+ }
+
+ return 0;
+}
diff --git a/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp b/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp
index 5e3502aace..fe099782c0 100644
--- a/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp
+++ b/libs/binder/tests/unit_fuzzers/TextOutputFuzz.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
+#include "../../file.h"
+
#include <fuzzer/FuzzedDataProvider.h>
#include <binder/Parcel.h>
#include <binder/TextOutput.h>
-#include "android-base/file.h"
#include "android-base/test_utils.h"
#include <fcntl.h>
diff --git a/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording
new file mode 100644
index 0000000000..79442078c2
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/power_recording
Binary files differ
diff --git a/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction
new file mode 100644
index 0000000000..658addbed5
--- /dev/null
+++ b/libs/binder/tests/unit_fuzzers/recorded_transaction_corpus/recorded_binder_transaction
Binary files differ
diff --git a/libs/binder/trusty/OS.cpp b/libs/binder/trusty/OS.cpp
index 8ec982345d..a8dabc3a1d 100644
--- a/libs/binder/trusty/OS.cpp
+++ b/libs/binder/trusty/OS.cpp
@@ -22,17 +22,34 @@
#endif
#include <binder/RpcTransportTipcTrusty.h>
+#include <log/log.h>
+#include <trusty_log.h>
#include "../OS.h"
#include "TrustyStatus.h"
-using android::base::Result;
+#include <cstdarg>
-namespace android {
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
-Result<void> setNonBlocking(android::base::borrowed_fd /*fd*/) {
+namespace android::binder::os {
+
+void trace_begin(uint64_t, const char*) {}
+
+void trace_end(uint64_t) {}
+
+uint64_t GetThreadId() {
+ return 0;
+}
+
+bool report_sysprop_change() {
+ return false;
+}
+
+status_t setNonBlocking(borrowed_fd /*fd*/) {
// Trusty IPC syscalls are all non-blocking by default.
- return {};
+ return OK;
}
status_t getRandomBytes(uint8_t* data, size_t size) {
@@ -61,16 +78,51 @@ std::unique_ptr<RpcTransportCtxFactory> makeDefaultRpcTransportCtxFactory() {
ssize_t sendMessageOnSocket(
const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* /* ancillaryFds */) {
errno = ENOTSUP;
return -1;
}
ssize_t receiveMessageFromSocket(
const RpcTransportFd& /* socket */, iovec* /* iovs */, int /* niovs */,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* /* ancillaryFds */) {
+ std::vector<std::variant<unique_fd, borrowed_fd>>* /* ancillaryFds */) {
errno = ENOTSUP;
return -1;
}
-} // namespace android
+} // namespace android::binder::os
+
+int __android_log_print(int prio [[maybe_unused]], const char* tag, const char* fmt, ...) {
+#ifdef TRUSTY_USERSPACE
+#define trusty_tlog _tlog
+#define trusty_vtlog _vtlog
+#else
+ // mapping taken from kernel trusty_log.h (TLOGx)
+ int kernelLogLevel;
+ if (prio <= ANDROID_LOG_DEBUG) {
+ kernelLogLevel = LK_DEBUGLEVEL_ALWAYS;
+ } else if (prio == ANDROID_LOG_INFO) {
+ kernelLogLevel = LK_DEBUGLEVEL_SPEW;
+ } else if (prio == ANDROID_LOG_WARN) {
+ kernelLogLevel = LK_DEBUGLEVEL_INFO;
+ } else if (prio == ANDROID_LOG_ERROR) {
+ kernelLogLevel = LK_DEBUGLEVEL_CRITICAL;
+ } else { /* prio >= ANDROID_LOG_FATAL */
+ kernelLogLevel = LK_DEBUGLEVEL_CRITICAL;
+ }
+#if LK_DEBUGLEVEL_NO_ALIASES
+ auto LK_DEBUGLEVEL_kernelLogLevel = kernelLogLevel;
+#endif
+
+#define trusty_tlog(...) _tlog(kernelLogLevel, __VA_ARGS__)
+#define trusty_vtlog(...) _vtlog(kernelLogLevel, __VA_ARGS__)
+#endif
+
+ va_list args;
+ va_start(args, fmt);
+ trusty_tlog((tag[0] == '\0') ? "libbinder" : "libbinder-");
+ trusty_vtlog(fmt, args);
+ va_end(args);
+
+ return 1;
+}
diff --git a/libs/binder/trusty/RpcServerTrusty.cpp b/libs/binder/trusty/RpcServerTrusty.cpp
index 109da7509a..17919c2a25 100644
--- a/libs/binder/trusty/RpcServerTrusty.cpp
+++ b/libs/binder/trusty/RpcServerTrusty.cpp
@@ -27,11 +27,11 @@
#include "../RpcState.h"
#include "TrustyStatus.h"
-using android::base::unexpected;
+using android::binder::unique_fd;
namespace android {
-android::base::expected<sp<RpcServerTrusty>, int> RpcServerTrusty::make(
+sp<RpcServerTrusty> RpcServerTrusty::make(
tipc_hset* handleSet, std::string&& portName, std::shared_ptr<const PortAcl>&& portAcl,
size_t msgMaxSize, std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory) {
// Default is without TLS.
@@ -39,25 +39,28 @@ android::base::expected<sp<RpcServerTrusty>, int> RpcServerTrusty::make(
rpcTransportCtxFactory = RpcTransportCtxFactoryTipcTrusty::make();
auto ctx = rpcTransportCtxFactory->newServerCtx();
if (ctx == nullptr) {
- return unexpected(ERR_NO_MEMORY);
+ ALOGE("Failed to create RpcServerTrusty: can't create server context");
+ return nullptr;
}
auto srv = sp<RpcServerTrusty>::make(std::move(ctx), std::move(portName), std::move(portAcl),
msgMaxSize);
if (srv == nullptr) {
- return unexpected(ERR_NO_MEMORY);
+ ALOGE("Failed to create RpcServerTrusty: can't create server object");
+ return nullptr;
}
int rc = tipc_add_service(handleSet, &srv->mTipcPort, 1, 0, &kTipcOps);
if (rc != NO_ERROR) {
- return unexpected(rc);
+ ALOGE("Failed to create RpcServerTrusty: can't add service: %d", rc);
+ return nullptr;
}
return srv;
}
RpcServerTrusty::RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize)
- : mRpcServer(sp<RpcServer>::make(std::move(ctx))),
+ : mRpcServer(makeRpcServer(std::move(ctx))),
mPortName(std::move(portName)),
mPortAcl(std::move(portAcl)) {
mTipcPort.name = mPortName.c_str();
@@ -94,8 +97,13 @@ RpcServerTrusty::RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::stri
int RpcServerTrusty::handleConnect(const tipc_port* port, handle_t chan, const uuid* peer,
void** ctx_p) {
auto* server = reinterpret_cast<RpcServerTrusty*>(const_cast<void*>(port->priv));
- server->mRpcServer->mShutdownTrigger = FdTrigger::make();
- server->mRpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
+ return handleConnectInternal(server->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int RpcServerTrusty::handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+ void** ctx_p) {
+ rpcServer->mShutdownTrigger = FdTrigger::make();
+ rpcServer->mConnectingThreads[rpc_this_thread::get_id()] = RpcMaybeThread();
int rc = NO_ERROR;
auto joinFn = [&](sp<RpcSession>&& session, RpcSession::PreJoinSetupResult&& result) {
@@ -125,19 +133,23 @@ int RpcServerTrusty::handleConnect(const tipc_port* port, handle_t chan, const u
if (chanDup < 0) {
return chanDup;
}
- base::unique_fd clientFd(chanDup);
+ unique_fd clientFd(chanDup);
android::RpcTransportFd transportFd(std::move(clientFd));
std::array<uint8_t, RpcServer::kRpcAddressSize> addr;
constexpr size_t addrLen = sizeof(*peer);
memcpy(addr.data(), peer, addrLen);
- RpcServer::establishConnection(sp(server->mRpcServer), std::move(transportFd), addr, addrLen,
- joinFn);
+ RpcServer::establishConnection(sp<RpcServer>::fromExisting(rpcServer), std::move(transportFd),
+ addr, addrLen, joinFn);
return rc;
}
int RpcServerTrusty::handleMessage(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+ return handleMessageInternal(ctx);
+}
+
+int RpcServerTrusty::handleMessageInternal(void* ctx) {
auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
LOG_ALWAYS_FATAL_IF(channelContext == nullptr,
"bad state: message received on uninitialized channel");
@@ -154,8 +166,22 @@ int RpcServerTrusty::handleMessage(const tipc_port* /*port*/, handle_t /*chan*/,
return NO_ERROR;
}
-void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/,
- void* /*ctx*/) {}
+void RpcServerTrusty::handleDisconnect(const tipc_port* /*port*/, handle_t /*chan*/, void* ctx) {
+ return handleDisconnectInternal(ctx);
+}
+
+void RpcServerTrusty::handleDisconnectInternal(void* ctx) {
+ auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
+ if (channelContext == nullptr) {
+ // Connections marked "incoming" (outgoing from the server's side)
+ // do not have a valid channel context because joinFn does not get
+ // called for them. We ignore them here.
+ return;
+ }
+
+ auto& session = channelContext->session;
+ (void)session->shutdownAndWait(false);
+}
void RpcServerTrusty::handleChannelCleanup(void* ctx) {
auto* channelContext = reinterpret_cast<ChannelContext*>(ctx);
diff --git a/libs/binder/trusty/RpcTransportTipcTrusty.cpp b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
index d249b2e1eb..c74ba0a65e 100644
--- a/libs/binder/trusty/RpcTransportTipcTrusty.cpp
+++ b/libs/binder/trusty/RpcTransportTipcTrusty.cpp
@@ -29,7 +29,9 @@
namespace android {
-namespace {
+using namespace android::binder::impl;
+using android::binder::borrowed_fd;
+using android::binder::unique_fd;
// RpcTransport for Trusty.
class RpcTransportTipcTrusty : public RpcTransport {
@@ -47,9 +49,8 @@ public:
status_t interruptableWriteFully(
FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& /*altPoll*/,
- const std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds)
- override {
+ const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
+ const std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
if (niovs < 0) {
return BAD_VALUE;
}
@@ -117,8 +118,8 @@ public:
status_t interruptableReadFully(
FdTrigger* /*fdTrigger*/, iovec* iovs, int niovs,
- const std::optional<android::base::function_ref<status_t()>>& /*altPoll*/,
- std::vector<std::variant<base::unique_fd, base::borrowed_fd>>* ancillaryFds) override {
+ const std::optional<SmallFunction<status_t()>>& /*altPoll*/,
+ std::vector<std::variant<unique_fd, borrowed_fd>>* ancillaryFds) override {
if (niovs < 0) {
return BAD_VALUE;
}
@@ -170,7 +171,7 @@ public:
if (ancillaryFds != nullptr) {
ancillaryFds->reserve(ancillaryFds->size() + mMessageInfo.num_handles);
for (size_t i = 0; i < mMessageInfo.num_handles; i++) {
- ancillaryFds->emplace_back(base::unique_fd(msgHandles[i]));
+ ancillaryFds->emplace_back(unique_fd(msgHandles[i]));
}
// Clear the saved number of handles so we don't accidentally
@@ -282,8 +283,6 @@ public:
std::vector<uint8_t> getCertificate(RpcCertificateFormat) const override { return {}; }
};
-} // namespace
-
std::unique_ptr<RpcTransportCtx> RpcTransportCtxFactoryTipcTrusty::newServerCtx() const {
return std::make_unique<RpcTransportCtxTipcTrusty>();
}
diff --git a/libs/binder/trusty/binderRpcTest/manifest.json b/libs/binder/trusty/binderRpcTest/manifest.json
index d8b080f0d4..6e20b8a7ad 100644
--- a/libs/binder/trusty/binderRpcTest/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/manifest.json
@@ -1,6 +1,6 @@
{
"uuid": "9dbe9fb8-60fd-4bdd-af86-03e95d7ad78b",
"app_name": "binderRpcTest",
- "min_heap": 163840,
- "min_stack": 16384
+ "min_heap": 262144,
+ "min_stack": 20480
}
diff --git a/libs/binder/trusty/binderRpcTest/rules.mk b/libs/binder/trusty/binderRpcTest/rules.mk
index ae3949246d..e46ccfb967 100644
--- a/libs/binder/trusty/binderRpcTest/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/rules.mk
@@ -21,6 +21,7 @@ MODULE := $(LOCAL_DIR)
MANIFEST := $(LOCAL_DIR)/manifest.json
MODULE_SRCS += \
+ $(FMTLIB_DIR)/src/format.cc \
$(LIBBINDER_TESTS_DIR)/binderRpcUniversalTests.cpp \
$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
$(LIBBINDER_TESTS_DIR)/binderRpcTestTrusty.cpp \
@@ -32,4 +33,8 @@ MODULE_LIBRARY_DEPS += \
trusty/user/base/lib/googletest \
trusty/user/base/lib/libstdc++-trusty \
+# TEST_P tests from binderRpcUniversalTests.cpp don't get linked in
+# unless we pass in --whole-archive to the linker (b/275620340).
+MODULE_USE_WHOLE_ARCHIVE := true
+
include make/trusted_app.mk
diff --git a/libs/binder/trusty/binderRpcTest/service/manifest.json b/libs/binder/trusty/binderRpcTest/service/manifest.json
index 1c4f7ee0d1..d2a1fc0a48 100644
--- a/libs/binder/trusty/binderRpcTest/service/manifest.json
+++ b/libs/binder/trusty/binderRpcTest/service/manifest.json
@@ -2,7 +2,7 @@
"uuid": "87e424e5-69d7-4bbd-8b7c-7e24812cbc94",
"app_name": "binderRpcTestService",
"min_heap": 65536,
- "min_stack": 16384,
+ "min_stack": 20480,
"mgmt_flags": {
"restart_on_exit": true,
"non_critical_app": true
diff --git a/libs/binder/trusty/binderRpcTest/service/rules.mk b/libs/binder/trusty/binderRpcTest/service/rules.mk
index 5d1a51dca2..50ae3d25fa 100644
--- a/libs/binder/trusty/binderRpcTest/service/rules.mk
+++ b/libs/binder/trusty/binderRpcTest/service/rules.mk
@@ -21,6 +21,7 @@ MODULE := $(LOCAL_DIR)
MANIFEST := $(LOCAL_DIR)/manifest.json
MODULE_SRCS := \
+ $(FMTLIB_DIR)/src/format.cc \
$(LIBBINDER_TESTS_DIR)/binderRpcTestCommon.cpp \
$(LIBBINDER_TESTS_DIR)/binderRpcTestServiceTrusty.cpp \
diff --git a/libs/binder/trusty/binder_rpc_unstable/rules.mk b/libs/binder/trusty/binder_rpc_unstable/rules.mk
new file mode 100644
index 0000000000..d8dbce54e8
--- /dev/null
+++ b/libs/binder/trusty/binder_rpc_unstable/rules.mk
@@ -0,0 +1,32 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+ $(LIBBINDER_DIR)/libbinder_rpc_unstable.cpp \
+
+MODULE_EXPORT_INCLUDES += \
+ $(LIBBINDER_DIR)/include_rpc_unstable \
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ trusty/user/base/lib/libstdc++-trusty \
+
+include make/library.mk
diff --git a/libs/binder/trusty/build-config-usertests b/libs/binder/trusty/build-config-usertests
index d0a1fbca49..72e5ff9b2a 100644
--- a/libs/binder/trusty/build-config-usertests
+++ b/libs/binder/trusty/build-config-usertests
@@ -16,4 +16,5 @@
[
porttest("com.android.trusty.binderRpcTest"),
+ porttest("com.android.trusty.rust.binder_rpc_test.test"),
]
diff --git a/libs/binder/trusty/fuzzer/Android.bp b/libs/binder/trusty/fuzzer/Android.bp
new file mode 100644
index 0000000000..4f9b5c4d9d
--- /dev/null
+++ b/libs/binder/trusty/fuzzer/Android.bp
@@ -0,0 +1,65 @@
+// 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_fuzz {
+ name: "trusty_binder_fuzzer",
+ defaults: ["trusty_fuzzer_defaults"],
+ srcs: [":trusty_tipc_fuzzer"],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.binder.test.service\"",
+ "-DTRUSTY_APP_UUID=\"d42f06c5-9dc5-455b-9914-cf094116cfa8\"",
+ "-DTRUSTY_APP_FILENAME=\"binder-test-service.syms.elf\"",
+ "-DTRUSTY_APP_MAX_CONNECTIONS=1",
+ ],
+}
+
+cc_fuzz {
+ name: "trusty_binder_rpc_fuzzer",
+ defaults: ["trusty_fuzzer_defaults"],
+ srcs: [":trusty_tipc_fuzzer"],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.binderRpcTestService.V0\"",
+ "-DTRUSTY_APP_UUID=\"87e424e5-69d7-4bbd-8b7c-7e24812cbc94\"",
+ "-DTRUSTY_APP_FILENAME=\"binderRpcTestService.syms.elf\"",
+ "-DTRUSTY_APP_MAX_CONNECTIONS=1",
+ ],
+}
+
+cc_fuzz {
+ name: "trusty_binder_fuzzer_multi_connection",
+ defaults: ["trusty_fuzzer_defaults"],
+ srcs: [":trusty_tipc_fuzzer"],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.binder.test.service\"",
+ "-DTRUSTY_APP_UUID=\"d42f06c5-9dc5-455b-9914-cf094116cfa8\"",
+ "-DTRUSTY_APP_FILENAME=\"binder-test-service.syms.elf\"",
+ "-DTRUSTY_APP_MAX_CONNECTIONS=10",
+ ],
+}
+
+cc_fuzz {
+ name: "trusty_binder_rpc_fuzzer_multi_connection",
+ defaults: ["trusty_fuzzer_defaults"],
+ srcs: [":trusty_tipc_fuzzer"],
+ cflags: [
+ "-DTRUSTY_APP_PORT=\"com.android.trusty.binderRpcTestService.V0\"",
+ "-DTRUSTY_APP_UUID=\"87e424e5-69d7-4bbd-8b7c-7e24812cbc94\"",
+ "-DTRUSTY_APP_FILENAME=\"binderRpcTestService.syms.elf\"",
+ "-DTRUSTY_APP_MAX_CONNECTIONS=10",
+ ],
+}
diff --git a/libs/binder/trusty/include/binder/ARpcServerTrusty.h b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
new file mode 100644
index 0000000000..c82268b47b
--- /dev/null
+++ b/libs/binder/trusty/include/binder/ARpcServerTrusty.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <lib/tipc/tipc_srv.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+struct AIBinder;
+struct ARpcServerTrusty;
+
+struct ARpcServerTrusty* ARpcServerTrusty_newPerSession(struct AIBinder* (*)(const void*, size_t,
+ char*),
+ char*, void (*)(char*));
+void ARpcServerTrusty_delete(struct ARpcServerTrusty*);
+int ARpcServerTrusty_handleConnect(struct ARpcServerTrusty*, handle_t, const struct uuid*, void**);
+int ARpcServerTrusty_handleMessage(void*);
+void ARpcServerTrusty_handleDisconnect(void*);
+void ARpcServerTrusty_handleChannelCleanup(void*);
+
+#if defined(__cplusplus)
+}
+#endif
diff --git a/libs/binder/trusty/include/binder/RpcServerTrusty.h b/libs/binder/trusty/include/binder/RpcServerTrusty.h
index 6678eb8fec..fe44ea5e28 100644
--- a/libs/binder/trusty/include/binder/RpcServerTrusty.h
+++ b/libs/binder/trusty/include/binder/RpcServerTrusty.h
@@ -16,13 +16,12 @@
#pragma once
-#include <android-base/expected.h>
-#include <android-base/macros.h>
-#include <android-base/unique_fd.h>
+#include <binder/ARpcServerTrusty.h>
#include <binder/IBinder.h>
#include <binder/RpcServer.h>
#include <binder/RpcSession.h>
#include <binder/RpcTransport.h>
+#include <binder/unique_fd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
@@ -54,19 +53,22 @@ public:
* The caller is responsible for calling tipc_run_event_loop() to start
* the TIPC event loop after creating one or more services here.
*/
- static android::base::expected<sp<RpcServerTrusty>, int> make(
+ static sp<RpcServerTrusty> make(
tipc_hset* handleSet, std::string&& portName, std::shared_ptr<const PortAcl>&& portAcl,
size_t msgMaxSize,
std::unique_ptr<RpcTransportCtxFactory> rpcTransportCtxFactory = nullptr);
- void setProtocolVersion(uint32_t version) { mRpcServer->setProtocolVersion(version); }
+ [[nodiscard]] bool setProtocolVersion(uint32_t version) {
+ return mRpcServer->setProtocolVersion(version);
+ }
void setSupportedFileDescriptorTransportModes(
const std::vector<RpcSession::FileDescriptorTransportMode>& modes) {
mRpcServer->setSupportedFileDescriptorTransportModes(modes);
}
void setRootObject(const sp<IBinder>& binder) { mRpcServer->setRootObject(binder); }
void setRootObjectWeak(const wp<IBinder>& binder) { mRpcServer->setRootObjectWeak(binder); }
- void setPerSessionRootObject(std::function<sp<IBinder>(const void*, size_t)>&& object) {
+ void setPerSessionRootObject(
+ std::function<sp<IBinder>(wp<RpcSession> session, const void*, size_t)>&& object) {
mRpcServer->setPerSessionRootObject(std::move(object));
}
sp<IBinder> getRootObject() { return mRpcServer->getRootObject(); }
@@ -80,12 +82,35 @@ private:
// Both this class and RpcServer have multiple non-copyable fields,
// including mPortAcl below which can't be copied because mUuidPtrs
// holds pointers into it
- DISALLOW_COPY_AND_ASSIGN(RpcServerTrusty);
+ RpcServerTrusty(const RpcServerTrusty&) = delete;
+ void operator=(const RpcServerTrusty&) = delete;
friend sp<RpcServerTrusty>;
explicit RpcServerTrusty(std::unique_ptr<RpcTransportCtx> ctx, std::string&& portName,
std::shared_ptr<const PortAcl>&& portAcl, size_t msgMaxSize);
+ // Internal helper that creates the RpcServer.
+ // This is used both from here and Rust.
+ static sp<RpcServer> makeRpcServer(std::unique_ptr<RpcTransportCtx> ctx) {
+ auto rpcServer = sp<RpcServer>::make(std::move(ctx));
+
+ // TODO(b/266741352): follow-up to prevent needing this in the future
+ // Trusty needs to be set to the latest stable version that is in prebuilts there.
+ LOG_ALWAYS_FATAL_IF(!rpcServer->setProtocolVersion(0));
+
+ return rpcServer;
+ }
+
+ friend struct ::ARpcServerTrusty;
+ friend ::ARpcServerTrusty* ::ARpcServerTrusty_newPerSession(::AIBinder* (*)(const void*, size_t,
+ char*),
+ char*, void (*)(char*));
+ friend void ::ARpcServerTrusty_delete(::ARpcServerTrusty*);
+ friend int ::ARpcServerTrusty_handleConnect(::ARpcServerTrusty*, handle_t, const uuid*, void**);
+ friend int ::ARpcServerTrusty_handleMessage(void*);
+ friend void ::ARpcServerTrusty_handleDisconnect(void*);
+ friend void ::ARpcServerTrusty_handleChannelCleanup(void*);
+
// The Rpc-specific context maintained for every open TIPC channel.
struct ChannelContext {
sp<RpcSession> session;
@@ -97,6 +122,11 @@ private:
static void handleDisconnect(const tipc_port* port, handle_t chan, void* ctx);
static void handleChannelCleanup(void* ctx);
+ static int handleConnectInternal(RpcServer* rpcServer, handle_t chan, const uuid* peer,
+ void** ctx_p);
+ static int handleMessageInternal(void* ctx);
+ static void handleDisconnectInternal(void* ctx);
+
static constexpr tipc_srv_ops kTipcOps = {
.on_connect = &handleConnect,
.on_message = &handleMessage,
diff --git a/libs/binder/trusty/include/log/log.h b/libs/binder/trusty/include/log/log.h
deleted file mode 100644
index de84617343..0000000000
--- a/libs/binder/trusty/include/log/log.h
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#define BINDER_LOG_LEVEL_NONE 0
-#define BINDER_LOG_LEVEL_NORMAL 1
-#define BINDER_LOG_LEVEL_VERBOSE 2
-
-#ifndef BINDER_LOG_LEVEL
-#define BINDER_LOG_LEVEL BINDER_LOG_LEVEL_NORMAL
-#endif // BINDER_LOG_LEVEL
-
-#ifndef TLOG_TAG
-#ifdef LOG_TAG
-#define TLOG_TAG "libbinder-" LOG_TAG
-#else // LOG_TAG
-#define TLOG_TAG "libbinder"
-#endif // LOG_TAG
-#endif // TLOG_TAG
-
-#include <stdlib.h>
-#include <trusty_log.h>
-
-static inline void __ignore_va_args__(...) {}
-
-#if BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-#define ALOGD(fmt, ...) TLOGD(fmt "\n", ##__VA_ARGS__)
-#define ALOGI(fmt, ...) TLOGI(fmt "\n", ##__VA_ARGS__)
-#define ALOGW(fmt, ...) TLOGW(fmt "\n", ##__VA_ARGS__)
-#define ALOGE(fmt, ...) TLOGE(fmt "\n", ##__VA_ARGS__)
-#else // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-#define ALOGD(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#define ALOGI(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#define ALOGW(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#define ALOGE(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#endif // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_NORMAL
-
-#if BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-#define IF_ALOGV() if (TLOG_LVL >= TLOG_LVL_INFO)
-#define ALOGV(fmt, ...) TLOGI(fmt "\n", ##__VA_ARGS__)
-#else // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-#define IF_ALOGV() if (false)
-#define ALOGV(fmt, ...) \
- while (0) { \
- __ignore_va_args__(__VA_ARGS__); \
- }
-#endif // BINDER_LOG_LEVEL >= BINDER_LOG_LEVEL_VERBOSE
-
-#define ALOGI_IF(cond, ...) \
- do { \
- if (cond) { \
- ALOGI(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-#define ALOGE_IF(cond, ...) \
- do { \
- if (cond) { \
- ALOGE(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-#define ALOGW_IF(cond, ...) \
- do { \
- if (cond) { \
- ALOGW(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-
-#define LOG_ALWAYS_FATAL(fmt, ...) \
- do { \
- TLOGE("libbinder fatal error: " fmt "\n", ##__VA_ARGS__); \
- abort(); \
- } while (0)
-#define LOG_ALWAYS_FATAL_IF(cond, ...) \
- do { \
- if (cond) { \
- LOG_ALWAYS_FATAL(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-#define LOG_FATAL(fmt, ...) \
- do { \
- TLOGE("libbinder fatal error: " fmt "\n", ##__VA_ARGS__); \
- abort(); \
- } while (0)
-#define LOG_FATAL_IF(cond, ...) \
- do { \
- if (cond) { \
- LOG_FATAL(#cond ": " __VA_ARGS__); \
- } \
- } while (0)
-
-#define ALOG_ASSERT(cond, ...) LOG_FATAL_IF(!(cond), ##__VA_ARGS__)
-
-#define android_errorWriteLog(tag, subTag) \
- do { \
- TLOGE("android_errorWriteLog: tag:%x subTag:%s\n", tag, subTag); \
- } while (0)
-
-// Override the definition of __assert from binder_status.h
-#ifndef __BIONIC__
-#undef __assert
-#define __assert(file, line, str) LOG_ALWAYS_FATAL("%s:%d: %s", file, line, str)
-#endif // __BIONIC__
diff --git a/libs/binder/trusty/include_mock/trusty_log.h b/libs/binder/trusty/include_mock/trusty_log.h
index d51e75280c..9aa90310c0 100644
--- a/libs/binder/trusty/include_mock/trusty_log.h
+++ b/libs/binder/trusty/include_mock/trusty_log.h
@@ -24,3 +24,6 @@
#define TLOGW(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define TLOGE(fmt, ...) printf(fmt, ##__VA_ARGS__)
#define TLOGC(fmt, ...) printf(fmt, ##__VA_ARGS__)
+
+#define _tlog(fmt, ...) printf(fmt, ##__VA_ARGS__)
+#define _vtlog(fmt, args) vprintf(fmt, args)
diff --git a/libs/binder/trusty/kernel/rules.mk b/libs/binder/trusty/kernel/rules.mk
index ab7a50d3f7..5cbe0afafa 100644
--- a/libs/binder/trusty/kernel/rules.mk
+++ b/libs/binder/trusty/kernel/rules.mk
@@ -18,13 +18,14 @@ LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
LIBBINDER_DIR := frameworks/native/libs/binder
+# TODO(b/302723053): remove libbase after aidl prebuilt gets updated to December release
LIBBASE_DIR := system/libbase
-LIBCUTILS_DIR := system/core/libcutils
-LIBUTILS_DIR := system/core/libutils
+LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
+LIBUTILS_BINDER_DIR := system/core/libutils/binder
FMTLIB_DIR := external/fmtlib
MODULE_SRCS := \
- $(LOCAL_DIR)/../logging.cpp \
+ $(LOCAL_DIR)/../OS.cpp \
$(LOCAL_DIR)/../TrustyStatus.cpp \
$(LIBBINDER_DIR)/Binder.cpp \
$(LIBBINDER_DIR)/BpBinder.cpp \
@@ -35,24 +36,14 @@ MODULE_SRCS := \
$(LIBBINDER_DIR)/Stability.cpp \
$(LIBBINDER_DIR)/Status.cpp \
$(LIBBINDER_DIR)/Utils.cpp \
- $(LIBBASE_DIR)/hex.cpp \
- $(LIBBASE_DIR)/stringprintf.cpp \
- $(LIBUTILS_DIR)/Errors.cpp \
- $(LIBUTILS_DIR)/misc.cpp \
- $(LIBUTILS_DIR)/RefBase.cpp \
- $(LIBUTILS_DIR)/StrongPointer.cpp \
- $(LIBUTILS_DIR)/Unicode.cpp \
-
-# TODO: remove the following when libbinder supports std::string
-# instead of String16 and String8 for Status and descriptors
-MODULE_SRCS += \
- $(LIBUTILS_DIR)/SharedBuffer.cpp \
- $(LIBUTILS_DIR)/String16.cpp \
- $(LIBUTILS_DIR)/String8.cpp \
-
-# TODO: disable dump() transactions to get rid of Vector
-MODULE_SRCS += \
- $(LIBUTILS_DIR)/VectorImpl.cpp \
+ $(LIBUTILS_BINDER_DIR)/Errors.cpp \
+ $(LIBUTILS_BINDER_DIR)/RefBase.cpp \
+ $(LIBUTILS_BINDER_DIR)/SharedBuffer.cpp \
+ $(LIBUTILS_BINDER_DIR)/String16.cpp \
+ $(LIBUTILS_BINDER_DIR)/String8.cpp \
+ $(LIBUTILS_BINDER_DIR)/StrongPointer.cpp \
+ $(LIBUTILS_BINDER_DIR)/Unicode.cpp \
+ $(LIBUTILS_BINDER_DIR)/VectorImpl.cpp \
MODULE_DEFINES += \
LK_DEBUGLEVEL_NO_ALIASES=1 \
@@ -63,17 +54,22 @@ MODULE_INCLUDES += \
GLOBAL_INCLUDES += \
$(LOCAL_DIR)/include \
$(LOCAL_DIR)/../include \
+ $(LIBLOG_STUB_DIR)/include \
$(LIBBINDER_DIR)/include \
$(LIBBINDER_DIR)/ndk/include_cpp \
$(LIBBASE_DIR)/include \
- $(LIBCUTILS_DIR)/include \
- $(LIBUTILS_DIR)/include \
+ $(LIBUTILS_BINDER_DIR)/include \
$(FMTLIB_DIR)/include \
GLOBAL_COMPILEFLAGS += \
-DANDROID_BASE_UNIQUE_FD_DISABLE_IMPLICIT_CONVERSION \
-DBINDER_NO_KERNEL_IPC \
-DBINDER_RPC_SINGLE_THREADED \
+ -DBINDER_ENABLE_LIBLOG_ASSERT \
+ -DBINDER_DISABLE_NATIVE_HANDLE \
+ -DBINDER_DISABLE_BLOB \
+ -DBINDER_NO_LIBBASE \
+ -D__ANDROID_VENDOR__ \
-D__ANDROID_VNDK__ \
MODULE_DEPS += \
diff --git a/libs/binder/trusty/logging.cpp b/libs/binder/trusty/logging.cpp
deleted file mode 100644
index b4243af891..0000000000
--- a/libs/binder/trusty/logging.cpp
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define TLOG_TAG "libbinder"
-
-#include "android-base/logging.h"
-
-#include <trusty_log.h>
-#include <iostream>
-#include <string>
-
-#include <android-base/macros.h>
-#include <android-base/strings.h>
-
-namespace android {
-namespace base {
-
-static const char* GetFileBasename(const char* file) {
- const char* last_slash = strrchr(file, '/');
- if (last_slash != nullptr) {
- return last_slash + 1;
- }
- return file;
-}
-
-// This splits the message up line by line, by calling log_function with a pointer to the start of
-// each line and the size up to the newline character. It sends size = -1 for the final line.
-template <typename F, typename... Args>
-static void SplitByLines(const char* msg, const F& log_function, Args&&... args) {
- const char* newline;
- while ((newline = strchr(msg, '\n')) != nullptr) {
- log_function(msg, newline - msg, args...);
- msg = newline + 1;
- }
-
- log_function(msg, -1, args...);
-}
-
-void DefaultAborter(const char* abort_message) {
- TLOGC("aborting: %s\n", abort_message);
- abort();
-}
-
-static void TrustyLogLine(const char* msg, int /*length*/, android::base::LogSeverity severity,
- const char* tag) {
- switch (severity) {
- case VERBOSE:
- case DEBUG:
- TLOGD("%s: %s\n", tag, msg);
- break;
- case INFO:
- TLOGI("%s: %s\n", tag, msg);
- break;
- case WARNING:
- TLOGW("%s: %s\n", tag, msg);
- break;
- case ERROR:
- TLOGE("%s: %s\n", tag, msg);
- break;
- case FATAL_WITHOUT_ABORT:
- case FATAL:
- TLOGC("%s: %s\n", tag, msg);
- break;
- }
-}
-
-void TrustyLogger(android::base::LogId, android::base::LogSeverity severity, const char* tag,
- const char*, unsigned int, const char* full_message) {
- SplitByLines(full_message, TrustyLogLine, severity, tag);
-}
-
-// This indirection greatly reduces the stack impact of having lots of
-// checks/logging in a function.
-class LogMessageData {
-public:
- LogMessageData(const char* file, unsigned int line, LogSeverity severity, const char* tag,
- int error)
- : file_(GetFileBasename(file)),
- line_number_(line),
- severity_(severity),
- tag_(tag),
- error_(error) {}
-
- const char* GetFile() const { return file_; }
-
- unsigned int GetLineNumber() const { return line_number_; }
-
- LogSeverity GetSeverity() const { return severity_; }
-
- const char* GetTag() const { return tag_; }
-
- int GetError() const { return error_; }
-
- std::ostream& GetBuffer() { return buffer_; }
-
- std::string ToString() const { return buffer_.str(); }
-
-private:
- std::ostringstream buffer_;
- const char* const file_;
- const unsigned int line_number_;
- const LogSeverity severity_;
- const char* const tag_;
- const int error_;
-
- DISALLOW_COPY_AND_ASSIGN(LogMessageData);
-};
-
-LogMessage::LogMessage(const char* file, unsigned int line, LogId, LogSeverity severity,
- const char* tag, int error)
- : LogMessage(file, line, severity, tag, error) {}
-
-LogMessage::LogMessage(const char* file, unsigned int line, LogSeverity severity, const char* tag,
- int error)
- : data_(new LogMessageData(file, line, severity, tag, error)) {}
-
-LogMessage::~LogMessage() {
- // Check severity again. This is duplicate work wrt/ LOG macros, but not LOG_STREAM.
- if (!WOULD_LOG(data_->GetSeverity())) {
- return;
- }
-
- // Finish constructing the message.
- if (data_->GetError() != -1) {
- data_->GetBuffer() << ": " << strerror(data_->GetError());
- }
- std::string msg(data_->ToString());
-
- LogLine(data_->GetFile(), data_->GetLineNumber(), data_->GetSeverity(), data_->GetTag(),
- msg.c_str());
-
- // Abort if necessary.
- if (data_->GetSeverity() == FATAL) {
- DefaultAborter(msg.c_str());
- }
-}
-
-std::ostream& LogMessage::stream() {
- return data_->GetBuffer();
-}
-
-void LogMessage::LogLine(const char* file, unsigned int line, LogSeverity severity, const char* tag,
- const char* message) {
- TrustyLogger(DEFAULT, severity, tag ?: "<unknown>", file, line, message);
-}
-
-bool ShouldLog(LogSeverity /*severity*/, const char* /*tag*/) {
- // This is controlled by Trusty's log level.
- return true;
-}
-
-} // namespace base
-} // namespace android
diff --git a/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl b/libs/binder/trusty/ndk/Android.bp
index e30e9072fc..af9874a57a 100644
--- a/libs/binder/aidl/android/content/pm/PackageChangeEvent.aidl
+++ b/libs/binder/trusty/ndk/Android.bp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,19 +14,18 @@
* limitations under the License.
*/
-package android.content.pm;
+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"],
+}
-/**
- * This event is designed for notification to native code listener about
- * any changes on a package including update, deletion and etc.
- *
- * @hide
- */
-parcelable PackageChangeEvent {
- @utf8InCpp String packageName;
- long version;
- long lastUpdateTimeMillis;
- boolean newInstalled;
- boolean dataRemoved;
- boolean isDeleted;
+cc_library_headers {
+ name: "libbinder_trusty_ndk_headers",
+ export_include_dirs: ["include"],
+ host_supported: true,
+ vendor_available: true,
}
diff --git a/libs/binder/trusty/ndk/include/android/llndk-versioning.h b/libs/binder/trusty/ndk/include/android/llndk-versioning.h
new file mode 100644
index 0000000000..3ae3d8f577
--- /dev/null
+++ b/libs/binder/trusty/ndk/include/android/llndk-versioning.h
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#define __INTRODUCED_IN_LLNDK(x) /* nothing on Trusty */
diff --git a/libs/binder/trusty/ndk/include/sys/cdefs.h b/libs/binder/trusty/ndk/include/sys/cdefs.h
index 6a48d2b776..4e9b0e83c6 100644
--- a/libs/binder/trusty/ndk/include/sys/cdefs.h
+++ b/libs/binder/trusty/ndk/include/sys/cdefs.h
@@ -15,10 +15,15 @@
*/
#pragma once
+#if __has_include(<lk/compiler.h>)
#include <lk/compiler.h>
/* Alias the bionic macros to the ones from lk/compiler.h */
#define __BEGIN_DECLS __BEGIN_CDECLS
#define __END_DECLS __END_CDECLS
+#else // __has_include(<lk/compiler.h>)
+#include_next <sys/cdefs.h>
+#endif
+
#define __INTRODUCED_IN(x) /* nothing on Trusty */
diff --git a/libs/binder/trusty/ndk/rules.mk b/libs/binder/trusty/ndk/rules.mk
index 03fd006fd2..7a275c2696 100644
--- a/libs/binder/trusty/ndk/rules.mk
+++ b/libs/binder/trusty/ndk/rules.mk
@@ -23,6 +23,7 @@ MODULE_SRCS := \
$(LIBBINDER_NDK_DIR)/ibinder.cpp \
$(LIBBINDER_NDK_DIR)/libbinder.cpp \
$(LIBBINDER_NDK_DIR)/parcel.cpp \
+ $(LIBBINDER_NDK_DIR)/stability.cpp \
$(LIBBINDER_NDK_DIR)/status.cpp \
MODULE_EXPORT_INCLUDES += \
diff --git a/libs/binder/trusty/rules.mk b/libs/binder/trusty/rules.mk
index 42db29a2ef..f2f140d9ba 100644
--- a/libs/binder/trusty/rules.mk
+++ b/libs/binder/trusty/rules.mk
@@ -18,13 +18,13 @@ LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
LIBBINDER_DIR := frameworks/native/libs/binder
+# TODO(b/302723053): remove libbase after aidl prebuilt gets updated to December release
LIBBASE_DIR := system/libbase
-LIBCUTILS_DIR := system/core/libcutils
-LIBUTILS_DIR := system/core/libutils
+LIBLOG_STUB_DIR := $(LIBBINDER_DIR)/liblog_stub
+LIBUTILS_BINDER_DIR := system/core/libutils/binder
FMTLIB_DIR := external/fmtlib
MODULE_SRCS := \
- $(LOCAL_DIR)/logging.cpp \
$(LOCAL_DIR)/OS.cpp \
$(LOCAL_DIR)/RpcServerTrusty.cpp \
$(LOCAL_DIR)/RpcTransportTipcTrusty.cpp \
@@ -43,31 +43,22 @@ MODULE_SRCS := \
$(LIBBINDER_DIR)/Stability.cpp \
$(LIBBINDER_DIR)/Status.cpp \
$(LIBBINDER_DIR)/Utils.cpp \
- $(LIBBASE_DIR)/hex.cpp \
- $(LIBBASE_DIR)/stringprintf.cpp \
- $(LIBUTILS_DIR)/Errors.cpp \
- $(LIBUTILS_DIR)/misc.cpp \
- $(LIBUTILS_DIR)/RefBase.cpp \
- $(LIBUTILS_DIR)/StrongPointer.cpp \
- $(LIBUTILS_DIR)/Unicode.cpp \
-
-# TODO: remove the following when libbinder supports std::string
-# instead of String16 and String8 for Status and descriptors
-MODULE_SRCS += \
- $(LIBUTILS_DIR)/SharedBuffer.cpp \
- $(LIBUTILS_DIR)/String16.cpp \
- $(LIBUTILS_DIR)/String8.cpp \
-
-# TODO: disable dump() transactions to get rid of Vector
-MODULE_SRCS += \
- $(LIBUTILS_DIR)/VectorImpl.cpp \
+ $(LIBBINDER_DIR)/file.cpp \
+ $(LIBUTILS_BINDER_DIR)/Errors.cpp \
+ $(LIBUTILS_BINDER_DIR)/RefBase.cpp \
+ $(LIBUTILS_BINDER_DIR)/SharedBuffer.cpp \
+ $(LIBUTILS_BINDER_DIR)/String16.cpp \
+ $(LIBUTILS_BINDER_DIR)/String8.cpp \
+ $(LIBUTILS_BINDER_DIR)/StrongPointer.cpp \
+ $(LIBUTILS_BINDER_DIR)/Unicode.cpp \
+ $(LIBUTILS_BINDER_DIR)/VectorImpl.cpp \
MODULE_EXPORT_INCLUDES += \
$(LOCAL_DIR)/include \
+ $(LIBLOG_STUB_DIR)/include \
$(LIBBINDER_DIR)/include \
$(LIBBASE_DIR)/include \
- $(LIBCUTILS_DIR)/include \
- $(LIBUTILS_DIR)/include \
+ $(LIBUTILS_BINDER_DIR)/include \
$(FMTLIB_DIR)/include \
# The android/binder_to_string.h header is shared between libbinder and
@@ -77,6 +68,11 @@ MODULE_EXPORT_INCLUDES += \
MODULE_EXPORT_COMPILEFLAGS += \
-DBINDER_RPC_SINGLE_THREADED \
+ -DBINDER_ENABLE_LIBLOG_ASSERT \
+ -DBINDER_DISABLE_NATIVE_HANDLE \
+ -DBINDER_DISABLE_BLOB \
+ -DBINDER_NO_LIBBASE \
+ -D__ANDROID_VENDOR__ \
-D__ANDROID_VNDK__ \
# libbinder has some deprecated declarations that we want to produce warnings
diff --git a/libs/binder/trusty/rust/binder_ndk_sys/rules.mk b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
new file mode 100644
index 0000000000..2aaa061ddf
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_ndk_sys/rules.mk
@@ -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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+LIBBINDER_NDK_BINDGEN_FLAG_FILE := \
+ $(LIBBINDER_DIR)/rust/libbinder_ndk_bindgen_flags.txt
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LIBBINDER_DIR)/rust/sys/lib.rs
+
+MODULE_CRATE_NAME := binder_ndk_sys
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ trusty/user/base/lib/trusty-sys \
+
+MODULE_RUSTFLAGS += \
+ --cfg 'android_vendor' \
+ --cfg 'trusty' \
+
+MODULE_BINDGEN_SRC_HEADER := $(LIBBINDER_DIR)/rust/sys/BinderBindings.hpp
+
+# Add the flags from the flag file
+MODULE_BINDGEN_FLAGS += $(shell cat $(LIBBINDER_NDK_BINDGEN_FLAG_FILE))
+MODULE_SRCDEPS += $(LIBBINDER_NDK_BINDGEN_FLAG_FILE)
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
new file mode 100644
index 0000000000..451383a90a
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/ARpcServerTrusty.cpp
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/binder_libbinder.h>
+#include <binder/RpcServer.h>
+#include <binder/RpcServerTrusty.h>
+#include <binder/RpcSession.h>
+#include <binder/RpcTransportTipcTrusty.h>
+
+using android::RpcServer;
+using android::RpcServerTrusty;
+using android::RpcSession;
+using android::RpcTransportCtxFactoryTipcTrusty;
+using android::sp;
+using android::wp;
+
+struct ARpcServerTrusty {
+ sp<RpcServer> mRpcServer;
+
+ ARpcServerTrusty() = delete;
+ ARpcServerTrusty(sp<RpcServer> rpcServer) : mRpcServer(std::move(rpcServer)) {}
+};
+
+ARpcServerTrusty* ARpcServerTrusty_newPerSession(AIBinder* (*cb)(const void*, size_t, char*),
+ char* cbArg, void (*cbArgDeleter)(char*)) {
+ std::shared_ptr<char> cbArgSp(cbArg, cbArgDeleter);
+
+ auto rpcTransportCtxFactory = RpcTransportCtxFactoryTipcTrusty::make();
+ if (rpcTransportCtxFactory == nullptr) {
+ return nullptr;
+ }
+
+ auto ctx = rpcTransportCtxFactory->newServerCtx();
+ if (ctx == nullptr) {
+ return nullptr;
+ }
+
+ auto rpcServer = RpcServerTrusty::makeRpcServer(std::move(ctx));
+ if (rpcServer == nullptr) {
+ return nullptr;
+ }
+
+ rpcServer->setPerSessionRootObject(
+ [cb, cbArgSp](wp<RpcSession> /*session*/, const void* addrPtr, size_t len) {
+ auto* aib = (*cb)(addrPtr, len, cbArgSp.get());
+ auto b = AIBinder_toPlatformBinder(aib);
+
+ // We have a new sp<IBinder> backed by the same binder, so we can
+ // finally release the AIBinder* from the callback
+ AIBinder_decStrong(aib);
+
+ return b;
+ });
+
+ return new (std::nothrow) ARpcServerTrusty(std::move(rpcServer));
+}
+
+void ARpcServerTrusty_delete(ARpcServerTrusty* rstr) {
+ delete rstr;
+}
+
+int ARpcServerTrusty_handleConnect(ARpcServerTrusty* rstr, handle_t chan, const uuid* peer,
+ void** ctx_p) {
+ return RpcServerTrusty::handleConnectInternal(rstr->mRpcServer.get(), chan, peer, ctx_p);
+}
+
+int ARpcServerTrusty_handleMessage(void* ctx) {
+ return RpcServerTrusty::handleMessageInternal(ctx);
+}
+
+void ARpcServerTrusty_handleDisconnect(void* ctx) {
+ RpcServerTrusty::handleDisconnectInternal(ctx);
+}
+
+void ARpcServerTrusty_handleChannelCleanup(void* ctx) {
+ RpcServerTrusty::handleChannelCleanup(ctx);
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
new file mode 100644
index 0000000000..6def6343fa
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/cpp/rules.mk
@@ -0,0 +1,29 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := \
+ $(LOCAL_DIR)/ARpcServerTrusty.cpp \
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ trusty/user/base/lib/libstdc++-trusty \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
new file mode 100644
index 0000000000..2e8b3ecb03
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Generated Rust bindings to binder_rpc_server
+
+#[allow(bad_style)]
+mod sys {
+ include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use sys::*;
diff --git a/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
new file mode 100644
index 0000000000..4ee333f5be
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_server_bindgen/rules.mk
@@ -0,0 +1,37 @@
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_server_bindgen
+
+MODULE_LIBRARY_DEPS += \
+ $(LOCAL_DIR)/cpp \
+ trusty/user/base/lib/libstdc++-trusty \
+ trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := \
+ $(LIBBINDER_DIR)/trusty/include/binder/ARpcServerTrusty.h
+
+MODULE_BINDGEN_FLAGS += \
+ --allowlist-type="ARpcServerTrusty" \
+ --allowlist-function="ARpcServerTrusty_.*" \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
new file mode 100644
index 0000000000..1b0dca09f4
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/aidl/rules.mk
@@ -0,0 +1,34 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_TESTS_DIR := $(LOCAL_DIR)/../../../../tests
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_AIDL_LANGUAGE := rust
+
+MODULE_CRATE_NAME := binder_rpc_test_aidl
+
+MODULE_AIDLS := \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestClientInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerConfig.aidl \
+ $(LIBBINDER_TESTS_DIR)/BinderRpcTestServerInfo.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcCallback.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcSession.aidl \
+ $(LIBBINDER_TESTS_DIR)/IBinderRpcTest.aidl \
+ $(LIBBINDER_TESTS_DIR)/ParcelableCertificateData.aidl \
+
+include make/aidl.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
new file mode 100644
index 0000000000..22cba44975
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/lib.rs
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+use binder::{Interface, ParcelFileDescriptor, SpIBinder, Status, StatusCode, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcCallback::IBinderRpcCallback;
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::IBinderRpcSession;
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::IBinderRpcTest;
+use std::sync::Mutex;
+
+static G_NUM: Mutex<i32> = Mutex::new(0);
+
+#[derive(Debug, Default)]
+pub struct MyBinderRpcSession {
+ name: String,
+}
+
+impl MyBinderRpcSession {
+ pub fn new(name: &str) -> Self {
+ Self::increment_instance_count();
+ Self { name: name.to_string() }
+ }
+
+ pub fn get_instance_count() -> i32 {
+ *G_NUM.lock().unwrap()
+ }
+
+ fn increment_instance_count() {
+ *G_NUM.lock().unwrap() += 1;
+ }
+
+ fn decrement_instance_count() {
+ *G_NUM.lock().unwrap() -= 1;
+ }
+}
+
+impl Drop for MyBinderRpcSession {
+ fn drop(&mut self) {
+ MyBinderRpcSession::decrement_instance_count();
+ }
+}
+
+impl Interface for MyBinderRpcSession {}
+
+impl IBinderRpcSession for MyBinderRpcSession {
+ fn getName(&self) -> Result<String, Status> {
+ Ok(self.name.clone())
+ }
+}
+
+impl IBinderRpcTest for MyBinderRpcSession {
+ fn sendString(&self, _: &str) -> Result<(), Status> {
+ todo!()
+ }
+ fn doubleString(&self, _s: &str) -> Result<String, Status> {
+ todo!()
+ }
+ fn getClientPort(&self) -> Result<i32, Status> {
+ todo!()
+ }
+ fn countBinders(&self) -> Result<Vec<i32>, Status> {
+ todo!()
+ }
+ fn getNullBinder(&self) -> Result<SpIBinder, Status> {
+ todo!()
+ }
+ fn pingMe(&self, _binder: &SpIBinder) -> Result<i32, Status> {
+ todo!()
+ }
+ fn repeatBinder(&self, _binder: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
+ todo!()
+ }
+ fn holdBinder(&self, _binder: Option<&SpIBinder>) -> Result<(), Status> {
+ todo!()
+ }
+ fn getHeldBinder(&self) -> Result<Option<SpIBinder>, Status> {
+ todo!()
+ }
+ fn nestMe(
+ &self,
+ binder: &Strong<(dyn IBinderRpcTest + 'static)>,
+ count: i32,
+ ) -> Result<(), Status> {
+ if count < 0 {
+ Ok(())
+ } else {
+ binder.nestMe(binder, count - 1)
+ }
+ }
+ fn alwaysGiveMeTheSameBinder(&self) -> Result<SpIBinder, Status> {
+ todo!()
+ }
+ fn openSession(
+ &self,
+ _name: &str,
+ ) -> Result<Strong<(dyn IBinderRpcSession + 'static)>, Status> {
+ todo!()
+ }
+ fn getNumOpenSessions(&self) -> Result<i32, Status> {
+ todo!()
+ }
+ fn lock(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn unlockInMsAsync(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn lockUnlock(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn sleepMs(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn sleepMsAsync(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn doCallback(
+ &self,
+ _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+ _: bool,
+ _: bool,
+ _: &str,
+ ) -> Result<(), Status> {
+ todo!()
+ }
+ fn doCallbackAsync(
+ &self,
+ _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+ _: bool,
+ _: bool,
+ _: &str,
+ ) -> Result<(), Status> {
+ todo!()
+ }
+ fn die(&self, _: bool) -> Result<(), Status> {
+ Err(Status::from(StatusCode::UNKNOWN_TRANSACTION))
+ }
+ fn scheduleShutdown(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn useKernelBinderCallingId(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn echoAsFile(&self, _: &str) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn concatFiles(&self, _: &[ParcelFileDescriptor]) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn blockingSendFdOneway(&self, _: &ParcelFileDescriptor) -> Result<(), Status> {
+ todo!()
+ }
+ fn blockingRecvFd(&self) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn blockingSendIntOneway(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn blockingRecvInt(&self) -> Result<i32, Status> {
+ todo!()
+ }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk
new file mode 100644
index 0000000000..ae26355521
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/binder_rpc_test_session/rules.mk
@@ -0,0 +1,32 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_test_session
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+ $(LOCAL_DIR)/../aidl \
+ $(call FIND_CRATE,log) \
+ trusty/user/base/lib/trusty-std \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/main.rs b/libs/binder/trusty/rust/binder_rpc_test/main.rs
new file mode 100644
index 0000000000..baea5a827b
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/main.rs
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+#![cfg(test)]
+
+use binder::{BinderFeatures, IBinder, Status, StatusCode, Strong};
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
+use binder_rpc_test_session::MyBinderRpcSession;
+use libc::{clock_gettime, CLOCK_REALTIME};
+use rpcbinder::RpcSession;
+use trusty_std::ffi::{CString, FallibleCString};
+
+test::init!();
+
+const SERVICE_PORT: &str = "com.android.trusty.binderRpcTestService.V1";
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+macro_rules! service_test {
+ ($c_name:ident, $rust_name:ident, $body:expr) => {
+ #[test]
+ fn $c_name() {
+ $body(get_service(SERVICE_PORT))
+ }
+ #[test]
+ fn $rust_name() {
+ $body(get_service(RUST_SERVICE_PORT))
+ }
+ };
+}
+
+fn get_service(port: &str) -> Strong<dyn IBinderRpcTest> {
+ let port = CString::try_new(port).expect("Failed to allocate port name");
+ RpcSession::new().setup_trusty_client(port.as_c_str()).expect("Failed to create session")
+}
+
+fn expect_sessions(expected: i32, srv: &Strong<dyn IBinderRpcTest>) {
+ let count = srv.getNumOpenSessions();
+ assert!(count.is_ok());
+ assert_eq!(expected, count.unwrap());
+}
+
+fn get_time_ns() -> u64 {
+ let mut ts = libc::timespec { tv_sec: 0, tv_nsec: 0 };
+
+ // Safety: Passing valid pointer to variable ts which lives past end of call
+ assert_eq!(unsafe { clock_gettime(CLOCK_REALTIME, &mut ts) }, 0);
+
+ ts.tv_sec as u64 * 1_000_000_000u64 + ts.tv_nsec as u64
+}
+
+fn get_time_ms() -> u64 {
+ get_time_ns() / 1_000_000u64
+}
+
+// ----------
+
+service_test! {ping, ping_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ assert_eq!(srv.as_binder().ping_binder(), Ok(()));
+}}
+
+service_test! {send_something_oneway, send_something_oneway_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ assert_eq!(srv.sendString("Foo"), Ok(()));
+}}
+
+service_test! {send_and_get_result_back, send_and_get_result_back_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ assert_eq!(srv.doubleString("Foo"), Ok(String::from("FooFoo")));
+}}
+
+service_test! {send_and_get_result_back_big, send_and_get_result_back_big_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let single_len = 512;
+ let single = "a".repeat(single_len);
+ assert_eq!(srv.doubleString(&single), Ok(String::from(single.clone() + &single)));
+}}
+
+service_test! {invalid_null_binder_return, invalid_null_binder_return_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let binder = srv.getNullBinder();
+ assert!(binder == Err(Status::from(StatusCode::UNEXPECTED_NULL)) || binder == Err(Status::from(StatusCode::UNKNOWN_TRANSACTION)));
+}}
+
+service_test! {call_me_back, call_me_back_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let binder =
+ BnBinderRpcSession::new_binder(MyBinderRpcSession::new("Foo"), BinderFeatures::default())
+ .as_binder();
+ let result = srv.pingMe(&binder);
+ assert_eq!(result, Ok(0));
+}}
+
+service_test! {repeat_binder, repeat_binder_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let in_binder =
+ BnBinderRpcSession::new_binder(MyBinderRpcSession::new("Foo"), BinderFeatures::default())
+ .as_binder();
+ let result = srv.repeatBinder(Some(&in_binder));
+ assert_eq!(result.unwrap().unwrap(), in_binder);
+}}
+
+service_test! {repeat_their_binder, repeat_their_binder_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let session = srv.openSession("Test");
+ assert!(session.is_ok());
+
+ let in_binder = session.unwrap().as_binder();
+ let out_binder = srv.repeatBinder(Some(&in_binder));
+ assert_eq!(out_binder.unwrap().unwrap(), in_binder);
+}}
+
+service_test! {hold_binder, hold_binder_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let name = "Foo";
+
+ let binder =
+ BnBinderRpcSession::new_binder(MyBinderRpcSession::new(name), BinderFeatures::default())
+ .as_binder();
+ assert!(srv.holdBinder(Some(&binder)).is_ok());
+
+ let held = srv.getHeldBinder();
+ assert!(held.is_ok());
+ let held = held.unwrap();
+ assert!(held.is_some());
+ let held = held.unwrap();
+ assert_eq!(binder, held);
+
+ let session = held.into_interface::<dyn IBinderRpcSession>();
+ assert!(session.is_ok());
+
+ let session_name = session.unwrap().getName();
+ assert!(session_name.is_ok());
+ let session_name = session_name.unwrap();
+ assert_eq!(session_name, name);
+
+ assert!(srv.holdBinder(None).is_ok());
+}}
+
+service_test! {nested_transactions, nested_transactions_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let binder =
+ BnBinderRpcTest::new_binder(MyBinderRpcSession::new("Nest"), BinderFeatures::default());
+ assert!(srv.nestMe(&binder, 10).is_ok());
+}}
+
+service_test! {same_binder_equality, same_binder_equality_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let a = srv.alwaysGiveMeTheSameBinder();
+ assert!(a.is_ok());
+
+ let b = srv.alwaysGiveMeTheSameBinder();
+ assert!(b.is_ok());
+
+ assert_eq!(a.unwrap(), b.unwrap());
+}}
+
+service_test! {single_session, single_session_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let session = srv.openSession("aoeu");
+ assert!(session.is_ok());
+ let session = session.unwrap();
+ let name = session.getName();
+ assert!(name.is_ok());
+ assert_eq!(name.unwrap(), "aoeu");
+
+ let count = srv.getNumOpenSessions();
+ assert!(count.is_ok());
+ assert_eq!(count.unwrap(), 1);
+
+ drop(session);
+ let count = srv.getNumOpenSessions();
+ assert!(count.is_ok());
+ assert_eq!(count.unwrap(), 0);
+}}
+
+service_test! {many_session, many_session_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let mut sessions = Vec::new();
+
+ for i in 0..15 {
+ expect_sessions(i, &srv);
+
+ let session = srv.openSession(&(i.to_string()));
+ assert!(session.is_ok());
+ sessions.push(session.unwrap());
+ }
+
+ expect_sessions(sessions.len() as i32, &srv);
+
+ for i in 0..sessions.len() {
+ let name = sessions[i].getName();
+ assert!(name.is_ok());
+ assert_eq!(name.unwrap(), i.to_string());
+ }
+
+ expect_sessions(sessions.len() as i32, &srv);
+
+ while !sessions.is_empty() {
+ sessions.pop();
+
+ expect_sessions(sessions.len() as i32, &srv);
+ }
+
+ expect_sessions(0, &srv);
+}}
+
+service_test! {one_way_call_does_not_wait, one_way_call_does_not_wait_rust, |srv: Strong<dyn IBinderRpcTest>| {
+ let really_long_time_ms = 100;
+ let sleep_ms = really_long_time_ms * 5;
+
+ let before = get_time_ms();
+ let _ = srv.sleepMsAsync(sleep_ms);
+ let after = get_time_ms();
+
+ assert!(after < before + really_long_time_ms as u64);
+}}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
new file mode 100644
index 0000000000..384ed441c6
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/manifest.json
@@ -0,0 +1,9 @@
+{
+ "uuid": "91eed949-8a9e-4569-9c83-5935fb624025",
+ "app_name": "rust_binder_rpc_test",
+ "min_heap": 32768,
+ "min_stack": 16384,
+ "mgmt_flags": {
+ "non_critical_app": true
+ }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
new file mode 100644
index 0000000000..8347a359db
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/rules.mk
@@ -0,0 +1,37 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+ $(LOCAL_DIR)/aidl \
+ $(LOCAL_DIR)/binder_rpc_test_session \
+ $(call FIND_CRATE,log) \
+ trusty/user/base/lib/trusty-std \
+
+MODULE_RUST_TESTS := true
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/main.rs b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
new file mode 100644
index 0000000000..c4a758a214
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/main.rs
@@ -0,0 +1,234 @@
+/*
+ * 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.
+ */
+use binder::{
+ BinderFeatures, IBinder, Interface, ParcelFileDescriptor, SpIBinder, Status, StatusCode, Strong,
+};
+use binder_rpc_test_aidl::aidl::IBinderRpcCallback::IBinderRpcCallback;
+use binder_rpc_test_aidl::aidl::IBinderRpcSession::{BnBinderRpcSession, IBinderRpcSession};
+use binder_rpc_test_aidl::aidl::IBinderRpcTest::{BnBinderRpcTest, IBinderRpcTest};
+use binder_rpc_test_session::MyBinderRpcSession;
+use libc::{c_long, nanosleep, timespec};
+use rpcbinder::RpcServer;
+use std::rc::Rc;
+use std::sync::Mutex;
+use tipc::{service_dispatcher, wrap_service, Manager, PortCfg};
+
+const RUST_SERVICE_PORT: &str = "com.android.trusty.rust.binderRpcTestService.V1";
+
+// -----------------------------------------------------------------------------
+
+static SESSION_COUNT: Mutex<i32> = Mutex::new(0);
+static HOLD_BINDER: Mutex<Option<SpIBinder>> = Mutex::new(None);
+static SAME_BINDER: Mutex<Option<SpIBinder>> = Mutex::new(None);
+
+#[derive(Debug, Default)]
+struct TestService {
+ port: i32,
+ name: String,
+}
+
+#[allow(dead_code)]
+impl TestService {
+ fn new(name: &str) -> Self {
+ *SESSION_COUNT.lock().unwrap() += 1;
+ Self { name: name.to_string(), ..Default::default() }
+ }
+
+ fn get_instance_count() -> i32 {
+ *SESSION_COUNT.lock().unwrap()
+ }
+}
+
+impl Drop for TestService {
+ fn drop(&mut self) {
+ *SESSION_COUNT.lock().unwrap() -= 1;
+ }
+}
+
+impl Interface for TestService {}
+
+impl IBinderRpcSession for TestService {
+ fn getName(&self) -> Result<String, Status> {
+ Ok(self.name.clone())
+ }
+}
+
+impl IBinderRpcTest for TestService {
+ fn sendString(&self, _: &str) -> Result<(), Status> {
+ // This is a oneway function, so caller returned immediately and gives back an Ok(()) regardless of what this returns
+ Ok(())
+ }
+ fn doubleString(&self, s: &str) -> Result<String, Status> {
+ let ss = [s, s].concat();
+ Ok(ss)
+ }
+ fn getClientPort(&self) -> Result<i32, Status> {
+ Ok(self.port)
+ }
+ fn countBinders(&self) -> Result<Vec<i32>, Status> {
+ todo!()
+ }
+ fn getNullBinder(&self) -> Result<SpIBinder, Status> {
+ Err(Status::from(StatusCode::UNKNOWN_TRANSACTION))
+ }
+ fn pingMe(&self, binder: &SpIBinder) -> Result<i32, Status> {
+ match binder.clone().ping_binder() {
+ Ok(()) => Ok(StatusCode::OK as i32),
+ Err(e) => Err(Status::from(e)),
+ }
+ }
+ fn repeatBinder(&self, binder: Option<&SpIBinder>) -> Result<Option<SpIBinder>, Status> {
+ match binder {
+ Some(x) => Ok(Some(x.clone())),
+ None => Err(Status::from(StatusCode::BAD_VALUE)),
+ }
+ }
+ fn holdBinder(&self, binder: Option<&SpIBinder>) -> Result<(), Status> {
+ *HOLD_BINDER.lock().unwrap() = binder.cloned();
+ Ok(())
+ }
+ fn getHeldBinder(&self) -> Result<Option<SpIBinder>, Status> {
+ Ok((*HOLD_BINDER.lock().unwrap()).clone())
+ }
+ fn nestMe(
+ &self,
+ binder: &Strong<(dyn IBinderRpcTest + 'static)>,
+ count: i32,
+ ) -> Result<(), Status> {
+ if count < 0 {
+ Ok(())
+ } else {
+ binder.nestMe(binder, count - 1)
+ }
+ }
+ fn alwaysGiveMeTheSameBinder(&self) -> Result<SpIBinder, Status> {
+ let mut locked = SAME_BINDER.lock().unwrap();
+ Ok((*locked)
+ .get_or_insert_with(|| {
+ BnBinderRpcTest::new_binder(TestService::default(), BinderFeatures::default())
+ .as_binder()
+ })
+ .clone())
+ }
+ fn openSession(&self, name: &str) -> Result<Strong<(dyn IBinderRpcSession + 'static)>, Status> {
+ let s = BnBinderRpcSession::new_binder(
+ MyBinderRpcSession::new(name),
+ BinderFeatures::default(),
+ );
+ Ok(s)
+ }
+ fn getNumOpenSessions(&self) -> Result<i32, Status> {
+ let count = MyBinderRpcSession::get_instance_count();
+ Ok(count)
+ }
+ fn lock(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn unlockInMsAsync(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn lockUnlock(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn sleepMs(&self, ms: i32) -> Result<(), Status> {
+ let ts = timespec {
+ tv_sec: (ms / 1000) as c_long,
+ tv_nsec: (ms % 1000) as c_long * 1_000_000 as c_long,
+ };
+
+ let mut rem = timespec { tv_sec: 0, tv_nsec: 0 };
+
+ // Safety: Passing valid pointers to variables ts & rem which live past end of call
+ assert_eq!(unsafe { nanosleep(&ts, &mut rem) }, 0);
+
+ Ok(())
+ }
+ fn sleepMsAsync(&self, ms: i32) -> Result<(), Status> {
+ self.sleepMs(ms)
+ }
+ fn doCallback(
+ &self,
+ _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+ _: bool,
+ _: bool,
+ _: &str,
+ ) -> Result<(), Status> {
+ todo!()
+ }
+ fn doCallbackAsync(
+ &self,
+ _: &Strong<(dyn IBinderRpcCallback + 'static)>,
+ _: bool,
+ _: bool,
+ _: &str,
+ ) -> Result<(), Status> {
+ todo!()
+ }
+ fn die(&self, _: bool) -> Result<(), Status> {
+ Err(Status::from(StatusCode::UNKNOWN_TRANSACTION))
+ }
+ fn scheduleShutdown(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn useKernelBinderCallingId(&self) -> Result<(), Status> {
+ todo!()
+ }
+ fn echoAsFile(&self, _: &str) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn concatFiles(&self, _: &[ParcelFileDescriptor]) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn blockingSendFdOneway(&self, _: &ParcelFileDescriptor) -> Result<(), Status> {
+ todo!()
+ }
+ fn blockingRecvFd(&self) -> Result<ParcelFileDescriptor, Status> {
+ todo!()
+ }
+ fn blockingSendIntOneway(&self, _: i32) -> Result<(), Status> {
+ todo!()
+ }
+ fn blockingRecvInt(&self) -> Result<i32, Status> {
+ todo!()
+ }
+}
+
+wrap_service!(TestRpcServer(RpcServer: UnbufferedService));
+
+service_dispatcher! {
+ enum TestDispatcher {
+ TestRpcServer,
+ }
+}
+
+fn main() {
+ let mut dispatcher = TestDispatcher::<1>::new().expect("Could not create test dispatcher");
+
+ let service = BnBinderRpcTest::new_binder(TestService::default(), BinderFeatures::default());
+ let rpc_server =
+ TestRpcServer::new(RpcServer::new_per_session(move |_uuid| Some(service.as_binder())));
+
+ let cfg = PortCfg::new(RUST_SERVICE_PORT)
+ .expect("Could not create port config")
+ .allow_ta_connect()
+ .allow_ns_connect();
+ dispatcher.add_service(Rc::new(rpc_server), cfg).expect("Could not add service to dispatcher");
+
+ Manager::<_, _, 1, 4>::new_with_dispatcher(dispatcher, [])
+ .expect("Could not create service manager")
+ .run_event_loop()
+ .expect("Test event loop failed");
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
new file mode 100644
index 0000000000..121ba112a8
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/manifest.json
@@ -0,0 +1,10 @@
+{
+ "uuid": "4741fc65-8b65-4893-ba55-b182c003c8b7",
+ "app_name": "rust_binder_rpc_test_service",
+ "min_heap": 16384,
+ "min_stack": 16384,
+ "mgmt_flags": {
+ "non_critical_app": true,
+ "restart_on_exit": true
+ }
+}
diff --git a/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
new file mode 100644
index 0000000000..f71ee9bcbd
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_test/service/rules.mk
@@ -0,0 +1,36 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/main.rs
+
+MODULE_CRATE_NAME := binder_rpc_test_service
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/rpcbinder \
+ $(LIBBINDER_DIR)/trusty/rust/binder_rpc_server \
+ $(LOCAL_DIR)/../aidl \
+ $(LOCAL_DIR)/../binder_rpc_test_session \
+ $(LOCAL_DIR)/.. \
+ trusty/user/base/lib/tipc/rust \
+
+MANIFEST := $(LOCAL_DIR)/manifest.json
+
+include make/trusted_app.mk
diff --git a/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp
new file mode 100644
index 0000000000..6f96566618
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/BinderBindings.hpp
@@ -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.
+ */
+
+#include <binder_rpc_unstable.hpp>
diff --git a/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs
new file mode 100644
index 0000000000..c7036f45f7
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+//! Generated Rust bindings to binder_rpc_unstable
+
+#[allow(bad_style)]
+mod sys {
+ include!(env!("BINDGEN_INC_FILE"));
+}
+
+pub use sys::*;
diff --git a/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk
new file mode 100644
index 0000000000..ef1b7c3cf8
--- /dev/null
+++ b/libs/binder/trusty/rust/binder_rpc_unstable_bindgen/rules.mk
@@ -0,0 +1,40 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LOCAL_DIR)/lib.rs
+
+MODULE_CRATE_NAME := binder_rpc_unstable_bindgen
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/binder_rpc_unstable \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ $(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
+ trusty/user/base/lib/libstdc++-trusty \
+ trusty/user/base/lib/trusty-sys \
+
+MODULE_BINDGEN_SRC_HEADER := $(LOCAL_DIR)/BinderBindings.hpp
+
+MODULE_BINDGEN_FLAGS += \
+ --blocklist-type="AIBinder" \
+ --raw-line="use binder_ndk_sys::AIBinder;" \
+ --rustified-enum="ARpcSession_FileDescriptorTransportMode" \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/rpcbinder/rules.mk b/libs/binder/trusty/rust/rpcbinder/rules.mk
new file mode 100644
index 0000000000..97f5c03cba
--- /dev/null
+++ b/libs/binder/trusty/rust/rpcbinder/rules.mk
@@ -0,0 +1,37 @@
+# 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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LIBBINDER_DIR)/rust/rpcbinder/src/lib.rs
+
+MODULE_CRATE_NAME := rpcbinder
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ $(LIBBINDER_DIR)/trusty/rust \
+ $(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
+ $(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+ $(LIBBINDER_DIR)/trusty/rust/binder_rpc_server_bindgen \
+ external/rust/crates/cfg-if \
+ external/rust/crates/foreign-types \
+ trusty/user/base/lib/tipc/rust \
+ trusty/user/base/lib/trusty-sys \
+
+include make/library.mk
diff --git a/libs/binder/trusty/rust/rules.mk b/libs/binder/trusty/rust/rules.mk
new file mode 100644
index 0000000000..36bd3a2e75
--- /dev/null
+++ b/libs/binder/trusty/rust/rules.mk
@@ -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.
+#
+
+LOCAL_DIR := $(GET_LOCAL_DIR)
+LIBBINDER_DIR := $(LOCAL_DIR)/../..
+
+MODULE := $(LOCAL_DIR)
+
+MODULE_SRCS := $(LIBBINDER_DIR)/rust/src/lib.rs
+
+MODULE_CRATE_NAME := binder
+
+MODULE_LIBRARY_DEPS += \
+ $(LIBBINDER_DIR)/trusty \
+ $(LIBBINDER_DIR)/trusty/ndk \
+ $(LIBBINDER_DIR)/trusty/rust/binder_ndk_sys \
+ $(LIBBINDER_DIR)/trusty/rust/binder_rpc_unstable_bindgen \
+ external/rust/crates/downcast-rs \
+ external/rust/crates/libc \
+ trusty/user/base/lib/trusty-sys \
+
+MODULE_RUSTFLAGS += \
+ --cfg 'android_vendor' \
+ --cfg 'trusty' \
+
+# Trusty does not have `ProcessState`, so there are a few
+# doc links in `IBinder` that are still broken.
+MODULE_RUSTFLAGS += \
+ --allow rustdoc::broken-intra-doc-links \
+
+include make/library.mk
diff --git a/libs/binder/trusty/usertests-inc.mk b/libs/binder/trusty/usertests-inc.mk
index 13001219d4..833d209012 100644
--- a/libs/binder/trusty/usertests-inc.mk
+++ b/libs/binder/trusty/usertests-inc.mk
@@ -16,4 +16,8 @@
TRUSTY_USER_TESTS += \
frameworks/native/libs/binder/trusty/binderRpcTest \
frameworks/native/libs/binder/trusty/binderRpcTest/service \
+ frameworks/native/libs/binder/trusty/rust/binder_rpc_test/service \
+
+TRUSTY_RUST_USER_TESTS += \
+ frameworks/native/libs/binder/trusty/rust/binder_rpc_test \
diff --git a/libs/binderdebug/Android.bp b/libs/binderdebug/Android.bp
index 3eeaf3e4f1..1454727114 100644
--- a/libs/binderdebug/Android.bp
+++ b/libs/binderdebug/Android.bp
@@ -21,6 +21,17 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
+cc_benchmark {
+ name: "binder_thread_stats",
+ shared_libs: [
+ "libutils",
+ "libbinder",
+ "libbase",
+ ],
+ static_libs: ["libbinderdebug"],
+ srcs: ["stats.cpp"],
+}
+
cc_library {
name: "libbinderdebug",
vendor_available: true,
diff --git a/libs/binderdebug/BinderDebug.cpp b/libs/binderdebug/BinderDebug.cpp
index d086b49a7b..19f3aad04f 100644
--- a/libs/binderdebug/BinderDebug.cpp
+++ b/libs/binderdebug/BinderDebug.cpp
@@ -48,14 +48,12 @@ static status_t scanBinderContext(pid_t pid, const std::string& contextName,
return -errno;
}
}
- static const std::regex kContextLine("^context (\\w+)$");
bool isDesiredContext = false;
std::string line;
- std::smatch match;
while (getline(ifs, line)) {
- if (std::regex_search(line, match, kContextLine)) {
- isDesiredContext = match.str(1) == contextName;
+ if (base::StartsWith(line, "context")) {
+ isDesiredContext = base::Split(line, " ").back() == contextName;
continue;
}
if (!isDesiredContext) {
@@ -66,57 +64,72 @@ static status_t scanBinderContext(pid_t pid, const std::string& contextName,
return OK;
}
+// Examples of what we are looking at:
+// node 66730: u00007590061890e0 c0000759036130950 pri 0:120 hs 1 hw 1 ls 0 lw 0 is 2 iw 2 tr 1 proc 2300 1790
+// thread 2999: l 00 need_return 1 tr 0
status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo* pidInfo) {
std::smatch match;
static const std::regex kReferencePrefix("^\\s*node \\d+:\\s+u([0-9a-f]+)\\s+c([0-9a-f]+)\\s+");
static const std::regex kThreadPrefix("^\\s*thread \\d+:\\s+l\\s+(\\d)(\\d)");
std::string contextStr = contextToString(context);
status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) {
- if (std::regex_search(line, match, kReferencePrefix)) {
- const std::string& ptrString = "0x" + match.str(2); // use number after c
- uint64_t ptr;
- if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
- // Should not reach here, but just be tolerant.
- return;
- }
- const std::string proc = " proc ";
- auto pos = line.rfind(proc);
- if (pos != std::string::npos) {
- for (const std::string& pidStr : base::Split(line.substr(pos + proc.size()), " ")) {
- int32_t pid;
- if (!::android::base::ParseInt(pidStr, &pid)) {
+ if (base::StartsWith(line, " node")) {
+ std::vector<std::string> splitString = base::Tokenize(line, " ");
+ bool pids = false;
+ uint64_t ptr = 0;
+ for (const auto& token : splitString) {
+ if (base::StartsWith(token, "u")) {
+ const std::string ptrString = "0x" + token.substr(1);
+ if (!::android::base::ParseUint(ptrString.c_str(), &ptr)) {
+ LOG(ERROR) << "Failed to parse pointer: " << ptrString;
return;
}
- pidInfo->refPids[ptr].push_back(pid);
+ } else {
+ // The last numbers in the line after "proc" are all client PIDs
+ if (token == "proc") {
+ pids = true;
+ } else if (pids) {
+ int32_t pid;
+ if (!::android::base::ParseInt(token, &pid)) {
+ LOG(ERROR) << "Failed to parse pid int: " << token;
+ return;
+ }
+ if (ptr == 0) {
+ LOG(ERROR) << "We failed to parse the pointer, so we can't add the refPids";
+ return;
+ }
+ pidInfo->refPids[ptr].push_back(pid);
+ }
}
}
+ } else if (base::StartsWith(line, " thread")) {
+ auto pos = line.find("l ");
+ if (pos != std::string::npos) {
+ // "1" is waiting in binder driver
+ // "2" is poll. It's impossible to tell if these are in use.
+ // and HIDL default code doesn't use it.
+ bool isInUse = line.at(pos + 2) != '1';
+ // "0" is a thread that has called into binder
+ // "1" is looper thread
+ // "2" is main looper thread
+ bool isBinderThread = line.at(pos + 3) != '0';
+ if (!isBinderThread) {
+ return;
+ }
+ if (isInUse) {
+ pidInfo->threadUsage++;
+ }
- return;
- }
- if (std::regex_search(line, match, kThreadPrefix)) {
- // "1" is waiting in binder driver
- // "2" is poll. It's impossible to tell if these are in use.
- // and HIDL default code doesn't use it.
- bool isInUse = match.str(1) != "1";
- // "0" is a thread that has called into binder
- // "1" is looper thread
- // "2" is main looper thread
- bool isBinderThread = match.str(2) != "0";
- if (!isBinderThread) {
- return;
+ pidInfo->threadCount++;
}
- if (isInUse) {
- pidInfo->threadUsage++;
- }
-
- pidInfo->threadCount++;
- return;
}
- return;
});
return ret;
}
+// Examples of what we are looking at:
+// ref 52493: desc 910 node 52492 s 1 w 1 d 0000000000000000
+// node 29413: u00007803fc982e80 c000078042c982210 pri 0:139 hs 1 hw 1 ls 0 lw 0 is 2 iw 2 tr 1 proc 488 683
status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servicePid,
int32_t handle, std::vector<pid_t>* pids) {
std::smatch match;
@@ -124,53 +137,93 @@ status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servic
std::string contextStr = contextToString(context);
int32_t node;
status_t ret = scanBinderContext(pid, contextStr, [&](const std::string& line) {
- if (std::regex_search(line, match, kNodeNumber)) {
- const std::string& descString = match.str(1);
- int32_t desc;
- if (!::android::base::ParseInt(descString.c_str(), &desc)) {
- LOG(ERROR) << "Failed to parse desc int: " << descString;
- return;
- }
- if (handle != desc) {
- return;
- }
- const std::string& nodeString = match.str(2);
- if (!::android::base::ParseInt(nodeString.c_str(), &node)) {
- LOG(ERROR) << "Failed to parse node int: " << nodeString;
- return;
- }
+ if (!base::StartsWith(line, " ref")) return;
+
+ std::vector<std::string> splitString = base::Tokenize(line, " ");
+ if (splitString.size() < 12) {
+ LOG(ERROR) << "Failed to parse binder_logs ref entry. Expecting size greater than 11, but got: " << splitString.size();
+ return;
+ }
+ int32_t desc;
+ if (!::android::base::ParseInt(splitString[3].c_str(), &desc)) {
+ LOG(ERROR) << "Failed to parse desc int: " << splitString[3];
return;
}
- return;
+ if (handle != desc) {
+ return;
+ }
+ if (!::android::base::ParseInt(splitString[5].c_str(), &node)) {
+ LOG(ERROR) << "Failed to parse node int: " << splitString[5];
+ return;
+ }
+ LOG(INFO) << "Parsed the node: " << node;
});
if (ret != OK) {
return ret;
}
- static const std::regex kClients("^\\s+node\\s+(\\d+).*proc\\s+([\\d+\\s*]*)");
+
ret = scanBinderContext(servicePid, contextStr, [&](const std::string& line) {
- if (std::regex_search(line, match, kClients)) {
- const std::string nodeString = match.str(1);
- int32_t matchedNode;
- if (!::android::base::ParseInt(nodeString.c_str(), &matchedNode)) {
- LOG(ERROR) << "Failed to parse node int: " << nodeString;
- return;
- }
- if (node != matchedNode) {
- return;
- }
- const std::string clients = match.str(2);
- for (const std::string& pidStr : base::Split(clients, " ")) {
+ if (!base::StartsWith(line, " node")) return;
+
+ std::vector<std::string> splitString = base::Tokenize(line, " ");
+ if (splitString.size() < 21) {
+ LOG(ERROR) << "Failed to parse binder_logs node entry. Expecting size greater than 20, but got: " << splitString.size();
+ return;
+ }
+
+ // remove the colon
+ const std::string nodeString = splitString[1].substr(0, splitString[1].size() - 1);
+ int32_t matchedNode;
+ if (!::android::base::ParseInt(nodeString.c_str(), &matchedNode)) {
+ LOG(ERROR) << "Failed to parse node int: " << nodeString;
+ return;
+ }
+
+ if (node != matchedNode) {
+ return;
+ }
+ bool pidsSection = false;
+ for (const auto& token : splitString) {
+ if (token == "proc") {
+ pidsSection = true;
+ } else if (pidsSection == true) {
int32_t pid;
- if (!::android::base::ParseInt(pidStr, &pid)) {
+ if (!::android::base::ParseInt(token.c_str(), &pid)) {
+ LOG(ERROR) << "Failed to parse PID int: " << token;
return;
}
pids->push_back(pid);
}
- return;
}
- return;
});
return ret;
}
+status_t getBinderTransactions(pid_t pid, std::string& transactionsOutput) {
+ std::ifstream ifs("/dev/binderfs/binder_logs/transactions");
+ if (!ifs.is_open()) {
+ ifs.open("/d/binder/transactions");
+ if (!ifs.is_open()) {
+ LOG(ERROR) << "Could not open /dev/binderfs/binder_logs/transactions. "
+ << "Likely a permissions issue. errno: " << errno;
+ return -errno;
+ }
+ }
+
+ std::string line;
+ while (getline(ifs, line)) {
+ // The section for this pid ends with another "proc <pid>" for another
+ // process. There is only one entry per pid so we can stop looking after
+ // we've grabbed the whole section
+ if (base::StartsWith(line, "proc " + std::to_string(pid))) {
+ do {
+ transactionsOutput += line + '\n';
+ } while (getline(ifs, line) && !base::StartsWith(line, "proc "));
+ return OK;
+ }
+ }
+
+ return NAME_NOT_FOUND;
+}
+
} // namespace android
diff --git a/libs/binderdebug/include/binderdebug/BinderDebug.h b/libs/binderdebug/include/binderdebug/BinderDebug.h
index 6ce8edfc7c..018393c128 100644
--- a/libs/binderdebug/include/binderdebug/BinderDebug.h
+++ b/libs/binderdebug/include/binderdebug/BinderDebug.h
@@ -44,4 +44,12 @@ status_t getBinderPidInfo(BinderDebugContext context, pid_t pid, BinderPidInfo*
status_t getBinderClientPids(BinderDebugContext context, pid_t pid, pid_t servicePid,
int32_t handle, std::vector<pid_t>* pids);
+/**
+ * Get the transactions for a given process from /dev/binderfs/binder_logs/transactions
+ * Return: OK if the file was found and the pid was found in the file.
+ * -errno if there was an issue opening the file
+ * NAME_NOT_FOUND if the pid wasn't found in the file
+ */
+status_t getBinderTransactions(pid_t pid, std::string& transactionOutput);
+
} // namespace android
diff --git a/libs/binderdebug/stats.cpp b/libs/binderdebug/stats.cpp
new file mode 100644
index 0000000000..9c26afaa97
--- /dev/null
+++ b/libs/binderdebug/stats.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 <android-base/logging.h>
+#include <binder/BpBinder.h>
+#include <binder/IServiceManager.h>
+#include <binderdebug/BinderDebug.h>
+#include <utils/Errors.h>
+
+#include <inttypes.h>
+
+namespace android {
+
+extern "C" int main() {
+ // ignore args - we only print csv
+
+ // we should use a csv library here for escaping, because
+ // the name is coming from another process
+ printf("name,binder_threads_in_use,binder_threads_started,client_count\n");
+
+ for (const String16& name : defaultServiceManager()->listServices()) {
+ sp<IBinder> binder = defaultServiceManager()->checkService(name);
+ if (binder == nullptr) {
+ fprintf(stderr, "%s is null", String8(name).c_str());
+ continue;
+ }
+
+ BpBinder* remote = binder->remoteBinder();
+ const auto handle = remote->getDebugBinderHandle();
+ CHECK(handle != std::nullopt);
+
+ pid_t pid;
+ CHECK_EQ(OK, binder->getDebugPid(&pid));
+
+ BinderPidInfo info;
+ CHECK_EQ(OK, getBinderPidInfo(BinderDebugContext::BINDER, pid, &info));
+
+ std::vector<pid_t> clientPids;
+ CHECK_EQ(OK,
+ getBinderClientPids(BinderDebugContext::BINDER, getpid(), pid, *handle,
+ &clientPids));
+
+ printf("%s,%" PRIu32 ",%" PRIu32 ",%zu\n", String8(name).c_str(), info.threadUsage,
+ info.threadCount, clientPids.size());
+ }
+ return 0;
+}
+
+} // namespace android
diff --git a/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h b/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h
index a3e5026a07..54259d2dc4 100644
--- a/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h
+++ b/libs/binderthreadstate/include/binderthreadstate/CallerUtils.h
@@ -36,8 +36,12 @@ enum class BinderCallType {
// Based on where we are in recursion of nested binder/hwbinder calls, determine
// which one we are closer to.
inline static BinderCallType getCurrentServingCall() {
- const void* hwbinderSp = android::hardware::IPCThreadState::self()->getServingStackPointer();
- const void* binderSp = android::IPCThreadState::self()->getServingStackPointer();
+ auto* hwState = android::hardware::IPCThreadState::selfOrNull();
+ auto* state = android::IPCThreadState::selfOrNull();
+
+ // getServingStackPointer can also return nullptr
+ const void* hwbinderSp = hwState ? hwState->getServingStackPointer() : nullptr;
+ const void* binderSp = state ? state->getServingStackPointer() : nullptr;
if (hwbinderSp == nullptr && binderSp == nullptr) return BinderCallType::NONE;
if (hwbinderSp == nullptr) return BinderCallType::BINDER;
diff --git a/libs/binderthreadstate/test.cpp b/libs/binderthreadstate/test.cpp
index df1f35d9a7..e888b0aea8 100644
--- a/libs/binderthreadstate/test.cpp
+++ b/libs/binderthreadstate/test.cpp
@@ -16,11 +16,17 @@
#include <BnAidlStuff.h>
#include <android-base/logging.h>
+#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binderthreadstate/CallerUtils.h>
#include <binderthreadstateutilstest/1.0/IHidlStuff.h>
#include <gtest/gtest.h>
#include <hidl/HidlTransportSupport.h>
+#include <hidl/ServiceManagement.h>
+#include <hwbinder/IPCThreadState.h>
+
+#include <thread>
+
#include <linux/prctl.h>
#include <sys/prctl.h>
@@ -32,6 +38,7 @@ using android::OK;
using android::sp;
using android::String16;
using android::binder::Status;
+using android::hardware::isHidlSupported;
using android::hardware::Return;
using binderthreadstateutilstest::V1_0::IHidlStuff;
@@ -62,6 +69,7 @@ std::string id2name(size_t id) {
// complicated calls are possible, but this should do here.
static void callHidl(size_t id, int32_t idx) {
+ CHECK_EQ(true, isHidlSupported()) << "We shouldn't be calling HIDL if it's not supported";
auto stuff = IHidlStuff::getService(id2name(id));
CHECK(stuff->call(idx).isOk());
}
@@ -154,7 +162,22 @@ TEST(BinderThreadState, LocalAidlCall) {
EXPECT_TRUE(server->callLocal().isOk());
}
+TEST(BinderThreadState, DoesntInitializeBinderDriver) {
+ // this is on another thread, because it's testing thread-specific
+ // state and we expect it not to be initialized.
+ std::thread([&] {
+ EXPECT_EQ(nullptr, android::IPCThreadState::selfOrNull());
+ EXPECT_EQ(nullptr, android::hardware::IPCThreadState::selfOrNull());
+
+ (void)getCurrentServingCall();
+
+ EXPECT_EQ(nullptr, android::IPCThreadState::selfOrNull());
+ EXPECT_EQ(nullptr, android::hardware::IPCThreadState::selfOrNull());
+ }).join();
+}
+
TEST(BindThreadState, RemoteHidlCall) {
+ if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device";
auto stuff = IHidlStuff::getService(id2name(kP1Id));
ASSERT_NE(nullptr, stuff);
ASSERT_TRUE(stuff->call(0).isOk());
@@ -167,11 +190,14 @@ TEST(BindThreadState, RemoteAidlCall) {
}
TEST(BindThreadState, RemoteNestedStartHidlCall) {
+ if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device";
auto stuff = IHidlStuff::getService(id2name(kP1Id));
ASSERT_NE(nullptr, stuff);
ASSERT_TRUE(stuff->call(100).isOk());
}
TEST(BindThreadState, RemoteNestedStartAidlCall) {
+ // this test case is trying ot nest a HIDL call which requires HIDL support
+ if (!isHidlSupported()) GTEST_SKIP() << "No HIDL support on device";
sp<IAidlStuff> stuff;
ASSERT_EQ(OK, android::getService<IAidlStuff>(String16(id2name(kP1Id).c_str()), &stuff));
ASSERT_NE(nullptr, stuff);
@@ -186,11 +212,15 @@ int server(size_t thisId, size_t otherId) {
defaultServiceManager()->addService(String16(id2name(thisId).c_str()), aidlServer));
android::ProcessState::self()->startThreadPool();
- // HIDL
- android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/);
- sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId);
- CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str()));
- android::hardware::joinRpcThreadpool();
+ if (isHidlSupported()) {
+ // HIDL
+ android::hardware::configureRpcThreadpool(1, true /*callerWillJoin*/);
+ sp<IHidlStuff> hidlServer = new HidlServer(thisId, otherId);
+ CHECK_EQ(OK, hidlServer->registerAsService(id2name(thisId).c_str()));
+ android::hardware::joinRpcThreadpool();
+ } else {
+ android::IPCThreadState::self()->joinThreadPool(true);
+ }
return EXIT_FAILURE;
}
@@ -208,9 +238,15 @@ int main(int argc, char** argv) {
}
android::waitForService<IAidlStuff>(String16(id2name(kP1Id).c_str()));
- android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP1Id).c_str());
+ if (isHidlSupported()) {
+ android::hardware::details::waitForHwService(IHidlStuff::descriptor,
+ id2name(kP1Id).c_str());
+ }
android::waitForService<IAidlStuff>(String16(id2name(kP2Id).c_str()));
- android::hardware::details::waitForHwService(IHidlStuff::descriptor, id2name(kP2Id).c_str());
+ if (isHidlSupported()) {
+ android::hardware::details::waitForHwService(IHidlStuff::descriptor,
+ id2name(kP2Id).c_str());
+ }
return RUN_ALL_TESTS();
}
diff --git a/libs/bufferqueueconverter/Android.bp b/libs/bufferqueueconverter/Android.bp
index c5d3a3207c..2c25f746a8 100644
--- a/libs/bufferqueueconverter/Android.bp
+++ b/libs/bufferqueueconverter/Android.bp
@@ -13,15 +13,13 @@ cc_library_headers {
export_include_dirs: ["include"],
}
-cc_library_shared {
+cc_library {
name: "libbufferqueueconverter",
vendor_available: true,
- vndk: {
- enabled: true,
- },
double_loadable: true,
srcs: [
+ ":libgui_frame_event_aidl",
"BufferQueueConverter.cpp",
],
@@ -33,5 +31,7 @@ cc_library_shared {
"libbase",
"liblog",
],
+ static_libs: ["libguiflags"],
export_include_dirs: ["include"],
+ export_static_lib_headers: ["libguiflags"],
}
diff --git a/libs/bufferstreams/Android.bp b/libs/bufferstreams/Android.bp
new file mode 100644
index 0000000000..6c2a980f71
--- /dev/null
+++ b/libs/bufferstreams/Android.bp
@@ -0,0 +1,37 @@
+// 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 {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+aconfig_declarations {
+ name: "bufferstreams_flags",
+ package: "com.android.graphics.bufferstreams.flags",
+ container: "system",
+ srcs: [
+ "aconfig/bufferstreams_flags.aconfig",
+ ],
+}
+
+rust_aconfig_library {
+ name: "libbufferstreams_flags_rust",
+ crate_name: "bufferstreams_flags",
+ aconfig_declarations: "bufferstreams_flags",
+}
+
+cc_aconfig_library {
+ name: "libbufferstreams_flags_cc",
+ aconfig_declarations: "bufferstreams_flags",
+}
diff --git a/libs/bufferstreams/OWNERS b/libs/bufferstreams/OWNERS
new file mode 100644
index 0000000000..32b72b8592
--- /dev/null
+++ b/libs/bufferstreams/OWNERS
@@ -0,0 +1,7 @@
+carlosmr@google.com
+hibrian@google.com
+jreck@google.com
+jshargo@google.com
+
+file:/services/surfaceflinger/OWNERS
+
diff --git a/libs/bufferstreams/README.md b/libs/bufferstreams/README.md
new file mode 100644
index 0000000000..860adef281
--- /dev/null
+++ b/libs/bufferstreams/README.md
@@ -0,0 +1,13 @@
+# libbufferstreams: Reactive Streams for Graphics Buffers
+
+This library is currently **experimental** and **under active development**.
+It is not production ready yet.
+
+For more information on reactive streams, please see <https://www.reactive-streams.org/>
+
+## Contributing
+
+This library is natively written in Rust and exposes a C API. If you make changes to the Rust API,
+you **must** update the C API in turn. To do so, with cbindgen installed, run:
+
+```$ ./update_include.sh```
diff --git a/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig
new file mode 100644
index 0000000000..d0f7812d21
--- /dev/null
+++ b/libs/bufferstreams/aconfig/bufferstreams_flags.aconfig
@@ -0,0 +1,66 @@
+package: "com.android.graphics.bufferstreams.flags"
+container: "system"
+
+flag {
+ name: "bufferstreams_steel_thread"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams steel thread milestone"
+ bug: "296101122"
+}
+
+flag {
+ name: "bufferstreams_local"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams single-process functionality milestone"
+ bug: "296100790"
+}
+
+flag {
+ name: "bufferstreams_pooling"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams buffer pooling milestone"
+ bug: "296101127"
+}
+
+flag {
+ name: "bufferstreams_ipc"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams IPC milestone"
+ bug: "296099728"
+}
+
+flag {
+ name: "bufferstreams_cpp"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams C/C++ milestone"
+ bug: "296100536"
+}
+
+flag {
+ name: "bufferstreams_utils"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams extra utilities milestone"
+ bug: "285322189"
+}
+
+flag {
+ name: "bufferstreams_demo"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams demo milestone"
+ bug: "297242965"
+}
+
+flag {
+ name: "bufferstreams_perf"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams performance enhancement milestone"
+ bug: "297242843"
+}
+
+flag {
+ name: "bufferstreams_tooling"
+ namespace: "core_graphics"
+ description: "Flag for bufferstreams tooling milestone"
+ bug: "297243180"
+}
+
diff --git a/libs/bufferstreams/examples/app/Android.bp b/libs/bufferstreams/examples/app/Android.bp
new file mode 100644
index 0000000000..bb573c596c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/Android.bp
@@ -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.
+
+android_app {
+ name: "BufferStreamsDemoApp",
+ srcs: ["java/**/*.kt"],
+ sdk_version: "current",
+
+ jni_uses_platform_apis: true,
+ jni_libs: ["libbufferstreamdemoapp"],
+ use_embedded_native_libs: true,
+ kotlincflags: [
+ "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api",
+ ],
+ optimize: {
+ proguard_flags_files: ["proguard-rules.pro"],
+ },
+
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.activity_activity-compose",
+ "androidx.appcompat_appcompat",
+ "androidx.compose.foundation_foundation",
+ "androidx.compose.material3_material3",
+ "androidx.compose.runtime_runtime",
+ "androidx.compose.ui_ui",
+ "androidx.compose.ui_ui-graphics",
+ "androidx.compose.ui_ui-tooling-preview",
+ "androidx.core_core-ktx",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
+ "androidx.navigation_navigation-common-ktx",
+ "androidx.navigation_navigation-compose",
+ "androidx.navigation_navigation-fragment-ktx",
+ "androidx.navigation_navigation-runtime-ktx",
+ "androidx.navigation_navigation-ui-ktx",
+ ],
+}
diff --git a/libs/bufferstreams/examples/app/AndroidManifest.xml b/libs/bufferstreams/examples/app/AndroidManifest.xml
new file mode 100644
index 0000000000..a5e2fa8a63
--- /dev/null
+++ b/libs/bufferstreams/examples/app/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.graphics.bufferstreamsdemoapp"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ <application
+ android:allowBackup="true"
+ android:icon="@mipmap/ic_launcher"
+ android:label="@string/app_name"
+ android:roundIcon="@mipmap/ic_launcher_round"
+ android:supportsRtl="true"
+ android:theme="@style/Theme.Jetpack"
+ tools:targetApi="34">
+ <activity
+ android:name=".MainActivity"
+ android:exported="true"
+ android:label="@string/app_name"
+ android:theme="@style/Theme.Jetpack">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
new file mode 100644
index 0000000000..ff3ae5a090
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferDemosAppBar.kt
@@ -0,0 +1,40 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.ArrowBack
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+
+@Composable
+fun BufferDemosAppBar(
+ currentScreen: BufferDemoScreen,
+ canNavigateBack: Boolean,
+ navigateUp: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ TopAppBar(
+ title = { Text(stringResource(currentScreen.title)) },
+ colors =
+ TopAppBarDefaults.mediumTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primaryContainer
+ ),
+ modifier = modifier,
+ navigationIcon = {
+ if (canNavigateBack) {
+ IconButton(onClick = navigateUp) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Filled.ArrowBack,
+ contentDescription = stringResource(R.string.back_button)
+ )
+ }
+ }
+ }
+ )
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
new file mode 100644
index 0000000000..ede77938de
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/BufferStreamJNI.kt
@@ -0,0 +1,27 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+class BufferStreamJNI {
+ // Used to load the 'bufferstreamsdemoapp' library on application startup.
+ init {
+ System.loadLibrary("bufferstreamdemoapp")
+ }
+
+ /**
+ * A native method that is implemented by the 'bufferstreamsdemoapp' native library, which is
+ * packaged with this application.
+ */
+ external fun stringFromJNI(): String;
+ external fun testBufferQueueCreation();
+
+ companion object {
+ fun companion_stringFromJNI(): String {
+ val instance = BufferStreamJNI()
+ return instance.stringFromJNI()
+ }
+
+ fun companion_testBufferQueueCreation() {
+ val instance = BufferStreamJNI()
+ return instance.testBufferQueueCreation()
+ }
+ }
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
new file mode 100644
index 0000000000..95e415ecd5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen1.kt
@@ -0,0 +1,35 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+
+@Composable
+fun DemoScreen1(modifier: Modifier = Modifier) {
+ Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
+ LogOutput.getInstance().LogOutputComposable()
+ Row(modifier = Modifier.weight(1f, false).padding(16.dp)) {
+ Column(verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ Button(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = { BufferStreamJNI.companion_testBufferQueueCreation() }) {
+ Text("Run")
+ }
+
+ OutlinedButton(
+ modifier = Modifier.fillMaxWidth(),
+ onClick = { LogOutput.getInstance().clearText() }) {
+ Text("Clear")
+ }
+ }
+ }
+ }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt
new file mode 100644
index 0000000000..5efee92f76
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen2.kt
@@ -0,0 +1,7 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun DemoScreen2() {
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt
new file mode 100644
index 0000000000..8cba857737
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/DemoScreen3.kt
@@ -0,0 +1,7 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.runtime.Composable
+
+@Composable
+fun DemoScreen3() {
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
new file mode 100644
index 0000000000..3f0926f497
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/LogOutput.kt
@@ -0,0 +1,65 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Card
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import java.util.Collections
+
+/*
+LogOutput centralizes logging: storing, displaying, adding, and clearing log messages with
+thread safety. It is a singleton that's also accessed from C++. The private constructor will
+not allow this class to be initialized, limiting it to getInstance().
+ */
+class LogOutput private constructor() {
+ val logs = Collections.synchronizedList(mutableStateListOf<String>())
+
+ @Composable
+ fun LogOutputComposable() {
+ val rlogs = remember { logs }
+
+ Card(modifier = Modifier.fillMaxWidth().padding(16.dp).height(400.dp)) {
+ Column(
+ modifier =
+ Modifier.padding(10.dp).size(380.dp).verticalScroll(rememberScrollState())) {
+ for (log in rlogs) {
+ Text(log, modifier = Modifier.padding(0.dp))
+ }
+ }
+ }
+ }
+
+ fun clearText() {
+ logs.clear()
+ }
+
+ fun addLog(log: String) {
+ logs.add(log)
+ }
+
+ companion object {
+ @Volatile private var instance: LogOutput? = null
+
+ @JvmStatic
+ fun getInstance(): LogOutput {
+ if (instance == null) {
+ synchronized(this) {
+ if (instance == null) {
+ instance = LogOutput()
+ }
+ }
+ }
+ return instance!!
+ }
+ }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
new file mode 100644
index 0000000000..2ccd8d75ef
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/MainActivity.kt
@@ -0,0 +1,132 @@
+package com.android.graphics.bufferstreamsdemoapp
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.annotation.StringRes
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import com.android.graphics.bufferstreamsdemoapp.ui.theme.JetpackTheme
+import java.util.*
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ JetpackTheme {
+ Surface(
+ modifier = Modifier.fillMaxSize(),
+ color = MaterialTheme.colorScheme.background) {
+ BufferDemosApp()
+ }
+ }
+ }
+ }
+}
+
+enum class BufferDemoScreen(val route: String, @StringRes val title: Int) {
+ Start(route = "start", title = R.string.start),
+ Demo1(route = "demo1", title = R.string.demo1),
+ Demo2(route = "demo2", title = R.string.demo2),
+ Demo3(route = "demo3", title = R.string.demo3);
+
+ companion object {
+ fun findByRoute(route: String): BufferDemoScreen {
+ return values().find { it.route == route }!!
+ }
+ }
+}
+
+@Composable
+fun BufferDemosApp() {
+ var navController: NavHostController = rememberNavController()
+ // Get current back stack entry
+ val backStackEntry by navController.currentBackStackEntryAsState()
+ // Get the name of the current screen
+ val currentScreen =
+ BufferDemoScreen.findByRoute(
+ backStackEntry?.destination?.route ?: BufferDemoScreen.Start.route)
+
+ Scaffold(
+ topBar = {
+ BufferDemosAppBar(
+ currentScreen = currentScreen,
+ canNavigateBack = navController.previousBackStackEntry != null,
+ navigateUp = { navController.navigateUp() })
+ }) {
+ NavHost(
+ navController = navController,
+ startDestination = BufferDemoScreen.Start.route,
+ modifier = Modifier.padding(10.dp)) {
+ composable(route = BufferDemoScreen.Start.route) {
+ DemoList(
+ onButtonClicked = { navController.navigate(it) },
+ )
+ }
+ composable(route = BufferDemoScreen.Demo1.route) {
+ DemoScreen1(modifier = Modifier.fillMaxHeight().padding(top = 100.dp))
+ }
+ composable(route = BufferDemoScreen.Demo2.route) { DemoScreen2() }
+ composable(route = BufferDemoScreen.Demo3.route) { DemoScreen3() }
+ }
+ }
+}
+
+@Composable
+fun DemoList(onButtonClicked: (String) -> Unit) {
+ var modifier = Modifier.fillMaxSize().padding(16.dp)
+
+ Column(modifier = modifier, verticalArrangement = Arrangement.SpaceBetween) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(8.dp)) {
+ Spacer(modifier = Modifier.height(100.dp))
+ Text(text = "Buffer Demos", style = MaterialTheme.typography.titleLarge)
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+ Row(modifier = Modifier.weight(2f, false)) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.spacedBy(16.dp)) {
+ for (item in BufferDemoScreen.values()) {
+ if (item.route != BufferDemoScreen.Start.route)
+ SelectDemoButton(
+ name = stringResource(item.title),
+ onClick = { onButtonClicked(item.route) })
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun SelectDemoButton(name: String, onClick: () -> Unit, modifier: Modifier = Modifier) {
+ Button(onClick = onClick, modifier = modifier.widthIn(min = 250.dp)) { Text(name) }
+}
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt
new file mode 100644
index 0000000000..d85ea724de
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Color.kt
@@ -0,0 +1,11 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import androidx.compose.ui.graphics.Color
+
+val Purple80 = Color(0xFFD0BCFF)
+val PurpleGrey80 = Color(0xFFCCC2DC)
+val Pink80 = Color(0xFFEFB8C8)
+
+val Purple40 = Color(0xFF6650a4)
+val PurpleGrey40 = Color(0xFF625b71)
+val Pink40 = Color(0xFF7D5260) \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt
new file mode 100644
index 0000000000..fccd93a10b
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Theme.kt
@@ -0,0 +1,60 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import android.app.Activity
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.darkColorScheme
+import androidx.compose.material3.dynamicDarkColorScheme
+import androidx.compose.material3.dynamicLightColorScheme
+import androidx.compose.material3.lightColorScheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalView
+import androidx.core.view.WindowCompat
+
+private val DarkColorScheme = darkColorScheme(
+ primary = Purple80,
+ secondary = PurpleGrey80,
+ tertiary = Pink80
+)
+
+private val LightColorScheme = lightColorScheme(
+ primary = Purple40,
+ secondary = PurpleGrey40,
+ tertiary = Pink40
+)
+
+@Composable
+fun JetpackTheme(
+ darkTheme: Boolean = isSystemInDarkTheme(),
+ // Dynamic color is available on Android 12+
+ dynamicColor: Boolean = true,
+ content: @Composable () -> Unit
+) {
+ val colorScheme = when {
+ dynamicColor -> {
+ val context = LocalContext.current
+ if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
+ }
+
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+ val view = LocalView.current
+ if (!view.isInEditMode) {
+ SideEffect {
+ val window = (view.context as Activity).window
+ window.statusBarColor = colorScheme.primary.toArgb()
+ WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
+ }
+ }
+
+ MaterialTheme(
+ colorScheme = colorScheme,
+ typography = Typography,
+ content = content
+ )
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt
new file mode 100644
index 0000000000..06814ead8b
--- /dev/null
+++ b/libs/bufferstreams/examples/app/java/com/android/graphics/bufferstreamsdemoapp/ui/Type.kt
@@ -0,0 +1,18 @@
+package com.android.graphics.bufferstreamsdemoapp.ui.theme
+
+import androidx.compose.material3.Typography
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+
+// Set of Material typography styles to start with
+val Typography = Typography(
+ bodyLarge = TextStyle(
+ fontFamily = FontFamily.Default,
+ fontWeight = FontWeight.Normal,
+ fontSize = 16.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.5.sp
+ )
+) \ No newline at end of file
diff --git a/cmds/ip-up-vpn/Android.bp b/libs/bufferstreams/examples/app/jni/Android.bp
index c746f7fde3..67910a1c4d 100644
--- a/cmds/ip-up-vpn/Android.bp
+++ b/libs/bufferstreams/examples/app/jni/Android.bp
@@ -1,4 +1,4 @@
-// Copyright 2011 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.
@@ -12,20 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-cc_binary {
- name: "ip-up-vpn",
-
- srcs: ["ip-up-vpn.c"],
+cc_library_shared {
+ name: "libbufferstreamdemoapp",
cflags: [
- "-Wall",
"-Werror",
+ "-Wno-error=unused-parameter",
],
shared_libs: [
- "libcutils",
- "liblog",
+ "libgui",
+ "libbase",
+ "libutils",
],
+ header_libs: ["jni_headers"],
+ srcs: ["*.cpp"],
}
diff --git a/libs/bufferstreams/examples/app/jni/main.cpp b/libs/bufferstreams/examples/app/jni/main.cpp
new file mode 100644
index 0000000000..550ad22ae8
--- /dev/null
+++ b/libs/bufferstreams/examples/app/jni/main.cpp
@@ -0,0 +1,53 @@
+// 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 <jni.h>
+#include <string>
+
+#include <gui/BufferQueue.h>
+
+void log(JNIEnv* env, std::string l) {
+ jclass clazz = env->FindClass("com/android/graphics/bufferstreamsdemoapp/LogOutput");
+ jmethodID getInstance = env->GetStaticMethodID(clazz, "getInstance",
+ "()Lcom/android/graphics/bufferstreamsdemoapp/LogOutput;");
+ jmethodID addLog = env->GetMethodID(clazz, "addLog", "(Ljava/lang/String;)V");
+ jobject dmg = env->CallStaticObjectMethod(clazz, getInstance);
+
+ jstring jlog = env->NewStringUTF(l.c_str());
+ env->CallVoidMethod(dmg, addLog, jlog);
+}
+
+extern "C" {
+
+JNIEXPORT jstring JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_stringFromJNI(JNIEnv* env,
+ jobject /* this */) {
+ const char* hello = "Hello from C++";
+ return env->NewStringUTF(hello);
+}
+
+JNIEXPORT void JNICALL
+Java_com_android_graphics_bufferstreamsdemoapp_BufferStreamJNI_testBufferQueueCreation(
+ JNIEnv* env, jobject /* thiz */) {
+
+ log(env, "Calling testBufferQueueCreation.");
+ android::sp<android::IGraphicBufferProducer> producer;
+ log(env, "Created producer.");
+ android::sp<android::IGraphicBufferConsumer> consumer;
+ log(env, "Created consumer.");
+ android::BufferQueue::createBufferQueue(&producer, &consumer);
+ log(env, "Created BufferQueue successfully.");
+ log(env, "Done!");
+}
+} \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/proguard-rules.pro b/libs/bufferstreams/examples/app/proguard-rules.pro
new file mode 100644
index 0000000000..7a987fc7c4
--- /dev/null
+++ b/libs/bufferstreams/examples/app/proguard-rules.pro
@@ -0,0 +1,23 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
+
+-keep,allowoptimization,allowobfuscation class com.android.graphics.bufferstreamsdemoapp.** { *; }
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000000..07d5da9cbf
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path
+ android:fillColor="#3DDC84"
+ android:pathData="M0,0h108v108h-108z" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M9,0L9,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,0L19,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,0L29,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,0L39,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,0L49,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,0L59,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,0L69,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,0L79,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M89,0L89,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M99,0L99,108"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,9L108,9"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,19L108,19"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,29L108,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,39L108,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,49L108,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,59L108,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,69L108,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,79L108,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,89L108,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M0,99L108,99"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,29L89,29"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,39L89,39"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,49L89,49"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,59L89,59"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,69L89,69"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M19,79L89,79"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M29,19L29,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M39,19L39,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M49,19L49,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M59,19L59,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M69,19L69,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+ <path
+ android:fillColor="#00000000"
+ android:pathData="M79,19L79,89"
+ android:strokeWidth="0.8"
+ android:strokeColor="#33FFFFFF" />
+</vector>
diff --git a/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
new file mode 100644
index 0000000000..2b068d1146
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/drawable/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt"
+ android:width="108dp"
+ android:height="108dp"
+ android:viewportWidth="108"
+ android:viewportHeight="108">
+ <path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
+ <aapt:attr name="android:fillColor">
+ <gradient
+ android:endX="85.84757"
+ android:endY="92.4963"
+ android:startX="42.9492"
+ android:startY="49.59793"
+ android:type="linear">
+ <item
+ android:color="#44000000"
+ android:offset="0.0" />
+ <item
+ android:color="#00000000"
+ android:offset="1.0" />
+ </gradient>
+ </aapt:attr>
+ </path>
+ <path
+ android:fillColor="#FFFFFF"
+ android:fillType="nonZero"
+ android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
+ android:strokeWidth="1"
+ android:strokeColor="#00000000" />
+</vector> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
new file mode 100644
index 0000000000..6f3b755bf5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+ <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
new file mode 100644
index 0000000000..6f3b755bf5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-anydpi/ic_launcher_round.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+ <background android:drawable="@drawable/ic_launcher_background" />
+ <foreground android:drawable="@drawable/ic_launcher_foreground" />
+ <monochrome android:drawable="@drawable/ic_launcher_foreground" />
+</adaptive-icon> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
new file mode 100644
index 0000000000..c209e78ecd
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..b2dfe3d1ba
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-hdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
new file mode 100644
index 0000000000..4f0f1d64e5
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..62b611da08
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-mdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..948a3070fe
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..1b9a6956b3
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..28d4b77f9f
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9287f50836
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
new file mode 100644
index 0000000000..aa7d6427e6
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
new file mode 100644
index 0000000000..9126ae37cb
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/mipmap-xxxhdpi/ic_launcher_round.webp
Binary files differ
diff --git a/libs/bufferstreams/examples/app/res/values/colors.xml b/libs/bufferstreams/examples/app/res/values/colors.xml
new file mode 100644
index 0000000000..f8c6127d32
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/colors.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <color name="purple_200">#FFBB86FC</color>
+ <color name="purple_500">#FF6200EE</color>
+ <color name="purple_700">#FF3700B3</color>
+ <color name="teal_200">#FF03DAC5</color>
+ <color name="teal_700">#FF018786</color>
+ <color name="black">#FF000000</color>
+ <color name="white">#FFFFFFFF</color>
+</resources> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/strings.xml b/libs/bufferstreams/examples/app/res/values/strings.xml
new file mode 100644
index 0000000000..75c8ab5e1c
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/strings.xml
@@ -0,0 +1,8 @@
+<resources>
+ <string name="app_name">Buffer Demos</string>
+ <string name="start">Start</string>
+ <string name="demo1">Demo 1</string>
+ <string name="demo2">Demo 2</string>
+ <string name="demo3">Demo 3</string>
+ <string name="back_button">Back</string>
+</resources> \ No newline at end of file
diff --git a/libs/bufferstreams/examples/app/res/values/themes.xml b/libs/bufferstreams/examples/app/res/values/themes.xml
new file mode 100644
index 0000000000..eeb308ae44
--- /dev/null
+++ b/libs/bufferstreams/examples/app/res/values/themes.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="Theme.Jetpack" parent="android:Theme.Material.Light.NoActionBar" />
+</resources> \ No newline at end of file
diff --git a/libs/bufferstreams/include/bufferstreams.h b/libs/bufferstreams/include/bufferstreams.h
new file mode 100644
index 0000000000..5308de24c0
--- /dev/null
+++ b/libs/bufferstreams/include/bufferstreams.h
@@ -0,0 +1,13 @@
+/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */
+
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+
+/**
+ * This function will print Hello World.
+ */
+bool hello(void);
diff --git a/libs/bufferstreams/rust/Android.bp b/libs/bufferstreams/rust/Android.bp
new file mode 100644
index 0000000000..7fcb222085
--- /dev/null
+++ b/libs/bufferstreams/rust/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+
+rust_defaults {
+ name: "libbufferstreams_defaults",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libnativewindow_rs",
+ ],
+ edition: "2021",
+}
+
+rust_library {
+ name: "libbufferstreams",
+ crate_name: "bufferstreams",
+ defaults: ["libbufferstreams_defaults"],
+ min_sdk_version: "30",
+}
+
+rust_test {
+ name: "libbufferstreams-internal_test",
+ crate_name: "bufferstreams",
+ defaults: ["libbufferstreams_defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/libs/bufferstreams/rust/Cargo.lock b/libs/bufferstreams/rust/Cargo.lock
new file mode 100644
index 0000000000..4482dba6cd
--- /dev/null
+++ b/libs/bufferstreams/rust/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "bufferstreams"
+version = "0.1.0"
diff --git a/libs/bufferstreams/rust/Cargo.toml b/libs/bufferstreams/rust/Cargo.toml
new file mode 100644
index 0000000000..d30c55c551
--- /dev/null
+++ b/libs/bufferstreams/rust/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "bufferstreams"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
diff --git a/libs/bufferstreams/rust/cbindgen.toml b/libs/bufferstreams/rust/cbindgen.toml
new file mode 100644
index 0000000000..eda837f360
--- /dev/null
+++ b/libs/bufferstreams/rust/cbindgen.toml
@@ -0,0 +1,149 @@
+# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml
+# for detailed documentation of every option here.
+
+
+
+language = "C"
+
+
+
+############## Options for Wrapping the Contents of the Header #################
+
+# header = "/* Text to put at the beginning of the generated file. Probably a license. */"
+# trailer = "/* Text to put at the end of the generated file */"
+# include_guard = "my_bindings_h"
+# pragma_once = true
+autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */"
+include_version = false
+# namespace = "my_namespace"
+namespaces = []
+using_namespaces = []
+sys_includes = []
+includes = []
+no_includes = false
+after_includes = ""
+
+
+
+
+############################ Code Style Options ################################
+
+braces = "SameLine"
+line_length = 100
+tab_width = 2
+documentation = true
+documentation_style = "auto"
+documentation_length = "full"
+line_endings = "LF" # also "CR", "CRLF", "Native"
+
+
+
+
+############################# Codegen Options ##################################
+
+style = "both"
+sort_by = "Name" # default for `fn.sort_by` and `const.sort_by`
+usize_is_size_t = true
+
+
+
+[defines]
+# "target_os = freebsd" = "DEFINE_FREEBSD"
+# "feature = serde" = "DEFINE_SERDE"
+
+
+
+[export]
+include = []
+exclude = []
+# prefix = "CAPI_"
+item_types = []
+renaming_overrides_prefixing = false
+
+
+
+[export.rename]
+
+
+
+[export.body]
+
+
+[export.mangle]
+
+
+[fn]
+rename_args = "None"
+# must_use = "MUST_USE_FUNC"
+# no_return = "NO_RETURN"
+# prefix = "START_FUNC"
+# postfix = "END_FUNC"
+args = "auto"
+sort_by = "Name"
+
+
+
+
+[struct]
+rename_fields = "None"
+# must_use = "MUST_USE_STRUCT"
+derive_constructor = false
+derive_eq = false
+derive_neq = false
+derive_lt = false
+derive_lte = false
+derive_gt = false
+derive_gte = false
+
+
+
+
+[enum]
+rename_variants = "None"
+# must_use = "MUST_USE_ENUM"
+add_sentinel = false
+prefix_with_name = false
+derive_helper_methods = false
+derive_const_casts = false
+derive_mut_casts = false
+# cast_assert_name = "ASSERT"
+derive_tagged_enum_destructor = false
+derive_tagged_enum_copy_constructor = false
+enum_class = true
+private_default_tagged_enum_constructor = false
+
+
+
+
+[const]
+allow_static_const = true
+allow_constexpr = false
+sort_by = "Name"
+
+
+
+
+[macro_expansion]
+bitflags = false
+
+
+
+
+
+
+############## Options for How Your Rust library Should Be Parsed ##############
+
+[parse]
+parse_deps = false
+# include = []
+exclude = []
+clean = false
+extra_bindings = []
+
+
+
+[parse.expand]
+crates = []
+all_features = false
+default_features = true
+features = [] \ No newline at end of file
diff --git a/libs/bufferstreams/rust/src/buffers/buffer.rs b/libs/bufferstreams/rust/src/buffers/buffer.rs
new file mode 100644
index 0000000000..0a8516e8e3
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer.rs
@@ -0,0 +1,80 @@
+// 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.
+
+//! Wrapper around the HardwareBuffer
+
+use nativewindow::*;
+
+use super::{buffer_owner::NoBufferOwner, BufferOwner};
+
+/// A wrapper for a hardware buffer.
+///
+/// This buffer may be associated with a buffer pool to which it will be returned to it when dropped.
+pub struct Buffer {
+ buffer_owner: Box<dyn BufferOwner>,
+ hardware_buffer: HardwareBuffer,
+}
+
+impl Buffer {
+ /// Create new buffer with a custom [BufferOwner].
+ pub fn new(buffer_owner: Box<dyn BufferOwner>, hardware_buffer: HardwareBuffer) -> Self {
+ Self { buffer_owner, hardware_buffer }
+ }
+
+ /// Create a new buffer with no association to any buffer pool.
+ pub fn new_unowned(hardware_buffer: HardwareBuffer) -> Self {
+ Self { buffer_owner: Box::new(NoBufferOwner), hardware_buffer }
+ }
+
+ /// Get the id of the underlying buffer.
+ pub fn id(&self) -> u64 {
+ self.hardware_buffer.id()
+ }
+
+ /// Get a reference to the underlying hardware buffer.
+ pub fn buffer(&self) -> &HardwareBuffer {
+ &self.hardware_buffer
+ }
+}
+
+impl Drop for Buffer {
+ fn drop(&mut self) {
+ self.buffer_owner.on_return(self);
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ use crate::StreamConfig;
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ #[test]
+ fn test_get_buffer_id() {
+ let hardware_buffer = STREAM_CONFIG.create_hardware_buffer().unwrap();
+ let buffer_id = hardware_buffer.id();
+
+ let buffer = Buffer::new_unowned(hardware_buffer);
+ assert_eq!(buffer_id, buffer.id());
+ }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_owner.rs b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
new file mode 100644
index 0000000000..a4abb9d3b7
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_owner.rs
@@ -0,0 +1,28 @@
+// 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.
+
+use super::Buffer;
+
+/// Trait that represents an owner of a buffer that might need to handle events such as a buffer
+/// being dropped.
+pub trait BufferOwner {
+ /// Called when a buffer is dropped.
+ fn on_return(&self, buffer: &Buffer);
+}
+
+pub(super) struct NoBufferOwner;
+
+impl BufferOwner for NoBufferOwner {
+ fn on_return(&self, _buffer: &Buffer) {}
+}
diff --git a/libs/bufferstreams/rust/src/buffers/buffer_pool.rs b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
new file mode 100644
index 0000000000..05804e2e3a
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/buffer_pool.rs
@@ -0,0 +1,137 @@
+// 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.
+
+//! A Buffer Pool containing and managing HardwareBuffers
+
+use std::{
+ collections::HashMap,
+ sync::{Arc, Mutex, Weak},
+};
+
+use nativewindow::*;
+
+use crate::StreamConfig;
+
+use super::{Buffer, BufferOwner};
+
+pub(super) struct BufferPoolInner {
+ size: usize,
+ hardware_buffers: HashMap<u64, HardwareBuffer>,
+ available_buffers: Vec<u64>,
+}
+
+impl BufferPoolInner {
+ pub(super) fn return_buffer(&mut self, buffer_id: u64) {
+ assert!(self.hardware_buffers.contains_key(&buffer_id));
+ assert!(!self.available_buffers.contains(&buffer_id));
+
+ self.available_buffers.push(buffer_id);
+ }
+}
+
+struct BufferPoolOwner(Weak<Mutex<BufferPoolInner>>);
+
+impl BufferOwner for BufferPoolOwner {
+ fn on_return(&self, buffer: &Buffer) {
+ if let Some(locked_buffer_pool) = self.0.upgrade() {
+ let mut buffer_pool = locked_buffer_pool.lock().unwrap();
+
+ buffer_pool.return_buffer(buffer.id());
+ }
+ }
+}
+
+/// A thread-safe collection of buffers.
+///
+/// A buffer pool can be of arbitrary size. It creates and then holds references to all buffers
+/// associated with it.
+pub struct BufferPool(Arc<Mutex<BufferPoolInner>>);
+
+impl BufferPool {
+ /// Creates a new buffer pool of size pool_size. All buffers will be created according to
+ /// the stream config.
+ ///
+ /// This constructor creates all buffers at initialization.
+ pub fn new(pool_size: usize, stream_config: StreamConfig) -> Option<Self> {
+ let mut hardware_buffers = HashMap::new();
+ let mut available_buffers = Vec::new();
+ for _ in 0..pool_size {
+ if let Some(buffer) = stream_config.create_hardware_buffer() {
+ available_buffers.push(buffer.id());
+ hardware_buffers.insert(buffer.id(), buffer);
+ } else {
+ return None;
+ }
+ }
+ Some(Self(Arc::new(Mutex::new(BufferPoolInner {
+ size: pool_size,
+ hardware_buffers,
+ available_buffers,
+ }))))
+ }
+
+ /// Try to acquire the next available buffer in the buffer pool.
+ ///
+ /// If all buffers are in use it will return None.
+ pub fn next_buffer(&mut self) -> Option<Buffer> {
+ let mut inner = self.0.lock().unwrap();
+ if let Some(buffer_id) = inner.available_buffers.pop() {
+ Some(Buffer::new(
+ Box::new(BufferPoolOwner(Arc::downgrade(&self.0))),
+ inner.hardware_buffers[&buffer_id].clone(),
+ ))
+ } else {
+ None
+ }
+ }
+
+ /// Gets the size of the buffer pool.
+ pub fn size(&self) -> usize {
+ let inner = self.0.lock().unwrap();
+ inner.size
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ #[test]
+ fn buffer_pool_next_buffer() {
+ let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+ let next_buffer = buffer_pool.next_buffer();
+
+ assert!(next_buffer.is_some());
+ assert!(buffer_pool.next_buffer().is_none());
+ }
+
+ #[test]
+ fn drop_buffer_returns_to_pool() {
+ let mut buffer_pool = BufferPool::new(1, STREAM_CONFIG).unwrap();
+ let next_buffer = buffer_pool.next_buffer();
+
+ assert!(next_buffer.is_some());
+ drop(next_buffer);
+ assert!(buffer_pool.next_buffer().is_some());
+ }
+}
diff --git a/libs/bufferstreams/rust/src/buffers/mod.rs b/libs/bufferstreams/rust/src/buffers/mod.rs
new file mode 100644
index 0000000000..83360d6c00
--- /dev/null
+++ b/libs/bufferstreams/rust/src/buffers/mod.rs
@@ -0,0 +1,23 @@
+// 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.
+
+//! Module containing Buffers and BufferPools
+
+mod buffer;
+mod buffer_owner;
+mod buffer_pool;
+
+pub use buffer::*;
+pub use buffer_owner::*;
+pub use buffer_pool::*;
diff --git a/libs/bufferstreams/rust/src/lib.rs b/libs/bufferstreams/rust/src/lib.rs
new file mode 100644
index 0000000000..be1525d41f
--- /dev/null
+++ b/libs/bufferstreams/rust/src/lib.rs
@@ -0,0 +1,257 @@
+// 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.
+
+//! libbufferstreams: Reactive Streams for Graphics Buffers
+
+pub mod buffers;
+pub mod publishers;
+mod stream_config;
+pub mod subscribers;
+pub mod subscriptions;
+
+use buffers::Buffer;
+pub use stream_config::*;
+
+use std::time::Instant;
+
+/// This function will print Hello World.
+#[no_mangle]
+pub extern "C" fn hello() -> bool {
+ println!("Hello world.");
+ true
+}
+
+/// BufferPublishers provide buffers to BufferSusbscribers. Depending on the
+/// particular object in question, these could be allocated locally or provided
+/// over IPC.
+///
+/// BufferPublishers are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
+/// MUST be less than or equal to the total number of elements requested by that
+/// Subscriber´s Subscription at all times.
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
+/// Subscription by calling on_complete or on_error.
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// MUST be signaled serially.
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
+/// on_complete.
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
+/// that Subscriber’s Subscription MUST be considered cancelled.
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
+/// REQUIRED that no further signals occur.
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// signaled.
+/// * A Publisher MAY support multiple Subscribers and decides whether each
+/// Subscription is unicast or multicast.
+pub trait BufferPublisher {
+ /// Returns the StreamConfig of buffers that publisher creates.
+ fn get_publisher_stream_config(&self) -> StreamConfig;
+ /// This function will create the subscription between the publisher and
+ /// the subscriber.
+ fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static);
+}
+
+/// BufferSubscribers can subscribe to BufferPublishers. They can request Frames
+/// via the BufferSubscription they get from the publisher, then receive Frames
+/// via on_next.
+///
+/// BufferSubcribers are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * The total number of on_next´s signalled by a Publisher to a Subscriber
+/// MUST be less than or equal to the total number of elements requested by that
+/// Subscriber´s Subscription at all times.
+/// * A Publisher MAY signal fewer on_next than requested and terminate the
+/// Subscription by calling on_complete or on_error.
+/// * on_subscribe, on_next, on_error and on_complete signaled to a Subscriber
+/// MUST be signaled serially.
+/// * If a Publisher fails it MUST signal an on_error.
+/// * If a Publisher terminates successfully (finite stream) it MUST signal an
+/// on_complete.
+/// * If a Publisher signals either on_error or on_complete on a Subscriber,
+/// that Subscriber’s Subscription MUST be considered cancelled.
+/// * Once a terminal state has been signaled (on_error, on_complete) it is
+/// REQUIRED that no further signals occur.
+/// * If a Subscription is cancelled its Subscriber MUST eventually stop being
+/// signaled.
+/// * Publisher.subscribe MAY be called as many times as wanted but MUST be
+/// with a different Subscriber each time.
+/// * A Publisher MAY support multiple Subscribers and decides whether each
+/// Subscription is unicast or multicast.
+pub trait BufferSubscriber {
+ /// The StreamConfig of buffers that this subscriber expects.
+ fn get_subscriber_stream_config(&self) -> StreamConfig;
+ /// This function will be called at the beginning of the subscription.
+ fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>);
+ /// This function will be called for buffer that comes in.
+ fn on_next(&mut self, frame: Frame);
+ /// This function will be called in case of an error.
+ fn on_error(&mut self, error: BufferError);
+ /// This function will be called on finite streams when done.
+ fn on_complete(&mut self);
+}
+
+/// BufferSubscriptions serve as the bridge between BufferPublishers and
+/// BufferSubscribers. BufferSubscribers receive a BufferSubscription when they
+/// subscribe to a BufferPublisher via on_subscribe.
+/// This object is to be used by the BufferSubscriber to cancel its subscription
+/// or request more buffers.
+///
+/// BufferSubcriptions are required to adhere to the following, based on the
+/// reactive streams specification:
+/// * Subscription.request and Subscription.cancel MUST only be called inside
+/// of its Subscriber context.
+/// * The Subscription MUST allow the Subscriber to call Subscription.request
+/// synchronously from within on_next or on_subscribe.
+/// * Subscription.request MUST place an upper bound on possible synchronous
+/// recursion between Publisher and Subscriber.
+/// * Subscription.request SHOULD respect the responsivity of its caller by
+/// returning in a timely manner.
+/// * Subscription.cancel MUST respect the responsivity of its caller by
+/// returning in a timely manner, MUST be idempotent and MUST be thread-safe.
+/// * After the Subscription is cancelled, additional
+/// Subscription.request(n: u64) MUST be NOPs.
+/// * After the Subscription is cancelled, additional Subscription.cancel()
+/// MUST be NOPs.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MUST register the given number of additional elements to be produced to the
+/// respective subscriber.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MUST signal on_error if the argument is <= 0. The cause message SHOULD
+/// explain that non-positive request signals are illegal.
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MAY synchronously call on_next on this (or other) subscriber(s).
+/// * While the Subscription is not cancelled, Subscription.request(n: u64)
+/// MAY synchronously call on_complete or on_error on this (or other)
+/// subscriber(s).
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
+/// request the Publisher to eventually stop signaling its Subscriber. The
+/// operation is NOT REQUIRED to affect the Subscription immediately.
+/// * While the Subscription is not cancelled, Subscription.cancel() MUST
+/// request the Publisher to eventually drop any references to the corresponding
+/// subscriber.
+/// * While the Subscription is not cancelled, calling Subscription.cancel MAY
+/// cause the Publisher, if stateful, to transition into the shut-down state if
+/// no other Subscription exists at this point.
+/// * Calling Subscription.cancel MUST return normally.
+/// * Calling Subscription.request MUST return normally.
+pub trait BufferSubscription {
+ /// request
+ fn request(&self, n: u64);
+ /// cancel
+ fn cancel(&self);
+}
+
+/// Type used to describe errors produced by subscriptions.
+pub type BufferError = anyhow::Error;
+
+/// Struct used to contain the buffer.
+pub struct Frame {
+ /// A buffer to be used this frame.
+ pub buffer: Buffer,
+ /// The time at which the buffer was dispatched.
+ pub present_time: Instant,
+ /// A fence used for reading/writing safely.
+ pub fence: i32,
+}
+
+#[cfg(test)]
+mod test {
+ #![allow(warnings, unused)]
+ use super::*;
+
+ use anyhow::anyhow;
+ use buffers::Buffer;
+ use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+ use std::borrow::BorrowMut;
+ use std::error::Error;
+ use std::ops::Add;
+ use std::sync::Arc;
+ use std::time::Duration;
+
+ use crate::publishers::testing::*;
+ use crate::subscribers::{testing::*, SharedSubscriber};
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ fn make_frame() -> Frame {
+ Frame {
+ buffer: Buffer::new_unowned(
+ STREAM_CONFIG
+ .create_hardware_buffer()
+ .expect("Unable to create hardware buffer for test"),
+ ),
+ present_time: Instant::now() + Duration::from_secs(1),
+ fence: 0,
+ }
+ }
+
+ #[test]
+ fn test_test_implementations_next() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+ let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+ publisher.subscribe(subscriber.clone());
+ assert!(subscriber.map_inner(|s| s.has_subscription()));
+ assert!(publisher.has_subscriber());
+
+ publisher.send_frame(make_frame());
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(!matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+
+ subscriber.map_inner(|s| s.request(1));
+ assert_eq!(publisher.pending_requests(), 1);
+
+ publisher.send_frame(make_frame());
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+ assert_eq!(publisher.pending_requests(), 0);
+ }
+
+ #[test]
+ fn test_test_implementations_complete() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+ let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+ publisher.subscribe(subscriber.clone());
+ assert!(subscriber.map_inner(|s| s.has_subscription()));
+ assert!(publisher.has_subscriber());
+
+ publisher.send_complete();
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Complete));
+ }
+
+ #[test]
+ fn test_test_implementations_error() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+ let mut publisher = TestPublisher::new(STREAM_CONFIG);
+
+ publisher.subscribe(subscriber.clone());
+ assert!(subscriber.map_inner(|s| s.has_subscription()));
+ assert!(publisher.has_subscriber());
+
+ publisher.send_error(anyhow!("error"));
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Error(_)));
+ }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
new file mode 100644
index 0000000000..846105dacd
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/buffer_pool_publisher.rs
@@ -0,0 +1,112 @@
+// 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.
+
+//!
+
+use std::time::Instant;
+
+use crate::{
+ buffers::BufferPool, subscriptions::SharedBufferSubscription, BufferPublisher,
+ BufferSubscriber, Frame, StreamConfig,
+};
+
+/// The [BufferPoolPublisher] submits buffers from a pool over to the subscriber.
+pub struct BufferPoolPublisher {
+ stream_config: StreamConfig,
+ buffer_pool: BufferPool,
+ subscription: SharedBufferSubscription,
+ subscriber: Option<Box<dyn BufferSubscriber>>,
+}
+
+impl BufferPoolPublisher {
+ /// The [BufferPoolPublisher] needs to initialize a [BufferPool], the [BufferPool] will create
+ /// all buffers at initialization using the stream_config.
+ pub fn new(stream_config: StreamConfig, size: usize) -> Option<Self> {
+ BufferPool::new(size, stream_config).map(|buffer_pool| Self {
+ stream_config,
+ buffer_pool,
+ subscription: SharedBufferSubscription::new(),
+ subscriber: None,
+ })
+ }
+
+ /// If the [SharedBufferSubscription] is ready for a [Frame], a buffer will be requested from
+ /// [BufferPool] and sent over to the [BufferSubscriber].
+ pub fn send_next_frame(&mut self, present_time: Instant) -> bool {
+ if let Some(subscriber) = self.subscriber.as_mut() {
+ if self.subscription.take_request() {
+ if let Some(buffer) = self.buffer_pool.next_buffer() {
+ let frame = Frame { buffer, present_time, fence: 0 };
+
+ subscriber.on_next(frame);
+ return true;
+ }
+ }
+ }
+ false
+ }
+}
+
+impl BufferPublisher for BufferPoolPublisher {
+ fn get_publisher_stream_config(&self) -> StreamConfig {
+ self.stream_config
+ }
+
+ fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+ assert!(self.subscriber.is_none());
+
+ self.subscriber = Some(Box::new(subscriber));
+ self.subscriber.as_mut().unwrap().on_subscribe(self.subscription.clone_for_subscriber());
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use nativewindow::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+
+ use super::*;
+
+ use crate::{
+ subscribers::{
+ testing::{TestSubscriber, TestingSubscriberEvent},
+ SharedSubscriber,
+ },
+ StreamConfig,
+ };
+
+ const STREAM_CONFIG: StreamConfig = StreamConfig {
+ width: 1,
+ height: 1,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ stride: 0,
+ };
+
+ #[test]
+ fn test_send_next_frame() {
+ let subscriber = SharedSubscriber::new(TestSubscriber::new(STREAM_CONFIG));
+
+ let mut buffer_pool_publisher = BufferPoolPublisher::new(STREAM_CONFIG, 1).unwrap();
+ buffer_pool_publisher.subscribe(subscriber.clone());
+
+ subscriber.map_inner(|s| s.request(1));
+
+ assert!(buffer_pool_publisher.send_next_frame(Instant::now()));
+
+ let events = subscriber.map_inner_mut(|s| s.take_events());
+ assert!(matches!(events.last().unwrap(), TestingSubscriberEvent::Next(_)));
+ assert_eq!(buffer_pool_publisher.subscription.pending_requests(), 0);
+ }
+}
diff --git a/libs/bufferstreams/rust/src/publishers/mod.rs b/libs/bufferstreams/rust/src/publishers/mod.rs
new file mode 100644
index 0000000000..8ed3ba0e00
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/mod.rs
@@ -0,0 +1,20 @@
+// 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 module provides [BufferSubscriber] implementations and helpers.
+
+mod buffer_pool_publisher;
+pub mod testing;
+
+pub use buffer_pool_publisher::*;
diff --git a/libs/bufferstreams/rust/src/publishers/testing.rs b/libs/bufferstreams/rust/src/publishers/testing.rs
new file mode 100644
index 0000000000..1593b18d7f
--- /dev/null
+++ b/libs/bufferstreams/rust/src/publishers/testing.rs
@@ -0,0 +1,103 @@
+// 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.
+
+//! Provides useful publishers for testing specifically. These should not be used in normal code.
+
+use crate::{subscriptions::SharedBufferSubscription, *};
+
+/// A [BufferPublisher] specifically for testing.
+///
+/// Provides users the ability to send events and read the state of the subscription.
+pub struct TestPublisher {
+ config: StreamConfig,
+ subscriber: Option<Box<dyn BufferSubscriber>>,
+ subscription: SharedBufferSubscription,
+}
+
+impl TestPublisher {
+ /// Create a new [TestPublisher].
+ pub fn new(config: StreamConfig) -> Self {
+ Self { config, subscriber: None, subscription: SharedBufferSubscription::new() }
+ }
+
+ /// Send a [BufferSubscriber::on_next] event to an owned [BufferSubscriber] if it has any
+ /// requested and returns true. Drops the frame and returns false otherwise.
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscriber.
+ pub fn send_frame(&mut self, frame: Frame) -> bool {
+ let subscriber =
+ self.subscriber.as_deref_mut().expect("Tried to send_frame with no subscriber");
+
+ if self.subscription.take_request() {
+ subscriber.on_next(frame);
+ true
+ } else {
+ false
+ }
+ }
+
+ /// Send a [BufferSubscriber::on_complete] event to an owned [BufferSubscriber].
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscriber.
+ pub fn send_complete(&mut self) {
+ let subscriber =
+ self.subscriber.as_deref_mut().expect("Tried to send_complete with no subscriber");
+ subscriber.on_complete();
+ }
+
+ /// Send a [BufferSubscriber::on_error] event to an owned [BufferSubscriber].
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscriber.
+ pub fn send_error(&mut self, error: BufferError) {
+ let subscriber =
+ self.subscriber.as_deref_mut().expect("Tried to send_error with no subscriber");
+ subscriber.on_error(error);
+ }
+
+ /// Returns whether this [BufferPublisher] owns a subscriber.
+ pub fn has_subscriber(&self) -> bool {
+ self.subscriber.is_some()
+ }
+
+ /// Returns the nummber of frames requested by the [BufferSubscriber].
+ pub fn pending_requests(&self) -> u64 {
+ self.subscription.pending_requests()
+ }
+
+ /// Returns whether the [BufferSubscriber] has cancelled the subscription.
+ pub fn is_cancelled(&self) -> bool {
+ self.subscription.is_cancelled()
+ }
+}
+
+impl BufferPublisher for TestPublisher {
+ fn get_publisher_stream_config(&self) -> crate::StreamConfig {
+ self.config
+ }
+
+ fn subscribe(&mut self, subscriber: impl BufferSubscriber + 'static) {
+ assert!(self.subscriber.is_none(), "TestingPublishers can only take one subscriber");
+ self.subscriber = Some(Box::new(subscriber));
+
+ if let Some(ref mut subscriber) = self.subscriber {
+ subscriber.on_subscribe(self.subscription.clone_for_subscriber());
+ }
+ }
+}
diff --git a/libs/bufferstreams/rust/src/stream_config.rs b/libs/bufferstreams/rust/src/stream_config.rs
new file mode 100644
index 0000000000..454bdf144e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/stream_config.rs
@@ -0,0 +1,67 @@
+// 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.
+
+use nativewindow::*;
+
+/// The configuration of the buffers published by a [BufferPublisher] or
+/// expected by a [BufferSubscriber].
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub struct StreamConfig {
+ /// Width in pixels of streaming buffers.
+ pub width: u32,
+ /// Height in pixels of streaming buffers.
+ pub height: u32,
+ /// Number of layers of streaming buffers.
+ pub layers: u32,
+ /// Format of streaming buffers.
+ pub format: AHardwareBuffer_Format::Type,
+ /// Usage of streaming buffers.
+ pub usage: AHardwareBuffer_UsageFlags,
+ /// Stride of streaming buffers.
+ pub stride: u32,
+}
+
+impl StreamConfig {
+ /// Tries to create a new HardwareBuffer from settings in a [StreamConfig].
+ pub fn create_hardware_buffer(&self) -> Option<HardwareBuffer> {
+ HardwareBuffer::new(self.width, self.height, self.layers, self.format, self.usage)
+ }
+}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn test_create_hardware_buffer() {
+ let config = StreamConfig {
+ width: 123,
+ height: 456,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN
+ | AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN,
+ stride: 0,
+ };
+
+ let maybe_buffer = config.create_hardware_buffer();
+ assert!(maybe_buffer.is_some());
+
+ let buffer = maybe_buffer.unwrap();
+ assert_eq!(config.width, buffer.width());
+ assert_eq!(config.height, buffer.height());
+ assert_eq!(config.format, buffer.format());
+ assert_eq!(config.usage, buffer.usage());
+ }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/mod.rs b/libs/bufferstreams/rust/src/subscribers/mod.rs
new file mode 100644
index 0000000000..dd038c6c32
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/mod.rs
@@ -0,0 +1,20 @@
+// 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 module provides [BufferSubscriber] implementations and helpers.
+
+mod shared;
+pub mod testing;
+
+pub use shared::*;
diff --git a/libs/bufferstreams/rust/src/subscribers/shared.rs b/libs/bufferstreams/rust/src/subscribers/shared.rs
new file mode 100644
index 0000000000..46c58dc04a
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/shared.rs
@@ -0,0 +1,94 @@
+// 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 module provides [BufferSubscriber] implementations and helpers.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A [BufferSubscriber] wrapper that provides shared access.
+///
+/// Normally, [BufferSubscriber]s are fully owned by the publisher that they are attached to. With
+/// [SharedSubscriber], a
+///
+/// # Panics
+///
+/// [BufferSubscriber::on_subscribe] on a [SharedSubscriber] can only be called once, otherwise it
+/// will panic. This is to prevent accidental and unsupported sharing between multiple publishers to
+/// reflect the usual behavior where a publisher takes full ownership of a subscriber.
+pub struct SharedSubscriber<S: BufferSubscriber>(Arc<Mutex<SharedSubscriberInner<S>>>);
+
+struct SharedSubscriberInner<S: BufferSubscriber> {
+ subscriber: S,
+ is_subscribed: bool,
+}
+
+impl<S: BufferSubscriber> SharedSubscriber<S> {
+ /// Create a new wrapper around a [BufferSubscriber].
+ pub fn new(subscriber: S) -> Self {
+ Self(Arc::new(Mutex::new(SharedSubscriberInner { subscriber, is_subscribed: false })))
+ }
+
+ /// Provides access to an immutable reference to the wrapped [BufferSubscriber].
+ pub fn map_inner<R, F: FnOnce(&S) -> R>(&self, f: F) -> R {
+ let inner = self.0.lock().unwrap();
+ f(&inner.subscriber)
+ }
+
+ /// Provides access to a mutable reference to the wrapped [BufferSubscriber].
+ pub fn map_inner_mut<R, F: FnOnce(&mut S) -> R>(&self, f: F) -> R {
+ let mut inner = self.0.lock().unwrap();
+ f(&mut inner.subscriber)
+ }
+}
+
+impl<S: BufferSubscriber> Clone for SharedSubscriber<S> {
+ fn clone(&self) -> Self {
+ Self(Arc::clone(&self.0))
+ }
+}
+
+impl<S: BufferSubscriber> BufferSubscriber for SharedSubscriber<S> {
+ fn get_subscriber_stream_config(&self) -> StreamConfig {
+ let inner = self.0.lock().unwrap();
+ inner.subscriber.get_subscriber_stream_config()
+ }
+
+ fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+ let mut inner = self.0.lock().unwrap();
+ assert!(
+ !inner.is_subscribed,
+ "A SharedSubscriber can not be shared between two BufferPublishers"
+ );
+ inner.is_subscribed = true;
+
+ inner.subscriber.on_subscribe(subscription);
+ }
+
+ fn on_next(&mut self, frame: Frame) {
+ let mut inner = self.0.lock().unwrap();
+ inner.subscriber.on_next(frame);
+ }
+
+ fn on_error(&mut self, error: BufferError) {
+ let mut inner = self.0.lock().unwrap();
+ inner.subscriber.on_error(error);
+ }
+
+ fn on_complete(&mut self) {
+ let mut inner = self.0.lock().unwrap();
+ inner.subscriber.on_complete();
+ }
+}
diff --git a/libs/bufferstreams/rust/src/subscribers/testing.rs b/libs/bufferstreams/rust/src/subscribers/testing.rs
new file mode 100644
index 0000000000..b7e970579e
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscribers/testing.rs
@@ -0,0 +1,106 @@
+// 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.
+
+//! Provides useful subscribers for testing specifically. These should not be used in normal code.
+
+use crate::*;
+
+/// Represents a callback called by a [BufferPublisher] on a [BufferSubscriber].
+pub enum TestingSubscriberEvent {
+ /// Represents a call to [BufferSubscriber::on_subscribe].
+ Subscribe,
+ /// Represents a call to [BufferSubscriber::on_next].
+ Next(Frame),
+ /// Represents a call to [BufferSubscriber::on_error].
+ Error(BufferError),
+ /// Represents a call to [BufferSubscriber::on_complete].
+ Complete,
+}
+
+/// A [BufferSubscriber] specifically for testing. Logs events as they happen which can be retrieved
+/// by the test to ensure appropriate behavior.
+pub struct TestSubscriber {
+ config: StreamConfig,
+ subscription: Option<Box<dyn BufferSubscription>>,
+ events: Vec<TestingSubscriberEvent>,
+}
+
+impl TestSubscriber {
+ /// Create a new [TestSubscriber].
+ pub fn new(config: StreamConfig) -> Self {
+ Self { config, subscription: None, events: Vec::new() }
+ }
+
+ /// Returns true if this [BufferSubscriber] has an active subscription.
+ pub fn has_subscription(&self) -> bool {
+ self.subscription.is_some()
+ }
+
+ /// Make a request on behalf of this test subscriber.
+ ///
+ /// This will panic if there is no owned subscription.
+ pub fn request(&self, n: u64) {
+ let subscription = self
+ .subscription
+ .as_deref()
+ .expect("Tried to request on a TestSubscriber with no subscription");
+ subscription.request(n);
+ }
+
+ /// Cancel on behalf of this test subscriber.
+ ///
+ /// # Panics
+ ///
+ /// This will panic if there is no owned subscription.
+ pub fn cancel(&self) {
+ let subscription = self
+ .subscription
+ .as_deref()
+ .expect("Tried to cancel a TestSubscriber with no subscription");
+ subscription.cancel();
+ }
+
+ /// Gets all of the events that have happened to this [BufferSubscriber] since the last call
+ /// to this function or it was created.
+ pub fn take_events(&mut self) -> Vec<TestingSubscriberEvent> {
+ let mut out = Vec::new();
+ out.append(&mut self.events);
+ out
+ }
+}
+
+impl BufferSubscriber for TestSubscriber {
+ fn get_subscriber_stream_config(&self) -> StreamConfig {
+ self.config
+ }
+
+ fn on_subscribe(&mut self, subscription: Box<dyn BufferSubscription>) {
+ assert!(self.subscription.is_none(), "TestSubscriber must only be subscribed to once");
+ self.subscription = Some(subscription);
+
+ self.events.push(TestingSubscriberEvent::Subscribe);
+ }
+
+ fn on_next(&mut self, frame: Frame) {
+ self.events.push(TestingSubscriberEvent::Next(frame));
+ }
+
+ fn on_error(&mut self, error: BufferError) {
+ self.events.push(TestingSubscriberEvent::Error(error));
+ }
+
+ fn on_complete(&mut self) {
+ self.events.push(TestingSubscriberEvent::Complete);
+ }
+}
diff --git a/libs/bufferstreams/rust/src/subscriptions/mod.rs b/libs/bufferstreams/rust/src/subscriptions/mod.rs
new file mode 100644
index 0000000000..e046dbbda3
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/mod.rs
@@ -0,0 +1,19 @@
+// 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 module provides [BufferSubscription] implementations and helpers.
+
+mod shared_buffer_subscription;
+
+pub use shared_buffer_subscription::*;
diff --git a/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
new file mode 100644
index 0000000000..90275c7320
--- /dev/null
+++ b/libs/bufferstreams/rust/src/subscriptions/shared_buffer_subscription.rs
@@ -0,0 +1,84 @@
+// 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.
+
+use std::sync::{Arc, Mutex};
+
+use crate::*;
+
+/// A simple sharable helper that can be used as a [BufferSubscription] by a [BufferSubscriber] and
+/// as a state tracker by a [BufferPublisher].
+#[derive(Clone, Debug)]
+pub struct SharedBufferSubscription(Arc<Mutex<BufferSubscriptionData>>);
+
+#[derive(Debug, Default)]
+struct BufferSubscriptionData {
+ requests: u64,
+ is_cancelled: bool,
+}
+
+impl SharedBufferSubscription {
+ /// Create a new [SharedBufferSubscription].
+ pub fn new() -> Self {
+ SharedBufferSubscription::default()
+ }
+
+ /// Clone this [SharedBufferSubscription] so it can be passed into
+ /// [BufferSubscriber::on_subscribe].
+ pub fn clone_for_subscriber(&self) -> Box<dyn BufferSubscription> {
+ Box::new(self.clone()) as Box<dyn BufferSubscription>
+ }
+
+ /// If possible (not cancelled and with requests pending), take
+ pub fn take_request(&self) -> bool {
+ let mut data = self.0.lock().unwrap();
+
+ if data.is_cancelled || data.requests == 0 {
+ false
+ } else {
+ data.requests -= 1;
+ true
+ }
+ }
+
+ /// Get the number of pending requests made by the [BufferSubscriber] via
+ /// [BufferSubscription::request].
+ pub fn pending_requests(&self) -> u64 {
+ self.0.lock().unwrap().requests
+ }
+
+ /// Get get whether the [BufferSubscriber] has called [BufferSubscription::cancel].
+ pub fn is_cancelled(&self) -> bool {
+ self.0.lock().unwrap().is_cancelled
+ }
+}
+
+impl Default for SharedBufferSubscription {
+ fn default() -> Self {
+ Self(Arc::new(Mutex::new(BufferSubscriptionData::default())))
+ }
+}
+
+impl BufferSubscription for SharedBufferSubscription {
+ fn request(&self, n: u64) {
+ let mut data = self.0.lock().unwrap();
+ if !data.is_cancelled {
+ data.requests = data.requests.saturating_add(n);
+ }
+ }
+
+ fn cancel(&self) {
+ let mut data = self.0.lock().unwrap();
+ data.is_cancelled = true;
+ }
+}
diff --git a/libs/bufferstreams/update_include.sh b/libs/bufferstreams/update_include.sh
new file mode 100755
index 0000000000..e986e9fb08
--- /dev/null
+++ b/libs/bufferstreams/update_include.sh
@@ -0,0 +1,2 @@
+cd rust
+cbindgen --config cbindgen.toml --crate bufferstreams --output ../include/bufferstreams.h
diff --git a/libs/cputimeinstate/cputimeinstate.cpp b/libs/cputimeinstate/cputimeinstate.cpp
index 706704ad34..4a7bd36202 100644
--- a/libs/cputimeinstate/cputimeinstate.cpp
+++ b/libs/cputimeinstate/cputimeinstate.cpp
@@ -55,6 +55,7 @@ static uint32_t gNPolicies = 0;
static uint32_t gNCpus = 0;
static std::vector<std::vector<uint32_t>> gPolicyFreqs;
static std::vector<std::vector<uint32_t>> gPolicyCpus;
+static std::vector<uint32_t> gCpuIndexMap;
static std::set<uint32_t> gAllFreqs;
static unique_fd gTisTotalMapFd;
static unique_fd gTisMapFd;
@@ -108,7 +109,7 @@ static bool initGlobals() {
free(dirlist[i]);
}
free(dirlist);
-
+ uint32_t max_cpu_number = 0;
for (const auto &policy : policyFileNames) {
std::vector<uint32_t> freqs;
for (const auto &name : {"available", "boost"}) {
@@ -127,8 +128,19 @@ static bool initGlobals() {
std::string path = StringPrintf("%s/%s/%s", basepath, policy.c_str(), "related_cpus");
auto cpus = readNumbersFromFile(path);
if (!cpus) return false;
+ for (auto cpu : *cpus) {
+ if(cpu > max_cpu_number)
+ max_cpu_number = cpu;
+ }
gPolicyCpus.emplace_back(*cpus);
}
+ gCpuIndexMap = std::vector<uint32_t>(max_cpu_number+1, -1);
+ uint32_t cpuorder = 0;
+ for (const auto &cpuList : gPolicyCpus) {
+ for (auto cpu : cpuList) {
+ gCpuIndexMap[cpu] = cpuorder++;
+ }
+ }
gTisTotalMapFd =
unique_fd{bpf_obj_get(BPF_FS_PATH "map_timeInState_total_time_in_state_map")};
@@ -277,7 +289,7 @@ std::optional<std::vector<std::vector<uint64_t>>> getTotalCpuFreqTimes() {
for (uint32_t policyIdx = 0; policyIdx < gNPolicies; ++policyIdx) {
if (freqIdx >= gPolicyFreqs[policyIdx].size()) continue;
for (const auto &cpu : gPolicyCpus[policyIdx]) {
- out[policyIdx][freqIdx] += vals[cpu];
+ out[policyIdx][freqIdx] += vals[gCpuIndexMap[cpu]];
}
}
}
@@ -316,7 +328,8 @@ std::optional<std::vector<std::vector<uint64_t>>> getUidCpuFreqTimes(uint32_t ui
auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY : out[j].end();
for (const auto &cpu : gPolicyCpus[j]) {
- std::transform(begin, end, std::begin(vals[cpu].ar), begin, std::plus<uint64_t>());
+ std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin,
+ std::plus<uint64_t>());
}
}
}
@@ -382,7 +395,8 @@ getUidsUpdatedCpuFreqTimes(uint64_t *lastUpdate) {
auto end = nextOffset < gPolicyFreqs[i].size() ? begin + FREQS_PER_ENTRY :
map[key.uid][i].end();
for (const auto &cpu : gPolicyCpus[i]) {
- std::transform(begin, end, std::begin(vals[cpu].ar), begin, std::plus<uint64_t>());
+ std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin,
+ std::plus<uint64_t>());
}
}
prevKey = key;
@@ -437,8 +451,8 @@ std::optional<concurrent_time_t> getUidConcurrentTimes(uint32_t uid, bool retry)
: ret.policy[policy].end();
for (const auto &cpu : gPolicyCpus[policy]) {
- std::transform(policyBegin, policyEnd, std::begin(vals[cpu].policy), policyBegin,
- std::plus<uint64_t>());
+ std::transform(policyBegin, policyEnd, std::begin(vals[gCpuIndexMap[cpu]].policy),
+ policyBegin, std::plus<uint64_t>());
}
}
}
@@ -506,8 +520,8 @@ std::optional<std::unordered_map<uint32_t, concurrent_time_t>> getUidsUpdatedCon
: ret[key.uid].policy[policy].end();
for (const auto &cpu : gPolicyCpus[policy]) {
- std::transform(policyBegin, policyEnd, std::begin(vals[cpu].policy), policyBegin,
- std::plus<uint64_t>());
+ std::transform(policyBegin, policyEnd, std::begin(vals[gCpuIndexMap[cpu]].policy),
+ policyBegin, std::plus<uint64_t>());
}
}
} while (prevKey = key, !getNextMapKey(gConcurrentMapFd, &prevKey, &key));
@@ -640,7 +654,7 @@ getAggregatedTaskCpuFreqTimes(pid_t tgid, const std::vector<uint16_t> &aggregati
auto end = nextOffset < gPolicyFreqs[j].size() ? begin + FREQS_PER_ENTRY
: map[key.aggregation_key][j].end();
for (const auto &cpu : gPolicyCpus[j]) {
- std::transform(begin, end, std::begin(vals[cpu].ar), begin,
+ std::transform(begin, end, std::begin(vals[gCpuIndexMap[cpu]].ar), begin,
std::plus<uint64_t>());
}
}
diff --git a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
index f835997187..9df5632e97 100644
--- a/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
+++ b/libs/cputimeinstate/fuzz/cputimeinstate_fuzzer/cputimeinstate_fuzzer.cpp
@@ -20,6 +20,7 @@
#include <fuzzer/FuzzedDataProvider.h>
#include <android-base/unique_fd.h>
#include <cputimeinstate.h>
+#include <functional>
using namespace android::bpf;
diff --git a/libs/cputimeinstate/testtimeinstate.cpp b/libs/cputimeinstate/testtimeinstate.cpp
index 6ccc6cafb6..81f6a585ab 100644
--- a/libs/cputimeinstate/testtimeinstate.cpp
+++ b/libs/cputimeinstate/testtimeinstate.cpp
@@ -40,6 +40,9 @@ namespace bpf {
static constexpr uint64_t NSEC_PER_SEC = 1000000000;
static constexpr uint64_t NSEC_PER_YEAR = NSEC_PER_SEC * 60 * 60 * 24 * 365;
+// Declare busy loop variable globally to prevent removal during optimization
+static long sum __attribute__((used)) = 0;
+
using std::vector;
class TimeInStateTest : public testing::Test {
@@ -576,7 +579,7 @@ uint64_t timeNanos() {
// Keeps CPU busy with some number crunching
void useCpu() {
- long sum = 0;
+ sum = 0;
for (int i = 0; i < 100000; i++) {
sum *= i;
}
diff --git a/libs/debugstore/OWNERS b/libs/debugstore/OWNERS
new file mode 100644
index 0000000000..428a1a2215
--- /dev/null
+++ b/libs/debugstore/OWNERS
@@ -0,0 +1,3 @@
+benmiles@google.com
+gaillard@google.com
+mohamadmahmoud@google.com
diff --git a/libs/debugstore/rust/Android.bp b/libs/debugstore/rust/Android.bp
new file mode 100644
index 0000000000..55ba3c32d1
--- /dev/null
+++ b/libs/debugstore/rust/Android.bp
@@ -0,0 +1,71 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_team: "trendy_team_android_telemetry_infra",
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+rust_defaults {
+ name: "libdebugstore_defaults",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libcrossbeam_queue",
+ "libparking_lot",
+ "libonce_cell",
+ "libcxx",
+ ],
+ shared_libs: ["libutils"],
+ edition: "2021",
+}
+
+rust_ffi_static {
+ name: "libdebugstore_rust_ffi",
+ crate_name: "debugstore",
+ defaults: ["libdebugstore_defaults"],
+}
+
+cc_library {
+ name: "libdebugstore_cxx",
+ generated_headers: ["libdebugstore_cxx_bridge_header"],
+ generated_sources: ["libdebugstore_cxx_bridge_code"],
+ export_generated_headers: ["libdebugstore_cxx_bridge_header"],
+ shared_libs: ["libutils"],
+ whole_static_libs: ["libdebugstore_rust_ffi"],
+}
+
+rust_test {
+ name: "libdebugstore_tests",
+ defaults: ["libdebugstore_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ shared_libs: ["libdebugstore_cxx"],
+}
+
+genrule {
+ name: "libdebugstore_cxx_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["debugstore/debugstore_cxx_bridge.rs.h"],
+}
+
+genrule {
+ name: "libdebugstore_cxx_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["src/lib.rs"],
+ out: ["debugstore/debugstore_cxx_bridge.rs.cpp"],
+}
diff --git a/libs/debugstore/rust/Cargo.toml b/libs/debugstore/rust/Cargo.toml
new file mode 100644
index 0000000000..23a8d24647
--- /dev/null
+++ b/libs/debugstore/rust/Cargo.toml
@@ -0,0 +1,9 @@
+[package]
+name = "debugstore"
+version = "0.1.0"
+edition = "2021"
+
+[lib]
+crate-type = ["cdylib", "rlib"]
+
+[dependencies] \ No newline at end of file
diff --git a/libs/debugstore/rust/src/core.rs b/libs/debugstore/rust/src/core.rs
new file mode 100644
index 0000000000..1dfa512151
--- /dev/null
+++ b/libs/debugstore/rust/src/core.rs
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+use super::event::Event;
+use super::event_type::EventType;
+use super::storage::Storage;
+use crate::cxxffi::uptimeMillis;
+use once_cell::sync::Lazy;
+use std::fmt;
+use std::sync::atomic::{AtomicU64, Ordering};
+
+// Lazily initialized static instance of DebugStore.
+static INSTANCE: Lazy<DebugStore> = Lazy::new(DebugStore::new);
+
+/// The `DebugStore` struct is responsible for managing debug events and data.
+pub struct DebugStore {
+ /// Atomic counter for generating unique event IDs.
+ id_generator: AtomicU64,
+ /// Non-blocking storage for debug events.
+ event_store: Storage<Event, { DebugStore::DEFAULT_EVENT_LIMIT }>,
+}
+
+impl DebugStore {
+ /// The default limit for the number of events that can be stored.
+ ///
+ /// This limit is used to initialize the storage for debug events.
+ const DEFAULT_EVENT_LIMIT: usize = 16;
+ /// A designated identifier used for events that cannot be closed.
+ ///
+ /// This ID is used for point/instantaneous events, or events do not have
+ /// a distinct end.
+ const NON_CLOSABLE_ID: u64 = 0;
+ /// The version number for the encoding of debug store data.
+ ///
+ /// This constant is used as a part of the debug store's data format,
+ /// allowing for version tracking and compatibility checks.
+ const ENCODE_VERSION: u32 = 1;
+
+ /// Creates a new instance of `DebugStore` with specified event limit and maximum delay.
+ fn new() -> Self {
+ Self { id_generator: AtomicU64::new(1), event_store: Storage::new() }
+ }
+
+ /// Returns a shared instance of `DebugStore`.
+ ///
+ /// This method provides a singleton pattern access to `DebugStore`.
+ pub fn get_instance() -> &'static DebugStore {
+ &INSTANCE
+ }
+
+ /// Begins a new debug event with the given name and data.
+ ///
+ /// This method logs the start of a debug event, assigning it a unique ID and marking its start time.
+ /// - `name`: The name of the debug event.
+ /// - `data`: Associated data as key-value pairs.
+ /// - Returns: A unique ID for the debug event.
+ pub fn begin(&self, name: String, data: Vec<(String, String)>) -> u64 {
+ let id = self.generate_id();
+ self.event_store.insert(Event::new(
+ id,
+ Some(name),
+ uptimeMillis(),
+ EventType::DurationStart,
+ data,
+ ));
+ id
+ }
+
+ /// Records a debug event without a specific duration, with the given name and data.
+ ///
+ /// This method logs an instantaneous debug event, useful for events that don't have a duration but are significant.
+ /// - `name`: The name of the debug event.
+ /// - `data`: Associated data as key-value pairs.
+ pub fn record(&self, name: String, data: Vec<(String, String)>) {
+ self.event_store.insert(Event::new(
+ Self::NON_CLOSABLE_ID,
+ Some(name),
+ uptimeMillis(),
+ EventType::Point,
+ data,
+ ));
+ }
+
+ /// Ends a debug event that was previously started with the given ID.
+ ///
+ /// This method marks the end of a debug event, completing its lifecycle.
+ /// - `id`: The unique ID of the debug event to end.
+ /// - `data`: Additional data to log at the end of the event.
+ pub fn end(&self, id: u64, data: Vec<(String, String)>) {
+ if id != Self::NON_CLOSABLE_ID {
+ self.event_store.insert(Event::new(
+ id,
+ None,
+ uptimeMillis(),
+ EventType::DurationEnd,
+ data,
+ ));
+ }
+ }
+
+ fn generate_id(&self) -> u64 {
+ let mut id = self.id_generator.fetch_add(1, Ordering::Relaxed);
+ while id == Self::NON_CLOSABLE_ID {
+ id = self.id_generator.fetch_add(1, Ordering::Relaxed);
+ }
+ id
+ }
+}
+
+impl fmt::Display for DebugStore {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ let uptime_now = uptimeMillis();
+ write!(f, "{},{},{}::", Self::ENCODE_VERSION, self.event_store.len(), uptime_now)?;
+
+ write!(
+ f,
+ "{}",
+ self.event_store.fold(String::new(), |mut acc, event| {
+ if !acc.is_empty() {
+ acc.push_str("||");
+ }
+ acc.push_str(&event.to_string());
+ acc
+ })
+ )
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_begin_event() {
+ let debug_store = DebugStore::new();
+ let _event_id = debug_store.begin("test_event".to_string(), vec![]);
+ let output = debug_store.to_string();
+ assert!(
+ output.contains("test_event"),
+ "The output should contain the event name 'test_event'"
+ );
+ }
+
+ #[test]
+ fn test_unique_event_ids() {
+ let debug_store = DebugStore::new();
+ let event_id1 = debug_store.begin("event1".to_string(), vec![]);
+ let event_id2 = debug_store.begin("event2".to_string(), vec![]);
+ assert_ne!(event_id1, event_id2, "Event IDs should be unique");
+ }
+
+ #[test]
+ fn test_end_event() {
+ let debug_store = DebugStore::new();
+ let event_id = debug_store.begin("test_event".to_string(), vec![]);
+ debug_store.end(event_id, vec![]);
+ let output = debug_store.to_string();
+
+ let id_pattern = format!("ID:{},", event_id);
+ assert!(
+ output.contains("test_event"),
+ "The output should contain the event name 'test_event'"
+ );
+ assert_eq!(
+ output.matches(&id_pattern).count(),
+ 2,
+ "The output should contain two events (start and end) associated with the given ID"
+ );
+ }
+
+ #[test]
+ fn test_event_data_handling() {
+ let debug_store = DebugStore::new();
+ debug_store
+ .record("data_event".to_string(), vec![("key".to_string(), "value".to_string())]);
+ let output = debug_store.to_string();
+ assert!(
+ output.contains("data_event"),
+ "The output should contain the event name 'data_event'"
+ );
+ assert!(
+ output.contains("key=value"),
+ "The output should contain the event data 'key=value'"
+ );
+ }
+}
diff --git a/libs/debugstore/rust/src/event.rs b/libs/debugstore/rust/src/event.rs
new file mode 100644
index 0000000000..0c16665df4
--- /dev/null
+++ b/libs/debugstore/rust/src/event.rs
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use super::event_type::EventType;
+use std::fmt;
+
+/// Represents a single debug event within the Debug Store system.
+///
+/// It contains all the necessary information for a debug event.
+#[derive(Clone)]
+pub struct Event {
+ /// The unique identifier for this event.
+ pub id: u64,
+ /// The optional name of the event.
+ pub name: Option<String>,
+ /// The system uptime when the event occurred.
+ pub timestamp: i64,
+ /// The type of the event.
+ pub event_type: EventType,
+ /// Additional data associated with the event, stored in the given order as key-value pairs.
+ data: Vec<(String, String)>,
+}
+
+impl Event {
+ /// Constructs a new `Event`.
+ ///
+ /// - `id`: The unique identifier for the event.
+ /// - `name`: An optional name for the event.
+ /// - `timestamp`: The system uptime when the event occurred.
+ /// - `event_type`: The type of the event.
+ /// - `data`: Additional data for the event, represented as ordered key-value pairs.
+ pub fn new(
+ id: u64,
+ name: Option<String>,
+ timestamp: i64,
+ event_type: EventType,
+ data: Vec<(String, String)>,
+ ) -> Self {
+ Self { id, name, timestamp, event_type, data }
+ }
+}
+
+impl fmt::Display for Event {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "ID:{},C:{},T:{}", self.id, self.event_type, self.timestamp)?;
+
+ if let Some(ref name) = self.name {
+ write!(f, ",N:{}", name)?;
+ }
+
+ if !self.data.is_empty() {
+ let data_str =
+ self.data.iter().map(|(k, v)| format!("{}={}", k, v)).collect::<Vec<_>>().join(";");
+ write!(f, ",D:{}", data_str)?;
+ }
+
+ Ok(())
+ }
+}
diff --git a/libs/debugstore/rust/src/event_type.rs b/libs/debugstore/rust/src/event_type.rs
new file mode 100644
index 0000000000..6f4bafb58a
--- /dev/null
+++ b/libs/debugstore/rust/src/event_type.rs
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+use std::fmt;
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+pub enum EventType {
+ /// Marks the an unknown or invalid event, for convenient mapping to a protobuf enum.
+ Invalid,
+ /// Marks the beginning of a duration-based event, indicating the start of a timed operation.
+ DurationStart,
+ /// Marks the end of a duration-based event, indicating the end of a timed operation.
+ DurationEnd,
+ /// Represents a single, instantaneous event with no duration.
+ Point,
+}
+
+impl fmt::Display for EventType {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ EventType::Invalid => "I",
+ EventType::DurationStart => "S",
+ EventType::DurationEnd => "E",
+ EventType::Point => "P",
+ }
+ )
+ }
+}
diff --git a/libs/debugstore/rust/src/lib.rs b/libs/debugstore/rust/src/lib.rs
new file mode 100644
index 0000000000..f2195c0529
--- /dev/null
+++ b/libs/debugstore/rust/src/lib.rs
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! # Debug Store Crate
+/// The Debug Store Crate provides functionalities for storing debug events.
+/// It allows logging and retrieval of debug events, and associated data.
+mod core;
+mod event;
+mod event_type;
+mod storage;
+
+pub use core::*;
+pub use event::*;
+
+use cxx::{CxxString, CxxVector};
+
+#[cxx::bridge(namespace = "android::debugstore")]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod cxxffi {
+ extern "Rust" {
+ fn debug_store_to_string() -> String;
+ fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>);
+ fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64;
+ fn debug_store_end(id: u64, data: &CxxVector<CxxString>);
+ }
+
+ #[namespace = "android"]
+ unsafe extern "C++" {
+ include!("utils/SystemClock.h");
+ fn uptimeMillis() -> i64;
+ }
+}
+
+fn debug_store_to_string() -> String {
+ DebugStore::get_instance().to_string()
+}
+
+fn debug_store_record(name: &CxxString, data: &CxxVector<CxxString>) {
+ DebugStore::get_instance().record(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data));
+}
+
+fn debug_store_begin(name: &CxxString, data: &CxxVector<CxxString>) -> u64 {
+ DebugStore::get_instance().begin(name.to_string_lossy().into_owned(), cxx_vec_to_pairs(data))
+}
+
+fn debug_store_end(id: u64, data: &CxxVector<CxxString>) {
+ DebugStore::get_instance().end(id, cxx_vec_to_pairs(data));
+}
+
+fn cxx_vec_to_pairs(vec: &CxxVector<CxxString>) -> Vec<(String, String)> {
+ vec.iter()
+ .map(|s| s.to_string_lossy().into_owned())
+ .collect::<Vec<_>>()
+ .chunks(2)
+ .filter_map(|chunk| match chunk {
+ [k, v] => Some((k.clone(), v.clone())),
+ _ => None,
+ })
+ .collect()
+}
diff --git a/libs/debugstore/rust/src/storage.rs b/libs/debugstore/rust/src/storage.rs
new file mode 100644
index 0000000000..2ad7f4e0b4
--- /dev/null
+++ b/libs/debugstore/rust/src/storage.rs
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+use crossbeam_queue::ArrayQueue;
+
+/// A thread-safe storage that allows non-blocking attempts to store and visit elements.
+pub struct Storage<T, const N: usize> {
+ insertion_buffer: ArrayQueue<T>,
+}
+
+impl<T, const N: usize> Storage<T, N> {
+ /// Creates a new Storage with the specified size.
+ pub fn new() -> Self {
+ Self { insertion_buffer: ArrayQueue::new(N) }
+ }
+
+ /// Inserts a value into the storage, returning an error if the lock cannot be acquired.
+ pub fn insert(&self, value: T) {
+ self.insertion_buffer.force_push(value);
+ }
+
+ /// Folds over the elements in the storage using the provided function.
+ pub fn fold<U, F>(&self, init: U, mut func: F) -> U
+ where
+ F: FnMut(U, &T) -> U,
+ {
+ let mut acc = init;
+ while let Some(value) = self.insertion_buffer.pop() {
+ acc = func(acc, &value);
+ }
+ acc
+ }
+
+ /// Returns the number of elements that have been inserted into the storage.
+ pub fn len(&self) -> usize {
+ self.insertion_buffer.len()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_insert_and_retrieve() {
+ let storage = Storage::<i32, 10>::new();
+ storage.insert(7);
+
+ let sum = storage.fold(0, |acc, &x| acc + x);
+ assert_eq!(sum, 7, "The sum of the elements should be equal to the inserted value.");
+ }
+
+ #[test]
+ fn test_fold_functionality() {
+ let storage = Storage::<i32, 5>::new();
+ storage.insert(1);
+ storage.insert(2);
+ storage.insert(3);
+
+ let sum = storage.fold(0, |acc, &x| acc + x);
+ assert_eq!(
+ sum, 6,
+ "The sum of the elements should be equal to the sum of inserted values."
+ );
+ }
+
+ #[test]
+ fn test_insert_and_retrieve_multiple_values() {
+ let storage = Storage::<i32, 10>::new();
+ storage.insert(1);
+ storage.insert(2);
+ storage.insert(5);
+
+ let first_sum = storage.fold(0, |acc, &x| acc + x);
+ assert_eq!(first_sum, 8, "The sum of the elements should be equal to the inserted values.");
+
+ storage.insert(30);
+ storage.insert(22);
+
+ let second_sum = storage.fold(0, |acc, &x| acc + x);
+ assert_eq!(
+ second_sum, 52,
+ "The sum of the elements should be equal to the inserted values."
+ );
+ }
+
+ #[test]
+ fn test_storage_limit() {
+ let storage = Storage::<i32, 1>::new();
+ storage.insert(1);
+ // This value should overwrite the previously inserted value (1).
+ storage.insert(4);
+ let sum = storage.fold(0, |acc, &x| acc + x);
+ assert_eq!(sum, 4, "The sum of the elements should be equal to the inserted values.");
+ }
+
+ #[test]
+ fn test_concurrent_insertions() {
+ use std::sync::Arc;
+ use std::thread;
+
+ let storage = Arc::new(Storage::<i32, 100>::new());
+ let threads: Vec<_> = (0..10)
+ .map(|_| {
+ let storage_clone = Arc::clone(&storage);
+ thread::spawn(move || {
+ for i in 0..10 {
+ storage_clone.insert(i);
+ }
+ })
+ })
+ .collect();
+
+ for thread in threads {
+ thread.join().expect("Thread should finish without panicking");
+ }
+
+ let count = storage.fold(0, |acc, _| acc + 1);
+ assert_eq!(count, 100, "Storage should be filled to its limit with concurrent insertions.");
+ }
+}
diff --git a/libs/dumputils/dump_utils.cpp b/libs/dumputils/dump_utils.cpp
index 067ce17c33..f4cf11edb6 100644
--- a/libs/dumputils/dump_utils.cpp
+++ b/libs/dumputils/dump_utils.cpp
@@ -16,6 +16,7 @@
#include <set>
#include <android-base/file.h>
+#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
@@ -61,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",
@@ -85,8 +89,16 @@ static const char* hidl_hal_interfaces_to_dump[] {
/* list of hal interface to dump containing process during native dumps */
static const std::vector<std::string> aidl_interfaces_to_dump {
+ "android.hardware.audio.core.IConfig",
+ "android.hardware.audio.core.IModule",
+ "android.hardware.audio.effect.IFactory",
"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",
@@ -210,3 +222,18 @@ bool IsZygote(int pid) {
return cmdline == "zygote" || cmdline == "zygote64" || cmdline == "usap32" ||
cmdline == "usap64" || cmdline == "webview_zygote";
}
+
+bool IsCached(int pid) {
+ std::string oom_score_adj;
+ if (!android::base::ReadFileToString(android::base::StringPrintf("/proc/%d/oom_score_adj",
+ pid),
+ &oom_score_adj)) {
+ return false;
+ }
+ int32_t oom_score_adj_value;
+ if (!android::base::ParseInt(android::base::Trim(oom_score_adj), &oom_score_adj_value)) {
+ return false;
+ }
+ // An OOM score greater than 900 indicates a cached process.
+ return oom_score_adj_value >= 900;
+}
diff --git a/libs/dumputils/include/dumputils/dump_utils.h b/libs/dumputils/include/dumputils/dump_utils.h
index 7c5329d01b..f973d9fb52 100644
--- a/libs/dumputils/include/dumputils/dump_utils.h
+++ b/libs/dumputils/include/dumputils/dump_utils.h
@@ -25,4 +25,6 @@ std::set<int> get_interesting_pids();
bool IsZygote(int pid);
+bool IsCached(int pid);
+
#endif // DUMPUTILS_H_
diff --git a/libs/fakeservicemanager/Android.bp b/libs/fakeservicemanager/Android.bp
index 29924ff500..38233934c8 100644
--- a/libs/fakeservicemanager/Android.bp
+++ b/libs/fakeservicemanager/Android.bp
@@ -11,12 +11,13 @@ cc_defaults {
name: "fakeservicemanager_defaults",
host_supported: true,
srcs: [
- "ServiceManager.cpp",
+ "FakeServiceManager.cpp",
],
shared_libs: [
"libbinder",
"libutils",
+ "liblog",
],
target: {
darwin: {
@@ -28,7 +29,7 @@ cc_defaults {
cc_library {
name: "libfakeservicemanager",
defaults: ["fakeservicemanager_defaults"],
- export_include_dirs: ["include/fakeservicemanager"],
+ export_include_dirs: ["include"],
}
cc_test_host {
@@ -38,5 +39,43 @@ cc_test_host {
"test_sm.cpp",
],
static_libs: ["libgmock"],
- local_include_dirs: ["include/fakeservicemanager"],
+ local_include_dirs: ["include"],
+}
+
+rust_bindgen {
+ name: "libfakeservicemanager_bindgen",
+ crate_name: "fakeservicemanager_bindgen",
+ host_supported: true,
+ wrapper_src: "rust/wrappers/FakeServiceManagerWrapper.hpp",
+ source_stem: "bindings",
+ visibility: [":__subpackages__"],
+ bindgen_flags: [
+ "--allowlist-function",
+ "setupFakeServiceManager",
+ "--allowlist-function",
+ "clearFakeServiceManager",
+ ],
+ shared_libs: [
+ "libc++",
+ "libbinder",
+ "libfakeservicemanager",
+ ],
+}
+
+rust_library {
+ name: "libfakeservicemanager_rs",
+ crate_name: "fakeservicemanager_rs",
+ host_supported: true,
+ srcs: [
+ "rust/src/lib.rs",
+ ],
+ shared_libs: [
+ "libc++",
+ "libfakeservicemanager",
+ ],
+ rustlibs: [
+ "libfakeservicemanager_bindgen",
+ ],
+ lints: "none",
+ clippy_lints: "none",
}
diff --git a/libs/fakeservicemanager/FakeServiceManager.cpp b/libs/fakeservicemanager/FakeServiceManager.cpp
new file mode 100644
index 0000000000..08f30de637
--- /dev/null
+++ b/libs/fakeservicemanager/FakeServiceManager.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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 "fakeservicemanager/FakeServiceManager.h"
+
+using android::sp;
+using android::FakeServiceManager;
+using android::setDefaultServiceManager;
+
+namespace android {
+
+FakeServiceManager::FakeServiceManager() {}
+
+sp<IBinder> FakeServiceManager::getService( const String16& name) const {
+ // Servicemanager is single-threaded and cannot block. This method exists for legacy reasons.
+ return checkService(name);
+}
+
+sp<IBinder> FakeServiceManager::checkService( const String16& name) const {
+ std::lock_guard<std::mutex> l(mMutex);
+
+ auto it = mNameToService.find(name);
+ if (it == mNameToService.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+status_t FakeServiceManager::addService(const String16& name, const sp<IBinder>& service,
+ bool /*allowIsolated*/,
+ int /*dumpsysFlags*/) {
+ std::lock_guard<std::mutex> l(mMutex);
+
+ if (service == nullptr) {
+ return UNEXPECTED_NULL;
+ }
+ mNameToService[name] = service;
+ return NO_ERROR;
+}
+
+Vector<String16> FakeServiceManager::listServices(int /*dumpsysFlags*/) {
+ std::lock_guard<std::mutex> l(mMutex);
+
+ Vector<String16> services;
+ for (auto const& [name, service] : mNameToService) {
+ (void) service;
+ services.push_back(name);
+ }
+ return services;
+}
+
+IBinder* FakeServiceManager::onAsBinder() {
+ return nullptr;
+}
+
+sp<IBinder> FakeServiceManager::waitForService(const String16& name) {
+ return checkService(name);
+}
+
+bool FakeServiceManager::isDeclared(const String16& name) {
+ std::lock_guard<std::mutex> l(mMutex);
+
+ return mNameToService.find(name) != mNameToService.end();
+}
+
+Vector<String16> FakeServiceManager::getDeclaredInstances(const String16& name) {
+ std::lock_guard<std::mutex> l(mMutex);
+
+ Vector<String16> out;
+ const String16 prefix = name + String16("/");
+ for (const auto& [registeredName, service] : mNameToService) {
+ (void) service;
+ if (registeredName.startsWith(prefix)) {
+ out.add(String16(registeredName.c_str() + prefix.size()));
+ }
+ }
+ return out;
+}
+
+std::optional<String16> FakeServiceManager::updatableViaApex(const String16& name) {
+ (void)name;
+ return std::nullopt;
+}
+
+Vector<String16> FakeServiceManager::getUpdatableNames(const String16& apexName) {
+ (void)apexName;
+ return {};
+}
+
+std::optional<IServiceManager::ConnectionInfo> FakeServiceManager::getConnectionInfo(
+ const String16& name) {
+ (void)name;
+ return std::nullopt;
+}
+
+status_t FakeServiceManager::registerForNotifications(const String16&,
+ const sp<LocalRegistrationCallback>&) {
+ return INVALID_OPERATION;
+}
+
+status_t FakeServiceManager::unregisterForNotifications(const String16&,
+ const sp<LocalRegistrationCallback>&) {
+ return INVALID_OPERATION;
+}
+
+std::vector<IServiceManager::ServiceDebugInfo> FakeServiceManager::getServiceDebugInfo() {
+ std::vector<IServiceManager::ServiceDebugInfo> ret;
+ return ret;
+}
+
+void FakeServiceManager::clear() {
+ std::map<String16, sp<IBinder>> backup;
+
+ {
+ std::lock_guard<std::mutex> l(mMutex);
+ backup = mNameToService;
+ mNameToService.clear();
+ }
+
+ // destructors may access FSM, so avoid recursive lock
+ backup.clear(); // explicit
+
+ // TODO: destructors may have added more services here - may want
+ // to check this or abort
+}
+} // namespace android
+
+[[clang::no_destroy]] static sp<FakeServiceManager> gFakeServiceManager;
+[[clang::no_destroy]] static std::once_flag gSmOnce;
+
+extern "C" {
+
+// Setup FakeServiceManager to mock dependencies in test using this API for rust backend
+void setupFakeServiceManager() {
+ /* Create a FakeServiceManager instance and add required services */
+ std::call_once(gSmOnce, [&]() {
+ gFakeServiceManager = new FakeServiceManager();
+ android::setDefaultServiceManager(gFakeServiceManager);
+ });
+}
+
+// Clear existing services from Fake SM for rust backend
+void clearFakeServiceManager() {
+ LOG_ALWAYS_FATAL_IF(gFakeServiceManager == nullptr, "Fake Service Manager is not available. Forgot to call setupFakeServiceManager?");
+ gFakeServiceManager->clear();
+}
+} //extern "C"
diff --git a/libs/fakeservicemanager/ServiceManager.cpp b/libs/fakeservicemanager/ServiceManager.cpp
deleted file mode 100644
index 1109ad8594..0000000000
--- a/libs/fakeservicemanager/ServiceManager.cpp
+++ /dev/null
@@ -1,113 +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 "ServiceManager.h"
-
-namespace android {
-
-ServiceManager::ServiceManager() {}
-
-sp<IBinder> ServiceManager::getService( const String16& name) const {
- // Servicemanager is single-threaded and cannot block. This method exists for legacy reasons.
- return checkService(name);
-}
-
-sp<IBinder> ServiceManager::checkService( const String16& name) const {
- auto it = mNameToService.find(name);
- if (it == mNameToService.end()) {
- return nullptr;
- }
- return it->second;
-}
-
-status_t ServiceManager::addService(const String16& name, const sp<IBinder>& service,
- bool /*allowIsolated*/,
- int /*dumpsysFlags*/) {
- if (service == nullptr) {
- return UNEXPECTED_NULL;
- }
- mNameToService[name] = service;
- return NO_ERROR;
-}
-
-Vector<String16> ServiceManager::listServices(int /*dumpsysFlags*/) {
- Vector<String16> services;
- for (auto const& [name, service] : mNameToService) {
- (void) service;
- services.push_back(name);
- }
- return services;
-}
-
-IBinder* ServiceManager::onAsBinder() {
- return nullptr;
-}
-
-sp<IBinder> ServiceManager::waitForService(const String16& name) {
- return checkService(name);
-}
-
-bool ServiceManager::isDeclared(const String16& name) {
- return mNameToService.find(name) != mNameToService.end();
-}
-
-Vector<String16> ServiceManager::getDeclaredInstances(const String16& name) {
- Vector<String16> out;
- const String16 prefix = name + String16("/");
- for (const auto& [registeredName, service] : mNameToService) {
- (void) service;
- if (registeredName.startsWith(prefix)) {
- out.add(String16(registeredName.string() + prefix.size()));
- }
- }
- return out;
-}
-
-std::optional<String16> ServiceManager::updatableViaApex(const String16& name) {
- (void)name;
- return std::nullopt;
-}
-
-Vector<String16> ServiceManager::getUpdatableNames(const String16& apexName) {
- (void)apexName;
- return {};
-}
-
-std::optional<IServiceManager::ConnectionInfo> ServiceManager::getConnectionInfo(
- const String16& name) {
- (void)name;
- return std::nullopt;
-}
-
-status_t ServiceManager::registerForNotifications(const String16&,
- const sp<LocalRegistrationCallback>&) {
- return INVALID_OPERATION;
-}
-
-status_t ServiceManager::unregisterForNotifications(const String16&,
- const sp<LocalRegistrationCallback>&) {
- return INVALID_OPERATION;
-}
-
-std::vector<IServiceManager::ServiceDebugInfo> ServiceManager::getServiceDebugInfo() {
- std::vector<IServiceManager::ServiceDebugInfo> ret;
- return ret;
-}
-
-void ServiceManager::clear() {
- mNameToService.clear();
-}
-} // namespace android
diff --git a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
index ba6bb7d95b..f62241d9c2 100644
--- a/libs/fakeservicemanager/include/fakeservicemanager/ServiceManager.h
+++ b/libs/fakeservicemanager/include/fakeservicemanager/FakeServiceManager.h
@@ -19,6 +19,7 @@
#include <binder/IServiceManager.h>
#include <map>
+#include <mutex>
#include <optional>
#include <vector>
@@ -28,9 +29,9 @@ namespace android {
* A local host simple implementation of IServiceManager, that does not
* communicate over binder.
*/
-class ServiceManager : public IServiceManager {
+class FakeServiceManager : public IServiceManager {
public:
- ServiceManager();
+ FakeServiceManager();
sp<IBinder> getService( const String16& name) const override;
@@ -68,6 +69,7 @@ public:
void clear();
private:
+ mutable std::mutex mMutex;
std::map<String16, sp<IBinder>> mNameToService;
};
diff --git a/libs/fakeservicemanager/rust/src/lib.rs b/libs/fakeservicemanager/rust/src/lib.rs
new file mode 100644
index 0000000000..5b7e756914
--- /dev/null
+++ b/libs/fakeservicemanager/rust/src/lib.rs
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+use fakeservicemanager_bindgen::{clearFakeServiceManager, setupFakeServiceManager};
+// Setup FakeServiceManager for testing and fuzzing purposes
+pub fn setup_fake_service_manager() {
+ unsafe {
+ // Safety: This API creates a new FakeSm object which will be always valid and sets up
+ // defaultServiceManager
+ setupFakeServiceManager();
+ }
+}
+
+// Setup FakeServiceManager for testing and fuzzing purposes
+pub fn clear_fake_service_manager() {
+ unsafe {
+ // Safety: This API clears all registered services with Fake SM. This should be only used
+ // setupFakeServiceManager is already called.
+ clearFakeServiceManager();
+ }
+}
diff --git a/libs/fakeservicemanager/rust/wrappers/FakeServiceManagerWrapper.hpp b/libs/fakeservicemanager/rust/wrappers/FakeServiceManagerWrapper.hpp
new file mode 100644
index 0000000000..1f5923afba
--- /dev/null
+++ b/libs/fakeservicemanager/rust/wrappers/FakeServiceManagerWrapper.hpp
@@ -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.
+ */
+
+#include "fakeservicemanager/FakeServiceManager.h"
+
+extern "C" {
+ // Setup FakeServiceManager to mock dependencies in test using this API
+ void setupFakeServiceManager();
+
+ // Clear existing services from Fake SM.
+ void clearFakeServiceManager();
+} // extern "C"
diff --git a/libs/fakeservicemanager/test_sm.cpp b/libs/fakeservicemanager/test_sm.cpp
index 8682c1c795..cb6784c04a 100644
--- a/libs/fakeservicemanager/test_sm.cpp
+++ b/libs/fakeservicemanager/test_sm.cpp
@@ -21,16 +21,18 @@
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
-#include "ServiceManager.h"
+#include "fakeservicemanager/FakeServiceManager.h"
+#include "rust/wrappers/FakeServiceManagerWrapper.hpp"
using android::sp;
using android::BBinder;
using android::IBinder;
using android::OK;
using android::status_t;
-using android::ServiceManager;
+using android::FakeServiceManager;
using android::String16;
using android::IServiceManager;
+using android::defaultServiceManager;
using testing::ElementsAre;
static sp<IBinder> getBinder() {
@@ -45,19 +47,19 @@ static sp<IBinder> getBinder() {
}
TEST(AddService, HappyHappy) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
}
TEST(AddService, SadNullBinder) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->addService(String16("foo"), nullptr, false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), android::UNEXPECTED_NULL);
}
TEST(AddService, HappyOverExistingService) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
@@ -65,7 +67,7 @@ TEST(AddService, HappyOverExistingService) {
}
TEST(AddService, HappyClearAddedService) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->addService(String16("foo"), getBinder(), false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
EXPECT_NE(sm->getService(String16("foo")), nullptr);
@@ -74,7 +76,7 @@ TEST(AddService, HappyClearAddedService) {
}
TEST(GetService, HappyHappy) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
sp<IBinder> service = getBinder();
EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/,
@@ -83,14 +85,14 @@ TEST(GetService, HappyHappy) {
EXPECT_EQ(sm->getService(String16("foo")), service);
}
-TEST(GetService, NonExistant) {
- auto sm = new ServiceManager();
+TEST(GetService, NonExistent) {
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->getService(String16("foo")), nullptr);
}
TEST(ListServices, AllServices) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->addService(String16("sd"), getBinder(), false /*allowIsolated*/,
IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
@@ -108,14 +110,14 @@ TEST(ListServices, AllServices) {
String16("sd")));
}
-TEST(WaitForService, NonExistant) {
- auto sm = new ServiceManager();
+TEST(WaitForService, NonExistent) {
+ auto sm = new FakeServiceManager();
EXPECT_EQ(sm->waitForService(String16("foo")), nullptr);
}
TEST(WaitForService, HappyHappy) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
sp<IBinder> service = getBinder();
EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/,
@@ -124,14 +126,14 @@ TEST(WaitForService, HappyHappy) {
EXPECT_EQ(sm->waitForService(String16("foo")), service);
}
-TEST(IsDeclared, NonExistant) {
- auto sm = new ServiceManager();
+TEST(IsDeclared, NonExistent) {
+ auto sm = new FakeServiceManager();
EXPECT_FALSE(sm->isDeclared(String16("foo")));
}
TEST(IsDeclared, HappyHappy) {
- auto sm = new ServiceManager();
+ auto sm = new FakeServiceManager();
sp<IBinder> service = getBinder();
EXPECT_EQ(sm->addService(String16("foo"), service, false /*allowIsolated*/,
@@ -139,3 +141,31 @@ TEST(IsDeclared, HappyHappy) {
EXPECT_TRUE(sm->isDeclared(String16("foo")));
}
+
+TEST(SetupFakeServiceManager, NonExistent) {
+ setupFakeServiceManager();
+
+ EXPECT_EQ(defaultServiceManager()->getService(String16("foo")), nullptr);
+}
+
+TEST(SetupFakeServiceManager, GetExistingService) {
+ setupFakeServiceManager();
+ sp<IBinder> service = getBinder();
+
+ EXPECT_EQ(defaultServiceManager()->addService(String16("foo"), service, false /*allowIsolated*/,
+ IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
+
+ EXPECT_EQ(defaultServiceManager()->getService(String16("foo")), service);
+ clearFakeServiceManager();
+}
+
+TEST(ClearFakeServiceManager, GetServiceAfterClear) {
+ setupFakeServiceManager();
+
+ sp<IBinder> service = getBinder();
+ EXPECT_EQ(defaultServiceManager()->addService(String16("foo"), service, false /*allowIsolated*/,
+ IServiceManager::DUMP_FLAG_PRIORITY_DEFAULT), OK);
+
+ clearFakeServiceManager();
+ EXPECT_EQ(defaultServiceManager()->getService(String16("foo")), nullptr);
+} \ No newline at end of file
diff --git a/libs/ftl/Android.bp b/libs/ftl/Android.bp
index c010a2e58a..918680d6a7 100644
--- a/libs/ftl/Android.bp
+++ b/libs/ftl/Android.bp
@@ -10,16 +10,20 @@ package {
cc_test {
name: "ftl_test",
test_suites: ["device-tests"],
- sanitize: {
- address: true,
- },
srcs: [
+ "algorithm_test.cpp",
"cast_test.cpp",
"concat_test.cpp",
"enum_test.cpp",
"fake_guard_test.cpp",
"flags_test.cpp",
+ "function_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/OWNERS b/libs/ftl/OWNERS
new file mode 100644
index 0000000000..3f6129226a
--- /dev/null
+++ b/libs/ftl/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/native:/services/surfaceflinger/OWNERS \ No newline at end of file
diff --git a/libs/ftl/TEST_MAPPING b/libs/ftl/TEST_MAPPING
new file mode 100644
index 0000000000..ec0c671213
--- /dev/null
+++ b/libs/ftl/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "ftl_test"
+ }
+ ],
+ "hwasan-presubmit": [
+ {
+ "name": "ftl_test"
+ }
+ ]
+}
diff --git a/libs/ftl/algorithm_test.cpp b/libs/ftl/algorithm_test.cpp
new file mode 100644
index 0000000000..487b1b8759
--- /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 <ftl/algorithm.h>
+#include <ftl/small_map.h>
+#include <ftl/static_vector.h>
+#include <gtest/gtest.h>
+
+#include <string_view>
+
+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<int, ftl::StaticVector<std::string_view, 3>>(
+ 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<Map>));
+
+ const auto opt = ftl::find_if(map, [](const auto& pair) {
+ return pair.second.size() == 1;
+ }).transform(ftl::to_mapped_ref<Map>);
+
+ 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<std::string_view>([] { 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<Map>)
+ .or_else(ftl::static_ref<std::string_view>([] { return "snow cone"sv; }))
+ ->get());
+}
+
+} // namespace android::test
diff --git a/libs/ftl/concat_test.cpp b/libs/ftl/concat_test.cpp
index 8ecb1b252d..771f05478a 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/enum_test.cpp b/libs/ftl/enum_test.cpp
index 5592a01fde..b68c2c3d02 100644
--- a/libs/ftl/enum_test.cpp
+++ b/libs/ftl/enum_test.cpp
@@ -33,6 +33,11 @@ static_assert(ftl::enum_name<E::ftl_last>() == "F");
static_assert(ftl::enum_name(E::C).value_or("?") == "C");
static_assert(ftl::enum_name(E{3}).value_or("?") == "?");
+static_assert(ftl::enum_name_full<E::B>() == "E::B");
+static_assert(ftl::enum_name_full<E::ftl_last>() == "E::F");
+static_assert(ftl::enum_name_full(E::C).value_or("?") == "E::C");
+static_assert(ftl::enum_name_full(E{3}).value_or("?") == "?");
+
enum class F : std::uint16_t { X = 0b1, Y = 0b10, Z = 0b100 };
static_assert(ftl::enum_begin_v<F> == F{0});
@@ -60,6 +65,10 @@ static_assert(ftl::enum_name<Flags::kNone>() == "kNone");
static_assert(ftl::enum_name<Flags::kFlag4>() == "kFlag4");
static_assert(ftl::enum_name<Flags::kFlag7>() == "kFlag7");
+static_assert(ftl::enum_name_full<Flags::kNone>() == "Flags::kNone");
+static_assert(ftl::enum_name_full<Flags::kFlag4>() == "Flags::kFlag4");
+static_assert(ftl::enum_name_full<Flags::kFlag7>() == "Flags::kFlag7");
+
// Though not flags, the enumerators are within the implicit range of bit indices.
enum class Planet : std::uint8_t {
kMercury,
@@ -81,6 +90,9 @@ static_assert(ftl::enum_size_v<Planet> == 8);
static_assert(ftl::enum_name<Planet::kMercury>() == "kMercury");
static_assert(ftl::enum_name<Planet::kSaturn>() == "kSaturn");
+static_assert(ftl::enum_name_full<Planet::kMercury>() == "Planet::kMercury");
+static_assert(ftl::enum_name_full<Planet::kSaturn>() == "Planet::kSaturn");
+
// Unscoped enum must define explicit range, even if the underlying type is fixed.
enum Temperature : int {
kRoom = 20,
@@ -122,16 +134,28 @@ TEST(Enum, Name) {
EXPECT_EQ(ftl::enum_name(Planet::kEarth), "kEarth");
EXPECT_EQ(ftl::enum_name(Planet::kNeptune), "kNeptune");
+ EXPECT_EQ(ftl::enum_name_full(Planet::kEarth), "Planet::kEarth");
+ EXPECT_EQ(ftl::enum_name_full(Planet::kNeptune), "Planet::kNeptune");
+
EXPECT_EQ(ftl::enum_name(kPluto), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(kPluto), std::nullopt);
}
{
EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+ EXPECT_EQ(ftl::enum_name(kRoom), "kRoom");
+ EXPECT_EQ(ftl::enum_name(kFridge), "kFridge");
+ EXPECT_EQ(ftl::enum_name(kFreezer), "kFreezer");
+
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(-30)), std::nullopt);
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(0)), std::nullopt);
EXPECT_EQ(ftl::enum_name(static_cast<Temperature>(100)), std::nullopt);
+
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(-30)), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(0)), std::nullopt);
+ EXPECT_EQ(ftl::enum_name_full(static_cast<Temperature>(100)), std::nullopt);
}
}
@@ -158,16 +182,30 @@ TEST(Enum, String) {
EXPECT_EQ(ftl::enum_string(Planet::kEarth), "kEarth");
EXPECT_EQ(ftl::enum_string(Planet::kNeptune), "kNeptune");
+ EXPECT_EQ(ftl::enum_string_full(Planet::kEarth), "Planet::kEarth");
+ EXPECT_EQ(ftl::enum_string_full(Planet::kNeptune), "Planet::kNeptune");
+
EXPECT_EQ(ftl::enum_string(kPluto), "8");
+
+ EXPECT_EQ(ftl::enum_string_full(kPluto), "8");
+
}
{
EXPECT_EQ(ftl::enum_string(kRoom), "kRoom");
EXPECT_EQ(ftl::enum_string(kFridge), "kFridge");
EXPECT_EQ(ftl::enum_string(kFreezer), "kFreezer");
+ EXPECT_EQ(ftl::enum_string_full(kRoom), "20");
+ EXPECT_EQ(ftl::enum_string_full(kFridge), "4");
+ EXPECT_EQ(ftl::enum_string_full(kFreezer), "-18");
+
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(-30)), "-30");
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(0)), "0");
EXPECT_EQ(ftl::enum_string(static_cast<Temperature>(100)), "100");
+
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(-30)), "-30");
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(0)), "0");
+ EXPECT_EQ(ftl::enum_string_full(static_cast<Temperature>(100)), "100");
}
}
diff --git a/libs/ftl/flags_test.cpp b/libs/ftl/flags_test.cpp
index eea052ba33..1279d1147d 100644
--- a/libs/ftl/flags_test.cpp
+++ b/libs/ftl/flags_test.cpp
@@ -35,6 +35,7 @@ TEST(Flags, Test) {
TEST(Flags, Any) {
Flags<TestFlags> 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<TestFlags> emptyFlags;
+ ASSERT_FALSE(emptyFlags.any());
}
TEST(Flags, All) {
diff --git a/libs/ftl/function_test.cpp b/libs/ftl/function_test.cpp
new file mode 100644
index 0000000000..91b5e08041
--- /dev/null
+++ b/libs/ftl/function_test.cpp
@@ -0,0 +1,379 @@
+/*
+ * 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 <ftl/function.h>
+#include <gtest/gtest.h>
+
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <string_view>
+#include <type_traits>
+
+namespace android::test {
+namespace {
+
+// Create an alias to composite requirements defined by the trait class `T` for easier testing.
+template <typename T, typename S>
+inline constexpr bool is_opaquely_storable = (T::template require_trivially_copyable<S> &&
+ T::template require_trivially_destructible<S> &&
+ T::template require_will_fit_in_opaque_storage<S> &&
+ T::template require_alignment_compatible<S>);
+
+// `I` gives a count of sizeof(std::intptr_t) bytes , and `J` gives a raw count of bytes
+template <size_t I, size_t J = 0>
+struct KnownSizeFunctionObject {
+ using Data = std::array<std::byte, sizeof(std::intptr_t) * I + J>;
+ void operator()() const {};
+ Data data{};
+};
+
+} // namespace
+
+// static_assert the expected type traits
+static_assert(std::is_invocable_r_v<void, ftl::Function<void()>>);
+static_assert(std::is_trivially_copyable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_destructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_constructible_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_copy_assignable_v<ftl::Function<void()>>);
+static_assert(std::is_trivially_move_assignable_v<ftl::Function<void()>>);
+
+template <typename T>
+using function_traits = ftl::details::function_traits<T>;
+
+// static_assert that the expected value of N is used for known function object sizes.
+static_assert(function_traits<KnownSizeFunctionObject<0, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<0, 1>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 0>>::size == 0);
+static_assert(function_traits<KnownSizeFunctionObject<1, 1>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 0>>::size == 1);
+static_assert(function_traits<KnownSizeFunctionObject<2, 1>>::size == 2);
+
+// Check that is_function_v works
+static_assert(!ftl::is_function_v<KnownSizeFunctionObject<0>>);
+static_assert(!ftl::is_function_v<std::function<void()>>);
+static_assert(ftl::is_function_v<ftl::Function<void()>>);
+
+// static_assert what can and cannot be stored inside the opaque storage
+
+template <size_t N>
+using function_opaque_storage = ftl::details::function_opaque_storage<N>;
+
+// Function objects can be stored if they fit.
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<0>>);
+static_assert(is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<1>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<0>, KnownSizeFunctionObject<2>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<2>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<1>, KnownSizeFunctionObject<3>>);
+
+static_assert(is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<3>>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, KnownSizeFunctionObject<4>>);
+
+// Another opaque storage can be stored if it fits. This property is used to copy smaller
+// ftl::Functions into larger ones.
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<0>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<1>::type>);
+static_assert(is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<2>::type>);
+static_assert(!is_opaquely_storable<function_opaque_storage<2>, function_opaque_storage<3>::type>);
+
+// Function objects that aren't trivially copyable or destroyable cannot be stored.
+auto lambda_capturing_unique_ptr = [ptr = std::unique_ptr<void*>()] { static_cast<void>(ptr); };
+static_assert(
+ !is_opaquely_storable<function_opaque_storage<2>, decltype(lambda_capturing_unique_ptr)>);
+
+// Keep in sync with "Example usage" in header file.
+TEST(Function, Example) {
+ using namespace std::string_view_literals;
+
+ class MyClass {
+ public:
+ void on_event() const {}
+ int on_string(int*, std::string_view) { return 1; }
+
+ auto get_function() {
+ return ftl::make_function([this] { on_event(); });
+ }
+ } cls;
+
+ // A function container with no arguments, and returning no value.
+ ftl::Function<void()> f;
+
+ // Construct a ftl::Function containing a small lambda.
+ f = cls.get_function();
+
+ // Construct a ftl::Function that calls `cls.on_event()`.
+ f = ftl::make_function<&MyClass::on_event>(&cls);
+
+ // Create a do-nothing function.
+ f = ftl::no_op;
+
+ // Invoke the contained function.
+ f();
+
+ // Also invokes it.
+ std::invoke(f);
+
+ // Create a typedef to give a more meaningful name and bound the size.
+ using MyFunction = ftl::Function<int(std::string_view), 2>;
+ int* ptr = nullptr;
+ auto f1 =
+ MyFunction::make([cls = &cls, ptr](std::string_view sv) { return cls->on_string(ptr, sv); });
+ int r = f1("abc"sv);
+
+ // Returns a default-constructed int (0).
+ f1 = ftl::no_op;
+ r = f1("abc"sv);
+ EXPECT_EQ(r, 0);
+}
+
+TEST(Function, BasicOperations) {
+ // Default constructible.
+ ftl::Function<int()> f;
+
+ // Compares as empty
+ EXPECT_FALSE(f);
+ EXPECT_TRUE(f == nullptr);
+ EXPECT_FALSE(f != nullptr);
+ EXPECT_TRUE(ftl::Function<int()>() == f);
+ EXPECT_FALSE(ftl::Function<int()>() != f);
+
+ // Assigning no_op sets it to not empty.
+ f = ftl::no_op;
+
+ // Verify it can be called, and that it returns a default constructed value.
+ EXPECT_EQ(f(), 0);
+
+ // Comparable when non-empty.
+ EXPECT_TRUE(f);
+ EXPECT_FALSE(f == nullptr);
+ EXPECT_TRUE(f != nullptr);
+ EXPECT_FALSE(ftl::Function<int()>() == f);
+ EXPECT_TRUE(ftl::Function<int()>() != f);
+
+ // Constructing from nullptr means empty.
+ f = ftl::Function<int()>{nullptr};
+ EXPECT_FALSE(f);
+
+ // Assigning nullptr means it is empty.
+ f = nullptr;
+ EXPECT_FALSE(f);
+
+ // Move construction
+ f = ftl::no_op;
+ ftl::Function<int()> g{std::move(f)};
+ EXPECT_TRUE(g != nullptr);
+
+ // Move assignment
+ f = nullptr;
+ f = std::move(g);
+ EXPECT_TRUE(f != nullptr);
+
+ // Copy construction
+ ftl::Function<int()> h{f};
+ EXPECT_TRUE(h != nullptr);
+
+ // Copy assignment
+ g = h;
+ EXPECT_TRUE(g != nullptr);
+}
+
+TEST(Function, CanMoveConstructFromLambda) {
+ auto lambda = [] {};
+ ftl::Function<void()> f{std::move(lambda)};
+}
+
+TEST(Function, TerseDeducedConstructAndAssignFromLambda) {
+ auto f = ftl::Function([] { return 1; });
+ EXPECT_EQ(f(), 1);
+
+ f = [] { return 2; };
+ EXPECT_EQ(f(), 2);
+}
+
+namespace {
+
+struct ImplicitConversionsHelper {
+ auto exact(int) -> int { return 0; }
+ auto inexact(long) -> short { return 0; }
+ // TODO: Switch to `auto templated(auto x)` with C++20
+ template <typename T>
+ T templated(T x) {
+ return x;
+ }
+
+ static auto static_exact(int) -> int { return 0; }
+ static auto static_inexact(long) -> short { return 0; }
+ // TODO: Switch to `static auto static_templated(auto x)` with C++20
+ template <typename T>
+ static T static_templated(T x) {
+ return x;
+ }
+};
+
+} // namespace
+
+TEST(Function, ImplicitConversions) {
+ using Function = ftl::Function<int(int)>;
+ auto check = [](Function f) { return f(0); };
+ auto exact = [](int) -> int { return 0; };
+ auto inexact = [](long) -> short { return 0; };
+ auto templated = [](auto x) { return x; };
+
+ ImplicitConversionsHelper helper;
+
+ // Note, `check(nullptr)` would crash, so we can only check if it would be invocable.
+ static_assert(std::is_invocable_v<decltype(check), decltype(nullptr)>);
+
+ // Note: We invoke each of these to fully expand all the templates involved.
+ EXPECT_EQ(check(ftl::no_op), 0);
+
+ EXPECT_EQ(check(exact), 0);
+ EXPECT_EQ(check(inexact), 0);
+ EXPECT_EQ(check(templated), 0);
+
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::exact>(&helper)), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::inexact>(&helper)), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::templated<int>>(&helper)), 0);
+
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_exact>()), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_inexact>()), 0);
+ EXPECT_EQ(check(Function::make<&ImplicitConversionsHelper::static_templated<int>>()), 0);
+}
+
+TEST(Function, MakeWithNonConstMemberFunction) {
+ struct Observer {
+ bool called = false;
+ void setCalled() { called = true; }
+ } observer;
+
+ auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+ f();
+
+ EXPECT_TRUE(observer.called);
+
+ EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstMemberFunction) {
+ struct Observer {
+ mutable bool called = false;
+ void setCalled() const { called = true; }
+ } observer;
+
+ const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+ f();
+
+ EXPECT_TRUE(observer.called);
+
+ EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithConstClassPointer) {
+ const struct Observer {
+ mutable bool called = false;
+ void setCalled() const { called = true; }
+ } observer;
+
+ const auto f = ftl::make_function<&Observer::setCalled>(&observer);
+
+ f();
+
+ EXPECT_TRUE(observer.called);
+
+ EXPECT_TRUE(f == ftl::Function<void()>::make<&Observer::setCalled>(&observer));
+}
+
+TEST(Function, MakeWithNonCapturingLambda) {
+ auto f = ftl::make_function([](int a, int b) { return a + b; });
+ EXPECT_EQ(f(1, 2), 3);
+}
+
+TEST(Function, MakeWithCapturingLambda) {
+ bool called = false;
+ auto f = ftl::make_function([&called](int a, int b) {
+ called = true;
+ return a + b;
+ });
+ EXPECT_EQ(f(1, 2), 3);
+ EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithCapturingMutableLambda) {
+ bool called = false;
+ auto f = ftl::make_function([&called](int a, int b) mutable {
+ called = true;
+ return a + b;
+ });
+ EXPECT_EQ(f(1, 2), 3);
+ EXPECT_TRUE(called);
+}
+
+TEST(Function, MakeWithThreePointerCapturingLambda) {
+ bool my_bool = false;
+ int my_int = 0;
+ float my_float = 0.f;
+
+ auto f = ftl::make_function(
+ [ptr_bool = &my_bool, ptr_int = &my_int, ptr_float = &my_float](int a, int b) mutable {
+ *ptr_bool = true;
+ *ptr_int = 1;
+ *ptr_float = 1.f;
+
+ return a + b;
+ });
+
+ EXPECT_EQ(f(1, 2), 3);
+
+ EXPECT_TRUE(my_bool);
+ EXPECT_EQ(my_int, 1);
+ EXPECT_EQ(my_float, 1.f);
+}
+
+TEST(Function, MakeWithFreeFunction) {
+ auto f = ftl::make_function<&std::make_unique<int, int>>();
+ std::unique_ptr<int> unique_int = f(1);
+ ASSERT_TRUE(unique_int);
+ EXPECT_EQ(*unique_int, 1);
+}
+
+TEST(Function, CopyToLarger) {
+ int counter = 0;
+ ftl::Function<void()> a{[ptr_counter = &counter] { (*ptr_counter)++; }};
+ ftl::Function<void(), 1> b = a;
+ ftl::Function<void(), 2> c = a;
+
+ EXPECT_EQ(counter, 0);
+ a();
+ EXPECT_EQ(counter, 1);
+ b();
+ EXPECT_EQ(counter, 2);
+ c();
+ EXPECT_EQ(counter, 3);
+
+ b = [ptr_counter = &counter] { (*ptr_counter) += 2; };
+ c = [ptr_counter = &counter] { (*ptr_counter) += 3; };
+
+ b();
+ EXPECT_EQ(counter, 5);
+ c();
+ EXPECT_EQ(counter, 8);
+}
+
+} // namespace android::test
diff --git a/libs/ftl/match_test.cpp b/libs/ftl/match_test.cpp
new file mode 100644
index 0000000000..a6cff2eed6
--- /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 <ftl/match.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <string>
+#include <variant>
+
+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<seconds, minutes, hours> 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<hours>(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 0000000000..2c9f9dfd8a
--- /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 <ftl/mixins.h>
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+namespace android::test {
+namespace {
+
+// Keep in sync with example usage in header file.
+
+struct Id : ftl::Constructible<Id, std::int32_t>, ftl::Equatable<Id> {
+ using Constructible::Constructible;
+};
+
+static_assert(!std::is_default_constructible_v<Id>);
+
+struct Color : ftl::DefaultConstructible<Color, std::uint8_t>,
+ ftl::Equatable<Color>,
+ ftl::Orderable<Color> {
+ 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<Sequence, std::int8_t, -1>,
+ ftl::Equatable<Sequence>,
+ ftl::Orderable<Sequence>,
+ ftl::Incrementable<Sequence> {
+ using DefaultConstructible::DefaultConstructible;
+};
+
+static_assert(Sequence() == Sequence(-1));
+
+struct Timeout : ftl::DefaultConstructible<Timeout, std::chrono::seconds, 10>,
+ ftl::Equatable<Timeout>,
+ ftl::Addable<Timeout> {
+ 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<std::int32_t>(Id(-1)) == -1);
+static_assert(static_cast<std::int8_t>(kSequence) == -1);
+
+static_assert(!std::is_convertible_v<std::int32_t, Id>);
+static_assert(!std::is_convertible_v<Id, std::int32_t>);
+
+// 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 <typename Op, typename T, typename... Ts>
+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 <typename, typename = void>
+struct is_incrementable : std::false_type {};
+
+template <typename T>
+struct is_incrementable<T, std::void_t<decltype(++std::declval<T&>())>> : std::true_type {};
+
+template <typename T>
+constexpr bool is_incrementable_v = is_incrementable<T>{};
+
+template <typename, typename, typename, typename = void>
+struct has_binary_op : std::false_type {};
+
+template <typename Op, typename T, typename U>
+struct has_binary_op<Op, T, U, std::void_t<decltype(Op{}(std::declval<T&>(), std::declval<U&>()))>>
+ : std::true_type {};
+
+template <typename T, typename U>
+constexpr bool is_equatable_v =
+ has_binary_op<std::equal_to<void>, T, U>{} && has_binary_op<std::not_equal_to<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_orderable_v =
+ has_binary_op<std::less<void>, T, U>{} && has_binary_op<std::less_equal<void>, T, U>{} &&
+ has_binary_op<std::greater<void>, T, U>{} && has_binary_op<std::greater_equal<void>, T, U>{};
+
+template <typename T, typename U>
+constexpr bool is_addable_v = has_binary_op<std::plus<void>, T, U>{};
+
+} // namespace traits
+
+struct Real : ftl::Constructible<Real, float> {
+ using Constructible::Constructible;
+};
+
+static_assert(traits::is_equatable_v<Id, Id>);
+static_assert(!traits::is_equatable_v<Real, Real>);
+static_assert(!traits::is_equatable_v<Id, Color>);
+static_assert(!traits::is_equatable_v<Sequence, Id>);
+static_assert(!traits::is_equatable_v<Id, std::int32_t>);
+static_assert(!traits::is_equatable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_orderable_v<Color, Color>);
+static_assert(!traits::is_orderable_v<Id, Id>);
+static_assert(!traits::is_orderable_v<Real, Real>);
+static_assert(!traits::is_orderable_v<Color, Sequence>);
+static_assert(!traits::is_orderable_v<Color, std::uint8_t>);
+static_assert(!traits::is_orderable_v<std::chrono::seconds, Timeout>);
+
+static_assert(traits::is_incrementable_v<Sequence>);
+static_assert(traits::is_incrementable_v<Timeout>);
+static_assert(!traits::is_incrementable_v<Id>);
+static_assert(!traits::is_incrementable_v<Color>);
+static_assert(!traits::is_incrementable_v<Real>);
+
+static_assert(traits::is_addable_v<Timeout, Timeout>);
+static_assert(!traits::is_addable_v<Id, Id>);
+static_assert(!traits::is_addable_v<Real, Real>);
+static_assert(!traits::is_addable_v<Sequence, Sequence>);
+static_assert(!traits::is_addable_v<Timeout, Sequence>);
+static_assert(!traits::is_addable_v<Color, Timeout>);
+
+} // 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 0000000000..bd0462b3b6
--- /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 <ftl/non_null.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+#include <string>
+#include <string_view>
+
+namespace android::test {
+namespace {
+
+void get_length(const ftl::NonNull<std::shared_ptr<std::string>>& string_ptr,
+ ftl::NonNull<std::size_t*> length_ptr) {
+ // No need for `nullptr` checks.
+ *length_ptr = string_ptr->length();
+}
+
+using Pair = std::pair<ftl::NonNull<std::shared_ptr<int>>, std::shared_ptr<int>>;
+
+Pair dupe_if(ftl::NonNull<std::unique_ptr<int>> 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<int>(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<std::string>("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<int>(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<const std::string_view*>;
+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 0000000000..91bf7bc5b6
--- /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 <ftl/optional.h>
+#include <ftl/static_vector.h>
+#include <ftl/string.h>
+#include <ftl/unit.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <functional>
+#include <numeric>
+#include <utility>
+
+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<int>());
+ EXPECT_EQ(std::nullopt, Optional<std::string>(std::nullopt));
+
+ // Value.
+ EXPECT_EQ('?', Optional('?'));
+ EXPECT_EQ(""s, Optional(std::string()));
+
+ // In place.
+ EXPECT_EQ("???"s, Optional<std::string>(std::in_place, 3u, '?'));
+ EXPECT_EQ("abc"s, Optional<std::string>(std::in_place, {'a', 'b', 'c'}));
+
+ // Implicit downcast.
+ {
+ Optional opt = std::optional("test"s);
+ static_assert(std::is_same_v<decltype(opt), Optional<std::string>>);
+
+ ASSERT_TRUE(opt);
+ EXPECT_EQ(opt.value(), "test"s);
+ }
+}
+
+TEST(Optional, Transform) {
+ // Empty.
+ EXPECT_EQ(std::nullopt, Optional<int>().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<int>, _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<std::string, 3>&& v) {
+ v.push_back("cake"s);
+ return v;
+ })
+ .transform([](const StaticVector<std::string, 3>& v) {
+ return std::accumulate(v.begin(), v.end(), std::string());
+ })
+ .transform([](const std::string& s) { return s.length(); }));
+}
+
+namespace {
+
+Optional<int> 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<int>().and_then([](int) -> Optional<int> { return 0; }));
+ EXPECT_EQ(std::nullopt, Optional<int>().and_then([](int) { return Optional<int>(); }));
+
+ // 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<std::string, 3>;
+ EXPECT_EQ(14u, Optional(StaticVector{"-"s, "1"s})
+ .and_then([](StringVector&& v) -> Optional<StringVector> {
+ if (v.push_back("4"s)) return v;
+ return {};
+ })
+ .and_then([](const StringVector& v) -> Optional<std::string> {
+ 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<unsigned>(-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<int> opt;
+ EXPECT_EQ(123, opt.or_else([]() -> Optional<int> { return 123; }));
+ EXPECT_EQ("abc"s, Optional<std::string>().or_else([] { return Optional("abc"s); }));
+ }
+ {
+ bool empty = false;
+ EXPECT_EQ(Optional<float>(), Optional<float>().or_else([&empty]() -> Optional<float> {
+ empty = true;
+ return std::nullopt;
+ }));
+ EXPECT_TRUE(empty);
+ }
+
+ // Chaining.
+ using StringVector = StaticVector<std::string, 3>;
+ EXPECT_EQ(999, Optional(StaticVector{"1"s, "0"s, "0"s})
+ .and_then([](StringVector&& v) -> Optional<StringVector> {
+ 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<int> kOptional1 = 1;
+constexpr Optional<int> kAnotherOptional1 = 1;
+constexpr Optional<int> kOptional2 = 2;
+constexpr Optional<int> kOptionalEmpty, kAnotherOptionalEmpty;
+
+constexpr std::optional<int> 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 0000000000..6da7061ae0
--- /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 <ftl/shared_mutex.h>
+#include <gtest/gtest.h>
+#include <ftl/fake_guard.h>
+
+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 1740a2b54c..634877f672 100644
--- a/libs/ftl/small_map_test.cpp
+++ b/libs/ftl/small_map_test.cpp
@@ -15,12 +15,15 @@
*/
#include <ftl/small_map.h>
+#include <ftl/unit.h>
#include <gtest/gtest.h>
#include <cctype>
#include <string>
+#include <string_view>
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<char, std::string>('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 f5af425fca..6e2b803174 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",
@@ -36,10 +37,7 @@ cc_library {
},
vendor_available: true,
- vndk: {
- enabled: true,
- support_system_process: true,
- },
+ double_loadable: true,
apex_available: [
"//apex_available:platform",
"com.android.media.swcodec",
@@ -51,14 +49,13 @@ cc_library {
],
shared_libs: [
- "android.hardware.graphics.common-V4-ndk",
"android.hardware.graphics.mapper@4.0",
"libhidlbase",
"liblog",
],
export_shared_lib_headers: [
- "android.hardware.graphics.common-V4-ndk",
+ "android.hardware.graphics.common-V5-ndk",
"android.hardware.graphics.mapper@4.0",
"libhidlbase",
],
diff --git a/libs/gralloc/types/Gralloc4.cpp b/libs/gralloc/types/Gralloc4.cpp
index 61e6657621..ce35906949 100644
--- a/libs/gralloc/types/Gralloc4.cpp
+++ b/libs/gralloc/types/Gralloc4.cpp
@@ -1307,6 +1307,10 @@ std::string getChromaSitingName(const ExtendableType& chromaSiting) {
return "SitedInterstitial";
case ChromaSiting::COSITED_HORIZONTAL:
return "CositedHorizontal";
+ case ChromaSiting::COSITED_VERTICAL:
+ return "CositedVertical";
+ case ChromaSiting::COSITED_BOTH:
+ return "CositedBoth";
}
}
diff --git a/libs/graphicsenv/Android.bp b/libs/graphicsenv/Android.bp
index a96a07a9b8..af50a2980c 100644
--- a/libs/graphicsenv/Android.bp
+++ b/libs/graphicsenv/Android.bp
@@ -27,10 +27,13 @@ cc_library_shared {
srcs: [
"GpuStatsInfo.cpp",
"GraphicsEnv.cpp",
- "IGpuService.cpp"
+ "IGpuService.cpp",
],
- cflags: ["-Wall", "-Werror"],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
shared_libs: [
"libbase",
@@ -46,4 +49,13 @@ cc_library_shared {
],
export_include_dirs: ["include"],
+
+ product_variables: {
+ // `debuggable` is set for eng and userdebug builds
+ debuggable: {
+ cflags: [
+ "-DANDROID_DEBUGGABLE",
+ ],
+ },
+ },
}
diff --git a/libs/graphicsenv/GpuStatsInfo.cpp b/libs/graphicsenv/GpuStatsInfo.cpp
index 858739c9dd..7b7421424d 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 4a0a839948..50c05f4e5b 100644
--- a/libs/graphicsenv/GraphicsEnv.cpp
+++ b/libs/graphicsenv/GraphicsEnv.cpp
@@ -60,6 +60,16 @@ typedef bool (*fpANGLEShouldBeUsedForApplication)(void* rulesHandle, int rulesVe
typedef bool (*fpANGLEFreeRulesHandle)(void* handle);
typedef bool (*fpANGLEFreeSystemInfoHandle)(void* handle);
+namespace {
+static bool isVndkEnabled() {
+#ifdef __BIONIC__
+ static bool isVndkEnabled = android::base::GetProperty("ro.vndk.version", "") != "";
+ return isVndkEnabled;
+#endif
+ return false;
+}
+} // namespace
+
namespace android {
enum NativeLibrary {
@@ -71,6 +81,8 @@ static constexpr const char* kNativeLibrariesSystemConfigPath[] =
{"/apex/com.android.vndk.v{}/etc/llndk.libraries.{}.txt",
"/apex/com.android.vndk.v{}/etc/vndksp.libraries.{}.txt"};
+static const char* kLlndkLibrariesTxtPath = "/system/etc/llndk.libraries.txt";
+
static std::string vndkVersionStr() {
#ifdef __BIONIC__
return base::GetProperty("ro.vndk.version", "");
@@ -108,8 +120,14 @@ static bool readConfig(const std::string& configFile, std::vector<std::string>*
}
static const std::string getSystemNativeLibraries(NativeLibrary type) {
- std::string nativeLibrariesSystemConfig = kNativeLibrariesSystemConfigPath[type];
- insertVndkVersionStr(&nativeLibrariesSystemConfig);
+ std::string nativeLibrariesSystemConfig = "";
+
+ if (!isVndkEnabled() && type == NativeLibrary::LLNDK) {
+ nativeLibrariesSystemConfig = kLlndkLibrariesTxtPath;
+ } else {
+ nativeLibrariesSystemConfig = kNativeLibrariesSystemConfigPath[type];
+ insertVndkVersionStr(&nativeLibrariesSystemConfig);
+ }
std::vector<std::string> soNames;
if (!readConfig(nativeLibrariesSystemConfig, &soNames)) {
@@ -120,17 +138,44 @@ static const std::string getSystemNativeLibraries(NativeLibrary type) {
return base::Join(soNames, ':');
}
+static sp<IGpuService> getGpuService() {
+ static const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu"));
+ if (!binder) {
+ ALOGE("Failed to get gpu service");
+ return nullptr;
+ }
+
+ return interface_cast<IGpuService>(binder);
+}
+
/*static*/ GraphicsEnv& GraphicsEnv::getInstance() {
static GraphicsEnv env;
return env;
}
bool GraphicsEnv::isDebuggable() {
- return prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+ // This flag determines if the application is marked debuggable
+ bool appDebuggable = prctl(PR_GET_DUMPABLE, 0, 0, 0, 0) > 0;
+
+ // This flag is set only in `debuggable` builds of the platform
+#if defined(ANDROID_DEBUGGABLE)
+ bool platformDebuggable = true;
+#else
+ bool platformDebuggable = false;
+#endif
+
+ ALOGV("GraphicsEnv::isDebuggable returning appDebuggable=%s || platformDebuggable=%s",
+ appDebuggable ? "true" : "false", platformDebuggable ? "true" : "false");
+
+ return appDebuggable || platformDebuggable;
}
-void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
- const std::string sphalLibraries) {
+/**
+ * APIs for updatable graphics drivers
+ */
+
+void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string& path,
+ const std::string& sphalLibraries) {
if (!mDriverPath.empty() || !mSphalLibraries.empty()) {
ALOGV("ignoring attempt to change driver path from '%s' to '%s' or change sphal libraries "
"from '%s' to '%s'",
@@ -143,6 +188,109 @@ void GraphicsEnv::setDriverPathAndSphalLibraries(const std::string path,
mSphalLibraries = sphalLibraries;
}
+// Return true if all the required libraries from vndk and sphal namespace are
+// linked to the driver namespace correctly.
+bool GraphicsEnv::linkDriverNamespaceLocked(android_namespace_t* destNamespace,
+ android_namespace_t* vndkNamespace,
+ const std::string& sharedSphalLibraries) {
+ const std::string llndkLibraries = getSystemNativeLibraries(NativeLibrary::LLNDK);
+ if (llndkLibraries.empty()) {
+ return false;
+ }
+ if (!android_link_namespaces(destNamespace, nullptr, llndkLibraries.c_str())) {
+ ALOGE("Failed to link default namespace[%s]", dlerror());
+ return false;
+ }
+
+ const std::string vndkspLibraries = getSystemNativeLibraries(NativeLibrary::VNDKSP);
+ if (vndkspLibraries.empty()) {
+ return false;
+ }
+ if (!android_link_namespaces(destNamespace, vndkNamespace, vndkspLibraries.c_str())) {
+ ALOGE("Failed to link vndk namespace[%s]", dlerror());
+ return false;
+ }
+
+ if (sharedSphalLibraries.empty()) {
+ return true;
+ }
+
+ // Make additional libraries in sphal to be accessible
+ auto sphalNamespace = android_get_exported_namespace("sphal");
+ if (!sphalNamespace) {
+ ALOGE("Depend on these libraries[%s] in sphal, but failed to get sphal namespace",
+ sharedSphalLibraries.c_str());
+ return false;
+ }
+
+ if (!android_link_namespaces(destNamespace, sphalNamespace, sharedSphalLibraries.c_str())) {
+ ALOGE("Failed to link sphal namespace[%s]", dlerror());
+ return false;
+ }
+
+ return true;
+}
+
+android_namespace_t* GraphicsEnv::getDriverNamespace() {
+ std::lock_guard<std::mutex> lock(mNamespaceMutex);
+
+ if (mDriverNamespace) {
+ return mDriverNamespace;
+ }
+
+ if (mDriverPath.empty()) {
+ // For an application process, driver path is empty means this application is not opted in
+ // to use updatable driver. Application process doesn't have the ability to set up
+ // environment variables and hence before `getenv` call will return.
+ // For a process that is not an application process, if it's run from an environment,
+ // for example shell, where environment variables can be set, then it can opt into using
+ // udpatable driver by setting UPDATABLE_GFX_DRIVER to 1. By setting to 1 the developer
+ // driver will be used currently.
+ // TODO(b/159240322) Support the production updatable driver.
+ const char* id = getenv("UPDATABLE_GFX_DRIVER");
+ if (id == nullptr || std::strcmp(id, "1") != 0) {
+ return nullptr;
+ }
+ const sp<IGpuService> gpuService = getGpuService();
+ if (!gpuService) {
+ return nullptr;
+ }
+ mDriverPath = gpuService->getUpdatableDriverPath();
+ if (mDriverPath.empty()) {
+ return nullptr;
+ }
+ mDriverPath.append(UPDATABLE_DRIVER_ABI);
+ ALOGI("Driver path is setup via UPDATABLE_GFX_DRIVER: %s", mDriverPath.c_str());
+ }
+
+ auto vndkNamespace = android_get_exported_namespace("vndk");
+ if (!vndkNamespace) {
+ mDriverNamespace = nullptr;
+ return mDriverNamespace;
+ }
+
+ mDriverNamespace = android_create_namespace("updatable gfx driver",
+ mDriverPath.c_str(), // ld_library_path
+ mDriverPath.c_str(), // default_library_path
+ ANDROID_NAMESPACE_TYPE_ISOLATED,
+ nullptr, // permitted_when_isolated_path
+ nullptr);
+
+ if (!linkDriverNamespaceLocked(mDriverNamespace, vndkNamespace, mSphalLibraries)) {
+ mDriverNamespace = nullptr;
+ }
+
+ return mDriverNamespace;
+}
+
+std::string GraphicsEnv::getDriverPath() const {
+ return mDriverPath;
+}
+
+/**
+ * APIs for GpuStats
+ */
+
void GraphicsEnv::hintActivityLaunch() {
ATRACE_CALL();
@@ -246,14 +394,55 @@ void GraphicsEnv::setDriverLoaded(GpuStatsInfo::Api api, bool isDriverLoaded,
sendGpuStatsLocked(api, isDriverLoaded, driverLoadingTime);
}
-static sp<IGpuService> getGpuService() {
- static const sp<IBinder> binder = defaultServiceManager()->checkService(String16("gpu"));
- if (!binder) {
- ALOGE("Failed to get gpu service");
- return nullptr;
+// 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;
+}
- return interface_cast<IGpuService>(binder);
+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);
}
bool GraphicsEnv::readyToSendGpuStatsLocked() {
@@ -263,6 +452,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<std::mutex> lock(mStatsLock);
@@ -270,8 +464,8 @@ void GraphicsEnv::setTargetStats(const GpuStatsInfo::Stats stats, const uint64_t
const sp<IGpuService> gpuService = getGpuService();
if (gpuService) {
- gpuService->setTargetStats(mGpuStats.appPackageName, mGpuStats.driverVersionCode, stats,
- value);
+ gpuService->setTargetStatsArray(mGpuStats.appPackageName, mGpuStats.driverVersionCode,
+ stats, values, valueCount);
}
}
@@ -323,142 +517,141 @@ bool GraphicsEnv::setInjectLayersPrSetDumpable() {
return true;
}
-void* GraphicsEnv::loadLibrary(std::string name) {
- const android_dlextinfo dlextinfo = {
- .flags = ANDROID_DLEXT_USE_NAMESPACE,
- .library_namespace = getAngleNamespace(),
- };
-
- std::string libName = std::string("lib") + name + "_angle.so";
-
- void* so = android_dlopen_ext(libName.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
-
- if (so) {
- ALOGD("dlopen_ext from APK (%s) success at %p", libName.c_str(), so);
- return so;
- } else {
- ALOGE("dlopen_ext(\"%s\") failed: %s", libName.c_str(), dlerror());
- }
-
- return nullptr;
-}
-
-bool GraphicsEnv::shouldUseAngle(std::string appName) {
- if (appName != mAngleAppName) {
- // Make sure we are checking the app we were init'ed for
- ALOGE("App name does not match: expected '%s', got '%s'", mAngleAppName.c_str(),
- appName.c_str());
- return false;
- }
-
- return shouldUseAngle();
-}
+/**
+ * APIs for ANGLE
+ */
bool GraphicsEnv::shouldUseAngle() {
// Make sure we are init'ed
- if (mAngleAppName.empty()) {
- ALOGV("App name is empty. setAngleInfo() has not been called to enable ANGLE.");
+ if (mPackageName.empty()) {
+ ALOGV("Package name is empty. setAngleInfo() has not been called to enable ANGLE.");
return false;
}
- return (mUseAngle == YES) ? true : false;
+ return mShouldUseAngle;
}
-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;
+// Set ANGLE information.
+// If path is "system", it means system ANGLE must be used for the process.
+// If shouldUseNativeDriver is true, it means native GLES drivers must be used for the process.
+// If path is set to nonempty and shouldUseNativeDriver is true, ANGLE will be used regardless.
+void GraphicsEnv::setAngleInfo(const std::string& path, const bool shouldUseNativeDriver,
+ const std::string& packageName,
+ const std::vector<std::string> eglFeatures) {
+ if (mShouldUseAngle) {
+ // ANGLE is already set up for this application process, even if the application
+ // needs to switch from apk to system or vice versa, the application process must
+ // be killed and relaunch so that the loader can properly load ANGLE again.
+ // The architecture does not support runtime switch between drivers, so just return.
+ ALOGE("ANGLE is already set for %s", packageName.c_str());
+ return;
}
- 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;
+ mAngleEglFeatures = std::move(eglFeatures);
+ ALOGV("setting ANGLE path to '%s'", path.c_str());
+ mAnglePath = std::move(path);
+ ALOGV("setting app package name to '%s'", packageName.c_str());
+ mPackageName = std::move(packageName);
+ if (mAnglePath == "system") {
+ mShouldUseSystemAngle = true;
}
-
- return (mAngleIsSystemDriver == YES && mUseAngle == NO) ? true : false;
+ if (!mAnglePath.empty()) {
+ mShouldUseAngle = true;
+ }
+ mShouldUseNativeDriver = shouldUseNativeDriver;
}
-std::string GraphicsEnv::getLegacySuffix() {
- return mLegacyDriverSuffix;
+std::string& GraphicsEnv::getPackageName() {
+ return mPackageName;
}
-void GraphicsEnv::updateUseAngle() {
- mUseAngle = NO;
+const std::vector<std::string>& GraphicsEnv::getAngleEglFeatures() {
+ return mAngleEglFeatures;
+}
- 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";
+android_namespace_t* GraphicsEnv::getAngleNamespace() {
+ std::lock_guard<std::mutex> lock(mNamespaceMutex);
- mUseAngle = NO;
- if (mAngleDeveloperOptIn == ANGLE_PREFER_ANGLE) {
- ALOGI("Using ANGLE, the %s GLES driver for package '%s'",
- mAngleIsSystemDriver == YES ? "system" : "optional", mAngleAppName.c_str());
- 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 {
- ALOGV("User set invalid \"Developer Options\": '%s'", mAngleDeveloperOptIn.c_str());
+ if (mAngleNamespace) {
+ return mAngleNamespace;
}
-}
-void GraphicsEnv::setAngleInfo(const std::string path, const std::string appName,
- const bool angleIsSystemDriver, const std::string developerOptIn,
- const std::vector<std::string> 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(),
- (mUseAngle == YES) ? "true" : "false");
- return;
+ // If ANGLE path is not set, it means ANGLE should not be used for this process;
+ // or if ANGLE path is set and set to use system ANGLE, then a namespace is not needed
+ // because:
+ // 1) if the default OpenGL ES driver is already ANGLE, then the loader will skip;
+ // 2) if the default OpenGL ES driver is native, then there's no symbol conflict;
+ // 3) if there's no OpenGL ES driver is preloaded, then there's no symbol conflict.
+ if (mAnglePath.empty() || mShouldUseSystemAngle) {
+ ALOGV("mAnglePath is empty or use system ANGLE, abort creating ANGLE namespace");
+ return nullptr;
}
- mAngleEglFeatures = std::move(eglFeatures);
+ // Construct the search paths for system ANGLE.
+ const char* const defaultLibraryPaths =
+#if defined(__LP64__)
+ "/vendor/lib64/egl:/system/lib64";
+#else
+ "/vendor/lib/egl:/system/lib";
+#endif
- ALOGV("setting ANGLE path to '%s'", path.c_str());
- mAnglePath = path;
- ALOGV("setting ANGLE app name to '%s'", appName.c_str());
- mAngleAppName = appName;
- ALOGV("setting ANGLE application opt-in to '%s'", developerOptIn.c_str());
- mAngleDeveloperOptIn = developerOptIn;
+ // If the application process will run on top of system ANGLE, construct the namespace
+ // with sphal namespace being the parent namespace so that search paths and libraries
+ // are properly inherited.
+ mAngleNamespace =
+ android_create_namespace("ANGLE",
+ mShouldUseSystemAngle ? defaultLibraryPaths
+ : mAnglePath.c_str(), // ld_library_path
+ mShouldUseSystemAngle
+ ? defaultLibraryPaths
+ : mAnglePath.c_str(), // default_library_path
+ ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED,
+ nullptr, // permitted_when_isolated_path
+ mShouldUseSystemAngle ? android_get_exported_namespace("sphal")
+ : nullptr); // parent
- // Update the current status of whether we should use ANGLE or not
- updateUseAngle();
-}
+ ALOGD_IF(!mAngleNamespace, "Could not create ANGLE namespace from default");
-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;
+ if (!mShouldUseSystemAngle) {
+ return mAngleNamespace;
+ }
- // 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'");
+ auto vndkNamespace = android_get_exported_namespace("vndk");
+ if (!vndkNamespace) {
+ mAngleNamespace = nullptr;
+ return mAngleNamespace;
+ }
- // Set whether ANGLE is the system driver:
- mAngleIsSystemDriver = angleIsSystemDriver ? YES : NO;
+ if (!linkDriverNamespaceLocked(mAngleNamespace, vndkNamespace, "")) {
+ mAngleNamespace = nullptr;
+ }
- mLegacyDriverSuffix = legacyDriverName;
+ return mAngleNamespace;
+}
- // Update the current status of whether we should use ANGLE or not
- updateUseAngle();
+void GraphicsEnv::nativeToggleAngleAsSystemDriver(bool enabled) {
+ const sp<IGpuService> gpuService = getGpuService();
+ if (!gpuService) {
+ ALOGE("No GPU service");
+ return;
+ }
+ gpuService->toggleAngleAsSystemDriver(enabled);
}
-void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths) {
+bool GraphicsEnv::shouldUseSystemAngle() {
+ return mShouldUseSystemAngle;
+}
+
+bool GraphicsEnv::shouldUseNativeDriver() {
+ return mShouldUseNativeDriver;
+}
+
+/**
+ * APIs for debuggable layers
+ */
+
+void GraphicsEnv::setLayerPaths(NativeLoaderNamespace* appNamespace,
+ const std::string& layerPaths) {
if (mLayerPaths.empty()) {
mLayerPaths = layerPaths;
mAppNamespace = appNamespace;
@@ -472,14 +665,6 @@ NativeLoaderNamespace* GraphicsEnv::getAppNamespace() {
return mAppNamespace;
}
-std::string& GraphicsEnv::getAngleAppName() {
- return mAngleAppName;
-}
-
-const std::vector<std::string>& GraphicsEnv::getAngleEglFeatures() {
- return mAngleEglFeatures;
-}
-
const std::string& GraphicsEnv::getLayerPaths() {
return mLayerPaths;
}
@@ -492,132 +677,12 @@ const std::string& GraphicsEnv::getDebugLayersGLES() {
return mDebugLayersGLES;
}
-void GraphicsEnv::setDebugLayers(const std::string layers) {
+void GraphicsEnv::setDebugLayers(const std::string& layers) {
mDebugLayers = layers;
}
-void GraphicsEnv::setDebugLayersGLES(const std::string layers) {
+void GraphicsEnv::setDebugLayersGLES(const std::string& layers) {
mDebugLayersGLES = layers;
}
-// Return true if all the required libraries from vndk and sphal namespace are
-// linked to the updatable gfx driver namespace correctly.
-bool GraphicsEnv::linkDriverNamespaceLocked(android_namespace_t* vndkNamespace) {
- const std::string llndkLibraries = getSystemNativeLibraries(NativeLibrary::LLNDK);
- if (llndkLibraries.empty()) {
- return false;
- }
- if (!android_link_namespaces(mDriverNamespace, nullptr, llndkLibraries.c_str())) {
- ALOGE("Failed to link default namespace[%s]", dlerror());
- return false;
- }
-
- const std::string vndkspLibraries = getSystemNativeLibraries(NativeLibrary::VNDKSP);
- if (vndkspLibraries.empty()) {
- return false;
- }
- if (!android_link_namespaces(mDriverNamespace, vndkNamespace, vndkspLibraries.c_str())) {
- ALOGE("Failed to link vndk namespace[%s]", dlerror());
- return false;
- }
-
- if (mSphalLibraries.empty()) {
- return true;
- }
-
- // Make additional libraries in sphal to be accessible
- auto sphalNamespace = android_get_exported_namespace("sphal");
- if (!sphalNamespace) {
- ALOGE("Depend on these libraries[%s] in sphal, but failed to get sphal namespace",
- mSphalLibraries.c_str());
- return false;
- }
-
- if (!android_link_namespaces(mDriverNamespace, sphalNamespace, mSphalLibraries.c_str())) {
- ALOGE("Failed to link sphal namespace[%s]", dlerror());
- return false;
- }
-
- return true;
-}
-
-android_namespace_t* GraphicsEnv::getDriverNamespace() {
- std::lock_guard<std::mutex> lock(mNamespaceMutex);
-
- if (mDriverNamespace) {
- return mDriverNamespace;
- }
-
- if (mDriverPath.empty()) {
- // For an application process, driver path is empty means this application is not opted in
- // to use updatable driver. Application process doesn't have the ability to set up
- // environment variables and hence before `getenv` call will return.
- // For a process that is not an application process, if it's run from an environment,
- // for example shell, where environment variables can be set, then it can opt into using
- // udpatable driver by setting UPDATABLE_GFX_DRIVER to 1. By setting to 1 the developer
- // driver will be used currently.
- // TODO(b/159240322) Support the production updatable driver.
- const char* id = getenv("UPDATABLE_GFX_DRIVER");
- if (id == nullptr || std::strcmp(id, "1")) {
- return nullptr;
- }
- const sp<IGpuService> gpuService = getGpuService();
- if (!gpuService) {
- return nullptr;
- }
- mDriverPath = gpuService->getUpdatableDriverPath();
- if (mDriverPath.empty()) {
- return nullptr;
- }
- mDriverPath.append(UPDATABLE_DRIVER_ABI);
- ALOGI("Driver path is setup via UPDATABLE_GFX_DRIVER: %s", mDriverPath.c_str());
- }
-
- auto vndkNamespace = android_get_exported_namespace("vndk");
- if (!vndkNamespace) {
- return nullptr;
- }
-
- mDriverNamespace = android_create_namespace("gfx driver",
- mDriverPath.c_str(), // ld_library_path
- mDriverPath.c_str(), // default_library_path
- ANDROID_NAMESPACE_TYPE_ISOLATED,
- nullptr, // permitted_when_isolated_path
- nullptr);
-
- if (!linkDriverNamespaceLocked(vndkNamespace)) {
- mDriverNamespace = nullptr;
- }
-
- return mDriverNamespace;
-}
-
-std::string GraphicsEnv::getDriverPath() const {
- return mDriverPath;
-}
-
-android_namespace_t* GraphicsEnv::getAngleNamespace() {
- std::lock_guard<std::mutex> lock(mNamespaceMutex);
-
- if (mAngleNamespace) {
- return mAngleNamespace;
- }
-
- if (mAnglePath.empty()) {
- ALOGV("mAnglePath is empty, not creating ANGLE namespace");
- return nullptr;
- }
-
- mAngleNamespace = android_create_namespace("ANGLE",
- nullptr, // ld_library_path
- mAnglePath.c_str(), // default_library_path
- ANDROID_NAMESPACE_TYPE_SHARED_ISOLATED,
- nullptr, // permitted_when_isolated_path
- nullptr);
-
- ALOGD_IF(!mAngleNamespace, "Could not create ANGLE namespace from default");
-
- return mAngleNamespace;
-}
-
} // namespace android
diff --git a/libs/graphicsenv/IGpuService.cpp b/libs/graphicsenv/IGpuService.cpp
index fa25c5516d..5dc195c438 100644
--- a/libs/graphicsenv/IGpuService.cpp
+++ b/libs/graphicsenv/IGpuService.cpp
@@ -61,6 +61,22 @@ 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 {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
+
+ data.writeUtf8AsUtf16(appPackageName);
+ data.writeUint64(driverVersionCode);
+ data.writeInt32(static_cast<int32_t>(stats));
+ data.writeUint32(valueCount);
+ data.write(values, valueCount * sizeof(uint64_t));
+
+ remote()->transact(BnGpuService::SET_TARGET_STATS_ARRAY, data, &reply,
+ IBinder::FLAG_ONEWAY);
+ }
+
void setUpdatableDriverPath(const std::string& driverPath) override {
Parcel data, reply;
data.writeInterfaceToken(IGpuService::getInterfaceDescriptor());
@@ -70,6 +86,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());
@@ -147,6 +172,31 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
return OK;
}
+ case SET_TARGET_STATS_ARRAY: {
+ CHECK_INTERFACE(IGpuService, data, reply);
+
+ std::string appPackageName;
+ if ((status = data.readUtf8FromUtf16(&appPackageName)) != OK) return status;
+
+ uint64_t driverVersionCode;
+ if ((status = data.readUint64(&driverVersionCode)) != OK) return status;
+
+ int32_t stats;
+ if ((status = data.readInt32(&stats)) != OK) return status;
+
+ uint32_t valueCount;
+ if ((status = data.readUint32(&valueCount)) != OK) return status;
+
+ std::vector<uint64_t> values(valueCount);
+ if ((status = data.read(values.data(), valueCount * sizeof(uint64_t))) != OK) {
+ return status;
+ }
+
+ setTargetStatsArray(appPackageName, driverVersionCode,
+ static_cast<GpuStatsInfo::Stats>(stats), values.data(), valueCount);
+
+ return OK;
+ }
case SET_UPDATABLE_DRIVER_PATH: {
CHECK_INTERFACE(IGpuService, data, reply);
@@ -163,9 +213,9 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
return reply->writeUtf8AsUtf16(driverPath);
}
case SHELL_COMMAND_TRANSACTION: {
- int in = data.readFileDescriptor();
- int out = data.readFileDescriptor();
- int err = data.readFileDescriptor();
+ int in = dup(data.readFileDescriptor());
+ int out = dup(data.readFileDescriptor());
+ int err = dup(data.readFileDescriptor());
std::vector<String16> args;
data.readString16Vector(&args);
@@ -178,7 +228,19 @@ status_t BnGpuService::onTransact(uint32_t code, const Parcel& data, Parcel* rep
status = shellCommand(in, out, err, args);
if (resultReceiver != nullptr) resultReceiver->send(status);
+ ::close(in);
+ ::close(out);
+ ::close(err);
+
+ 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:
diff --git a/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h b/libs/graphicsenv/include/graphicsenv/GpuStatsInfo.h
index 5b513d2a79..9ebaf16eb4 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<int32_t> vulkanInstanceExtensions = {};
+ std::vector<int32_t> vulkanDeviceExtensions = {};
std::chrono::time_point<std::chrono::system_clock> lastAccessTime;
};
@@ -94,13 +104,20 @@ public:
GL_UPDATED = 2,
VULKAN = 3,
VULKAN_UPDATED = 4,
- ANGLE = 5,
+ ANGLE = 5, // cover both system ANGLE and ANGLE APK
};
enum Stats {
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 82a6b6c2c0..6cce3f6998 100644
--- a/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
+++ b/libs/graphicsenv/include/graphicsenv/GraphicsEnv.h
@@ -29,13 +29,18 @@ namespace android {
struct NativeLoaderNamespace;
+// The GraphicsEnv is a singleton per application process and is used to properly set up the
+// graphics drivers for the application process during application starts. The architecture of
+// the graphics driver loader does not support runtime switch and only supports switch to different
+// graphics drivers when application process launches and hence the only way to switch to different
+// graphics drivers is to completely kill the application process and relaunch the application.
class GraphicsEnv {
public:
static GraphicsEnv& getInstance();
// Check if the process is debuggable. It returns false except in any of the
// following circumstances:
- // 1. ro.debuggable=1 (global debuggable enabled).
+ // 1. ANDROID_DEBUGGABLE is defined (global debuggable enabled).
// 2. android:debuggable="true" in the manifest for an individual app.
// 3. An app which explicitly calls prctl(PR_SET_DUMPABLE, 1).
// 4. GraphicsEnv calls prctl(PR_SET_DUMPABLE, 1) in the presence of
@@ -55,7 +60,7 @@ public:
// Also set additional required sphal libraries to the linker for loading
// graphics drivers. The string is a list of libraries separated by ':',
// which is required by android_link_namespaces.
- void setDriverPathAndSphalLibraries(const std::string path, const std::string sphalLibraries);
+ void setDriverPathAndSphalLibraries(const std::string& path, const std::string& sphalLibraries);
// Get the updatable driver namespace.
android_namespace_t* getDriverNamespace();
std::string getDriverPath() const;
@@ -71,10 +76,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
@@ -87,105 +101,109 @@ public:
/*
* Apis for ANGLE
*/
- // Check if the requested app should use ANGLE.
- 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,
- const std::vector<std::string> 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);
+ // If the search patch is "system", then it means the system ANGLE should be used.
+ // If shouldUseNativeDriver is true, it means native GLES drivers must be used for the process.
+ // If path is set to nonempty and shouldUseNativeDriver is true, ANGLE will be used regardless.
+ void setAngleInfo(const std::string& path, const bool shouldUseNativeDriver,
+ const std::string& packageName, const std::vector<std::string> eglFeatures);
// 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();
-
+ // Get the app package name.
+ std::string& getPackageName();
const std::vector<std::string>& getAngleEglFeatures();
+ // Set the persist.graphics.egl system property value.
+ void nativeToggleAngleAsSystemDriver(bool enabled);
+ bool shouldUseSystemAngle();
+ bool shouldUseNativeDriver();
/*
* Apis for debug layer
*/
// Set additional layer search paths.
- void setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string layerPaths);
+ void setLayerPaths(NativeLoaderNamespace* appNamespace, const std::string& layerPaths);
// Get the app namespace for loading layers.
NativeLoaderNamespace* getAppNamespace();
// Get additional layer search paths.
const std::string& getLayerPaths();
// Set the Vulkan debug layers.
- void setDebugLayers(const std::string layers);
+ void setDebugLayers(const std::string& layers);
// Set the GL debug layers.
- void setDebugLayersGLES(const std::string layers);
+ void setDebugLayersGLES(const std::string& layers);
// Get the debug layers to load.
const std::string& getDebugLayers();
// Get the debug layers to load.
const std::string& getDebugLayersGLES();
private:
- enum UseAngle { UNKNOWN, YES, NO };
-
- // Load requested ANGLE library.
- void* loadLibrary(std::string name);
- // Update whether ANGLE should be used.
- void updateUseAngle();
// Link updatable driver namespace with llndk and vndk-sp libs.
- bool linkDriverNamespaceLocked(android_namespace_t* vndkNamespace);
+ bool linkDriverNamespaceLocked(android_namespace_t* destNamespace,
+ android_namespace_t* vndkNamespace,
+ const std::string& sharedSphalLibraries);
// Check whether this process is ready to send stats.
bool readyToSendGpuStatsLocked();
// Send the initial complete GpuStats to GpuService.
void sendGpuStatsLocked(GpuStatsInfo::Api api, bool isDriverLoaded, int64_t driverLoadingTime);
GraphicsEnv() = default;
+
+ // This mutex protects the namespace creation.
+ std::mutex mNamespaceMutex;
+
+ /**
+ * Updatable driver variables.
+ */
// Path to updatable driver libs.
std::string mDriverPath;
// Path to additional sphal libs linked to updatable driver namespace.
std::string mSphalLibraries;
+ // Updatable driver namespace.
+ android_namespace_t* mDriverNamespace = nullptr;
+
+ /**
+ * ANGLE variables.
+ */
+ // Path to ANGLE libs.
+ std::string mAnglePath;
+ // App's package name.
+ std::string mPackageName;
+ // ANGLE EGL features;
+ std::vector<std::string> mAngleEglFeatures;
+ // Whether ANGLE should be used.
+ bool mShouldUseAngle = false;
+ // Whether loader should load system ANGLE.
+ bool mShouldUseSystemAngle = false;
+ // Whether loader should load native GLES driver.
+ bool mShouldUseNativeDriver = false;
+ // ANGLE namespace.
+ android_namespace_t* mAngleNamespace = nullptr;
+
+ /**
+ * GPU metrics.
+ */
// This mutex protects mGpuStats and get gpuservice call.
std::mutex mStatsLock;
// Cache the activity launch info
bool mActivityLaunched = false;
// Information bookkept for GpuStats.
GpuStatsInfo mGpuStats;
- // Path to ANGLE libs.
- std::string mAnglePath;
- // This App's name.
- std::string mAngleAppName;
- // ANGLE developer opt in status.
- std::string mAngleDeveloperOptIn;
- // ANGLE EGL features;
- std::vector<std::string> 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;
+
+ /**
+ * Debug layers.
+ */
// Vulkan debug layers libs.
std::string mDebugLayers;
// GL debug layers libs.
std::string mDebugLayersGLES;
// Additional debug layers search path.
std::string mLayerPaths;
- // This mutex protects the namespace creation.
- std::mutex mNamespaceMutex;
- // Updatable driver namespace.
- android_namespace_t* mDriverNamespace = nullptr;
- // ANGLE namespace.
- android_namespace_t* mAngleNamespace = nullptr;
- // This App's namespace.
+ // This App's namespace to open native libraries.
NativeLoaderNamespace* mAppNamespace = nullptr;
};
diff --git a/libs/graphicsenv/include/graphicsenv/IGpuService.h b/libs/graphicsenv/include/graphicsenv/IGpuService.h
index 2d59fa0165..45f05d6555 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<IGpuService> {
@@ -55,6 +62,8 @@ public:
SET_TARGET_STATS,
SET_UPDATABLE_DRIVER_PATH,
GET_UPDATABLE_DRIVER_PATH,
+ TOGGLE_ANGLE_AS_SYSTEM_DRIVER,
+ SET_TARGET_STATS_ARRAY,
// Always append new enum to the end.
};
diff --git a/libs/gui/Android.bp b/libs/gui/Android.bp
index 2ac11746c3..059e19ebb8 100644
--- a/libs/gui/Android.bp
+++ b/libs/gui/Android.bp
@@ -20,6 +20,26 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
+aconfig_declarations {
+ name: "libgui_flags",
+ package: "com.android.graphics.libgui.flags",
+ container: "system",
+ srcs: ["libgui_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "libguiflags",
+ host_supported: true,
+ vendor_available: true,
+ min_sdk_version: "29",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.media.swcodec",
+ "test_com.android.media.swcodec",
+ ],
+ aconfig_declarations: "libgui_flags",
+}
+
cc_library_headers {
name: "libgui_headers",
vendor_available: true,
@@ -36,6 +56,8 @@ cc_library_headers {
"android.hardware.graphics.bufferqueue@1.0",
"android.hardware.graphics.bufferqueue@2.0",
],
+ static_libs: ["libguiflags"],
+ export_static_lib_headers: ["libguiflags"],
min_sdk_version: "29",
// TODO(b/218719284) can media use be constrained to libgui_bufferqueue_static?
apex_available: [
@@ -62,10 +84,25 @@ filegroup {
name: "guiconstants_aidl",
srcs: [
"android/gui/DropInputMode.aidl",
+ "android/gui/StalledTransactionInfo.aidl",
"android/**/TouchOcclusionMode.aidl",
],
}
+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 +114,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 +154,37 @@ 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/StalledTransactionInfo.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,39 +195,27 @@ 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"],
},
}
-cc_library_shared {
- name: "libgui",
- vendor_available: true,
- vndk: {
- enabled: true,
- private: true,
- },
- double_loadable: true,
-
- defaults: ["libgui_bufferqueue-defaults"],
-
- static_libs: [
- "libgui_aidl_static",
- "libgui_window_info_static",
- ],
- export_static_lib_headers: [
- "libgui_aidl_static",
- "libgui_window_info_static",
- ],
-
+filegroup {
+ name: "libgui-sources",
srcs: [
":framework_native_aidl_binder",
":framework_native_aidl_gui",
@@ -178,22 +225,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 +251,6 @@ cc_library_shared {
"SurfaceControl.cpp",
"SurfaceComposerClient.cpp",
"SyncFeatures.cpp",
- "TransactionTracing.cpp",
"VsyncEventData.cpp",
"view/Surface.cpp",
"WindowInfosListenerReporter.cpp",
@@ -211,9 +259,38 @@ cc_library_shared {
"bufferqueue/2.0/B2HProducerListener.cpp",
"bufferqueue/2.0/H2BGraphicBufferProducer.cpp",
],
+}
+cc_defaults {
+ name: "libgui-defaults",
+ defaults: ["libgui_bufferqueue-defaults"],
+ srcs: [":libgui-sources"],
+ static_libs: [
+ "libgui_aidl_static",
+ "libgui_window_info_static",
+ "libguiflags",
+ ],
shared_libs: [
"libbinder",
+ "libGLESv2",
+ ],
+ export_static_lib_headers: [
+ "libguiflags",
+ ],
+}
+
+cc_library_shared {
+ name: "libgui",
+ vendor_available: true,
+ double_loadable: true,
+
+ defaults: [
+ "libgui-defaults",
+ ],
+
+ export_static_lib_headers: [
+ "libgui_aidl_static",
+ "libgui_window_info_static",
],
export_shared_lib_headers: [
@@ -222,6 +299,7 @@ cc_library_shared {
export_header_lib_headers: [
"libgui_aidl_headers",
+ "jni_headers",
],
aidl: {
@@ -229,6 +307,7 @@ cc_library_shared {
},
header_libs: [
+ "jni_headers",
"libdvr_headers",
"libgui_aidl_headers",
"libpdx_headers",
@@ -239,6 +318,10 @@ cc_library_shared {
lto: {
thin: true,
},
+
+ cflags: [
+ "-Wthread-safety",
+ ],
}
// Used by media codec services exclusively as a static lib for
@@ -259,10 +342,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 {
@@ -276,6 +365,7 @@ filegroup {
"BufferQueueProducer.cpp",
"BufferQueueThreadState.cpp",
"BufferSlot.cpp",
+ "FrameRateUtils.cpp",
"FrameTimestamps.cpp",
"GLConsumerUtils.cpp",
"HdrMetadata.cpp",
@@ -293,6 +383,8 @@ filegroup {
cc_defaults {
name: "libgui_bufferqueue-defaults",
+ defaults: ["android.hardware.graphics.common-ndk_shared"],
+
cflags: [
"-Wall",
"-Werror",
@@ -321,19 +413,16 @@ 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",
"libEGL",
- "libGLESv2",
"libhidlbase",
"liblog",
"libnativewindow",
"libsync",
"libui",
"libutils",
- "libvndksupport",
],
static_libs: [
@@ -378,9 +467,11 @@ cc_library_static {
static_libs: [
"libgtest",
"libgmock",
+ "libguiflags",
],
srcs: [
+ ":libgui_frame_event_aidl",
"mock/GraphicBufferConsumer.cpp",
"mock/GraphicBufferProducer.cpp",
],
diff --git a/libs/gui/BLASTBufferQueue.cpp b/libs/gui/BLASTBufferQueue.cpp
index 1242ac83e3..f317a2eea0 100644
--- a/libs/gui/BLASTBufferQueue.cpp
+++ b/libs/gui/BLASTBufferQueue.cpp
@@ -20,11 +20,14 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
//#define LOG_NDEBUG 0
+#include <cutils/atomic.h>
#include <gui/BLASTBufferQueue.h>
#include <gui/BufferItemConsumer.h>
#include <gui/BufferQueueConsumer.h>
#include <gui/BufferQueueCore.h>
#include <gui/BufferQueueProducer.h>
+
+#include <gui/FrameRateUtils.h>
#include <gui/GLConsumer.h>
#include <gui/IProducerListener.h>
#include <gui/Surface.h>
@@ -33,9 +36,14 @@
#include <utils/Trace.h>
#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
+#include <android-base/thread_annotations.h>
#include <chrono>
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
using namespace std::chrono_literals;
namespace {
@@ -62,6 +70,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;
@@ -92,12 +104,11 @@ void BLASTBufferItemConsumer::addAndGetFrameTimestamps(const NewFrameEventsEntry
}
}
-void BLASTBufferItemConsumer::updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
- const sp<Fence>& glDoneFence,
- const sp<Fence>& presentFence,
- const sp<Fence>& prevReleaseFence,
- CompositorTiming compositorTiming,
- nsecs_t latchTime, nsecs_t dequeueReadyTime) {
+void BLASTBufferItemConsumer::updateFrameTimestamps(
+ uint64_t frameNumber, uint64_t previousFrameNumber, nsecs_t refreshStartTime,
+ const sp<Fence>& glDoneFence, const sp<Fence>& presentFence,
+ const sp<Fence>& prevReleaseFence, CompositorTiming compositorTiming, nsecs_t latchTime,
+ nsecs_t dequeueReadyTime) {
Mutex::Autolock lock(mMutex);
// if the producer is not connected, don't bother updating,
@@ -108,7 +119,15 @@ void BLASTBufferItemConsumer::updateFrameTimestamps(uint64_t frameNumber, nsecs_
std::shared_ptr<FenceTime> releaseFenceTime = std::make_shared<FenceTime>(prevReleaseFence);
mFrameEventHistory.addLatch(frameNumber, latchTime);
- mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+ if (flags::frametimestamps_previousrelease()) {
+ if (previousFrameNumber > 0) {
+ mFrameEventHistory.addRelease(previousFrameNumber, dequeueReadyTime,
+ std::move(releaseFenceTime));
+ }
+ } else {
+ mFrameEventHistory.addRelease(frameNumber, dequeueReadyTime, std::move(releaseFenceTime));
+ }
+
mFrameEventHistory.addPreComposition(frameNumber, refreshStartTime);
mFrameEventHistory.addPostComposition(frameNumber, glDoneFenceTime, presentFenceTime,
compositorTiming);
@@ -132,6 +151,21 @@ void BLASTBufferItemConsumer::onSidebandStreamChanged() {
}
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+void BLASTBufferItemConsumer::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+ if (bbq != nullptr) {
+ bbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
+void BLASTBufferItemConsumer::resizeFrameEventHistory(size_t newSize) {
+ Mutex::Autolock lock(mMutex);
+ mFrameEventHistory.resize(newSize);
+}
+
BLASTBufferQueue::BLASTBufferQueue(const std::string& name, bool updateDestinationFrame)
: mSurfaceControl(nullptr),
mSize(1, 1),
@@ -151,30 +185,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<uint32_t> 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<void(bool)> callbackCopy;
- {
- std::unique_lock _lock{mMutex};
- callbackCopy = mTransactionHangCallback;
- }
- if (callbackCopy) callbackCopy(true);
- }, this);
+ [&](const std::string& reason) {
+ std::function<void(const std::string&)> callbackCopy;
+ {
+ std::unique_lock _lock{mMutex};
+ callbackCopy = mTransactionHangCallback;
+ }
+ if (callbackCopy) callbackCopy(reason);
+ },
+ this);
BQA_LOGV("BLASTBufferQueue created");
}
@@ -206,7 +240,7 @@ void BLASTBufferQueue::update(const sp<SurfaceControl>& 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));
@@ -276,7 +310,7 @@ void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/,
const sp<Fence>& /*presentFence*/,
const std::vector<SurfaceControlStats>& stats) {
{
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
BBQ_TRACE();
BQA_LOGV("transactionCommittedCallback");
if (!mSurfaceControlsWithPendingCallback.empty()) {
@@ -291,13 +325,8 @@ void BLASTBufferQueue::transactionCommittedCallback(nsecs_t /*latchTime*/,
// frame numbers that were in a sync. We remove the frame from mSyncedFrameNumbers
// set and then check if it's empty. If there are no more pending syncs, we can
// proceed with flushing the shadow queue.
- // We also want to check if mSyncTransaction is null because it's possible another
- // sync request came in while waiting, but it hasn't started processing yet. In that
- // case, we don't actually want to flush the frames in between since they will get
- // processed and merged with the sync transaction and released earlier than if they
- // were sent to SF
mSyncedFrameNumbers.erase(currFrameNumber);
- if (mSyncedFrameNumbers.empty() && mSyncTransaction == nullptr) {
+ if (mSyncedFrameNumbers.empty()) {
flushShadowQueue();
}
} else {
@@ -324,7 +353,7 @@ static void transactionCallbackThunk(void* context, nsecs_t latchTime,
void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence>& /*presentFence*/,
const std::vector<SurfaceControlStats>& stats) {
{
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
BBQ_TRACE();
BQA_LOGV("transactionCallback");
@@ -334,14 +363,17 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence
std::optional<SurfaceControlStats> 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) {
mBufferItemConsumer
->updateFrameTimestamps(stat.frameEventStats.frameNumber,
+ stat.frameEventStats.previousFrameNumber,
stat.frameEventStats.refreshStartTime,
stat.frameEventStats.gpuCompositionDoneFence,
stat.presentFence, stat.previousReleaseFence,
@@ -357,11 +389,12 @@ void BLASTBufferQueue::transactionCallback(nsecs_t /*latchTime*/, const sp<Fence
}
}
for (const auto& staleRelease : staleReleases) {
- BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback");
- BBQ_TRACE("FakeReleaseCallback");
releaseBufferCallbackLocked(staleRelease,
- stat.previousReleaseFence ? stat.previousReleaseFence : Fence::NO_FENCE,
- stat.currentMaxAcquiredBufferCount);
+ stat.previousReleaseFence
+ ? stat.previousReleaseFence
+ : Fence::NO_FENCE,
+ stat.currentMaxAcquiredBufferCount,
+ true /* fakeRelease */);
}
} else {
BQA_LOGE("Failed to find matching SurfaceControl in transactionCallback");
@@ -402,14 +435,15 @@ void BLASTBufferQueue::flushShadowQueue() {
void BLASTBufferQueue::releaseBufferCallback(
const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+ std::lock_guard _lock{mMutex};
BBQ_TRACE();
-
- std::unique_lock _lock{mMutex};
- releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount);
+ releaseBufferCallbackLocked(id, releaseFence, currentMaxAcquiredBufferCount,
+ false /* fakeRelease */);
}
-void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id,
- const sp<Fence>& releaseFence, std::optional<uint32_t> currentMaxAcquiredBufferCount) {
+void BLASTBufferQueue::releaseBufferCallbackLocked(
+ const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
+ std::optional<uint32_t> currentMaxAcquiredBufferCount, bool fakeRelease) {
ATRACE_CALL();
BQA_LOGV("releaseBufferCallback %s", id.to_string().c_str());
@@ -417,10 +451,8 @@ void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id,
// 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;
@@ -432,6 +464,11 @@ void BLASTBufferQueue::releaseBufferCallbackLocked(const ReleaseCallbackId& id,
auto rb = ReleasedBuffer{id, releaseFence};
if (std::find(mPendingRelease.begin(), mPendingRelease.end(), rb) == mPendingRelease.end()) {
mPendingRelease.emplace_back(rb);
+ if (fakeRelease) {
+ BQA_LOGE("Faking releaseBufferCallback from transactionCompleteCallback %" PRIu64,
+ id.framenumber);
+ BBQ_TRACE("FakeReleaseCallback");
+ }
}
// Release all buffers that are beyond the ones that we need to hold
@@ -472,20 +509,37 @@ void BLASTBufferQueue::releaseBuffer(const ReleaseCallbackId& callbackId,
mSyncedFrameNumbers.erase(callbackId.framenumber);
}
-void BLASTBufferQueue::acquireNextBufferLocked(
+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<SurfaceComposerClient::Transaction*> transaction) {
- // If the next transaction is set, we want to guarantee the our acquire will not fail, so don't
- // include the extra buffer when checking if we can acquire the next buffer.
- const bool includeExtraAcquire = !transaction;
- const bool maxAcquired = maxBuffersAcquired(includeExtraAcquire);
- if (mNumFrameAvailable == 0 || maxAcquired) {
- BQA_LOGV("Can't process next buffer maxBuffersAcquired=%s", boolToString(maxAcquired));
- return;
+ // Check if we have frames available and we have not acquired the maximum number of buffers.
+ // Even with this check, the consumer can fail to acquire an additional buffer if the consumer
+ // has already acquired (mMaxAcquiredBuffers + 1) and the new buffer is not droppable. In this
+ // case mBufferItemConsumer->acquireBuffer will return with NO_BUFFER_AVAILABLE.
+ if (mNumFrameAvailable == 0) {
+ BQA_LOGV("Can't acquire next buffer. No available frames");
+ return BufferQueue::NO_BUFFER_AVAILABLE;
+ }
+
+ if (mNumAcquired >= (mMaxAcquiredBuffers + 2)) {
+ BQA_LOGV("Can't acquire next buffer. Already acquired max frames %d max:%d + 2",
+ mNumAcquired, mMaxAcquiredBuffers);
+ return BufferQueue::NO_BUFFER_AVAILABLE;
}
if (mSurfaceControl == nullptr) {
BQA_LOGE("ERROR : surface control is null");
- return;
+ return NAME_NOT_FOUND;
}
SurfaceComposerClient::Transaction localTransaction;
@@ -502,10 +556,10 @@ void BLASTBufferQueue::acquireNextBufferLocked(
mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
if (status == BufferQueue::NO_BUFFER_AVAILABLE) {
BQA_LOGV("Failed to acquire a buffer, err=NO_BUFFER_AVAILABLE");
- return;
+ return status;
} else if (status != OK) {
BQA_LOGE("Failed to acquire a buffer, err=%s", statusToString(status).c_str());
- return;
+ return status;
}
auto buffer = bufferItem.mGraphicBuffer;
@@ -515,7 +569,7 @@ void BLASTBufferQueue::acquireNextBufferLocked(
if (buffer == nullptr) {
mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
BQA_LOGE("Buffer was empty");
- return;
+ return BAD_VALUE;
}
if (rejectBuffer(bufferItem)) {
@@ -524,8 +578,7 @@ void BLASTBufferQueue::acquireNextBufferLocked(
mSize.width, mSize.height, mRequestedSize.width, mRequestedSize.height,
buffer->getWidth(), buffer->getHeight(), bufferItem.mTransform);
mBufferItemConsumer->releaseBuffer(bufferItem, Fence::NO_FENCE);
- acquireNextBufferLocked(transaction);
- return;
+ return acquireNextBufferLocked(transaction);
}
mNumAcquired++;
@@ -544,7 +597,13 @@ void 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,
@@ -554,7 +613,8 @@ void BLASTBufferQueue::acquireNextBufferLocked(
std::bind(releaseBufferCallbackThunk, wp<BLASTBufferQueue>(this) /* callbackContext */,
std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
sp<Fence> 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<ui::Dataspace>(bufferItem.mDataSpace));
t->setHdrMetadata(mSurfaceControl, bufferItem.mHdrMetadata);
t->setSurfaceDamageRegion(mSurfaceControl, bufferItem.mSurfaceDamage);
@@ -579,18 +639,32 @@ void BLASTBufferQueue::acquireNextBufferLocked(
t->setDesiredPresentTime(bufferItem.mTimestamp);
}
- if (!mNextFrameTimelineInfoQueue.empty()) {
- t->setFrameTimelineInfo(mNextFrameTimelineInfoQueue.front());
- mNextFrameTimelineInfoQueue.pop();
+ // Drop stale frame timeline infos
+ while (!mPendingFrameTimelines.empty() &&
+ mPendingFrameTimelines.front().first < bufferItem.mFrameNumber) {
+ ATRACE_FORMAT_INSTANT("dropping stale frameNumber: %" PRIu64 " vsyncId: %" PRId64,
+ mPendingFrameTimelines.front().first,
+ mPendingFrameTimelines.front().second.vsyncId);
+ mPendingFrameTimelines.pop();
+ }
+
+ if (!mPendingFrameTimelines.empty() &&
+ mPendingFrameTimelines.front().first == bufferItem.mFrameNumber) {
+ ATRACE_FORMAT_INSTANT("Transaction::setFrameTimelineInfo frameNumber: %" PRIu64
+ " vsyncId: %" PRId64,
+ bufferItem.mFrameNumber,
+ mPendingFrameTimelines.front().second.vsyncId);
+ t->setFrameTimelineInfo(mPendingFrameTimelines.front().second);
+ mPendingFrameTimelines.pop();
}
{
- 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);
}
}
@@ -613,6 +687,7 @@ void BLASTBufferQueue::acquireNextBufferLocked(
bufferItem.mTimestamp, bufferItem.mIsAutoTimestamp ? "(auto)" : "",
static_cast<uint32_t>(mPendingTransactions.size()), bufferItem.mGraphicBuffer->getId(),
bufferItem.mAutoRefresh ? " mAutoRefresh" : "", bufferItem.mTransform);
+ return OK;
}
Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
@@ -623,6 +698,7 @@ Rect BLASTBufferQueue::computeCrop(const BufferItem& item) {
}
void BLASTBufferQueue::acquireAndReleaseBuffer() {
+ BBQ_TRACE();
BufferItem bufferItem;
status_t status =
mBufferItemConsumer->acquireBuffer(&bufferItem, 0 /* expectedPresent */, false);
@@ -635,44 +711,19 @@ void BLASTBufferQueue::acquireAndReleaseBuffer() {
mBufferItemConsumer->releaseBuffer(bufferItem, bufferItem.mFence);
}
-void BLASTBufferQueue::flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock) {
- if (!mSyncedFrameNumbers.empty() && mNumFrameAvailable > 0) {
- // We are waiting on a previous sync's transaction callback so allow another sync
- // transaction to proceed.
- //
- // We need to first flush out the transactions that were in between the two syncs.
- // We do this by merging them into mSyncTransaction so any buffer merging will get
- // a release callback invoked. The release callback will be async so we need to wait
- // on max acquired to make sure we have the capacity to acquire another buffer.
- if (maxBuffersAcquired(false /* includeExtraAcquire */)) {
- BQA_LOGD("waiting to flush shadow queue...");
- mCallbackCV.wait(lock);
- }
- while (mNumFrameAvailable > 0) {
- // flush out the shadow queue
- acquireAndReleaseBuffer();
- }
- }
-
- while (maxBuffersAcquired(false /* includeExtraAcquire */)) {
- BQA_LOGD("waiting for free buffer.");
- mCallbackCV.wait(lock);
- }
-}
-
void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
std::function<void(SurfaceComposerClient::Transaction*)> prevCallback = nullptr;
SurfaceComposerClient::Transaction* prevTransaction = nullptr;
- bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
{
+ UNIQUE_LOCK_WITH_ASSERTION(mMutex);
BBQ_TRACE();
- std::unique_lock _lock{mMutex};
+ bool waitForTransactionCallback = !mSyncedFrameNumbers.empty();
+
const bool syncTransactionSet = mTransactionReadyCallback != nullptr;
BQA_LOGV("onFrameAvailable-start syncTransactionSet=%s", boolToString(syncTransactionSet));
if (syncTransactionSet) {
- bool mayNeedToWaitForBuffer = true;
// If we are going to re-use the same mSyncTransaction, release the buffer that may
// already be set in the Transaction. This is to allow us a free slot early to continue
// processing a new buffer.
@@ -683,14 +734,29 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
bufferData->frameNumber);
releaseBuffer(bufferData->generateReleaseCallbackId(),
bufferData->acquireFence);
- // Because we just released a buffer, we know there's no need to wait for a free
- // buffer.
- mayNeedToWaitForBuffer = false;
}
}
- if (mayNeedToWaitForBuffer) {
- flushAndWaitForFreeBuffer(_lock);
+ if (waitForTransactionCallback) {
+ // We are waiting on a previous sync's transaction callback so allow another sync
+ // transaction to proceed.
+ //
+ // We need to first flush out the transactions that were in between the two syncs.
+ // We do this by merging them into mSyncTransaction so any buffer merging will get
+ // a release callback invoked.
+ while (mNumFrameAvailable > 0) {
+ // flush out the shadow queue
+ acquireAndReleaseBuffer();
+ }
+ } else {
+ // Make sure the frame available count is 0 before proceeding with a sync to ensure
+ // the correct frame is used for the sync. The only way mNumFrameAvailable would be
+ // greater than 0 is if we already ran out of buffers previously. This means we
+ // need to flush the buffers before proceeding with the sync.
+ while (mNumFrameAvailable > 0) {
+ BQA_LOGD("waiting until no queued buffers");
+ mCallbackCV.wait(_lock);
+ }
}
}
@@ -706,14 +772,23 @@ void BLASTBufferQueue::onFrameAvailable(const BufferItem& item) {
item.mFrameNumber, boolToString(syncTransactionSet));
if (syncTransactionSet) {
- acquireNextBufferLocked(mSyncTransaction);
+ // Add to mSyncedFrameNumbers before waiting in case any buffers are released
+ // while waiting for a free buffer. The release and commit callback will try to
+ // acquire buffers if there are any available, but we don't want it to acquire
+ // in the case where a sync transaction wants the buffer.
+ mSyncedFrameNumbers.emplace(item.mFrameNumber);
+ // If there's no available buffer and we're in a sync transaction, we need to wait
+ // instead of returning since we guarantee a buffer will be acquired for the sync.
+ while (acquireNextBufferLocked(mSyncTransaction) == BufferQueue::NO_BUFFER_AVAILABLE) {
+ BQA_LOGD("waiting for available buffer");
+ mCallbackCV.wait(_lock);
+ }
// Only need a commit callback when syncing to ensure the buffer that's synced has been
// sent to SF
incStrong((void*)transactionCommittedCallbackThunk);
mSyncTransaction->addTransactionCommittedCallback(transactionCommittedCallbackThunk,
static_cast<void*>(this));
- mSyncedFrameNumbers.emplace(item.mFrameNumber);
if (mAcquireSingleBuffer) {
prevCallback = mTransactionReadyCallback;
prevTransaction = mSyncTransaction;
@@ -735,44 +810,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<void(SurfaceComposerClient::Transaction*)> callback,
bool acquireSingleBuffer) {
- BBQ_TRACE();
-
- std::function<void(SurfaceComposerClient::Transaction*)> 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() {
@@ -780,34 +844,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;
}
@@ -816,20 +888,11 @@ bool BLASTBufferQueue::rejectBuffer(const BufferItem& item) {
return mSize != bufferSize;
}
-// Check if we have acquired the maximum number of buffers.
-// Consumer can acquire an additional buffer if that buffer is not droppable. Set
-// includeExtraAcquire is true to include this buffer to the count. Since this depends on the state
-// of the buffer, the next acquire may return with NO_BUFFER_AVAILABLE.
-bool BLASTBufferQueue::maxBuffersAcquired(bool includeExtraAcquire) const {
- int maxAcquiredBuffers = mMaxAcquiredBuffers + (includeExtraAcquire ? 2 : 1);
- return mNumAcquired >= maxAcquiredBuffers;
-}
-
class BBQSurface : public Surface {
private:
std::mutex mMutex;
- sp<BLASTBufferQueue> mBbq;
- bool mDestroyed = false;
+ sp<BLASTBufferQueue> mBbq GUARDED_BY(mMutex);
+ bool mDestroyed GUARDED_BY(mMutex) = false;
public:
BBQSurface(const sp<IGraphicBufferProducer>& igbp, bool controlledByApp,
@@ -850,7 +913,11 @@ public:
status_t setFrameRate(float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy) override {
- std::unique_lock _lock{mMutex};
+ if (flags::bq_setframerate()) {
+ return Surface::setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+
+ std::lock_guard _lock{mMutex};
if (mDestroyed) {
return DEAD_OBJECT;
}
@@ -861,18 +928,19 @@ public:
return mBbq->setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
}
- status_t setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) override {
- std::unique_lock _lock{mMutex};
+ status_t setFrameTimelineInfo(uint64_t frameNumber,
+ const FrameTimelineInfo& frameTimelineInfo) override {
+ std::lock_guard _lock{mMutex};
if (mDestroyed) {
return DEAD_OBJECT;
}
- return mBbq->setFrameTimelineInfo(frameTimelineInfo);
+ return mBbq->setFrameTimelineInfo(frameNumber, frameTimelineInfo);
}
void destroy() override {
Surface::destroy();
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
mDestroyed = true;
mBbq = nullptr;
}
@@ -882,27 +950,30 @@ 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();
}
-status_t BLASTBufferQueue::setFrameTimelineInfo(const FrameTimelineInfo& frameTimelineInfo) {
- std::unique_lock _lock{mMutex};
- mNextFrameTimelineInfoQueue.push(frameTimelineInfo);
+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::lock_guard _lock{mMutex};
+ mPendingFrameTimelines.push({frameNumber, frameTimelineInfo});
return OK;
}
void BLASTBufferQueue::setSidebandStream(const sp<NativeHandle>& stream) {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
SurfaceComposerClient::Transaction t;
t.setSidebandStream(mSurfaceControl, stream).apply();
}
sp<Surface> BLASTBufferQueue::getSurface(bool includeSurfaceControlHandle) {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
sp<IBinder> scHandle = nullptr;
if (includeSurfaceControlHandle && mSurfaceControl) {
scHandle = mSurfaceControl->getHandle();
@@ -1031,8 +1102,9 @@ public:
// can be non-blocking when the producer is in the client process.
class BBQBufferQueueProducer : public BufferQueueProducer {
public:
- BBQBufferQueueProducer(const sp<BufferQueueCore>& core)
- : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/) {}
+ BBQBufferQueueProducer(const sp<BufferQueueCore>& core, wp<BLASTBufferQueue> bbq)
+ : BufferQueueProducer(core, false /* consumerIsSurfaceFlinger*/),
+ mBLASTBufferQueue(std::move(bbq)) {}
status_t connect(const sp<IProducerListener>& listener, int api, bool producerControlledByApp,
QueueBufferOutput* output) override {
@@ -1044,6 +1116,26 @@ public:
producerControlledByApp, output);
}
+ // We want to resize the frame history when changing the size of the buffer queue
+ status_t setMaxDequeuedBufferCount(int maxDequeuedBufferCount) override {
+ int maxBufferCount;
+ status_t status = BufferQueueProducer::setMaxDequeuedBufferCount(maxDequeuedBufferCount,
+ &maxBufferCount);
+ // if we can't determine the max buffer count, then just skip growing the history size
+ if (status == OK) {
+ size_t newFrameHistorySize = maxBufferCount + 2; // +2 because triple buffer rendering
+ // optimize away resizing the frame history unless it will grow
+ if (newFrameHistorySize > FrameEventHistory::INITIAL_MAX_FRAME_HISTORY) {
+ sp<BLASTBufferQueue> bbq = mBLASTBufferQueue.promote();
+ if (bbq != nullptr) {
+ ALOGV("increasing frame history size to %zu", newFrameHistorySize);
+ bbq->resizeFrameEventHistory(newFrameHistorySize);
+ }
+ }
+ }
+ return status;
+ }
+
int query(int what, int* value) override {
if (what == NATIVE_WINDOW_QUEUES_TO_WINDOW_COMPOSER) {
*value = 1;
@@ -1051,6 +1143,9 @@ public:
}
return BufferQueueProducer::query(what, value);
}
+
+private:
+ const wp<BLASTBufferQueue> mBLASTBufferQueue;
};
// Similar to BufferQueue::createBufferQueue but creates an adapter specific bufferqueue producer.
@@ -1065,7 +1160,7 @@ void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer
sp<BufferQueueCore> core(new BufferQueueCore());
LOG_ALWAYS_FATAL_IF(core == nullptr, "BLASTBufferQueue: failed to create BufferQueueCore");
- sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core));
+ sp<IGraphicBufferProducer> producer(new BBQBufferQueueProducer(core, this));
LOG_ALWAYS_FATAL_IF(producer == nullptr,
"BLASTBufferQueue: failed to create BBQBufferQueueProducer");
@@ -1078,6 +1173,16 @@ void BLASTBufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer
*outConsumer = consumer;
}
+void BLASTBufferQueue::resizeFrameEventHistory(size_t newSize) {
+ // This can be null during creation of the buffer queue, but resizing won't do anything at that
+ // point in time, so just ignore. This can go away once the class relationships and lifetimes of
+ // objects are cleaned up with a major refactor of BufferQueue as a whole.
+ if (mBufferItemConsumer != nullptr) {
+ std::unique_lock _lock{mMutex};
+ mBufferItemConsumer->resizeFrameEventHistory(newSize);
+ }
+}
+
PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) {
PixelFormat convertedFormat = format;
switch (format) {
@@ -1093,6 +1198,7 @@ PixelFormat BLASTBufferQueue::convertBufferFormat(PixelFormat& format) {
}
uint32_t BLASTBufferQueue::getLastTransformHint() const {
+ std::lock_guard _lock{mMutex};
if (mSurfaceControl != nullptr) {
return mSurfaceControl->getTransformHint();
} else {
@@ -1101,62 +1207,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<uint32_t>(mPendingTransactions.size()));
- SurfaceComposerClient::Transaction t;
- mergePendingTransactions(&t, std::numeric_limits<uint64_t>::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>& surfaceControl) const {
- std::unique_lock _lock{mMutex};
+ std::lock_guard _lock{mMutex};
return SurfaceControl::isSameSurface(mSurfaceControl, surfaceControl);
}
-void BLASTBufferQueue::setTransactionHangCallback(std::function<void(bool)> callback) {
- std::unique_lock _lock{mMutex};
+void BLASTBufferQueue::setTransactionHangCallback(
+ std::function<void(const std::string&)> callback) {
+ std::lock_guard _lock{mMutex};
mTransactionHangCallback = callback;
}
diff --git a/libs/gui/BufferItemConsumer.cpp b/libs/gui/BufferItemConsumer.cpp
index f50bc203e8..e6331e7282 100644
--- a/libs/gui/BufferItemConsumer.cpp
+++ b/libs/gui/BufferItemConsumer.cpp
@@ -24,11 +24,11 @@
#include <gui/BufferItem.h>
#include <gui/BufferItemConsumer.h>
-#define BI_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define BI_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define BI_LOGI(x, ...) ALOGI("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define BI_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define BI_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
+#define BI_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define BI_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define BI_LOGI(x, ...) ALOGI("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define BI_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define BI_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__)
namespace android {
diff --git a/libs/gui/BufferQueue.cpp b/libs/gui/BufferQueue.cpp
index 66cad03fec..b0f6e69115 100644
--- a/libs/gui/BufferQueue.cpp
+++ b/libs/gui/BufferQueue.cpp
@@ -98,6 +98,16 @@ void BufferQueue::ProxyConsumerListener::addAndGetFrameTimestamps(
}
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+void BufferQueue::ProxyConsumerListener::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ sp<ConsumerListener> listener(mConsumerListener.promote());
+ if (listener != nullptr) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
void BufferQueue::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
bool consumerIsSurfaceFlinger) {
diff --git a/libs/gui/BufferQueueConsumer.cpp b/libs/gui/BufferQueueConsumer.cpp
index 7f7a0437f1..11f5174d76 100644
--- a/libs/gui/BufferQueueConsumer.cpp
+++ b/libs/gui/BufferQueueConsumer.cpp
@@ -33,11 +33,11 @@
#include <gui/BufferQueueCore.h>
#include <gui/IConsumerListener.h>
#include <gui/IProducerListener.h>
+#include <gui/TraceUtils.h>
#include <private/gui/BufferQueueThreadState.h>
-#ifndef __ANDROID_VNDK__
+#if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
#include <binder/PermissionCache.h>
-#include <vndksupport/linker.h>
#endif
#include <system/window.h>
@@ -46,23 +46,23 @@ namespace android {
// Macros for include BufferQueueCore information in log messages
#define BQ_LOGV(x, ...) \
- ALOGV("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGV("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGD(x, ...) \
- ALOGD("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGD("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGI(x, ...) \
- ALOGI("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGI("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGW(x, ...) \
- ALOGW("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGW("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGE(x, ...) \
- ALOGE("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGE("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
@@ -297,8 +297,7 @@ status_t BufferQueueConsumer::acquireBuffer(BufferItem* outBuffer,
// decrease.
mCore->mDequeueCondition.notify_all();
- ATRACE_INT(mCore->mConsumerName.string(),
- static_cast<int32_t>(mCore->mQueue.size()));
+ ATRACE_INT(mCore->mConsumerName.c_str(), static_cast<int32_t>(mCore->mQueue.size()));
#ifndef NO_BINDER
mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
#endif
@@ -318,35 +317,44 @@ status_t BufferQueueConsumer::detachBuffer(int slot) {
ATRACE_CALL();
ATRACE_BUFFER_INDEX(slot);
BQ_LOGV("detachBuffer: slot %d", slot);
- std::lock_guard<std::mutex> lock(mCore->mMutex);
+ sp<IProducerListener> listener;
+ {
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
- if (mCore->mIsAbandoned) {
- BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
- return NO_INIT;
- }
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("detachBuffer: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
- if (mCore->mSharedBufferMode || slot == mCore->mSharedBufferSlot) {
- BQ_LOGE("detachBuffer: detachBuffer not allowed in shared buffer mode");
- return BAD_VALUE;
- }
+ if (mCore->mSharedBufferMode || slot == mCore->mSharedBufferSlot) {
+ BQ_LOGE("detachBuffer: detachBuffer not allowed in shared buffer mode");
+ return BAD_VALUE;
+ }
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
- slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
- return BAD_VALUE;
- } else if (!mSlots[slot].mBufferState.isAcquired()) {
- BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
- "(state = %s)", slot, mSlots[slot].mBufferState.string());
- return BAD_VALUE;
- }
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("detachBuffer: slot index %d out of range [0, %d)",
+ slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mBufferState.isAcquired()) {
+ BQ_LOGE("detachBuffer: slot %d is not owned by the consumer "
+ "(state = %s)", slot, mSlots[slot].mBufferState.string());
+ return BAD_VALUE;
+ }
+ if (mCore->mBufferReleasedCbEnabled) {
+ listener = mCore->mConnectedProducerListener;
+ }
- mSlots[slot].mBufferState.detachConsumer();
- mCore->mActiveBuffers.erase(slot);
- mCore->mFreeSlots.insert(slot);
- mCore->clearBufferSlotLocked(slot);
- mCore->mDequeueCondition.notify_all();
- VALIDATE_CONSISTENCY();
+ mSlots[slot].mBufferState.detachConsumer();
+ mCore->mActiveBuffers.erase(slot);
+ mCore->mFreeSlots.insert(slot);
+ mCore->clearBufferSlotLocked(slot);
+ mCore->mDequeueCondition.notify_all();
+ VALIDATE_CONSISTENCY();
+ }
+ if (listener) {
+ listener->onBufferDetached(slot);
+ }
return NO_ERROR;
}
@@ -646,7 +654,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) {
@@ -717,7 +725,7 @@ status_t BufferQueueConsumer::setMaxAcquiredBufferCount(
status_t BufferQueueConsumer::setConsumerName(const String8& name) {
ATRACE_CALL();
- BQ_LOGV("setConsumerName: '%s'", name.string());
+ BQ_LOGV("setConsumerName: '%s'", name.c_str());
std::lock_guard<std::mutex> lock(mCore->mMutex);
mCore->mConsumerName = name;
mConsumerName = name;
@@ -802,18 +810,14 @@ status_t BufferQueueConsumer::dumpState(const String8& prefix, String8* outResul
const uid_t uid = BufferQueueThreadState::getCallingUid();
#if !defined(__ANDROID_VNDK__) && !defined(NO_BINDER)
// permission check can't be done for vendors as vendors have no access to
- // the PermissionController. We need to do a runtime check as well, since
- // the system variant of libgui can be loaded in a vendor process. For eg:
- // if a HAL uses an llndk library that depends on libgui (libmediandk etc).
- if (!android_is_in_vendor_process()) {
- const pid_t pid = BufferQueueThreadState::getCallingPid();
- if ((uid != shellUid) &&
- !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
- outResult->appendFormat("Permission Denial: can't dump BufferQueueConsumer "
- "from pid=%d, uid=%d\n",
- pid, uid);
- denied = true;
- }
+ // the PermissionController.
+ const pid_t pid = BufferQueueThreadState::getCallingPid();
+ if ((uid != shellUid) &&
+ !PermissionCache::checkPermission(String16("android.permission.DUMP"), pid, uid)) {
+ outResult->appendFormat("Permission Denial: can't dump BufferQueueConsumer "
+ "from pid=%d, uid=%d\n",
+ pid, uid);
+ denied = true;
}
#else
if (uid != shellUid) {
diff --git a/libs/gui/BufferQueueCore.cpp b/libs/gui/BufferQueueCore.cpp
index 2930154ad4..648db67fc1 100644
--- a/libs/gui/BufferQueueCore.cpp
+++ b/libs/gui/BufferQueueCore.cpp
@@ -41,20 +41,20 @@
namespace android {
// Macros for include BufferQueueCore information in log messages
-#define BQ_LOGV(x, ...) \
- ALOGV("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), mUniqueId, \
+#define BQ_LOGV(x, ...) \
+ ALOGV("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), mUniqueId, \
mConnectedApi, mConnectedPid, mUniqueId >> 32, ##__VA_ARGS__)
-#define BQ_LOGD(x, ...) \
- ALOGD("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), mUniqueId, \
+#define BQ_LOGD(x, ...) \
+ ALOGD("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), mUniqueId, \
mConnectedApi, mConnectedPid, mUniqueId >> 32, ##__VA_ARGS__)
-#define BQ_LOGI(x, ...) \
- ALOGI("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), mUniqueId, \
+#define BQ_LOGI(x, ...) \
+ ALOGI("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), mUniqueId, \
mConnectedApi, mConnectedPid, mUniqueId >> 32, ##__VA_ARGS__)
-#define BQ_LOGW(x, ...) \
- ALOGW("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), mUniqueId, \
+#define BQ_LOGW(x, ...) \
+ ALOGW("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), mUniqueId, \
mConnectedApi, mConnectedPid, mUniqueId >> 32, ##__VA_ARGS__)
-#define BQ_LOGE(x, ...) \
- ALOGE("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), mUniqueId, \
+#define BQ_LOGE(x, ...) \
+ ALOGE("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), mUniqueId, \
mConnectedApi, mConnectedPid, mUniqueId >> 32, ##__VA_ARGS__)
static String8 getUniqueName() {
@@ -146,23 +146,23 @@ BufferQueueCore::~BufferQueueCore() {}
void BufferQueueCore::dumpState(const String8& prefix, String8* outResult) const {
std::lock_guard<std::mutex> lock(mMutex);
- outResult->appendFormat("%s- BufferQueue ", prefix.string());
+ outResult->appendFormat("%s- BufferQueue ", prefix.c_str());
outResult->appendFormat("mMaxAcquiredBufferCount=%d mMaxDequeuedBufferCount=%d\n",
mMaxAcquiredBufferCount, mMaxDequeuedBufferCount);
- outResult->appendFormat("%s mDequeueBufferCannotBlock=%d mAsyncMode=%d\n", prefix.string(),
+ outResult->appendFormat("%s mDequeueBufferCannotBlock=%d mAsyncMode=%d\n", prefix.c_str(),
mDequeueBufferCannotBlock, mAsyncMode);
- outResult->appendFormat("%s mQueueBufferCanDrop=%d mLegacyBufferDrop=%d\n", prefix.string(),
+ outResult->appendFormat("%s mQueueBufferCanDrop=%d mLegacyBufferDrop=%d\n", prefix.c_str(),
mQueueBufferCanDrop, mLegacyBufferDrop);
- outResult->appendFormat("%s default-size=[%dx%d] default-format=%d ", prefix.string(),
+ outResult->appendFormat("%s default-size=[%dx%d] default-format=%d ", prefix.c_str(),
mDefaultWidth, mDefaultHeight, mDefaultBufferFormat);
- outResult->appendFormat("%s transform-hint=%02x frame-counter=%" PRIu64 "\n", prefix.string(),
+ outResult->appendFormat("%s transform-hint=%02x frame-counter=%" PRIu64 "\n", prefix.c_str(),
mTransformHint, mFrameCounter);
- outResult->appendFormat("%s mTransformHintInUse=%02x mAutoPrerotation=%d\n", prefix.string(),
+ outResult->appendFormat("%s mTransformHintInUse=%02x mAutoPrerotation=%d\n", prefix.c_str(),
mTransformHintInUse, mAutoPrerotation);
- outResult->appendFormat("%sFIFO(%zu):\n", prefix.string(), mQueue.size());
+ outResult->appendFormat("%sFIFO(%zu):\n", prefix.c_str(), mQueue.size());
- outResult->appendFormat("%s(mConsumerName=%s, ", prefix.string(), mConsumerName.string());
+ outResult->appendFormat("%s(mConsumerName=%s, ", prefix.c_str(), mConsumerName.c_str());
outResult->appendFormat("mConnectedApi=%d, mConsumerUsageBits=%" PRIu64 ", ", mConnectedApi,
mConsumerUsageBits);
@@ -173,12 +173,11 @@ void BufferQueueCore::dumpState(const String8& prefix, String8* outResult) const
getProcessName(mConnectedPid, producerProcName);
getProcessName(pid, consumerProcName);
outResult->appendFormat("mId=%" PRIx64 ", producer=[%d:%s], consumer=[%d:%s])\n", mUniqueId,
- mConnectedPid, producerProcName.string(), pid,
- consumerProcName.string());
+ mConnectedPid, producerProcName.c_str(), pid, consumerProcName.c_str());
Fifo::const_iterator current(mQueue.begin());
while (current != mQueue.end()) {
double timestamp = current->mTimestamp / 1e9;
- outResult->appendFormat("%s %02d:%p ", prefix.string(), current->mSlot,
+ outResult->appendFormat("%s %02d:%p ", prefix.c_str(), current->mSlot,
current->mGraphicBuffer.get());
outResult->appendFormat("crop=[%d,%d,%d,%d] ", current->mCrop.left, current->mCrop.top,
current->mCrop.right, current->mCrop.bottom);
@@ -187,12 +186,12 @@ void BufferQueueCore::dumpState(const String8& prefix, String8* outResult) const
++current;
}
- outResult->appendFormat("%sSlots:\n", prefix.string());
+ outResult->appendFormat("%sSlots:\n", prefix.c_str());
for (int s : mActiveBuffers) {
const sp<GraphicBuffer>& buffer(mSlots[s].mGraphicBuffer);
// A dequeued buffer might be null if it's still being allocated
if (buffer.get()) {
- outResult->appendFormat("%s %s[%02d:%p] ", prefix.string(),
+ outResult->appendFormat("%s %s[%02d:%p] ", prefix.c_str(),
(mSlots[s].mBufferState.isAcquired()) ? ">" : " ", s,
buffer.get());
outResult->appendFormat("state=%-8s %p frame=%" PRIu64, mSlots[s].mBufferState.string(),
@@ -200,14 +199,14 @@ void BufferQueueCore::dumpState(const String8& prefix, String8* outResult) const
outResult->appendFormat(" [%4ux%4u:%4u,%3X]\n", buffer->width, buffer->height,
buffer->stride, buffer->format);
} else {
- outResult->appendFormat("%s [%02d:%p] ", prefix.string(), s, buffer.get());
+ outResult->appendFormat("%s [%02d:%p] ", prefix.c_str(), s, buffer.get());
outResult->appendFormat("state=%-8s frame=%" PRIu64 "\n",
mSlots[s].mBufferState.string(), mSlots[s].mFrameNumber);
}
}
for (int s : mFreeBuffers) {
const sp<GraphicBuffer>& buffer(mSlots[s].mGraphicBuffer);
- outResult->appendFormat("%s [%02d:%p] ", prefix.string(), s, buffer.get());
+ outResult->appendFormat("%s [%02d:%p] ", prefix.c_str(), s, buffer.get());
outResult->appendFormat("state=%-8s %p frame=%" PRIu64, mSlots[s].mBufferState.string(),
buffer->handle, mSlots[s].mFrameNumber);
outResult->appendFormat(" [%4ux%4u:%4u,%3X]\n", buffer->width, buffer->height,
@@ -216,7 +215,7 @@ void BufferQueueCore::dumpState(const String8& prefix, String8* outResult) const
for (int s : mFreeSlots) {
const sp<GraphicBuffer>& buffer(mSlots[s].mGraphicBuffer);
- outResult->appendFormat("%s [%02d:%p] state=%-8s\n", prefix.string(), s, buffer.get(),
+ outResult->appendFormat("%s [%02d:%p] state=%-8s\n", prefix.c_str(), s, buffer.get(),
mSlots[s].mBufferState.string());
}
}
diff --git a/libs/gui/BufferQueueProducer.cpp b/libs/gui/BufferQueueProducer.cpp
index 5fe5e71db9..19693e37cf 100644
--- a/libs/gui/BufferQueueProducer.cpp
+++ b/libs/gui/BufferQueueProducer.cpp
@@ -32,9 +32,12 @@
#include <gui/BufferItem.h>
#include <gui/BufferQueueCore.h>
#include <gui/BufferQueueProducer.h>
+
+#include <gui/FrameRateUtils.h>
#include <gui/GLConsumer.h>
#include <gui/IConsumerListener.h>
#include <gui/IProducerListener.h>
+#include <gui/TraceUtils.h>
#include <private/gui/BufferQueueThreadState.h>
#include <utils/Log.h>
@@ -46,23 +49,23 @@ namespace android {
// Macros for include BufferQueueCore information in log messages
#define BQ_LOGV(x, ...) \
- ALOGV("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGV("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGD(x, ...) \
- ALOGD("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGD("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGI(x, ...) \
- ALOGI("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGI("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGW(x, ...) \
- ALOGW("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGW("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
#define BQ_LOGE(x, ...) \
- ALOGE("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.string(), \
+ ALOGE("[%s](id:%" PRIx64 ",api:%d,p:%d,c:%" PRIu64 ") " x, mConsumerName.c_str(), \
mCore->mUniqueId, mCore->mConnectedApi, mCore->mConnectedPid, (mCore->mUniqueId) >> 32, \
##__VA_ARGS__)
@@ -119,7 +122,13 @@ status_t BufferQueueProducer::requestBuffer(int slot, sp<GraphicBuffer>* buf) {
status_t BufferQueueProducer::setMaxDequeuedBufferCount(
int maxDequeuedBuffers) {
- ATRACE_CALL();
+ int maxBufferCount;
+ return setMaxDequeuedBufferCount(maxDequeuedBuffers, &maxBufferCount);
+}
+
+status_t BufferQueueProducer::setMaxDequeuedBufferCount(int maxDequeuedBuffers,
+ int* maxBufferCount) {
+ ATRACE_FORMAT("%s(%d)", __func__, maxDequeuedBuffers);
BQ_LOGV("setMaxDequeuedBufferCount: maxDequeuedBuffers = %d",
maxDequeuedBuffers);
@@ -134,6 +143,8 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount(
return NO_INIT;
}
+ *maxBufferCount = mCore->getMaxBufferCountLocked();
+
if (maxDequeuedBuffers == mCore->mMaxDequeuedBufferCount) {
return NO_ERROR;
}
@@ -183,6 +194,7 @@ status_t BufferQueueProducer::setMaxDequeuedBufferCount(
return BAD_VALUE;
}
mCore->mMaxDequeuedBufferCount = maxDequeuedBuffers;
+ *maxBufferCount = mCore->getMaxBufferCountLocked();
VALIDATE_CONSISTENCY();
if (delta < 0) {
listener = mCore->mConsumerListener;
@@ -408,6 +420,9 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
EGLSyncKHR eglFence = EGL_NO_SYNC_KHR;
bool attachedByConsumer = false;
+ sp<IConsumerListener> listener;
+ bool callOnFrameDequeued = false;
+ uint64_t bufferId = 0; // Only used if callOnFrameDequeued == true
{ // Autolock scope
std::unique_lock<std::mutex> lock(mCore->mMutex);
@@ -493,6 +508,20 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* 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;
@@ -537,17 +566,18 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
}
if (!(returnFlags & BUFFER_NEEDS_REALLOCATION)) {
- if (mCore->mConsumerListener != nullptr) {
- mCore->mConsumerListener->onFrameDequeued(mSlots[*outSlot].mGraphicBuffer->getId());
- }
+ callOnFrameDequeued = true;
+ bufferId = mSlots[*outSlot].mGraphicBuffer->getId();
}
+
+ listener = mCore->mConsumerListener;
} // Autolock scope
if (returnFlags & BUFFER_NEEDS_REALLOCATION) {
BQ_LOGV("dequeueBuffer: allocating a new buffer for slot %d", *outSlot);
- sp<GraphicBuffer> graphicBuffer = new GraphicBuffer(
- width, height, format, BQ_LAYER_COUNT, usage,
- {mConsumerName.string(), mConsumerName.size()});
+ sp<GraphicBuffer> graphicBuffer =
+ new GraphicBuffer(width, height, format, BQ_LAYER_COUNT, usage,
+ {mConsumerName.c_str(), mConsumerName.size()});
status_t error = graphicBuffer->initCheck();
@@ -557,10 +587,8 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
if (error == NO_ERROR && !mCore->mIsAbandoned) {
graphicBuffer->setGenerationNumber(mCore->mGenerationNumber);
mSlots[*outSlot].mGraphicBuffer = graphicBuffer;
- if (mCore->mConsumerListener != nullptr) {
- mCore->mConsumerListener->onFrameDequeued(
- mSlots[*outSlot].mGraphicBuffer->getId());
- }
+ callOnFrameDequeued = true;
+ bufferId = mSlots[*outSlot].mGraphicBuffer->getId();
}
mCore->mIsAllocating = false;
@@ -584,6 +612,10 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
} // Autolock scope
}
+ if (listener != nullptr && callOnFrameDequeued) {
+ listener->onFrameDequeued(bufferId);
+ }
+
if (attachedByConsumer) {
returnFlags |= BUFFER_NEEDS_REALLOCATION;
}
@@ -606,7 +638,8 @@ status_t BufferQueueProducer::dequeueBuffer(int* outSlot, sp<android::Fence>* ou
BQ_LOGV("dequeueBuffer: returning slot=%d/%" PRIu64 " buf=%p flags=%#x",
*outSlot,
mSlots[*outSlot].mFrameNumber,
- mSlots[*outSlot].mGraphicBuffer->handle, returnFlags);
+ mSlots[*outSlot].mGraphicBuffer != nullptr ?
+ mSlots[*outSlot].mGraphicBuffer->handle : nullptr, returnFlags);
if (outBufferAge) {
*outBufferAge = mCore->mBufferAge;
@@ -622,6 +655,8 @@ status_t BufferQueueProducer::detachBuffer(int slot) {
BQ_LOGV("detachBuffer: slot %d", slot);
sp<IConsumerListener> listener;
+ bool callOnFrameDetached = false;
+ uint64_t bufferId = 0; // Only used if callOnFrameDetached is true
{
std::lock_guard<std::mutex> lock(mCore->mMutex);
@@ -659,8 +694,9 @@ status_t BufferQueueProducer::detachBuffer(int slot) {
listener = mCore->mConsumerListener;
auto gb = mSlots[slot].mGraphicBuffer;
- if (listener != nullptr && gb != nullptr) {
- listener->onFrameDetached(gb->getId());
+ if (gb != nullptr) {
+ callOnFrameDetached = true;
+ bufferId = gb->getId();
}
mSlots[slot].mBufferState.detachProducer();
mCore->mActiveBuffers.erase(slot);
@@ -670,6 +706,10 @@ status_t BufferQueueProducer::detachBuffer(int slot) {
VALIDATE_CONSISTENCY();
}
+ if (listener != nullptr && callOnFrameDetached) {
+ listener->onFrameDetached(bufferId);
+ }
+
if (listener != nullptr) {
listener->onBuffersReleased();
}
@@ -1008,8 +1048,7 @@ status_t BufferQueueProducer::queueBuffer(int slot,
output->numPendingBuffers = static_cast<uint32_t>(mCore->mQueue.size());
output->nextFrameNumber = mCore->mFrameCounter + 1;
- ATRACE_INT(mCore->mConsumerName.string(),
- static_cast<int32_t>(mCore->mQueue.size()));
+ ATRACE_INT(mCore->mConsumerName.c_str(), static_cast<int32_t>(mCore->mQueue.size()));
#ifndef NO_BINDER
mCore->mOccupancyTracker.registerOccupancyChange(mCore->mQueue.size());
#endif
@@ -1080,57 +1119,70 @@ status_t BufferQueueProducer::queueBuffer(int slot,
status_t BufferQueueProducer::cancelBuffer(int slot, const sp<Fence>& fence) {
ATRACE_CALL();
BQ_LOGV("cancelBuffer: slot %d", slot);
- std::lock_guard<std::mutex> lock(mCore->mMutex);
- if (mCore->mIsAbandoned) {
- BQ_LOGE("cancelBuffer: BufferQueue has been abandoned");
- return NO_INIT;
- }
+ sp<IConsumerListener> listener;
+ bool callOnFrameCancelled = false;
+ uint64_t bufferId = 0; // Only used if callOnFrameCancelled == true
+ {
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
- if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
- BQ_LOGE("cancelBuffer: BufferQueue has no connected producer");
- return NO_INIT;
- }
+ if (mCore->mIsAbandoned) {
+ BQ_LOGE("cancelBuffer: BufferQueue has been abandoned");
+ return NO_INIT;
+ }
- if (mCore->mSharedBufferMode) {
- BQ_LOGE("cancelBuffer: cannot cancel a buffer in shared buffer mode");
- return BAD_VALUE;
- }
+ if (mCore->mConnectedApi == BufferQueueCore::NO_CONNECTED_API) {
+ BQ_LOGE("cancelBuffer: BufferQueue has no connected producer");
+ return NO_INIT;
+ }
- if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
- BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)",
- slot, BufferQueueDefs::NUM_BUFFER_SLOTS);
- return BAD_VALUE;
- } else if (!mSlots[slot].mBufferState.isDequeued()) {
- BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
- "(state = %s)", slot, mSlots[slot].mBufferState.string());
- return BAD_VALUE;
- } else if (fence == nullptr) {
- BQ_LOGE("cancelBuffer: fence is NULL");
- return BAD_VALUE;
- }
+ if (mCore->mSharedBufferMode) {
+ BQ_LOGE("cancelBuffer: cannot cancel a buffer in shared buffer mode");
+ return BAD_VALUE;
+ }
+
+ if (slot < 0 || slot >= BufferQueueDefs::NUM_BUFFER_SLOTS) {
+ BQ_LOGE("cancelBuffer: slot index %d out of range [0, %d)", slot,
+ BufferQueueDefs::NUM_BUFFER_SLOTS);
+ return BAD_VALUE;
+ } else if (!mSlots[slot].mBufferState.isDequeued()) {
+ BQ_LOGE("cancelBuffer: slot %d is not owned by the producer "
+ "(state = %s)",
+ slot, mSlots[slot].mBufferState.string());
+ return BAD_VALUE;
+ } else if (fence == nullptr) {
+ BQ_LOGE("cancelBuffer: fence is NULL");
+ return BAD_VALUE;
+ }
- mSlots[slot].mBufferState.cancel();
+ mSlots[slot].mBufferState.cancel();
- // After leaving shared buffer mode, the shared buffer will still be around.
- // Mark it as no longer shared if this operation causes it to be free.
- if (!mCore->mSharedBufferMode && mSlots[slot].mBufferState.isFree()) {
- mSlots[slot].mBufferState.mShared = false;
- }
+ // After leaving shared buffer mode, the shared buffer will still be around.
+ // Mark it as no longer shared if this operation causes it to be free.
+ if (!mCore->mSharedBufferMode && mSlots[slot].mBufferState.isFree()) {
+ mSlots[slot].mBufferState.mShared = false;
+ }
- // Don't put the shared buffer on the free list.
- if (!mSlots[slot].mBufferState.isShared()) {
- mCore->mActiveBuffers.erase(slot);
- mCore->mFreeBuffers.push_back(slot);
+ // Don't put the shared buffer on the free list.
+ if (!mSlots[slot].mBufferState.isShared()) {
+ mCore->mActiveBuffers.erase(slot);
+ mCore->mFreeBuffers.push_back(slot);
+ }
+
+ auto gb = mSlots[slot].mGraphicBuffer;
+ if (gb != nullptr) {
+ callOnFrameCancelled = true;
+ bufferId = gb->getId();
+ }
+ mSlots[slot].mFence = fence;
+ mCore->mDequeueCondition.notify_all();
+ listener = mCore->mConsumerListener;
+ VALIDATE_CONSISTENCY();
}
- auto gb = mSlots[slot].mGraphicBuffer;
- if (mCore->mConsumerListener != nullptr && gb != nullptr) {
- mCore->mConsumerListener->onFrameCancelled(gb->getId());
+ if (listener != nullptr && callOnFrameCancelled) {
+ listener->onFrameCancelled(bufferId);
}
- mSlots[slot].mFence = fence;
- mCore->mDequeueCondition.notify_all();
- VALIDATE_CONSISTENCY();
return NO_ERROR;
}
@@ -1436,7 +1488,7 @@ void BufferQueueProducer::allocateBuffers(uint32_t width, uint32_t height,
allocFormat = format != 0 ? format : mCore->mDefaultBufferFormat;
allocUsage = usage | mCore->mConsumerUsageBits;
- allocName.assign(mCore->mConsumerName.string(), mCore->mConsumerName.size());
+ allocName.assign(mCore->mConsumerName.c_str(), mCore->mConsumerName.size());
mCore->mIsAllocating = true;
} // Autolock scope
@@ -1538,7 +1590,7 @@ status_t BufferQueueProducer::setGenerationNumber(uint32_t generationNumber) {
String8 BufferQueueProducer::getConsumerName() const {
ATRACE_CALL();
std::lock_guard<std::mutex> lock(mCore->mMutex);
- BQ_LOGV("getConsumerName: %s", mConsumerName.string());
+ BQ_LOGV("getConsumerName: %s", mConsumerName.c_str());
return mConsumerName;
}
@@ -1701,4 +1753,27 @@ status_t BufferQueueProducer::setAutoPrerotation(bool autoPrerotation) {
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+status_t BufferQueueProducer::setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ ATRACE_CALL();
+ BQ_LOGV("setFrameRate: %.2f", frameRate);
+
+ if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
+ "BufferQueueProducer::setFrameRate")) {
+ return BAD_VALUE;
+ }
+
+ sp<IConsumerListener> listener;
+ {
+ std::lock_guard<std::mutex> lock(mCore->mMutex);
+ listener = mCore->mConsumerListener;
+ }
+ if (listener != nullptr) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+ return NO_ERROR;
+}
+#endif
+
} // namespace android
diff --git a/libs/gui/Choreographer.cpp b/libs/gui/Choreographer.cpp
new file mode 100644
index 0000000000..79c5fd1006
--- /dev/null
+++ b/libs/gui/Choreographer.cpp
@@ -0,0 +1,403 @@
+/*
+ * 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 <gui/Choreographer.h>
+#include <gui/TraceUtils.h>
+#include <jni.h>
+
+#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<jclass>(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 = 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>& looper, const sp<IBinder>& layerHandle)
+ : DisplayEventDispatcher(looper, gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp, {},
+ layerHandle),
+ mLooper(looper),
+ mThreadId(std::this_thread::get_id()) {
+ std::lock_guard<std::mutex> _l(gChoreographers.lock);
+ gChoreographers.ptrs.push_back(this);
+}
+
+Choreographer::~Choreographer() {
+ std::lock_guard<std::mutex> _l(gChoreographers.lock);
+ gChoreographers.ptrs.erase(std::remove_if(gChoreographers.ptrs.begin(),
+ gChoreographers.ptrs.end(),
+ [=, this](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<std::mutex> _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<std::mutex> _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<std::mutex> _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<int64_t>(this));
+ env->DeleteLocalRef(dmg);
+ {
+ std::lock_guard<std::mutex> _l2(gChoreographers.lock);
+ gChoreographers.registeredToDisplayManager = true;
+ }
+ }
+ } else {
+ scheduleLatestConfigRequest();
+ }
+}
+
+void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb,
+ void* data) {
+ std::lock_guard<std::mutex> _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<std::mutex> _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<RefreshRateCallback> callbacks{};
+ const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load();
+ const nsecs_t lastPeriod = mLatestVsyncPeriod;
+ if (pendingPeriod > 0) {
+ mLatestVsyncPeriod = pendingPeriod;
+ }
+ {
+ std::lock_guard<std::mutex> _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<FrameCallback> callbacks{};
+ {
+ std::lock_guard<std::mutex> _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<const AChoreographerFrameCallbackData*>(
+ &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::dispatchHotplugConnectionError(nsecs_t, int32_t connectionError) {
+ ALOGV("choreographer %p ~ received hotplug connection error event (connectionError=%d), "
+ "ignoring.",
+ this, connectionError);
+}
+
+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<FrameRateOverride>) {
+ 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<std::mutex> _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
diff --git a/libs/gui/CompositorTiming.cpp b/libs/gui/CompositorTiming.cpp
new file mode 100644
index 0000000000..50f7b252b6
--- /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 <cutils/compiler.h>
+#include <gui/CompositorTiming.h>
+#include <log/log.h>
+
+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/ConsumerBase.cpp b/libs/gui/ConsumerBase.cpp
index 9f91d9d3aa..b625c3f75e 100644
--- a/libs/gui/ConsumerBase.cpp
+++ b/libs/gui/ConsumerBase.cpp
@@ -41,11 +41,11 @@
#include <utils/Trace.h>
// Macros for including the ConsumerBase name in log messages
-#define CB_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define CB_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define CB_LOGI(x, ...) ALOGI("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define CB_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define CB_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
+#define CB_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define CB_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define CB_LOGI(x, ...) ALOGI("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define CB_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define CB_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__)
namespace android {
@@ -86,8 +86,10 @@ ConsumerBase::~ConsumerBase() {
// be done by ConsumerBase::onLastStrongRef(), but it's possible for a
// derived class to override that method and not call
// ConsumerBase::onLastStrongRef().
- LOG_ALWAYS_FATAL_IF(!mAbandoned, "[%s] ~ConsumerBase was called, but the "
- "consumer is not abandoned!", mName.string());
+ LOG_ALWAYS_FATAL_IF(!mAbandoned,
+ "[%s] ~ConsumerBase was called, but the "
+ "consumer is not abandoned!",
+ mName.c_str());
}
void ConsumerBase::onLastStrongRef(const void* id __attribute__((unused))) {
@@ -451,7 +453,7 @@ status_t ConsumerBase::addReleaseFenceLocked(int slot,
// them to get an accurate timestamp.
if (currentStatus == incomingStatus) {
char fenceName[32] = {};
- snprintf(fenceName, 32, "%.28s:%d", mName.string(), slot);
+ snprintf(fenceName, 32, "%.28s:%d", mName.c_str(), slot);
sp<Fence> mergedFence = Fence::merge(
fenceName, mSlots[slot].mFence, fence);
if (!mergedFence.get()) {
diff --git a/libs/gui/CpuConsumer.cpp b/libs/gui/CpuConsumer.cpp
index a62697064b..3031fa11fc 100644
--- a/libs/gui/CpuConsumer.cpp
+++ b/libs/gui/CpuConsumer.cpp
@@ -23,11 +23,11 @@
#include <gui/BufferItem.h>
#include <utils/Log.h>
-#define CC_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define CC_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define CC_LOGI(x, ...) ALOGI("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define CC_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define CC_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
+#define CC_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define CC_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define CC_LOGI(x, ...) ALOGI("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define CC_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define CC_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__)
namespace android {
diff --git a/libs/gui/DisplayEventDispatcher.cpp b/libs/gui/DisplayEventDispatcher.cpp
index dfdce20438..5dd058cf9f 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>& looper, ISurfaceComposer::VsyncSource vsyncSource,
- ISurfaceComposer::EventRegistrationFlags eventRegistration)
- : mLooper(looper), mReceiver(vsyncSource, eventRegistration), mWaitingForVsync(false),
- mLastVsyncCount(0), mLastScheduleVsyncTime(0) {
+DisplayEventDispatcher::DisplayEventDispatcher(const sp<Looper>& looper,
+ gui::ISurfaceComposer::VsyncSource vsyncSource,
+ EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle)
+ : mLooper(looper),
+ mReceiver(vsyncSource, eventRegistration, layerHandle),
+ mWaitingForVsync(false),
+ mLastVsyncCount(0),
+ mLastScheduleVsyncTime(0) {
ALOGV("dispatcher %p ~ Initializing display event dispatcher.", this);
}
@@ -169,7 +173,13 @@ bool DisplayEventDispatcher::processPendingEvents(nsecs_t* outTimestamp,
*outVsyncEventData = ev.vsync.vsyncData;
break;
case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
- dispatchHotplug(ev.header.timestamp, ev.header.displayId, ev.hotplug.connected);
+ if (ev.hotplug.connectionError == 0) {
+ dispatchHotplug(ev.header.timestamp, ev.header.displayId,
+ ev.hotplug.connected);
+ } else {
+ dispatchHotplugConnectionError(ev.header.timestamp,
+ ev.hotplug.connectionError);
+ }
break;
case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE:
dispatchModeChanged(ev.header.timestamp, ev.header.displayId,
diff --git a/libs/gui/DisplayEventReceiver.cpp b/libs/gui/DisplayEventReceiver.cpp
index bfb77699c0..67cbc7b111 100644
--- a/libs/gui/DisplayEventReceiver.cpp
+++ b/libs/gui/DisplayEventReceiver.cpp
@@ -14,15 +14,16 @@
* limitations under the License.
*/
+#define LOG_TAG "DisplayEventReceiver"
+
#include <string.h>
#include <utils/Errors.h>
#include <gui/DisplayEventReceiver.h>
-#include <gui/ISurfaceComposer.h>
#include <gui/VsyncEventData.h>
-#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
#include <private/gui/BitTube.h>
@@ -32,21 +33,29 @@ namespace android {
// ---------------------------------------------------------------------------
-DisplayEventReceiver::DisplayEventReceiver(
- ISurfaceComposer::VsyncSource vsyncSource,
- ISurfaceComposer::EventRegistrationFlags eventRegistration) {
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+DisplayEventReceiver::DisplayEventReceiver(gui::ISurfaceComposer::VsyncSource vsyncSource,
+ EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle) {
+ sp<gui::ISurfaceComposer> 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<gui::BitTube>();
- 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_t>(status.transactionError());
mDataChannel.reset();
mEventConnection.clear();
}
+ } else {
+ ALOGE("DisplayEventConnection creation failed: status=%s", status.toString8().c_str());
}
}
}
@@ -90,7 +99,7 @@ status_t DisplayEventReceiver::getLatestVsyncEventData(
if (mEventConnection != nullptr) {
auto status = mEventConnection->getLatestVsyncEventData(outVsyncEventData);
if (!status.isOk()) {
- ALOGE("Failed to get latest vsync event data: %s", status.exceptionMessage().c_str());
+ ALOGE("Failed to get latest vsync event data: %s", status.toString8().c_str());
return status.transactionError();
}
return NO_ERROR;
diff --git a/libs/gui/DisplayInfo.cpp b/libs/gui/DisplayInfo.cpp
index 52d9540eeb..bd640df81e 100644
--- a/libs/gui/DisplayInfo.cpp
+++ b/libs/gui/DisplayInfo.cpp
@@ -20,8 +20,13 @@
#include <gui/DisplayInfo.h>
#include <private/gui/ParcelUtils.h>
+#include <android-base/stringprintf.h>
#include <log/log.h>
+#include <inttypes.h>
+
+#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 0000000000..230c81a0b3
--- /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 <gui/FenceMonitor.h>
+#include <gui/TraceUtils.h>
+#include <log/log.h>
+
+#include <thread>
+
+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>& fence) {
+ char message[64];
+
+ std::lock_guard<std::mutex> 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> fence;
+ uint32_t fenceNum;
+ {
+ std::unique_lock<std::mutex> 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<std::mutex> 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/FrameRateUtils.cpp b/libs/gui/FrameRateUtils.cpp
new file mode 100644
index 0000000000..11524e2b51
--- /dev/null
+++ b/libs/gui/FrameRateUtils.cpp
@@ -0,0 +1,67 @@
+/*
+ * 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 <gui/FrameRateUtils.h>
+#include <system/window.h>
+#include <utils/Log.h>
+
+#include <cmath>
+
+#include <com_android_graphics_libgui_flags.h>
+
+namespace android {
+using namespace com::android::graphics::libgui;
+// Returns true if the frameRate is valid.
+//
+// @param frameRate the frame rate in Hz
+// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
+// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
+// @param functionName calling function or nullptr. Used for logging
+// @param privileged whether caller has unscoped surfaceflinger access
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+ const char* inFunctionName, bool privileged) {
+ const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
+ int floatClassification = std::fpclassify(frameRate);
+ if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
+ ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
+ return false;
+ }
+
+ if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
+ compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
+ (!privileged ||
+ (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
+ compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
+ ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
+ compatibility, privileged ? "yes" : "no");
+ return false;
+ }
+
+ if (__builtin_available(android 31, *)) {
+ if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
+ changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
+ ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
+ changeFrameRateStrategy);
+ if (flags::bq_setframerate()) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+} // namespace android
diff --git a/libs/gui/FrameTimelineInfo.cpp b/libs/gui/FrameTimelineInfo.cpp
deleted file mode 100644
index 3800b88ab0..0000000000
--- 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 <inttypes.h>
-
-#include <android/os/IInputConstants.h>
-#include <gui/FrameTimelineInfo.h>
-#include <gui/LayerState.h>
-#include <private/gui/ParcelUtils.h>
-#include <utils/Errors.h>
-
-#include <cmath>
-
-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/FrameTimestamps.cpp b/libs/gui/FrameTimestamps.cpp
index e2ea3f9ab1..afb09de94b 100644
--- a/libs/gui/FrameTimestamps.cpp
+++ b/libs/gui/FrameTimestamps.cpp
@@ -168,10 +168,11 @@ struct FrameNumberEqual {
} // namespace
-const size_t FrameEventHistory::MAX_FRAME_HISTORY =
+const size_t FrameEventHistory::INITIAL_MAX_FRAME_HISTORY =
sysprop::LibGuiProperties::frame_event_history_size().value_or(8);
-FrameEventHistory::FrameEventHistory() : mFrames(std::vector<FrameEvents>(MAX_FRAME_HISTORY)) {}
+FrameEventHistory::FrameEventHistory()
+ : mFrames(std::vector<FrameEvents>(INITIAL_MAX_FRAME_HISTORY)) {}
FrameEventHistory::~FrameEventHistory() = default;
@@ -227,7 +228,6 @@ void FrameEventHistory::dump(std::string& outString) const {
}
}
-
// ============================================================================
// ProducerFrameEventHistory
// ============================================================================
@@ -255,7 +255,6 @@ void ProducerFrameEventHistory::updateAcquireFence(
uint64_t frameNumber, std::shared_ptr<FenceTime>&& acquire) {
FrameEvents* frame = getFrame(frameNumber, &mAcquireOffset);
if (frame == nullptr) {
- ALOGE("updateAcquireFence: Did not find frame.");
return;
}
@@ -273,6 +272,13 @@ void ProducerFrameEventHistory::applyDelta(
const FrameEventHistoryDelta& delta) {
mCompositorTiming = delta.mCompositorTiming;
+ // Deltas should have enough reserved capacity for the consumer-side, therefore if there's a
+ // different capacity, we re-sized on the consumer side and now need to resize on the producer
+ // side.
+ if (delta.mDeltas.capacity() > mFrames.capacity()) {
+ resize(delta.mDeltas.capacity());
+ }
+
for (auto& d : delta.mDeltas) {
// Avoid out-of-bounds access.
if (CC_UNLIKELY(d.mIndex >= mFrames.size())) {
@@ -349,13 +355,48 @@ std::shared_ptr<FenceTime> ProducerFrameEventHistory::createFenceTime(
return std::make_shared<FenceTime>(fence);
}
+void ProducerFrameEventHistory::resize(size_t newSize) {
+ // we don't want to drop events by resizing too small, so don't resize in the negative direction
+ if (newSize <= mFrames.size()) {
+ return;
+ }
+
+ // This algorithm for resizing needs to be the same as ConsumerFrameEventHistory::resize,
+ // because the indexes need to match when communicating the FrameEventDeltas.
+
+ // We need to find the oldest frame, because that frame needs to move to index 0 in the new
+ // frame history.
+ size_t oldestFrameIndex = 0;
+ size_t oldestFrameNumber = INT32_MAX;
+ for (size_t i = 0; i < mFrames.size(); ++i) {
+ if (mFrames[i].frameNumber < oldestFrameNumber && mFrames[i].valid) {
+ oldestFrameNumber = mFrames[i].frameNumber;
+ oldestFrameIndex = i;
+ }
+ }
+
+ // move the existing frame information into a new vector, so that the oldest frames are at
+ // index 0, and the latest frames are at the end of the vector
+ std::vector<FrameEvents> newFrames(newSize);
+ size_t oldI = oldestFrameIndex;
+ size_t newI = 0;
+ do {
+ if (mFrames[oldI].valid) {
+ newFrames[newI++] = std::move(mFrames[oldI]);
+ }
+ oldI = (oldI + 1) % mFrames.size();
+ } while (oldI != oldestFrameIndex);
+
+ mFrames = std::move(newFrames);
+ mAcquireOffset = 0; // this is just a hint, so setting this to anything is fine
+}
// ============================================================================
// ConsumerFrameEventHistory
// ============================================================================
ConsumerFrameEventHistory::ConsumerFrameEventHistory()
- : mFramesDirty(std::vector<FrameEventDirtyFields>(MAX_FRAME_HISTORY)) {}
+ : mFramesDirty(std::vector<FrameEventDirtyFields>(INITIAL_MAX_FRAME_HISTORY)) {}
ConsumerFrameEventHistory::~ConsumerFrameEventHistory() = default;
@@ -489,6 +530,36 @@ void ConsumerFrameEventHistory::getAndResetDelta(
}
}
+void ConsumerFrameEventHistory::resize(size_t newSize) {
+ // we don't want to drop events by resizing too small, so don't resize in the negative direction
+ if (newSize <= mFrames.size()) {
+ return;
+ }
+
+ // This algorithm for resizing needs to be the same as ProducerFrameEventHistory::resize,
+ // because the indexes need to match when communicating the FrameEventDeltas.
+
+ // move the existing frame information into a new vector, so that the oldest frames are at
+ // index 0, and the latest frames are towards the end of the vector
+ std::vector<FrameEvents> newFrames(newSize);
+ std::vector<FrameEventDirtyFields> newFramesDirty(newSize);
+ size_t oldestFrameIndex = mQueueOffset;
+ size_t oldI = oldestFrameIndex;
+ size_t newI = 0;
+ do {
+ if (mFrames[oldI].valid) {
+ newFrames[newI] = std::move(mFrames[oldI]);
+ newFramesDirty[newI] = mFramesDirty[oldI];
+ newI += 1;
+ }
+ oldI = (oldI + 1) % mFrames.size();
+ } while (oldI != oldestFrameIndex);
+
+ mFrames = std::move(newFrames);
+ mFramesDirty = std::move(newFramesDirty);
+ mQueueOffset = newI;
+ mCompositionOffset = 0; // this is just a hint, so setting this to anything is fine
+}
// ============================================================================
// FrameEventsDelta
@@ -558,8 +629,7 @@ status_t FrameEventsDelta::flatten(void*& buffer, size_t& size, int*& fds,
return NO_MEMORY;
}
- if (mIndex >= FrameEventHistory::MAX_FRAME_HISTORY ||
- mIndex > std::numeric_limits<uint16_t>::max()) {
+ if (mIndex >= UINT8_MAX || mIndex < 0) {
return BAD_VALUE;
}
@@ -601,7 +671,7 @@ status_t FrameEventsDelta::unflatten(void const*& buffer, size_t& size,
uint16_t temp16 = 0;
FlattenableUtils::read(buffer, size, temp16);
mIndex = temp16;
- if (mIndex >= FrameEventHistory::MAX_FRAME_HISTORY) {
+ if (mIndex >= UINT8_MAX) {
return BAD_VALUE;
}
uint8_t temp8 = 0;
@@ -627,6 +697,25 @@ status_t FrameEventsDelta::unflatten(void const*& buffer, size_t& size,
return NO_ERROR;
}
+uint64_t FrameEventsDelta::getFrameNumber() const {
+ return mFrameNumber;
+}
+
+bool FrameEventsDelta::getLatchTime(nsecs_t* latchTime) const {
+ if (mLatchTime == FrameEvents::TIMESTAMP_PENDING) {
+ return false;
+ }
+ *latchTime = mLatchTime;
+ return true;
+}
+
+bool FrameEventsDelta::getDisplayPresentFence(sp<Fence>* fence) const {
+ if (mDisplayPresentFence.fence == Fence::NO_FENCE) {
+ return false;
+ }
+ *fence = mDisplayPresentFence.fence;
+ return true;
+}
// ============================================================================
// FrameEventHistoryDelta
@@ -665,7 +754,7 @@ size_t FrameEventHistoryDelta::getFdCount() const {
status_t FrameEventHistoryDelta::flatten(
void*& buffer, size_t& size, int*& fds, size_t& count) const {
- if (mDeltas.size() > FrameEventHistory::MAX_FRAME_HISTORY) {
+ if (mDeltas.size() > UINT8_MAX) {
return BAD_VALUE;
}
if (size < getFlattenedSize()) {
@@ -695,7 +784,7 @@ status_t FrameEventHistoryDelta::unflatten(
uint32_t deltaCount = 0;
FlattenableUtils::read(buffer, size, deltaCount);
- if (deltaCount > FrameEventHistory::MAX_FRAME_HISTORY) {
+ if (deltaCount > UINT8_MAX) {
return BAD_VALUE;
}
mDeltas.resize(deltaCount);
@@ -708,5 +797,12 @@ status_t FrameEventHistoryDelta::unflatten(
return NO_ERROR;
}
+std::vector<FrameEventsDelta>::const_iterator FrameEventHistoryDelta::begin() const {
+ return mDeltas.begin();
+}
+
+std::vector<FrameEventsDelta>::const_iterator FrameEventHistoryDelta::end() const {
+ return mDeltas.end();
+}
} // namespace android
diff --git a/libs/gui/GLConsumer.cpp b/libs/gui/GLConsumer.cpp
index b3647d6126..d49489c5a8 100644
--- a/libs/gui/GLConsumer.cpp
+++ b/libs/gui/GLConsumer.cpp
@@ -52,11 +52,11 @@
namespace android {
// Macros for including the GLConsumer name in log messages
-#define GLC_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define GLC_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define GLC_LOGI(x, ...) ALOGI("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define GLC_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define GLC_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
+#define GLC_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define GLC_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+// #define GLC_LOGI(x, ...) ALOGI("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define GLC_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define GLC_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__)
static const struct {
uint32_t width, height;
diff --git a/libs/gui/GLConsumerUtils.cpp b/libs/gui/GLConsumerUtils.cpp
index 7a06c3d801..a1c69e7d6d 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<GraphicBuffer>& 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/IGraphicBufferProducer.cpp b/libs/gui/IGraphicBufferProducer.cpp
index 918ff2dd25..e81c098b85 100644
--- a/libs/gui/IGraphicBufferProducer.cpp
+++ b/libs/gui/IGraphicBufferProducer.cpp
@@ -27,11 +27,12 @@
#include <binder/Parcel.h>
#include <binder/IInterface.h>
-#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
-#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
#include <gui/BufferQueueDefs.h>
+
#include <gui/IGraphicBufferProducer.h>
#include <gui/IProducerListener.h>
+#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
namespace android {
// ----------------------------------------------------------------------------
@@ -78,6 +79,7 @@ enum {
CANCEL_BUFFERS,
QUERY_MULTIPLE,
GET_LAST_QUEUED_BUFFER2,
+ SET_FRAME_RATE,
};
class BpGraphicBufferProducer : public BpInterface<IGraphicBufferProducer>
@@ -761,6 +763,21 @@ public:
}
return result;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override {
+ Parcel data, reply;
+ data.writeInterfaceToken(IGraphicBufferProducer::getInterfaceDescriptor());
+ data.writeFloat(frameRate);
+ data.writeInt32(compatibility);
+ data.writeInt32(changeFrameRateStrategy);
+ status_t result = remote()->transact(SET_FRAME_RATE, data, &reply);
+ if (result == NO_ERROR) {
+ result = reply.readInt32();
+ }
+ return result;
+ }
+#endif
};
// Out-of-line virtual method definition to trigger vtable emission in this
@@ -956,6 +973,14 @@ status_t IGraphicBufferProducer::setAutoPrerotation(bool autoPrerotation) {
return INVALID_OPERATION;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+status_t IGraphicBufferProducer::setFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+ int8_t /*changeFrameRateStrategy*/) {
+ // No-op for IGBP other than BufferQueue.
+ return INVALID_OPERATION;
+}
+#endif
+
status_t IGraphicBufferProducer::exportToParcel(Parcel* parcel) {
status_t res = OK;
res = parcel->writeUint32(USE_BUFFER_QUEUE);
@@ -1497,6 +1522,17 @@ status_t BnGraphicBufferProducer::onTransact(
reply->writeInt32(result);
return NO_ERROR;
}
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ case SET_FRAME_RATE: {
+ CHECK_INTERFACE(IGraphicBuffer, data, reply);
+ float frameRate = data.readFloat();
+ int8_t compatibility = data.readInt32();
+ int8_t changeFrameRateStrategy = data.readInt32();
+ status_t result = setFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ reply->writeInt32(result);
+ return NO_ERROR;
+ }
+#endif
}
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/libs/gui/ISurfaceComposer.cpp b/libs/gui/ISurfaceComposer.cpp
index 24d39fe86a..ff6b558d41 100644
--- a/libs/gui/ISurfaceComposer.cpp
+++ b/libs/gui/ISurfaceComposer.cpp
@@ -19,15 +19,13 @@
#include <android/gui/IDisplayEventConnection.h>
#include <android/gui/IRegionSamplingListener.h>
-#include <android/gui/ITransactionTraceListener.h>
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/Parcel.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/ISurfaceComposer.h>
-#include <gui/ISurfaceComposerClient.h>
-#include <gui/LayerDebugInfo.h>
#include <gui/LayerState.h>
+#include <gui/SchedulingPolicy.h>
#include <private/gui/ParcelUtils.h>
#include <stdint.h>
#include <sys/types.h>
@@ -37,7 +35,6 @@
#include <ui/DisplayState.h>
#include <ui/DynamicDisplayInfo.h>
#include <ui/HdrCapabilities.h>
-#include <ui/StaticDisplayInfo.h>
#include <utils/Log.h>
// ---------------------------------------------------------------------------
@@ -63,26 +60,17 @@ public:
virtual ~BpSurfaceComposer();
- virtual sp<ISurfaceComposerClient> createConnection()
- {
- Parcel data, reply;
- data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
- remote()->transact(BnSurfaceComposer::CREATE_CONNECTION, data, &reply);
- return interface_cast<ISurfaceComposerClient>(reply.readStrongBinder());
- }
-
- status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
- const Vector<ComposerState>& state,
- const Vector<DisplayState>& displays, uint32_t flags,
- const sp<IBinder>& applyToken, const InputWindowCommands& commands,
- int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& listenerCallbacks,
- uint64_t transactionId) override {
+ status_t setTransactionState(
+ const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
+ const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+ InputWindowCommands commands, int64_t desiredPresentTime, bool isAutoTimestamp,
+ const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
+ const std::vector<uint64_t>& mergedTransactionIds) override {
Parcel data, reply;
data.writeInterfaceToken(ISurfaceComposer::getInterfaceDescriptor());
- SAFE_PARCEL(frameTimelineInfo.write, data);
+ frameTimelineInfo.writeToParcel(&data);
SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(state.size()));
for (const auto& s : state) {
@@ -99,8 +87,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<uint32_t>(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 +102,11 @@ public:
SAFE_PARCEL(data.writeUint64, transactionId);
+ SAFE_PARCEL(data.writeUint32, static_cast<uint32_t>(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 +115,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<IGraphicBufferProducer>& 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<FrameEvent>* 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<int32_t> 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<FrameEvent>(s));
- }
- return NO_ERROR;
- }
-
- sp<IDisplayEventConnection> createDisplayEventConnection(
- VsyncSource vsyncSource, EventRegistrationFlags eventRegistration) override {
- Parcel data, reply;
- sp<IDisplayEventConnection> result;
- int err = data.writeInterfaceToken(
- ISurfaceComposer::getInterfaceDescriptor());
- if (err != NO_ERROR) {
- return result;
- }
- data.writeInt32(static_cast<int32_t>(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<IDisplayEventConnection>(reply.readStrongBinder());
- return result;
- }
-
- status_t getStaticDisplayInfo(const sp<IBinder>& 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<IBinder>& 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<IBinder>& 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<IBinder>& 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<int32_t>(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<status_t>(reply.readInt32());
- }
-
- status_t setBootDisplayMode(const sp<IBinder>& 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<IBinder>& display,
- const std::vector<ui::Hdr>& hdrTypes) {
- Parcel data, reply;
- SAFE_PARCEL(data.writeInterfaceToken, ISurfaceComposer::getInterfaceDescriptor());
- SAFE_PARCEL(data.writeStrongBinder, display);
-
- std::vector<int32_t> hdrTypesVector;
- for (ui::Hdr i : hdrTypes) {
- hdrTypesVector.push_back(static_cast<int32_t>(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<LayerDebugInfo>* 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<status_t>(reply.readInt32());
- if (error == NO_ERROR) {
- *defaultDataspace = static_cast<ui::Dataspace>(reply.readInt32());
- *defaultPixelFormat = static_cast<ui::PixelFormat>(reply.readInt32());
- *wideColorGamutDataspace = static_cast<ui::Dataspace>(reply.readInt32());
- *wideColorGamutPixelFormat = static_cast<ui::PixelFormat>(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<IBinder>& 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<ui::PixelFormat>(value);
-
- error = reply.readUint32(&value);
- if (error != NO_ERROR) {
- return error;
- }
- *outDataspace = static_cast<ui::Dataspace>(value);
-
- error = reply.readUint32(&value);
- if (error != NO_ERROR) {
- return error;
- }
- *outComponentMask = static_cast<uint8_t>(value);
- return error;
- }
-
- status_t setDisplayContentSamplingEnabled(const sp<IBinder>& 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<int8_t>(componentMask));
- data.writeUint64(maxFrames);
- status_t result =
- remote()->transact(BnSurfaceComposer::SET_DISPLAY_CONTENT_SAMPLING_ENABLED, data,
- &reply);
- return result;
- }
-
- status_t getDisplayedContentSample(const sp<IBinder>& 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<IBinder>& stopLayerHandle,
- const sp<IRegionSamplingListener>& 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<IRegionSamplingListener>& 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<gui::IFpsListener>& 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<gui::IFpsListener>& 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<gui::ITunnelModeEnabledListener>& 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<gui::ITunnelModeEnabledListener>& 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<IBinder>& 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<IBinder>& 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<float> 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<IBinder>& displayToken,
- std::optional<common::DisplayDecorationSupport>* 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<common::PixelFormat>(format);
- outSupport->value().alphaInterpretation =
- static_cast<common::AlphaInterpretation>(alphaInterpretation);
- } else {
- outSupport->reset();
- }
- return NO_ERROR;
- }
-
- status_t setFrameRate(const sp<IGraphicBufferProducer>& 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<IGraphicBufferProducer>& 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<gui::ITransactionTraceListener>& 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<IWindowInfosListener>& 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<IWindowInfosListener>& 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 +128,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<IBinder> 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 +166,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<client_cache_t> uncacheBuffers(count);
sp<IBinder> 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,647 +191,29 @@ 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<IGraphicBufferProducer> bufferProducer =
- interface_cast<IGraphicBufferProducer>(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<FrameEvent> 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<int32_t> supported;
- supported.reserve(supportedTimestamps.size());
- for (FrameEvent s : supportedTimestamps) {
- supported.push_back(static_cast<int32_t>(s));
- }
- return reply->writeInt32Vector(supported);
- }
- case CREATE_DISPLAY_EVENT_CONNECTION: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- auto vsyncSource = static_cast<ISurfaceComposer::VsyncSource>(data.readInt32());
- EventRegistrationFlags eventRegistration =
- static_cast<EventRegistration>(data.readUint32());
-
- sp<IDisplayEventConnection> 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<IBinder> 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<IBinder> 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<IBinder> 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<IBinder> 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<ColorMode>(colorModeInt));
- result = reply->writeInt32(result);
- return result;
- }
- case SET_BOOT_DISPLAY_MODE: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- sp<IBinder> 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<LayerDebugInfo> 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<int32_t>(defaultDataspace));
- reply->writeInt32(static_cast<int32_t>(defaultPixelFormat));
- reply->writeInt32(static_cast<int32_t>(wideColorGamutDataspace));
- reply->writeInt32(static_cast<int32_t>(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<IBinder> 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<uint32_t>(format));
- reply->writeUint32(static_cast<uint32_t>(dataspace));
- reply->writeUint32(static_cast<uint32_t>(component));
- }
- return result;
- }
- case SET_DISPLAY_CONTENT_SAMPLING_ENABLED: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
-
- sp<IBinder> 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<int8_t*>(&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<uint8_t>(componentMask), maxFrames);
- }
- case GET_DISPLAYED_CONTENT_SAMPLE: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
-
- sp<IBinder> 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(&timestamp);
- 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<IBinder> stopLayerHandle;
- result = data.readNullableStrongBinder(&stopLayerHandle);
- if (result != NO_ERROR) {
- ALOGE("addRegionSamplingListener: Failed to read stop layer handle");
- return result;
- }
- sp<IRegionSamplingListener> 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<IRegionSamplingListener> 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<gui::IFpsListener> 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<gui::IFpsListener> 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<gui::ITunnelModeEnabledListener> 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<gui::ITunnelModeEnabledListener> 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<IBinder> 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<IBinder> 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<float> shadowConfig;
- status_t error = data.readFloatVector(&shadowConfig);
- if (error != NO_ERROR || shadowConfig.size() != 11) {
- ALOGE("setGlobalShadowSettings: failed to read shadowConfig: %d", error);
- return error;
+ SAFE_PARCEL_READ_SIZE(data.readUint32, &count, data.dataSize());
+ std::vector<uint64_t> mergedTransactions(count);
+ for (size_t i = 0; i < count; i++) {
+ SAFE_PARCEL(data.readUint64, &mergedTransactions[i]);
}
- 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<IBinder> displayToken;
- SAFE_PARCEL(data.readNullableStrongBinder, &displayToken);
- std::optional<common::DisplayDecorationSupport> 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<int32_t>(support.value().format));
- reply->writeInt32(static_cast<int32_t>(support.value().alphaInterpretation));
- }
- return error;
+ return setTransactionState(frameTimelineInfo, state, displays, stateFlags, applyToken,
+ std::move(inputWindowCommands), desiredPresentTime,
+ isAutoTimestamp, uncacheBuffers, hasListenerCallbacks,
+ listenerCallbacks, transactionId, mergedTransactions);
}
- case SET_FRAME_RATE: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- sp<IBinder> binder;
- SAFE_PARCEL(data.readStrongBinder, &binder);
-
- sp<IGraphicBufferProducer> surface = interface_cast<IGraphicBufferProducer>(binder);
- if (!surface) {
- ALOGE("setFrameRate: failed to cast to IGraphicBufferProducer");
- return BAD_VALUE;
+ case GET_SCHEDULING_POLICY: {
+ gui::SchedulingPolicy policy;
+ const auto status = gui::getSchedulingPolicy(&policy);
+ if (!status.isOk()) {
+ return status.exceptionCode();
}
- 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);
+ SAFE_PARCEL(reply->writeInt32, policy.policy);
+ SAFE_PARCEL(reply->writeInt32, policy.priority);
return NO_ERROR;
}
- case SET_FRAME_TIMELINE_INFO: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- sp<IBinder> 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<IGraphicBufferProducer> surface = interface_cast<IGraphicBufferProducer>(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<gui::ITransactionTraceListener> 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(reply->writeInt32, buffers);
- return NO_ERROR;
- }
- case OVERRIDE_HDR_TYPES: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- sp<IBinder> display = nullptr;
- SAFE_PARCEL(data.readStrongBinder, &display);
-
- std::vector<int32_t> hdrTypes;
- SAFE_PARCEL(data.readInt32Vector, &hdrTypes);
-
- std::vector<ui::Hdr> hdrTypesVector;
- for (int i : hdrTypes) {
- hdrTypesVector.push_back(static_cast<ui::Hdr>(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<const uint8_t*>(pulledData.data()));
- SAFE_PARCEL(reply->writeBool, success);
- return err;
- }
- case ADD_WINDOW_INFOS_LISTENER: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- sp<IWindowInfosListener> listener;
- SAFE_PARCEL(data.readStrongBinder, &listener);
-
- return addWindowInfosListener(listener);
- }
- case REMOVE_WINDOW_INFOS_LISTENER: {
- CHECK_INTERFACE(ISurfaceComposer, data, reply);
- sp<IWindowInfosListener> 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);
- }
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 5e7a7ec67b..0000000000
--- 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 <gui/ISurfaceComposerClient.h>
-
-#include <gui/IGraphicBufferProducer.h>
-
-#include <binder/SafeInterface.h>
-
-#include <ui/FrameStats.h>
-
-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<ISurfaceComposerClient> {
-public:
- explicit BpSurfaceComposerClient(const sp<IBinder>& impl)
- : SafeBpInterface<ISurfaceComposerClient>(impl, "BpSurfaceComposerClient") {}
-
- ~BpSurfaceComposerClient() override;
-
- status_t createSurface(const String8& name, uint32_t width, uint32_t height, PixelFormat format,
- uint32_t flags, const sp<IBinder>& parent, LayerMetadata metadata,
- sp<IBinder>* handle, sp<IGraphicBufferProducer>* gbp,
- int32_t* outLayerId, uint32_t* outTransformHint) override {
- return callRemote<decltype(&ISurfaceComposerClient::createSurface)>(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<IGraphicBufferProducer>& parent,
- LayerMetadata metadata, sp<IBinder>* handle,
- sp<IGraphicBufferProducer>* gbp, int32_t* outLayerId,
- uint32_t* outTransformHint) override {
- return callRemote<decltype(
- &ISurfaceComposerClient::createWithSurfaceParent)>(Tag::CREATE_WITH_SURFACE_PARENT,
- name, width, height, format,
- flags, parent,
- std::move(metadata), handle, gbp,
- outLayerId, outTransformHint);
- }
-
- status_t clearLayerFrameStats(const sp<IBinder>& handle) const override {
- return callRemote<decltype(
- &ISurfaceComposerClient::clearLayerFrameStats)>(Tag::CLEAR_LAYER_FRAME_STATS,
- handle);
- }
-
- status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const override {
- return callRemote<decltype(
- &ISurfaceComposerClient::getLayerFrameStats)>(Tag::GET_LAYER_FRAME_STATS, handle,
- outStats);
- }
-
- status_t mirrorSurface(const sp<IBinder>& mirrorFromHandle, sp<IBinder>* outHandle,
- int32_t* outLayerId) override {
- return callRemote<decltype(&ISurfaceComposerClient::mirrorSurface)>(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<uint32_t>(Tag::LAST)) {
- return BBinder::onTransact(code, data, reply, flags);
- }
- auto tag = static_cast<Tag>(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 e4b8bad8f8..f5d19aac78 100644
--- a/libs/gui/ITransactionCompletedListener.cpp
+++ b/libs/gui/ITransactionCompletedListener.cpp
@@ -17,11 +17,18 @@
#define LOG_TAG "ITransactionCompletedListener"
//#define LOG_NDEBUG 0
+#include <cstdint>
+#include <optional>
+
#include <gui/ISurfaceComposer.h>
#include <gui/ITransactionCompletedListener.h>
#include <gui/LayerState.h>
#include <private/gui/ParcelUtils.h>
+#include <com_android_graphics_libgui_flags.h>
+
+using namespace com::android::graphics::libgui;
+
namespace android {
namespace { // Anonymous
@@ -30,15 +37,27 @@ 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;
+ if (flags::frametimestamps_previousrelease()) {
+ err = output->writeUint64(previousFrameNumber);
+ if (err != NO_ERROR) return err;
+ }
+
if (gpuCompositionDoneFence) {
err = output->writeBool(true);
if (err != NO_ERROR) return err;
@@ -69,6 +88,11 @@ status_t FrameEventHistoryStats::readFromParcel(const Parcel* input) {
status_t err = input->readUint64(&frameNumber);
if (err != NO_ERROR) return err;
+ if (flags::frametimestamps_previousrelease()) {
+ err = input->readUint64(&previousFrameNumber);
+ if (err != NO_ERROR) return err;
+ }
+
bool hasFence = false;
err = input->readBool(&hasFence);
if (err != NO_ERROR) return err;
@@ -101,12 +125,14 @@ JankData::JankData()
status_t JankData::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeInt64, frameVsyncId);
SAFE_PARCEL(output->writeInt32, jankType);
+ SAFE_PARCEL(output->writeInt64, frameIntervalNs);
return NO_ERROR;
}
status_t JankData::readFromParcel(const Parcel* input) {
SAFE_PARCEL(input->readInt64, &frameVsyncId);
SAFE_PARCEL(input->readInt32, &jankType);
+ SAFE_PARCEL(input->readInt64, &frameIntervalNs);
return NO_ERROR;
}
@@ -126,7 +152,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<int32_t>(jankData.size()));
@@ -156,7 +187,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, &currentMaxAcquiredBufferCount);
SAFE_PARCEL(input->readParcelable, &eventStats);
@@ -273,15 +313,22 @@ public:
void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> releaseFence,
uint32_t currentMaxAcquiredBufferCount) override {
- callRemoteAsync<decltype(
- &ITransactionCompletedListener::onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER,
- callbackId, releaseFence,
- currentMaxAcquiredBufferCount);
+ callRemoteAsync<decltype(&ITransactionCompletedListener::
+ onReleaseBuffer)>(Tag::ON_RELEASE_BUFFER, callbackId,
+ releaseFence,
+ currentMaxAcquiredBufferCount);
}
- void onTransactionQueueStalled() override {
- callRemoteAsync<decltype(&ITransactionCompletedListener::onTransactionQueueStalled)>(
- Tag::ON_TRANSACTION_QUEUE_STALLED);
+ void onTransactionQueueStalled(const String8& reason) override {
+ callRemoteAsync<
+ decltype(&ITransactionCompletedListener::
+ onTransactionQueueStalled)>(Tag::ON_TRANSACTION_QUEUE_STALLED,
+ reason);
+ }
+
+ void onTrustedPresentationChanged(int id, bool inTrustedPresentationState) override {
+ callRemoteAsync<decltype(&ITransactionCompletedListener::onTrustedPresentationChanged)>(
+ Tag::ON_TRUSTED_PRESENTATION_CHANGED, id, inTrustedPresentationState);
}
};
@@ -306,6 +353,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 +371,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<int32_t>(type));
+ if (type == Type::ON_COMPLETE && includeJankData) {
+ SAFE_PARCEL(output->writeInt32, kSerializedCallbackTypeOnCompelteWithJankData);
+ } else {
+ SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(type));
+ }
return NO_ERROR;
}
@@ -329,7 +383,13 @@ status_t CallbackId::readFromParcel(const Parcel* input) {
SAFE_PARCEL(input->readInt64, &id);
int32_t typeAsInt;
SAFE_PARCEL(input->readInt32, &typeAsInt);
- type = static_cast<CallbackId::Type>(typeAsInt);
+ if (typeAsInt == kSerializedCallbackTypeOnCompelteWithJankData) {
+ type = Type::ON_COMPLETE;
+ includeJankData = true;
+ } else {
+ type = static_cast<CallbackId::Type>(typeAsInt);
+ includeJankData = false;
+ }
return NO_ERROR;
}
diff --git a/libs/gui/LayerDebugInfo.cpp b/libs/gui/LayerDebugInfo.cpp
index ea5fb293a6..15b2221464 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 189d51a4c1..4e12fd330c 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 502031c8d8..38fab9cdaa 100644
--- a/libs/gui/LayerState.cpp
+++ b/libs/gui/LayerState.cpp
@@ -19,15 +19,37 @@
#include <cinttypes>
#include <cmath>
+#include <android/gui/ISurfaceComposerClient.h>
#include <android/native_window.h>
#include <binder/Parcel.h>
+#include <gui/FrameRateUtils.h>
#include <gui/IGraphicBufferProducer.h>
-#include <gui/ISurfaceComposerClient.h>
#include <gui/LayerState.h>
+#include <gui/SurfaceControl.h>
#include <private/gui/ParcelUtils.h>
#include <system/window.h>
#include <utils/Errors.h>
+#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 +62,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 +83,14 @@ 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),
+ frameRateCategory(ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT),
+ frameRateCategorySmoothSwitchOnly(false),
+ frameRateSelectionStrategy(ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE),
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 +107,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<uint32_t>(dataspace));
SAFE_PARCEL(output.write, hdrMetadata);
SAFE_PARCEL(output.write, surfaceDamageRegion);
@@ -117,7 +144,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<uint32_t>(bgColorDataspace));
SAFE_PARCEL(output.writeBool, colorSpaceAgnostic);
SAFE_PARCEL(output.writeVectorSize, listeners);
@@ -131,6 +161,10 @@ 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.writeByte, frameRateCategory);
+ SAFE_PARCEL(output.writeBool, frameRateCategorySmoothSwitchOnly);
+ SAFE_PARCEL(output.writeByte, frameRateSelectionStrategy);
SAFE_PARCEL(output.writeUint32, fixedTransformHint);
SAFE_PARCEL(output.writeBool, autoRefresh);
SAFE_PARCEL(output.writeBool, dimmingEnabled);
@@ -161,6 +195,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<int32_t>(cachingHint))
return NO_ERROR;
}
@@ -172,10 +211,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 +219,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 +230,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 +269,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<ui::Dataspace>(tmpUint32);
SAFE_PARCEL(input.readBool, &colorSpaceAgnostic);
@@ -240,6 +296,10 @@ 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.readByte, &frameRateCategory);
+ SAFE_PARCEL(input.readBool, &frameRateCategorySmoothSwitchOnly);
+ SAFE_PARCEL(input.readByte, &frameRateSelectionStrategy);
SAFE_PARCEL(input.readUint32, &tmpUint32);
fixedTransformHint = static_cast<ui::Transform::RotationFlags>(tmpUint32);
SAFE_PARCEL(input.readBool, &autoRefresh);
@@ -280,6 +340,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<gui::CachingHint>(tmpInt32);
+
return NO_ERROR;
}
@@ -353,6 +426,27 @@ void DisplayState::merge(const DisplayState& other) {
}
}
+void DisplayState::sanitize(int32_t permissions) {
+ if (what & DisplayState::eLayerStackChanged) {
+ if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) {
+ what &= ~DisplayState::eLayerStackChanged;
+ ALOGE("Stripped attempt to set eLayerStackChanged in sanitize");
+ }
+ }
+ if (what & DisplayState::eDisplayProjectionChanged) {
+ if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) {
+ what &= ~DisplayState::eDisplayProjectionChanged;
+ ALOGE("Stripped attempt to set eDisplayProjectionChanged in sanitize");
+ }
+ }
+ if (what & DisplayState::eSurfaceChanged) {
+ if (!(permissions & layer_state_t::Permission::ACCESS_SURFACE_FLINGER)) {
+ what &= ~DisplayState::eSurfaceChanged;
+ ALOGE("Stripped attempt to set eSurfaceChanged in sanitize");
+ }
+ }
+}
+
void layer_state_t::sanitize(int32_t permissions) {
// TODO: b/109894387
//
@@ -437,14 +531,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;
@@ -486,12 +575,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;
@@ -505,10 +591,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;
@@ -538,8 +638,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) {
@@ -550,6 +649,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;
@@ -560,6 +669,15 @@ void layer_state_t::merge(const layer_state_t& other) {
frameRateCompatibility = other.frameRateCompatibility;
changeFrameRateStrategy = other.changeFrameRateStrategy;
}
+ if (other.what & eFrameRateCategoryChanged) {
+ what |= eFrameRateCategoryChanged;
+ frameRateCategory = other.frameRateCategory;
+ frameRateCategorySmoothSwitchOnly = other.frameRateCategorySmoothSwitchOnly;
+ }
+ if (other.what & eFrameRateSelectionStrategyChanged) {
+ what |= eFrameRateSelectionStrategyChanged;
+ frameRateSelectionStrategy = other.frameRateSelectionStrategy;
+ }
if (other.what & eFixedTransformHintChanged) {
what |= eFixedTransformHintChanged;
fixedTransformHint = other.fixedTransformHint;
@@ -593,7 +711,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;
@@ -603,6 +721,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,
@@ -610,12 +731,86 @@ 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_DIFF2(diff, eFrameRateCategoryChanged, other, frameRateCategory,
+ frameRateCategorySmoothSwitchOnly);
+ CHECK_DIFF(diff, eFrameRateSelectionStrategyChanged, other, frameRateSelectionStrategy);
+ CHECK_DIFF(diff, eFixedTransformHintChanged, other, fixedTransformHint);
+ CHECK_DIFF(diff, eAutoRefreshChanged, other, autoRefresh);
+ CHECK_DIFF(diff, eTrustedOverlayChanged, other, isTrustedOverlay);
+ CHECK_DIFF(diff, 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 {
@@ -641,58 +836,45 @@ 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);
- return NO_ERROR;
-}
-
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
- const char* inFunctionName, bool privileged) {
- const char* functionName = inFunctionName != nullptr ? inFunctionName : "call";
- int floatClassification = std::fpclassify(frameRate);
- if (frameRate < 0 || floatClassification == FP_INFINITE || floatClassification == FP_NAN) {
- ALOGE("%s failed - invalid frame rate %f", functionName, frameRate);
- return false;
- }
- if (compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT &&
- compatibility != ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE &&
- (!privileged ||
- (compatibility != ANATIVEWINDOW_FRAME_RATE_EXACT &&
- compatibility != ANATIVEWINDOW_FRAME_RATE_NO_VOTE))) {
- ALOGE("%s failed - invalid compatibility value %d privileged: %s", functionName,
- compatibility, privileged ? "yes" : "no");
- return false;
+ int listenerSize = 0;
+ SAFE_PARCEL_READ_SIZE(input.readInt32, &listenerSize, input.dataSize());
+ windowInfosReportedListeners.reserve(listenerSize);
+ for (int i = 0; i < listenerSize; i++) {
+ sp<gui::IWindowInfosReportedListener> listener;
+ SAFE_PARCEL(input.readStrongBinder, &listener);
+ windowInfosReportedListeners.insert(listener);
}
- if (changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS &&
- changeFrameRateStrategy != ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS) {
- ALOGE("%s failed - invalid change frame rate strategy value %d", functionName,
- changeFrameRateStrategy);
- }
-
- return true;
+ return NO_ERROR;
}
// ----------------------------------------------------------------------------
@@ -709,6 +891,11 @@ status_t CaptureArgs::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeInt32, static_cast<int32_t>(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;
}
@@ -725,6 +912,15 @@ status_t CaptureArgs::readFromParcel(const Parcel* input) {
dataspace = static_cast<ui::Dataspace>(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<IBinder> binder;
+ SAFE_PARCEL(input->readStrongBinder, &binder);
+ excludeHandles.emplace(binder);
+ }
+ SAFE_PARCEL(input->readBool, &hintForSeamlessTransition);
return NO_ERROR;
}
@@ -734,7 +930,6 @@ status_t DisplayCaptureArgs::writeToParcel(Parcel* output) const {
SAFE_PARCEL(output->writeStrongBinder, displayToken);
SAFE_PARCEL(output->writeUint32, width);
SAFE_PARCEL(output->writeUint32, height);
- SAFE_PARCEL(output->writeBool, useIdentityTransform);
return NO_ERROR;
}
@@ -744,7 +939,6 @@ status_t DisplayCaptureArgs::readFromParcel(const Parcel* input) {
SAFE_PARCEL(input->readStrongBinder, &displayToken);
SAFE_PARCEL(input->readUint32, &width);
SAFE_PARCEL(input->readUint32, &height);
- SAFE_PARCEL(input->readBool, &useIdentityTransform);
return NO_ERROR;
}
@@ -752,10 +946,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;
}
@@ -765,15 +955,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<IBinder> binder;
- SAFE_PARCEL(input->readStrongBinder, &binder);
- excludeHandles.emplace(binder);
- }
-
SAFE_PARCEL(input->readBool, &childrenOnly);
return NO_ERROR;
}
@@ -815,6 +996,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;
}
@@ -853,7 +1035,24 @@ 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<IBinder> tmpBinder = nullptr;
+ SAFE_PARCEL(parcel->readNullableStrongBinder, &tmpBinder);
+ if (tmpBinder) {
+ callbackInterface = checked_interface_cast<ITransactionCompletedListener>(tmpBinder);
+ }
+ SAFE_PARCEL(parcel->readInt32, &callbackId);
return NO_ERROR;
}
diff --git a/libs/gui/LayerStatePermissions.cpp b/libs/gui/LayerStatePermissions.cpp
new file mode 100644
index 0000000000..28697ca953
--- /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 <binder/IPCThreadState.h>
+#include <gui/LayerStatePermissions.h>
+#include <private/android_filesystem_config.h>
+#ifndef __ANDROID_VNDK__
+#include <binder/PermissionCache.h>
+#endif // __ANDROID_VNDK__
+#include <gui/LayerState.h>
+
+namespace android {
+std::unordered_map<std::string, int> 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/OWNERS b/libs/gui/OWNERS
index 05b55337ab..070f6bf532 100644
--- a/libs/gui/OWNERS
+++ b/libs/gui/OWNERS
@@ -1,12 +1,9 @@
-adyabr@google.com
-alecmouri@google.com
-chaviw@google.com
+# Bug component: 1075131
+
chrisforbes@google.com
jreck@google.com
-lpy@google.com
-pdwilliams@google.com
-racarr@google.com
-vishnun@google.com
+
+file:/services/surfaceflinger/OWNERS
per-file EndToEndNativeInputTest.cpp = svv@google.com
diff --git a/libs/gui/ScreenCaptureResults.cpp b/libs/gui/ScreenCaptureResults.cpp
index fe387064bc..601a5f9b33 100644
--- a/libs/gui/ScreenCaptureResults.cpp
+++ b/libs/gui/ScreenCaptureResults.cpp
@@ -17,6 +17,7 @@
#include <gui/ScreenCaptureResults.h>
#include <private/gui/ParcelUtils.h>
+#include <ui/FenceResult.h>
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<uint32_t>(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<Fence>::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<ui::Dataspace>(dataspace);
- SAFE_PARCEL(parcel->readInt32, &result);
return NO_ERROR;
}
diff --git a/libs/gui/Surface.cpp b/libs/gui/Surface.cpp
index 3b137080ab..07a0cfed63 100644
--- a/libs/gui/Surface.cpp
+++ b/libs/gui/Surface.cpp
@@ -30,16 +30,20 @@
#include <android/gui/DisplayStatInfo.h>
#include <android/native_window.h>
+#include <gui/FenceMonitor.h>
+#include <gui/TraceUtils.h>
#include <utils/Log.h>
-#include <utils/Trace.h>
#include <utils/NativeHandle.h>
+#include <utils/Trace.h>
#include <ui/DynamicDisplayInfo.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
#include <ui/Region.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/BufferItem.h>
+
#include <gui/IProducerListener.h>
#include <gui/ISurfaceComposer.h>
@@ -47,12 +51,22 @@
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
+using namespace com::android::graphics::libgui;
+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 +196,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 +359,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<IBinder> 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<IBinder> 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 +550,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>& fence) {
- char message[64];
-
- std::lock_guard<std::mutex> 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> fence;
- uint32_t fenceNum;
- {
- std::unique_lock<std::mutex> 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<std::mutex> lock(mMutex);
- mQueue.pop_front();
- mFencesSignaled++;
- ATRACE_INT(mName, int32_t(mQueue.size()));
- }
- }
-
- const char* mName;
- uint32_t mFencesQueued;
- uint32_t mFencesSignaled;
- std::deque<sp<Fence>> 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 +564,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 +623,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);
}
@@ -792,11 +722,15 @@ int Surface::dequeueBuffers(std::vector<BatchBuffer>* buffers) {
return result;
}
- std::vector<CancelBufferInput> cancelBufferInputs(numBufferRequested);
+ std::vector<CancelBufferInput> cancelBufferInputs;
+ cancelBufferInputs.reserve(numBufferRequested);
std::vector<status_t> cancelBufferOutputs;
for (size_t i = 0; i < numBufferRequested; i++) {
- cancelBufferInputs[i].slot = dequeueOutput[i].slot;
- cancelBufferInputs[i].fence = dequeueOutput[i].fence;
+ if (dequeueOutput[i].result >= 0) {
+ CancelBufferInput& input = cancelBufferInputs.emplace_back();
+ input.slot = dequeueOutput[i].slot;
+ input.fence = dequeueOutput[i].fence;
+ }
}
for (const auto& output : dequeueOutput) {
@@ -888,7 +822,7 @@ int Surface::dequeueBuffers(std::vector<BatchBuffer>* buffers) {
sp<GraphicBuffer>& 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);
}
@@ -1158,7 +1092,7 @@ void Surface::onBufferQueuedLocked(int slot, sp<Fence> 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);
}
}
@@ -1259,10 +1193,10 @@ void Surface::querySupportedTimestampsLocked() const {
mQueriedSupportedTimestamps = true;
std::vector<FrameEvent> supportedFrameTimestamps;
- status_t err = composerService()->getSupportedFrameTimestamps(
- &supportedFrameTimestamps);
+ binder::Status status =
+ composerServiceAIDL()->getSupportedFrameTimestamps(&supportedFrameTimestamps);
- if (err != NO_ERROR) {
+ if (!status.isOk()) {
return;
}
@@ -1290,15 +1224,12 @@ int Surface::query(int what, int* value) const {
if (err == NO_ERROR) {
return NO_ERROR;
}
- sp<ISurfaceComposer> surfaceComposer = composerService();
+ sp<gui::ISurfaceComposer> 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:
@@ -1865,12 +1796,20 @@ int Surface::dispatchGetLastQueuedBuffer2(va_list args) {
int Surface::dispatchSetFrameTimelineInfo(va_list args) {
ATRACE_CALL();
- auto frameTimelineVsyncId = static_cast<int64_t>(va_arg(args, int64_t));
- auto inputEventId = static_cast<int32_t>(va_arg(args, int32_t));
- auto startTimeNanos = static_cast<int64_t>(va_arg(args, int64_t));
-
ALOGV("Surface::%s", __func__);
- return setFrameTimelineInfo({frameTimelineVsyncId, inputEventId, startTimeNanos});
+
+ const auto nativeWindowFtlInfo = static_cast<ANativeWindowFrameTimelineInfo>(
+ va_arg(args, ANativeWindowFrameTimelineInfo));
+
+ FrameTimelineInfo ftlInfo;
+ ftlInfo.vsyncId = nativeWindowFtlInfo.frameTimelineVsyncId;
+ ftlInfo.inputEventId = nativeWindowFtlInfo.inputEventId;
+ ftlInfo.startTimeNanos = nativeWindowFtlInfo.startTimeNanos;
+ ftlInfo.useForRefreshRateSelection = nativeWindowFtlInfo.useForRefreshRateSelection;
+ ftlInfo.skippedFrameVsyncId = nativeWindowFtlInfo.skippedFrameVsyncId;
+ ftlInfo.skippedFrameStartTimeNanos = nativeWindowFtlInfo.skippedFrameStartTimeNanos;
+
+ return setFrameTimelineInfo(nativeWindowFtlInfo.frameNumber, ftlInfo);
}
bool Surface::transformToDisplayInverse() const {
@@ -2632,20 +2571,31 @@ void Surface::ProducerListenerProxy::onBuffersDiscarded(const std::vector<int32_
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;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ if (flags::bq_setframerate()) {
+ status_t err = mGraphicBufferProducer->setFrameRate(frameRate, compatibility,
+ changeFrameRateStrategy);
+ ALOGE_IF(err, "IGraphicBufferProducer::setFrameRate(%.2f) returned %s", frameRate,
+ strerror(-err));
+ return err;
}
+#else
+ static_cast<void>(frameRate);
+ static_cast<void>(compatibility);
+ static_cast<void>(changeFrameRateStrategy);
+#endif
- return composerService()->setFrameRate(mGraphicBufferProducer, frameRate, compatibility,
- 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(const FrameTimelineInfo& frameTimelineInfo) {
- return composerService()->setFrameTimelineInfo(mGraphicBufferProducer, frameTimelineInfo);
+status_t Surface::setFrameTimelineInfo(uint64_t /*frameNumber*/,
+ const FrameTimelineInfo& /*frameTimelineInfo*/) {
+ // ISurfaceComposer no longer supports setFrameTimelineInfo
+ return BAD_VALUE;
}
sp<IBinder> Surface::getSurfaceControlHandle() const {
@@ -2658,4 +2608,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 0f5192d41c..079ccda0fb 100644
--- a/libs/gui/SurfaceComposerClient.cpp
+++ b/libs/gui/SurfaceComposerClient.cpp
@@ -16,11 +16,18 @@
#define LOG_TAG "SurfaceComposerClient"
+#include <semaphore.h>
#include <stdint.h>
#include <sys/types.h>
+#include <android/gui/BnWindowInfosReportedListener.h>
#include <android/gui/DisplayState.h>
+#include <android/gui/ISurfaceComposerClient.h>
#include <android/gui/IWindowInfosListener.h>
+#include <android/gui/TrustedPresentationThresholds.h>
+#include <android/os/IInputConstants.h>
+#include <gui/FrameRateUtils.h>
+#include <gui/TraceUtils.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/SortedVector.h>
@@ -33,11 +40,11 @@
#include <system/graphics.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/BufferItemConsumer.h>
#include <gui/CpuConsumer.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/ISurfaceComposer.h>
-#include <gui/ISurfaceComposerClient.h>
#include <gui/LayerState.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
@@ -47,20 +54,24 @@
#include <ui/DisplayState.h>
#include <ui/DynamicDisplayInfo.h>
+#include <android-base/thread_annotations.h>
+#include <gui/LayerStatePermissions.h>
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
// This server size should always be smaller than the server cache size
-#define BUFFER_CACHE_MAX_SIZE 64
+#define BUFFER_CACHE_MAX_SIZE 4096
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 +84,8 @@ std::atomic<uint32_t> idCounter = 0;
int64_t generateId() {
return (((int64_t)getpid()) << 32) | ++idCounter;
}
+
+void emptyCallback(nsecs_t, const sp<Fence>&, const std::vector<SurfaceControlStats>&) {}
} // namespace
ComposerService::ComposerService()
@@ -111,7 +124,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 +171,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 +253,14 @@ CallbackId TransactionCompletedListener::addCallbackFunction(
surfaceControls,
CallbackId::Type callbackType) {
std::lock_guard<std::mutex> lock(mMutex);
+ return addCallbackFunctionLocked(callbackFunction, surfaceControls, callbackType);
+}
+
+CallbackId TransactionCompletedListener::addCallbackFunctionLocked(
+ const TransactionCompletedCallback& callbackFunction,
+ const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
+ surfaceControls,
+ CallbackId::Type callbackType) {
startListeningLocked();
CallbackId callbackId(getNextIdLocked(), callbackType);
@@ -248,6 +269,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 +322,26 @@ void TransactionCompletedListener::removeSurfaceStatsListener(void* context, voi
}
void TransactionCompletedListener::addSurfaceControlToCallbacks(
- const sp<SurfaceControl>& surfaceControl,
- const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds) {
+ SurfaceComposerClient::CallbackInfo& callbackInfo,
+ const sp<SurfaceControl>& surfaceControl) {
std::lock_guard<std::mutex> 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);
}
}
@@ -341,7 +378,6 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
}
auto& [callbackFunction, callbackSurfaceControls] = callbacksMap[callbackId];
if (!callbackFunction) {
- ALOGE("cannot call null callback function, skipping");
continue;
}
std::vector<SurfaceControlStats> surfaceControlStats;
@@ -358,6 +394,11 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
surfaceControlStats);
+
+ // More than one transaction may contain the same callback id. Erase the callback from
+ // the map to ensure that it is only called once. This can happen if transactions are
+ // parcelled out of process and applied in both processes.
+ callbacksMap.erase(callbackId);
}
// handle on complete callbacks
@@ -380,10 +421,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
@@ -409,7 +451,9 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
callbackFunction(transactionStats.latchTime, transactionStats.presentFence,
surfaceControlStats);
}
+ }
+ for (const auto& transactionStats : listenerStats.transactionStats) {
for (const auto& surfaceStats : transactionStats.surfaceStats) {
// The callbackMap contains the SurfaceControl object, which we need to look up the
// layerId. Since we don't know which callback contains the SurfaceControl, iterate
@@ -449,23 +493,24 @@ void TransactionCompletedListener::onTransactionCompleted(ListenerStats listener
}
}
-void TransactionCompletedListener::onTransactionQueueStalled() {
- std::unordered_map<void*, std::function<void()>> callbackCopy;
- {
- std::scoped_lock<std::mutex> lock(mMutex);
- callbackCopy = mQueueStallListeners;
- }
- for (auto const& it : callbackCopy) {
- it.second();
- }
+void TransactionCompletedListener::onTransactionQueueStalled(const String8& reason) {
+ std::unordered_map<void*, std::function<void(const std::string&)>> callbackCopy;
+ {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ callbackCopy = mQueueStallListeners;
+ }
+ for (auto const& it : callbackCopy) {
+ it.second(reason.c_str());
+ }
}
-void TransactionCompletedListener::addQueueStallListener(std::function<void()> stallListener,
- void* id) {
+void TransactionCompletedListener::addQueueStallListener(
+ std::function<void(const std::string&)> stallListener, void* id) {
std::scoped_lock<std::mutex> lock(mMutex);
mQueueStallListeners[id] = stallListener;
}
-void TransactionCompletedListener::removeQueueStallListener(void *id) {
+
+void TransactionCompletedListener::removeQueueStallListener(void* id) {
std::scoped_lock<std::mutex> lock(mMutex);
mQueueStallListeners.erase(id);
}
@@ -510,6 +555,45 @@ void TransactionCompletedListener::removeReleaseBufferCallback(
}
}
+SurfaceComposerClient::PresentationCallbackRAII::PresentationCallbackRAII(
+ TransactionCompletedListener* tcl, int id) {
+ mTcl = tcl;
+ mId = id;
+}
+
+SurfaceComposerClient::PresentationCallbackRAII::~PresentationCallbackRAII() {
+ mTcl->clearTrustedPresentationCallback(mId);
+}
+
+sp<SurfaceComposerClient::PresentationCallbackRAII>
+TransactionCompletedListener::addTrustedPresentationCallback(TrustedPresentationCallback tpc,
+ int id, void* context) {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ mTrustedPresentationCallbacks[id] =
+ std::tuple<TrustedPresentationCallback, void*>(tpc, context);
+ return new SurfaceComposerClient::PresentationCallbackRAII(this, id);
+}
+
+void TransactionCompletedListener::clearTrustedPresentationCallback(int id) {
+ std::scoped_lock<std::mutex> lock(mMutex);
+ mTrustedPresentationCallbacks.erase(id);
+}
+
+void TransactionCompletedListener::onTrustedPresentationChanged(int id,
+ bool presentedWithinThresholds) {
+ TrustedPresentationCallback tpc;
+ void* context;
+ {
+ std::scoped_lock<std::mutex> 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 +641,13 @@ public:
return NO_ERROR;
}
- uint64_t cache(const sp<GraphicBuffer>& buffer) {
+ uint64_t cache(const sp<GraphicBuffer>& buffer,
+ std::optional<client_cache_t>& outUncacheBuffer) {
std::lock_guard<std::mutex> lock(mMutex);
if (mBuffers.size() >= BUFFER_CACHE_MAX_SIZE) {
- evictLeastRecentlyUsedBuffer();
+ outUncacheBuffer = findLeastRecentlyUsedBuffer();
+ mBuffers.erase(outUncacheBuffer->id);
}
buffer->addDeathCallback(removeDeadBufferCallback, nullptr);
@@ -572,16 +658,13 @@ public:
void uncache(uint64_t cacheId) {
std::lock_guard<std::mutex> 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 +678,8 @@ private:
}
itr++;
}
- uncacheLocked(minBuffer->first);
+
+ return {.token = getToken(), .id = minBuffer->first};
}
uint64_t getCounter() REQUIRES(mMutex) {
@@ -625,12 +709,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 +724,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<SurfaceComposerClient::Transaction>
@@ -659,16 +747,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<IBinder> applyToken;
parcel->readNullableStrongBinder(&applyToken);
@@ -735,13 +822,33 @@ status_t SurfaceComposerClient::Transaction::readFromParcel(const Parcel* parcel
InputWindowCommands inputWindowCommands;
inputWindowCommands.read(*parcel);
+ count = static_cast<size_t>(parcel->readUint32());
+ if (count > parcel->dataSize()) {
+ return BAD_VALUE;
+ }
+ std::vector<client_cache_t> uncacheBuffers(count);
+ for (size_t i = 0; i < count; i++) {
+ sp<IBinder> tmpBinder;
+ SAFE_PARCEL(parcel->readStrongBinder, &tmpBinder);
+ uncacheBuffers[i].token = tmpBinder;
+ SAFE_PARCEL(parcel->readUint64, &uncacheBuffers[i].id);
+ }
+
+ count = static_cast<size_t>(parcel->readUint32());
+ if (count > parcel->dataSize()) {
+ return BAD_VALUE;
+ }
+ std::vector<uint64_t> 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 +857,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 +876,14 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const
const_cast<SurfaceComposerClient::Transaction*>(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<uint32_t>(mDisplayStates.size()));
for (auto const& displayState : mDisplayStates) {
@@ -802,11 +910,23 @@ status_t SurfaceComposerClient::Transaction::writeToParcel(Parcel* parcel) const
}
mInputWindowCommands.write(*parcel);
+
+ SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(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<uint32_t>(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 +946,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 +1000,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 +1026,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();
+ mFrameTimelineInfo = {};
mApplyToken = nullptr;
+ mMergedTransactionIds.clear();
}
uint64_t SurfaceComposerClient::Transaction::getId() {
return mId;
}
+std::vector<uint64_t> SurfaceComposerClient::Transaction::getMergedTransactionIds() {
+ return mMergedTransactionIds;
+}
+
void SurfaceComposerClient::doUncacheBufferTransaction(uint64_t cacheId) {
sp<ISurfaceComposer> sf(ComposerService::getComposerService());
client_cache_t uncacheBuffer;
uncacheBuffer.token = BufferCache::getInstance().getToken();
uncacheBuffer.id = cacheId;
-
- sp<IBinder> applyToken = IInterface::asBinder(TransactionCompletedListener::getIInstance());
- sf->setTransactionState(FrameTimelineInfo{}, {}, {}, 0, applyToken, {}, systemTime(), true,
- uncacheBuffer, false, {}, generateId());
+ Vector<ComposerState> 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 +1095,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<client_cache_t> 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 +1114,58 @@ void SurfaceComposerClient::Transaction::cacheBuffers() {
}
}
+class SyncCallback {
+public:
+ static auto getCallback(std::shared_ptr<SyncCallback>& callbackContext) {
+ return [callbackContext](void* /* unused context */, nsecs_t /* latchTime */,
+ const sp<Fence>& /* presentFence */,
+ const std::vector<SurfaceControlStats>& /* 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<void*>(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<ISurfaceComposer> sf(ComposerService::getComposerService());
+ std::shared_ptr<SyncCallback> syncCallback = std::make_shared<SyncCallback>();
+ if (synchronous) {
+ syncCallback->init();
+ addTransactionCommittedCallback(SyncCallback::getCallback(syncCallback),
+ /*callbackContext=*/nullptr);
+ }
bool hasListenerCallbacks = !mListenerCallbacks.empty();
std::vector<ListenerCallbacks> listenerCallbacks;
@@ -1001,27 +1200,22 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay
Vector<DisplayState> 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 +1227,62 @@ status_t SurfaceComposerClient::Transaction::apply(bool synchronous, bool oneWay
flags |= ISurfaceComposer::eEarlyWakeupEnd;
}
- sp<IBinder> applyToken = mApplyToken
- ? mApplyToken
- : IInterface::asBinder(TransactionCompletedListener::getIInstance());
+ sp<IBinder> applyToken = mApplyToken ? mApplyToken : getDefaultApplyToken();
+ sp<ISurfaceComposer> 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<IBinder> SurfaceComposerClient::Transaction::sApplyToken = new BBinder();
+
+std::mutex SurfaceComposerClient::Transaction::sApplyTokenMutex;
+
+sp<IBinder> SurfaceComposerClient::Transaction::getDefaultApplyToken() {
+ std::scoped_lock lock{sApplyTokenMutex};
+ return sApplyToken;
+}
+
+void SurfaceComposerClient::Transaction::setDefaultApplyToken(sp<IBinder> applyToken) {
+ std::scoped_lock lock{sApplyTokenMutex};
+ sApplyToken = applyToken;
+}
+
+status_t SurfaceComposerClient::Transaction::sendSurfaceFlushJankDataTransaction(
+ const sp<SurfaceControl>& 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<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure) {
+sp<IBinder> SurfaceComposerClient::createDisplay(const String8& displayName, bool secure,
+ float requestedRefereshRate) {
sp<IBinder> display = nullptr;
binder::Status status =
ComposerServiceAIDL::getComposerService()->createDisplay(std::string(
- displayName.string()),
- secure, &display);
+ displayName.c_str()),
+ secure, requestedRefereshRate,
+ &display);
return status.isOk() ? display : nullptr;
}
@@ -1080,21 +1305,6 @@ std::vector<PhysicalDisplayId> 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<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
- }
- return status.transactionError();
-}
-
-std::optional<PhysicalDisplayId> SurfaceComposerClient::getInternalDisplayId() {
- ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance();
- return instance.getInternalDisplayId();
-}
-
sp<IBinder> SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId displayId) {
sp<IBinder> display = nullptr;
binder::Status status =
@@ -1103,9 +1313,11 @@ sp<IBinder> SurfaceComposerClient::getPhysicalDisplayToken(PhysicalDisplayId dis
return status.isOk() ? display : nullptr;
}
-sp<IBinder> SurfaceComposerClient::getInternalDisplayToken() {
- ComposerServiceAIDL& instance = ComposerServiceAIDL::getInstance();
- return instance.getInternalDisplayToken();
+std::optional<gui::StalledTransactionInfo> SurfaceComposerClient::getStalledTransactionInfo(
+ pid_t pid) {
+ std::optional<gui::StalledTransactionInfo> result;
+ ComposerServiceAIDL::getComposerService()->getStalledTransactionInfo(pid, &result);
+ return result;
}
void SurfaceComposerClient::Transaction::setAnimationTransaction() {
@@ -1141,8 +1353,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 +1381,6 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::hide(
return setFlags(sc, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden);
}
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setSize(
- const sp<SurfaceControl>& 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<SurfaceControl>& sc, int32_t z) {
layer_state_t* s = getLayerState(sc);
@@ -1227,7 +1423,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;
@@ -1274,8 +1472,11 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setAlpha
mStatus = BAD_INDEX;
return *this;
}
+ if (alpha < 0.0f || alpha > 1.0f) {
+ ALOGE("SurfaceComposerClient::Transaction::setAlpha: invalid alpha %f, clamping", alpha);
+ }
s->what |= layer_state_t::eAlphaChanged;
- s->alpha = alpha;
+ s->color.a = std::clamp(alpha, 0.f, 1.f);
registerSurfaceControlForCallback(sc);
return *this;
@@ -1406,7 +1607,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;
@@ -1421,8 +1622,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);
@@ -1436,8 +1637,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;
@@ -1475,7 +1676,6 @@ std::shared_ptr<BufferData> SurfaceComposerClient::Transaction::getAndClearBuffe
s->what &= ~layer_state_t::eBufferChanged;
s->bufferData = nullptr;
- mContainsBuffer = false;
return bufferData;
}
@@ -1494,7 +1694,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffer(
const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
const std::optional<sp<Fence>>& fence, const std::optional<uint64_t>& optFrameNumber,
- ReleaseBufferCallback callback) {
+ uint32_t producerId, ReleaseBufferCallback callback) {
layer_state_t* s = getLayerState(sc);
if (!s) {
mStatus = BAD_INDEX;
@@ -1503,28 +1703,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> bufferData = std::make_shared<BufferData>();
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);
@@ -1541,7 +1738,26 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setBuffe
const std::vector<SurfaceControlStats>&) {},
nullptr);
- mContainsBuffer = true;
+ mMayContainBuffer = true;
+ return *this;
+}
+
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::unsetBuffer(
+ const sp<SurfaceControl>& 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;
}
@@ -1576,6 +1792,35 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDatas
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setExtendedRangeBrightness(
+ const sp<SurfaceControl>& 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<SurfaceControl>& 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<SurfaceControl>& sc, const HdrMetadata& hdrMetadata) {
layer_state_t* s = getLayerState(sc);
@@ -1729,8 +1974,10 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFocus
return *this;
}
-SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::syncInputWindows() {
- mInputWindowCommands.syncInputWindows = true;
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::addWindowInfosReportedListener(
+ sp<gui::IWindowInfosReportedListener> windowInfosReportedListener) {
+ mInputWindowCommands.windowInfosReportedListeners.insert(windowInfosReportedListener);
return *this;
}
@@ -1837,6 +2084,45 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrame
return *this;
}
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setDefaultFrameRateCompatibility(const sp<SurfaceControl>& 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::setFrameRateCategory(
+ const sp<SurfaceControl>& sc, int8_t category, bool smoothSwitchOnly) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eFrameRateCategoryChanged;
+ s->frameRateCategory = category;
+ s->frameRateCategorySmoothSwitchOnly = smoothSwitchOnly;
+ return *this;
+}
+
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc,
+ int8_t strategy) {
+ layer_state_t* s = getLayerState(sc);
+ if (!s) {
+ mStatus = BAD_INDEX;
+ return *this;
+ }
+ s->what |= layer_state_t::eFrameRateSelectionStrategyChanged;
+ s->frameRateSelectionStrategy = strategy;
+ return *this;
+}
+
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixedTransformHint(
const sp<SurfaceControl>& sc, int32_t fixedTransformHint) {
layer_state_t* s = getLayerState(sc);
@@ -1855,7 +2141,7 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFixed
SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setFrameTimelineInfo(
const FrameTimelineInfo& frameTimelineInfo) {
- mFrameTimelineInfo.merge(frameTimelineInfo);
+ mergeFrameTimelineInfo(mFrameTimelineInfo, frameTimelineInfo);
return *this;
}
@@ -1949,6 +2235,23 @@ SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::setDropI
return *this;
}
+SurfaceComposerClient::Transaction& SurfaceComposerClient::Transaction::enableBorder(
+ const sp<SurfaceControl>& 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<IBinder>& token) {
@@ -2004,7 +2307,6 @@ void SurfaceComposerClient::Transaction::setDisplayProjection(const sp<IBinder>&
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<IBinder>& token, uint32_t width, uint32_t height) {
@@ -2014,6 +2316,59 @@ void SurfaceComposerClient::Transaction::setDisplaySize(const sp<IBinder>& 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 = other;
+ }
+ } else if (t.vsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
+ t = other;
+ }
+}
+
+SurfaceComposerClient::Transaction&
+SurfaceComposerClient::Transaction::setTrustedPresentationCallback(
+ const sp<SurfaceControl>& sc, TrustedPresentationCallback cb,
+ const TrustedPresentationThresholds& thresholds, void* context,
+ sp<SurfaceComposerClient::PresentationCallbackRAII>& 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<SurfaceControl>& 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) {}
@@ -2022,11 +2377,11 @@ SurfaceComposerClient::SurfaceComposerClient(const sp<ISurfaceComposerClient>& c
: mStatus(NO_ERROR), mClient(client) {}
void SurfaceComposerClient::onFirstRef() {
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+ sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
if (sf != nullptr && mStatus == NO_INIT) {
sp<ISurfaceComposerClient> conn;
- conn = sf->createConnection();
- if (conn != nullptr) {
+ binder::Status status = sf->createConnection(&conn);
+ if (status.isOk() && conn != nullptr) {
mClient = conn;
mStatus = NO_ERROR;
}
@@ -2063,8 +2418,14 @@ void SurfaceComposerClient::dispose() {
mStatus = NO_INIT;
}
+status_t SurfaceComposerClient::bootFinished() {
+ sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+ binder::Status status = sf->bootFinished();
+ return statusTFromBinderStatus(status);
+}
+
sp<SurfaceControl> SurfaceComposerClient::createSurface(const String8& name, uint32_t w, uint32_t h,
- PixelFormat format, uint32_t flags,
+ PixelFormat format, int32_t flags,
const sp<IBinder>& parentHandle,
LayerMetadata metadata,
uint32_t* outTransformHint) {
@@ -2074,60 +2435,31 @@ sp<SurfaceControl> SurfaceComposerClient::createSurface(const String8& name, uin
return s;
}
-sp<SurfaceControl> SurfaceComposerClient::createWithSurfaceParent(const String8& name, uint32_t w,
- uint32_t h, PixelFormat format,
- uint32_t flags, Surface* parent,
- LayerMetadata metadata,
- uint32_t* outTransformHint) {
- sp<SurfaceControl> sur;
- status_t err = mStatus;
-
- if (mStatus == NO_ERROR) {
- sp<IBinder> handle;
- sp<IGraphicBufferProducer> parentGbp = parent->getIGraphicBufferProducer();
- sp<IGraphicBufferProducer> 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<SurfaceControl>* outSurface, uint32_t flags,
+ sp<SurfaceControl>* outSurface, int32_t flags,
const sp<IBinder>& parentHandle,
LayerMetadata metadata,
uint32_t* outTransformHint) {
- sp<SurfaceControl> sur;
status_t err = mStatus;
if (mStatus == NO_ERROR) {
- sp<IBinder> handle;
- sp<IGraphicBufferProducer> 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;
@@ -2138,12 +2470,22 @@ sp<SurfaceControl> SurfaceComposerClient::mirrorSurface(SurfaceControl* mirrorFr
return nullptr;
}
- sp<IBinder> handle;
sp<IBinder> 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<SurfaceControl> 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;
}
@@ -2152,7 +2494,8 @@ status_t SurfaceComposerClient::clearLayerFrameStats(const sp<IBinder>& 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<IBinder>& token,
@@ -2160,21 +2503,28 @@ status_t SurfaceComposerClient::getLayerFrameStats(const sp<IBinder>& 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<ISurfaceComposer> sf(ComposerService::getComposerService());
- return sf->enableVSyncInjections(enable);
-}
-
-status_t SurfaceComposerClient::injectVSync(nsecs_t when) {
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
- return sf->injectVSync(when);
-}
-
status_t SurfaceComposerClient::getDisplayState(const sp<IBinder>& display,
ui::DisplayState* state) {
gui::DisplayState ds;
@@ -2186,23 +2536,139 @@ status_t SurfaceComposerClient::getDisplayState(const sp<IBinder>& display,
state->layerStackSpaceRect =
ui::Size(ds.layerStackSpaceRect.width, ds.layerStackSpaceRect.height);
}
- return status.transactionError();
+ return statusTFromBinderStatus(status);
}
-status_t SurfaceComposerClient::getStaticDisplayInfo(const sp<IBinder>& display,
- ui::StaticDisplayInfo* info) {
- return ComposerService::getComposerService()->getStaticDisplayInfo(display, info);
+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<ui::DisplayConnectionType>(ginfo.connectionType);
+ outInfo->density = ginfo.density;
+ outInfo->secure = ginfo.secure;
+ outInfo->installOrientation = static_cast<ui::Rotation>(ginfo.installOrientation);
+
+ if (const std::optional<gui::DeviceProductInfo> dpi = ginfo.deviceProductInfo) {
+ DeviceProductInfo info;
+ info.name = dpi->name;
+ if (dpi->manufacturerPnpId.size() > 0) {
+ // copid from PnpId = std::array<char, 4> in ui/DeviceProductInfo.h
+ constexpr int kMaxPnpIdSize = 4;
+ size_t count = std::max<size_t>(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;
+
+ const gui::DeviceProductInfo::ManufactureOrModelDate& date =
+ dpi->manufactureOrModelDate;
+ if (date.getTag() == Tag::modelYear) {
+ DeviceProductInfo::ModelYear modelYear;
+ modelYear.year = static_cast<uint32_t>(date.get<Tag::modelYear>().year);
+ info.manufactureOrModelDate = modelYear;
+ } else if (date.getTag() == Tag::manufactureYear) {
+ DeviceProductInfo::ManufactureYear manufactureYear;
+ manufactureYear.year = date.get<Tag::manufactureYear>().modelYear.year;
+ info.manufactureOrModelDate = manufactureYear;
+ } else if (date.getTag() == Tag::manufactureWeekAndYear) {
+ DeviceProductInfo::ManufactureWeekAndYear weekAndYear;
+ weekAndYear.year =
+ date.get<Tag::manufactureWeekAndYear>().manufactureYear.modelYear.year;
+ weekAndYear.week = date.get<Tag::manufactureWeekAndYear>().week;
+ info.manufactureOrModelDate = weekAndYear;
+ }
+
+ outInfo->deviceProductInfo = info;
+ }
+ }
+ return statusTFromBinderStatus(status);
+}
+
+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.peakRefreshRate = mode.peakRefreshRate;
+ outMode.vsyncRate = mode.vsyncRate;
+ 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<ui::Hdr>(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<ui::ColorMode>(cmode));
+ }
+
+ outInfo->activeColorMode = static_cast<ui::ColorMode>(ginfo.activeColorMode);
+
+ std::vector<ui::Hdr> types;
+ types.reserve(ginfo.hdrCapabilities.supportedHdrTypes.size());
+ for (const auto& hdr : ginfo.hdrCapabilities.supportedHdrTypes) {
+ types.push_back(static_cast<ui::Hdr>(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<IBinder>& display,
- ui::DynamicDisplayInfo* info) {
- return ComposerService::getComposerService()->getDynamicDisplayInfo(display, info);
+status_t SurfaceComposerClient::getDynamicDisplayInfoFromToken(const sp<IBinder>& 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<IBinder>& 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;
}
@@ -2216,58 +2682,130 @@ status_t SurfaceComposerClient::getActiveDisplayMode(const sp<IBinder>& display,
return NAME_NOT_FOUND;
}
-status_t SurfaceComposerClient::setDesiredDisplayModeSpecs(
- const sp<IBinder>& 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<IBinder>& displayToken,
+ const gui::DisplayModeSpecs& specs) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->setDesiredDisplayModeSpecs(displayToken,
+ specs);
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::getDesiredDisplayModeSpecs(const sp<IBinder>& 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<IBinder>& 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<IBinder>& display,
ColorMode colorMode) {
- return ComposerService::getComposerService()->setActiveColorMode(display, colorMode);
+ binder::Status status = ComposerServiceAIDL::getComposerService()
+ ->setActiveColorMode(display, static_cast<int>(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<IBinder>& display,
ui::DisplayModeId displayModeId) {
- return ComposerService::getComposerService()->setBootDisplayMode(display, displayModeId);
+ binder::Status status = ComposerServiceAIDL::getComposerService()
+ ->setBootDisplayMode(display, static_cast<int>(displayModeId));
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::clearBootDisplayMode(const sp<IBinder>& display) {
binder::Status status =
ComposerServiceAIDL::getComposerService()->clearBootDisplayMode(display);
- return status.transactionError();
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* 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<ui::Hdr>(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);
+status_t SurfaceComposerClient::setGameModeFrameRateOverride(uid_t uid, float frameRate) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->setGameModeFrameRateOverride(uid, frameRate);
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setGameDefaultFrameRateOverride(uid_t uid, float frameRate) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->setGameDefaultFrameRateOverride(uid,
+ frameRate);
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::updateSmallAreaDetection(std::vector<int32_t>& appIds,
+ std::vector<float>& thresholds) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->updateSmallAreaDetection(appIds, thresholds);
+ return statusTFromBinderStatus(status);
+}
+
+status_t SurfaceComposerClient::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->setSmallAreaDetectionThreshold(appId,
+ threshold);
+ return statusTFromBinderStatus(status);
}
void SurfaceComposerClient::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
@@ -2286,57 +2824,137 @@ void SurfaceComposerClient::setDisplayPowerMode(const sp<IBinder>& 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<ui::Dataspace>(pref.defaultDataspace);
+ *defaultPixelFormat = static_cast<ui::PixelFormat>(pref.defaultPixelFormat);
+ *wideColorGamutDataspace = static_cast<ui::Dataspace>(pref.wideColorGamutDataspace);
+ *wideColorGamutPixelFormat = static_cast<ui::PixelFormat>(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<IBinder>& display,
const std::vector<ui::Hdr>& hdrTypes) {
- return ComposerService::getComposerService()->overrideHdrTypes(display, hdrTypes);
+ std::vector<int32_t> hdrTypesVector;
+ hdrTypesVector.reserve(hdrTypes.size());
+ for (auto t : hdrTypes) {
+ hdrTypesVector.push_back(static_cast<int32_t>(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<IBinder>& 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<ui::PixelFormat>(attrs.format);
+ *outDataspace = static_cast<ui::Dataspace>(attrs.dataspace);
+ *outComponentMask = static_cast<uint8_t>(attrs.componentMask);
+ }
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::setDisplayContentSamplingEnabled(const sp<IBinder>& 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<int8_t>(componentMask),
+ static_cast<int64_t>(maxFrames));
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::getDisplayedContentSample(const sp<IBinder>& 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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(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<uint64_t>(s));
+ }
+ }
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::isWideColorDisplay(const sp<IBinder>& display,
@@ -2344,39 +2962,55 @@ status_t SurfaceComposerClient::isWideColorDisplay(const sp<IBinder>& display,
binder::Status status =
ComposerServiceAIDL::getComposerService()->isWideColorDisplay(display,
outIsWideColorDisplay);
- return status.transactionError();
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::addRegionSamplingListener(
const Rect& samplingArea, const sp<IBinder>& stopLayerHandle,
const sp<IRegionSamplingListener>& 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<IRegionSamplingListener>& 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<gui::IFpsListener>& listener) {
- return ComposerService::getComposerService()->addFpsListener(taskId, listener);
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->addFpsListener(taskId, listener);
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::removeFpsListener(const sp<gui::IFpsListener>& listener) {
- return ComposerService::getComposerService()->removeFpsListener(listener);
+ binder::Status status = ComposerServiceAIDL::getComposerService()->removeFpsListener(listener);
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::addTunnelModeEnabledListener(
const sp<gui::ITunnelModeEnabledListener>& listener) {
- return ComposerService::getComposerService()->addTunnelModeEnabledListener(listener);
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->addTunnelModeEnabledListener(listener);
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::removeTunnelModeEnabledListener(
const sp<gui::ITunnelModeEnabledListener>& listener) {
- return ComposerService::getComposerService()->removeTunnelModeEnabledListener(listener);
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->removeTunnelModeEnabledListener(listener);
+ return statusTFromBinderStatus(status);
}
bool SurfaceComposerClient::getDisplayBrightnessSupport(const sp<IBinder>& displayToken) {
@@ -2392,7 +3026,7 @@ status_t SurfaceComposerClient::setDisplayBrightness(const sp<IBinder>& displayT
binder::Status status =
ComposerServiceAIDL::getComposerService()->setDisplayBrightness(displayToken,
brightness);
- return status.transactionError();
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::addHdrLayerInfoListener(
@@ -2400,7 +3034,7 @@ status_t SurfaceComposerClient::addHdrLayerInfoListener(
binder::Status status =
ComposerServiceAIDL::getComposerService()->addHdrLayerInfoListener(displayToken,
listener);
- return status.transactionError();
+ return statusTFromBinderStatus(status);
}
status_t SurfaceComposerClient::removeHdrLayerInfoListener(
@@ -2408,45 +3042,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<DisplayDecorationSupport> SurfaceComposerClient::getDisplayDecorationSupport(
const sp<IBinder>& displayToken) {
+ std::optional<gui::DisplayDecorationSupport> gsupport;
+ binder::Status status =
+ ComposerServiceAIDL::getComposerService()->getDisplayDecorationSupport(displayToken,
+ &gsupport);
std::optional<DisplayDecorationSupport> support;
- ComposerService::getComposerService()->getDisplayDecorationSupport(displayToken, &support);
+ if (status.isOk() && gsupport.has_value()) {
+ support.emplace(DisplayDecorationSupport{
+ .format =
+ static_cast<aidl::android::hardware::graphics::common::PixelFormat>(
+ gsupport->format),
+ .alphaInterpretation =
+ static_cast<aidl::android::hardware::graphics::common::AlphaInterpretation>(
+ 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>& windowInfosListener,
std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* outInitialInfo) {
return WindowInfosListenerReporter::getInstance()
- ->addWindowInfosListener(windowInfosListener, ComposerService::getComposerService(),
+ ->addWindowInfosListener(windowInfosListener, ComposerServiceAIDL::getComposerService(),
outInitialInfo);
}
status_t SurfaceComposerClient::removeWindowInfosListener(
const sp<WindowInfosListener>& windowInfosListener) {
return WindowInfosListenerReporter::getInstance()
- ->removeWindowInfosListener(windowInfosListener, ComposerService::getComposerService());
+ ->removeWindowInfosListener(windowInfosListener,
+ ComposerServiceAIDL::getComposerService());
}
// ----------------------------------------------------------------------------
@@ -2457,16 +3125,16 @@ 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,
+status_t ScreenshotClient::captureDisplay(DisplayId displayId, const gui::CaptureArgs& captureArgs,
const sp<IScreenCaptureListener>& captureListener) {
sp<gui::ISurfaceComposer> s(ComposerServiceAIDL::getComposerService());
if (s == nullptr) return NO_INIT;
- binder::Status status = s->captureDisplayById(displayId.value, captureListener);
- return status.transactionError();
+ binder::Status status = s->captureDisplayById(displayId.value, captureArgs, captureListener);
+ return statusTFromBinderStatus(status);
}
status_t ScreenshotClient::captureLayers(const LayerCaptureArgs& captureArgs,
@@ -2475,7 +3143,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);
}
// ---------------------------------------------------------------------------------
@@ -2498,6 +3166,7 @@ void ReleaseCallbackThread::threadMain() {
while (true) {
{
std::unique_lock<std::mutex> lock(mMutex);
+ base::ScopedLockAssertion assumeLocked(mMutex);
callbackInfos = std::move(mCallbackInfos);
mCallbackInfos = {};
}
@@ -2510,6 +3179,7 @@ void ReleaseCallbackThread::threadMain() {
{
std::unique_lock<std::mutex> 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 654fb336fe..c5f9c38ca3 100644
--- a/libs/gui/SurfaceControl.cpp
+++ b/libs/gui/SurfaceControl.cpp
@@ -26,6 +26,7 @@
#include <utils/Errors.h>
#include <utils/KeyedVector.h>
#include <utils/Log.h>
+#include <utils/Looper.h>
#include <utils/threads.h>
#include <binder/IPCThreadState.h>
@@ -34,8 +35,9 @@
#include <ui/Rect.h>
#include <ui/StaticDisplayInfo.h>
-#include <gui/BufferQueueCore.h>
#include <gui/BLASTBufferQueue.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/Choreographer.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
@@ -49,13 +51,12 @@ namespace android {
// ============================================================================
SurfaceControl::SurfaceControl(const sp<SurfaceComposerClient>& client, const sp<IBinder>& handle,
- const sp<IGraphicBufferProducer>& 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<SurfaceComposerClient>& client, const sp
SurfaceControl::SurfaceControl(const sp<SurfaceControl>& 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<Surface> 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<IBinder> 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<Choreographer> SurfaceControl::getChoreographer() {
+ if (mChoreographer) {
+ return mChoreographer;
+ }
+ sp<Looper> looper = Looper::getForThread();
+ if (!looper.get()) {
+ ALOGE("%s: No looper prepared for thread", __func__);
+ return nullptr;
+ }
+ mChoreographer = std::make_shared<Choreographer>(looper, getHandle());
+ status_t result = mChoreographer->initialize();
+ if (result != OK) {
+ ALOGE("Failed to initialize choreographer");
+ mChoreographer = nullptr;
+ }
+ return mChoreographer;
+}
+
sp<IGraphicBufferProducer> 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<IBinder> client;
sp<IBinder> 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<ISurfaceComposerClient>(client)),
- handle.get(), nullptr, layerId,
- width, height, format,
- transformHint);
+ *outSurfaceControl = new SurfaceControl(new SurfaceComposerClient(
+ interface_cast<ISurfaceComposerClient>(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 1a8fc1a00a..2d863c2585 100644
--- a/libs/gui/SyncFeatures.cpp
+++ b/libs/gui/SyncFeatures.cpp
@@ -36,8 +36,12 @@ SyncFeatures::SyncFeatures() : Singleton<SyncFeatures>(),
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<SyncFeatures>(),
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 1c435304a8..a590c86ceb 100644
--- a/libs/gui/TEST_MAPPING
+++ b/libs/gui/TEST_MAPPING
@@ -2,6 +2,63 @@
"imports": [
{
"path": "frameworks/native/libs/nativewindow"
+ },
+ {
+ "path": "frameworks/native/services/surfaceflinger"
+ }
+ ],
+ "presubmit": [
+ {
+ "name": "libgui_test",
+ "options": [
+ // TODO(b/277604286): Failing on Cuttlefish.
+ {
+ "exclude-filter": "MultiTextureConsumerTest#EGLImageTargetWorks"
+ },
+
+ // TODO(b/285011590): Failing on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTest#GetHdrSupport"
+ },
+ {
+ "exclude-filter": "SurfaceTest#GetWideColorSupport"
+ },
+
+ // TODO(b/285006554): Failing on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTextureGLTest#InvalidWidthOrHeightFails"
+ },
+
+ // TODO(b/277347351): Known test data issues, failing across devices.
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferNpot"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferPow2"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BufferWithCrop"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLTest#TexturingFromCpuFilledYV12BuffersRepeatedly"
+ },
+
+ // TODO(b/285041169): Hanging on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTextureGLThreadToGLTest#UpdateTexImageBeforeFrameFinishedCompletes"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLThreadToGLTest#RepeatedUpdateTexImageBeforeFrameFinishedCompletes"
+ },
+ {
+ "exclude-filter": "SurfaceTextureGLThreadToGLTest#RepeatedUpdateTexImageAfterFrameFinishedCompletes"
+ },
+
+ // TODO(b/285041070): Failing on Cuttlefish.
+ {
+ "exclude-filter": "SurfaceTextureGLToGLTest#EglDestroySurfaceUnrefsBuffers"
+ }
+ ]
}
]
}
diff --git a/libs/gui/TransactionTracing.cpp b/libs/gui/TransactionTracing.cpp
deleted file mode 100644
index eedc3df009..0000000000
--- 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 <private/gui/ComposerService.h>
-
-namespace android {
-
-sp<TransactionTraceListener> TransactionTraceListener::sInstance = nullptr;
-std::mutex TransactionTraceListener::sMutex;
-
-TransactionTraceListener::TransactionTraceListener() {}
-
-sp<TransactionTraceListener> TransactionTraceListener::getInstance() {
- const std::lock_guard<std::mutex> lock(sMutex);
-
- if (sInstance == nullptr) {
- sInstance = new TransactionTraceListener;
-
- sp<ISurfaceComposer> 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 23f0921e99..8e00c2fe32 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<size_t>(uintPreferredFrameTimelineIndex);
- for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+ uint32_t uintFrameTimelinesLength;
+ SAFE_PARCEL(parcel->readUint32, &uintFrameTimelinesLength);
+ vsync.frameTimelinesLength = static_cast<size_t>(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 4e966d1393..ba1d196e9c 100644
--- a/libs/gui/WindowInfo.cpp
+++ b/libs/gui/WindowInfo.cpp
@@ -26,6 +26,41 @@
namespace android::gui {
+namespace {
+
+std::ostream& operator<<(std::ostream& out, const sp<IBinder>& binder) {
+ if (binder == nullptr) {
+ out << "<null>";
+ } else {
+ out << binder.get();
+ }
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const Region& region) {
+ if (region.isEmpty()) {
+ out << "<empty>";
+ return out;
+ }
+
+ bool first = true;
+ Region::const_iterator cur = region.begin();
+ Region::const_iterator const tail = region.end();
+ while (cur != tail) {
+ if (first) {
+ first = false;
+ } else {
+ out << "|";
+ }
+ out << "[" << cur->left << "," << cur->top << "][" << cur->right << "," << cur->bottom
+ << "]";
+ cur++;
+ }
+ return out;
+}
+
+} // namespace
+
void WindowInfo::setInputConfig(ftl::Flags<InputConfig> config, bool value) {
if (value) {
inputConfig |= config;
@@ -43,7 +78,7 @@ bool WindowInfo::touchableRegionContainsPoint(int32_t x, int32_t y) const {
}
bool WindowInfo::frameContainsPoint(int32_t x, int32_t y) const {
- return x >= frameLeft && x < frameRight && y >= frameTop && y < frameBottom;
+ return x >= frame.left && x < frame.right && y >= frame.top && y < frame.bottom;
}
bool WindowInfo::supportsSplitTouch() const {
@@ -59,16 +94,14 @@ bool WindowInfo::interceptsStylus() const {
}
bool WindowInfo::overlaps(const WindowInfo* other) const {
- const bool nonEmpty = (frameRight - frameLeft > 0) || (frameBottom - frameTop > 0);
- return nonEmpty && frameLeft < other->frameRight && frameRight > other->frameLeft &&
- frameTop < other->frameBottom && frameBottom > other->frameTop;
+ return !frame.isEmpty() && frame.left < other->frame.right && frame.right > other->frame.left &&
+ frame.top < other->frame.bottom && frame.bottom > other->frame.top;
}
bool WindowInfo::operator==(const WindowInfo& info) const {
return info.token == token && info.id == id && info.name == name &&
- info.dispatchingTimeout == dispatchingTimeout && info.frameLeft == frameLeft &&
- info.frameTop == frameTop && info.frameRight == frameRight &&
- info.frameBottom == frameBottom && info.surfaceInset == surfaceInset &&
+ info.dispatchingTimeout == dispatchingTimeout && info.frame == frame &&
+ info.contentSize == contentSize && info.surfaceInset == surfaceInset &&
info.globalScaleFactor == globalScaleFactor && info.transform == transform &&
info.touchableRegion.hasSameRects(touchableRegion) &&
info.touchOcclusionMode == touchOcclusionMode && info.ownerPid == ownerPid &&
@@ -76,7 +109,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 {
@@ -90,8 +123,10 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
}
parcel->writeInt32(1);
- // Ensure that the size of the flags that we use is 32 bits for writing into the parcel.
+ // Ensure that the size of custom types are what we expect for writing into the parcel.
static_assert(sizeof(inputConfig) == 4u);
+ static_assert(sizeof(ownerPid.val()) == 4u);
+ static_assert(sizeof(ownerUid.val()) == 4u);
// clang-format off
status_t status = parcel->writeStrongBinder(token) ?:
@@ -101,10 +136,9 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
parcel->writeInt32(layoutParamsFlags.get()) ?:
parcel->writeInt32(
static_cast<std::underlying_type_t<WindowInfo::Type>>(layoutParamsType)) ?:
- parcel->writeInt32(frameLeft) ?:
- parcel->writeInt32(frameTop) ?:
- parcel->writeInt32(frameRight) ?:
- parcel->writeInt32(frameBottom) ?:
+ parcel->write(frame) ?:
+ parcel->writeInt32(contentSize.width) ?:
+ parcel->writeInt32(contentSize.height) ?:
parcel->writeInt32(surfaceInset) ?:
parcel->writeFloat(globalScaleFactor) ?:
parcel->writeFloat(alpha) ?:
@@ -115,8 +149,8 @@ status_t WindowInfo::writeToParcel(android::Parcel* parcel) const {
parcel->writeFloat(transform.dsdy()) ?:
parcel->writeFloat(transform.ty()) ?:
parcel->writeInt32(static_cast<int32_t>(touchOcclusionMode)) ?:
- parcel->writeInt32(ownerPid) ?:
- parcel->writeInt32(ownerUid) ?:
+ parcel->writeInt32(ownerPid.val()) ?:
+ parcel->writeInt32(ownerUid.val()) ?:
parcel->writeUtf8AsUtf16(packageName) ?:
parcel->writeInt32(inputConfig.get()) ?:
parcel->writeInt32(displayId) ?:
@@ -124,8 +158,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;
}
@@ -147,16 +181,15 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
}
float dsdx, dtdx, tx, dtdy, dsdy, ty;
- int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt;
+ int32_t lpFlags, lpType, touchOcclusionModeInt, inputConfigInt, ownerPidInt, ownerUidInt;
sp<IBinder> touchableRegionCropHandleSp;
// clang-format off
status = parcel->readInt32(&lpFlags) ?:
parcel->readInt32(&lpType) ?:
- parcel->readInt32(&frameLeft) ?:
- parcel->readInt32(&frameTop) ?:
- parcel->readInt32(&frameRight) ?:
- parcel->readInt32(&frameBottom) ?:
+ parcel->read(frame) ?:
+ parcel->readInt32(&contentSize.width) ?:
+ parcel->readInt32(&contentSize.height) ?:
parcel->readInt32(&surfaceInset) ?:
parcel->readFloat(&globalScaleFactor) ?:
parcel->readFloat(&alpha) ?:
@@ -167,8 +200,8 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
parcel->readFloat(&dsdy) ?:
parcel->readFloat(&ty) ?:
parcel->readInt32(&touchOcclusionModeInt) ?:
- parcel->readInt32(&ownerPid) ?:
- parcel->readInt32(&ownerUid) ?:
+ parcel->readInt32(&ownerPidInt) ?:
+ parcel->readInt32(&ownerUidInt) ?:
parcel->readUtf8FromUtf16(&packageName) ?:
parcel->readInt32(&inputConfigInt) ?:
parcel->readInt32(&displayId) ?:
@@ -177,7 +210,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) {
@@ -189,6 +223,8 @@ status_t WindowInfo::readFromParcel(const android::Parcel* parcel) {
transform.set({dsdx, dtdx, tx, dtdy, dsdy, ty, 0, 0, 1});
touchOcclusionMode = static_cast<TouchOcclusionMode>(touchOcclusionModeInt);
inputConfig = ftl::Flags<InputConfig>(inputConfigInt);
+ ownerPid = Pid{ownerPidInt};
+ ownerUid = Uid{static_cast<uid_t>(ownerUidInt)};
touchableRegionCropHandle = touchableRegionCropHandleSp;
return OK;
@@ -221,4 +257,24 @@ sp<IBinder> WindowInfoHandle::getToken() const {
void WindowInfoHandle::updateFrom(sp<WindowInfoHandle> handle) {
mInfo = handle->mInfo;
}
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window) {
+ const WindowInfo& info = *window.getInfo();
+ std::string transform;
+ info.transform.dump(transform, "transform", " ");
+ out << "name=" << info.name << ", id=" << info.id << ", displayId=" << info.displayId
+ << ", inputConfig=" << info.inputConfig.string() << ", alpha=" << info.alpha << ", frame=["
+ << info.frame.left << "," << info.frame.top << "][" << info.frame.right << ","
+ << info.frame.bottom << "], globalScale=" << info.globalScaleFactor
+ << ", applicationInfo.name=" << info.applicationInfo.name
+ << ", applicationInfo.token=" << info.applicationInfo.token
+ << ", touchableRegion=" << info.touchableRegion << ", ownerPid=" << info.ownerPid.toString()
+ << ", ownerUid=" << info.ownerUid.toString() << ", dispatchingTimeout="
+ << std::chrono::duration_cast<std::chrono::milliseconds>(info.dispatchingTimeout).count()
+ << "ms, token=" << info.token.get()
+ << ", touchOcclusionMode=" << ftl::enum_string(info.touchOcclusionMode) << "\n"
+ << transform;
+ return out;
+}
+
} // namespace android::gui
diff --git a/libs/gui/WindowInfosListenerReporter.cpp b/libs/gui/WindowInfosListenerReporter.cpp
index cfc7dbc463..0929b8e120 100644
--- a/libs/gui/WindowInfosListenerReporter.cpp
+++ b/libs/gui/WindowInfosListenerReporter.cpp
@@ -14,15 +14,17 @@
* limitations under the License.
*/
-#include <gui/ISurfaceComposer.h>
+#include <android/gui/ISurfaceComposer.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/WindowInfosListenerReporter.h>
+#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> WindowInfosListenerReporter::getInstance() {
static sp<WindowInfosListenerReporter> sInstance = new WindowInfosListenerReporter;
@@ -31,13 +33,19 @@ sp<WindowInfosListenerReporter> WindowInfosListenerReporter::getInstance() {
status_t WindowInfosListenerReporter::addWindowInfosListener(
const sp<WindowInfosListener>& windowInfosListener,
- const sp<ISurfaceComposer>& surfaceComposer,
+ const sp<gui::ISurfaceComposer>& surfaceComposer,
std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* 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>& windowInfosListener,
- const sp<ISurfaceComposer>& surfaceComposer) {
+ const sp<gui::ISurfaceComposer>& 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<WindowInfo>& windowInfos, const std::vector<DisplayInfo>& displayInfos,
- const sp<IWindowInfosReportedListener>& windowInfosReportedListener) {
- std::unordered_set<sp<WindowInfosListener>, SpHash<WindowInfosListener>> windowInfosListeners;
+ const gui::WindowInfosUpdate& update) {
+ std::unordered_set<sp<WindowInfosListener>, gui::SpHash<WindowInfosListener>>
+ 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<ISurfaceComposer>& composerService) {
+void WindowInfosListenerReporter::reconnect(const sp<gui::ISurfaceComposer>& 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 0000000000..38ae5ef102
--- /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 <gui/WindowInfosUpdate.h>
+#include <private/gui/ParcelUtils.h>
+
+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, &timestamp);
+
+ 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<uint32_t>(windowInfos.size()));
+ for (auto& windowInfo : windowInfos) {
+ SAFE_PARCEL(windowInfo.writeToParcel, parcel);
+ }
+
+ SAFE_PARCEL(parcel->writeUint32, static_cast<uint32_t>(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
index 1b13761392..5785907a9c 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/gui/aidl/android/gui/CachingHint.aidl b/libs/gui/aidl/android/gui/CachingHint.aidl
new file mode 100644
index 0000000000..b35c79547f
--- /dev/null
+++ b/libs/gui/aidl/android/gui/CachingHint.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;
+
+/*
+ * Hint for configuring caching behavior for a layer
+ * @hide
+ */
+@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/CaptureArgs.aidl b/libs/gui/aidl/android/gui/CaptureArgs.aidl
new file mode 100644
index 0000000000..920d94980a
--- /dev/null
+++ b/libs/gui/aidl/android/gui/CaptureArgs.aidl
@@ -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.
+ */
+
+package android.gui;
+
+parcelable CaptureArgs cpp_header "gui/DisplayCaptureArgs.h";
diff --git a/libs/gui/aidl/android/gui/Color.aidl b/libs/gui/aidl/android/gui/Color.aidl
new file mode 100644
index 0000000000..12af066562
--- /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 0000000000..b615824a7d
--- /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 0000000000..5d913b1da6
--- /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 0000000000..eea12dc75d
--- /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 0000000000..98404cf0fd
--- /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 0000000000..72c4ede7ac
--- /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
index 9504e993f8..023049657b 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 0000000000..f605177cfd
--- /dev/null
+++ b/libs/gui/aidl/android/gui/DisplayMode.aidl
@@ -0,0 +1,39 @@
+/*
+ * 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;
+
+ // Some modes have peak refresh rate lower than the panel vsync rate.
+ float peakRefreshRate = 0.0f;
+ float vsyncRate = 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 0000000000..af138c7539
--- /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/gui/aidl/android/gui/DisplayPrimaries.aidl b/libs/gui/aidl/android/gui/DisplayPrimaries.aidl
new file mode 100644
index 0000000000..dbf668c629
--- /dev/null
+++ b/libs/gui/aidl/android/gui/DisplayPrimaries.aidl
@@ -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.
+ */
+
+package android.gui;
+
+// copied from libui ConfigStoreTypes.h
+
+/** @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 0000000000..f4b6dadc49
--- /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 0000000000..3114929e86
--- /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<DisplayMode> 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 0000000000..aaabdb5b54
--- /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 0000000000..a145e74b11
--- /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
index 255ce568d2..4b647a4ad2 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,33 @@
* limitations under the License.
*/
-#pragma once
+package android.gui;
-#include <stdint.h>
-
-#include <binder/Parcel.h>
-
-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;
+ long startTimeNanos = 0;
- status_t write(Parcel& output) const;
- status_t read(const Parcel& input);
+ // 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;
- void merge(const FrameTimelineInfo& other);
- void clear();
-};
+ // The VsyncId of a frame that was not drawn and squashed into this frame.
+ long skippedFrameVsyncId = INVALID_VSYNC_ID;
-} // namespace android
+ // The start time of a frame that was not drawn and squashed into this frame.
+ long skippedFrameStartTimeNanos = 0;
+}
diff --git a/libs/gui/aidl/android/gui/HdrCapabilities.aidl b/libs/gui/aidl/android/gui/HdrCapabilities.aidl
new file mode 100644
index 0000000000..9d06da9f27
--- /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 0000000000..1bcfd38eff
--- /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 0000000000..1be74b46e7
--- /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/IDisplayEventConnection.aidl b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
index 9781ca96f4..008ef19244 100644
--- a/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
+++ b/libs/gui/aidl/android/gui/IDisplayEventConnection.aidl
@@ -18,6 +18,7 @@ package android.gui;
import android.gui.BitTube;
import android.gui.ParcelableVsyncEventData;
+import android.gui.SchedulingPolicy;
/** @hide */
interface IDisplayEventConnection {
@@ -44,4 +45,9 @@ interface IDisplayEventConnection {
* getLatestVsyncEventData() gets the latest vsync event data.
*/
ParcelableVsyncEventData getLatestVsyncEventData();
+
+ /*
+ * getSchedulingPolicy() used in tests to validate the binder thread pririty
+ */
+ SchedulingPolicy getSchedulingPolicy();
}
diff --git a/libs/gui/aidl/android/gui/IHdrConversionConstants.aidl b/libs/gui/aidl/android/gui/IHdrConversionConstants.aidl
new file mode 100644
index 0000000000..7697f2995b
--- /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 fc809c4c88..e8c36ee001 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 b31b37bada..e3122bc300 100644
--- a/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
+++ b/libs/gui/aidl/android/gui/ISurfaceComposer.aidl
@@ -16,39 +16,120 @@
package android.gui;
-import android.gui.DisplayCaptureArgs;
+import android.gui.CaptureArgs;
+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.SchedulingPolicy;
+import android.gui.StalledTransactionInfo;
+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,12 +141,33 @@ 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.
*/
@@ -85,6 +187,28 @@ interface ISurfaceComposer {
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<HdrConversionCapability> 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
* Latency Mode as reported in #getDynamicDisplayInfo.
@@ -109,22 +233,157 @@ interface ISurfaceComposer {
* The subregion can be optionally rotated. It will also be scaled to
* match the size of the output buffer.
*/
- void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
- void captureDisplayById(long displayId, IScreenCaptureListener listener);
+ oneway void captureDisplay(in DisplayCaptureArgs args, IScreenCaptureListener listener);
+
+ /**
+ * Capture the specified screen. This requires the READ_FRAME_BUFFER
+ * permission.
+ */
+ oneway void captureDisplayById(long displayId, in CaptureArgs args,
+ 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
* is a secure window on screen
*/
- void captureLayers(in LayerCaptureArgs args, IScreenCaptureListener listener);
+ oneway 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<LayerDebugInfo> getLayerDebugInfo();
+
+ /**
+ * 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 +397,7 @@ interface ISurfaceComposer {
*/
boolean getDisplayBrightnessSupport(IBinder displayToken);
- /*
+ /**
* Sets the brightness of a display.
*
* displayToken
@@ -153,7 +412,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 +421,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 +430,7 @@ interface ISurfaceComposer {
*/
void removeHdrLayerInfoListener(IBinder displayToken, IHdrLayerInfoListener listener);
- /*
+ /**
* Sends a power boost to the composer. This function is asynchronous.
*
* boostId
@@ -179,5 +438,134 @@ 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.
+ * This override is controlled by game mode interventions.
+ * Passing the frame rate and uid to SurfaceFlinger to update the override mapping
+ * in the LayerHistory.
+ */
+ void setGameModeFrameRateOverride(int uid, float frameRate);
+
+ /**
+ * Set the override frame rate for a specified uid by GameManagerService.
+ * This override is controlled by game default frame rate sysprop:
+ * "ro.surface_flinger.game_default_frame_rate_override" holding the override value,
+ * "persisit.graphics.game_default_frame_rate.enabled" to determine if it's enabled.
+ * Passing the frame rate and uid to SurfaceFlinger to update the override mapping
+ * in the scheduler.
+ */
+ void setGameDefaultFrameRateOverride(int uid, float frameRate);
+
+ oneway void updateSmallAreaDetection(in int[] appIds, in float[] thresholds);
+
+ /**
+ * Set the small area detection threshold for a specified appId by SmallAreaDetectionController.
+ * Passing the threshold and appId to SurfaceFlinger to update the appId-threshold mapping
+ * in the scheduler.
+ */
+ oneway void setSmallAreaDetectionThreshold(int appId, float threshold);
+
+ /**
+ * Enables or disables the frame rate overlay in the top left corner.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void enableRefreshRateOverlay(boolean active);
+
+ /**
+ * Enables or disables the debug flash.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void setDebugFlash(int delay);
+
+ /**
+ * Force composite ahead of next VSYNC.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void scheduleComposite();
+
+ /**
+ * Force commit ahead of next VSYNC.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void scheduleCommit();
+
+ /**
+ * Force all window composition to the GPU (i.e. disable Hardware Overlays).
+ * This can help check if there is a bug in HW Composer.
+ * Requires root or android.permission.HARDWARE_TEST
+ */
+ void forceClientComposition(boolean enabled);
+
+ /**
+ * 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();
+
+ /**
+ * Returns an instance of StalledTransaction if a transaction from the passed pid has not been
+ * applied in SurfaceFlinger due to an unsignaled fence. Otherwise, null is returned.
+ */
+ @nullable StalledTransactionInfo getStalledTransactionInfo(int pid);
+
+ SchedulingPolicy getSchedulingPolicy();
}
diff --git a/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
new file mode 100644
index 0000000000..920257c449
--- /dev/null
+++ b/libs/gui/aidl/android/gui/ISurfaceComposerClient.aidl
@@ -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.
+ */
+
+package android.gui;
+
+import android.gui.CreateSurfaceResult;
+import android.gui.FrameStats;
+import android.gui.LayerMetadata;
+import android.gui.SchedulingPolicy;
+
+/** @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);
+
+ SchedulingPolicy getSchedulingPolicy();
+}
diff --git a/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl b/libs/gui/aidl/android/gui/ITransactionTraceListener.aidl
deleted file mode 100644
index 5cd12fdc2b..0000000000
--- 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 0000000000..faca980f3c
--- /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 0000000000..1368ac512f
--- /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 0000000000..5fb1a83c65
--- /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 0000000000..c307cef70e
--- /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/SchedulingPolicy.aidl b/libs/gui/aidl/android/gui/SchedulingPolicy.aidl
new file mode 100644
index 0000000000..4f7cf0a493
--- /dev/null
+++ b/libs/gui/aidl/android/gui/SchedulingPolicy.aidl
@@ -0,0 +1,23 @@
+/*
+ * 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 */
+parcelable SchedulingPolicy {
+ int policy;
+ int priority;
+}
diff --git a/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl b/libs/gui/aidl/android/gui/StaticDisplayInfo.aidl
new file mode 100644
index 0000000000..0ccda56ef5
--- /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 0000000000..1eea5b44a4
--- /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 0000000000..0ca13b768a
--- /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 b13c60049c..62d1b68147 100644
--- a/libs/gui/android/gui/FocusRequest.aidl
+++ b/libs/gui/android/gui/FocusRequest.aidl
@@ -24,15 +24,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
* from another source such as pointer down.
diff --git a/libs/gui/android/gui/IWindowInfosListener.aidl b/libs/gui/android/gui/IWindowInfosListener.aidl
index a5b2762318..07cb5ed0e6 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
index 5aeb21f6b4..5a9c32845e 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/StalledTransactionInfo.aidl b/libs/gui/android/gui/StalledTransactionInfo.aidl
new file mode 100644
index 0000000000..e6aa9bd1c7
--- /dev/null
+++ b/libs/gui/android/gui/StalledTransactionInfo.aidl
@@ -0,0 +1,24 @@
+/*
+ * 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 */
+parcelable StalledTransactionInfo {
+ String layerName;
+ long bufferId;
+ long frameNumber;
+} \ No newline at end of file
diff --git a/libs/gui/android/gui/TouchOcclusionMode.aidl b/libs/gui/android/gui/TouchOcclusionMode.aidl
index d91d052135..ed721054df 100644
--- a/libs/gui/android/gui/TouchOcclusionMode.aidl
+++ b/libs/gui/android/gui/TouchOcclusionMode.aidl
@@ -43,5 +43,6 @@ enum TouchOcclusionMode {
* The window won't count for touch occlusion rules if the touch passes
* through it.
*/
- ALLOW
+ ALLOW,
+ ftl_last=ALLOW,
}
diff --git a/libs/gui/android/gui/WindowInfosUpdate.aidl b/libs/gui/android/gui/WindowInfosUpdate.aidl
new file mode 100644
index 0000000000..0c6109da8f
--- /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/bufferqueue/1.0/Conversion.cpp b/libs/gui/bufferqueue/1.0/Conversion.cpp
index 55462c3b1f..96679549b2 100644
--- a/libs/gui/bufferqueue/1.0/Conversion.cpp
+++ b/libs/gui/bufferqueue/1.0/Conversion.cpp
@@ -725,12 +725,7 @@ status_t unflatten(HGraphicBufferProducer::FrameEventsDelta* t,
// These were written as uint8_t for alignment.
uint8_t temp = 0;
FlattenableUtils::read(buffer, size, temp);
- size_t index = static_cast<size_t>(temp);
- if (index >= ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
- return BAD_VALUE;
- }
- t->index = static_cast<uint32_t>(index);
-
+ t->index = static_cast<uint32_t>(temp);
FlattenableUtils::read(buffer, size, temp);
t->addPostCompositeCalled = static_cast<bool>(temp);
FlattenableUtils::read(buffer, size, temp);
@@ -786,8 +781,7 @@ status_t unflatten(HGraphicBufferProducer::FrameEventsDelta* t,
status_t flatten(HGraphicBufferProducer::FrameEventsDelta const& t,
void*& buffer, size_t& size, int*& fds, size_t numFds) {
// Check that t.index is within a valid range.
- if (t.index >= static_cast<uint32_t>(FrameEventHistory::MAX_FRAME_HISTORY)
- || t.index > std::numeric_limits<uint8_t>::max()) {
+ if (t.index > UINT8_MAX || t.index < 0) {
return BAD_VALUE;
}
@@ -887,8 +881,7 @@ status_t unflatten(
uint32_t deltaCount = 0;
FlattenableUtils::read(buffer, size, deltaCount);
- if (static_cast<size_t>(deltaCount) >
- ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
+ if (deltaCount > UINT8_MAX) {
return BAD_VALUE;
}
t->deltas.resize(deltaCount);
@@ -919,7 +912,7 @@ status_t unflatten(
status_t flatten(
HGraphicBufferProducer::FrameEventHistoryDelta const& t,
void*& buffer, size_t& size, int*& fds, size_t& numFds) {
- if (t.deltas.size() > ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
+ if (t.deltas.size() > UINT8_MAX) {
return BAD_VALUE;
}
if (size < getFlattenedSize(t)) {
diff --git a/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp b/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp
index cee1b811a4..fd8ffe1f01 100644
--- a/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp
+++ b/libs/gui/bufferqueue/1.0/H2BGraphicBufferProducer.cpp
@@ -725,8 +725,7 @@ inline status_t flatten(HGraphicBufferProducer::FrameEventsDelta const& t,
std::vector<native_handle_t*>* nh,
void*& buffer, size_t& size, int*& fds, size_t numFds) {
// Check that t.index is within a valid range.
- if (t.index >= static_cast<uint32_t>(FrameEventHistory::MAX_FRAME_HISTORY)
- || t.index > std::numeric_limits<uint8_t>::max()) {
+ if (t.index > UINT8_MAX || t.index < 0) {
return BAD_VALUE;
}
@@ -829,7 +828,7 @@ inline status_t flatten(
HGraphicBufferProducer::FrameEventHistoryDelta const& t,
std::vector<std::vector<native_handle_t*> >* nh,
void*& buffer, size_t& size, int*& fds, size_t& numFds) {
- if (t.deltas.size() > ::android::FrameEventHistory::MAX_FRAME_HISTORY) {
+ if (t.deltas.size() > UINT8_MAX) {
return BAD_VALUE;
}
if (size < getFlattenedSize(t)) {
@@ -1173,9 +1172,12 @@ status_t H2BGraphicBufferProducer::setGenerationNumber(uint32_t generationNumber
String8 H2BGraphicBufferProducer::getConsumerName() const {
String8 lName;
- mBase->getConsumerName([&lName] (hidl_string const& name) {
- lName = name.c_str();
- });
+ status_t transStatus = toStatusT(
+ mBase->getConsumerName([&lName](hidl_string const& name) { lName = name.c_str(); }));
+ if (transStatus != NO_ERROR) {
+ ALOGE("getConsumerName failed to transact: %d", transStatus);
+ return String8("TransactFailed");
+ }
return lName;
}
diff --git a/libs/gui/bufferqueue/2.0/H2BGraphicBufferProducer.cpp b/libs/gui/bufferqueue/2.0/H2BGraphicBufferProducer.cpp
index 2f5b73ccbb..ae00a2642e 100644
--- a/libs/gui/bufferqueue/2.0/H2BGraphicBufferProducer.cpp
+++ b/libs/gui/bufferqueue/2.0/H2BGraphicBufferProducer.cpp
@@ -437,6 +437,10 @@ String8 H2BGraphicBufferProducer::getConsumerName() const {
[&bName](hidl_string const& name) {
bName = name.c_str();
});
+ if (!transResult.isOk()) {
+ LOG(ERROR) << "getConsumerName: corrupted transaction.";
+ return String8("TransactFailed");
+ }
return bName;
}
diff --git a/libs/gui/fuzzer/Android.bp b/libs/gui/fuzzer/Android.bp
new file mode 100644
index 0000000000..cd738acde2
--- /dev/null
+++ b/libs/gui/fuzzer/Android.bp
@@ -0,0 +1,146 @@
+/*
+ * 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",
+ defaults: ["android.hardware.power-ndk_shared"],
+ 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.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,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libgui library",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
+ },
+}
+
+cc_fuzz {
+ name: "libgui_surfaceComposer_fuzzer",
+ srcs: [
+ "libgui_surfaceComposer_fuzzer.cpp",
+ ],
+ defaults: [
+ "libgui_fuzzer_defaults",
+ "service_fuzzer_defaults",
+ ],
+}
+
+cc_fuzz {
+ name: "libgui_surfaceComposerClient_fuzzer",
+ srcs: [
+ "libgui_surfaceComposerClient_fuzzer.cpp",
+ ],
+ defaults: [
+ "libgui_fuzzer_defaults",
+ "service_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 0000000000..96e27c989f
--- /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)
+
+# <a name="libgui_surfaceComposer_fuzzer"></a> 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
+```
+
+# <a name="libgui_surfaceComposerClient_fuzzer"></a> 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
+```
+
+# <a name="libgui_parcelable_fuzzer"></a> 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
+```
+
+# <a name="libgui_bufferQueue_fuzzer"></a> 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
+```
+
+# <a name="libgui_consumer_fuzzer"></a> 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
+```
+
+# <a name="libgui_displayEvent_fuzzer"></a> 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 0000000000..2e270b721f
--- /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 <android-base/stringprintf.h>
+#include <gui/BufferQueueConsumer.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
+#include <gui/bufferqueue/2.0/types.h>
+#include <system/window.h>
+
+#include <libgui_fuzzer_utils.h>
+
+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<BufferQueueProducer>);
+ void invokeQuery(sp<V1_0::utils::H2BGraphicBufferProducer>);
+ void invokeQuery(sp<V2_0::utils::H2BGraphicBufferProducer>);
+ void invokeAcquireBuffer(sp<BufferQueueConsumer>);
+ void invokeOccupancyTracker(sp<BufferQueueConsumer>);
+ sp<SurfaceControl> makeSurfaceControl();
+ sp<BLASTBufferQueue> makeBLASTBufferQueue(sp<SurfaceControl>);
+
+ 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<NativeHandle> getStream() { return mStream; }
+
+private:
+ bool mShouldOwn;
+ sp<NativeHandle> mStream;
+ native_handle_t* mNativeHandle;
+};
+
+sp<SurfaceControl> BufferQueueFuzzer::makeSurfaceControl() {
+ sp<IBinder> handle;
+ const sp<FakeBnSurfaceComposerClient> testClient(new FakeBnSurfaceComposerClient());
+ sp<SurfaceComposerClient> client = new SurfaceComposerClient(testClient);
+ sp<BnGraphicBufferProducer> producer;
+ uint32_t layerId = mFdp.ConsumeIntegral<uint32_t>();
+ std::string layerName = base::StringPrintf("#%d", layerId);
+ return sp<SurfaceControl>::make(client, handle, layerId, layerName,
+ mFdp.ConsumeIntegral<int32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<int32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>());
+}
+
+sp<BLASTBufferQueue> BufferQueueFuzzer::makeBLASTBufferQueue(sp<SurfaceControl> surface) {
+ return sp<BLASTBufferQueue>::make(mFdp.ConsumeRandomLengthString(kMaxBytes), surface,
+ mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<int32_t>());
+}
+
+void BufferQueueFuzzer::invokeBlastBufferQueue() {
+ sp<SurfaceControl> surface = makeSurfaceControl();
+ sp<BLASTBufferQueue> queue = makeBLASTBufferQueue(surface);
+
+ BufferItem item;
+ queue->onFrameAvailable(item);
+ queue->onFrameReplaced(item);
+ uint64_t bufferId = mFdp.ConsumeIntegral<uint64_t>();
+ queue->onFrameDequeued(bufferId);
+ queue->onFrameCancelled(bufferId);
+
+ SurfaceComposerClient::Transaction next;
+ uint64_t frameNumber = mFdp.ConsumeIntegral<uint64_t>();
+ queue->mergeWithNextTransaction(&next, frameNumber);
+ queue->applyPendingTransactions(frameNumber);
+
+ queue->update(surface, mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<int32_t>());
+ queue->setFrameRate(mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeIntegral<int8_t>(),
+ mFdp.ConsumeBool() /*shouldBeSeamless*/);
+ FrameTimelineInfo info;
+ queue->setFrameTimelineInfo(mFdp.ConsumeIntegral<uint64_t>(), info);
+
+ ManageResourceHandle handle(&mFdp);
+ queue->setSidebandStream(handle.getStream());
+
+ queue->getLastTransformHint();
+ queue->getLastAcquiredFrameNum();
+
+ CompositorTiming compTiming;
+ sp<Fence> previousFence = new Fence(memfd_create("pfd", MFD_ALLOW_SEALING));
+ sp<Fence> gpuFence = new Fence(memfd_create("gfd", MFD_ALLOW_SEALING));
+ FrameEventHistoryStats frameStats(frameNumber, mFdp.ConsumeIntegral<uint64_t>(), gpuFence,
+ compTiming, mFdp.ConsumeIntegral<int64_t>(),
+ mFdp.ConsumeIntegral<int64_t>());
+ std::vector<SurfaceControlStats> stats;
+ sp<Fence> presentFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
+ SurfaceControlStats controlStats(surface, mFdp.ConsumeIntegral<int64_t>(),
+ mFdp.ConsumeIntegral<int64_t>(), presentFence, previousFence,
+ mFdp.ConsumeIntegral<uint32_t>(), frameStats,
+ mFdp.ConsumeIntegral<uint32_t>());
+ stats.push_back(controlStats);
+}
+
+void BufferQueueFuzzer::invokeQuery(sp<BufferQueueProducer> producer) {
+ int32_t value;
+ producer->query(mFdp.ConsumeIntegral<int32_t>(), &value);
+}
+
+void BufferQueueFuzzer::invokeQuery(sp<V1_0::utils::H2BGraphicBufferProducer> producer) {
+ int32_t value;
+ producer->query(mFdp.ConsumeIntegral<int32_t>(), &value);
+}
+
+void BufferQueueFuzzer::invokeQuery(sp<V2_0::utils::H2BGraphicBufferProducer> producer) {
+ int32_t value;
+ producer->query(mFdp.ConsumeIntegral<int32_t>(), &value);
+}
+
+void BufferQueueFuzzer::invokeBufferQueueProducer() {
+ sp<BufferQueueCore> core(new BufferQueueCore());
+ sp<BufferQueueProducer> producer(new BufferQueueProducer(core));
+ const sp<android::IProducerListener> listener;
+ android::IGraphicBufferProducer::QueueBufferOutput output;
+ uint32_t api = mFdp.ConsumeIntegral<uint32_t>();
+ producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output);
+
+ sp<GraphicBuffer> buffer;
+ int32_t slot = mFdp.ConsumeIntegral<int32_t>();
+ uint32_t maxBuffers = mFdp.ConsumeIntegral<uint32_t>();
+ 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<int32_t>();
+ uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+ uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+ uint64_t usage = mFdp.ConsumeIntegral<uint64_t>();
+ uint64_t outBufferAge;
+ FrameEventHistoryDelta outTimestamps;
+ sp<android::Fence> 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<uint32_t>());
+ producer->setDequeueTimeout(mFdp.ConsumeIntegral<uint32_t>());
+ producer->disconnect(api);
+}
+
+void BufferQueueFuzzer::invokeAcquireBuffer(sp<BufferQueueConsumer> consumer) {
+ BufferItem item;
+ consumer->acquireBuffer(&item, mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint64_t>());
+}
+
+void BufferQueueFuzzer::invokeOccupancyTracker(sp<BufferQueueConsumer> consumer) {
+ String8 outResult;
+ String8 prefix((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str());
+ consumer->dumpState(prefix, &outResult);
+
+ std::vector<OccupancyTracker::Segment> outHistory;
+ consumer->getOccupancyHistory(mFdp.ConsumeBool() /*forceFlush*/, &outHistory);
+}
+
+void BufferQueueFuzzer::invokeBufferQueueConsumer() {
+ sp<BufferQueueCore> core(new BufferQueueCore());
+ sp<BufferQueueConsumer> consumer(new BufferQueueConsumer(core));
+ sp<android::IConsumerListener> listener;
+ consumer->consumerConnect(listener, mFdp.ConsumeBool() /*controlledByApp*/);
+ invokeAcquireBuffer(consumer);
+
+ int32_t slot = mFdp.ConsumeIntegral<int32_t>();
+ sp<GraphicBuffer> buffer =
+ new GraphicBuffer(mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint64_t>());
+ consumer->attachBuffer(&slot, buffer);
+ consumer->detachBuffer(slot);
+
+ consumer->setDefaultBufferSize(mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>());
+ consumer->setMaxBufferCount(mFdp.ConsumeIntegral<int32_t>());
+ consumer->setMaxAcquiredBufferCount(mFdp.ConsumeIntegral<int32_t>());
+
+ String8 name((mFdp.ConsumeRandomLengthString(kMaxBytes)).c_str());
+ consumer->setConsumerName(name);
+ consumer->setDefaultBufferFormat(mFdp.ConsumeIntegral<int32_t>());
+ android_dataspace dataspace =
+ static_cast<android_dataspace>(mFdp.PickValueInArray(kDataspaces));
+ consumer->setDefaultBufferDataSpace(dataspace);
+
+ consumer->setTransformHint(mFdp.ConsumeIntegral<uint32_t>());
+ consumer->setConsumerUsageBits(mFdp.ConsumeIntegral<uint64_t>());
+ consumer->setConsumerIsProtected(mFdp.ConsumeBool() /*isProtected*/);
+ invokeOccupancyTracker(consumer);
+
+ sp<Fence> releaseFence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
+ consumer->releaseBuffer(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<uint64_t>(),
+ 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<V1_0::utils::H2BGraphicBufferProducer> producer(
+ new V1_0::utils::H2BGraphicBufferProducer(new FakeGraphicBufferProducerV1()));
+ const sp<android::IProducerListener> listener;
+ android::IGraphicBufferProducer::QueueBufferOutput output;
+ uint32_t api = mFdp.ConsumeIntegral<uint32_t>();
+ producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output);
+
+ sp<GraphicBuffer> buffer;
+ int32_t slot = mFdp.ConsumeIntegral<int32_t>();
+ producer->requestBuffer(slot, &buffer);
+ producer->setMaxDequeuedBufferCount(mFdp.ConsumeIntegral<int32_t>());
+ 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<int32_t>();
+ uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+ uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+ uint64_t usage = mFdp.ConsumeIntegral<uint64_t>();
+ uint64_t outBufferAge;
+ FrameEventHistoryDelta outTimestamps;
+ sp<android::Fence> 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<uint32_t>());
+ producer->setDequeueTimeout(mFdp.ConsumeIntegral<uint32_t>());
+ producer->disconnect(api);
+}
+
+void BufferQueueFuzzer::invokeH2BGraphicBufferV2() {
+ sp<V2_0::utils::H2BGraphicBufferProducer> producer(
+ new V2_0::utils::H2BGraphicBufferProducer(new FakeGraphicBufferProducerV2()));
+ const sp<android::IProducerListener> listener;
+ android::IGraphicBufferProducer::QueueBufferOutput output;
+ uint32_t api = mFdp.ConsumeIntegral<uint32_t>();
+ producer->connect(listener, api, mFdp.ConsumeBool() /*producerControlledByApp*/, &output);
+
+ sp<GraphicBuffer> buffer;
+ int32_t slot = mFdp.ConsumeIntegral<int32_t>();
+ producer->requestBuffer(slot, &buffer);
+ producer->setMaxDequeuedBufferCount(mFdp.ConsumeIntegral<uint32_t>());
+ 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<int32_t>();
+ uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+ uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+ uint64_t usage = mFdp.ConsumeIntegral<uint64_t>();
+ uint64_t outBufferAge;
+ FrameEventHistoryDelta outTimestamps;
+ sp<android::Fence> 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<uint32_t>());
+ producer->setDequeueTimeout(mFdp.ConsumeIntegral<uint32_t>());
+ 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 0000000000..24a046d3a9
--- /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 <gui/BufferQueueConsumer.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
+#include <gui/GLConsumer.h>
+#include <libgui_fuzzer_utils.h>
+
+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<BufferQueueCore> core(new BufferQueueCore());
+ sp<IGraphicBufferConsumer> consumer(new BufferQueueConsumer(core));
+
+ uint64_t maxBuffers = mFdp.ConsumeIntegralInRange<uint64_t>(kMinBuffer, kMaxBuffer);
+ sp<CpuConsumer> cpu(
+ new CpuConsumer(consumer, maxBuffers, mFdp.ConsumeBool() /*controlledByApp*/));
+ CpuConsumer::LockedBuffer lockBuffer;
+ cpu->lockNextBuffer(&lockBuffer);
+ cpu->unlockBuffer(lockBuffer);
+ cpu->abandon();
+
+ uint32_t tex = mFdp.ConsumeIntegral<uint32_t>();
+ sp<GLConsumer> glComsumer(new GLConsumer(consumer, tex, GLConsumer::TEXTURE_EXTERNAL,
+ mFdp.ConsumeBool() /*useFenceSync*/,
+ mFdp.ConsumeBool() /*isControlledByApp*/));
+ sp<Fence> releaseFence = new Fence(memfd_create("rfd", MFD_ALLOW_SEALING));
+ glComsumer->setReleaseFence(releaseFence);
+ glComsumer->updateTexImage();
+ glComsumer->releaseTexImage();
+
+ sp<GraphicBuffer> buffer =
+ new GraphicBuffer(mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint64_t>());
+ float mtx[16];
+ glComsumer->getTransformMatrix(mtx);
+ glComsumer->computeTransformMatrix(mtx, buffer, getRect(&mFdp),
+ mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeBool() /*filtering*/);
+ glComsumer->scaleDownCrop(getRect(&mFdp), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>());
+
+ glComsumer->setDefaultBufferSize(mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>());
+ glComsumer->setFilteringEnabled(mFdp.ConsumeBool() /*enabled*/);
+
+ glComsumer->setConsumerUsageBits(mFdp.ConsumeIntegral<uint64_t>());
+ 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 0000000000..0d2a52b576
--- /dev/null
+++ b/libs/gui/fuzzer/libgui_displayEvent_fuzzer.cpp
@@ -0,0 +1,107 @@
+/*
+ * 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 <android/gui/ISurfaceComposer.h>
+
+#include <libgui_fuzzer_utils.h>
+
+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<uint32_t>();
+ event.vsync.vsyncData.frameInterval = fdp->ConsumeIntegral<uint64_t>();
+ event.vsync.vsyncData.preferredFrameTimelineIndex = fdp->ConsumeIntegral<uint32_t>();
+ for (size_t idx = 0; idx < gui::VsyncEventData::kFrameTimelinesCapacity; ++idx) {
+ event.vsync.vsyncData.frameTimelines[idx].vsyncId = fdp->ConsumeIntegral<int64_t>();
+ event.vsync.vsyncData.frameTimelines[idx].deadlineTimestamp =
+ fdp->ConsumeIntegral<uint64_t>();
+ event.vsync.vsyncData.frameTimelines[idx].expectedPresentationTime =
+ fdp->ConsumeIntegral<uint64_t>();
+ }
+ break;
+
+ }
+ case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG: {
+ event.hotplug =
+ DisplayEventReceiver::Event::Hotplug{fdp->ConsumeBool() /*connected*/,
+ fdp->ConsumeIntegral<
+ int32_t>() /*connectionError*/};
+ break;
+ }
+ case DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE: {
+ event.modeChange =
+ DisplayEventReceiver::Event::ModeChange{fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int64_t>()};
+ break;
+ }
+ case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE:
+ case DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH: {
+ event.frameRateOverride =
+ DisplayEventReceiver::Event::FrameRateOverride{fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeFloatingPoint<
+ float>()};
+ break;
+ }
+ }
+ return event;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider fdp(data, size);
+ sp<Looper> looper;
+ sp<FakeDisplayEventDispatcher> 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<int64_t>()};
+ 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 0000000000..9933680c4b
--- /dev/null
+++ b/libs/gui/fuzzer/libgui_fuzzer_utils.h
@@ -0,0 +1,329 @@
+/*
+ * 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 <android/gui/BnRegionSamplingListener.h>
+#include <android/gui/BnSurfaceComposer.h>
+#include <android/gui/BnSurfaceComposerClient.h>
+#include <android/gui/IDisplayEventConnection.h>
+#include <android/gui/ISurfaceComposerClient.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <gmock/gmock.h>
+#include <gui/BLASTBufferQueue.h>
+#include <gui/DisplayEventDispatcher.h>
+#include <gui/IGraphicBufferProducer.h>
+#include <gui/LayerDebugInfo.h>
+#include <gui/LayerState.h>
+#include <gui/bufferqueue/1.0/H2BGraphicBufferProducer.h>
+#include <gui/bufferqueue/2.0/H2BGraphicBufferProducer.h>
+#include <ui/fuzzer/FuzzableDataspaces.h>
+
+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<int32_t>();
+ const int32_t top = fdp->ConsumeIntegral<int32_t>();
+ const int32_t right = fdp->ConsumeIntegral<int32_t>();
+ const int32_t bottom = fdp->ConsumeIntegral<int32_t>();
+ 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<float>(kMinBrightness, kMaxBrightness);
+ brightness.sdrWhitePointNits =
+ fdp->ConsumeFloatingPointInRange<float>(kMinBrightness, kMaxBrightness);
+ brightness.displayBrightness =
+ fdp->ConsumeFloatingPointInRange<float>(kMinBrightness, kMaxBrightness);
+ brightness.displayBrightnessNits =
+ fdp->ConsumeFloatingPointInRange<float>(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<IBinder>& /*layerHandle*/, sp<gui::IDisplayEventConnection>*),
+ (override));
+ MOCK_METHOD(binder::Status, createConnection, (sp<gui::ISurfaceComposerClient>*), (override));
+ MOCK_METHOD(binder::Status, createDisplay, (const std::string&, bool, float, sp<IBinder>*),
+ (override));
+ MOCK_METHOD(binder::Status, destroyDisplay, (const sp<IBinder>&), (override));
+ MOCK_METHOD(binder::Status, getPhysicalDisplayIds, (std::vector<int64_t>*), (override));
+ MOCK_METHOD(binder::Status, getPhysicalDisplayToken, (int64_t, sp<IBinder>*), (override));
+ MOCK_METHOD(binder::Status, setPowerMode, (const sp<IBinder>&, int), (override));
+ MOCK_METHOD(binder::Status, getSupportedFrameTimestamps, (std::vector<FrameEvent>*),
+ (override));
+ MOCK_METHOD(binder::Status, getDisplayStats, (const sp<IBinder>&, gui::DisplayStatInfo*),
+ (override));
+ MOCK_METHOD(binder::Status, getDisplayState, (const sp<IBinder>&, 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<IBinder>&, gui::DynamicDisplayInfo*), (override));
+ MOCK_METHOD(binder::Status, getDisplayNativePrimaries,
+ (const sp<IBinder>&, gui::DisplayPrimaries*), (override));
+ MOCK_METHOD(binder::Status, setActiveColorMode, (const sp<IBinder>&, int), (override));
+ MOCK_METHOD(binder::Status, setBootDisplayMode, (const sp<IBinder>&, int), (override));
+ MOCK_METHOD(binder::Status, clearBootDisplayMode, (const sp<IBinder>&), (override));
+ MOCK_METHOD(binder::Status, getBootDisplayModeSupport, (bool*), (override));
+ MOCK_METHOD(binder::Status, getHdrConversionCapabilities,
+ (std::vector<gui::HdrConversionCapability>*), (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<IBinder>&, bool), (override));
+ MOCK_METHOD(binder::Status, setGameContentType, (const sp<IBinder>&, bool), (override));
+ MOCK_METHOD(binder::Status, captureDisplay,
+ (const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
+ MOCK_METHOD(binder::Status, captureDisplayById,
+ (int64_t, const gui::CaptureArgs&, const sp<IScreenCaptureListener>&), (override));
+ MOCK_METHOD(binder::Status, captureLayers,
+ (const LayerCaptureArgs&, const sp<IScreenCaptureListener>&), (override));
+ MOCK_METHOD(binder::Status, clearAnimationFrameStats, (), (override));
+ MOCK_METHOD(binder::Status, getAnimationFrameStats, (gui::FrameStats*), (override));
+ MOCK_METHOD(binder::Status, overrideHdrTypes, (const sp<IBinder>&, const std::vector<int32_t>&),
+ (override));
+ MOCK_METHOD(binder::Status, onPullAtom, (int32_t, gui::PullAtomData*), (override));
+ MOCK_METHOD(binder::Status, getLayerDebugInfo, (std::vector<gui::LayerDebugInfo>*), (override));
+ MOCK_METHOD(binder::Status, getCompositionPreference, (gui::CompositionPreference*),
+ (override));
+ MOCK_METHOD(binder::Status, getDisplayedContentSamplingAttributes,
+ (const sp<IBinder>&, gui::ContentSamplingAttributes*), (override));
+ MOCK_METHOD(binder::Status, setDisplayContentSamplingEnabled,
+ (const sp<IBinder>&, bool, int8_t, int64_t), (override));
+ MOCK_METHOD(binder::Status, getDisplayedContentSample,
+ (const sp<IBinder>&, int64_t, int64_t, gui::DisplayedFrameStats*), (override));
+ MOCK_METHOD(binder::Status, getProtectedContentSupport, (bool*), (override));
+ MOCK_METHOD(binder::Status, isWideColorDisplay, (const sp<IBinder>&, bool*), (override));
+ MOCK_METHOD(binder::Status, addRegionSamplingListener,
+ (const gui::ARect&, const sp<IBinder>&, const sp<gui::IRegionSamplingListener>&),
+ (override));
+ MOCK_METHOD(binder::Status, removeRegionSamplingListener,
+ (const sp<gui::IRegionSamplingListener>&), (override));
+ MOCK_METHOD(binder::Status, addFpsListener, (int32_t, const sp<gui::IFpsListener>&),
+ (override));
+ MOCK_METHOD(binder::Status, removeFpsListener, (const sp<gui::IFpsListener>&), (override));
+ MOCK_METHOD(binder::Status, addTunnelModeEnabledListener,
+ (const sp<gui::ITunnelModeEnabledListener>&), (override));
+ MOCK_METHOD(binder::Status, removeTunnelModeEnabledListener,
+ (const sp<gui::ITunnelModeEnabledListener>&), (override));
+ MOCK_METHOD(binder::Status, setDesiredDisplayModeSpecs,
+ (const sp<IBinder>&, const gui::DisplayModeSpecs&), (override));
+ MOCK_METHOD(binder::Status, getDesiredDisplayModeSpecs,
+ (const sp<IBinder>&, gui::DisplayModeSpecs*), (override));
+ MOCK_METHOD(binder::Status, getDisplayBrightnessSupport, (const sp<IBinder>&, bool*),
+ (override));
+ MOCK_METHOD(binder::Status, setDisplayBrightness,
+ (const sp<IBinder>&, const gui::DisplayBrightness&), (override));
+ MOCK_METHOD(binder::Status, addHdrLayerInfoListener,
+ (const sp<IBinder>&, const sp<gui::IHdrLayerInfoListener>&), (override));
+ MOCK_METHOD(binder::Status, removeHdrLayerInfoListener,
+ (const sp<IBinder>&, const sp<gui::IHdrLayerInfoListener>&), (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<IBinder>&, std::optional<gui::DisplayDecorationSupport>*), (override));
+ MOCK_METHOD(binder::Status, setGameModeFrameRateOverride, (int32_t, float), (override));
+ MOCK_METHOD(binder::Status, setGameDefaultFrameRateOverride, (int32_t, float), (override));
+ MOCK_METHOD(binder::Status, enableRefreshRateOverlay, (bool), (override));
+ MOCK_METHOD(binder::Status, setDebugFlash, (int), (override));
+ MOCK_METHOD(binder::Status, scheduleComposite, (), (override));
+ MOCK_METHOD(binder::Status, scheduleCommit, (), (override));
+ MOCK_METHOD(binder::Status, forceClientComposition, (bool), (override));
+ MOCK_METHOD(binder::Status, updateSmallAreaDetection,
+ (const std::vector<int32_t>&, const std::vector<float>&), (override));
+ MOCK_METHOD(binder::Status, setSmallAreaDetectionThreshold, (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::IWindowInfosListener>&, gui::WindowInfosListenerInfo*), (override));
+ MOCK_METHOD(binder::Status, removeWindowInfosListener, (const sp<gui::IWindowInfosListener>&),
+ (override));
+ MOCK_METHOD(binder::Status, getOverlaySupport, (gui::OverlayProperties*), (override));
+ MOCK_METHOD(binder::Status, getStalledTransactionInfo,
+ (int32_t, std::optional<gui::StalledTransactionInfo>*), (override));
+ MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
+};
+
+class FakeBnSurfaceComposerClient : public gui::BnSurfaceComposerClient {
+public:
+ MOCK_METHOD(binder::Status, createSurface,
+ (const std::string& name, int32_t flags, const sp<IBinder>& parent,
+ const gui::LayerMetadata& metadata, gui::CreateSurfaceResult* outResult),
+ (override));
+
+ MOCK_METHOD(binder::Status, clearLayerFrameStats, (const sp<IBinder>& handle), (override));
+
+ MOCK_METHOD(binder::Status, getLayerFrameStats,
+ (const sp<IBinder>& handle, gui::FrameStats* outStats), (override));
+
+ MOCK_METHOD(binder::Status, mirrorSurface,
+ (const sp<IBinder>& mirrorFromHandle, gui::CreateSurfaceResult* outResult),
+ (override));
+
+ MOCK_METHOD(binder::Status, mirrorDisplay,
+ (int64_t displayId, gui::CreateSurfaceResult* outResult), (override));
+
+ MOCK_METHOD(binder::Status, getSchedulingPolicy, (gui::SchedulingPolicy*), (override));
+};
+
+class FakeDisplayEventDispatcher : public DisplayEventDispatcher {
+public:
+ FakeDisplayEventDispatcher(const sp<Looper>& 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_METHOD2(dispatchHotplugConnectionError, void(nsecs_t, int32_t));
+ 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<FrameRateOverride>));
+};
+
+} // 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<void>(int, requestBuffer_cb));
+ MOCK_METHOD1(setMaxDequeuedBufferCount, Return<int32_t>(int32_t));
+ MOCK_METHOD1(setAsyncMode, Return<int32_t>(bool));
+ MOCK_METHOD6(dequeueBuffer,
+ Return<void>(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t,
+ bool, dequeueBuffer_cb));
+ MOCK_METHOD1(detachBuffer, Return<int32_t>(int));
+ MOCK_METHOD1(detachNextBuffer, Return<void>(detachNextBuffer_cb));
+ MOCK_METHOD2(attachBuffer, Return<void>(const media::V1_0::AnwBuffer&, attachBuffer_cb));
+ MOCK_METHOD3(
+ queueBuffer,
+ Return<void>(
+ int,
+ const graphics::bufferqueue::V1_0::IGraphicBufferProducer::QueueBufferInput&,
+ queueBuffer_cb));
+ MOCK_METHOD2(cancelBuffer, Return<int32_t>(int, const hidl_handle&));
+ MOCK_METHOD2(query, Return<void>(int32_t, query_cb));
+ MOCK_METHOD4(connect,
+ Return<void>(const sp<graphics::bufferqueue::V1_0::IProducerListener>&, int32_t,
+ bool, connect_cb));
+ MOCK_METHOD2(disconnect,
+ Return<int32_t>(
+ int, graphics::bufferqueue::V1_0::IGraphicBufferProducer::DisconnectMode));
+ MOCK_METHOD1(setSidebandStream, Return<int32_t>(const hidl_handle&));
+ MOCK_METHOD4(allocateBuffers,
+ Return<void>(uint32_t, uint32_t, graphics::common::V1_0::PixelFormat, uint32_t));
+ MOCK_METHOD1(allowAllocation, Return<int32_t>(bool));
+ MOCK_METHOD1(setGenerationNumber, Return<int32_t>(uint32_t));
+ MOCK_METHOD1(getConsumerName, Return<void>(getConsumerName_cb));
+ MOCK_METHOD1(setSharedBufferMode, Return<int32_t>(bool));
+ MOCK_METHOD1(setAutoRefresh, Return<int32_t>(bool));
+ MOCK_METHOD1(setDequeueTimeout, Return<int32_t>(nsecs_t));
+ MOCK_METHOD1(setLegacyBufferDrop, Return<int32_t>(bool));
+ MOCK_METHOD1(getLastQueuedBuffer, Return<void>(getLastQueuedBuffer_cb));
+ MOCK_METHOD1(getFrameTimestamps, Return<void>(getFrameTimestamps_cb));
+ MOCK_METHOD1(getUniqueId, Return<void>(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<void>(int, requestBuffer_cb));
+ MOCK_METHOD1(setMaxDequeuedBufferCount, Return<graphics::bufferqueue::V2_0::Status>(int));
+ MOCK_METHOD1(setAsyncMode, Return<graphics::bufferqueue::V2_0::Status>(bool));
+ MOCK_METHOD2(
+ dequeueBuffer,
+ Return<void>(
+ const graphics::bufferqueue::V2_0::IGraphicBufferProducer::DequeueBufferInput&,
+ dequeueBuffer_cb));
+ MOCK_METHOD1(detachBuffer, Return<graphics::bufferqueue::V2_0::Status>(int));
+ MOCK_METHOD1(detachNextBuffer, Return<void>(detachNextBuffer_cb));
+ MOCK_METHOD3(attachBuffer,
+ Return<void>(const graphics::common::V1_2::HardwareBuffer&, uint32_t,
+ attachBuffer_cb));
+ MOCK_METHOD3(
+ queueBuffer,
+ Return<void>(
+ int,
+ const graphics::bufferqueue::V2_0::IGraphicBufferProducer::QueueBufferInput&,
+ queueBuffer_cb));
+ MOCK_METHOD2(cancelBuffer,
+ Return<graphics::bufferqueue::V2_0::Status>(int, const hidl_handle&));
+ MOCK_METHOD2(query, Return<void>(int32_t, query_cb));
+ MOCK_METHOD4(connect,
+ Return<void>(const sp<graphics::bufferqueue::V2_0::IProducerListener>&,
+ graphics::bufferqueue::V2_0::ConnectionType, bool, connect_cb));
+ MOCK_METHOD1(disconnect,
+ Return<graphics::bufferqueue::V2_0::Status>(
+ graphics::bufferqueue::V2_0::ConnectionType));
+ MOCK_METHOD4(allocateBuffers,
+ Return<graphics::bufferqueue::V2_0::Status>(uint32_t, uint32_t, uint32_t,
+ uint64_t));
+ MOCK_METHOD1(allowAllocation, Return<graphics::bufferqueue::V2_0::Status>(bool));
+ MOCK_METHOD1(setGenerationNumber, Return<graphics::bufferqueue::V2_0::Status>(uint32_t));
+ MOCK_METHOD1(getConsumerName, Return<void>(getConsumerName_cb));
+ MOCK_METHOD1(setDequeueTimeout, Return<graphics::bufferqueue::V2_0::Status>(int64_t));
+ MOCK_METHOD0(getUniqueId, Return<uint64_t>());
+};
+
+}; // 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 0000000000..9f0f6cac19
--- /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 <gui/BufferQueueConsumer.h>
+#include <gui/BufferQueueCore.h>
+#include <gui/BufferQueueProducer.h>
+#include <gui/LayerMetadata.h>
+#include <gui/OccupancyTracker.h>
+#include <gui/StreamSplitter.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceControl.h>
+#include <gui/view/Surface.h>
+#include <libgui_fuzzer_utils.h>
+#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<uint32_t, std::vector<uint8_t>> map;
+ for (size_t idx = 0; idx < kLayerMetadataKeyCount; ++idx) {
+ std::vector<uint8_t> data;
+ for (size_t idx1 = 0; idx1 < mFdp.ConsumeIntegral<uint32_t>(); ++idx1) {
+ data.push_back(mFdp.ConsumeIntegral<uint8_t>());
+ }
+ map[kMetadataKey[idx]] = data;
+ }
+ LayerMetadata metadata(map);
+ uint32_t key = mFdp.PickValueInArray(kMetadataKey);
+ metadata.setInt32(key, mFdp.ConsumeIntegral<int32_t>());
+ 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<uint32_t>();
+ info.mX = mFdp.ConsumeFloatingPoint<float>();
+ info.mY = mFdp.ConsumeFloatingPoint<float>();
+ info.mZ = mFdp.ConsumeIntegral<uint32_t>();
+ info.mWidth = mFdp.ConsumeIntegral<int32_t>();
+ info.mHeight = mFdp.ConsumeIntegral<int32_t>();
+ info.mActiveBufferWidth = mFdp.ConsumeIntegral<int32_t>();
+ info.mActiveBufferHeight = mFdp.ConsumeIntegral<int32_t>();
+ info.mActiveBufferStride = mFdp.ConsumeIntegral<int32_t>();
+ info.mActiveBufferFormat = mFdp.ConsumeIntegral<int32_t>();
+ info.mNumQueuedFrames = mFdp.ConsumeIntegral<int32_t>();
+
+ info.mFlags = mFdp.ConsumeIntegral<uint32_t>();
+ info.mPixelFormat = mFdp.ConsumeIntegral<int32_t>();
+ info.mTransparentRegion = Region(getRect(&mFdp));
+ info.mVisibleRegion = Region(getRect(&mFdp));
+ info.mSurfaceDamageRegion = Region(getRect(&mFdp));
+ info.mCrop = getRect(&mFdp);
+ info.mDataSpace = static_cast<android_dataspace>(mFdp.PickValueInArray(kDataspaces));
+ info.mColor = half4(mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+ mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>());
+ for (size_t idx = 0; idx < kMatrixSize; ++idx) {
+ info.mMatrix[idx / 2][idx % 2] = mFdp.ConsumeFloatingPoint<float>();
+ }
+ info.mIsOpaque = mFdp.ConsumeBool();
+ info.mContentDirty = mFdp.ConsumeBool();
+ info.mStretchEffect.width = mFdp.ConsumeFloatingPoint<float>();
+ info.mStretchEffect.height = mFdp.ConsumeFloatingPoint<float>();
+ info.mStretchEffect.vectorX = mFdp.ConsumeFloatingPoint<float>();
+ info.mStretchEffect.vectorY = mFdp.ConsumeFloatingPoint<float>();
+ info.mStretchEffect.maxAmountX = mFdp.ConsumeFloatingPoint<float>();
+ info.mStretchEffect.maxAmountY = mFdp.ConsumeFloatingPoint<float>();
+ info.mStretchEffect.mappedChildBounds =
+ FloatRect(mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>(),
+ mFdp.ConsumeFloatingPoint<float>(), mFdp.ConsumeFloatingPoint<float>());
+
+ Parcel parcel;
+ info.writeToParcel(&parcel);
+ parcel.setDataPosition(0);
+ info.readFromParcel(&parcel);
+}
+
+void ParcelableFuzzer::invokeOccupancyTracker() {
+ nsecs_t totalTime = mFdp.ConsumeIntegral<uint32_t>();
+ size_t numFrames = mFdp.ConsumeIntegral<size_t>();
+ float occupancyAverage = mFdp.ConsumeFloatingPoint<float>();
+ OccupancyTracker::Segment segment(totalTime, numFrames, occupancyAverage,
+ mFdp.ConsumeBool() /*usedThirdBuffer*/);
+ Parcel parcel;
+ segment.writeToParcel(&parcel);
+ parcel.setDataPosition(0);
+ segment.readFromParcel(&parcel);
+}
+
+void ParcelableFuzzer::invokeStreamSplitter() {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+ sp<StreamSplitter> 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 0000000000..4daa3be36f
--- /dev/null
+++ b/libs/gui/fuzzer/libgui_surfaceComposerClient_fuzzer.cpp
@@ -0,0 +1,328 @@
+/*
+ * 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 <aidl/android/hardware/power/Boost.h>
+#include <fuzzbinder/libbinder_driver.h>
+#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
+#include <libgui_fuzzer_utils.h>
+#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 aidl::android::hardware::power::Boost kBoost[] = {
+ aidl::android::hardware::power::Boost::INTERACTION,
+ aidl::android::hardware::power::Boost::DISPLAY_UPDATE_IMMINENT,
+ aidl::android::hardware::power::Boost::ML_ACC,
+ aidl::android::hardware::power::Boost::AUDIO_LAUNCH,
+ aidl::android::hardware::power::Boost::CAMERA_LAUNCH,
+ aidl::android::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<SurfaceControl> 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<float>();
+ range.max = mFdp.ConsumeFloatingPoint<float>();
+ 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<IBinder> displayToken =
+ SurfaceComposerClient::createDisplay(displayName, mFdp.ConsumeBool() /*secure*/);
+ gui::DisplayModeSpecs specs;
+ specs.defaultMode = mFdp.ConsumeIntegral<int32_t>();
+ specs.allowGroupSwitching = mFdp.ConsumeBool();
+ specs.primaryRanges = getRefreshRateRanges();
+ specs.appRequestRanges = getRefreshRateRanges();
+ return specs;
+}
+
+BlurRegion SurfaceComposerClientFuzzer::getBlurRegion() {
+ int32_t left = mFdp.ConsumeIntegral<int32_t>();
+ int32_t right = mFdp.ConsumeIntegral<int32_t>();
+ int32_t top = mFdp.ConsumeIntegral<int32_t>();
+ int32_t bottom = mFdp.ConsumeIntegral<int32_t>();
+ uint32_t blurRadius = mFdp.ConsumeIntegral<uint32_t>();
+ float alpha = mFdp.ConsumeFloatingPoint<float>();
+ float cornerRadiusTL = mFdp.ConsumeFloatingPoint<float>();
+ float cornerRadiusTR = mFdp.ConsumeFloatingPoint<float>();
+ float cornerRadiusBL = mFdp.ConsumeFloatingPoint<float>();
+ float cornerRadiusBR = mFdp.ConsumeFloatingPoint<float>();
+ return BlurRegion{blurRadius, cornerRadiusTL, cornerRadiusTR, cornerRadiusBL,
+ cornerRadiusBR, alpha, left, top,
+ right, bottom};
+}
+
+void SurfaceComposerClientFuzzer::getWindowInfo(gui::WindowInfo* windowInfo) {
+ windowInfo->id = mFdp.ConsumeIntegral<int32_t>();
+ windowInfo->name = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
+ windowInfo->layoutParamsFlags = mFdp.PickValueInArray(kFlags);
+ windowInfo->layoutParamsType = mFdp.PickValueInArray(kType);
+ windowInfo->frame = Rect(mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>(),
+ mFdp.ConsumeIntegral<int32_t>(), mFdp.ConsumeIntegral<int32_t>());
+ windowInfo->surfaceInset = mFdp.ConsumeIntegral<int32_t>();
+ windowInfo->alpha = mFdp.ConsumeFloatingPointInRange<float>(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 = gui::Pid{mFdp.ConsumeIntegral<pid_t>()};
+ windowInfo->ownerUid = gui::Uid{mFdp.ConsumeIntegral<uid_t>()};
+ windowInfo->packageName = mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes);
+ windowInfo->inputConfig = mFdp.PickValueInArray(kFeatures);
+}
+
+sp<SurfaceControl> SurfaceComposerClientFuzzer::makeSurfaceControl() {
+ sp<IBinder> handle;
+ const sp<FakeBnSurfaceComposerClient> testClient(new FakeBnSurfaceComposerClient());
+ sp<SurfaceComposerClient> client = new SurfaceComposerClient(testClient);
+ sp<BnGraphicBufferProducer> producer;
+ uint32_t width = mFdp.ConsumeIntegral<uint32_t>();
+ uint32_t height = mFdp.ConsumeIntegral<uint32_t>();
+ uint32_t transformHint = mFdp.ConsumeIntegral<uint32_t>();
+ uint32_t flags = mFdp.ConsumeIntegral<uint32_t>();
+ int32_t format = mFdp.ConsumeIntegral<int32_t>();
+ int32_t layerId = mFdp.ConsumeIntegral<int32_t>();
+ std::string layerName = base::StringPrintf("#%d", layerId);
+ return new SurfaceControl(client, handle, layerId, layerName, width, height, format,
+ transformHint, flags);
+}
+
+void SurfaceComposerClientFuzzer::invokeSurfaceComposerTransaction() {
+ sp<SurfaceControl> surface = makeSurfaceControl();
+
+ SurfaceComposerClient::Transaction transaction;
+ int32_t layer = mFdp.ConsumeIntegral<int32_t>();
+ transaction.setLayer(surface, layer);
+
+ sp<SurfaceControl> relativeSurface = makeSurfaceControl();
+ transaction.setRelativeLayer(surface, relativeSurface, layer);
+
+ Region transparentRegion(getRect(&mFdp));
+ transaction.setTransparentRegionHint(surface, transparentRegion);
+ transaction.setAlpha(surface, mFdp.ConsumeFloatingPoint<float>());
+
+ transaction.setCornerRadius(surface, mFdp.ConsumeFloatingPoint<float>());
+ transaction.setBackgroundBlurRadius(surface, mFdp.ConsumeFloatingPoint<float>());
+ std::vector<BlurRegion> regions;
+ uint32_t vectorSize = mFdp.ConsumeIntegralInRange<uint32_t>(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<uint32_t>()});
+ half3 color = {mFdp.ConsumeIntegral<uint32_t>(), mFdp.ConsumeIntegral<uint32_t>(),
+ mFdp.ConsumeIntegral<uint32_t>()};
+ transaction.setColor(surface, color);
+ transaction.setBackgroundColor(surface, color, mFdp.ConsumeFloatingPoint<float>(),
+ mFdp.PickValueInArray(kDataspaces));
+
+ transaction.setApi(surface, mFdp.ConsumeIntegral<int32_t>());
+ transaction.setFrameRateSelectionPriority(surface, mFdp.ConsumeIntegral<int32_t>());
+ 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>();
+ int32_t pointY = mFdp.ConsumeIntegral<int32_t>();
+ 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<int32_t>(), &outData, &success);
+}
+
+void SurfaceComposerClientFuzzer::invokeSurfaceComposerClient() {
+ String8 displayName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
+ sp<IBinder> 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<int32_t>());
+ SurfaceComposerClient::doUncacheBufferTransaction(mFdp.ConsumeIntegral<uint64_t>());
+
+ SurfaceComposerClient::setDisplayBrightness(displayToken, getBrightness(&mFdp));
+ aidl::android::hardware::power::Boost boostId = mFdp.PickValueInArray(kBoost);
+ SurfaceComposerClient::notifyPowerBoost((int32_t)boostId);
+
+ String8 surfaceName((mFdp.ConsumeRandomLengthString(kRandomStringMaxBytes)).c_str());
+ sp<BBinder> handle(new BBinder());
+ sp<BnGraphicBufferProducer> producer;
+ sp<Surface> surfaceParent(
+ new Surface(producer, mFdp.ConsumeBool() /*controlledByApp*/, handle));
+
+ fuzzOnPullAtom();
+ SurfaceComposerClient::setDisplayContentSamplingEnabled(displayToken,
+ mFdp.ConsumeBool() /*enable*/,
+ mFdp.ConsumeIntegral<uint8_t>(),
+ mFdp.ConsumeIntegral<uint64_t>());
+
+ sp<IBinder> stopLayerHandle;
+ sp<gui::IRegionSamplingListener> listener = sp<gui::IRegionSamplingListenerDefault>::make();
+ sp<gui::IRegionSamplingListenerDelegator> sampleListener =
+ new gui::IRegionSamplingListenerDelegator(listener);
+ SurfaceComposerClient::addRegionSamplingListener(getRect(&mFdp), stopLayerHandle,
+ sampleListener);
+ sp<gui::IFpsListenerDefault> fpsListener;
+ SurfaceComposerClient::addFpsListener(mFdp.ConsumeIntegral<int32_t>(), fpsListener);
+}
+
+void SurfaceComposerClientFuzzer::invokeSurfaceComposerClientBinder() {
+ sp<FakeBnSurfaceComposerClient> 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 0000000000..6d5427bc9e
--- /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 <fuzzbinder/libbinder_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <libgui_fuzzer_utils.h>
+
+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<FakeBnSurfaceComposer> 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 0000000000..55be27bf35
--- /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 <binder/Status.h>
+
+// 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 f5898d20f1..0e1a505c69 100644
--- a/libs/gui/include/gui/BLASTBufferQueue.h
+++ b/libs/gui/include/gui/BLASTBufferQueue.h
@@ -17,9 +17,10 @@
#ifndef ANDROID_GUI_BLAST_BUFFER_QUEUE_H
#define ANDROID_GUI_BLAST_BUFFER_QUEUE_H
-#include <gui/IGraphicBufferProducer.h>
-#include <gui/BufferItemConsumer.h>
#include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
+
+#include <gui/IGraphicBufferProducer.h>
#include <gui/SurfaceComposerClient.h>
#include <utils/Condition.h>
@@ -30,6 +31,8 @@
#include <thread>
#include <queue>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
class BLASTBufferQueue;
@@ -44,23 +47,29 @@ public:
mCurrentlyConnected(false),
mPreviouslyConnected(false) {}
- void onDisconnect() override;
+ void onDisconnect() override EXCLUDES(mMutex);
void addAndGetFrameTimestamps(const NewFrameEventsEntry* newTimestamps,
- FrameEventHistoryDelta* outDelta) override REQUIRES(mMutex);
- void updateFrameTimestamps(uint64_t frameNumber, nsecs_t refreshStartTime,
- const sp<Fence>& gpuCompositionDoneFence,
+ FrameEventHistoryDelta* outDelta) override EXCLUDES(mMutex);
+ void updateFrameTimestamps(uint64_t frameNumber, uint64_t previousFrameNumber,
+ nsecs_t refreshStartTime, const sp<Fence>& gpuCompositionDoneFence,
const sp<Fence>& presentFence, const sp<Fence>& 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);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
private:
const wp<BLASTBufferQueue> mBLASTBufferQueue;
- uint64_t mCurrentFrameNumber = 0;
+ uint64_t mCurrentFrameNumber GUARDED_BY(mMutex) = 0;
Mutex mMutex;
ConsumerFrameEventHistory mFrameEventHistory GUARDED_BY(mMutex);
@@ -69,9 +78,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<SurfaceControl>& surface, int width,
@@ -83,7 +90,6 @@ public:
sp<Surface> getSurface(bool includeSurfaceControlHandle);
bool isSameSurfaceControl(const sp<SurfaceControl>& surfaceControl) const;
- void onBufferFreed(const wp<GraphicBuffer>&/* graphicBuffer*/) override { /* TODO */ }
void onFrameReplaced(const BufferItem& item) override;
void onFrameAvailable(const BufferItem& item) override;
void onFrameDequeued(const uint64_t) override;
@@ -96,10 +102,12 @@ public:
void releaseBufferCallback(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
std::optional<uint32_t> currentMaxAcquiredBufferCount);
void releaseBufferCallbackLocked(const ReleaseCallbackId& id, const sp<Fence>& releaseFence,
- std::optional<uint32_t> currentMaxAcquiredBufferCount);
- void syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
+ std::optional<uint32_t> currentMaxAcquiredBufferCount,
+ bool fakeRelease) REQUIRES(mMutex);
+ bool syncNextTransaction(std::function<void(SurfaceComposerClient::Transaction*)> callback,
bool acquireSingleBuffer = true);
void stopContinuousSyncTransaction();
+ void clearSyncTransaction();
void mergeWithNextTransaction(SurfaceComposerClient::Transaction* t, uint64_t frameNumber);
void applyPendingTransactions(uint64_t frameNumber);
@@ -108,26 +116,24 @@ public:
void update(const sp<SurfaceControl>& surface, uint32_t width, uint32_t height, int32_t format);
status_t setFrameRate(float frameRate, int8_t compatibility, bool shouldBeSeamless);
- status_t setFrameTimelineInfo(const FrameTimelineInfo& info);
+ status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
void setSidebandStream(const sp<NativeHandle>& stream);
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<void(bool)> callback);
+ void setTransactionHangCallback(std::function<void(const std::string&)> callback);
virtual ~BLASTBufferQueue();
private:
friend class BLASTBufferQueueHelper;
+ friend class BBQBufferQueueProducer;
// can't be copied
BLASTBufferQueue& operator = (const BLASTBufferQueue& rhs);
@@ -135,12 +141,13 @@ private:
void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer);
- void acquireNextBufferLocked(
+ void resizeFrameEventHistory(size_t newSize);
+
+ status_t acquireNextBufferLocked(
const std::optional<SurfaceComposerClient::Transaction*> transaction) REQUIRES(mMutex);
Rect computeCrop(const BufferItem& item) REQUIRES(mMutex);
// Return true if we need to reject the buffer based on the scaling mode and the buffer size.
bool rejectBuffer(const BufferItem& item) REQUIRES(mMutex);
- bool maxBuffersAcquired(bool includeExtraAcquire) const REQUIRES(mMutex);
static PixelFormat convertBufferFormat(PixelFormat& format);
void mergePendingTransactions(SurfaceComposerClient::Transaction* t, uint64_t frameNumber)
REQUIRES(mMutex);
@@ -149,7 +156,6 @@ private:
void acquireAndReleaseBuffer() REQUIRES(mMutex);
void releaseBuffer(const ReleaseCallbackId& callbackId, const sp<Fence>& releaseFence)
REQUIRES(mMutex);
- void flushAndWaitForFreeBuffer(std::unique_lock<std::mutex>& lock);
std::string mName;
// Represents the queued buffer count from buffer queue,
@@ -157,7 +163,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<SurfaceControl> mSurfaceControl;
+ sp<SurfaceControl> mSurfaceControl GUARDED_BY(mMutex);
mutable std::mutex mMutex;
std::condition_variable mCallbackCV;
@@ -169,6 +175,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<ReleaseCallbackId, BufferItem, ReleaseBufferCallbackIdHash> mSubmitted
@@ -238,14 +249,14 @@ private:
std::vector<std::tuple<uint64_t /* framenumber */, SurfaceComposerClient::Transaction>>
mPendingTransactions GUARDED_BY(mMutex);
- std::queue<FrameTimelineInfo> mNextFrameTimelineInfoQueue GUARDED_BY(mMutex);
+ std::queue<std::pair<uint64_t, FrameTimelineInfo>> mPendingFrameTimelines GUARDED_BY(mMutex);
// Tracks the last acquired frame number
uint64_t mLastAcquiredFrameNumber GUARDED_BY(mMutex) = 0;
// 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<IBinder> mApplyToken GUARDED_BY(mMutex) = new BBinder();
+ const sp<IBinder> mApplyToken GUARDED_BY(mMutex) = sp<BBinder>::make();
// Guards access to mDequeueTimestamps since we cannot hold to mMutex in onFrameDequeued or
// we will deadlock.
@@ -259,7 +270,7 @@ private:
// callback for them.
std::queue<sp<SurfaceControl>> 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
@@ -283,10 +294,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<void(bool)> mTransactionHangCallback;
+ std::function<void(const std::string&)> mTransactionHangCallback;
std::unordered_set<uint64_t> mSyncedFrameNumbers GUARDED_BY(mMutex);
};
diff --git a/libs/gui/include/gui/BufferQueue.h b/libs/gui/include/gui/BufferQueue.h
index 690587f0e6..0948c4d076 100644
--- a/libs/gui/include/gui/BufferQueue.h
+++ b/libs/gui/include/gui/BufferQueue.h
@@ -19,9 +19,12 @@
#include <gui/BufferItem.h>
#include <gui/BufferQueueDefs.h>
+
+#include <gui/IConsumerListener.h>
#include <gui/IGraphicBufferConsumer.h>
#include <gui/IGraphicBufferProducer.h>
-#include <gui/IConsumerListener.h>
+
+#include <com_android_graphics_libgui_flags.h>
namespace android {
@@ -69,6 +72,10 @@ public:
void addAndGetFrameTimestamps(
const NewFrameEventsEntry* newTimestamps,
FrameEventHistoryDelta* outDelta) override;
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
private:
// mConsumerListener is a weak reference to the IConsumerListener. This is
// the raison d'etre of ProxyConsumerListener.
diff --git a/libs/gui/include/gui/BufferQueueCore.h b/libs/gui/include/gui/BufferQueueCore.h
index 8d0828d88e..22c2be7bc7 100644
--- a/libs/gui/include/gui/BufferQueueCore.h
+++ b/libs/gui/include/gui/BufferQueueCore.h
@@ -34,13 +34,13 @@
#include <mutex>
#include <condition_variable>
-#define ATRACE_BUFFER_INDEX(index) \
- do { \
- if (ATRACE_ENABLED()) { \
- char ___traceBuf[1024]; \
- snprintf(___traceBuf, 1024, "%s: %d", mCore->mConsumerName.string(), (index)); \
- android::ScopedTrace ___bufTracer(ATRACE_TAG, ___traceBuf); \
- } \
+#define ATRACE_BUFFER_INDEX(index) \
+ do { \
+ if (ATRACE_ENABLED()) { \
+ char ___traceBuf[1024]; \
+ snprintf(___traceBuf, 1024, "%s: %d", mCore->mConsumerName.c_str(), (index)); \
+ android::ScopedTrace ___bufTracer(ATRACE_TAG, ___traceBuf); \
+ } \
} while (false)
namespace android {
diff --git a/libs/gui/include/gui/BufferQueueProducer.h b/libs/gui/include/gui/BufferQueueProducer.h
index 0ad3075a4d..de47483dca 100644
--- a/libs/gui/include/gui/BufferQueueProducer.h
+++ b/libs/gui/include/gui/BufferQueueProducer.h
@@ -18,6 +18,7 @@
#define ANDROID_GUI_BUFFERQUEUEPRODUCER_H
#include <gui/BufferQueueDefs.h>
+
#include <gui/IGraphicBufferProducer.h>
namespace android {
@@ -201,6 +202,16 @@ public:
// See IGraphicBufferProducer::setAutoPrerotation
virtual status_t setAutoPrerotation(bool autoPrerotation);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ // See IGraphicBufferProducer::setFrameRate
+ status_t setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
+
+protected:
+ // see IGraphicsBufferProducer::setMaxDequeuedBufferCount, but with the ability to retrieve the
+ // total maximum buffer count for the buffer queue (dequeued AND acquired)
+ status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers, int* maxBufferCount);
private:
// This is required by the IBinder::DeathRecipient interface
diff --git a/libs/gui/include/gui/Choreographer.h b/libs/gui/include/gui/Choreographer.h
new file mode 100644
index 0000000000..9fef512b64
--- /dev/null
+++ b/libs/gui/include/gui/Choreographer.h
@@ -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.
+ */
+
+#pragma once
+
+#include <android/choreographer.h>
+#include <gui/DisplayEventDispatcher.h>
+#include <jni.h>
+#include <utils/Looper.h>
+
+#include <mutex>
+#include <queue>
+#include <thread>
+
+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<Choreographer*> ptrs GUARDED_BY(lock);
+ std::map<AVsyncId, int64_t> startTimes GUARDED_BY(lock);
+ bool registeredToDisplayManager GUARDED_BY(lock) = false;
+
+ std::atomic<nsecs_t> mLastKnownVsync = -1;
+ };
+ static Context gChoreographers;
+
+ explicit Choreographer(const sp<Looper>& looper, const sp<IBinder>& 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 dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) 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<FrameRateOverride> overrides) override;
+
+ void scheduleCallbacks();
+
+ ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
+ void registerStartTime() const;
+
+ std::mutex mLock;
+ // Protected by mLock
+ std::priority_queue<FrameCallback> mFrameCallbacks;
+ std::vector<RefreshRateCallback> mRefreshRateCallbacks;
+
+ nsecs_t mLatestVsyncPeriod = -1;
+ VsyncEventData mLastVsyncEventData;
+ bool mInCallback = false;
+
+ const sp<Looper> 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
diff --git a/libs/gui/include/gui/CompositorTiming.h b/libs/gui/include/gui/CompositorTiming.h
new file mode 100644
index 0000000000..cb8ca7a15c
--- /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 <utils/Timers.h>
+
+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 ec884cfa8c..e29ce41bd5 100644
--- a/libs/gui/include/gui/DisplayCaptureArgs.h
+++ b/libs/gui/include/gui/DisplayCaptureArgs.h
@@ -22,8 +22,11 @@
#include <binder/IBinder.h>
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
+#include <gui/SpHash.h>
#include <ui/GraphicTypes.h>
#include <ui/PixelFormat.h>
+#include <ui/Rect.h>
+#include <unordered_set>
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<sp<IBinder>, SpHash<IBinder>> 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);
};
@@ -62,7 +76,6 @@ struct DisplayCaptureArgs : CaptureArgs {
sp<IBinder> displayToken;
uint32_t width{0};
uint32_t height{0};
- bool useIdentityTransform{false};
status_t writeToParcel(Parcel* output) const override;
status_t readFromParcel(const Parcel* input) override;
diff --git a/libs/gui/include/gui/DisplayEventDispatcher.h b/libs/gui/include/gui/DisplayEventDispatcher.h
index a3425395bf..fe2dd206ed 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>& looper,
- ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp,
- ISurfaceComposer::EventRegistrationFlags eventRegistration = {});
+ explicit DisplayEventDispatcher(const sp<Looper>& looper,
+ gui::ISurfaceComposer::VsyncSource vsyncSource =
+ gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
+ EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
status_t initialize();
void dispose();
@@ -52,6 +53,9 @@ private:
VsyncEventData vsyncEventData) = 0;
virtual void dispatchHotplug(nsecs_t timestamp, PhysicalDisplayId displayId,
bool connected) = 0;
+
+ virtual void dispatchHotplugConnectionError(nsecs_t timestamp, int32_t connectionError) = 0;
+
virtual void dispatchModeChanged(nsecs_t timestamp, PhysicalDisplayId displayId, int32_t modeId,
nsecs_t vsyncPeriod) = 0;
// AChoreographer-specific hook for processing null-events so that looper
diff --git a/libs/gui/include/gui/DisplayEventReceiver.h b/libs/gui/include/gui/DisplayEventReceiver.h
index cf7a4e5522..79582ce685 100644
--- a/libs/gui/include/gui/DisplayEventReceiver.h
+++ b/libs/gui/include/gui/DisplayEventReceiver.h
@@ -20,20 +20,26 @@
#include <stdint.h>
#include <sys/types.h>
+#include <ftl/flags.h>
+
#include <utils/Errors.h>
#include <utils/RefBase.h>
#include <utils/Timers.h>
+#include <android/gui/ISurfaceComposer.h>
#include <binder/IInterface.h>
-#include <gui/ISurfaceComposer.h>
#include <gui/VsyncEventData.h>
+#include <ui/DisplayId.h>
+
// ----------------------------------------------------------------------------
namespace android {
// ----------------------------------------------------------------------------
+using EventRegistrationFlags = ftl::Flags<gui::ISurfaceComposer::EventRegistration>;
+
using gui::IDisplayEventConnection;
using gui::ParcelableVsyncEventData;
using gui::VsyncEventData;
@@ -82,6 +88,7 @@ public:
struct Hotplug {
bool connected;
+ int32_t connectionError __attribute__((aligned(4)));
};
struct ModeChange {
@@ -111,9 +118,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<IBinder>& 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 74f33a2a87..42b62c755c 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 0000000000..62ceddee5f
--- /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 <cstdint>
+#include <deque>
+#include <mutex>
+
+#include <ui/Fence.h>
+
+namespace android::gui {
+
+class FenceMonitor {
+public:
+ explicit FenceMonitor(const char* name);
+ void queueFence(const sp<Fence>& fence);
+
+private:
+ void loop();
+ void threadLoop();
+
+ const char* mName;
+ uint32_t mFencesQueued;
+ uint32_t mFencesSignaled;
+ std::deque<sp<Fence>> mQueue;
+ std::condition_variable mCondition;
+ std::mutex mMutex;
+};
+
+} // namespace android::gui \ No newline at end of file
diff --git a/libs/ui/include_types/ui/DataspaceUtils.h b/libs/gui/include/gui/FrameRateUtils.h
index a461cb4e68..16896efe1f 100644
--- a/libs/ui/include_types/ui/DataspaceUtils.h
+++ b/libs/gui/include/gui/FrameRateUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2021 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,14 +16,11 @@
#pragma once
-#include <ui/GraphicTypes.h>
+#include <stdint.h>
namespace android {
-inline bool isHdrDataspace(ui::Dataspace dataspace) {
- const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
-
- return transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG;
-}
+bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
+ const char* inFunctionName, bool privileged = false);
} // namespace android \ No newline at end of file
diff --git a/libs/gui/include/gui/FrameTimestamps.h b/libs/gui/include/gui/FrameTimestamps.h
index dd3de58844..3d1be4d2eb 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 <android/gui/FrameEvent.h>
+
+#include <gui/CompositorTiming.h>
#include <ui/FenceTime.h>
#include <utils/Flattenable.h>
#include <utils/StrongPointer.h>
@@ -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<FenceTime> 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 {
@@ -114,9 +97,11 @@ public:
void checkFencesForCompletion();
void dump(std::string& outString) const;
- static const size_t MAX_FRAME_HISTORY;
+ static const size_t INITIAL_MAX_FRAME_HISTORY;
protected:
+ void resize(size_t newSize);
+
std::vector<FrameEvents> mFrames;
CompositorTiming mCompositorTiming;
@@ -155,6 +140,8 @@ protected:
virtual std::shared_ptr<FenceTime> createFenceTime(
const sp<Fence>& fence) const;
+ void resize(size_t newSize);
+
size_t mAcquireOffset{0};
// The consumer updates it's timelines in Layer and SurfaceFlinger since
@@ -225,6 +212,8 @@ public:
void getAndResetDelta(FrameEventHistoryDelta* delta);
+ void resize(size_t newSize);
+
private:
void getFrameDelta(FrameEventHistoryDelta* delta,
const std::vector<FrameEvents>::iterator& frame);
@@ -267,6 +256,10 @@ public:
status_t unflatten(void const*& buffer, size_t& size, int const*& fds,
size_t& count);
+ uint64_t getFrameNumber() const;
+ bool getLatchTime(nsecs_t* latchTime) const;
+ bool getDisplayPresentFence(sp<Fence>* fence) const;
+
private:
static constexpr size_t minFlattenedSize();
@@ -327,6 +320,9 @@ public:
status_t unflatten(void const*& buffer, size_t& size, int const*& fds,
size_t& count);
+ std::vector<FrameEventsDelta>::const_iterator begin() const;
+ std::vector<FrameEventsDelta>::const_iterator end() const;
+
private:
static constexpr size_t minFlattenedSize();
diff --git a/libs/gui/include/gui/GLConsumer.h b/libs/gui/include/gui/GLConsumer.h
index 2f538ffb86..ba268ab17a 100644
--- a/libs/gui/include/gui/GLConsumer.h
+++ b/libs/gui/include/gui/GLConsumer.h
@@ -138,6 +138,10 @@ public:
const sp<GraphicBuffer>& 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/IConsumerListener.h b/libs/gui/include/gui/IConsumerListener.h
index 0ab2399eb2..51d3959de7 100644
--- a/libs/gui/include/gui/IConsumerListener.h
+++ b/libs/gui/include/gui/IConsumerListener.h
@@ -24,6 +24,8 @@
#include <cstdint>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
class BufferItem;
@@ -90,6 +92,12 @@ public:
// WARNING: This method can only be called when the BufferQueue is in the consumer's process.
virtual void addAndGetFrameTimestamps(const NewFrameEventsEntry* /*newTimestamps*/,
FrameEventHistoryDelta* /*outDelta*/) {}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ // Notifies the consumer of a setFrameRate call from the producer side.
+ virtual void onSetFrameRate(float /*frameRate*/, int8_t /*compatibility*/,
+ int8_t /*changeFrameRateStrategy*/) {}
+#endif
};
#ifndef NO_BINDER
diff --git a/libs/gui/include/gui/IGraphicBufferProducer.h b/libs/gui/include/gui/IGraphicBufferProducer.h
index 98df83453d..7639e709ca 100644
--- a/libs/gui/include/gui/IGraphicBufferProducer.h
+++ b/libs/gui/include/gui/IGraphicBufferProducer.h
@@ -41,6 +41,8 @@
#include <optional>
#include <vector>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
// ----------------------------------------------------------------------------
@@ -676,6 +678,12 @@ public:
// the width and height used for dequeueBuffer will be additionally swapped.
virtual status_t setAutoPrerotation(bool autoPrerotation);
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ // Sets the apps intended frame rate.
+ virtual status_t setFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy);
+#endif
+
struct RequestBufferOutput : public Flattenable<RequestBufferOutput> {
RequestBufferOutput() = default;
diff --git a/libs/gui/include/gui/IProducerListener.h b/libs/gui/include/gui/IProducerListener.h
index f7ffbb99ea..b15f501518 100644
--- a/libs/gui/include/gui/IProducerListener.h
+++ b/libs/gui/include/gui/IProducerListener.h
@@ -49,6 +49,12 @@ public:
// onBuffersFreed is called from IGraphicBufferConsumer::discardFreeBuffers
// to notify the producer that certain free buffers are discarded by the consumer.
virtual void onBuffersDiscarded(const std::vector<int32_t>& slots) = 0; // Asynchronous
+ // onBufferDetached is called from IGraphicBufferConsumer::detachBuffer to
+ // notify the producer that a buffer slot is free and ready to be dequeued.
+ //
+ // This is called without any lock held and can be called concurrently by
+ // multiple threads.
+ virtual void onBufferDetached(int /*slot*/) {} // Asynchronous
};
#ifndef NO_BINDER
diff --git a/libs/gui/include/gui/ISurfaceComposer.h b/libs/gui/include/gui/ISurfaceComposer.h
index a610e940be..a836f4642a 100644
--- a/libs/gui/include/gui/ISurfaceComposer.h
+++ b/libs/gui/include/gui/ISurfaceComposer.h
@@ -16,19 +16,19 @@
#pragma once
+#include <android/gui/CachingHint.h>
#include <android/gui/DisplayBrightness.h>
+#include <android/gui/FrameTimelineInfo.h>
#include <android/gui/IDisplayEventConnection.h>
#include <android/gui/IFpsListener.h>
#include <android/gui/IHdrLayerInfoListener.h>
#include <android/gui/IRegionSamplingListener.h>
#include <android/gui/IScreenCaptureListener.h>
-#include <android/gui/ITransactionTraceListener.h>
#include <android/gui/ITunnelModeEnabledListener.h>
#include <android/gui/IWindowInfosListener.h>
+#include <android/gui/IWindowInfosPublisher.h>
#include <binder/IBinder.h>
#include <binder/IInterface.h>
-#include <ftl/flags.h>
-#include <gui/FrameTimelineInfo.h>
#include <gui/ITransactionCompletedListener.h>
#include <gui/SpHash.h>
#include <math/vec4.h>
@@ -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<EventRegistration>;
-
- /*
- * Create a connection with SurfaceFlinger.
- */
- virtual sp<ISurfaceComposerClient> createConnection() = 0;
-
- /* return an IDisplayEventConnection */
- virtual sp<IDisplayEventConnection> createDisplayEventConnection(
- VsyncSource vsyncSource = eVsyncSourceApp,
- EventRegistrationFlags eventRegistration = {}) = 0;
-
/* open/close transactions. requires ACCESS_SURFACE_FLINGER permission */
virtual status_t setTransactionState(
- const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& state,
+ const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
- const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& 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<IGraphicBufferProducer>& surface) const = 0;
-
- /* Returns the frame timestamps supported by SurfaceFlinger.
- */
- virtual status_t getSupportedFrameTimestamps(
- std::vector<FrameEvent>* outSupported) const = 0;
-
- /**
- * Gets immutable information about given physical display.
- */
- virtual status_t getStaticDisplayInfo(const sp<IBinder>& display, ui::StaticDisplayInfo*) = 0;
-
- /**
- * Gets dynamic information about given physical display.
- */
- virtual status_t getDynamicDisplayInfo(const sp<IBinder>& display, ui::DynamicDisplayInfo*) = 0;
-
- virtual status_t getDisplayNativePrimaries(const sp<IBinder>& display,
- ui::DisplayPrimaries& primaries) = 0;
- virtual status_t setActiveColorMode(const sp<IBinder>& display,
- ui::ColorMode colorMode) = 0;
-
- /**
- * Sets the user-preferred display mode that a device should boot in.
- */
- virtual status_t setBootDisplayMode(const sp<IBinder>& 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<IBinder>& display,
- const std::vector<ui::Hdr>& 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<LayerDebugInfo>* 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<IBinder>& 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<IBinder>& 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<IBinder>& 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<IBinder>& stopLayerHandle,
- const sp<IRegionSamplingListener>& listener) = 0;
-
- /*
- * Removes a listener that was streaming median luma updates from SurfaceFlinger.
- */
- virtual status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& 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<gui::IFpsListener>& listener) = 0;
- /*
- * Removes a listener that was streaming fps updates from SurfaceFlinger.
- */
- virtual status_t removeFpsListener(const sp<gui::IFpsListener>& listener) = 0;
-
- /* Registers a listener to receive tunnel mode enabled updates from SurfaceFlinger.
- *
- * Requires ACCESS_SURFACE_FLINGER permission.
- */
- virtual status_t addTunnelModeEnabledListener(
- const sp<gui::ITunnelModeEnabledListener>& 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<gui::ITunnelModeEnabledListener>& 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<IBinder>& displayToken, ui::DisplayModeId defaultMode,
- bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
- float appRequestRefreshRateMin, float appRequestRefreshRateMax) = 0;
-
- virtual status_t getDesiredDisplayModeSpecs(const sp<IBinder>& 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<IBinder>& displayToken,
- std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
- outSupport) const = 0;
-
- /*
- * Sets the intended frame rate for a surface. See ANativeWindow_setFrameRate() for more info.
- */
- virtual status_t setFrameRate(const sp<IGraphicBufferProducer>& 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<IGraphicBufferProducer>& surface,
- const FrameTimelineInfo& frameTimelineInfo) = 0;
-
- /*
- * Adds a TransactionTraceListener to listen for transaction tracing state updates.
- */
- virtual status_t addTransactionTraceListener(
- const sp<gui::ITransactionTraceListener>& 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<gui::IWindowInfosListener>& windowInfosListener) const = 0;
- virtual status_t removeWindowInfosListener(
- const sp<gui::IWindowInfosListener>& windowInfosListener) const = 0;
+ InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
+ bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffer,
+ bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
+ uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) = 0;
};
// ----------------------------------------------------------------------------
@@ -442,77 +128,78 @@ 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.
+ GET_SCHEDULING_POLICY,
// 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 9e9e191480..0000000000
--- 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 <binder/IInterface.h>
-#include <binder/SafeInterface.h>
-#include <gui/LayerMetadata.h>
-#include <ui/PixelFormat.h>
-
-#include <unordered_map>
-
-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<IBinder>& parent,
- LayerMetadata metadata, sp<IBinder>* handle,
- sp<IGraphicBufferProducer>* 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<IGraphicBufferProducer>& parent,
- LayerMetadata metadata, sp<IBinder>* handle,
- sp<IGraphicBufferProducer>* gbp, int32_t* outLayerId,
- uint32_t* outTransformHint) = 0;
-
- /*
- * Requires ACCESS_SURFACE_FLINGER permission
- */
- virtual status_t clearLayerFrameStats(const sp<IBinder>& handle) const = 0;
-
- /*
- * Requires ACCESS_SURFACE_FLINGER permission
- */
- virtual status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const = 0;
-
- virtual status_t mirrorSurface(const sp<IBinder>& mirrorFromHandle, sp<IBinder>* outHandle,
- int32_t* outLayerId) = 0;
-};
-
-class BnSurfaceComposerClient : public SafeBnInterface<ISurfaceComposerClient> {
-public:
- BnSurfaceComposerClient()
- : SafeBnInterface<ISurfaceComposerClient>("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 cc136bb40a..bc97cd0828 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;
@@ -90,15 +95,18 @@ public:
status_t readFromParcel(const Parcel* input) override;
FrameEventHistoryStats() = default;
- FrameEventHistoryStats(uint64_t fn, const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
+ FrameEventHistoryStats(uint64_t frameNumber, uint64_t previousFrameNumber,
+ const sp<Fence>& gpuCompFence, CompositorTiming compTiming,
nsecs_t refreshTime, nsecs_t dequeueReadyTime)
- : frameNumber(fn),
+ : frameNumber(frameNumber),
+ previousFrameNumber(previousFrameNumber),
gpuCompositionDoneFence(gpuCompFence),
compositorTiming(compTiming),
refreshStartTime(refreshTime),
dequeueReadyTime(dequeueReadyTime) {}
uint64_t frameNumber;
+ uint64_t previousFrameNumber;
sp<Fence> gpuCompositionDoneFence;
CompositorTiming compositorTiming;
nsecs_t refreshStartTime;
@@ -115,14 +123,17 @@ public:
status_t readFromParcel(const Parcel* input) override;
JankData();
- JankData(int64_t frameVsyncId, int32_t jankType)
- : frameVsyncId(frameVsyncId), jankType(jankType) {}
+ JankData(int64_t frameVsyncId, int32_t jankType, nsecs_t frameIntervalNs)
+ : frameVsyncId(frameVsyncId), jankType(jankType), frameIntervalNs(frameIntervalNs) {}
// Identifier for the frame submitted with Transaction.setFrameTimelineVsyncId
int64_t frameVsyncId;
// Bitmask of janks that occurred
int32_t jankType;
+
+ // Expected duration of the frame
+ nsecs_t frameIntervalNs;
};
class SurfaceStats : public Parcelable {
@@ -132,7 +143,7 @@ public:
SurfaceStats() = default;
SurfaceStats(const sp<IBinder>& sc, std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
- const sp<Fence>& prevReleaseFence, uint32_t hint,
+ const sp<Fence>& prevReleaseFence, std::optional<uint32_t> hint,
uint32_t currentMaxAcquiredBuffersCount, FrameEventHistoryStats frameEventStats,
std::vector<JankData> jankData, ReleaseCallbackId previousReleaseCallbackId)
: surfaceControl(sc),
@@ -147,7 +158,7 @@ public:
sp<IBinder> surfaceControl;
std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
sp<Fence> previousReleaseFence;
- uint32_t transformHint = 0;
+ std::optional<uint32_t> transformHint = 0;
uint32_t currentMaxAcquiredBufferCount = 0;
FrameEventHistoryStats eventStats;
std::vector<JankData> jankData;
@@ -194,7 +205,10 @@ public:
virtual void onReleaseBuffer(ReleaseCallbackId callbackId, sp<Fence> 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<ITransactionCompletedListener> {
diff --git a/libs/gui/include/gui/JankInfo.h b/libs/gui/include/gui/JankInfo.h
index ce9716f1fe..1fc80c30d7 100644
--- a/libs/gui/include/gui/JankInfo.h
+++ b/libs/gui/include/gui/JankInfo.h
@@ -18,15 +18,15 @@
namespace android {
-// Jank information tracked by SurfaceFlinger(SF) for perfetto tracing and telemetry.
+// Jank type tracked by SurfaceFlinger(SF) for Perfetto tracing and telemetry.
enum JankType {
// No Jank
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,
@@ -46,6 +46,20 @@ enum JankType {
// where the previous frame was presented in the current frame's expected vsync. This pushes the
// current frame to the next vsync. The behavior is similar to BufferStuffing.
SurfaceFlingerStuffing = 0x100,
+ // Frame was dropped, as a newer frame was ready and replaced this frame.
+ Dropped = 0x200,
+};
+
+// Jank severity type tracked by SurfaceFlinger(SF) for Perfetto tracing and telemetry.
+enum class JankSeverityType {
+ // Unknown: not enough information to classify the severity of a jank
+ Unknown = 0,
+ // None: no jank
+ None = 1,
+ // Partial: jank caused by missing the deadline by less than the app's frame interval
+ Partial = 2,
+ // Full: jank caused by missing the deadline by more than the app's frame interval
+ Full = 3,
};
} // namespace android
diff --git a/libs/gui/include/gui/LayerCaptureArgs.h b/libs/gui/include/gui/LayerCaptureArgs.h
index 05ff9d5b7b..fae2bcc787 100644
--- a/libs/gui/include/gui/LayerCaptureArgs.h
+++ b/libs/gui/include/gui/LayerCaptureArgs.h
@@ -20,14 +20,11 @@
#include <sys/types.h>
#include <gui/DisplayCaptureArgs.h>
-#include <gui/SpHash.h>
-#include <unordered_set>
namespace android::gui {
struct LayerCaptureArgs : CaptureArgs {
sp<IBinder> layerHandle;
- std::unordered_set<sp<IBinder>, SpHash<IBinder>> 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 af834d78df..dbb80e583c 100644
--- a/libs/gui/include/gui/LayerDebugInfo.h
+++ b/libs/gui/include/gui/LayerDebugInfo.h
@@ -25,7 +25,7 @@
#include <string>
#include <math/vec4.h>
-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 27f4d379e9..9cf62bc7d6 100644
--- a/libs/gui/include/gui/LayerMetadata.h
+++ b/libs/gui/include/gui/LayerMetadata.h
@@ -20,7 +20,7 @@
#include <unordered_map>
-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 0a9b75a7f1..e1dc7911a1 100644
--- a/libs/gui/include/gui/LayerState.h
+++ b/libs/gui/include/gui/LayerState.h
@@ -21,6 +21,8 @@
#include <stdint.h>
#include <sys/types.h>
+#include <android/gui/IWindowInfosReportedListener.h>
+#include <android/gui/TrustedPresentationThresholds.h>
#include <android/native_window.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/ITransactionCompletedListener.h>
@@ -51,7 +53,11 @@
namespace android {
class Parcel;
-class ISurfaceComposerClient;
+
+using gui::ISurfaceComposerClient;
+using gui::LayerMetadata;
+
+using gui::TrustedPresentationThresholds;
struct client_cache_t {
wp<IBinder> token = nullptr;
@@ -62,6 +68,19 @@ struct client_cache_t {
bool isValid() const { return token != nullptr; }
};
+class TrustedPresentationListener : public Parcelable {
+public:
+ sp<ITransactionCompletedListener> 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,
+ eFrameRateCategoryChanged = 0x00020000,
+ eBufferTransformChanged = 0x00040000,
eTransformToDisplayInverseChanged = 0x00080000,
eCropChanged = 0x00100000,
eBufferChanged = 0x00200000,
- /* unused 0x00400000, */
+ eDefaultFrameRateCompatibilityChanged = 0x00400000,
eDataspaceChanged = 0x00800000,
eHdrMetadataChanged = 0x01000000,
eSurfaceDamageRegionChanged = 0x02000000,
@@ -174,7 +197,7 @@ struct layer_state_t {
eInputInfoChanged = 0x40000000,
eCornerRadiusChanged = 0x80000000,
eDestinationFrameChanged = 0x1'00000000,
- /* unused = 0x2'00000000, */
+ eFrameRateSelectionStrategyChanged = 0x2'00000000,
eBackgroundColorChanged = 0x4'00000000,
eMetadataChanged = 0x8'00000000,
eColorSpaceAgnosticChanged = 0x10'00000000,
@@ -188,7 +211,8 @@ 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 +220,66 @@ 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::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
+ layer_state_t::eColorTransformChanged | layer_state_t::eCornerRadiusChanged |
+ layer_state_t::eFlagsChanged | layer_state_t::eTrustedOverlayChanged |
+ layer_state_t::eFrameRateChanged | layer_state_t::eFrameRateCategoryChanged |
+ layer_state_t::eFrameRateSelectionStrategyChanged |
+ layer_state_t::eFrameRateSelectionPriority | layer_state_t::eFixedTransformHintChanged;
+
+ // Changes affecting data sent to input.
+ static constexpr uint64_t INPUT_CHANGES = layer_state_t::eInputInfoChanged |
+ layer_state_t::eDropInputModeChanged | layer_state_t::eTrustedOverlayChanged |
+ layer_state_t::eLayerStackChanged;
+
+ // Changes that affect the visible region on a display.
+ static constexpr uint64_t VISIBLE_REGION_CHANGES = layer_state_t::GEOMETRY_CHANGES |
+ layer_state_t::HIERARCHY_CHANGES | layer_state_t::eAlphaChanged;
+
bool hasValidBuffer() const;
void sanitize(int32_t permissions);
@@ -207,6 +290,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<IBinder> surface;
int32_t layerId;
@@ -214,28 +302,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<SurfaceControl> reparentSurfaceControl;
sp<SurfaceControl> relativeLayerSurfaceControl;
sp<SurfaceControl> 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> bufferData = nullptr;
@@ -247,13 +330,13 @@ struct layer_state_t {
mat4 colorTransform;
std::vector<BlurRegion> blurRegions;
- sp<gui::WindowInfoHandle> windowInfoHandle = new gui::WindowInfoHandle();
+ sp<gui::WindowInfoHandle> windowInfoHandle = sp<gui::WindowInfoHandle>::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 +356,16 @@ 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;
+
+ // Frame rate category to suggest what frame rate range a surface should run.
+ int8_t frameRateCategory;
+ bool frameRateCategorySmoothSwitchOnly;
+
+ // Strategy of the layer for frame rate selection.
+ int8_t frameRateSelectionStrategy;
+
// 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 +384,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 +399,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);
@@ -320,6 +426,7 @@ struct DisplayState {
DisplayState();
void merge(const DisplayState& other);
+ void sanitize(int32_t permissions);
uint32_t what = 0;
uint32_t flags = 0;
@@ -352,7 +459,9 @@ struct DisplayState {
struct InputWindowCommands {
std::vector<gui::FocusRequest> focusRequests;
- bool syncInputWindows{false};
+ std::unordered_set<sp<gui::IWindowInfosReportedListener>,
+ SpHash<gui::IWindowInfosReportedListener>>
+ windowInfosReportedListeners;
// Merges the passed in commands and returns true if there were any changes.
bool merge(const InputWindowCommands& other);
@@ -372,16 +481,6 @@ static inline int compare_type(const DisplayState& lhs, const DisplayState& rhs)
return compare_type(lhs.token, rhs.token);
}
-// Returns true if the frameRate is valid.
-//
-// @param frameRate the frame rate in Hz
-// @param compatibility a ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_*
-// @param changeFrameRateStrategy a ANATIVEWINDOW_CHANGE_FRAME_RATE_*
-// @param functionName calling function or nullptr. Used for logging
-// @param privileged whether caller has unscoped surfaceflinger access
-bool ValidateFrameRate(float frameRate, int8_t compatibility, int8_t changeFrameRateStrategy,
- const char* functionName, bool privileged = false);
-
}; // namespace android
#endif // ANDROID_SF_LAYER_STATE_H
diff --git a/libs/gui/include/gui/LayerStatePermissions.h b/libs/gui/include/gui/LayerStatePermissions.h
new file mode 100644
index 0000000000..a90f30c621
--- /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 <stdint.h>
+#include <string>
+#include <unordered_map>
+
+namespace android {
+class LayerStatePermissions {
+public:
+ static uint32_t getTransactionPermissions(int pid, int uid);
+
+private:
+ static std::unordered_map<std::string, int> mPermissionMap;
+};
+} // namespace android \ No newline at end of file
diff --git a/libs/gui/include/gui/PidUid.h b/libs/gui/include/gui/PidUid.h
new file mode 100644
index 0000000000..7930942882
--- /dev/null
+++ b/libs/gui/include/gui/PidUid.h
@@ -0,0 +1,56 @@
+/*
+ * 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 <ftl/mixins.h>
+#include <sys/types.h>
+#include <string>
+
+namespace android::gui {
+
+// Type-safe wrapper for a PID.
+struct Pid : ftl::Constructible<Pid, pid_t>, ftl::Equatable<Pid>, ftl::Orderable<Pid> {
+ using Constructible::Constructible;
+
+ const static Pid INVALID;
+
+ constexpr auto val() const { return ftl::to_underlying(*this); }
+
+ constexpr bool isValid() const { return val() >= 0; }
+
+ std::string toString() const { return std::to_string(val()); }
+};
+
+const inline Pid Pid::INVALID{-1};
+
+// Type-safe wrapper for a UID.
+// We treat the unsigned equivalent of -1 as a singular invalid value.
+struct Uid : ftl::Constructible<Uid, uid_t>, ftl::Equatable<Uid>, ftl::Orderable<Uid> {
+ using Constructible::Constructible;
+
+ const static Uid INVALID;
+
+ constexpr auto val() const { return ftl::to_underlying(*this); }
+
+ constexpr bool isValid() const { return val() != static_cast<uid_t>(-1); }
+
+ std::string toString() const { return std::to_string(val()); }
+};
+
+const inline Uid Uid::INVALID{static_cast<uid_t>(-1)};
+
+} // namespace android::gui
diff --git a/libs/gui/include/gui/SchedulingPolicy.h b/libs/gui/include/gui/SchedulingPolicy.h
new file mode 100644
index 0000000000..48c9f0c7bf
--- /dev/null
+++ b/libs/gui/include/gui/SchedulingPolicy.h
@@ -0,0 +1,40 @@
+/*
+ * 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 <sched.h>
+
+#include <android/gui/SchedulingPolicy.h>
+#include <binder/Status.h>
+
+namespace android::gui {
+
+static binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ outPolicy->policy = sched_getscheduler(0);
+ if (outPolicy->policy < 0) {
+ return binder::Status::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ struct sched_param param;
+ if (sched_getparam(0, &param) < 0) {
+ return binder::Status::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ outPolicy->priority = param.sched_priority;
+ return binder::Status::ok();
+}
+
+} // namespace android::gui \ No newline at end of file
diff --git a/libs/gui/include/gui/ScreenCaptureResults.h b/libs/gui/include/gui/ScreenCaptureResults.h
index 724c11c881..6e17791a29 100644
--- a/libs/gui/include/gui/ScreenCaptureResults.h
+++ b/libs/gui/include/gui/ScreenCaptureResults.h
@@ -19,6 +19,7 @@
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <ui/Fence.h>
+#include <ui/FenceResult.h>
#include <ui/GraphicBuffer.h>
namespace android::gui {
@@ -31,11 +32,10 @@ public:
status_t readFromParcel(const android::Parcel* parcel) override;
sp<GraphicBuffer> buffer;
- sp<Fence> 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 77615fe4c1..39a59e42aa 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 <android/gui/FrameTimelineInfo.h>
#include <gui/BufferQueueDefs.h>
-#include <gui/FrameTimelineInfo.h>
#include <gui/HdrMetadata.h>
#include <gui/IGraphicBufferProducer.h>
#include <gui/IProducerListener.h>
@@ -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,15 +205,15 @@ 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;
virtual status_t setFrameRate(float frameRate, int8_t compatibility,
int8_t changeFrameRateStrategy);
- virtual status_t setFrameTimelineInfo(const FrameTimelineInfo& info);
+ virtual status_t setFrameTimelineInfo(uint64_t frameNumber, const FrameTimelineInfo& info);
protected:
virtual ~Surface();
@@ -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 9033e17c53..14e3dd583e 100644
--- a/libs/gui/include/gui/SurfaceComposerClient.h
+++ b/libs/gui/include/gui/SurfaceComposerClient.h
@@ -38,6 +38,9 @@
#include <ui/GraphicTypes.h>
#include <ui/PixelFormat.h>
#include <ui/Rotation.h>
+#include <ui/StaticDisplayInfo.h>
+
+#include <android/gui/ISurfaceComposerClient.h>
#include <gui/CpuConsumer.h>
#include <gui/ISurfaceComposer.h>
@@ -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<SurfaceControl>& sc, nsecs_t latchTime,
std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence,
const sp<Fence>& presentFence, const sp<Fence>& prevReleaseFence,
- uint32_t hint, FrameEventHistoryStats eventStats,
+ std::optional<uint32_t> hint, FrameEventHistoryStats eventStats,
uint32_t currentMaxAcquiredBufferCount)
: surfaceControl(sc),
latchTime(latchTime),
@@ -81,7 +86,7 @@ struct SurfaceControlStats {
std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
sp<Fence> presentFence;
sp<Fence> previousReleaseFence;
- uint32_t transformHint = 0;
+ std::optional<uint32_t> transformHint = 0;
FrameEventHistoryStats frameEventStats;
uint32_t currentMaxAcquiredBufferCount = 0;
};
@@ -102,6 +107,8 @@ using SurfaceStatsCallback =
const sp<Fence>& /*presentFence*/,
const SurfaceStats& /*stats*/)>;
+using TrustedPresentationCallback = std::function<void(void*, bool)>;
+
// ---------------------------------------------------------------------------
class ReleaseCallbackThread {
@@ -141,32 +148,28 @@ public:
status_t linkToComposerDeath(const sp<IBinder::DeathRecipient>& 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<IBinder>& display, ui::DisplayState*);
// Get immutable information about given physical display.
- static status_t getStaticDisplayInfo(const sp<IBinder>& display, ui::StaticDisplayInfo*);
+ static status_t getStaticDisplayInfo(int64_t, ui::StaticDisplayInfo*);
- // Get dynamic information about given physical display.
- static status_t getDynamicDisplayInfo(const sp<IBinder>& 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<IBinder>& display, ui::DisplayMode*);
// Sets the refresh rate boundaries for the display.
- static status_t setDesiredDisplayModeSpecs(
- const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
- bool allowGroupSwitching, float primaryRefreshRateMin, float primaryRefreshRateMax,
- float appRequestRefreshRateMin, float appRequestRefreshRateMax);
+ static status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs&);
// Gets the refresh rate boundaries for the display.
static status_t getDesiredDisplayModeSpecs(const sp<IBinder>& 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<IBinder>& display,
@@ -178,14 +181,43 @@ 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<IBinder>& display, ui::DisplayModeId);
// Clears the user-preferred display mode
static status_t clearBootDisplayMode(const sp<IBinder>& display);
+ // Gets the HDR conversion capabilities of the device
+ static status_t getHdrConversionCapabilities(std::vector<gui::HdrConversionCapability>*);
+ // 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);
+ static status_t setGameModeFrameRateOverride(uid_t uid, float frameRate);
+
+ // Sets the frame rate of a particular app (uid). This is currently called
+ // by GameManager and controlled by two sysprops:
+ // "ro.surface_flinger.game_default_frame_rate_override" holding the override value,
+ // "persisit.graphics.game_default_frame_rate.enabled" to determine if it's enabled.
+ static status_t setGameDefaultFrameRateOverride(uid_t uid, float frameRate);
+
+ // Update the small area detection whole appId-threshold mappings by same size appId and
+ // threshold vector.
+ // Ref:setSmallAreaDetectionThreshold.
+ static status_t updateSmallAreaDetection(std::vector<int32_t>& appIds,
+ std::vector<float>& thresholds);
+
+ // Sets the small area detection threshold to particular apps (appId). Passing value 0 means
+ // to disable small area detection to the app.
+ static status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
// Switches on/off Auto Low Latency Mode on the connected display. This should only be
// called if the connected display supports Auto Low Latency Mode as reported by
@@ -218,7 +250,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 +346,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<IBinder>& parentHandle = nullptr, // parentHandle
LayerMetadata metadata = LayerMetadata(), // metadata
uint32_t* outTransformHint = nullptr);
@@ -324,21 +356,11 @@ public:
uint32_t h, // height in pixel
PixelFormat format, // pixel-format desired
sp<SurfaceControl>* outSurface,
- uint32_t flags = 0, // usage flags
+ int32_t flags = 0, // usage flags
const sp<IBinder>& parentHandle = nullptr, // parentHandle
LayerMetadata metadata = LayerMetadata(), // metadata
uint32_t* outTransformHint = nullptr);
- //! Create a surface
- sp<SurfaceControl> 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 +372,24 @@ public:
// B B'
sp<SurfaceControl> mirrorSurface(SurfaceControl* mirrorFromSurface);
+ sp<SurfaceControl> mirrorDisplay(DisplayId displayId);
+
//! Create a virtual display
- static sp<IBinder> createDisplay(const String8& displayName, bool secure);
+ static sp<IBinder> createDisplay(const String8& displayName, bool secure,
+ float requestedRefereshRate = 0);
//! Destroy a virtual display
static void destroyDisplay(const sp<IBinder>& display);
//! Get stable IDs for connected physical displays
static std::vector<PhysicalDisplayId> getPhysicalDisplayIds();
- static status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*);
- static std::optional<PhysicalDisplayId> getInternalDisplayId();
//! Get token for a physical display given its stable ID
static sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId);
- static sp<IBinder> getInternalDisplayToken();
-
- static status_t enableVSyncInjections(bool enable);
- static status_t injectVSync(nsecs_t when);
+ // Returns StalledTransactionInfo if a transaction from the provided pid has not been applied
+ // due to an unsignaled fence.
+ static std::optional<gui::StalledTransactionInfo> getStalledTransactionInfo(pid_t pid);
struct SCHash {
std::size_t operator()(const sp<SurfaceControl>& sc) const {
@@ -396,26 +418,43 @@ public:
std::unordered_set<sp<SurfaceControl>, SCHash> surfaceControls;
};
+ struct PresentationCallbackRAII : public RefBase {
+ sp<TransactionCompletedListener> mTcl;
+ int mId;
+ PresentationCallbackRAII(TransactionCompletedListener* tcl, int id);
+ virtual ~PresentationCallbackRAII();
+ };
+
class Transaction : public Parcelable {
private:
+ static sp<IBinder> sApplyToken;
+ static std::mutex sApplyTokenMutex;
void releaseBufferIfOverwriting(const layer_state_t& state);
+ static void mergeFrameTimelineInfo(FrameTimelineInfo& t, const FrameTimelineInfo& other);
protected:
std::unordered_map<sp<IBinder>, ComposerState, IBinderHash> mComposerStates;
SortedVector<DisplayState> mDisplayStates;
std::unordered_map<sp<ITransactionCompletedListener>, CallbackInfo, TCLHash>
mListenerCallbacks;
+ std::vector<client_cache_t> 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<uint64_t> 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 +507,17 @@ public:
// The id is updated every time the transaction is applied.
uint64_t getId();
+ std::vector<uint64_t> 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<SurfaceControl>& sc);
Transaction& hide(const sp<SurfaceControl>& sc);
- Transaction& setPosition(const sp<SurfaceControl>& sc,
- float x, float y);
- Transaction& setSize(const sp<SurfaceControl>& sc,
- uint32_t w, uint32_t h);
+ Transaction& setPosition(const sp<SurfaceControl>& sc, float x, float y);
+ // b/243180033 remove once functions are not called from vendor code
+ Transaction& setSize(const sp<SurfaceControl>&, uint32_t, uint32_t) { return *this; }
Transaction& setLayer(const sp<SurfaceControl>& sc,
int32_t z);
@@ -527,7 +567,8 @@ public:
Transaction& setBuffer(const sp<SurfaceControl>& sc, const sp<GraphicBuffer>& buffer,
const std::optional<sp<Fence>>& fence = std::nullopt,
const std::optional<uint64_t>& frameNumber = std::nullopt,
- ReleaseBufferCallback callback = nullptr);
+ uint32_t producerId = 0, ReleaseBufferCallback callback = nullptr);
+ Transaction& unsetBuffer(const sp<SurfaceControl>& sc);
std::shared_ptr<BufferData> getAndClearBuffer(const sp<SurfaceControl>& sc);
/**
@@ -551,6 +592,9 @@ public:
Transaction& setBufferHasBarrier(const sp<SurfaceControl>& sc,
uint64_t barrierFrameNumber);
Transaction& setDataspace(const sp<SurfaceControl>& sc, ui::Dataspace dataspace);
+ Transaction& setExtendedRangeBrightness(const sp<SurfaceControl>& sc,
+ float currentBufferRatio, float desiredRatio);
+ Transaction& setCachingHint(const sp<SurfaceControl>& sc, gui::CachingHint cachingHint);
Transaction& setHdrMetadata(const sp<SurfaceControl>& sc, const HdrMetadata& hdrMetadata);
Transaction& setSurfaceDamageRegion(const sp<SurfaceControl>& sc,
const Region& surfaceDamageRegion);
@@ -572,12 +616,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<SurfaceControl>& sc,
+ TrustedPresentationCallback callback,
+ const TrustedPresentationThresholds& thresholds,
+ void* context,
+ sp<PresentationCallbackRAII>& outCallbackOwner);
+
+ // Clear local memory in SCC
+ Transaction& clearTrustedPresentationCallback(const sp<SurfaceControl>& sc);
+
// ONLY FOR BLAST ADAPTER
Transaction& notifyProducerDisconnect(const sp<SurfaceControl>& sc);
Transaction& setInputWindowInfo(const sp<SurfaceControl>& sc, const gui::WindowInfo& info);
Transaction& setFocusedWindow(const gui::FocusRequest& request);
- Transaction& syncInputWindows();
+
+ Transaction& addWindowInfosReportedListener(
+ sp<gui::IWindowInfosReportedListener> windowInfosReportedListener);
// Set a color transform matrix on the given layer on the built-in display.
Transaction& setColorTransform(const sp<SurfaceControl>& sc, const mat3& matrix,
@@ -590,6 +689,14 @@ public:
Transaction& setFrameRate(const sp<SurfaceControl>& sc, float frameRate,
int8_t compatibility, int8_t changeFrameRateStrategy);
+ Transaction& setDefaultFrameRateCompatibility(const sp<SurfaceControl>& sc,
+ int8_t compatibility);
+
+ Transaction& setFrameRateCategory(const sp<SurfaceControl>& sc, int8_t category,
+ bool smoothSwitchOnly);
+
+ Transaction& setFrameRateSelectionStrategy(const sp<SurfaceControl>& sc, int8_t strategy);
+
// 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 +743,9 @@ public:
const Rect& destinationFrame);
Transaction& setDropInputMode(const sp<SurfaceControl>& sc, gui::DropInputMode mode);
+ Transaction& enableBorder(const sp<SurfaceControl>& sc, bool shouldEnable, float width,
+ const half4& color);
+
status_t setDisplaySurface(const sp<IBinder>& token,
const sp<IGraphicBufferProducer>& bufferProducer);
@@ -666,7 +776,12 @@ public:
*
* TODO (b/213644870): Remove all permissioned things from Transaction
*/
- void sanitize();
+ void sanitize(int pid, int uid);
+
+ static sp<IBinder> getDefaultApplyToken();
+ static void setDefaultApplyToken(sp<IBinder> applyToken);
+
+ static status_t sendSurfaceFlushJankDataTransaction(const sp<SurfaceControl>& sc);
};
status_t clearLayerFrameStats(const sp<IBinder>& token) const;
@@ -714,6 +829,12 @@ protected:
ReleaseCallbackThread mReleaseCallbackThread;
private:
+ // Get dynamic information about given physical display from token
+ static status_t getDynamicDisplayInfoFromToken(const sp<IBinder>& display,
+ ui::DynamicDisplayInfo*);
+
+ static void getDynamicDisplayInfoInternal(gui::DynamicDisplayInfo& ginfo,
+ ui::DynamicDisplayInfo*& outInfo);
virtual void onFirstRef();
mutable Mutex mLock;
@@ -726,8 +847,14 @@ private:
class ScreenshotClient {
public:
static status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
- static status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
+ static status_t captureDisplay(DisplayId, const gui::CaptureArgs&,
+ const sp<IScreenCaptureListener>&);
static status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+
+ [[deprecated]] static status_t captureDisplay(DisplayId id,
+ const sp<IScreenCaptureListener>& listener) {
+ return captureDisplay(id, gui::CaptureArgs(), listener);
+ }
};
// ---------------------------------------------------------------------------
@@ -779,7 +906,10 @@ protected:
// This is protected by mSurfaceStatsListenerMutex, but GUARDED_BY isn't supported for
// std::recursive_mutex
std::multimap<int32_t, SurfaceStatsCallbackEntry> mSurfaceStatsListeners;
- std::unordered_map<void*, std::function<void()>> mQueueStallListeners;
+ std::unordered_map<void*, std::function<void(const std::string&)>> mQueueStallListeners;
+
+ std::unordered_map<int, std::tuple<TrustedPresentationCallback, void*>>
+ mTrustedPresentationCallbacks;
public:
static sp<TransactionCompletedListener> getInstance();
@@ -792,14 +922,22 @@ public:
const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
surfaceControls,
CallbackId::Type callbackType);
+ CallbackId addCallbackFunctionLocked(
+ const TransactionCompletedCallback& callbackFunction,
+ const std::unordered_set<sp<SurfaceControl>, SurfaceComposerClient::SCHash>&
+ surfaceControls,
+ CallbackId::Type callbackType) REQUIRES(mMutex);
- void addSurfaceControlToCallbacks(
- const sp<SurfaceControl>& surfaceControl,
- const std::unordered_set<CallbackId, CallbackIdHash>& callbackIds);
+ void addSurfaceControlToCallbacks(SurfaceComposerClient::CallbackInfo& callbackInfo,
+ const sp<SurfaceControl>& surfaceControl);
- void addQueueStallListener(std::function<void()> stallListener, void* id);
+ void addQueueStallListener(std::function<void(const std::string&)> stallListener, void* id);
void removeQueueStallListener(void *id);
+ sp<SurfaceComposerClient::PresentationCallbackRAII> 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 +966,12 @@ public:
// For Testing Only
static void setInstance(const sp<TransactionCompletedListener>&);
- 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<TransactionCompletedListener> sInstance;
};
diff --git a/libs/gui/include/gui/SurfaceControl.h b/libs/gui/include/gui/SurfaceControl.h
index b72cf8390e..344b957ba7 100644
--- a/libs/gui/include/gui/SurfaceControl.h
+++ b/libs/gui/include/gui/SurfaceControl.h
@@ -24,17 +24,19 @@
#include <utils/RefBase.h>
#include <utils/threads.h>
+#include <android/gui/ISurfaceComposerClient.h>
+
#include <ui/FrameStats.h>
#include <ui/PixelFormat.h>
#include <ui/Region.h>
-#include <gui/ISurfaceComposerClient.h>
#include <math/vec3.h>
namespace android {
// ---------------------------------------------------------------------------
+class Choreographer;
class IGraphicBufferProducer;
class Surface;
class SurfaceComposerClient;
@@ -77,6 +79,10 @@ public:
sp<IBinder> getHandle() const;
sp<IBinder> getLayerStateHandle() const;
int32_t getLayerId() const;
+ const std::string& getName() const;
+
+ // TODO(b/267195698): Consider renaming.
+ std::shared_ptr<Choreographer> getChoreographer();
sp<IGraphicBufferProducer> getIGraphicBufferProducer();
@@ -93,9 +99,9 @@ public:
explicit SurfaceControl(const sp<SurfaceControl>& other);
SurfaceControl(const sp<SurfaceComposerClient>& client, const sp<IBinder>& handle,
- const sp<IGraphicBufferProducer>& 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<SurfaceControl> getParentingLayer();
@@ -115,19 +121,20 @@ private:
status_t validate() const;
sp<SurfaceComposerClient> mClient;
- sp<IBinder> mHandle;
- sp<IGraphicBufferProducer> mGraphicBufferProducer;
+ sp<IBinder> mHandle;
mutable Mutex mLock;
mutable sp<Surface> mSurfaceData;
mutable sp<BLASTBufferQueue> mBbq;
mutable sp<SurfaceControl> 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<Choreographer> mChoreographer;
};
}; // namespace android
diff --git a/libs/gui/include/gui/SyncScreenCaptureListener.h b/libs/gui/include/gui/SyncScreenCaptureListener.h
index 0784fbc058..bcf565a494 100644
--- a/libs/gui/include/gui/SyncScreenCaptureListener.h
+++ b/libs/gui/include/gui/SyncScreenCaptureListener.h
@@ -34,7 +34,9 @@ public:
ScreenCaptureResults waitForResults() {
std::future<ScreenCaptureResults> 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<ScreenCaptureResults> 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 e5d268445c..441b833b5d 100644
--- a/libs/gui/include/gui/TraceUtils.h
+++ b/libs/gui/include/gui/TraceUtils.h
@@ -21,11 +21,20 @@
#include <cutils/trace.h>
#include <utils/Trace.h>
-#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 ALOGE_AND_TRACE(fmt, ...) \
+ do { \
+ ALOGE(fmt, ##__VA_ARGS__); \
+ ATRACE_FORMAT_INSTANT(fmt, ##__VA_ARGS__); \
+ } while (false)
namespace android {
@@ -37,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];
@@ -50,6 +57,17 @@ public:
ATRACE_BEGIN(buf);
}
-}; // class TraceUtils
+ static void instantFormat(const char* fmt, ...) {
+ const int BUFFER_SIZE = 256;
+ va_list ap;
+ char buf[BUFFER_SIZE];
+
+ va_start(ap, fmt);
+ vsnprintf(buf, BUFFER_SIZE, fmt, ap);
+ va_end(ap);
+
+ ATRACE_INSTANT(buf);
+ }
+};
-} /* namespace android */
+} // namespace android
diff --git a/libs/gui/include/gui/VsyncEventData.h b/libs/gui/include/gui/VsyncEventData.h
index 8e99539fe9..b40a84099c 100644
--- a/libs/gui/include/gui/VsyncEventData.h
+++ b/libs/gui/include/gui/VsyncEventData.h
@@ -16,7 +16,7 @@
#pragma once
-#include <gui/FrameTimelineInfo.h>
+#include <android/gui/FrameTimelineInfo.h>
#include <array>
@@ -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 169f7f022b..4d4c5e4394 100644
--- a/libs/gui/include/gui/WindowInfo.h
+++ b/libs/gui/include/gui/WindowInfo.h
@@ -21,9 +21,12 @@
#include <binder/Parcel.h>
#include <binder/Parcelable.h>
#include <ftl/flags.h>
+#include <ftl/mixins.h>
+#include <gui/PidUid.h>
#include <gui/constants.h>
#include <ui/Rect.h>
#include <ui/Region.h>
+#include <ui/Size.h>
#include <ui/Transform.h>
#include <utils/RefBase.h>
#include <utils/Timers.h>
@@ -171,6 +174,8 @@ struct WindowInfo : public Parcelable {
static_cast<uint32_t>(os::InputConfig::SPY),
INTERCEPTS_STYLUS =
static_cast<uint32_t>(os::InputConfig::INTERCEPTS_STYLUS),
+ CLONE =
+ static_cast<uint32_t>(os::InputConfig::CLONE),
// clang-format on
};
@@ -190,10 +195,10 @@ struct WindowInfo : public Parcelable {
std::chrono::nanoseconds dispatchingTimeout = std::chrono::seconds(5);
/* These values are filled in by SurfaceFlinger. */
- int32_t frameLeft = -1;
- int32_t frameTop = -1;
- int32_t frameRight = -1;
- int32_t frameBottom = -1;
+ Rect frame = Rect::INVALID_RECT;
+
+ // The real size of the content, excluding any crop. If no buffer is rendered, this is 0,0
+ ui::Size contentSize = ui::Size(0, 0);
/*
* SurfaceFlinger consumes this value to shrink the computed frame. This is
@@ -221,8 +226,8 @@ struct WindowInfo : public Parcelable {
Region touchableRegion;
TouchOcclusionMode touchOcclusionMode = TouchOcclusionMode::BLOCK_UNTRUSTED;
- int32_t ownerPid = -1;
- int32_t ownerUid = -1;
+ Pid ownerPid = Pid::INVALID;
+ Uid ownerUid = Uid::INVALID;
std::string packageName;
ftl::Flags<InputConfig> inputConfig;
int32_t displayId = ADISPLAY_ID_NONE;
@@ -234,9 +239,12 @@ struct WindowInfo : public Parcelable {
Type layoutParamsType = Type::UNKNOWN;
ftl::Flags<Flag> layoutParamsFlags;
- void setInputConfig(ftl::Flags<InputConfig> 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<IBinder> focusTransferTarget;
- bool isClone = false;
+ void setInputConfig(ftl::Flags<InputConfig> config, bool value);
void addTouchableRegion(const Rect& region);
@@ -272,6 +280,7 @@ public:
WindowInfoHandle(const WindowInfo& other);
inline const WindowInfo* getInfo() const { return &mInfo; }
+ inline WindowInfo* editInfo() { return &mInfo; }
sp<IBinder> getToken() const;
@@ -306,4 +315,7 @@ protected:
WindowInfo mInfo;
};
+
+std::ostream& operator<<(std::ostream& out, const WindowInfoHandle& window);
+
} // namespace android::gui
diff --git a/libs/gui/include/gui/WindowInfosListener.h b/libs/gui/include/gui/WindowInfosListener.h
index a18a498c5e..02c8eb5ef3 100644
--- a/libs/gui/include/gui/WindowInfosListener.h
+++ b/libs/gui/include/gui/WindowInfosListener.h
@@ -16,15 +16,13 @@
#pragma once
-#include <gui/DisplayInfo.h>
-#include <gui/WindowInfo.h>
+#include <gui/WindowInfosUpdate.h>
#include <utils/RefBase.h>
namespace android::gui {
class WindowInfosListener : public virtual RefBase {
public:
- virtual void onWindowInfosChanged(const std::vector<WindowInfo>&,
- const std::vector<DisplayInfo>&) = 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 3b4aed442e..684e21ad96 100644
--- a/libs/gui/include/gui/WindowInfosListenerReporter.h
+++ b/libs/gui/include/gui/WindowInfosListenerReporter.h
@@ -17,36 +17,37 @@
#pragma once
#include <android/gui/BnWindowInfosListener.h>
-#include <android/gui/IWindowInfosReportedListener.h>
+#include <android/gui/ISurfaceComposer.h>
+#include <android/gui/IWindowInfosPublisher.h>
#include <binder/IBinder.h>
-#include <gui/ISurfaceComposer.h>
#include <gui/SpHash.h>
#include <gui/WindowInfosListener.h>
+#include <gui/WindowInfosUpdate.h>
#include <unordered_set>
namespace android {
-class ISurfaceComposer;
class WindowInfosListenerReporter : public gui::BnWindowInfosListener {
public:
static sp<WindowInfosListenerReporter> getInstance();
- binder::Status onWindowInfosChanged(const std::vector<gui::WindowInfo>&,
- const std::vector<gui::DisplayInfo>&,
- const sp<gui::IWindowInfosReportedListener>&) override;
-
+ binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update) override;
status_t addWindowInfosListener(
- const sp<gui::WindowInfosListener>& windowInfosListener, const sp<ISurfaceComposer>&,
+ const sp<gui::WindowInfosListener>& windowInfosListener,
+ const sp<gui::ISurfaceComposer>&,
std::pair<std::vector<gui::WindowInfo>, std::vector<gui::DisplayInfo>>* outInitialInfo);
status_t removeWindowInfosListener(const sp<gui::WindowInfosListener>& windowInfosListener,
- const sp<ISurfaceComposer>& surfaceComposer);
- void reconnect(const sp<ISurfaceComposer>&);
+ const sp<gui::ISurfaceComposer>& surfaceComposer);
+ void reconnect(const sp<gui::ISurfaceComposer>&);
private:
std::mutex mListenersMutex;
- std::unordered_set<sp<gui::WindowInfosListener>, SpHash<gui::WindowInfosListener>>
+ std::unordered_set<sp<gui::WindowInfosListener>, gui::SpHash<gui::WindowInfosListener>>
mWindowInfosListeners GUARDED_BY(mListenersMutex);
std::vector<gui::WindowInfo> mLastWindowInfos GUARDED_BY(mListenersMutex);
std::vector<gui::DisplayInfo> mLastDisplayInfos GUARDED_BY(mListenersMutex);
+
+ sp<gui::IWindowInfosPublisher> 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 0000000000..2ca59fb497
--- /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 <binder/Parcelable.h>
+#include <gui/DisplayInfo.h>
+#include <gui/WindowInfo.h>
+
+namespace android::gui {
+
+struct WindowInfosUpdate : public Parcelable {
+ WindowInfosUpdate() {}
+
+ WindowInfosUpdate(std::vector<WindowInfo> windowInfos, std::vector<DisplayInfo> displayInfos,
+ int64_t vsyncId, int64_t timestamp)
+ : windowInfos(std::move(windowInfos)),
+ displayInfos(std::move(displayInfos)),
+ vsyncId(vsyncId),
+ timestamp(timestamp) {}
+
+ std::vector<WindowInfo> windowInfos;
+ std::vector<DisplayInfo> 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/bufferqueue/1.0/WGraphicBufferProducer.h b/libs/gui/include/gui/bufferqueue/1.0/WGraphicBufferProducer.h
index 004d87574a..32dc88ba84 100644
--- a/libs/gui/include/gui/bufferqueue/1.0/WGraphicBufferProducer.h
+++ b/libs/gui/include/gui/bufferqueue/1.0/WGraphicBufferProducer.h
@@ -298,7 +298,7 @@ struct TWGraphicBufferProducer : public BASE {
}
Return<void> getConsumerName(HGraphicBufferProducer::getConsumerName_cb _hidl_cb) override {
- _hidl_cb(mBase->getConsumerName().string());
+ _hidl_cb(mBase->getConsumerName().c_str());
return Void();
}
diff --git a/libs/gui/include/gui/fake/BufferData.h b/libs/gui/include/gui/fake/BufferData.h
new file mode 100644
index 0000000000..725d11c313
--- /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 <gui/LayerState.h>
+
+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 08785b49c1..1c900e9da5 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<nsecs_t>(acquireTimeOrFence));
- ASSERT_EQ(std::get<nsecs_t>(acquireTimeOrFence) > 0,
- mBufferResult == ExpectedResult::Buffer::ACQUIRED)
- << "bad acquire time";
- ASSERT_LE(std::get<nsecs_t>(acquireTimeOrFence), latchTime)
- << "acquire time should be <= latch time";
+ surfaceControlStats;
+
+ nsecs_t acquireTime = -1;
+ if (std::holds_alternative<nsecs_t>(acquireTimeOrFence)) {
+ acquireTime = std::get<nsecs_t>(acquireTimeOrFence);
+ } else {
+ auto fence = std::get<sp<Fence>>(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/gui/view/Surface.h b/libs/gui/include/gui/view/Surface.h
index f7dcbc698d..b7aba2b9dc 100644
--- a/libs/gui/include/gui/view/Surface.h
+++ b/libs/gui/include/gui/view/Surface.h
@@ -24,9 +24,9 @@
#include <binder/IBinder.h>
#include <binder/Parcelable.h>
-namespace android {
+#include <gui/IGraphicBufferProducer.h>
-class IGraphicBufferProducer;
+namespace android {
namespace view {
diff --git a/libs/gui/include/private/gui/ComposerServiceAIDL.h b/libs/gui/include/private/gui/ComposerServiceAIDL.h
index 9a96976c0f..6352a5851c 100644
--- a/libs/gui/include/private/gui/ComposerServiceAIDL.h
+++ b/libs/gui/include/private/gui/ComposerServiceAIDL.h
@@ -20,6 +20,7 @@
#include <sys/types.h>
#include <android/gui/ISurfaceComposer.h>
+#include <ui/DisplayId.h>
#include <utils/Singleton.h>
#include <utils/StrongPointer.h>
@@ -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<gui::ISurfaceComposer> getComposerService();
-
- // the following two methods are moved from ISurfaceComposer.h
- // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic.
- std::optional<PhysicalDisplayId> getInternalDisplayId() const {
- std::vector<int64_t> displayIds;
- binder::Status status = mComposerService->getPhysicalDisplayIds(&displayIds);
- return (!status.isOk() || displayIds.empty())
- ? std::nullopt
- : DisplayId::fromValue<PhysicalDisplayId>(
- static_cast<uint64_t>(displayIds.front()));
- }
-
- // TODO(b/74619554): Remove this stopgap once the framework is display-agnostic.
- sp<IBinder> getInternalDisplayToken() const {
- const auto displayId = getInternalDisplayId();
- if (!displayId) return nullptr;
- sp<IBinder> display;
- binder::Status status =
- mComposerService->getPhysicalDisplayToken(static_cast<int64_t>(displayId->value),
- &display);
- return status.isOk() ? display : nullptr;
- }
};
// ---------------------------------------------------------------------------
diff --git a/libs/gui/libgui_flags.aconfig b/libs/gui/libgui_flags.aconfig
new file mode 100644
index 0000000000..38646992da
--- /dev/null
+++ b/libs/gui/libgui_flags.aconfig
@@ -0,0 +1,18 @@
+package: "com.android.graphics.libgui.flags"
+container: "system"
+
+flag {
+ name: "bq_setframerate"
+ namespace: "core_graphics"
+ description: "This flag controls plumbing setFrameRate thru BufferQueue"
+ bug: "281695725"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "frametimestamps_previousrelease"
+ namespace: "core_graphics"
+ description: "Controls a fence fixup for timestamp apis"
+ bug: "310927247"
+ is_fixed_read_only: true
+}
diff --git a/libs/gui/tests/Android.bp b/libs/gui/tests/Android.bp
index fa54c7d1f6..e606b9941e 100644
--- a/libs/gui/tests/Android.bp
+++ b/libs/gui/tests/Android.bp
@@ -3,6 +3,7 @@
// Build the binary to $(TARGET_OUT_DATA_NATIVE_TESTS)/$(LOCAL_MODULE)
// to integrate with auto-test framework.
package {
+ default_team: "trendy_team_android_core_graphics_stack",
// 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"
@@ -15,17 +16,24 @@ cc_test {
name: "libgui_test",
test_suites: ["device-tests"],
- cflags: [
+ defaults: ["libgui-defaults"],
+
+ cppflags: [
"-Wall",
"-Werror",
+ "-Wno-extra",
+ "-DCOM_ANDROID_GRAPHICS_LIBGUI_FLAGS_BQ_SETFRAMERATE=true",
],
srcs: [
+ "LibGuiMain.cpp", // Custom gtest entrypoint
"BLASTBufferQueue_test.cpp",
"BufferItemConsumer_test.cpp",
"BufferQueue_test.cpp",
+ "CompositorTiming_test.cpp",
"CpuConsumer_test.cpp",
"EndToEndNativeInputTest.cpp",
+ "FrameRateUtilsTest.cpp",
"DisplayInfo_test.cpp",
"DisplayedContentSampling_test.cpp",
"FillBuffer.cpp",
@@ -51,19 +59,12 @@ cc_test {
"android.hardware.configstore@1.0",
"android.hardware.configstore-utils",
"libSurfaceFlingerProp",
- "libbase",
- "liblog",
- "libEGL",
"libGLESv1_CM",
- "libGLESv2",
- "libbinder",
- "libcutils",
- "libgui",
- "libhidlbase",
"libinput",
- "libui",
- "libutils",
- "libnativewindow",
+ ],
+
+ static_libs: [
+ "libgmock",
],
header_libs: ["libsurfaceflinger_headers"],
@@ -90,6 +91,9 @@ cc_test {
compile_multilib: "both",
header_libs: ["libsurfaceflinger_headers"],
+ data: [
+ ":libgui_test",
+ ],
}
cc_test {
diff --git a/libs/gui/tests/AndroidTest.xml b/libs/gui/tests/AndroidTest.xml
index 5e09fff6bb..b4ccf87349 100644
--- a/libs/gui/tests/AndroidTest.xml
+++ b/libs/gui/tests/AndroidTest.xml
@@ -18,11 +18,13 @@
<option name="cleanup" value="true" />
<option name="push" value="libgui_test->/data/local/tmp/libgui_test" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
<option name="screen-always-on" value="on" />
</target_preparer>
<option name="test-suite-tag" value="apct" />
+ <option name="not-shardable" value="true" />
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="libgui_test" />
diff --git a/libs/gui/tests/BLASTBufferQueue_test.cpp b/libs/gui/tests/BLASTBufferQueue_test.cpp
index b993289e6a..ea7078dd05 100644
--- a/libs/gui/tests/BLASTBufferQueue_test.cpp
+++ b/libs/gui/tests/BLASTBufferQueue_test.cpp
@@ -19,6 +19,7 @@
#include <gui/BLASTBufferQueue.h>
#include <android/hardware/graphics/common/1.2/types.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/BufferQueueCore.h>
#include <gui/BufferQueueProducer.h>
#include <gui/FrameTimestamps.h>
@@ -30,16 +31,23 @@
#include <gui/test/CallbackUtils.h>
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
+#include <tests/utils/ScreenshotUtils.h>
#include <ui/DisplayMode.h>
+#include <ui/DisplayState.h>
#include <ui/GraphicBuffer.h>
#include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+#include <ui/Size.h>
#include <ui/Transform.h>
#include <gtest/gtest.h>
+#include <com_android_graphics_libgui_flags.h>
+
using namespace std::chrono_literals;
namespace android {
+using namespace com::android::graphics::libgui;
using Transaction = SurfaceComposerClient::Transaction;
using android::hardware::graphics::common::V1_2::BufferUsage;
@@ -115,15 +123,17 @@ public:
mBlastBufferQueueAdapter->syncNextTransaction(callback, acquireSingleBuffer);
}
- void syncNextTransaction(std::function<void(Transaction*)> callback,
+ bool syncNextTransaction(std::function<void(Transaction*)> 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; }
@@ -172,46 +182,44 @@ private:
class BLASTBufferQueueTest : public ::testing::Test {
public:
protected:
- BLASTBufferQueueTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("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());
- }
-
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);
+
+ mRootSurfaceControl = mClient->createSurface(String8("RootTestSurface"), mDisplayWidth,
+ mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ /*parent*/ nullptr);
+
+ t.setLayerStack(mRootSurfaceControl, ui::DEFAULT_LAYER_STACK)
+ .setLayer(mRootSurfaceControl, std::numeric_limits<int32_t>::max())
+ .show(mRootSurfaceControl)
+ .apply();
mSurfaceControl = mClient->createSurface(String8("TestSurface"), mDisplayWidth,
mDisplayHeight, PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
- t.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
- .setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
- .show(mSurfaceControl)
- .setDataspace(mSurfaceControl, ui::Dataspace::V0_SRGB)
- .apply();
+ /*parent*/ mRootSurfaceControl->getHandle());
- mCaptureArgs.displayToken = mDisplayToken;
- mCaptureArgs.dataspace = ui::Dataspace::V0_SRGB;
+ mCaptureArgs.sourceCrop = Rect(ui::Size(mDisplayWidth, mDisplayHeight));
+ mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
}
void setUpProducer(BLASTBufferQueueHelper& adapter, sp<IGraphicBufferProducer>& producer,
@@ -298,20 +306,6 @@ protected:
captureBuf->unlock();
}
- static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
- ScreenCaptureResults& captureResults) {
- const auto sf = ComposerServiceAIDL::getComposerService();
- SurfaceComposerClient::Transaction().apply(true);
-
- const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
- binder::Status status = sf->captureDisplay(captureArgs, captureListener);
- if (status.transactionError() != NO_ERROR) {
- return status.transactionError();
- }
- captureResults = captureListener->waitForResults();
- return captureResults.result;
- }
-
void queueBuffer(sp<IGraphicBufferProducer> igbp, uint8_t r, uint8_t g, uint8_t b,
nsecs_t presentTimeDelay) {
int slot;
@@ -344,11 +338,12 @@ protected:
sp<IBinder> mDisplayToken;
sp<SurfaceControl> mSurfaceControl;
+ sp<SurfaceControl> mRootSurfaceControl;
uint32_t mDisplayWidth;
uint32_t mDisplayHeight;
- DisplayCaptureArgs mCaptureArgs;
+ LayerCaptureArgs mCaptureArgs;
ScreenCaptureResults mCaptureResults;
sp<CountProducerListener> mProducerListener;
};
@@ -366,7 +361,9 @@ TEST_F(BLASTBufferQueueTest, Update) {
BLASTBufferQueueHelper adapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
sp<SurfaceControl> updateSurface =
mClient->createSurface(String8("UpdateTest"), mDisplayWidth / 2, mDisplayHeight / 2,
- PIXEL_FORMAT_RGBA_8888);
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ /*parent*/ mRootSurfaceControl->getHandle());
adapter.update(updateSurface, mDisplayWidth / 2, mDisplayHeight / 2);
ASSERT_EQ(updateSurface, adapter.getSurfaceControl());
sp<IGraphicBufferProducer> igbProducer;
@@ -449,10 +446,11 @@ TEST_F(BLASTBufferQueueTest, onFrameAvailable_Apply) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -533,9 +531,11 @@ TEST_F(BLASTBufferQueueTest, SetCrop_Item) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
+
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b,
@@ -551,16 +551,6 @@ TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
(mDisplayWidth < mDisplayHeight) ? mDisplayWidth / 2 : mDisplayHeight / 2;
int32_t finalCropSideLength = bufferSideLength / 2;
- auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect);
- ASSERT_NE(nullptr, bg.get());
- Transaction t;
- t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
- .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
- .setColor(bg, half3{0, 0, 0})
- .setLayer(bg, 0)
- .apply();
-
BLASTBufferQueueHelper adapter(mSurfaceControl, bufferSideLength, bufferSideLength);
sp<IGraphicBufferProducer> igbProducer;
setUpProducer(adapter, igbProducer);
@@ -592,9 +582,11 @@ TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
+
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(r, g, b,
{10, 10, (int32_t)bufferSideLength - 10,
(int32_t)bufferSideLength - 10}));
@@ -605,17 +597,6 @@ TEST_F(BLASTBufferQueueTest, SetCrop_ScalingModeScaleCrop) {
}
TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
- // add black background
- auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect);
- ASSERT_NE(nullptr, bg.get());
- Transaction t;
- t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
- .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
- .setColor(bg, half3{0, 0, 0})
- .setLayer(bg, 0)
- .apply();
-
Rect windowSize(1000, 1000);
Rect bufferSize(windowSize);
Rect bufferCrop(200, 200, 700, 700);
@@ -655,9 +636,10 @@ TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
// Verify cropped region is scaled correctly.
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0, {10, 10, 490, 490}));
@@ -672,17 +654,6 @@ TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToBufferSize) {
}
TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToWindowSize) {
- // add black background
- auto bg = mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect);
- ASSERT_NE(nullptr, bg.get());
- Transaction t;
- t.setLayerStack(bg, ui::DEFAULT_LAYER_STACK)
- .setCrop(bg, Rect(0, 0, mDisplayWidth, mDisplayHeight))
- .setColor(bg, half3{0, 0, 0})
- .setLayer(bg, 0)
- .apply();
-
Rect windowSize(1000, 1000);
Rect bufferSize(500, 500);
Rect bufferCrop(100, 100, 350, 350);
@@ -722,9 +693,10 @@ TEST_F(BLASTBufferQueueTest, ScaleCroppedBufferToWindowSize) {
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
// Verify cropped region is scaled correctly.
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(255, 0, 0, {10, 10, 490, 490}));
ASSERT_NO_FATAL_FAILURE(checkScreenCapture(0, 255, 0, {10, 510, 490, 990}));
@@ -769,10 +741,12 @@ TEST_F(BLASTBufferQueueTest, ScalingModeChanges) {
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0,
Fence::NO_FENCE);
igbProducer->queueBuffer(slot, input, &qbOutput);
- adapter.waitForCallbacks();
+
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
}
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b,
@@ -804,10 +778,11 @@ TEST_F(BLASTBufferQueueTest, ScalingModeChanges) {
NATIVE_WINDOW_SCALING_MODE_SCALE_TO_WINDOW,
0, Fence::NO_FENCE);
igbProducer->queueBuffer(slot, input, &qbOutput);
- adapter.waitForCallbacks();
+ // ensure the buffer queue transaction has been committed
+ Transaction().apply(true /* synchronous */);
}
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
// verify we still scale the buffer to the new size (half the screen height)
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b,
@@ -842,12 +817,12 @@ TEST_F(BLASTBufferQueueTest, SyncThenNoSync) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is green
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(0, 255, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
mProducerListener->waitOnNumberReleased(1);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -887,7 +862,7 @@ TEST_F(BLASTBufferQueueTest, MultipleSyncTransactions) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -935,7 +910,7 @@ TEST_F(BLASTBufferQueueTest, MultipleSyncTransactionWithNonSync) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -985,7 +960,7 @@ TEST_F(BLASTBufferQueueTest, MultipleSyncRunOutOfBuffers) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -1043,7 +1018,7 @@ TEST_F(BLASTBufferQueueTest, RunOutOfBuffersWaitingOnSF) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(r, g, b, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -1076,13 +1051,13 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionAcquireMultipleBuffers) {
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is blue
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(0, 0, 255, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
mProducerListener->waitOnNumberReleased(2);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(255, 0, 0, {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
}
@@ -1103,7 +1078,11 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionOverwrite) {
ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
auto callback2 = [](Transaction*) {};
- adapter.syncNextTransaction(callback2);
+ ASSERT_FALSE(adapter.syncNextTransaction(callback2));
+
+ sp<IGraphicBufferProducer> igbProducer;
+ setUpProducer(adapter, igbProducer);
+ queueBuffer(igbProducer, 0, 255, 0, 0);
std::unique_lock<std::mutex> lock(mutex);
if (!receivedCallback) {
@@ -1115,6 +1094,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<std::mutex> lock(mutex);
+ receivedCallback = true;
+ callbackReceivedCv.notify_one();
+ };
+ adapter.syncNextTransaction(callback);
+ ASSERT_NE(nullptr, adapter.getTransactionReadyCallback());
+
+ adapter.clearSyncTransaction();
+
+ sp<IGraphicBufferProducer> igbProducer;
+ setUpProducer(adapter, igbProducer);
+ queueBuffer(igbProducer, 0, 255, 0, 0);
+
+ std::unique_lock<std::mutex> 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;
@@ -1143,9 +1153,10 @@ TEST_F(BLASTBufferQueueTest, SyncNextTransactionDropBuffer) {
CallbackData callbackData;
transactionCallback.getCallbackData(&callbackData);
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(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
@@ -1161,7 +1172,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_DisconnectProducerTest) {
mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
+ /*parent*/ mRootSurfaceControl->getHandle());
Transaction()
.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
.setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
@@ -1186,7 +1197,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_DisconnectProducerTest) {
CallbackData callbackData;
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(255, 0, 0,
{0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -1204,7 +1215,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_UpdateSurfaceControlTest) {
mClient->createSurface(String8("TestSurface"), mDisplayWidth, mDisplayHeight,
PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
- /*parent*/ nullptr);
+ /*parent*/ mRootSurfaceControl->getHandle());
Transaction()
.setLayerStack(mSurfaceControl, ui::DEFAULT_LAYER_STACK)
.setLayer(mSurfaceControl, std::numeric_limits<int32_t>::max())
@@ -1229,7 +1240,7 @@ TEST_F(BLASTBufferQueueTest, DISABLED_UpdateSurfaceControlTest) {
CallbackData callbackData;
transactionCallback.getCallbackData(&callbackData);
// capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_NO_FATAL_FAILURE(
checkScreenCapture(255, 0, 0,
{0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight}));
@@ -1289,43 +1300,6 @@ TEST_F(BLASTBufferQueueTest, QueryNativeWindowQueuesToWindowComposer) {
ASSERT_EQ(queuesToNativeWindow, 1);
}
-// Test a slow producer doesn't hold up a faster producer from the same client. Essentially tests
-// BBQ uses separate transaction queues.
-TEST_F(BLASTBufferQueueTest, OutOfOrderTransactionTest) {
- sp<SurfaceControl> bgSurface =
- mClient->createSurface(String8("BGTest"), 0, 0, PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceBufferState);
- ASSERT_NE(nullptr, bgSurface.get());
- Transaction t;
- t.setLayerStack(bgSurface, ui::DEFAULT_LAYER_STACK)
- .show(bgSurface)
- .setDataspace(bgSurface, ui::Dataspace::V0_SRGB)
- .setLayer(bgSurface, std::numeric_limits<int32_t>::max() - 1)
- .apply();
-
- BLASTBufferQueueHelper slowAdapter(mSurfaceControl, mDisplayWidth, mDisplayHeight);
- sp<IGraphicBufferProducer> slowIgbProducer;
- setUpProducer(slowAdapter, slowIgbProducer);
- nsecs_t presentTimeDelay = std::chrono::nanoseconds(500ms).count();
- queueBuffer(slowIgbProducer, 0 /* r */, 255 /* g */, 0 /* b */, presentTimeDelay);
-
- BLASTBufferQueueHelper fastAdapter(bgSurface, mDisplayWidth, mDisplayHeight);
- sp<IGraphicBufferProducer> fastIgbProducer;
- setUpProducer(fastAdapter, fastIgbProducer);
- uint8_t r = 255;
- uint8_t g = 0;
- uint8_t b = 0;
- queueBuffer(fastIgbProducer, r, g, b, 0 /* presentTimeDelay */);
- fastAdapter.waitForCallbacks();
-
- // capture screen and verify that it is red
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
-
- ASSERT_NO_FATAL_FAILURE(
- checkScreenCapture(r, g, b,
- {0, 0, (int32_t)mDisplayWidth, (int32_t)mDisplayHeight / 2}));
-}
-
TEST_F(BLASTBufferQueueTest, TransformHint) {
// Transform hint is provided to BBQ via the surface control passed by WM
mSurfaceControl->setTransformHint(ui::Transform::ROT_90);
@@ -1398,8 +1372,8 @@ public:
igbProducer->queueBuffer(slot, input, &qbOutput);
ASSERT_NE(ui::Transform::ROT_INVALID, qbOutput.transformHint);
- adapter.waitForCallbacks();
- ASSERT_EQ(NO_ERROR, captureDisplay(mCaptureArgs, mCaptureResults));
+ Transaction().apply(true /* synchronous */);
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
switch (tr) {
case ui::Transform::ROT_0:
@@ -1610,6 +1584,9 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) {
nsecs_t postedTimeB = 0;
setUpAndQueueBuffer(igbProducer, &requestedPresentTimeB, &postedTimeB, &qbOutput, true);
history.applyDelta(qbOutput.frameTimestamps);
+
+ adapter.waitForCallback(2);
+
events = history.getFrame(1);
ASSERT_NE(nullptr, events);
@@ -1619,7 +1596,9 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) {
ASSERT_GE(events->postedTime, postedTimeA);
ASSERT_GE(events->latchTime, postedTimeA);
- ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+ if (flags::frametimestamps_previousrelease()) {
+ ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+ }
ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
ASSERT_NE(nullptr, events->displayPresentFence);
ASSERT_NE(nullptr, events->releaseFence);
@@ -1631,6 +1610,50 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_Basic) {
ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
ASSERT_GE(events->postedTime, postedTimeB);
+ // Now do the same as above with a third buffer, so that timings related to
+ // buffer releases make it back to the first frame.
+ nsecs_t requestedPresentTimeC = 0;
+ nsecs_t postedTimeC = 0;
+ setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
+ history.applyDelta(qbOutput.frameTimestamps);
+
+ adapter.waitForCallback(3);
+
+ // Check the first frame...
+ events = history.getFrame(1);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(1, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeA);
+ ASSERT_GE(events->latchTime, postedTimeA);
+ // Now dequeueReadyTime is valid, because the release timings finally
+ // propaged to queueBuffer()
+ ASSERT_GE(events->dequeueReadyTime, events->latchTime);
+ ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+ ASSERT_NE(nullptr, events->displayPresentFence);
+ ASSERT_NE(nullptr, events->releaseFence);
+
+ // ...and the second
+ events = history.getFrame(2);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(2, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeB);
+ ASSERT_GE(events->latchTime, postedTimeB);
+ if (flags::frametimestamps_previousrelease()) {
+ ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+ }
+ ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+ ASSERT_NE(nullptr, events->displayPresentFence);
+ ASSERT_NE(nullptr, events->releaseFence);
+
+ // ...and finally the third!
+ events = history.getFrame(3);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(3, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeC, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeC);
+
// wait for any callbacks that have not been received
adapter.waitForCallbacks();
}
@@ -1689,7 +1712,45 @@ TEST_F(BLASTFrameEventHistoryTest, FrameEventHistory_DroppedFrame) {
setUpAndQueueBuffer(igbProducer, &requestedPresentTimeC, &postedTimeC, &qbOutput, true);
history.applyDelta(qbOutput.frameTimestamps);
+ adapter.waitForCallback(3);
+
+ // frame number, requestedPresentTime, and postTime should not have changed
+ ASSERT_EQ(1, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeA);
+
+ // a valid latchtime and pre and post composition info should not be set for the dropped frame
+ ASSERT_FALSE(events->hasLatchInfo());
+ ASSERT_FALSE(events->hasDequeueReadyInfo());
+ ASSERT_FALSE(events->hasGpuCompositionDoneInfo());
+ ASSERT_FALSE(events->hasDisplayPresentInfo());
+ ASSERT_FALSE(events->hasReleaseInfo());
+
+ // we should also have gotten values for the presented frame
+ events = history.getFrame(2);
+ ASSERT_NE(nullptr, events);
+ ASSERT_EQ(2, events->frameNumber);
+ ASSERT_EQ(requestedPresentTimeB, events->requestedPresentTime);
+ ASSERT_GE(events->postedTime, postedTimeB);
+ ASSERT_GE(events->latchTime, postedTimeB);
+
+ if (flags::frametimestamps_previousrelease()) {
+ ASSERT_EQ(events->dequeueReadyTime, FrameEvents::TIMESTAMP_PENDING);
+ }
+ ASSERT_NE(nullptr, events->gpuCompositionDoneFence);
+ ASSERT_NE(nullptr, events->displayPresentFence);
+ ASSERT_NE(nullptr, events->releaseFence);
+
+ // Queue another buffer to check for timestamps that came late
+ nsecs_t requestedPresentTimeD = 0;
+ nsecs_t postedTimeD = 0;
+ setUpAndQueueBuffer(igbProducer, &requestedPresentTimeD, &postedTimeD, &qbOutput, true);
+ history.applyDelta(qbOutput.frameTimestamps);
+
+ adapter.waitForCallback(4);
+
// frame number, requestedPresentTime, and postTime should not have changed
+ events = history.getFrame(1);
ASSERT_EQ(1, events->frameNumber);
ASSERT_EQ(requestedPresentTimeA, events->requestedPresentTime);
ASSERT_GE(events->postedTime, postedTimeA);
diff --git a/libs/gui/tests/BufferItemConsumer_test.cpp b/libs/gui/tests/BufferItemConsumer_test.cpp
index fc6551c8e6..6880678050 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<std::mutex> 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 d1208ee5ae..df7739c3fb 100644
--- a/libs/gui/tests/BufferQueue_test.cpp
+++ b/libs/gui/tests/BufferQueue_test.cpp
@@ -17,11 +17,14 @@
#define LOG_TAG "BufferQueue_test"
//#define LOG_NDEBUG 0
+#include "Constants.h"
#include "MockConsumer.h"
#include <gui/BufferItem.h>
+#include <gui/BufferItemConsumer.h>
#include <gui/BufferQueue.h>
#include <gui/IProducerListener.h>
+#include <gui/Surface.h>
#include <ui/GraphicBuffer.h>
@@ -34,32 +37,23 @@
#include <system/window.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <future>
#include <thread>
+#include <com_android_graphics_libgui_flags.h>
+
using namespace std::chrono_literals;
namespace android {
+using namespace com::android::graphics::libgui;
class BufferQueueTest : public ::testing::Test {
public:
protected:
- BufferQueueTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("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(),
- testInfo->name());
- }
-
void GetMinUndequeuedBufferCount(int* bufferCount) {
ASSERT_TRUE(bufferCount != nullptr);
ASSERT_EQ(OK, mProducer->query(NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
@@ -535,7 +529,8 @@ TEST_F(BufferQueueTest, TestGenerationNumbers) {
int slot;
sp<Fence> fence;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
sp<GraphicBuffer> buffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
@@ -578,7 +573,8 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithoutAutoRefresh) {
sp<Fence> fence;
sp<GraphicBuffer> buffer;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
// Queue the buffer
@@ -592,7 +588,9 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithoutAutoRefresh) {
// always the same one and because async mode gets enabled.
int slot;
for (int i = 0; i < 5; i++) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(sharedSlot, slot);
ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
}
@@ -629,7 +627,8 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithAutoRefresh) {
sp<Fence> fence;
sp<GraphicBuffer> buffer;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
// Queue the buffer
@@ -656,7 +655,9 @@ TEST_F(BufferQueueTest, TestSharedBufferModeWithAutoRefresh) {
// always return the same one.
int slot;
for (int i = 0; i < 5; i++) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(sharedSlot, slot);
ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
}
@@ -695,7 +696,8 @@ TEST_F(BufferQueueTest, TestSharedBufferModeUsingAlreadyDequeuedBuffer) {
sp<Fence> fence;
sp<GraphicBuffer> buffer;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&sharedSlot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(sharedSlot, &buffer));
// Enable shared buffer mode
@@ -712,7 +714,9 @@ TEST_F(BufferQueueTest, TestSharedBufferModeUsingAlreadyDequeuedBuffer) {
// always the same one and because async mode gets enabled.
int slot;
for (int i = 0; i < 5; i++) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(sharedSlot, slot);
ASSERT_EQ(OK, mProducer->queueBuffer(sharedSlot, input, &output));
}
@@ -747,7 +751,8 @@ TEST_F(BufferQueueTest, TestTimeouts) {
for (int i = 0; i < 5; ++i) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence = Fence::NO_FENCE;
- auto result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr);
+ auto result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr);
if (i < 2) {
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
result);
@@ -774,7 +779,9 @@ TEST_F(BufferQueueTest, TestTimeouts) {
for (int i = 0; i < 2; ++i) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence = Fence::NO_FENCE;
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
IGraphicBufferProducer::QueueBufferInput input(0ull, true,
HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
@@ -785,7 +792,9 @@ TEST_F(BufferQueueTest, TestTimeouts) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence = Fence::NO_FENCE;
auto startTime = systemTime();
- ASSERT_EQ(TIMED_OUT, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(TIMED_OUT,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_GE(systemTime() - startTime, TIMEOUT);
// We're technically attaching the same buffer multiple times (since we
@@ -806,7 +815,8 @@ TEST_F(BufferQueueTest, CanAttachWhileDisallowingAllocation) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> sourceFence;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &sourceFence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &sourceFence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
sp<GraphicBuffer> buffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
ASSERT_EQ(OK, mProducer->detachBuffer(slot));
@@ -829,7 +839,8 @@ TEST_F(BufferQueueTest, CanRetrieveLastQueuedBuffer) {
int slot = BufferQueue::INVALID_BUFFER_SLOT;
sp<Fence> fence;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
sp<GraphicBuffer> firstBuffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &firstBuffer));
@@ -841,7 +852,8 @@ TEST_F(BufferQueueTest, CanRetrieveLastQueuedBuffer) {
// Dequeue a second buffer
slot = BufferQueue::INVALID_BUFFER_SLOT;
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION,
- mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
sp<GraphicBuffer> secondBuffer;
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &secondBuffer));
@@ -892,8 +904,8 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
int slots[3] = {};
mProducer->setMaxDequeuedBufferCount(3);
for (size_t i = 0; i < 3; ++i) {
- status_t result =
- mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+ TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
}
@@ -906,7 +918,9 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
// The first segment is a two-buffer segment, so we only put one buffer into
// the queue at a time
for (size_t i = 0; i < 5; ++i) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -921,16 +935,22 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
// two-buffer segment, but then at the end, we put two buffers in the queue
// at the same time before draining it.
for (size_t i = 0; i < 5; ++i) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
std::this_thread::sleep_for(16ms);
}
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -945,10 +965,14 @@ TEST_F(BufferQueueTest, TestOccupancyHistory) {
// The third segment is a triple-buffer segment, so the queue is switching
// between one buffer and two buffers deep.
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
for (size_t i = 0; i < 5; ++i) {
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
ASSERT_EQ(OK, mConsumer->releaseBuffer(item.mSlot, item.mFrameNumber,
@@ -1047,8 +1071,8 @@ TEST_F(BufferQueueTest, TestDiscardFreeBuffers) {
int slots[4] = {};
mProducer->setMaxDequeuedBufferCount(4);
for (size_t i = 0; i < 4; ++i) {
- status_t result =
- mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+ TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
}
@@ -1059,14 +1083,22 @@ TEST_F(BufferQueueTest, TestDiscardFreeBuffers) {
// Get buffers in all states: dequeued, filled, acquired, free
// Fill 3 buffers
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
// Dequeue 1 buffer
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
// Acquire and free 1 buffer
ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
@@ -1132,8 +1164,8 @@ TEST_F(BufferQueueTest, TestBufferReplacedInQueueBuffer) {
int slots[2] = {};
ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
for (size_t i = 0; i < 2; ++i) {
- status_t result =
- mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slots[i], &fence, 0, 0, 0,
+ TEST_PRODUCER_USAGE_BITS, nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slots[i], &buffer));
}
@@ -1143,14 +1175,88 @@ TEST_F(BufferQueueTest, TestBufferReplacedInQueueBuffer) {
// Fill 2 buffers without consumer consuming them. Verify that all
// queued buffer returns proper bufferReplaced flag
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(false, output.bufferReplaced);
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
ASSERT_EQ(true, output.bufferReplaced);
}
+struct BufferDetachedListener : public BnProducerListener {
+public:
+ BufferDetachedListener() = default;
+ virtual ~BufferDetachedListener() = default;
+
+ virtual void onBufferReleased() {}
+ virtual bool needsReleaseNotify() { return true; }
+ virtual void onBufferDetached(int slot) {
+ mDetachedSlots.push_back(slot);
+ }
+ const std::vector<int>& getDetachedSlots() const { return mDetachedSlots; }
+private:
+ std::vector<int> mDetachedSlots;
+};
+
+TEST_F(BufferQueueTest, TestConsumerDetachProducerListener) {
+ createBufferQueue();
+ sp<MockConsumer> mc(new MockConsumer);
+ ASSERT_EQ(OK, mConsumer->consumerConnect(mc, true));
+ IGraphicBufferProducer::QueueBufferOutput output;
+ sp<BufferDetachedListener> pl(new BufferDetachedListener);
+ ASSERT_EQ(OK, mProducer->connect(pl, NATIVE_WINDOW_API_CPU, true, &output));
+ ASSERT_EQ(OK, mProducer->setDequeueTimeout(0));
+ ASSERT_EQ(OK, mConsumer->setMaxAcquiredBufferCount(1));
+
+ sp<Fence> fence = Fence::NO_FENCE;
+ sp<GraphicBuffer> buffer = nullptr;
+ IGraphicBufferProducer::QueueBufferInput input(0ull, true,
+ HAL_DATASPACE_UNKNOWN, Rect::INVALID_RECT,
+ NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
+
+ int slots[2] = {};
+ status_t result = OK;
+ ASSERT_EQ(OK, mProducer->setMaxDequeuedBufferCount(2));
+
+ result = mProducer->dequeueBuffer(&slots[0], &fence, 0, 0, 0,
+ GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+ ASSERT_EQ(OK, mProducer->requestBuffer(slots[0], &buffer));
+
+ result = mProducer->dequeueBuffer(&slots[1], &fence, 0, 0, 0,
+ GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+ ASSERT_EQ(OK, mProducer->requestBuffer(slots[1], &buffer));
+
+ // Queue & detach one from two dequeued buffes.
+ ASSERT_EQ(OK, mProducer->queueBuffer(slots[1], input, &output));
+ BufferItem item{};
+ ASSERT_EQ(OK, mConsumer->acquireBuffer(&item, 0));
+ ASSERT_EQ(OK, mConsumer->detachBuffer(item.mSlot));
+
+ // Check whether the slot from IProducerListener is same to the detached slot.
+ ASSERT_EQ(pl->getDetachedSlots().size(), 1);
+ ASSERT_EQ(pl->getDetachedSlots()[0], slots[1]);
+
+ // Dequeue another buffer.
+ int slot;
+ result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0,
+ GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+ ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
+ ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
+
+ // Dequeue should fail here, since we dequeued 3 buffers and one buffer was
+ // detached from consumer(Two buffers are dequeued, and the current max
+ // dequeued buffer count is two).
+ result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0,
+ GRALLOC_USAGE_SW_READ_RARELY, nullptr, nullptr);
+ ASSERT_TRUE(result == WOULD_BLOCK || result == TIMED_OUT || result == INVALID_OPERATION);
+}
+
TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
createBufferQueue();
sp<MockConsumer> mc(new MockConsumer);
@@ -1167,7 +1273,8 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
NATIVE_WINDOW_SCALING_MODE_FREEZE, 0, Fence::NO_FENCE);
// Dequeue, request, and queue one buffer
- status_t result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr);
+ status_t result = mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS,
+ nullptr, nullptr);
ASSERT_EQ(IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION, result);
ASSERT_EQ(OK, mProducer->requestBuffer(slot, &buffer));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
@@ -1182,7 +1289,9 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
// Dequeue and queue the buffer again
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
// Acquire and release the buffer again. Upon acquiring, the buffer handle
@@ -1194,7 +1303,9 @@ TEST_F(BufferQueueTest, TestStaleBufferHandleSentAfterDisconnect) {
EGL_NO_DISPLAY, EGL_NO_SYNC_KHR, Fence::NO_FENCE));
// Dequeue and queue the buffer again
- ASSERT_EQ(OK, mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, 0, nullptr, nullptr));
+ ASSERT_EQ(OK,
+ mProducer->dequeueBuffer(&slot, &fence, 0, 0, 0, TEST_PRODUCER_USAGE_BITS, nullptr,
+ nullptr));
ASSERT_EQ(OK, mProducer->queueBuffer(slot, input, &output));
// Disconnect the producer end. This should clear all of the slots and mark
@@ -1224,4 +1335,109 @@ TEST_F(BufferQueueTest, TestProducerConnectDisconnect) {
ASSERT_EQ(NO_INIT, mProducer->disconnect(NATIVE_WINDOW_API_CPU));
}
+TEST_F(BufferQueueTest, TestBqSetFrameRateFlagBuildTimeIsSet) {
+ ASSERT_EQ(flags::bq_setframerate(), COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE));
+}
+
+struct BufferItemConsumerSetFrameRateListener : public BufferItemConsumer {
+ BufferItemConsumerSetFrameRateListener(const sp<IGraphicBufferConsumer>& consumer)
+ : BufferItemConsumer(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 1) {}
+
+ MOCK_METHOD(void, onSetFrameRate, (float, int8_t, int8_t), (override));
+};
+
+TEST_F(BufferQueueTest, TestSetFrameRate) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<BufferItemConsumerSetFrameRateListener> bufferConsumer =
+ sp<BufferItemConsumerSetFrameRateListener>::make(consumer);
+
+ EXPECT_CALL(*bufferConsumer, onSetFrameRate(12.34f, 1, 0)).Times(1);
+ producer->setFrameRate(12.34f, 1, 0);
+}
+
+class Latch {
+public:
+ explicit Latch(int expected) : mExpected(expected) {}
+ Latch(const Latch&) = delete;
+ Latch& operator=(const Latch&) = delete;
+
+ void CountDown() {
+ std::unique_lock<std::mutex> lock(mLock);
+ mExpected--;
+ if (mExpected <= 0) {
+ mCV.notify_all();
+ }
+ }
+
+ void Wait() {
+ std::unique_lock<std::mutex> lock(mLock);
+ mCV.wait(lock, [&] { return mExpected == 0; });
+ }
+
+private:
+ int mExpected;
+ std::mutex mLock;
+ std::condition_variable mCV;
+};
+
+struct OneshotOnDequeuedListener final : public BufferItemConsumer::FrameAvailableListener {
+ OneshotOnDequeuedListener(std::function<void()>&& oneshot)
+ : mOneshotRunnable(std::move(oneshot)) {}
+
+ std::function<void()> mOneshotRunnable;
+
+ void run() {
+ if (mOneshotRunnable) {
+ mOneshotRunnable();
+ mOneshotRunnable = nullptr;
+ }
+ }
+
+ void onFrameDequeued(const uint64_t) override { run(); }
+
+ void onFrameAvailable(const BufferItem&) override {}
+};
+
+// See b/270004534
+TEST(BufferQueueThreading, TestProducerDequeueConsumerDestroy) {
+ sp<IGraphicBufferProducer> producer;
+ sp<IGraphicBufferConsumer> consumer;
+ BufferQueue::createBufferQueue(&producer, &consumer);
+
+ sp<BufferItemConsumer> bufferConsumer =
+ sp<BufferItemConsumer>::make(consumer, GRALLOC_USAGE_SW_READ_OFTEN, 2);
+ ASSERT_NE(nullptr, bufferConsumer.get());
+ sp<Surface> surface = sp<Surface>::make(producer);
+ native_window_set_buffers_format(surface.get(), PIXEL_FORMAT_RGBA_8888);
+ native_window_set_buffers_dimensions(surface.get(), 100, 100);
+
+ Latch triggerDisconnect(1);
+ Latch resumeCallback(1);
+ auto luckyListener = sp<OneshotOnDequeuedListener>::make([&]() {
+ triggerDisconnect.CountDown();
+ resumeCallback.Wait();
+ });
+ bufferConsumer->setFrameAvailableListener(luckyListener);
+
+ std::future<void> disconnecter = std::async(std::launch::async, [&]() {
+ triggerDisconnect.Wait();
+ luckyListener = nullptr;
+ bufferConsumer = nullptr;
+ resumeCallback.CountDown();
+ });
+
+ std::future<void> render = std::async(std::launch::async, [=]() {
+ ANativeWindow_Buffer buffer;
+ surface->lock(&buffer, nullptr);
+ surface->unlockAndPost();
+ });
+
+ ASSERT_EQ(std::future_status::ready, render.wait_for(1s));
+ EXPECT_EQ(nullptr, luckyListener.get());
+ EXPECT_EQ(nullptr, bufferConsumer.get());
+}
+
} // namespace android
diff --git a/libs/gui/tests/CompositorTiming_test.cpp b/libs/gui/tests/CompositorTiming_test.cpp
new file mode 100644
index 0000000000..d8bb21d582
--- /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 <gtest/gtest.h>
+#include <gui/CompositorTiming.h>
+
+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/Constants.h b/libs/gui/tests/Constants.h
new file mode 100644
index 0000000000..85c0f9faab
--- /dev/null
+++ b/libs/gui/tests/Constants.h
@@ -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.
+ */
+
+#pragma once
+
+#include <hardware/gralloc.h>
+
+// Arbitrary non-zero usage flag.
+constexpr uint64_t TEST_PRODUCER_USAGE_BITS = GRALLOC_USAGE_SW_READ_RARELY;
diff --git a/libs/gui/tests/CpuConsumer_test.cpp b/libs/gui/tests/CpuConsumer_test.cpp
index 00e32d9124..d80bd9c62a 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 parameterized 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<CpuConsumer::LockedBuffer> 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 da88463d63..29eeaa806f 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,20 +45,21 @@ 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);
CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connected, 0);
+ CHECK_OFFSET(DisplayEventReceiver::Event::Hotplug, connectionError, 4);
CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, modeId, 0);
CHECK_OFFSET(DisplayEventReceiver::Event::ModeChange, vsyncPeriod, 8);
diff --git a/libs/gui/tests/DisplayedContentSampling_test.cpp b/libs/gui/tests/DisplayedContentSampling_test.cpp
index b647aaba8f..0a2750a4dd 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 2637f59b5e..d4b8dbeeb9 100644
--- a/libs/gui/tests/EndToEndNativeInputTest.cpp
+++ b/libs/gui/tests/EndToEndNativeInputTest.cpp
@@ -24,6 +24,7 @@
#include <memory>
+#include <android/gui/BnWindowInfosReportedListener.h>
#include <android/keycodes.h>
#include <android/native_window.h>
@@ -74,6 +75,26 @@ sp<IInputFlinger> getInputFlinger() {
static const int LAYER_BASE = INT32_MAX - 10;
static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 5s;
+class SynchronousWindowInfosReportedListener : public gui::BnWindowInfosReportedListener {
+public:
+ binder::Status onWindowInfosReported() override {
+ std::lock_guard<std::mutex> lock{mMutex};
+ mWindowInfosReported = true;
+ mConditionVariable.notify_one();
+ return binder::Status::ok();
+ }
+
+ void wait() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mConditionVariable.wait(lock, [&] { return mWindowInfosReported; });
+ }
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mConditionVariable;
+ bool mWindowInfosReported{false};
+};
+
class InputSurface {
public:
InputSurface(const sp<SurfaceControl> &sc, int width, int height, bool noInputChannel = false) {
@@ -164,7 +185,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<FocusEvent *>(ev);
EXPECT_EQ(hasFocus, focusEvent->getHasFocus());
}
@@ -172,7 +193,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<MotionEvent*>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
EXPECT_EQ(x, mev->getX(0));
@@ -181,7 +202,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<MotionEvent*>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
@@ -190,7 +211,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<MotionEvent *>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
EXPECT_EQ(x, mev->getX(0));
@@ -199,7 +220,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<MotionEvent *>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
EXPECT_EQ(flags, mev->getFlags() & flags);
@@ -208,7 +229,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<MotionEvent *>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, mev->getAction());
const PointerCoords &coords = *mev->getRawPointerCoords(0 /*pointerIndex*/);
@@ -218,7 +239,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<MotionEvent *>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_UP, mev->getAction());
EXPECT_EQ(0, mev->getFlags() & VERIFIED_MOTION_EVENT_FLAGS);
@@ -227,7 +248,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<KeyEvent *>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, keyEvent->getAction());
EXPECT_EQ(keycode, keyEvent->getKeyCode());
@@ -235,7 +256,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<KeyEvent *>(ev);
EXPECT_EQ(AMOTION_EVENT_ACTION_UP, keyEvent->getAction());
EXPECT_EQ(keycode, keyEvent->getKeyCode());
@@ -264,7 +285,10 @@ public:
t.setPosition(mSurfaceControl, x, y);
t.setCrop(mSurfaceControl, crop);
t.setAlpha(mSurfaceControl, 1);
- t.apply(true);
+ auto reportedListener = sp<SynchronousWindowInfosReportedListener>::make();
+ t.addWindowInfosReportedListener(reportedListener);
+ t.apply();
+ reportedListener->wait();
}
void requestFocus(int displayId = ADISPLAY_ID_DEFAULT) {
@@ -272,8 +296,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 +382,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;
@@ -370,7 +394,7 @@ public:
// After a new buffer is queued, SurfaceFlinger is notified and will
// latch the new buffer on next vsync. Let's heuristically wait for 3
// vsyncs.
- mBufferPostDelay = static_cast<int32_t>(1e6 / mode.refreshRate) * 3;
+ mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
}
void TearDown() {
@@ -510,6 +534,22 @@ TEST_F(InputSurfacesTest, input_respects_surface_insets) {
bgSurface->expectTap(1, 1);
}
+TEST_F(InputSurfacesTest, input_respects_surface_insets_with_replaceTouchableRegionWithCrop) {
+ std::unique_ptr<InputSurface> bgSurface = makeSurface(100, 100);
+ std::unique_ptr<InputSurface> 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<InputSurface> parentSurface = makeSurface(100, 100);
@@ -612,7 +652,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.
@@ -805,7 +845,7 @@ TEST_F(InputSurfacesTest, touch_flag_obscured) {
// with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED
std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- nonTouchableSurface->mInputInfo.ownerUid = 22222;
+ nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222};
// Overriding occlusion mode otherwise the touch would be discarded at InputDispatcher by
// the default obscured/untrusted touch filter introduced in S.
nonTouchableSurface->mInputInfo.touchOcclusionMode = TouchOcclusionMode::ALLOW;
@@ -826,8 +866,8 @@ TEST_F(InputSurfacesTest, touch_flag_partially_obscured_with_crop) {
std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- nonTouchableSurface->mInputInfo.ownerUid = 22222;
- parentSurface->mInputInfo.ownerUid = 22222;
+ nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ parentSurface->mInputInfo.ownerUid = gui::Uid{22222};
nonTouchableSurface->showAt(0, 0);
parentSurface->showAt(100, 100);
@@ -850,8 +890,8 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_crop) {
std::unique_ptr<InputSurface> nonTouchableSurface = makeSurface(100, 100);
nonTouchableSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
parentSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- nonTouchableSurface->mInputInfo.ownerUid = 22222;
- parentSurface->mInputInfo.ownerUid = 22222;
+ nonTouchableSurface->mInputInfo.ownerUid = gui::Uid{22222};
+ parentSurface->mInputInfo.ownerUid = gui::Uid{22222};
nonTouchableSurface->showAt(0, 0);
parentSurface->showAt(50, 50);
@@ -870,7 +910,7 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_bql) {
std::unique_ptr<InputSurface> bufferSurface =
InputSurface::makeBufferInputSurface(mComposerClient, 0, 0);
bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- bufferSurface->mInputInfo.ownerUid = 22222;
+ bufferSurface->mInputInfo.ownerUid = gui::Uid{22222};
surface->showAt(10, 10);
bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
@@ -885,7 +925,7 @@ TEST_F(InputSurfacesTest, touch_not_obscured_with_zero_sized_blast) {
std::unique_ptr<BlastInputSurface> bufferSurface =
BlastInputSurface::makeBlastInputSurface(mComposerClient, 0, 0);
bufferSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- bufferSurface->mInputInfo.ownerUid = 22222;
+ bufferSurface->mInputInfo.ownerUid = gui::Uid{22222};
surface->showAt(10, 10);
bufferSurface->showAt(50, 50, Rect::EMPTY_RECT);
@@ -932,13 +972,13 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_scaled_without_crop_window) {
TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) {
std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- surface->mInputInfo.ownerUid = 11111;
+ surface->mInputInfo.ownerUid = gui::Uid{11111};
surface->doTransaction(
[&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
surface->showAt(100, 100);
std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- obscuringSurface->mInputInfo.ownerUid = 22222;
+ obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
obscuringSurface->showAt(100, 100);
injectTap(101, 101);
EXPECT_EQ(surface->consumeEvent(100), nullptr);
@@ -951,13 +991,13 @@ TEST_F(InputSurfacesTest, strict_unobscured_input_obscured_window) {
TEST_F(InputSurfacesTest, strict_unobscured_input_partially_obscured_window) {
std::unique_ptr<InputSurface> surface = makeSurface(100, 100);
- surface->mInputInfo.ownerUid = 11111;
+ surface->mInputInfo.ownerUid = gui::Uid{11111};
surface->doTransaction(
[&](auto &t, auto &sc) { t.setDropInputMode(sc, gui::DropInputMode::OBSCURED); });
surface->showAt(100, 100);
std::unique_ptr<InputSurface> obscuringSurface = makeSurface(100, 100);
obscuringSurface->mInputInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, true);
- obscuringSurface->mInputInfo.ownerUid = 22222;
+ obscuringSurface->mInputInfo.ownerUid = gui::Uid{22222};
obscuringSurface->showAt(190, 190);
injectTap(101, 101);
@@ -1219,32 +1259,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<InputSurface> 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/FrameRateUtilsTest.cpp b/libs/gui/tests/FrameRateUtilsTest.cpp
new file mode 100644
index 0000000000..5fe22b05f9
--- /dev/null
+++ b/libs/gui/tests/FrameRateUtilsTest.cpp
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <gui/FrameRateUtils.h>
+#include <inttypes.h>
+#include <system/window.h>
+
+#include <com_android_graphics_libgui_flags.h>
+
+namespace android {
+using namespace com::android::graphics::libgui;
+
+TEST(FrameRateUtilsTest, ValidateFrameRate) {
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ // Privileged APIs.
+ EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ constexpr bool kPrivileged = true;
+ EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+ kPrivileged));
+ EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
+ kPrivileged));
+
+ // Invalid frame rate.
+ EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ // Invalid compatibility.
+ EXPECT_FALSE(
+ ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+ EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
+
+ // Invalid change frame rate strategy.
+ if (flags::bq_setframerate()) {
+ EXPECT_FALSE(
+ ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, -1, ""));
+ EXPECT_FALSE(
+ ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT, 2, ""));
+ }
+}
+
+} // namespace android
diff --git a/libs/gui/tests/GLTest.cpp b/libs/gui/tests/GLTest.cpp
index a1405fcb11..40af8e845a 100644
--- a/libs/gui/tests/GLTest.cpp
+++ b/libs/gui/tests/GLTest.cpp
@@ -29,10 +29,6 @@ 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());
-
mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
ASSERT_EQ(EGL_SUCCESS, eglGetError());
ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);
@@ -132,10 +128,6 @@ void GLTest::TearDown() {
eglTerminate(mEglDisplay);
}
ASSERT_EQ(EGL_SUCCESS, eglGetError());
-
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("End test: %s.%s", testInfo->test_case_name(), testInfo->name());
}
EGLint const* GLTest::getConfigAttribs() {
@@ -185,31 +177,31 @@ EGLSurface GLTest::createWindowSurface(EGLDisplay display, EGLConfig config,
while ((err = glGetError()) != GL_NO_ERROR) {
msg += String8::format(", %#x", err);
}
- return ::testing::AssertionFailure(::testing::Message(msg.string()));
+ return ::testing::AssertionFailure(::testing::Message(msg.c_str()));
}
if (r >= 0 && abs(r - int(pixel[0])) > tolerance) {
msg += String8::format("r(%d isn't %d)", pixel[0], r);
}
if (g >= 0 && abs(g - int(pixel[1])) > tolerance) {
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += " ";
}
msg += String8::format("g(%d isn't %d)", pixel[1], g);
}
if (b >= 0 && abs(b - int(pixel[2])) > tolerance) {
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += " ";
}
msg += String8::format("b(%d isn't %d)", pixel[2], b);
}
if (a >= 0 && abs(a - int(pixel[3])) > tolerance) {
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += " ";
}
msg += String8::format("a(%d isn't %d)", pixel[3], a);
}
- if (!msg.isEmpty()) {
- return ::testing::AssertionFailure(::testing::Message(msg.string()));
+ if (!msg.empty()) {
+ return ::testing::AssertionFailure(::testing::Message(msg.c_str()));
} else {
return ::testing::AssertionSuccess();
}
@@ -223,29 +215,29 @@ EGLSurface GLTest::createWindowSurface(EGLDisplay display, EGLConfig config,
msg += String8::format("left(%d isn't %d)", r1.left, r2.left);
}
if (abs(r1.top - r2.top) > tolerance) {
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += " ";
}
msg += String8::format("top(%d isn't %d)", r1.top, r2.top);
}
if (abs(r1.right - r2.right) > tolerance) {
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += " ";
}
msg += String8::format("right(%d isn't %d)", r1.right, r2.right);
}
if (abs(r1.bottom - r2.bottom) > tolerance) {
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += " ";
}
msg += String8::format("bottom(%d isn't %d)", r1.bottom, r2.bottom);
}
- if (!msg.isEmpty()) {
+ if (!msg.empty()) {
msg += String8::format(" R1: [%d %d %d %d] R2: [%d %d %d %d]",
r1.left, r1.top, r1.right, r1.bottom,
r2.left, r2.top, r2.right, r2.bottom);
- fprintf(stderr, "assertRectEq: %s\n", msg.string());
- return ::testing::AssertionFailure(::testing::Message(msg.string()));
+ fprintf(stderr, "assertRectEq: %s\n", msg.c_str());
+ return ::testing::AssertionFailure(::testing::Message(msg.c_str()));
} else {
return ::testing::AssertionSuccess();
}
diff --git a/libs/gui/tests/IGraphicBufferProducer_test.cpp b/libs/gui/tests/IGraphicBufferProducer_test.cpp
index 3427731fff..b1f5d083c7 100644
--- a/libs/gui/tests/IGraphicBufferProducer_test.cpp
+++ b/libs/gui/tests/IGraphicBufferProducer_test.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "IGraphicBufferProducer_test"
//#define LOG_NDEBUG 0
+#include "Constants.h"
#include "MockConsumer.h"
#include <gtest/gtest.h>
@@ -40,7 +41,6 @@
#define TEST_API NATIVE_WINDOW_API_CPU
#define TEST_API_OTHER NATIVE_WINDOW_API_EGL // valid API that's not TEST_API
#define TEST_CONTROLLED_BY_APP false
-#define TEST_PRODUCER_USAGE_BITS (0)
namespace android {
@@ -82,11 +82,6 @@ protected:
IGraphicBufferProducerTest() {}
virtual void SetUp() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
-
mMC = new MockConsumer;
switch (GetParam()) {
@@ -111,13 +106,6 @@ protected:
ASSERT_OK(mConsumer->consumerConnect(mMC, /*controlledByApp*/ false));
}
- virtual void TearDown() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
- }
-
status_t TryConnectProducer() {
IGraphicBufferProducer::QueueBufferOutput output;
return mProducer->connect(TEST_TOKEN,
diff --git a/libs/gui/tests/LibGuiMain.cpp b/libs/gui/tests/LibGuiMain.cpp
new file mode 100644
index 0000000000..10f7207588
--- /dev/null
+++ b/libs/gui/tests/LibGuiMain.cpp
@@ -0,0 +1,38 @@
+/*
+ * 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 "gtest/gtest.h"
+#include "log/log.h"
+
+namespace {
+
+class TestCaseLogger : public ::testing::EmptyTestEventListener {
+ void OnTestStart(const ::testing::TestInfo& testInfo) override {
+ ALOGD("Begin test: %s#%s", testInfo.test_suite_name(), testInfo.name());
+ }
+
+ void OnTestEnd(const testing::TestInfo& testInfo) override {
+ ALOGD("End test: %s#%s", testInfo.test_suite_name(), testInfo.name());
+ }
+};
+
+} // namespace
+
+int main(int argc, char** argv) {
+ testing::InitGoogleTest(&argc, argv);
+ testing::UnitTest::GetInstance()->listeners().Append(new TestCaseLogger());
+ return RUN_ALL_TESTS();
+} \ No newline at end of file
diff --git a/libs/gui/tests/Malicious.cpp b/libs/gui/tests/Malicious.cpp
index 58d7cc6f35..376420c42b 100644
--- a/libs/gui/tests/Malicious.cpp
+++ b/libs/gui/tests/Malicious.cpp
@@ -151,7 +151,6 @@ TEST(Malicious, Bug36991414Max) {
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -165,7 +164,6 @@ TEST(Malicious, Bug36991414Min) {
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -179,7 +177,6 @@ TEST(Malicious, Bug36991414NegativeOne) {
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
@@ -193,7 +190,6 @@ TEST(Malicious, Bug36991414NumSlots) {
sp<MaliciousBQP> malicious = getMaliciousBQP();
sp<Surface> surface = new Surface(malicious);
- ASSERT_EQ(NO_ERROR, surface->connect(NATIVE_WINDOW_API_CPU, nullptr, false));
ANativeWindow_Buffer buffer;
ASSERT_EQ(NO_ERROR, surface->lock(&buffer, nullptr));
ASSERT_EQ(NO_ERROR, surface->unlockAndPost());
diff --git a/libs/gui/tests/OWNERS b/libs/gui/tests/OWNERS
new file mode 100644
index 0000000000..48cd30d39d
--- /dev/null
+++ b/libs/gui/tests/OWNERS
@@ -0,0 +1,6 @@
+# Android > Android OS & Apps > Framework (Java + Native) > Window Manager > Surfaces
+# Bug component: 316245 = per-file BLASTBufferQueue_test.cpp, DisplayInfo_test.cpp, EndToEndNativeInputTest.cpp, WindowInfos_test.cpp
+# Buganizer template url: https://b.corp.google.com/issues/new?component=316245&template=1018194 = per-file BLASTBufferQueue_test.cpp, DisplayInfo_test.cpp, EndToEndNativeInputTest.cpp, WindowInfos_test.cpp
+
+# Android > Android OS & Apps > graphics > Core Graphics Stack (CoGS)
+# Bug component: 1075130
diff --git a/libs/gui/tests/RegionSampling_test.cpp b/libs/gui/tests/RegionSampling_test.cpp
index c9106bed4c..b18b544257 100644
--- a/libs/gui/tests/RegionSampling_test.cpp
+++ b/libs/gui/tests/RegionSampling_test.cpp
@@ -19,14 +19,16 @@
#include <android/gui/BnRegionSamplingListener.h>
#include <binder/ProcessState.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/DisplayEventReceiver.h>
#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
-#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
#include <utils/Looper.h>
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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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<Listener> 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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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<ISurfaceComposer> composer = ComposerService::getComposerService();
+ sp<gui::ISurfaceComposer> composer = ComposerServiceAIDL::getComposerService();
sp<Listener> 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 a083a228a6..f98437b4f8 100644
--- a/libs/gui/tests/SamplingDemo.cpp
+++ b/libs/gui/tests/SamplingDemo.cpp
@@ -26,7 +26,7 @@
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <gui/SurfaceControl.h>
-#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
#include <utils/Trace.h>
using namespace std::chrono_literals;
@@ -121,10 +121,22 @@ int main(int, const char**) {
const Rect backButtonArea{200, 1606, 248, 1654};
sp<android::Button> backButton = new android::Button("BackButton", backButtonArea);
- sp<ISurfaceComposer> 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<gui::ISurfaceComposer> 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 b65cddaea3..f34b03eade 100644
--- a/libs/gui/tests/StreamSplitter_test.cpp
+++ b/libs/gui/tests/StreamSplitter_test.cpp
@@ -30,23 +30,7 @@
namespace android {
-class StreamSplitterTest : public ::testing::Test {
-
-protected:
- StreamSplitterTest() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("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(),
- testInfo->name());
- }
-};
+class StreamSplitterTest : public ::testing::Test {};
struct FakeListener : public BnConsumerListener {
virtual void onFrameAvailable(const BufferItem& /* item */) {}
diff --git a/libs/gui/tests/SurfaceTextureClient_test.cpp b/libs/gui/tests/SurfaceTextureClient_test.cpp
index c7458a3755..b28dca8ab4 100644
--- a/libs/gui/tests/SurfaceTextureClient_test.cpp
+++ b/libs/gui/tests/SurfaceTextureClient_test.cpp
@@ -40,11 +40,6 @@ protected:
}
virtual void SetUp() {
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("Begin test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
-
sp<IGraphicBufferProducer> producer;
sp<IGraphicBufferConsumer> consumer;
BufferQueue::createBufferQueue(&producer, &consumer);
@@ -96,11 +91,6 @@ protected:
eglDestroyContext(mEglDisplay, mEglContext);
eglDestroySurface(mEglDisplay, mEglSurface);
eglTerminate(mEglDisplay);
-
- const ::testing::TestInfo* const testInfo =
- ::testing::UnitTest::GetInstance()->current_test_info();
- ALOGV("End test: %s.%s", testInfo->test_case_name(),
- testInfo->name());
}
virtual EGLint const* getConfigAttribs() {
diff --git a/libs/gui/tests/SurfaceTextureFBO_test.cpp b/libs/gui/tests/SurfaceTextureFBO_test.cpp
index f34561f668..ccd0e59616 100644
--- a/libs/gui/tests/SurfaceTextureFBO_test.cpp
+++ b/libs/gui/tests/SurfaceTextureFBO_test.cpp
@@ -59,7 +59,7 @@ TEST_F(SurfaceTextureFBOTest, BlitFromCpuFilledBufferToFbo) {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
for (int i = 0; i < 4; i++) {
- SCOPED_TRACE(String8::format("frame %d", i).string());
+ SCOPED_TRACE(String8::format("frame %d", i).c_str());
ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(mANW.get(),
&anb));
diff --git a/libs/gui/tests/SurfaceTextureGL.h b/libs/gui/tests/SurfaceTextureGL.h
index 53eb68cad8..9d8af5d0f5 100644
--- a/libs/gui/tests/SurfaceTextureGL.h
+++ b/libs/gui/tests/SurfaceTextureGL.h
@@ -17,6 +17,7 @@
#ifndef ANDROID_SURFACE_TEXTURE_GL_H
#define ANDROID_SURFACE_TEXTURE_GL_H
+#include "Constants.h"
#include "GLTest.h"
#include "FrameWaiter.h"
@@ -43,6 +44,7 @@ protected:
true, false);
mSTC = new Surface(producer);
mANW = mSTC;
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(), TEST_PRODUCER_USAGE_BITS));
mTextureRenderer = new TextureRenderer(TEX_ID, mST);
ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
mFW = new FrameWaiter;
diff --git a/libs/gui/tests/SurfaceTextureGL_test.cpp b/libs/gui/tests/SurfaceTextureGL_test.cpp
index e2b4f3d035..f76c0be265 100644
--- a/libs/gui/tests/SurfaceTextureGL_test.cpp
+++ b/libs/gui/tests/SurfaceTextureGL_test.cpp
@@ -147,8 +147,9 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BufferWithCrop) {
for (int i = 0; i < 5; i++) {
const android_native_rect_t& crop(crops[i]);
- SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }",
- crop.left, crop.top, crop.right, crop.bottom).string());
+ SCOPED_TRACE(String8::format("rect{ l: %d t: %d r: %d b: %d }", crop.left, crop.top,
+ crop.right, crop.bottom)
+ .c_str());
ASSERT_EQ(NO_ERROR, native_window_set_crop(mANW.get(), &crop));
@@ -308,7 +309,7 @@ TEST_F(SurfaceTextureGLTest, TexturingFromCpuFilledYV12BuffersRepeatedly) {
mFW->waitForFrame();
for (int i = 0; i < numFrames; i++) {
- SCOPED_TRACE(String8::format("frame %d", i).string());
+ SCOPED_TRACE(String8::format("frame %d", i).c_str());
// We must wait for each frame to come in because if we ever do an
// updateTexImage call that doesn't consume a newly available buffer
diff --git a/libs/gui/tests/Surface_test.cpp b/libs/gui/tests/Surface_test.cpp
index 065cd7a5c7..c6ea317949 100644
--- a/libs/gui/tests/Surface_test.cpp
+++ b/libs/gui/tests/Surface_test.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include "Constants.h"
#include "MockConsumer.h"
#include <gtest/gtest.h>
@@ -24,6 +25,7 @@
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <binder/ProcessState.h>
#include <configstore/Utils.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/BufferItemConsumer.h>
#include <gui/IProducerListener.h>
#include <gui/ISurfaceComposer.h>
@@ -147,6 +149,7 @@ protected:
/*listener*/listener));
const int BUFFER_COUNT = 4 + extraDiscardedBuffers;
ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
ANativeWindowBuffer* buffers[BUFFER_COUNT];
// Dequeue first to allocate a number of buffers
@@ -205,20 +208,6 @@ protected:
ASSERT_EQ(NO_ERROR, surface->disconnect(NATIVE_WINDOW_API_CPU));
}
- static status_t captureDisplay(DisplayCaptureArgs& captureArgs,
- ScreenCaptureResults& captureResults) {
- const auto sf = ComposerServiceAIDL::getComposerService();
- SurfaceComposerClient::Transaction().apply(true);
-
- const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
- binder::Status status = sf->captureDisplay(captureArgs, captureListener);
- if (status.transactionError() != NO_ERROR) {
- return status.transactionError();
- }
- captureResults = captureListener->waitForResults();
- return captureResults.result;
- }
-
sp<Surface> mSurface;
sp<SurfaceComposerClient> mComposerClient;
sp<SurfaceControl> mSurfaceControl;
@@ -256,53 +245,6 @@ TEST_F(SurfaceTest, QueuesToWindowComposerIsTrueWhenPurgatorized) {
EXPECT_EQ(1, result);
}
-// This test probably doesn't belong here.
-TEST_F(SurfaceTest, ScreenshotsOfProtectedBuffersDontSucceed) {
- sp<ANativeWindow> anw(mSurface);
-
- // Verify the screenshot works with no protected buffers.
- const sp<IBinder> display = ComposerServiceAIDL::getInstance().getInternalDisplayToken();
- ASSERT_FALSE(display == nullptr);
-
- DisplayCaptureArgs captureArgs;
- captureArgs.displayToken = display;
- captureArgs.width = 64;
- captureArgs.height = 64;
-
- ScreenCaptureResults captureResults;
- ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-
- ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(),
- NATIVE_WINDOW_API_CPU));
- // Set the PROTECTED usage bit and verify that the screenshot fails. Note
- // that we need to dequeue a buffer in order for it to actually get
- // allocated in SurfaceFlinger.
- ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(),
- GRALLOC_USAGE_PROTECTED));
- ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(anw.get(), 3));
- ANativeWindowBuffer* buf = nullptr;
-
- status_t err = native_window_dequeue_buffer_and_wait(anw.get(), &buf);
- if (err) {
- // we could fail if GRALLOC_USAGE_PROTECTED is not supported.
- // that's okay as long as this is the reason for the failure.
- // try again without the GRALLOC_USAGE_PROTECTED bit.
- ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), 0));
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
- &buf));
- return;
- }
- ASSERT_EQ(NO_ERROR, anw->cancelBuffer(anw.get(), buf, -1));
-
- for (int i = 0; i < 4; i++) {
- // Loop to make sure SurfaceFlinger has retired a protected buffer.
- ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
- &buf));
- ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf, -1));
- }
- ASSERT_EQ(NO_ERROR, captureDisplay(captureArgs, captureResults));
-}
-
TEST_F(SurfaceTest, ConcreteTypeIsSurface) {
sp<ANativeWindow> anw(mSurface);
int result = -123;
@@ -408,7 +350,7 @@ TEST_F(SurfaceTest, GetConsumerName) {
sp<ANativeWindow> window(surface);
native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
- EXPECT_STREQ("TestConsumer", surface->getConsumerName().string());
+ EXPECT_STREQ("TestConsumer", surface->getConsumerName().c_str());
}
TEST_F(SurfaceTest, GetWideColorSupport) {
@@ -525,7 +467,8 @@ TEST_F(SurfaceTest, DynamicSetBufferCount) {
ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(),
NATIVE_WINDOW_API_CPU));
- native_window_set_buffer_count(window.get(), 4);
+ ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), 4));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
int fence;
ANativeWindowBuffer* buffer;
@@ -555,6 +498,7 @@ TEST_F(SurfaceTest, GetAndFlushRemovedBuffers) {
/*reportBufferRemoval*/true));
const int BUFFER_COUNT = 4;
ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(window.get(), BUFFER_COUNT));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
sp<GraphicBuffer> detachedBuffer;
sp<Fence> outFence;
@@ -690,278 +634,254 @@ public:
mSupportsPresent = supportsPresent;
}
- sp<ISurfaceComposerClient> createConnection() override { return nullptr; }
- sp<IDisplayEventConnection> createDisplayEventConnection(
- ISurfaceComposer::VsyncSource, ISurfaceComposer::EventRegistrationFlags) override {
- return nullptr;
- }
- status_t setTransactionState(const FrameTimelineInfo& /*frameTimelineInfo*/,
- const Vector<ComposerState>& /*state*/,
- const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
- const sp<IBinder>& /*applyToken*/,
- const InputWindowCommands& /*inputWindowCommands*/,
- int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
- const client_cache_t& /*cachedBuffer*/,
- bool /*hasListenerCallbacks*/,
- const std::vector<ListenerCallbacks>& /*listenerCallbacks*/,
- uint64_t /*transactionId*/) override {
+ status_t setTransactionState(
+ const FrameTimelineInfo& /*frameTimelineInfo*/, Vector<ComposerState>& /*state*/,
+ const Vector<DisplayState>& /*displays*/, uint32_t /*flags*/,
+ const sp<IBinder>& /*applyToken*/, InputWindowCommands /*inputWindowCommands*/,
+ int64_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
+ const std::vector<client_cache_t>& /*cachedBuffer*/, bool /*hasListenerCallbacks*/,
+ const std::vector<ListenerCallbacks>& /*listenerCallbacks*/, uint64_t /*transactionId*/,
+ const std::vector<uint64_t>& /*mergedTransactionIds*/) override {
return NO_ERROR;
}
- void bootFinished() override {}
- bool authenticateSurfaceTexture(
- const sp<IGraphicBufferProducer>& /*surface*/) const override {
- return false;
- }
+protected:
+ IBinder* onAsBinder() override { return nullptr; }
- status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* 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<IBinder>& /*display*/, ui::StaticDisplayInfo*) override {
- return NO_ERROR;
- }
- status_t getDynamicDisplayInfo(const sp<IBinder>& /*display*/,
- ui::DynamicDisplayInfo*) override {
- return NO_ERROR;
- }
- status_t getDisplayNativePrimaries(const sp<IBinder>& /*display*/,
- ui::DisplayPrimaries& /*primaries*/) override {
- return NO_ERROR;
- }
- status_t setActiveColorMode(const sp<IBinder>& /*display*/, ColorMode /*colorMode*/) override {
- return NO_ERROR;
- }
- status_t setBootDisplayMode(const sp<IBinder>& /*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;
- }
- status_t overrideHdrTypes(const sp<IBinder>& /*display*/,
- const std::vector<ui::Hdr>& /*hdrTypes*/) 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<IBinder>& /*layerHandle*/,
+ sp<gui::IDisplayEventConnection>* outConnection) override {
+ *outConnection = nullptr;
+ return binder::Status::ok();
}
- status_t onPullAtom(const int32_t /*atomId*/, std::string* /*outData*/,
- bool* /*success*/) override {
- return NO_ERROR;
+
+ binder::Status createConnection(sp<gui::ISurfaceComposerClient>* outClient) override {
+ *outClient = nullptr;
+ return binder::Status::ok();
}
- status_t enableVSyncInjections(bool /*enable*/) override {
- return NO_ERROR;
+
+ binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
+ float /*requestedRefreshRate*/,
+ sp<IBinder>* /*outDisplay*/) override {
+ return binder::Status::ok();
}
- status_t injectVSync(nsecs_t /*when*/) override { return NO_ERROR; }
- status_t getLayerDebugInfo(std::vector<LayerDebugInfo>* /*layers*/) override {
- return NO_ERROR;
+
+ binder::Status destroyDisplay(const sp<IBinder>& /*display*/) 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 getPhysicalDisplayIds(std::vector<int64_t>* /*outDisplayIds*/) override {
+ return binder::Status::ok();
}
- status_t getDisplayedContentSamplingAttributes(const sp<IBinder>& /*display*/,
- ui::PixelFormat* /*outFormat*/,
- ui::Dataspace* /*outDataspace*/,
- uint8_t* /*outComponentMask*/) const override {
- return NO_ERROR;
+
+ binder::Status getPhysicalDisplayToken(int64_t /*displayId*/,
+ sp<IBinder>* /*outDisplay*/) override {
+ return binder::Status::ok();
}
- status_t setDisplayContentSamplingEnabled(const sp<IBinder>& /*display*/, bool /*enable*/,
- uint8_t /*componentMask*/,
- uint64_t /*maxFrames*/) override {
- return NO_ERROR;
+
+ binder::Status setPowerMode(const sp<IBinder>& /*display*/, int /*mode*/) override {
+ return binder::Status::ok();
}
- status_t getDisplayedContentSample(const sp<IBinder>& /*display*/, uint64_t /*maxFrames*/,
- uint64_t /*timestamp*/,
- DisplayedFrameStats* /*outStats*/) const override {
- return NO_ERROR;
+
+ binder::Status getSupportedFrameTimestamps(std::vector<FrameEvent>* 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 getColorManagement(bool* /*outGetColorManagement*/) const override { return NO_ERROR; }
- status_t getProtectedContentSupport(bool* /*outSupported*/) const override { return NO_ERROR; }
+ binder::Status getDisplayStats(const sp<IBinder>& /*display*/,
+ gui::DisplayStatInfo* /*outStatInfo*/) override {
+ return binder::Status::ok();
+ }
- status_t addRegionSamplingListener(const Rect& /*samplingArea*/,
- const sp<IBinder>& /*stopLayerHandle*/,
- const sp<IRegionSamplingListener>& /*listener*/) override {
- return NO_ERROR;
+ binder::Status getDisplayState(const sp<IBinder>& /*display*/,
+ gui::DisplayState* /*outState*/) override {
+ return binder::Status::ok();
}
- status_t removeRegionSamplingListener(
- const sp<IRegionSamplingListener>& /*listener*/) override {
- return NO_ERROR;
+
+ binder::Status getStaticDisplayInfo(int64_t /*displayId*/,
+ gui::StaticDisplayInfo* /*outInfo*/) override {
+ return binder::Status::ok();
}
- status_t addFpsListener(int32_t /*taskId*/, const sp<gui::IFpsListener>& /*listener*/) {
- return NO_ERROR;
+
+ binder::Status getDynamicDisplayInfoFromId(int64_t /*displayId*/,
+ gui::DynamicDisplayInfo* /*outInfo*/) override {
+ return binder::Status::ok();
}
- status_t removeFpsListener(const sp<gui::IFpsListener>& /*listener*/) { return NO_ERROR; }
- status_t addTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& /*listener*/) {
- return NO_ERROR;
+ binder::Status getDynamicDisplayInfoFromToken(const sp<IBinder>& /*display*/,
+ gui::DynamicDisplayInfo* /*outInfo*/) override {
+ return binder::Status::ok();
}
- status_t removeTunnelModeEnabledListener(
- const sp<gui::ITunnelModeEnabledListener>& /*listener*/) {
- return NO_ERROR;
+ binder::Status getDisplayNativePrimaries(const sp<IBinder>& /*display*/,
+ gui::DisplayPrimaries* /*outPrimaries*/) override {
+ return binder::Status::ok();
}
- status_t setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
- ui::DisplayModeId /*defaultMode*/,
- bool /*allowGroupSwitching*/,
- float /*primaryRefreshRateMin*/,
- float /*primaryRefreshRateMax*/,
- float /*appRequestRefreshRateMin*/,
- float /*appRequestRefreshRateMax*/) {
- return NO_ERROR;
+ binder::Status setActiveColorMode(const sp<IBinder>& /*display*/, int /*colorMode*/) override {
+ return binder::Status::ok();
}
- status_t getDesiredDisplayModeSpecs(const sp<IBinder>& /*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 setBootDisplayMode(const sp<IBinder>& /*display*/,
+ int /*displayModeId*/) override {
+ return binder::Status::ok();
}
- status_t getDisplayDecorationSupport(
- const sp<IBinder>& /*displayToken*/,
- std::optional<DisplayDecorationSupport>* /*outSupport*/) const override {
- return NO_ERROR;
+ binder::Status clearBootDisplayMode(const sp<IBinder>& /*display*/) override {
+ return binder::Status::ok();
}
- status_t setFrameRate(const sp<IGraphicBufferProducer>& /*surface*/, float /*frameRate*/,
- int8_t /*compatibility*/, int8_t /*changeFrameRateStrategy*/) override {
- return NO_ERROR;
+ binder::Status getBootDisplayModeSupport(bool* /*outMode*/) override {
+ return binder::Status::ok();
}
- status_t setFrameTimelineInfo(const sp<IGraphicBufferProducer>& /*surface*/,
- const FrameTimelineInfo& /*frameTimelineInfo*/) override {
- return NO_ERROR;
+ binder::Status getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>*) override {
+ return binder::Status::ok();
}
- status_t addTransactionTraceListener(
- const sp<gui::ITransactionTraceListener>& /*listener*/) override {
- return NO_ERROR;
+ binder::Status setHdrConversionStrategy(
+ const gui::HdrConversionStrategy& /*hdrConversionStrategy*/,
+ int32_t* /*outPreferredHdrOutputType*/) override {
+ return binder::Status::ok();
}
- int getGPUContextPriority() override { return 0; };
+ binder::Status getHdrOutputConversionSupport(bool* /*outSupport*/) override {
+ return binder::Status::ok();
+ }
- status_t getMaxAcquiredBufferCount(int* /*buffers*/) const override { return NO_ERROR; }
+ binder::Status setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {
+ return binder::Status::ok();
+ }
- status_t addWindowInfosListener(
- const sp<gui::IWindowInfosListener>& /*windowInfosListener*/) const override {
- return NO_ERROR;
+ binder::Status setGameContentType(const sp<IBinder>& /*display*/, bool /*on*/) override {
+ return binder::Status::ok();
}
- status_t removeWindowInfosListener(
- const sp<gui::IWindowInfosListener>& /*windowInfosListener*/) const override {
- return NO_ERROR;
+ binder::Status captureDisplay(const DisplayCaptureArgs&,
+ const sp<IScreenCaptureListener>&) override {
+ return binder::Status::ok();
}
- status_t setOverrideFrameRate(uid_t /*uid*/, float /*frameRate*/) override { return NO_ERROR; }
+ binder::Status captureDisplayById(int64_t, const gui::CaptureArgs&,
+ const sp<IScreenCaptureListener>&) override {
+ return binder::Status::ok();
+ }
-protected:
- IBinder* onAsBinder() override { return nullptr; }
+ binder::Status captureLayers(const LayerCaptureArgs&,
+ const sp<IScreenCaptureListener>&) override {
+ return binder::Status::ok();
+ }
-private:
- bool mSupportsPresent{true};
-};
+ binder::Status clearAnimationFrameStats() override { return binder::Status::ok(); }
-class FakeSurfaceComposerAIDL : public gui::ISurfaceComposer {
-public:
- ~FakeSurfaceComposerAIDL() override {}
+ binder::Status getAnimationFrameStats(gui::FrameStats* /*outStats*/) override {
+ return binder::Status::ok();
+ }
- void setSupportsPresent(bool supportsPresent) { mSupportsPresent = supportsPresent; }
+ binder::Status overrideHdrTypes(const sp<IBinder>& /*display*/,
+ const std::vector<int32_t>& /*hdrTypes*/) override {
+ return binder::Status::ok();
+ }
- binder::Status createDisplay(const std::string& /*displayName*/, bool /*secure*/,
- sp<IBinder>* /*outDisplay*/) override {
+ binder::Status onPullAtom(int32_t /*atomId*/, gui::PullAtomData* /*outPullData*/) override {
return binder::Status::ok();
}
- binder::Status destroyDisplay(const sp<IBinder>& /*display*/) override {
+ binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* /*outLayers*/) override {
return binder::Status::ok();
}
- binder::Status getPhysicalDisplayIds(std::vector<int64_t>* /*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<IBinder>& /*display*/, gui::ContentSamplingAttributes* /*outAttrs*/) override {
return binder::Status::ok();
}
- binder::Status getPhysicalDisplayToken(int64_t /*displayId*/,
- sp<IBinder>* /*outDisplay*/) override {
+ binder::Status setDisplayContentSamplingEnabled(const sp<IBinder>& /*display*/, bool /*enable*/,
+ int8_t /*componentMask*/,
+ int64_t /*maxFrames*/) override {
return binder::Status::ok();
}
- binder::Status setPowerMode(const sp<IBinder>& /*display*/, int /*mode*/) override {
+ binder::Status getProtectedContentSupport(bool* /*outSupporte*/) override {
return binder::Status::ok();
}
- binder::Status getDisplayStats(const sp<IBinder>& /*display*/,
- gui::DisplayStatInfo* /*outStatInfo*/) override {
+ binder::Status getDisplayedContentSample(const sp<IBinder>& /*display*/, int64_t /*maxFrames*/,
+ int64_t /*timestamp*/,
+ gui::DisplayedFrameStats* /*outStats*/) override {
return binder::Status::ok();
}
- binder::Status getDisplayState(const sp<IBinder>& /*display*/,
- gui::DisplayState* /*outState*/) override {
+ binder::Status isWideColorDisplay(const sp<IBinder>& /*token*/,
+ bool* /*outIsWideColorDisplay*/) override {
return binder::Status::ok();
}
- binder::Status clearBootDisplayMode(const sp<IBinder>& /*display*/) override {
+ binder::Status addRegionSamplingListener(
+ const gui::ARect& /*samplingArea*/, const sp<IBinder>& /*stopLayerHandle*/,
+ const sp<gui::IRegionSamplingListener>& /*listener*/) override {
return binder::Status::ok();
}
- binder::Status getBootDisplayModeSupport(bool* /*outMode*/) override {
+ binder::Status removeRegionSamplingListener(
+ const sp<gui::IRegionSamplingListener>& /*listener*/) override {
return binder::Status::ok();
}
- binder::Status setAutoLowLatencyMode(const sp<IBinder>& /*display*/, bool /*on*/) override {
+ binder::Status addFpsListener(int32_t /*taskId*/,
+ const sp<gui::IFpsListener>& /*listener*/) override {
return binder::Status::ok();
}
- binder::Status setGameContentType(const sp<IBinder>& /*display*/, bool /*on*/) override {
+ binder::Status removeFpsListener(const sp<gui::IFpsListener>& /*listener*/) override {
return binder::Status::ok();
}
- binder::Status captureDisplay(const DisplayCaptureArgs&,
- const sp<IScreenCaptureListener>&) override {
+ binder::Status addTunnelModeEnabledListener(
+ const sp<gui::ITunnelModeEnabledListener>& /*listener*/) override {
return binder::Status::ok();
}
- binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override {
+ binder::Status removeTunnelModeEnabledListener(
+ const sp<gui::ITunnelModeEnabledListener>& /*listener*/) override {
return binder::Status::ok();
}
- binder::Status captureLayers(const LayerCaptureArgs&,
- const sp<IScreenCaptureListener>&) override {
+ binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
+ const gui::DisplayModeSpecs&) override {
return binder::Status::ok();
}
- binder::Status isWideColorDisplay(const sp<IBinder>& /*token*/,
- bool* /*outIsWideColorDisplay*/) override {
+ binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& /*displayToken*/,
+ gui::DisplayModeSpecs*) override {
return binder::Status::ok();
}
@@ -989,6 +909,81 @@ 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<IBinder>& /*displayToken*/,
+ std::optional<gui::DisplayDecorationSupport>* /*outSupport*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status setGameModeFrameRateOverride(int32_t /*uid*/, float /*frameRate*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status setGameDefaultFrameRateOverride(int32_t /*uid*/, float /*frameRate*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status enableRefreshRateOverlay(bool /*active*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status setDebugFlash(int /*delay*/) override { return binder::Status::ok(); }
+
+ binder::Status scheduleComposite() override { return binder::Status::ok(); }
+
+ binder::Status scheduleCommit() override { return binder::Status::ok(); }
+
+ binder::Status forceClientComposition(bool /*enabled*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status updateSmallAreaDetection(const std::vector<int32_t>& /*appIds*/,
+ const std::vector<float>& /*thresholds*/) {
+ return binder::Status::ok();
+ }
+
+ binder::Status setSmallAreaDetectionThreshold(int32_t /*appId*/, float /*threshold*/) {
+ 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<gui::IWindowInfosListener>& /*windowInfosListener*/,
+ gui::WindowInfosListenerInfo* /*outInfo*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status removeWindowInfosListener(
+ const sp<gui::IWindowInfosListener>& /*windowInfosListener*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status getOverlaySupport(gui::OverlayProperties* /*properties*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status getStalledTransactionInfo(
+ int32_t /*pid*/, std::optional<gui::StalledTransactionInfo>* /*result*/) override {
+ return binder::Status::ok();
+ }
+
+ binder::Status getSchedulingPolicy(gui::SchedulingPolicy*) override {
+ return binder::Status::ok();
+ }
+
protected:
IBinder* onAsBinder() override { return nullptr; }
@@ -1034,10 +1029,10 @@ protected:
class TestSurface : public Surface {
public:
- TestSurface(const sp<IGraphicBufferProducer>& bufferProducer,
- FenceToFenceTimeMap* fenceMap)
- : Surface(bufferProducer),
- mFakeSurfaceComposer(new FakeSurfaceComposer) {
+ TestSurface(const sp<IGraphicBufferProducer>& bufferProducer, FenceToFenceTimeMap* fenceMap)
+ : Surface(bufferProducer),
+ mFakeSurfaceComposer(new FakeSurfaceComposer),
+ mFakeSurfaceComposerAIDL(new FakeSurfaceComposerAIDL) {
mFakeFrameEventHistory = new FakeProducerFrameEventHistory(fenceMap);
mFrameEventHistory.reset(mFakeFrameEventHistory);
}
@@ -1048,6 +1043,10 @@ public:
return mFakeSurfaceComposer;
}
+ sp<gui::ISurfaceComposer> composerServiceAIDL() const override {
+ return mFakeSurfaceComposerAIDL;
+ }
+
nsecs_t now() const override {
return mNow;
}
@@ -1058,6 +1057,7 @@ public:
public:
sp<FakeSurfaceComposer> mFakeSurfaceComposer;
+ sp<FakeSurfaceComposerAIDL> mFakeSurfaceComposerAIDL;
nsecs_t mNow = 0;
// mFrameEventHistory owns the instance of FakeProducerFrameEventHistory,
@@ -1070,20 +1070,30 @@ class GetFrameTimestampsTest : public ::testing::Test {
protected:
struct FenceAndFenceTime {
explicit FenceAndFenceTime(FenceToFenceTimeMap& fenceMap)
- : mFence(new Fence),
- mFenceTime(fenceMap.createFenceTimeForTest(mFence)) {}
- sp<Fence> mFence { nullptr };
- std::shared_ptr<FenceTime> mFenceTime { nullptr };
+ : mFenceTime(fenceMap.createFenceTimeForTest(mFence)) {}
+
+ sp<Fence> mFence = sp<Fence>::make();
+ std::shared_ptr<FenceTime> 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 +1103,8 @@ protected:
FenceToFenceTimeMap& mFenceMap;
- FenceAndFenceTime mGpuCompositionDone { mFenceMap };
- FenceAndFenceTime mPresent { mFenceMap };
+ FenceAndFenceTime mGpuCompositionDone{mFenceMap};
+ FenceAndFenceTime mPresent{mFenceMap};
const CompositorTiming kCompositorTiming;
@@ -1165,7 +1175,8 @@ protected:
ASSERT_EQ(NO_ERROR, native_window_api_connect(mWindow.get(),
NATIVE_WINDOW_API_CPU));
- native_window_set_buffer_count(mWindow.get(), 4);
+ ASSERT_EQ(NO_ERROR, native_window_set_buffer_count(mWindow.get(), 4));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(mWindow.get(), TEST_PRODUCER_USAGE_BITS));
}
void disableFrameTimestamps() {
@@ -1274,7 +1285,7 @@ protected:
newFrame->mRefreshes[0].mGpuCompositionDone.mFenceTime :
FenceTime::NO_FENCE;
// HWC2 releases the previous buffer after a new latch just before
- // calling postComposition.
+ // calling onCompositionPresented.
if (oldFrame != nullptr) {
mCfeh->addRelease(nOldFrame, oldFrame->kDequeueReadyTime,
std::shared_ptr<FenceTime>(oldFrame->mRelease.mFenceTime));
@@ -1360,11 +1371,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 +1431,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 +1443,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 +1510,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 +1591,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 +2013,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();
@@ -2040,8 +2042,9 @@ TEST_F(SurfaceTest, DequeueWithConsumerDrivenSize) {
sp<Surface> surface = new Surface(producer);
sp<ANativeWindow> window(surface);
- native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
- native_window_set_buffers_dimensions(window.get(), 0, 0);
+ ASSERT_EQ(NO_ERROR, native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU));
+ ASSERT_EQ(NO_ERROR, native_window_set_buffers_dimensions(window.get(), 0, 0));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS));
int fence;
ANativeWindowBuffer* buffer;
@@ -2093,6 +2096,7 @@ TEST_F(SurfaceTest, DequeueWithConsumerDrivenSize) {
native_window_api_connect(window.get(), NATIVE_WINDOW_API_CPU);
consumer->setTransformHint(NATIVE_WINDOW_TRANSFORM_ROT_270);
native_window_set_buffers_dimensions(window.get(), 0, 0);
+ native_window_set_usage(window.get(), TEST_PRODUCER_USAGE_BITS);
ASSERT_EQ(NO_ERROR, window->dequeueBuffer(window.get(), &buffer, &fence));
EXPECT_EQ(10, buffer->width);
EXPECT_EQ(20, buffer->height);
diff --git a/libs/gui/tests/VsyncEventData_test.cpp b/libs/gui/tests/VsyncEventData_test.cpp
index f114522951..a2138f2144 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 99658ccd4b..5eb5d3bff0 100644
--- a/libs/gui/tests/WindowInfo_test.cpp
+++ b/libs/gui/tests/WindowInfo_test.cpp
@@ -28,6 +28,7 @@ namespace android {
using gui::InputApplicationInfo;
using gui::TouchOcclusionMode;
using gui::WindowInfo;
+using ui::Size;
namespace test {
@@ -52,17 +53,15 @@ TEST(WindowInfo, Parcelling) {
i.layoutParamsFlags = WindowInfo::Flag::SLIPPERY;
i.layoutParamsType = WindowInfo::Type::INPUT_METHOD;
i.dispatchingTimeout = 12s;
- i.frameLeft = 93;
- i.frameTop = 34;
- i.frameRight = 16;
- i.frameBottom = 19;
+ i.frame = Rect(93, 34, 16, 19);
+ i.contentSize = Size(10, 40);
i.surfaceInset = 17;
i.globalScaleFactor = 0.3;
i.alpha = 0.7;
i.transform.set({0.4, -1, 100, 0.5, 0, 40, 0, 0, 1});
i.touchOcclusionMode = TouchOcclusionMode::ALLOW;
- i.ownerPid = 19;
- i.ownerUid = 24;
+ i.ownerPid = gui::Pid{19};
+ i.ownerUid = gui::Uid{24};
i.packageName = "com.example.package";
i.inputConfig = WindowInfo::InputConfig::NOT_FOCUSABLE;
i.displayId = 34;
@@ -71,7 +70,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);
@@ -85,10 +84,8 @@ TEST(WindowInfo, Parcelling) {
ASSERT_EQ(i.layoutParamsFlags, i2.layoutParamsFlags);
ASSERT_EQ(i.layoutParamsType, i2.layoutParamsType);
ASSERT_EQ(i.dispatchingTimeout, i2.dispatchingTimeout);
- ASSERT_EQ(i.frameLeft, i2.frameLeft);
- ASSERT_EQ(i.frameTop, i2.frameTop);
- ASSERT_EQ(i.frameRight, i2.frameRight);
- ASSERT_EQ(i.frameBottom, i2.frameBottom);
+ ASSERT_EQ(i.frame, i2.frame);
+ ASSERT_EQ(i.contentSize, i2.contentSize);
ASSERT_EQ(i.surfaceInset, i2.surfaceInset);
ASSERT_EQ(i.globalScaleFactor, i2.globalScaleFactor);
ASSERT_EQ(i.alpha, i2.alpha);
@@ -102,7 +99,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/gui/view/Surface.cpp b/libs/gui/view/Surface.cpp
index 1bfe4624f1..7c15e7cf92 100644
--- a/libs/gui/view/Surface.cpp
+++ b/libs/gui/view/Surface.cpp
@@ -16,17 +16,58 @@
#define LOG_TAG "Surface"
-#include <gui/view/Surface.h>
-
+#include <android/binder_libbinder.h>
+#include <android/binder_parcel.h>
+#include <android/native_window.h>
#include <binder/Parcel.h>
-
+#include <gui/Surface.h>
+#include <gui/view/Surface.h>
+#include <system/window.h>
#include <utils/Log.h>
-#include <gui/IGraphicBufferProducer.h>
-
namespace android {
namespace view {
+// Since this is a parcelable utility and we want to keep the wire format stable, only build this
+// when building the system libgui to detect any issues loading the wrong libgui from
+// libnativewindow
+
+#if (!defined(__ANDROID_APEX__) && !defined(__ANDROID_VNDK__))
+
+extern "C" status_t android_view_Surface_writeToParcel(ANativeWindow* _Nonnull window,
+ Parcel* _Nonnull parcel) {
+ int value;
+ int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
+ if (err != OK || value != NATIVE_WINDOW_SURFACE) {
+ ALOGE("Error: ANativeWindow is not backed by Surface");
+ return STATUS_BAD_VALUE;
+ }
+ // Use a android::view::Surface to parcelize the window
+ android::view::Surface shimSurface;
+ shimSurface.graphicBufferProducer = android::Surface::getIGraphicBufferProducer(window);
+ shimSurface.surfaceControlHandle = android::Surface::getSurfaceControlHandle(window);
+ return shimSurface.writeToParcel(parcel);
+}
+
+extern "C" status_t android_view_Surface_readFromParcel(
+ const Parcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
+ // Use a android::view::Surface to unparcel the window
+ android::view::Surface shimSurface;
+ status_t ret = shimSurface.readFromParcel(parcel);
+ if (ret != OK) {
+ ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
+ return STATUS_BAD_VALUE;
+ }
+ auto surface = sp<android::Surface>::make(shimSurface.graphicBufferProducer, false,
+ shimSurface.surfaceControlHandle);
+ ANativeWindow* anw = surface.get();
+ ANativeWindow_acquire(anw);
+ *outWindow = anw;
+ return STATUS_OK;
+}
+
+#endif
+
status_t Surface::writeToParcel(Parcel* parcel) const {
return writeToParcel(parcel, false);
}
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index b2fec7917b..20b35383bc 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",
@@ -34,6 +33,136 @@ filegroup {
],
}
+/////////////////////////////////////////////////
+// flags
+/////////////////////////////////////////////////
+aconfig_declarations {
+ name: "com.android.input.flags-aconfig",
+ package: "com.android.input.flags",
+ container: "system",
+ srcs: ["input_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "com.android.input.flags-aconfig-cc",
+ aconfig_declarations: "com.android.input.flags-aconfig",
+ host_supported: true,
+ // Use the test version of the aconfig flag library by default to allow tests to set local
+ // overrides for flags, without having to link against a separate version of libinput or of this
+ // library. Bundling this library directly into libinput prevents us from having to add this
+ // library as a shared lib dependency everywhere where libinput is used.
+ mode: "test",
+ shared: {
+ enabled: false,
+ },
+}
+
+aidl_interface {
+ name: "inputconstants",
+ host_supported: true,
+ vendor_available: true,
+ unstable: true,
+ srcs: [
+ ":inputconstants_aidl",
+ ],
+
+ backend: {
+ rust: {
+ enabled: true,
+ },
+ },
+}
+
+rust_bindgen {
+ name: "libinput_bindgen",
+ host_supported: true,
+ crate_name: "input_bindgen",
+ visibility: ["//frameworks/native/services/inputflinger"],
+ wrapper_src: "InputWrapper.hpp",
+
+ include_dirs: [
+ "frameworks/native/include",
+ ],
+
+ source_stem: "bindings",
+
+ bindgen_flags: [
+ "--verbose",
+ "--allowlist-var=AMOTION_EVENT_FLAG_CANCELED",
+ "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED",
+ "--allowlist-var=AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED",
+ "--allowlist-var=AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT",
+ "--allowlist-var=AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE",
+ "--allowlist-var=AMOTION_EVENT_ACTION_CANCEL",
+ "--allowlist-var=AMOTION_EVENT_ACTION_UP",
+ "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_DOWN",
+ "--allowlist-var=AMOTION_EVENT_ACTION_DOWN",
+ "--allowlist-var=AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT",
+ "--allowlist-var=MAX_POINTER_ID",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NONE",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_BUTTON",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POINTER",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_POSITION",
+ "--allowlist-var=AINPUT_SOURCE_CLASS_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_UNKNOWN",
+ "--allowlist-var=AINPUT_SOURCE_KEYBOARD",
+ "--allowlist-var=AINPUT_SOURCE_DPAD",
+ "--allowlist-var=AINPUT_SOURCE_GAMEPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHSCREEN",
+ "--allowlist-var=AINPUT_SOURCE_MOUSE",
+ "--allowlist-var=AINPUT_SOURCE_STYLUS",
+ "--allowlist-var=AINPUT_SOURCE_BLUETOOTH_STYLUS",
+ "--allowlist-var=AINPUT_SOURCE_TRACKBALL",
+ "--allowlist-var=AINPUT_SOURCE_MOUSE_RELATIVE",
+ "--allowlist-var=AINPUT_SOURCE_TOUCHPAD",
+ "--allowlist-var=AINPUT_SOURCE_TOUCH_NAVIGATION",
+ "--allowlist-var=AINPUT_SOURCE_JOYSTICK",
+ "--allowlist-var=AINPUT_SOURCE_HDMI",
+ "--allowlist-var=AINPUT_SOURCE_SENSOR",
+ "--allowlist-var=AINPUT_SOURCE_ROTARY_ENCODER",
+ ],
+
+ static_libs: [
+ "inputconstants-cpp",
+ "libui-types",
+ ],
+ shared_libs: ["libc++"],
+ header_libs: [
+ "native_headers",
+ "jni_headers",
+ "flatbuffer_headers",
+ ],
+}
+
+// Contains methods to help access C++ code from rust
+cc_library_static {
+ name: "libinput_from_rust_to_cpp",
+ cpp_std: "c++20",
+ host_supported: true,
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ "-Werror",
+ ],
+ srcs: [
+ "FromRustToCpp.cpp",
+ ],
+
+ generated_headers: [
+ "cxx-bridge-header",
+ ],
+ generated_sources: ["libinput_cxx_bridge_code"],
+
+ lto: {
+ never: true,
+ },
+
+ shared_libs: [
+ "libbase",
+ ],
+}
+
cc_library {
name: "libinput",
cpp_std: "c++20",
@@ -42,93 +171,119 @@ cc_library {
"-Wall",
"-Wextra",
"-Werror",
+ "-Wno-unused-parameter",
+ "-Wthread-safety",
+ "-Wshadow",
+ "-Wshadow-field-in-constructor-modified",
+ "-Wshadow-uncaptured-local",
+ "-DANDROID_UTILS_REF_BASE_DISABLE_IMPLICIT_CONSTRUCTION",
],
srcs: [
+ "android/os/IInputFlinger.aidl",
"Input.cpp",
"InputDevice.cpp",
"InputEventLabels.cpp",
+ "InputTransport.cpp",
+ "InputVerifier.cpp",
"Keyboard.cpp",
"KeyCharacterMap.cpp",
"KeyLayoutMap.cpp",
+ "MotionPredictor.cpp",
+ "MotionPredictorMetricsManager.cpp",
"PrintTools.cpp",
"PropertyMap.cpp",
+ "TfLiteMotionPredictor.cpp",
"TouchVideoFrame.cpp",
"VelocityControl.cpp",
"VelocityTracker.cpp",
+ "VirtualInputDevice.cpp",
"VirtualKeyMap.cpp",
],
- header_libs: ["jni_headers"],
- export_header_lib_headers: ["jni_headers"],
+ header_libs: [
+ "flatbuffer_headers",
+ "jni_headers",
+ "libeigen",
+ "tensorflow_headers",
+ ],
+ export_header_lib_headers: [
+ "jni_headers",
+ "libeigen",
+ ],
+
+ generated_headers: [
+ "cxx-bridge-header",
+ "libinput_cxx_bridge_header",
+ "toolbox_input_labels",
+ ],
shared_libs: [
"libbase",
- "liblog",
+ "libbinder",
"libcutils",
- "libvintf",
+ "liblog",
+ "libPlatformProperties",
+ "libtinyxml2",
+ "libutils",
+ "libz", // needed by libkernelconfigs
+ "server_configurable_flags",
+ ],
+
+ ldflags: [
+ "-Wl,--exclude-libs=libtflite_static.a",
],
+ sanitize: {
+ undefined: true,
+ all_undefined: true,
+ misc_undefined: ["integer"],
+ },
+
static_libs: [
+ "inputconstants-cpp",
+ "libgui_window_info_static",
"libui-types",
+ "libtflite_static",
+ "libkernelconfigs",
+ ],
+
+ whole_static_libs: [
+ "com.android.input.flags-aconfig-cc",
+ "libinput_rust_ffi",
],
export_static_lib_headers: [
+ "libgui_window_info_static",
"libui-types",
],
+ export_generated_headers: [
+ "cxx-bridge-header",
+ "libinput_cxx_bridge_header",
+ ],
+
target: {
android: {
- srcs: [
- "InputTransport.cpp",
- "android/os/IInputFlinger.aidl",
- ":inputconstants_aidl",
- ],
-
export_shared_lib_headers: ["libbinder"],
shared_libs: [
"libutils",
"libbinder",
- "libui",
+ // Stats logging library and its dependencies.
+ "libstatslog_libinput",
+ "libstatsbootstrap",
+ "android.os.statsbootstrap_aidl-cpp",
],
- static_libs: [
- "libgui_window_info_static",
+ required: [
+ "motion_predictor_model_prebuilt",
+ "motion_predictor_model_config",
],
-
- export_static_lib_headers: [
- "libgui_window_info_static",
- ],
-
- sanitize: {
- misc_undefined: ["integer"],
- },
},
host: {
- shared: {
- enabled: false,
- },
include_dirs: [
- "frameworks/native/libs/arect/include",
- ],
- },
- host_linux: {
- srcs: [
- "InputTransport.cpp",
- "android/os/IInputConstants.aidl",
- "android/os/IInputFlinger.aidl",
- "android/os/InputConfig.aidl",
- ],
- static_libs: [
- "libhostgraphics",
- "libgui_window_info_static",
- ],
- shared_libs: [
- "libbinder",
- ],
-
- export_static_lib_headers: [
- "libgui_window_info_static",
+ "bionic/libc/kernel/android/uapi/",
+ "bionic/libc/kernel/uapi",
],
},
},
@@ -142,8 +297,46 @@ cc_library {
},
}
+// Use bootstrap version of stats logging library.
+// libinput is a bootstrap process (starts early in the boot process), and thus can't use the normal
+// `libstatslog` because that requires `libstatssocket`, which is only available later in the boot.
+cc_library {
+ name: "libstatslog_libinput",
+ generated_sources: ["statslog_libinput.cpp"],
+ generated_headers: ["statslog_libinput.h"],
+ export_generated_headers: ["statslog_libinput.h"],
+ shared_libs: [
+ "libbinder",
+ "libstatsbootstrap",
+ "libutils",
+ "android.os.statsbootstrap_aidl-cpp",
+ ],
+}
+
+genrule {
+ name: "statslog_libinput.h",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --header $(genDir)/statslog_libinput.h --module libinput" +
+ " --namespace android,stats,libinput --bootstrap",
+ out: [
+ "statslog_libinput.h",
+ ],
+}
+
+genrule {
+ name: "statslog_libinput.cpp",
+ tools: ["stats-log-api-gen"],
+ cmd: "$(location stats-log-api-gen) --cpp $(genDir)/statslog_libinput.cpp --module libinput" +
+ " --namespace android,stats,libinput --importHeader statslog_libinput.h" +
+ " --bootstrap",
+ out: [
+ "statslog_libinput.cpp",
+ ],
+}
+
cc_defaults {
name: "libinput_fuzz_defaults",
+ cpp_std: "c++20",
host_supported: true,
shared_libs: [
"libutils",
diff --git a/libs/binder/Trace.cpp b/libs/input/FromRustToCpp.cpp
index 1ebfa1a165..e4ce62e734 100644
--- a/libs/binder/Trace.cpp
+++ b/libs/input/FromRustToCpp.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -14,19 +14,13 @@
* limitations under the License.
*/
-#include <binder/Trace.h>
-#include <cutils/trace.h>
+#include <android-base/logging.h>
+#include <ffi/FromRustToCpp.h>
namespace android {
-namespace binder {
-void atrace_begin(uint64_t tag, const char* name) {
- ::atrace_begin(tag, name);
+bool shouldLog(rust::Str tag) {
+ return android::base::ShouldLog(android::base::LogSeverity::DEBUG, tag.data());
}
-void atrace_end(uint64_t tag) {
- ::atrace_end(tag);
-}
-
-} // namespace binder
} // namespace android
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 2b7483d27d..bd5b67b1d0 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -21,7 +21,9 @@
#include <cutils/compiler.h>
#include <inttypes.h>
#include <string.h>
+#include <optional>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <cutils/compiler.h>
@@ -34,7 +36,7 @@
#ifdef __linux__
#include <binder/Parcel.h>
#endif
-#ifdef __ANDROID__
+#if defined(__ANDROID__)
#include <sys/random.h>
#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,41 +116,58 @@ int32_t IdGenerator::nextId() const {
}
totalBytes += bytes;
}
+#else
+#if defined(__linux__)
+ // On host, <sys/random.h> / GRND_NONBLOCK is not available
+ while (true) {
+ status_t result = getRandomBytes(reinterpret_cast<uint8_t*>(&id), sizeof(id));
+ if (result == OK) {
+ break;
+ }
+ }
+#endif // __linux__
#endif // __ANDROID__
-
return (id & SEQUENCE_NUMBER_MASK) | static_cast<int32_t>(mSource);
}
// --- InputEvent ---
+// Due to precision limitations when working with floating points, transforming - namely
+// scaling - floating points can lead to minute errors. We round transformed values to approximately
+// three decimal places so that values like 0.99997 show up as 1.0.
+inline float roundTransformedCoords(float val) {
+ // Use a power to two to approximate three decimal places to potentially reduce some cycles.
+ // This should be at least as precise as MotionEvent::ROUNDING_PRECISION.
+ return std::round(val * 1024.f) / 1024.f;
+}
+
+inline vec2 roundTransformedCoords(vec2 p) {
+ return {roundTransformedCoords(p.x), roundTransformedCoords(p.y)};
+}
+
vec2 transformWithoutTranslation(const ui::Transform& transform, const vec2& xy) {
const vec2 transformedXy = transform.transform(xy);
const vec2 transformedOrigin = transform.transform(0, 0);
- return transformedXy - transformedOrigin;
+ 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) {
@@ -210,6 +213,23 @@ bool isFromSource(uint32_t source, uint32_t test) {
return (source & test) == test;
}
+bool isStylusToolType(ToolType toolType) {
+ return toolType == ToolType::STYLUS || toolType == ToolType::ERASER;
+}
+
+bool isStylusEvent(uint32_t source, const std::vector<PointerProperties>& properties) {
+ if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+ return false;
+ }
+ // Need at least one stylus pointer for this event to be considered a stylus event
+ for (const PointerProperties& pointerProperties : properties) {
+ if (isStylusToolType(pointerProperties.toolType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
VerifiedKeyEvent verifiedKeyEventFromKeyEvent(const KeyEvent& event) {
return {{VerifiedInputEvent::Type::KEY, event.getDeviceId(), event.getEventTime(),
event.getSource(), event.getDisplayId()},
@@ -256,13 +276,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<const KeyEvent&>(event);
+ out << keyEvent;
+ return out;
+ }
+ case InputEventType::MOTION: {
+ const MotionEvent& motionEvent = static_cast<const MotionEvent&>(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<int> KeyEvent::getKeyCodeFromLabel(const char* label) {
return InputEventLookup::getKeyCodeByLabel(label);
}
@@ -306,6 +357,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 {
@@ -378,6 +451,8 @@ status_t PointerCoords::readFromParcel(Parcel* parcel) {
for (uint32_t i = 0; i < count; i++) {
values[i] = parcel->readFloat();
}
+
+ isResampled = parcel->readBool();
return OK;
}
@@ -388,6 +463,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
@@ -407,15 +484,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) {
@@ -438,19 +510,6 @@ void PointerCoords::transform(const ui::Transform& transform) {
}
}
-// --- PointerProperties ---
-
-bool PointerProperties::operator==(const PointerProperties& other) const {
- return id == other.id
- && toolType == other.toolType;
-}
-
-void PointerProperties::copyFrom(const PointerProperties& other) {
- id = other.id;
- toolType = other.toolType;
-}
-
-
// --- MotionEvent ---
void MotionEvent::initialize(int32_t id, int32_t deviceId, uint32_t source, int32_t displayId,
@@ -528,32 +587,32 @@ void MotionEvent::addSample(
&pointerCoords[getPointerCount()]);
}
-int MotionEvent::getSurfaceRotation() const {
+std::optional<ui::Rotation> 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;
}
}
float MotionEvent::getXCursorPosition() const {
vec2 vals = mTransform.transform(getRawXCursorPosition(), getRawYCursorPosition());
- return vals.x;
+ return roundTransformedCoords(vals.x);
}
float MotionEvent::getYCursorPosition() const {
vec2 vals = mTransform.transform(getRawXCursorPosition(), getRawYCursorPosition());
- return vals.y;
+ return roundTransformedCoords(vals.y);
}
void MotionEvent::setCursorPosition(float x, float y) {
@@ -739,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<ToolType>(parcel->readInt32());
}
while (sampleCount > 0) {
@@ -795,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<int32_t>(properties.toolType));
}
const PointerCoords* pc = mSamplePointerCoords.data();
@@ -833,7 +892,7 @@ const char* MotionEvent::getLabel(int32_t axis) {
return InputEventLookup::getAxisLabel(axis);
}
-int32_t MotionEvent::getAxisFromLabel(const char* label) {
+std::optional<int> MotionEvent::getAxisFromLabel(const char* label) {
return InputEventLookup::getAxisByLabel(label);
}
@@ -875,7 +934,7 @@ std::string MotionEvent::actionToString(int32_t action) {
static inline vec2 calculateTransformedXYUnchecked(uint32_t source, const ui::Transform& transform,
const vec2& xy) {
return shouldDisregardOffset(source) ? transformWithoutTranslation(transform, xy)
- : transform.transform(xy);
+ : roundTransformedCoords(transform.transform(xy));
}
vec2 MotionEvent::calculateTransformedXY(uint32_t source, const ui::Transform& transform,
@@ -959,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) {
@@ -1113,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<KeyEvent>(static_cast<KeyEvent*>(event)));
- return;
+ case InputEventType::KEY: {
+ if (mKeyEventPool.size() < mMaxPoolSize) {
+ mKeyEventPool.push(std::unique_ptr<KeyEvent>(static_cast<KeyEvent*>(event)));
+ return;
+ }
+ break;
}
- break;
- case AINPUT_EVENT_TYPE_MOTION:
- if (mMotionEventPool.size() < mMaxPoolSize) {
- mMotionEventPool.push(std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event)));
- return;
+ case InputEventType::MOTION: {
+ if (mMotionEventPool.size() < mMaxPoolSize) {
+ mMotionEventPool.push(
+ std::unique_ptr<MotionEvent>(static_cast<MotionEvent*>(event)));
+ return;
+ }
+ break;
}
- break;
- case AINPUT_EVENT_TYPE_FOCUS:
- if (mFocusEventPool.size() < mMaxPoolSize) {
- mFocusEventPool.push(std::unique_ptr<FocusEvent>(static_cast<FocusEvent*>(event)));
- return;
+ case InputEventType::FOCUS: {
+ if (mFocusEventPool.size() < mMaxPoolSize) {
+ mFocusEventPool.push(std::unique_ptr<FocusEvent>(static_cast<FocusEvent*>(event)));
+ return;
+ }
+ break;
}
- break;
- case AINPUT_EVENT_TYPE_CAPTURE:
- if (mCaptureEventPool.size() < mMaxPoolSize) {
- mCaptureEventPool.push(
- std::unique_ptr<CaptureEvent>(static_cast<CaptureEvent*>(event)));
- return;
+ case InputEventType::CAPTURE: {
+ if (mCaptureEventPool.size() < mMaxPoolSize) {
+ mCaptureEventPool.push(
+ std::unique_ptr<CaptureEvent>(static_cast<CaptureEvent*>(event)));
+ return;
+ }
+ break;
}
- break;
- case AINPUT_EVENT_TYPE_DRAG:
- if (mDragEventPool.size() < mMaxPoolSize) {
- mDragEventPool.push(std::unique_ptr<DragEvent>(static_cast<DragEvent*>(event)));
- return;
+ case InputEventType::DRAG: {
+ if (mDragEventPool.size() < mMaxPoolSize) {
+ mDragEventPool.push(std::unique_ptr<DragEvent>(static_cast<DragEvent*>(event)));
+ return;
+ }
+ break;
}
- break;
- case AINPUT_EVENT_TYPE_TOUCH_MODE:
- if (mTouchModeEventPool.size() < mMaxPoolSize) {
- mTouchModeEventPool.push(
- std::unique_ptr<TouchModeEvent>(static_cast<TouchModeEvent*>(event)));
- return;
+ case InputEventType::TOUCH_MODE: {
+ if (mTouchModeEventPool.size() < mMaxPoolSize) {
+ mTouchModeEventPool.push(
+ std::unique_ptr<TouchModeEvent>(static_cast<TouchModeEvent*>(event)));
+ return;
+ }
+ break;
}
- break;
}
delete event;
}
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index a9089690b0..ccc83231fa 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -20,11 +20,14 @@
#include <unistd.h>
#include <ctype.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <ftl/enum.h>
+#include <gui/constants.h>
#include <input/InputDevice.h>
#include <input/InputEventLabels.h>
+using android::base::GetProperty;
using android::base::StringPrintf;
namespace android {
@@ -95,21 +98,22 @@ std::string getInputDeviceConfigurationFilePathByName(
// Treblized input device config files will be located /product/usr, /system_ext/usr,
// /odm/usr or /vendor/usr.
- // These files may also be in the com.android.input.config APEX.
- const char* rootsForPartition[]{
- "/product",
- "/system_ext",
- "/odm",
- "/vendor",
- "/apex/com.android.input.config/etc",
- getenv("ANDROID_ROOT"),
+ std::vector<std::string> pathPrefixes{
+ "/product/usr/",
+ "/system_ext/usr/",
+ "/odm/usr/",
+ "/vendor/usr/",
};
- for (size_t i = 0; i < size(rootsForPartition); i++) {
- if (rootsForPartition[i] == nullptr) {
- continue;
- }
- path = rootsForPartition[i];
- path += "/usr/";
+ // These files may also be in the APEX pointed by input_device.config_file.apex sysprop.
+ if (auto apex = GetProperty("input_device.config_file.apex", ""); !apex.empty()) {
+ pathPrefixes.push_back("/apex/" + apex + "/etc/usr/");
+ }
+ // ANDROID_ROOT may not be set on host
+ if (auto android_root = getenv("ANDROID_ROOT"); android_root != nullptr) {
+ pathPrefixes.push_back(std::string(android_root) + "/usr/");
+ }
+ for (const auto& prefix : pathPrefixes) {
+ path = prefix;
appendInputDeviceConfigurationFileRelativePath(path, name, type);
#if DEBUG_PROBE
ALOGD("Probing for system provided input device configuration file: path='%s'",
@@ -166,7 +170,7 @@ std::string InputDeviceIdentifier::getCanonicalName() const {
// --- InputDeviceInfo ---
InputDeviceInfo::InputDeviceInfo() {
- initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false);
+ initialize(-1, 0, -1, InputDeviceIdentifier(), "", false, false, ADISPLAY_ID_NONE);
}
InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
@@ -177,9 +181,12 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other)
mAlias(other.mAlias),
mIsExternal(other.mIsExternal),
mHasMic(other.mHasMic),
+ mKeyboardLayoutInfo(other.mKeyboardLayoutInfo),
mSources(other.mSources),
mKeyboardType(other.mKeyboardType),
mKeyCharacterMap(other.mKeyCharacterMap),
+ mUsiVersion(other.mUsiVersion),
+ mAssociatedDisplayId(other.mAssociatedDisplayId),
mHasVibrator(other.mHasVibrator),
mHasBattery(other.mHasBattery),
mHasButtonUnderPad(other.mHasButtonUnderPad),
@@ -192,8 +199,8 @@ InputDeviceInfo::~InputDeviceInfo() {
}
void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t controllerNumber,
- const InputDeviceIdentifier& identifier, const std::string& alias, bool isExternal,
- bool hasMic) {
+ const InputDeviceIdentifier& identifier, const std::string& alias,
+ bool isExternal, bool hasMic, int32_t associatedDisplayId) {
mId = id;
mGeneration = generation;
mControllerNumber = controllerNumber;
@@ -203,10 +210,12 @@ void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t control
mHasMic = hasMic;
mSources = 0;
mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
+ mAssociatedDisplayId = associatedDisplayId;
mHasVibrator = false;
mHasBattery = false;
mHasButtonUnderPad = false;
mHasSensor = false;
+ mUsiVersion.reset();
mMotionRanges.clear();
mSensors.clear();
mLights.clear();
@@ -265,6 +274,10 @@ void InputDeviceInfo::setKeyboardType(int32_t keyboardType) {
mKeyboardType = std::max(mKeyboardType, keyboardType);
}
+void InputDeviceInfo::setKeyboardLayoutInfo(KeyboardLayoutInfo layoutInfo) {
+ mKeyboardLayoutInfo = std::move(layoutInfo);
+}
+
std::vector<InputDeviceSensorInfo> InputDeviceInfo::getSensors() {
std::vector<InputDeviceSensorInfo> infos;
infos.reserve(mSensors.size());
diff --git a/libs/input/InputEventLabels.cpp b/libs/input/InputEventLabels.cpp
index c0aa2e26a2..0e627e56fd 100644
--- a/libs/input/InputEventLabels.cpp
+++ b/libs/input/InputEventLabels.cpp
@@ -16,6 +16,10 @@
#include <input/InputEventLabels.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <strings.h>
+
#define DEFINE_KEYCODE(key) { #key, AKEYCODE_##key }
#define DEFINE_AXIS(axis) { #axis, AMOTION_EVENT_AXIS_##axis }
#define DEFINE_LED(led) { #led, ALED_##led }
@@ -23,6 +27,8 @@
namespace android {
+// clang-format off
+
// 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.
#define KEYCODES_SEQUENCE \
@@ -314,7 +320,35 @@ namespace android {
DEFINE_KEYCODE(REFRESH), \
DEFINE_KEYCODE(THUMBS_UP), \
DEFINE_KEYCODE(THUMBS_DOWN), \
- DEFINE_KEYCODE(PROFILE_SWITCH)
+ DEFINE_KEYCODE(PROFILE_SWITCH), \
+ DEFINE_KEYCODE(VIDEO_APP_1), \
+ DEFINE_KEYCODE(VIDEO_APP_2), \
+ DEFINE_KEYCODE(VIDEO_APP_3), \
+ DEFINE_KEYCODE(VIDEO_APP_4), \
+ DEFINE_KEYCODE(VIDEO_APP_5), \
+ DEFINE_KEYCODE(VIDEO_APP_6), \
+ DEFINE_KEYCODE(VIDEO_APP_7), \
+ DEFINE_KEYCODE(VIDEO_APP_8), \
+ DEFINE_KEYCODE(FEATURED_APP_1), \
+ DEFINE_KEYCODE(FEATURED_APP_2), \
+ DEFINE_KEYCODE(FEATURED_APP_3), \
+ DEFINE_KEYCODE(FEATURED_APP_4), \
+ DEFINE_KEYCODE(DEMO_APP_1), \
+ DEFINE_KEYCODE(DEMO_APP_2), \
+ DEFINE_KEYCODE(DEMO_APP_3), \
+ DEFINE_KEYCODE(DEMO_APP_4), \
+ DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_DOWN), \
+ DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_UP), \
+ DEFINE_KEYCODE(KEYBOARD_BACKLIGHT_TOGGLE), \
+ DEFINE_KEYCODE(STYLUS_BUTTON_PRIMARY), \
+ DEFINE_KEYCODE(STYLUS_BUTTON_SECONDARY), \
+ DEFINE_KEYCODE(STYLUS_BUTTON_TERTIARY), \
+ DEFINE_KEYCODE(STYLUS_BUTTON_TAIL), \
+ DEFINE_KEYCODE(RECENT_APPS), \
+ DEFINE_KEYCODE(MACRO_1), \
+ DEFINE_KEYCODE(MACRO_2), \
+ DEFINE_KEYCODE(MACRO_3), \
+ DEFINE_KEYCODE(MACRO_4)
// 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.
@@ -366,8 +400,13 @@ namespace android {
DEFINE_AXIS(GENERIC_13), \
DEFINE_AXIS(GENERIC_14), \
DEFINE_AXIS(GENERIC_15), \
- DEFINE_AXIS(GENERIC_16)
-
+ DEFINE_AXIS(GENERIC_16), \
+ DEFINE_AXIS(GESTURE_X_OFFSET), \
+ DEFINE_AXIS(GESTURE_Y_OFFSET), \
+ DEFINE_AXIS(GESTURE_SCROLL_X_DISTANCE), \
+ DEFINE_AXIS(GESTURE_SCROLL_Y_DISTANCE), \
+ DEFINE_AXIS(GESTURE_PINCH_SCALE_FACTOR), \
+ DEFINE_AXIS(GESTURE_SWIPE_FINGER_COUNT)
// NOTE: If you add new LEDs here, you must also add them to Input.h
#define LEDS_SEQUENCE \
@@ -391,26 +430,26 @@ namespace android {
DEFINE_FLAG(VIRTUAL), \
DEFINE_FLAG(FUNCTION), \
DEFINE_FLAG(GESTURE), \
- DEFINE_FLAG(WAKE)
-
-// --- InputEventLookup ---
-const std::unordered_map<std::string, int> InputEventLookup::KEYCODES = {KEYCODES_SEQUENCE};
+ DEFINE_FLAG(WAKE), \
+ DEFINE_FLAG(FALLBACK_USAGE_MAPPING)
-const std::vector<InputEventLabel> InputEventLookup::KEY_NAMES = {KEYCODES_SEQUENCE};
+// clang-format on
-const std::unordered_map<std::string, int> InputEventLookup::AXES = {AXES_SEQUENCE};
-
-const std::vector<InputEventLabel> InputEventLookup::AXES_NAMES = {AXES_SEQUENCE};
-
-const std::unordered_map<std::string, int> InputEventLookup::LEDS = {LEDS_SEQUENCE};
+// --- InputEventLookup ---
-const std::unordered_map<std::string, int> InputEventLookup::FLAGS = {FLAGS_SEQUENCE};
+InputEventLookup::InputEventLookup()
+ : KEYCODES({KEYCODES_SEQUENCE}),
+ KEY_NAMES({KEYCODES_SEQUENCE}),
+ AXES({AXES_SEQUENCE}),
+ AXES_NAMES({AXES_SEQUENCE}),
+ LEDS({LEDS_SEQUENCE}),
+ FLAGS({FLAGS_SEQUENCE}) {}
-int InputEventLookup::lookupValueByLabel(const std::unordered_map<std::string, int>& map,
- const char* literal) {
+std::optional<int> InputEventLookup::lookupValueByLabel(
+ const std::unordered_map<std::string, int>& map, const char* literal) {
std::string str(literal);
auto it = map.find(str);
- return it != map.end() ? it->second : 0;
+ return it != map.end() ? std::make_optional(it->second) : std::nullopt;
}
const char* InputEventLookup::lookupLabelByValue(const std::vector<InputEventLabel>& vec,
@@ -421,31 +460,139 @@ const char* InputEventLookup::lookupLabelByValue(const std::vector<InputEventLab
return nullptr;
}
-int32_t InputEventLookup::getKeyCodeByLabel(const char* label) {
- return int32_t(lookupValueByLabel(KEYCODES, label));
+std::optional<int> InputEventLookup::getKeyCodeByLabel(const char* label) {
+ const auto& self = get();
+ return self.lookupValueByLabel(self.KEYCODES, label);
}
const char* InputEventLookup::getLabelByKeyCode(int32_t keyCode) {
- if (keyCode >= 0 && static_cast<size_t>(keyCode) < KEYCODES.size()) {
- return lookupLabelByValue(KEY_NAMES, keyCode);
+ const auto& self = get();
+ if (keyCode >= 0 && static_cast<size_t>(keyCode) < self.KEYCODES.size()) {
+ return get().lookupLabelByValue(self.KEY_NAMES, keyCode);
}
return nullptr;
}
-uint32_t InputEventLookup::getKeyFlagByLabel(const char* label) {
- return uint32_t(lookupValueByLabel(FLAGS, label));
+std::optional<int> InputEventLookup::getKeyFlagByLabel(const char* label) {
+ const auto& self = get();
+ return lookupValueByLabel(self.FLAGS, label);
}
-int32_t InputEventLookup::getAxisByLabel(const char* label) {
- return int32_t(lookupValueByLabel(AXES, label));
+std::optional<int> InputEventLookup::getAxisByLabel(const char* label) {
+ const auto& self = get();
+ return lookupValueByLabel(self.AXES, label);
}
const char* InputEventLookup::getAxisLabel(int32_t axisId) {
- return lookupLabelByValue(AXES_NAMES, axisId);
+ const auto& self = get();
+ return lookupLabelByValue(self.AXES_NAMES, axisId);
+}
+
+std::optional<int> InputEventLookup::getLedByLabel(const char* label) {
+ const auto& self = get();
+ return lookupValueByLabel(self.LEDS, label);
+}
+
+namespace {
+
+struct label {
+ const char* name;
+ int value;
+};
+
+#define LABEL(constant) \
+ { #constant, constant }
+#define LABEL_END \
+ { nullptr, -1 }
+
+static struct label ev_key_value_labels[] = {
+ {"UP", 0},
+ {"DOWN", 1},
+ {"REPEAT", 2},
+ LABEL_END,
+};
+
+#include "input.h-labels.h"
+
+#undef LABEL
+#undef LABEL_END
+
+std::string getLabel(const label* labels, int value) {
+ if (labels == nullptr) return std::to_string(value);
+ while (labels->name != nullptr && value != labels->value) {
+ labels++;
+ }
+ return labels->name != nullptr ? labels->name : std::to_string(value);
+}
+
+std::optional<int> getValue(const label* labels, const char* searchLabel) {
+ if (labels == nullptr) return {};
+ while (labels->name != nullptr && ::strcasecmp(labels->name, searchLabel) != 0) {
+ labels++;
+ }
+ return labels->name != nullptr ? std::make_optional(labels->value) : std::nullopt;
+}
+
+const label* getCodeLabelsForType(int32_t type) {
+ switch (type) {
+ case EV_SYN:
+ return syn_labels;
+ case EV_KEY:
+ return key_labels;
+ case EV_REL:
+ return rel_labels;
+ case EV_ABS:
+ return abs_labels;
+ case EV_SW:
+ return sw_labels;
+ case EV_MSC:
+ return msc_labels;
+ case EV_LED:
+ return led_labels;
+ case EV_REP:
+ return rep_labels;
+ case EV_SND:
+ return snd_labels;
+ case EV_FF:
+ return ff_labels;
+ case EV_FF_STATUS:
+ return ff_status_labels;
+ default:
+ return nullptr;
+ }
+}
+
+const label* getValueLabelsForTypeAndCode(int32_t type, int32_t code) {
+ if (type == EV_KEY) {
+ return ev_key_value_labels;
+ }
+ if (type == EV_ABS && code == ABS_MT_TOOL_TYPE) {
+ return mt_tool_labels;
+ }
+ return nullptr;
+}
+
+} // namespace
+
+EvdevEventLabel InputEventLookup::getLinuxEvdevLabel(int32_t type, int32_t code, int32_t value) {
+ return {
+ .type = getLabel(ev_labels, type),
+ .code = getLabel(getCodeLabelsForType(type), code),
+ .value = getLabel(getValueLabelsForTypeAndCode(type, code), value),
+ };
+}
+
+std::optional<int> InputEventLookup::getLinuxEvdevEventTypeByLabel(const char* label) {
+ return getValue(ev_labels, label);
+}
+
+std::optional<int> InputEventLookup::getLinuxEvdevEventCodeByLabel(int32_t type,
+ const char* label) {
+ return getValue(getCodeLabelsForType(type), label);
}
-int32_t InputEventLookup::getLedByLabel(const char* label) {
- return int32_t(lookupValueByLabel(LEDS, label));
+std::optional<int> InputEventLookup::getLinuxEvdevInputPropByLabel(const char* label) {
+ return getValue(input_prop_labels, label);
}
} // namespace android
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index 61950522ff..09e98d0515 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -4,20 +4,7 @@
// Provides a shared memory transport for input events.
//
#define LOG_TAG "InputTransport"
-
-//#define LOG_NDEBUG 0
-
-// Log debug messages about channel messages (send message, receive message)
-#define DEBUG_CHANNEL_MESSAGES 0
-
-// Log debug messages whenever InputChannel objects are created/destroyed
-static constexpr bool DEBUG_CHANNEL_LIFECYCLE = false;
-
-// Log debug messages about transport actions
-static constexpr bool DEBUG_TRANSPORT_ACTIONS = false;
-
-// Log debug messages about touch event resampling
-#define DEBUG_RESAMPLING 0
+#define ATRACE_TAG ATRACE_TAG_INPUT
#include <errno.h>
#include <fcntl.h>
@@ -27,6 +14,8 @@ static constexpr bool DEBUG_TRANSPORT_ACTIONS = false;
#include <sys/types.h>
#include <unistd.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <binder/Parcel.h>
#include <cutils/properties.h>
@@ -34,8 +23,80 @@ static constexpr bool DEBUG_TRANSPORT_ACTIONS = false;
#include <log/log.h>
#include <utils/Trace.h>
+#include <com_android_input_flags.h>
#include <input/InputTransport.h>
+#include <input/TraceTools.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace {
+
+/**
+ * Log debug messages about channel messages (send message, receive message).
+ * Enable this via "adb shell setprop log.tag.InputTransportMessages DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_CHANNEL_MESSAGES =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Messages", ANDROID_LOG_INFO);
+
+/**
+ * Log debug messages whenever InputChannel objects are created/destroyed.
+ * Enable this via "adb shell setprop log.tag.InputTransportLifecycle DEBUG"
+ * (requires restart)
+ */
+const bool DEBUG_CHANNEL_LIFECYCLE =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Lifecycle", ANDROID_LOG_INFO);
+/**
+ * Log debug messages relating to the consumer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
+ */
+
+const bool DEBUG_TRANSPORT_CONSUMER =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+ android::base::GetBoolProperty("ro.debuggable", false);
+#else
+ true;
+#endif
+
+/**
+ * Log debug messages relating to the producer end of the transport channel.
+ * Enable this via "adb shell setprop log.tag.InputTransportPublisher DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
+ */
+bool debugTransportPublisher() {
+ if (!IS_DEBUGGABLE_BUILD) {
+ static const bool DEBUG_TRANSPORT_PUBLISHER =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
+ return DEBUG_TRANSPORT_PUBLISHER;
+ }
+ return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Publisher", ANDROID_LOG_INFO);
+}
+
+/**
+ * Log debug messages about touch event resampling.
+ *
+ * Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
+ */
+bool debugResampling() {
+ if (!IS_DEBUGGABLE_BUILD) {
+ static const bool DEBUG_TRANSPORT_RESAMPLING =
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
+ ANDROID_LOG_INFO);
+ return DEBUG_TRANSPORT_RESAMPLING;
+ }
+ return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
+}
+
+} // namespace
+
+using android::base::Result;
using android::base::StringPrintf;
namespace android {
@@ -51,7 +112,7 @@ static const nsecs_t NANOS_PER_MS = 1000000;
// Latency added during resampling. A few milliseconds doesn't hurt much but
// reduces the impact of mispredicted touch positions.
-static const nsecs_t RESAMPLE_LATENCY = 5 * NANOS_PER_MS;
+const std::chrono::duration RESAMPLE_LATENCY = 5ms;
// Minimum time difference between consecutive samples before attempting to resample.
static const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
@@ -76,6 +137,15 @@ static const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
*/
static const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
+/**
+ * Crash if the events that are getting sent to the InputPublisher are inconsistent.
+ * Enable this via "adb shell setprop log.tag.InputTransportVerifyEvents DEBUG"
+ */
+static bool verifyEvents() {
+ return input_flags::enable_outbound_event_verification() ||
+ __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "VerifyEvents", ANDROID_LOG_INFO);
+}
+
template<typename T>
inline static T min(const T& a, const T& b) {
return a < b ? a : b;
@@ -93,6 +163,10 @@ inline static const char* toString(bool value) {
return value ? "true" : "false";
}
+static bool shouldResampleTool(ToolType toolType) {
+ return toolType == ToolType::FINGER || toolType == ToolType::UNKNOWN;
+}
+
// --- InputMessage ---
bool InputMessage::isValid(size_t actualSize) const {
@@ -132,7 +206,7 @@ bool InputMessage::isValid(size_t actualSize) const {
return valid;
}
}
- ALOGE("Invalid message type: %" PRIu32, header.type);
+ ALOGE("Invalid message type: %s", ftl::enum_string(header.type).c_str());
return false;
}
@@ -267,6 +341,8 @@ void InputMessage::getSanitizedCopy(InputMessage* msg) const {
memcpy(&msg->body.motion.pointers[i].coords.values[0],
&body.motion.pointers[i].coords.values[0],
count * (sizeof(body.motion.pointers[i].coords.values[0])));
+ msg->body.motion.pointers[i].coords.isResampled =
+ body.motion.pointers[i].coords.isResampled;
}
break;
}
@@ -320,15 +396,13 @@ std::unique_ptr<InputChannel> InputChannel::create(const std::string& name,
InputChannel::InputChannel(const std::string name, android::base::unique_fd fd, sp<IBinder> token)
: mName(std::move(name)), mFd(std::move(fd)), mToken(std::move(token)) {
- if (DEBUG_CHANNEL_LIFECYCLE) {
- ALOGD("Input channel constructed: name='%s', fd=%d", getName().c_str(), getFd().get());
- }
+ ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel constructed: name='%s', fd=%d",
+ getName().c_str(), getFd().get());
}
InputChannel::~InputChannel() {
- if (DEBUG_CHANNEL_LIFECYCLE) {
- ALOGD("Input channel destroyed: name='%s', fd=%d", getName().c_str(), getFd().get());
- }
+ ALOGD_IF(DEBUG_CHANNEL_LIFECYCLE, "Input channel destroyed: name='%s', fd=%d",
+ getName().c_str(), getFd().get());
}
status_t InputChannel::openInputChannelPair(const std::string& name,
@@ -350,7 +424,7 @@ status_t InputChannel::openInputChannelPair(const std::string& name,
setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
- sp<IBinder> token = new BBinder();
+ sp<IBinder> token = sp<BBinder>::make();
std::string serverChannelName = name + " (server)";
android::base::unique_fd serverFd(sockets[0]);
@@ -363,20 +437,22 @@ status_t InputChannel::openInputChannelPair(const std::string& name,
}
status_t InputChannel::sendMessage(const InputMessage* msg) {
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("sendMessage(inputChannel=%s, seq=0x%" PRIx32 ", type=0x%" PRIx32
+ ")",
+ mName.c_str(), msg->header.seq, msg->header.type));
const size_t msgLength = msg->size();
InputMessage cleanMsg;
msg->getSanitizedCopy(&cleanMsg);
ssize_t nWrite;
do {
- nWrite = ::send(getFd(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
+ nWrite = ::send(getFd().get(), &cleanMsg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
} while (nWrite == -1 && errno == EINTR);
if (nWrite < 0) {
int error = errno;
-#if DEBUG_CHANNEL_MESSAGES
- ALOGD("channel '%s' ~ error sending message of type %d, %s", mName.c_str(),
- msg->header.type, strerror(error));
-#endif
+ ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ error sending message of type %s, %s",
+ mName.c_str(), ftl::enum_string(msg->header.type).c_str(), strerror(error));
if (error == EAGAIN || error == EWOULDBLOCK) {
return WOULD_BLOCK;
}
@@ -387,30 +463,28 @@ status_t InputChannel::sendMessage(const InputMessage* msg) {
}
if (size_t(nWrite) != msgLength) {
-#if DEBUG_CHANNEL_MESSAGES
- ALOGD("channel '%s' ~ error sending message type %d, send was incomplete",
- mName.c_str(), msg->header.type);
-#endif
+ ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
+ "channel '%s' ~ error sending message type %s, send was incomplete", mName.c_str(),
+ ftl::enum_string(msg->header.type).c_str());
return DEAD_OBJECT;
}
-#if DEBUG_CHANNEL_MESSAGES
- ALOGD("channel '%s' ~ sent message of type %d", mName.c_str(), msg->header.type);
-#endif
+ ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ sent message of type %s", mName.c_str(),
+ ftl::enum_string(msg->header.type).c_str());
+
return OK;
}
status_t InputChannel::receiveMessage(InputMessage* msg) {
ssize_t nRead;
do {
- nRead = ::recv(getFd(), msg, sizeof(InputMessage), MSG_DONTWAIT);
+ nRead = ::recv(getFd().get(), msg, sizeof(InputMessage), MSG_DONTWAIT);
} while (nRead == -1 && errno == EINTR);
if (nRead < 0) {
int error = errno;
-#if DEBUG_CHANNEL_MESSAGES
- ALOGD("channel '%s' ~ receive message failed, errno=%d", mName.c_str(), errno);
-#endif
+ ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ receive message failed, errno=%d",
+ mName.c_str(), errno);
if (error == EAGAIN || error == EWOULDBLOCK) {
return WOULD_BLOCK;
}
@@ -421,9 +495,8 @@ status_t InputChannel::receiveMessage(InputMessage* msg) {
}
if (nRead == 0) { // check for EOF
-#if DEBUG_CHANNEL_MESSAGES
- ALOGD("channel '%s' ~ receive message failed because peer was closed", mName.c_str());
-#endif
+ ALOGD_IF(DEBUG_CHANNEL_MESSAGES,
+ "channel '%s' ~ receive message failed because peer was closed", mName.c_str());
return DEAD_OBJECT;
}
@@ -432,9 +505,15 @@ status_t InputChannel::receiveMessage(InputMessage* msg) {
return BAD_VALUE;
}
-#if DEBUG_CHANNEL_MESSAGES
- ALOGD("channel '%s' ~ received message of type %d", mName.c_str(), msg->header.type);
-#endif
+ ALOGD_IF(DEBUG_CHANNEL_MESSAGES, "channel '%s' ~ received message of type %s", mName.c_str(),
+ ftl::enum_string(msg->header.type).c_str());
+ if (ATRACE_ENABLED()) {
+ // Add an additional trace point to include data about the received message.
+ std::string message = StringPrintf("receiveMessage(inputChannel=%s, seq=0x%" PRIx32
+ ", type=0x%" PRIx32 ")",
+ mName.c_str(), msg->header.seq, msg->header.type);
+ ATRACE_NAME(message.c_str());
+ }
return OK;
}
@@ -472,7 +551,7 @@ sp<IBinder> InputChannel::getConnectionToken() const {
}
base::unique_fd InputChannel::dupFd() const {
- android::base::unique_fd newFd(::dup(getFd()));
+ base::unique_fd newFd(::dup(getFd().get()));
if (!newFd.ok()) {
ALOGE("Could not duplicate fd %i for channel %s: %s", getFd().get(), getName().c_str(),
strerror(errno));
@@ -490,7 +569,8 @@ base::unique_fd InputChannel::dupFd() const {
// --- InputPublisher ---
-InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel) : mChannel(channel) {}
+InputPublisher::InputPublisher(const std::shared_ptr<InputChannel>& channel)
+ : mChannel(channel), mInputVerifier(channel->getName()) {}
InputPublisher::~InputPublisher() {
}
@@ -501,18 +581,17 @@ status_t InputPublisher::publishKeyEvent(uint32_t seq, int32_t eventId, int32_t
int32_t flags, int32_t keyCode, int32_t scanCode,
int32_t metaState, int32_t repeatCount, nsecs_t downTime,
nsecs_t eventTime) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("publishKeyEvent(inputChannel=%s, keyCode=%" PRId32 ")",
- mChannel->getName().c_str(), keyCode);
- ATRACE_NAME(message.c_str());
- }
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' publisher ~ publishKeyEvent: seq=%u, deviceId=%d, source=0x%x, "
- "action=0x%x, flags=0x%x, keyCode=%d, scanCode=%d, metaState=0x%x, repeatCount=%d,"
- "downTime=%" PRId64 ", eventTime=%" PRId64,
- mChannel->getName().c_str(), seq, deviceId, source, action, flags, keyCode, scanCode,
- metaState, repeatCount, downTime, eventTime);
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishKeyEvent(inputChannel=%s, action=%s, keyCode=%s)",
+ mChannel->getName().c_str(), KeyEvent::actionToString(action),
+ KeyEvent::getLabel(keyCode)));
+ ALOGD_IF(debugTransportPublisher(),
+ "channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
+ "action=%s, flags=0x%x, keyCode=%s, scanCode=%d, metaState=0x%x, repeatCount=%d,"
+ "downTime=%" PRId64 ", eventTime=%" PRId64,
+ mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
+ inputEventSourceToString(source).c_str(), KeyEvent::actionToString(action), flags,
+ KeyEvent::getLabel(keyCode), scanCode, metaState, repeatCount, downTime, eventTime);
if (!seq) {
ALOGE("Attempted to publish a key event with sequence number 0.");
@@ -547,25 +626,32 @@ status_t InputPublisher::publishMotionEvent(
const ui::Transform& rawTransform, nsecs_t downTime, nsecs_t eventTime,
uint32_t pointerCount, const PointerProperties* pointerProperties,
const PointerCoords* pointerCoords) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf(
- "publishMotionEvent(inputChannel=%s, action=%" PRId32 ")",
- mChannel->getName().c_str(), action);
- ATRACE_NAME(message.c_str());
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishMotionEvent(inputChannel=%s, action=%s)",
+ mChannel->getName().c_str(),
+ MotionEvent::actionToString(action).c_str()));
+ if (verifyEvents()) {
+ Result<void> result =
+ mInputVerifier.processMovement(deviceId, source, action, pointerCount,
+ pointerProperties, pointerCoords, flags);
+ if (!result.ok()) {
+ LOG(FATAL) << "Bad stream: " << result.error();
+ }
}
- if (DEBUG_TRANSPORT_ACTIONS) {
+ if (debugTransportPublisher()) {
std::string transformString;
transform.dump(transformString, "transform", " ");
- ALOGD("channel '%s' publisher ~ publishMotionEvent: seq=%u, deviceId=%d, source=0x%x, "
+ ALOGD("channel '%s' publisher ~ %s: seq=%u, id=%d, deviceId=%d, source=%s, "
"displayId=%" PRId32 ", "
- "action=0x%x, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
+ "action=%s, actionButton=0x%08x, flags=0x%x, edgeFlags=0x%x, "
"metaState=0x%x, buttonState=0x%x, classification=%s,"
"xPrecision=%f, yPrecision=%f, downTime=%" PRId64 ", eventTime=%" PRId64 ", "
"pointerCount=%" PRIu32 " \n%s",
- mChannel->getName().c_str(), seq, deviceId, source, displayId, action, actionButton,
- flags, edgeFlags, metaState, buttonState,
- motionClassificationToString(classification), xPrecision, yPrecision, downTime,
- eventTime, pointerCount, transformString.c_str());
+ mChannel->getName().c_str(), __func__, seq, eventId, deviceId,
+ inputEventSourceToString(source).c_str(), displayId,
+ MotionEvent::actionToString(action).c_str(), actionButton, flags, edgeFlags,
+ metaState, buttonState, motionClassificationToString(classification), xPrecision,
+ yPrecision, downTime, eventTime, pointerCount, transformString.c_str());
}
if (!seq) {
@@ -614,19 +700,19 @@ status_t InputPublisher::publishMotionEvent(
msg.body.motion.eventTime = eventTime;
msg.body.motion.pointerCount = pointerCount;
for (uint32_t i = 0; i < pointerCount; i++) {
- msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
- msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
+ msg.body.motion.pointers[i].properties = pointerProperties[i];
+ msg.body.motion.pointers[i].coords = pointerCoords[i];
}
return mChannel->sendMessage(&msg);
}
status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool hasFocus) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
- mChannel->getName().c_str(), toString(hasFocus));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishFocusEvent(inputChannel=%s, hasFocus=%s)",
+ mChannel->getName().c_str(), toString(hasFocus)));
+ ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: seq=%u, id=%d, hasFocus=%s",
+ mChannel->getName().c_str(), __func__, seq, eventId, toString(hasFocus));
InputMessage msg;
msg.header.type = InputMessage::Type::FOCUS;
@@ -638,12 +724,12 @@ status_t InputPublisher::publishFocusEvent(uint32_t seq, int32_t eventId, bool h
status_t InputPublisher::publishCaptureEvent(uint32_t seq, int32_t eventId,
bool pointerCaptureEnabled) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishCaptureEvent(inputChannel=%s, pointerCaptureEnabled=%s)",
- mChannel->getName().c_str(), toString(pointerCaptureEnabled));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishCaptureEvent(inputChannel=%s, pointerCaptureEnabled=%s)",
+ mChannel->getName().c_str(), toString(pointerCaptureEnabled)));
+ ALOGD_IF(debugTransportPublisher(),
+ "channel '%s' publisher ~ %s: seq=%u, id=%d, pointerCaptureEnabled=%s",
+ mChannel->getName().c_str(), __func__, seq, eventId, toString(pointerCaptureEnabled));
InputMessage msg;
msg.header.type = InputMessage::Type::CAPTURE;
@@ -655,12 +741,12 @@ status_t InputPublisher::publishCaptureEvent(uint32_t seq, int32_t eventId,
status_t InputPublisher::publishDragEvent(uint32_t seq, int32_t eventId, float x, float y,
bool isExiting) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishDragEvent(inputChannel=%s, x=%f, y=%f, isExiting=%s)",
- mChannel->getName().c_str(), x, y, toString(isExiting));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishDragEvent(inputChannel=%s, x=%f, y=%f, isExiting=%s)",
+ mChannel->getName().c_str(), x, y, toString(isExiting)));
+ ALOGD_IF(debugTransportPublisher(),
+ "channel '%s' publisher ~ %s: seq=%u, id=%d, x=%f, y=%f, isExiting=%s",
+ mChannel->getName().c_str(), __func__, seq, eventId, x, y, toString(isExiting));
InputMessage msg;
msg.header.type = InputMessage::Type::DRAG;
@@ -673,12 +759,12 @@ status_t InputPublisher::publishDragEvent(uint32_t seq, int32_t eventId, float x
}
status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bool isInTouchMode) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
- mChannel->getName().c_str(), toString(isInTouchMode));
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
+ mChannel->getName().c_str(), toString(isInTouchMode)));
+ ALOGD_IF(debugTransportPublisher(),
+ "channel '%s' publisher ~ %s: seq=%u, id=%d, isInTouchMode=%s",
+ mChannel->getName().c_str(), __func__, seq, eventId, toString(isInTouchMode));
InputMessage msg;
msg.header.type = InputMessage::Type::TOUCH_MODE;
@@ -689,16 +775,20 @@ status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bo
}
android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__);
- }
-
InputMessage msg;
status_t result = mChannel->receiveMessage(&msg);
if (result) {
+ if (debugTransportPublisher() && result != WOULD_BLOCK) {
+ LOG(INFO) << "channel '" << mChannel->getName() << "' publisher ~ " << __func__ << ": "
+ << strerror(result);
+ }
return android::base::Error(result);
}
if (msg.header.type == InputMessage::Type::FINISHED) {
+ ALOGD_IF(debugTransportPublisher(),
+ "channel '%s' publisher ~ %s: finished: seq=%u, handled=%s",
+ mChannel->getName().c_str(), __func__, msg.header.seq,
+ toString(msg.body.finished.handled));
return Finished{
.seq = msg.header.seq,
.handled = msg.body.finished.handled,
@@ -707,6 +797,8 @@ android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveC
}
if (msg.header.type == InputMessage::Type::TIMELINE) {
+ ALOGD_IF(debugTransportPublisher(), "channel '%s' publisher ~ %s: timeline: id=%d",
+ mChannel->getName().c_str(), __func__, msg.body.timeline.eventId);
return Timeline{
.inputEventId = msg.body.timeline.eventId,
.graphicsTimeline = msg.body.timeline.graphicsTimeline,
@@ -721,7 +813,11 @@ android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveC
// --- InputConsumer ---
InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
- : mResampleTouch(isTouchResamplingEnabled()), mChannel(channel), mMsgDeferred(false) {}
+ : InputConsumer(channel, isTouchResamplingEnabled()) {}
+
+InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
+ bool enableTouchResampling)
+ : mResampleTouch(enableTouchResampling), mChannel(channel), mMsgDeferred(false) {}
InputConsumer::~InputConsumer() {
}
@@ -732,10 +828,9 @@ bool InputConsumer::isTouchResamplingEnabled() {
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
- mChannel->getName().c_str(), toString(consumeBatches), frameTime);
- }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
+ mChannel->getName().c_str(), toString(consumeBatches), frameTime);
*outSeq = 0;
*outEvent = nullptr;
@@ -751,17 +846,19 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum
// Receive a fresh message.
status_t result = mChannel->receiveMessage(&mMsg);
if (result == OK) {
- mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+ const auto [_, inserted] =
+ mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
+ LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
+ mMsg.header.seq);
}
if (result) {
// Consume the next batched event unless batches are being held for later.
if (consumeBatches || result != WOULD_BLOCK) {
result = consumeBatch(factory, frameTime, outSeq, outEvent);
if (*outEvent) {
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed batch event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
break;
}
}
@@ -777,11 +874,10 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum
initializeKeyEvent(keyEvent, &mMsg);
*outSeq = mMsg.header.seq;
*outEvent = keyEvent;
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ consumed key event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- }
- break;
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed key event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
+ break;
}
case InputMessage::Type::MOTION: {
@@ -790,11 +886,10 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum
Batch& batch = mBatches[batchIndex];
if (canAddSample(batch, &mMsg)) {
batch.samples.push_back(mMsg);
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ appended to batch event",
- mChannel->getName().c_str());
- }
- break;
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ appended to batch event",
+ mChannel->getName().c_str());
+ break;
} else if (isPointerEvent(mMsg.body.motion.source) &&
mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
// No need to process events that we are going to cancel anyways
@@ -815,12 +910,11 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum
if (result) {
return result;
}
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ consumed batch event and "
- "deferred current event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- }
- break;
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed batch event and "
+ "deferred current event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
+ break;
}
}
@@ -830,10 +924,9 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum
Batch batch;
batch.samples.push_back(mMsg);
mBatches.push_back(batch);
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ started batch event",
- mChannel->getName().c_str());
- }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ started batch event",
+ mChannel->getName().c_str());
break;
}
@@ -845,10 +938,9 @@ status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consum
*outSeq = mMsg.header.seq;
*outEvent = motionEvent;
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ consumed motion event, seq=%u",
- mChannel->getName().c_str(), *outSeq);
- }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ consumed motion event, seq=%u",
+ mChannel->getName().c_str(), *outSeq);
break;
}
@@ -918,7 +1010,7 @@ status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
nsecs_t sampleTime = frameTime;
if (mResampleTouch) {
- sampleTime -= RESAMPLE_LATENCY;
+ sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
}
ssize_t split = findSampleNoLaterThan(batch, sampleTime);
if (split < 0) {
@@ -1065,13 +1157,12 @@ void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
state.recentCoordinatesAreIdentical(id)) {
PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
-#if DEBUG_RESAMPLING
- ALOGD("[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
- resampleCoords.getX(), resampleCoords.getY(),
- msgCoords.getX(), msgCoords.getY());
-#endif
+ ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
+ resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
+ msgCoords.getY());
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
+ msgCoords.isResampled = true;
} else {
state.lastResample.idBits.clearBit(id);
}
@@ -1089,17 +1180,13 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
if (index < 0) {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, no touch state for device.");
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
return;
}
TouchState& touchState = mTouchStates[index];
if (touchState.historySize < 1) {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, no history for device.");
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
return;
}
@@ -1109,9 +1196,7 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
for (size_t i = 0; i < pointerCount; i++) {
uint32_t id = event->getPointerId(i);
if (!current->idBits.hasBit(id)) {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, missing id %d", id);
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
return;
}
}
@@ -1127,9 +1212,8 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
other = &future;
nsecs_t delta = future.eventTime - current->eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta);
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+ delta);
return;
}
alpha = float(sampleTime - current->eventTime) / delta;
@@ -1139,30 +1223,30 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
other = touchState.getHistory(1);
nsecs_t delta = current->eventTime - other->eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, delta time is too small: %" PRId64 " ns.", delta);
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
+ delta);
return;
} else if (delta > RESAMPLE_MAX_DELTA) {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, delta time is too large: %" PRId64 " ns.", delta);
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
+ delta);
return;
}
nsecs_t maxPredict = current->eventTime + min(delta / 2, RESAMPLE_MAX_PREDICTION);
if (sampleTime > maxPredict) {
-#if DEBUG_RESAMPLING
- ALOGD("Sample time is too far in the future, adjusting prediction "
- "from %" PRId64 " to %" PRId64 " ns.",
- sampleTime - current->eventTime, maxPredict - current->eventTime);
-#endif
+ ALOGD_IF(debugResampling(),
+ "Sample time is too far in the future, adjusting prediction "
+ "from %" PRId64 " to %" PRId64 " ns.",
+ sampleTime - current->eventTime, maxPredict - current->eventTime);
sampleTime = maxPredict;
}
alpha = float(current->eventTime - sampleTime) / delta;
} else {
-#if DEBUG_RESAMPLING
- ALOGD("Not resampled, insufficient data.");
-#endif
+ ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
+ return;
+ }
+
+ if (current->eventTime == sampleTime) {
+ // Prevents having 2 events with identical times and coordinates.
return;
}
@@ -1179,54 +1263,45 @@ void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
// We maintain the previously resampled value for this pointer (stored in
// oldLastResample) when the coordinates for this pointer haven't changed since then.
// This way we don't introduce artificial jitter when pointers haven't actually moved.
+ // The isResampled flag isn't cleared as the values don't reflect what the device is
+ // actually reporting.
// We know here that the coordinates for the pointer haven't changed because we
// would've cleared the resampled bit in rewriteMessage if they had. We can't modify
// lastResample in place becasue the mapping from pointer ID to index may have changed.
- touchState.lastResample.pointers[i].copyFrom(oldLastResample.getPointerById(id));
+ touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
continue;
}
PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
const PointerCoords& currentCoords = current->getPointerById(id);
- resampledCoords.copyFrom(currentCoords);
- if (other->idBits.hasBit(id)
- && shouldResampleTool(event->getToolType(i))) {
+ resampledCoords = currentCoords;
+ resampledCoords.isResampled = true;
+ if (other->idBits.hasBit(id) && shouldResampleTool(event->getToolType(i))) {
const PointerCoords& otherCoords = other->getPointerById(id);
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
- lerp(currentCoords.getX(), otherCoords.getX(), alpha));
+ lerp(currentCoords.getX(), otherCoords.getX(), alpha));
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
- lerp(currentCoords.getY(), otherCoords.getY(), alpha));
-#if DEBUG_RESAMPLING
- ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
- "other (%0.3f, %0.3f), alpha %0.3f",
- id, resampledCoords.getX(), resampledCoords.getY(),
- currentCoords.getX(), currentCoords.getY(),
- otherCoords.getX(), otherCoords.getY(),
- alpha);
-#endif
+ lerp(currentCoords.getY(), otherCoords.getY(), alpha));
+ ALOGD_IF(debugResampling(),
+ "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
+ "other (%0.3f, %0.3f), alpha %0.3f",
+ id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+ currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
} else {
-#if DEBUG_RESAMPLING
- ALOGD("[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)",
- id, resampledCoords.getX(), resampledCoords.getY(),
- currentCoords.getX(), currentCoords.getY());
-#endif
+ ALOGD_IF(debugResampling(), "[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f)", id,
+ resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
+ currentCoords.getY());
}
}
event->addSample(sampleTime, touchState.lastResample.pointers);
}
-bool InputConsumer::shouldResampleTool(int32_t toolType) {
- return toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
- || toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN;
-}
-
status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
- mChannel->getName().c_str(), seq, toString(handled));
- }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
+ mChannel->getName().c_str(), seq, toString(handled));
if (!seq) {
ALOGE("Attempted to send a finished signal with sequence number 0.");
@@ -1273,13 +1348,12 @@ status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
status_t InputConsumer::sendTimeline(int32_t inputEventId,
std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
- if (DEBUG_TRANSPORT_ACTIONS) {
- ALOGD("channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
- ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
- mChannel->getName().c_str(), inputEventId,
- graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
- graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
- }
+ ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
+ "channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
+ ", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
+ mChannel->getName().c_str(), inputEventId,
+ graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
+ graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
InputMessage msg;
msg.header.type = InputMessage::Type::TIMELINE;
@@ -1379,8 +1453,8 @@ void InputConsumer::initializeMotionEvent(MotionEvent* event, const InputMessage
PointerProperties pointerProperties[pointerCount];
PointerCoords pointerCoords[pointerCount];
for (uint32_t i = 0; i < pointerCount; i++) {
- pointerProperties[i].copyFrom(msg->body.motion.pointers[i].properties);
- pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ pointerProperties[i] = msg->body.motion.pointers[i].properties;
+ pointerCoords[i] = msg->body.motion.pointers[i].coords;
}
ui::Transform transform;
@@ -1409,7 +1483,7 @@ void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
uint32_t pointerCount = msg->body.motion.pointerCount;
PointerCoords pointerCoords[pointerCount];
for (uint32_t i = 0; i < pointerCount; i++) {
- pointerCoords[i].copyFrom(msg->body.motion.pointers[i].coords);
+ pointerCoords[i] = msg->body.motion.pointers[i].coords;
}
event->setMetaState(event->getMetaState() | msg->body.motion.metaState);
diff --git a/libs/input/InputVerifier.cpp b/libs/input/InputVerifier.cpp
new file mode 100644
index 0000000000..cec244539e
--- /dev/null
+++ b/libs/input/InputVerifier.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "InputVerifier"
+
+#include <android-base/logging.h>
+#include <input/InputVerifier.h>
+#include "input_cxx_bridge.rs.h"
+
+using android::base::Error;
+using android::base::Result;
+using android::input::RustPointerProperties;
+
+using DeviceId = int32_t;
+
+namespace android {
+
+// --- InputVerifier ---
+
+InputVerifier::InputVerifier(const std::string& name)
+ : mVerifier(android::input::verifier::create(rust::String::lossy(name))){};
+
+Result<void> InputVerifier::processMovement(DeviceId deviceId, int32_t source, int32_t action,
+ uint32_t pointerCount,
+ const PointerProperties* pointerProperties,
+ const PointerCoords* pointerCoords, int32_t flags) {
+ std::vector<RustPointerProperties> rpp;
+ for (size_t i = 0; i < pointerCount; i++) {
+ rpp.emplace_back(RustPointerProperties{.id = pointerProperties[i].id});
+ }
+ rust::Slice<const RustPointerProperties> properties{rpp.data(), rpp.size()};
+ rust::String errorMessage =
+ android::input::verifier::process_movement(*mVerifier, deviceId, source, action,
+ properties, static_cast<uint32_t>(flags));
+ if (errorMessage.empty()) {
+ return {};
+ } else {
+ return Error() << errorMessage;
+ }
+}
+
+void InputVerifier::resetDevice(DeviceId deviceId) {
+ android::input::verifier::reset_device(*mVerifier, deviceId);
+}
+
+} // namespace android
diff --git a/libs/input/InputWrapper.hpp b/libs/input/InputWrapper.hpp
new file mode 100644
index 0000000000..a01080d319
--- /dev/null
+++ b/libs/input/InputWrapper.hpp
@@ -0,0 +1,18 @@
+/*
+ * 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 <android/input.h>
+#include "input/Input.h"
diff --git a/libs/input/KeyCharacterMap.cpp b/libs/input/KeyCharacterMap.cpp
index 2039fa6553..e2feabcbbe 100644
--- a/libs/input/KeyCharacterMap.cpp
+++ b/libs/input/KeyCharacterMap.cpp
@@ -43,7 +43,6 @@
// Enables debug output for mapping.
#define DEBUG_MAPPING 0
-
namespace android {
static const char* WHITESPACE = " \t\r";
@@ -86,75 +85,7 @@ static String8 toString(const char16_t* chars, size_t numChars) {
// --- KeyCharacterMap ---
-KeyCharacterMap::KeyCharacterMap(const std::string& filename)
- : mType(KeyboardType::UNKNOWN), mLoadFileName(filename) {}
-
-KeyCharacterMap::KeyCharacterMap(const KeyCharacterMap& other)
- : mType(other.mType),
- mLoadFileName(other.mLoadFileName),
- mLayoutOverlayApplied(other.mLayoutOverlayApplied),
- mKeysByScanCode(other.mKeysByScanCode),
- mKeysByUsageCode(other.mKeysByUsageCode) {
- for (size_t i = 0; i < other.mKeys.size(); i++) {
- mKeys.add(other.mKeys.keyAt(i), new Key(*other.mKeys.valueAt(i)));
- }
-}
-
-KeyCharacterMap::~KeyCharacterMap() {
- clear();
-}
-
-bool KeyCharacterMap::operator==(const KeyCharacterMap& other) const {
- if (mType != other.mType) {
- return false;
- }
- if (mLoadFileName != other.mLoadFileName) {
- return false;
- }
- if (mLayoutOverlayApplied != other.mLayoutOverlayApplied) {
- return false;
- }
- if (mKeys.size() != other.mKeys.size() ||
- mKeysByScanCode.size() != other.mKeysByScanCode.size() ||
- mKeysByUsageCode.size() != other.mKeysByUsageCode.size()) {
- return false;
- }
-
- for (size_t i = 0; i < mKeys.size(); i++) {
- if (mKeys.keyAt(i) != other.mKeys.keyAt(i)) {
- return false;
- }
- const Key* key = mKeys.valueAt(i);
- const Key* otherKey = other.mKeys.valueAt(i);
- if (key->label != otherKey->label || key->number != otherKey->number) {
- return false;
- }
- }
-
- for (size_t i = 0; i < mKeysByScanCode.size(); i++) {
- if (mKeysByScanCode.keyAt(i) != other.mKeysByScanCode.keyAt(i)) {
- return false;
- }
- if (mKeysByScanCode.valueAt(i) != other.mKeysByScanCode.valueAt(i)) {
- return false;
- }
- }
-
- for (size_t i = 0; i < mKeysByUsageCode.size(); i++) {
- if (mKeysByUsageCode.keyAt(i) != other.mKeysByUsageCode.keyAt(i)) {
- return false;
- }
- if (mKeysByUsageCode.valueAt(i) != other.mKeysByUsageCode.valueAt(i)) {
- return false;
- }
- }
-
- return true;
-}
-
-bool KeyCharacterMap::operator!=(const KeyCharacterMap& other) const {
- return !(*this == other);
-}
+KeyCharacterMap::KeyCharacterMap(const std::string& filename) : mLoadFileName(filename) {}
base::Result<std::shared_ptr<KeyCharacterMap>> KeyCharacterMap::load(const std::string& filename,
Format format) {
@@ -209,7 +140,7 @@ status_t KeyCharacterMap::load(Tokenizer* tokenizer, Format format) {
#if DEBUG_PARSER_PERFORMANCE
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
ALOGD("Parsed key character map file '%s' %d lines in %0.3fms.",
- tokenizer->getFilename().string(), tokenizer->getLineNumber(), elapsedTime / 1000000.0);
+ tokenizer->getFilename().c_str(), tokenizer->getLineNumber(), elapsedTime / 1000000.0);
#endif
if (status != OK) {
ALOGE("Loading KeyCharacterMap failed with status %s", statusToString(status).c_str());
@@ -220,10 +151,6 @@ status_t KeyCharacterMap::load(Tokenizer* tokenizer, Format format) {
void KeyCharacterMap::clear() {
mKeysByScanCode.clear();
mKeysByUsageCode.clear();
- for (size_t i = 0; i < mKeys.size(); i++) {
- Key* key = mKeys.editValueAt(i);
- delete key;
- }
mKeys.clear();
mLayoutOverlayApplied = false;
mType = KeyboardType::UNKNOWN;
@@ -246,30 +173,27 @@ void KeyCharacterMap::combine(const KeyCharacterMap& overlay) {
if (mLayoutOverlayApplied) {
reloadBaseFromFile();
}
- for (size_t i = 0; i < overlay.mKeys.size(); i++) {
- int32_t keyCode = overlay.mKeys.keyAt(i);
- Key* key = overlay.mKeys.valueAt(i);
- ssize_t oldIndex = mKeys.indexOfKey(keyCode);
- if (oldIndex >= 0) {
- delete mKeys.valueAt(oldIndex);
- mKeys.editValueAt(oldIndex) = new Key(*key);
- } else {
- mKeys.add(keyCode, new Key(*key));
- }
+ for (const auto& [keyCode, key] : overlay.mKeys) {
+ mKeys.insert_or_assign(keyCode, key);
}
- for (size_t i = 0; i < overlay.mKeysByScanCode.size(); i++) {
- mKeysByScanCode.replaceValueFor(overlay.mKeysByScanCode.keyAt(i),
- overlay.mKeysByScanCode.valueAt(i));
+ for (const auto& [fromScanCode, toAndroidKeyCode] : overlay.mKeysByScanCode) {
+ mKeysByScanCode.insert_or_assign(fromScanCode, toAndroidKeyCode);
}
- for (size_t i = 0; i < overlay.mKeysByUsageCode.size(); i++) {
- mKeysByUsageCode.replaceValueFor(overlay.mKeysByUsageCode.keyAt(i),
- overlay.mKeysByUsageCode.valueAt(i));
+ for (const auto& [fromHidUsageCode, toAndroidKeyCode] : overlay.mKeysByUsageCode) {
+ mKeysByUsageCode.insert_or_assign(fromHidUsageCode, toAndroidKeyCode);
}
mLayoutOverlayApplied = true;
}
+void KeyCharacterMap::clearLayoutOverlay() {
+ if (mLayoutOverlayApplied) {
+ reloadBaseFromFile();
+ mLayoutOverlayApplied = false;
+ }
+}
+
KeyCharacterMap::KeyboardType KeyCharacterMap::getKeyboardType() const {
return mType;
}
@@ -280,8 +204,8 @@ const std::string KeyCharacterMap::getLoadFileName() const {
char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const {
char16_t result = 0;
- const Key* key;
- if (getKey(keyCode, &key)) {
+ const Key* key = getKey(keyCode);
+ if (key != nullptr) {
result = key->label;
}
#if DEBUG_MAPPING
@@ -292,8 +216,8 @@ char16_t KeyCharacterMap::getDisplayLabel(int32_t keyCode) const {
char16_t KeyCharacterMap::getNumber(int32_t keyCode) const {
char16_t result = 0;
- const Key* key;
- if (getKey(keyCode, &key)) {
+ const Key* key = getKey(keyCode);
+ if (key != nullptr) {
result = key->number;
}
#if DEBUG_MAPPING
@@ -304,9 +228,8 @@ char16_t KeyCharacterMap::getNumber(int32_t keyCode) const {
char16_t KeyCharacterMap::getCharacter(int32_t keyCode, int32_t metaState) const {
char16_t result = 0;
- const Key* key;
- const Behavior* behavior;
- if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ const Behavior* behavior = getKeyBehavior(keyCode, metaState);
+ if (behavior != nullptr) {
result = behavior->character;
}
#if DEBUG_MAPPING
@@ -321,9 +244,8 @@ bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState,
outFallbackAction->metaState = 0;
bool result = false;
- const Key* key;
- const Behavior* behavior;
- if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ const Behavior* behavior = getKeyBehavior(keyCode, metaState);
+ if (behavior != nullptr) {
if (behavior->fallbackKeyCode) {
outFallbackAction->keyCode = behavior->fallbackKeyCode;
outFallbackAction->metaState = metaState & ~behavior->metaState;
@@ -342,30 +264,26 @@ bool KeyCharacterMap::getFallbackAction(int32_t keyCode, int32_t metaState,
char16_t KeyCharacterMap::getMatch(int32_t keyCode, const char16_t* chars, size_t numChars,
int32_t metaState) const {
char16_t result = 0;
- const Key* key;
- if (getKey(keyCode, &key)) {
+ const Key* key = getKey(keyCode);
+ if (key != nullptr) {
// Try to find the most general behavior that maps to this character.
// For example, the base key behavior will usually be last in the list.
// However, if we find a perfect meta state match for one behavior then use that one.
- for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
- if (behavior->character) {
+ for (const Behavior& behavior : key->behaviors) {
+ if (behavior.character) {
for (size_t i = 0; i < numChars; i++) {
- if (behavior->character == chars[i]) {
- result = behavior->character;
- if ((behavior->metaState & metaState) == behavior->metaState) {
- goto ExactMatch;
+ if (behavior.character == chars[i]) {
+ result = behavior.character;
+ if ((behavior.metaState & metaState) == behavior.metaState) {
+ // Found exact match!
+ return result;
}
break;
}
}
}
}
- ExactMatch: ;
}
-#if DEBUG_MAPPING
- ALOGD("getMatch: keyCode=%d, chars=[%s], metaState=0x%08x ~ Result %d.",
- keyCode, toString(chars, numChars).string(), metaState, result);
-#endif
return result;
}
@@ -379,7 +297,7 @@ bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t
if (!findKey(ch, &keyCode, &metaState)) {
#if DEBUG_MAPPING
ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Failed to find mapping for character %d.",
- deviceId, toString(chars, numChars).string(), ch);
+ deviceId, toString(chars, numChars).c_str(), ch);
#endif
return false;
}
@@ -391,8 +309,8 @@ bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t
addMetaKeys(outEvents, deviceId, metaState, false, now, &currentMetaState);
}
#if DEBUG_MAPPING
- ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.",
- deviceId, toString(chars, numChars).string(), int32_t(outEvents.size()));
+ ALOGD("getEvents: deviceId=%d, chars=[%s] ~ Generated %d events.", deviceId,
+ toString(chars, numChars).c_str(), int32_t(outEvents.size()));
for (size_t i = 0; i < outEvents.size(); i++) {
ALOGD(" Key: keyCode=%d, metaState=0x%08x, %s.",
outEvents[i].getKeyCode(), outEvents[i].getMetaState(),
@@ -402,11 +320,26 @@ bool KeyCharacterMap::getEvents(int32_t deviceId, const char16_t* chars, size_t
return true;
}
+void KeyCharacterMap::addKeyRemapping(int32_t fromKeyCode, int32_t toKeyCode) {
+ if (fromKeyCode == toKeyCode) {
+ mKeyRemapping.erase(fromKeyCode);
+#if DEBUG_MAPPING
+ ALOGD("addKeyRemapping: Cleared remapping forKeyCode=%d ~ Result Successful.", fromKeyCode);
+#endif
+ return;
+ }
+ mKeyRemapping.insert_or_assign(fromKeyCode, toKeyCode);
+#if DEBUG_MAPPING
+ ALOGD("addKeyRemapping: fromKeyCode=%d, toKeyCode=%d ~ Result Successful.", fromKeyCode,
+ toKeyCode);
+#endif
+}
+
status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* outKeyCode) const {
if (usageCode) {
- ssize_t index = mKeysByUsageCode.indexOfKey(usageCode);
- if (index >= 0) {
- *outKeyCode = mKeysByUsageCode.valueAt(index);
+ const auto it = mKeysByUsageCode.find(usageCode);
+ if (it != mKeysByUsageCode.end()) {
+ *outKeyCode = it->second;
#if DEBUG_MAPPING
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
scanCode, usageCode, *outKeyCode);
@@ -415,9 +348,9 @@ status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* o
}
}
if (scanCode) {
- ssize_t index = mKeysByScanCode.indexOfKey(scanCode);
- if (index >= 0) {
- *outKeyCode = mKeysByScanCode.valueAt(index);
+ const auto it = mKeysByScanCode.find(scanCode);
+ if (it != mKeysByScanCode.end()) {
+ *outKeyCode = it->second;
#if DEBUG_MAPPING
ALOGD("mapKey: scanCode=%d, usageCode=0x%08x ~ Result keyCode=%d.",
scanCode, usageCode, *outKeyCode);
@@ -433,72 +366,80 @@ status_t KeyCharacterMap::mapKey(int32_t scanCode, int32_t usageCode, int32_t* o
return NAME_NOT_FOUND;
}
-void KeyCharacterMap::tryRemapKey(int32_t keyCode, int32_t metaState,
- int32_t *outKeyCode, int32_t *outMetaState) const {
- *outKeyCode = keyCode;
- *outMetaState = metaState;
+int32_t KeyCharacterMap::applyKeyRemapping(int32_t fromKeyCode) const {
+ int32_t toKeyCode = fromKeyCode;
- const Key* key;
- const Behavior* behavior;
- if (getKeyBehavior(keyCode, metaState, &key, &behavior)) {
+ const auto it = mKeyRemapping.find(fromKeyCode);
+ if (it != mKeyRemapping.end()) {
+ toKeyCode = it->second;
+ }
+#if DEBUG_MAPPING
+ ALOGD("applyKeyRemapping: keyCode=%d ~ replacement keyCode=%d.", fromKeyCode, toKeyCode);
+#endif
+ return toKeyCode;
+}
+
+std::pair<int32_t, int32_t> KeyCharacterMap::applyKeyBehavior(int32_t fromKeyCode,
+ int32_t fromMetaState) const {
+ int32_t toKeyCode = fromKeyCode;
+ int32_t toMetaState = fromMetaState;
+
+ const Behavior* behavior = getKeyBehavior(fromKeyCode, fromMetaState);
+ if (behavior != nullptr) {
if (behavior->replacementKeyCode) {
- *outKeyCode = behavior->replacementKeyCode;
- int32_t newMetaState = metaState & ~behavior->metaState;
+ toKeyCode = behavior->replacementKeyCode;
+ toMetaState = fromMetaState & ~behavior->metaState;
// Reset dependent meta states.
if (behavior->metaState & AMETA_ALT_ON) {
- newMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON);
+ toMetaState &= ~(AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON);
}
if (behavior->metaState & (AMETA_ALT_LEFT_ON | AMETA_ALT_RIGHT_ON)) {
- newMetaState &= ~AMETA_ALT_ON;
+ toMetaState &= ~AMETA_ALT_ON;
}
if (behavior->metaState & AMETA_CTRL_ON) {
- newMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON);
+ toMetaState &= ~(AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON);
}
if (behavior->metaState & (AMETA_CTRL_LEFT_ON | AMETA_CTRL_RIGHT_ON)) {
- newMetaState &= ~AMETA_CTRL_ON;
+ toMetaState &= ~AMETA_CTRL_ON;
}
if (behavior->metaState & AMETA_SHIFT_ON) {
- newMetaState &= ~(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON);
+ toMetaState &= ~(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON);
}
if (behavior->metaState & (AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON)) {
- newMetaState &= ~AMETA_SHIFT_ON;
+ toMetaState &= ~AMETA_SHIFT_ON;
}
// ... and put universal bits back if needed
- *outMetaState = normalizeMetaState(newMetaState);
+ toMetaState = normalizeMetaState(toMetaState);
}
}
#if DEBUG_MAPPING
- ALOGD("tryRemapKey: keyCode=%d, metaState=0x%08x ~ "
- "replacement keyCode=%d, replacement metaState=0x%08x.",
- keyCode, metaState, *outKeyCode, *outMetaState);
+ ALOGD("applyKeyBehavior: keyCode=%d, metaState=0x%08x ~ "
+ "replacement keyCode=%d, replacement metaState=0x%08x.",
+ fromKeyCode, fromMetaState, toKeyCode, toMetaState);
#endif
+ return std::make_pair(toKeyCode, toMetaState);
}
-bool KeyCharacterMap::getKey(int32_t keyCode, const Key** outKey) const {
- ssize_t index = mKeys.indexOfKey(keyCode);
- if (index >= 0) {
- *outKey = mKeys.valueAt(index);
- return true;
+const KeyCharacterMap::Key* KeyCharacterMap::getKey(int32_t keyCode) const {
+ auto it = mKeys.find(keyCode);
+ if (it != mKeys.end()) {
+ return &it->second;
}
- return false;
+ return nullptr;
}
-bool KeyCharacterMap::getKeyBehavior(int32_t keyCode, int32_t metaState,
- const Key** outKey, const Behavior** outBehavior) const {
- const Key* key;
- if (getKey(keyCode, &key)) {
- const Behavior* behavior = key->firstBehavior;
- while (behavior) {
- if (matchesMetaState(metaState, behavior->metaState)) {
- *outKey = key;
- *outBehavior = behavior;
- return true;
+const KeyCharacterMap::Behavior* KeyCharacterMap::getKeyBehavior(int32_t keyCode,
+ int32_t metaState) const {
+ const Key* key = getKey(keyCode);
+ if (key != nullptr) {
+ for (const Behavior& behavior : key->behaviors) {
+ if (matchesMetaState(metaState, behavior.metaState)) {
+ return &behavior;
}
- behavior = behavior->next;
}
}
- return false;
+ return nullptr;
}
bool KeyCharacterMap::matchesMetaState(int32_t eventMetaState, int32_t behaviorMetaState) {
@@ -537,19 +478,17 @@ bool KeyCharacterMap::findKey(char16_t ch, int32_t* outKeyCode, int32_t* outMeta
return false;
}
- for (size_t i = 0; i < mKeys.size(); i++) {
- const Key* key = mKeys.valueAt(i);
-
+ for (const auto& [keyCode, key] : mKeys) {
// Try to find the most general behavior that maps to this character.
// For example, the base key behavior will usually be last in the list.
const Behavior* found = nullptr;
- for (const Behavior* behavior = key->firstBehavior; behavior; behavior = behavior->next) {
- if (behavior->character == ch) {
- found = behavior;
+ for (const Behavior& behavior : key.behaviors) {
+ if (behavior.character == ch) {
+ found = &behavior;
}
}
- if (found) {
- *outKeyCode = mKeys.keyAt(i);
+ if (found != nullptr) {
+ *outKeyCode = keyCode;
*outMetaState = found->metaState;
return true;
}
@@ -674,14 +613,14 @@ void KeyCharacterMap::addLockedMetaKey(Vector<KeyEvent>& outEvents,
}
#ifdef __linux__
-std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
+std::unique_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel) {
if (parcel == nullptr) {
ALOGE("%s: Null parcel", __func__);
return nullptr;
}
std::string loadFileName = parcel->readCString();
- std::shared_ptr<KeyCharacterMap> map =
- std::shared_ptr<KeyCharacterMap>(new KeyCharacterMap(loadFileName));
+ std::unique_ptr<KeyCharacterMap> map =
+ std::make_unique<KeyCharacterMap>(KeyCharacterMap(loadFileName));
map->mType = static_cast<KeyCharacterMap::KeyboardType>(parcel->readInt32());
map->mLayoutOverlayApplied = parcel->readBool();
size_t numKeys = parcel->readInt32();
@@ -701,12 +640,7 @@ std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel)
return nullptr;
}
- Key* key = new Key();
- key->label = label;
- key->number = number;
- map->mKeys.add(keyCode, key);
-
- Behavior* lastBehavior = nullptr;
+ Key key{.label = label, .number = number};
while (parcel->readInt32()) {
int32_t metaState = parcel->readInt32();
char16_t character = parcel->readInt32();
@@ -716,23 +650,31 @@ std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel)
return nullptr;
}
- Behavior* behavior = new Behavior();
- behavior->metaState = metaState;
- behavior->character = character;
- behavior->fallbackKeyCode = fallbackKeyCode;
- behavior->replacementKeyCode = replacementKeyCode;
- if (lastBehavior) {
- lastBehavior->next = behavior;
- } else {
- key->firstBehavior = behavior;
- }
- lastBehavior = behavior;
+ key.behaviors.push_back({
+ .metaState = metaState,
+ .character = character,
+ .fallbackKeyCode = fallbackKeyCode,
+ .replacementKeyCode = replacementKeyCode,
+ });
}
+ map->mKeys.emplace(keyCode, std::move(key));
if (parcel->errorCheck()) {
return nullptr;
}
}
+ size_t numKeyRemapping = parcel->readInt32();
+ if (parcel->errorCheck()) {
+ return nullptr;
+ }
+ for (size_t i = 0; i < numKeyRemapping; i++) {
+ int32_t key = parcel->readInt32();
+ int32_t value = parcel->readInt32();
+ map->mKeyRemapping.insert_or_assign(key, value);
+ if (parcel->errorCheck()) {
+ return nullptr;
+ }
+ }
size_t numKeysByScanCode = parcel->readInt32();
if (parcel->errorCheck()) {
return nullptr;
@@ -740,7 +682,7 @@ std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel)
for (size_t i = 0; i < numKeysByScanCode; i++) {
int32_t key = parcel->readInt32();
int32_t value = parcel->readInt32();
- map->mKeysByScanCode.add(key, value);
+ map->mKeysByScanCode.insert_or_assign(key, value);
if (parcel->errorCheck()) {
return nullptr;
}
@@ -752,7 +694,7 @@ std::shared_ptr<KeyCharacterMap> KeyCharacterMap::readFromParcel(Parcel* parcel)
for (size_t i = 0; i < numKeysByUsageCode; i++) {
int32_t key = parcel->readInt32();
int32_t value = parcel->readInt32();
- map->mKeysByUsageCode.add(key, value);
+ map->mKeysByUsageCode.insert_or_assign(key, value);
if (parcel->errorCheck()) {
return nullptr;
}
@@ -771,86 +713,51 @@ void KeyCharacterMap::writeToParcel(Parcel* parcel) const {
size_t numKeys = mKeys.size();
parcel->writeInt32(numKeys);
- for (size_t i = 0; i < numKeys; i++) {
- int32_t keyCode = mKeys.keyAt(i);
- const Key* key = mKeys.valueAt(i);
+ for (const auto& [keyCode, key] : mKeys) {
parcel->writeInt32(keyCode);
- parcel->writeInt32(key->label);
- parcel->writeInt32(key->number);
- for (const Behavior* behavior = key->firstBehavior; behavior != nullptr;
- behavior = behavior->next) {
+ parcel->writeInt32(key.label);
+ parcel->writeInt32(key.number);
+ for (const Behavior& behavior : key.behaviors) {
parcel->writeInt32(1);
- parcel->writeInt32(behavior->metaState);
- parcel->writeInt32(behavior->character);
- parcel->writeInt32(behavior->fallbackKeyCode);
- parcel->writeInt32(behavior->replacementKeyCode);
+ parcel->writeInt32(behavior.metaState);
+ parcel->writeInt32(behavior.character);
+ parcel->writeInt32(behavior.fallbackKeyCode);
+ parcel->writeInt32(behavior.replacementKeyCode);
}
parcel->writeInt32(0);
}
+ size_t numKeyRemapping = mKeyRemapping.size();
+ parcel->writeInt32(numKeyRemapping);
+ for (auto const& [fromAndroidKeyCode, toAndroidKeyCode] : mKeyRemapping) {
+ parcel->writeInt32(fromAndroidKeyCode);
+ parcel->writeInt32(toAndroidKeyCode);
+ }
size_t numKeysByScanCode = mKeysByScanCode.size();
parcel->writeInt32(numKeysByScanCode);
- for (size_t i = 0; i < numKeysByScanCode; i++) {
- parcel->writeInt32(mKeysByScanCode.keyAt(i));
- parcel->writeInt32(mKeysByScanCode.valueAt(i));
+ for (auto const& [fromScanCode, toAndroidKeyCode] : mKeysByScanCode) {
+ parcel->writeInt32(fromScanCode);
+ parcel->writeInt32(toAndroidKeyCode);
}
size_t numKeysByUsageCode = mKeysByUsageCode.size();
parcel->writeInt32(numKeysByUsageCode);
- for (size_t i = 0; i < numKeysByUsageCode; i++) {
- parcel->writeInt32(mKeysByUsageCode.keyAt(i));
- parcel->writeInt32(mKeysByUsageCode.valueAt(i));
+ for (auto const& [fromUsageCode, toAndroidKeyCode] : mKeysByUsageCode) {
+ parcel->writeInt32(fromUsageCode);
+ parcel->writeInt32(toAndroidKeyCode);
}
}
#endif // __linux__
-// --- KeyCharacterMap::Key ---
-
-KeyCharacterMap::Key::Key() :
- label(0), number(0), firstBehavior(nullptr) {
-}
-
-KeyCharacterMap::Key::Key(const Key& other) :
- label(other.label), number(other.number),
- firstBehavior(other.firstBehavior ? new Behavior(*other.firstBehavior) : nullptr) {
-}
-
-KeyCharacterMap::Key::~Key() {
- Behavior* behavior = firstBehavior;
- while (behavior) {
- Behavior* next = behavior->next;
- delete behavior;
- behavior = next;
- }
-}
-
-
-// --- KeyCharacterMap::Behavior ---
-
-KeyCharacterMap::Behavior::Behavior() :
- next(nullptr), metaState(0), character(0), fallbackKeyCode(0), replacementKeyCode(0) {
-}
-
-KeyCharacterMap::Behavior::Behavior(const Behavior& other) :
- next(other.next ? new Behavior(*other.next) : nullptr),
- metaState(other.metaState), character(other.character),
- fallbackKeyCode(other.fallbackKeyCode),
- replacementKeyCode(other.replacementKeyCode) {
-}
-
-
// --- KeyCharacterMap::Parser ---
KeyCharacterMap::Parser::Parser(KeyCharacterMap* map, Tokenizer* tokenizer, Format format) :
mMap(map), mTokenizer(tokenizer), mFormat(format), mState(STATE_TOP) {
}
-KeyCharacterMap::Parser::~Parser() {
-}
-
status_t KeyCharacterMap::Parser::parse() {
while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
- ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().c_str(),
+ mTokenizer->peekRemainderOfLine().c_str());
#endif
mTokenizer->skipDelimiters(WHITESPACE);
@@ -872,8 +779,8 @@ status_t KeyCharacterMap::Parser::parse() {
status_t status = parseKey();
if (status) return status;
} else {
- ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
- keywordToken.string());
+ ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().c_str(),
+ keywordToken.c_str());
return BAD_VALUE;
}
break;
@@ -888,10 +795,9 @@ status_t KeyCharacterMap::Parser::parse() {
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
- ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
- mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
- return BAD_VALUE;
+ ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
+ mTokenizer->getLocation().c_str(), mTokenizer->peekRemainderOfLine().c_str());
+ return BAD_VALUE;
}
}
@@ -900,27 +806,27 @@ status_t KeyCharacterMap::Parser::parse() {
if (mState != STATE_TOP) {
ALOGE("%s: Unterminated key description at end of file.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
if (mMap->mType == KeyboardType::UNKNOWN) {
ALOGE("%s: Keyboard layout missing required keyboard 'type' declaration.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
if (mFormat == Format::BASE) {
if (mMap->mType == KeyboardType::OVERLAY) {
ALOGE("%s: Base keyboard layout must specify a keyboard 'type' other than 'OVERLAY'.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
} else if (mFormat == Format::OVERLAY) {
if (mMap->mType != KeyboardType::OVERLAY) {
ALOGE("%s: Overlay keyboard layout missing required keyboard "
- "'type OVERLAY' declaration.",
- mTokenizer->getLocation().string());
+ "'type OVERLAY' declaration.",
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
}
@@ -930,8 +836,7 @@ status_t KeyCharacterMap::Parser::parse() {
status_t KeyCharacterMap::Parser::parseType() {
if (mMap->mType != KeyboardType::UNKNOWN) {
- ALOGE("%s: Duplicate keyboard 'type' declaration.",
- mTokenizer->getLocation().string());
+ ALOGE("%s: Duplicate keyboard 'type' declaration.", mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
@@ -953,8 +858,8 @@ status_t KeyCharacterMap::Parser::parseType() {
} else if (typeToken == "OVERLAY") {
type = KeyboardType::OVERLAY;
} else {
- ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().string(),
- typeToken.string());
+ ALOGE("%s: Expected keyboard type label, got '%s'.", mTokenizer->getLocation().c_str(),
+ typeToken.c_str());
return BAD_VALUE;
}
@@ -971,8 +876,8 @@ status_t KeyCharacterMap::Parser::parseMap() {
mTokenizer->skipDelimiters(WHITESPACE);
return parseMapKey();
}
- ALOGE("%s: Expected keyword after 'map', got '%s'.", mTokenizer->getLocation().string(),
- keywordToken.string());
+ ALOGE("%s: Expected keyword after 'map', got '%s'.", mTokenizer->getLocation().c_str(),
+ keywordToken.c_str());
return BAD_VALUE;
}
@@ -986,26 +891,26 @@ status_t KeyCharacterMap::Parser::parseMapKey() {
}
char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
+ int32_t code = int32_t(strtol(codeToken.c_str(), &end, 0));
if (*end) {
- ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
- mapUsage ? "usage" : "scan code", codeToken.string());
+ ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().c_str(),
+ mapUsage ? "usage" : "scan code", codeToken.c_str());
return BAD_VALUE;
}
- KeyedVector<int32_t, int32_t>& map =
- mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
- if (map.indexOfKey(code) >= 0) {
- ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
- mapUsage ? "usage" : "scan code", codeToken.string());
+ std::map<int32_t, int32_t>& map = mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
+ const auto it = map.find(code);
+ if (it != map.end()) {
+ ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().c_str(),
+ mapUsage ? "usage" : "scan code", codeToken.c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.c_str());
if (!keyCode) {
- ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
- keyCodeToken.string());
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().c_str(),
+ keyCodeToken.c_str());
return BAD_VALUE;
}
@@ -1013,66 +918,64 @@ status_t KeyCharacterMap::Parser::parseMapKey() {
ALOGD("Parsed map key %s: code=%d, keyCode=%d.",
mapUsage ? "usage" : "scan code", code, keyCode);
#endif
- map.add(code, keyCode);
+ map.insert_or_assign(code, *keyCode);
return NO_ERROR;
}
status_t KeyCharacterMap::Parser::parseKey() {
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.c_str());
if (!keyCode) {
- ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
- keyCodeToken.string());
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().c_str(),
+ keyCodeToken.c_str());
return BAD_VALUE;
}
- if (mMap->mKeys.indexOfKey(keyCode) >= 0) {
- ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().string(),
- keyCodeToken.string());
+ if (mMap->mKeys.find(*keyCode) != mMap->mKeys.end()) {
+ ALOGE("%s: Duplicate entry for key code '%s'.", mTokenizer->getLocation().c_str(),
+ keyCodeToken.c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 openBraceToken = mTokenizer->nextToken(WHITESPACE);
if (openBraceToken != "{") {
- ALOGE("%s: Expected '{' after key code label, got '%s'.",
- mTokenizer->getLocation().string(), openBraceToken.string());
+ ALOGE("%s: Expected '{' after key code label, got '%s'.", mTokenizer->getLocation().c_str(),
+ openBraceToken.c_str());
return BAD_VALUE;
}
-#if DEBUG_PARSER
- ALOGD("Parsed beginning of key: keyCode=%d.", keyCode);
-#endif
- mKeyCode = keyCode;
- mMap->mKeys.add(keyCode, new Key());
+ ALOGD_IF(DEBUG_PARSER, "Parsed beginning of key: keyCode=%d.", *keyCode);
+ mKeyCode = *keyCode;
+ mMap->mKeys.emplace(*keyCode, Key{});
mState = STATE_KEY;
return NO_ERROR;
}
status_t KeyCharacterMap::Parser::parseKeyProperty() {
- Key* key = mMap->mKeys.valueFor(mKeyCode);
+ Key& key = mMap->mKeys[mKeyCode];
String8 token = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
if (token == "}") {
mState = STATE_TOP;
return finishKey(key);
}
- Vector<Property> properties;
+ std::vector<Property> properties;
// Parse all comma-delimited property names up to the first colon.
for (;;) {
if (token == "label") {
- properties.add(Property(PROPERTY_LABEL));
+ properties.emplace_back(PROPERTY_LABEL);
} else if (token == "number") {
- properties.add(Property(PROPERTY_NUMBER));
+ properties.emplace_back(PROPERTY_NUMBER);
} else {
int32_t metaState;
- status_t status = parseModifier(token.string(), &metaState);
+ status_t status = parseModifier(token.c_str(), &metaState);
if (status) {
ALOGE("%s: Expected a property name or modifier, got '%s'.",
- mTokenizer->getLocation().string(), token.string());
+ mTokenizer->getLocation().c_str(), token.c_str());
return status;
}
- properties.add(Property(PROPERTY_META, metaState));
+ properties.emplace_back(PROPERTY_META, metaState);
}
mTokenizer->skipDelimiters(WHITESPACE);
@@ -1087,8 +990,7 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() {
}
}
- ALOGE("%s: Expected ',' or ':' after property name.",
- mTokenizer->getLocation().string());
+ ALOGE("%s: Expected ',' or ':' after property name.", mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
@@ -1106,18 +1008,17 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() {
char16_t character;
status_t status = parseCharacterLiteral(&character);
if (status || !character) {
- ALOGE("%s: Invalid character literal for key.",
- mTokenizer->getLocation().string());
+ ALOGE("%s: Invalid character literal for key.", mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
if (haveCharacter) {
ALOGE("%s: Cannot combine multiple character literals or 'none'.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
if (haveReplacement) {
ALOGE("%s: Cannot combine character literal with replace action.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
behavior.character = character;
@@ -1127,58 +1028,55 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() {
if (token == "none") {
if (haveCharacter) {
ALOGE("%s: Cannot combine multiple character literals or 'none'.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
if (haveReplacement) {
ALOGE("%s: Cannot combine 'none' with replace action.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
haveCharacter = true;
} else if (token == "fallback") {
mTokenizer->skipDelimiters(WHITESPACE);
token = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(token.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(token.c_str());
if (!keyCode) {
ALOGE("%s: Invalid key code label for fallback behavior, got '%s'.",
- mTokenizer->getLocation().string(),
- token.string());
+ mTokenizer->getLocation().c_str(), token.c_str());
return BAD_VALUE;
}
if (haveFallback || haveReplacement) {
ALOGE("%s: Cannot combine multiple fallback/replacement key codes.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
- behavior.fallbackKeyCode = keyCode;
+ behavior.fallbackKeyCode = *keyCode;
haveFallback = true;
} else if (token == "replace") {
mTokenizer->skipDelimiters(WHITESPACE);
token = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(token.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(token.c_str());
if (!keyCode) {
ALOGE("%s: Invalid key code label for replace, got '%s'.",
- mTokenizer->getLocation().string(),
- token.string());
+ mTokenizer->getLocation().c_str(), token.c_str());
return BAD_VALUE;
}
if (haveCharacter) {
ALOGE("%s: Cannot combine character literal with replace action.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
if (haveFallback || haveReplacement) {
ALOGE("%s: Cannot combine multiple fallback/replacement key codes.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
- behavior.replacementKeyCode = keyCode;
+ behavior.replacementKeyCode = *keyCode;
haveReplacement = true;
} else {
- ALOGE("%s: Expected a key behavior after ':'.",
- mTokenizer->getLocation().string());
+ ALOGE("%s: Expected a key behavior after ':'.", mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
}
@@ -1187,49 +1085,44 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() {
} while (!mTokenizer->isEol() && mTokenizer->peekChar() != '#');
// Add the behavior.
- for (size_t i = 0; i < properties.size(); i++) {
- const Property& property = properties.itemAt(i);
+ for (const Property& property : properties) {
switch (property.property) {
case PROPERTY_LABEL:
- if (key->label) {
- ALOGE("%s: Duplicate label for key.",
- mTokenizer->getLocation().string());
- return BAD_VALUE;
- }
- key->label = behavior.character;
+ if (key.label) {
+ ALOGE("%s: Duplicate label for key.", mTokenizer->getLocation().c_str());
+ return BAD_VALUE;
+ }
+ key.label = behavior.character;
#if DEBUG_PARSER
- ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key->label);
+ ALOGD("Parsed key label: keyCode=%d, label=%d.", mKeyCode, key.label);
#endif
break;
case PROPERTY_NUMBER:
- if (key->number) {
- ALOGE("%s: Duplicate number for key.",
- mTokenizer->getLocation().string());
- return BAD_VALUE;
+ if (key.number) {
+ ALOGE("%s: Duplicate number for key.", mTokenizer->getLocation().c_str());
+ return BAD_VALUE;
}
- key->number = behavior.character;
+ key.number = behavior.character;
#if DEBUG_PARSER
- ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key->number);
+ ALOGD("Parsed key number: keyCode=%d, number=%d.", mKeyCode, key.number);
#endif
break;
case PROPERTY_META: {
- for (Behavior* b = key->firstBehavior; b; b = b->next) {
- if (b->metaState == property.metaState) {
+ for (const Behavior& b : key.behaviors) {
+ if (b.metaState == property.metaState) {
ALOGE("%s: Duplicate key behavior for modifier.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
- }
+ }
}
- Behavior* newBehavior = new Behavior(behavior);
- newBehavior->metaState = property.metaState;
- newBehavior->next = key->firstBehavior;
- key->firstBehavior = newBehavior;
-#if DEBUG_PARSER
- ALOGD("Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d replace=%d.",
- mKeyCode,
- newBehavior->metaState, newBehavior->character,
- newBehavior->fallbackKeyCode, newBehavior->replacementKeyCode);
-#endif
+ Behavior newBehavior = behavior;
+ newBehavior.metaState = property.metaState;
+ key.behaviors.push_front(newBehavior);
+ ALOGD_IF(DEBUG_PARSER,
+ "Parsed key meta: keyCode=%d, meta=0x%x, char=%d, fallback=%d replace=%d.",
+ mKeyCode, key.behaviors.front().metaState, key.behaviors.front().character,
+ key.behaviors.front().fallbackKeyCode,
+ key.behaviors.front().replacementKeyCode);
break;
}
}
@@ -1237,13 +1130,13 @@ status_t KeyCharacterMap::Parser::parseKeyProperty() {
return NO_ERROR;
}
-status_t KeyCharacterMap::Parser::finishKey(Key* key) {
+status_t KeyCharacterMap::Parser::finishKey(Key& key) {
// Fill in default number property.
- if (!key->number) {
+ if (!key.number) {
char16_t digit = 0;
char16_t symbol = 0;
- for (Behavior* b = key->firstBehavior; b; b = b->next) {
- char16_t ch = b->character;
+ for (const Behavior& b : key.behaviors) {
+ char16_t ch = b.character;
if (ch) {
if (ch >= '0' && ch <= '9') {
digit = ch;
@@ -1254,7 +1147,7 @@ status_t KeyCharacterMap::Parser::finishKey(Key* key) {
}
}
}
- key->number = digit ? digit : symbol;
+ key.number = digit ? digit : symbol;
}
return NO_ERROR;
}
@@ -1285,8 +1178,8 @@ status_t KeyCharacterMap::Parser::parseModifier(const std::string& token, int32_
return BAD_VALUE;
}
if (combinedMeta & metaState) {
- ALOGE("%s: Duplicate modifier combination '%s'.",
- mTokenizer->getLocation().string(), token.c_str());
+ ALOGE("%s: Duplicate modifier combination '%s'.", mTokenizer->getLocation().c_str(),
+ token.c_str());
return BAD_VALUE;
}
@@ -1354,12 +1247,12 @@ status_t KeyCharacterMap::Parser::parseCharacterLiteral(char16_t* outCharacter)
}
// Ensure that we consumed the entire token.
- if (mTokenizer->nextToken(WHITESPACE).isEmpty()) {
+ if (mTokenizer->nextToken(WHITESPACE).empty()) {
return NO_ERROR;
}
Error:
- ALOGE("%s: Malformed character literal.", mTokenizer->getLocation().string());
+ ALOGE("%s: Malformed character literal.", mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
diff --git a/libs/input/KeyLayoutMap.cpp b/libs/input/KeyLayoutMap.cpp
index d6b4579a94..ab8c341b15 100644
--- a/libs/input/KeyLayoutMap.cpp
+++ b/libs/input/KeyLayoutMap.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "KeyLayoutMap"
+#include <android-base/logging.h>
#include <android/keycodes.h>
#include <ftl/enum.h>
#include <input/InputEventLabels.h>
@@ -26,8 +27,7 @@
#include <utils/Timers.h>
#include <utils/Tokenizer.h>
#if defined(__ANDROID__)
-#include <vintf/RuntimeInfo.h>
-#include <vintf/VintfObject.h>
+#include <vintf/KernelConfigs.h>
#endif
#include <cstdlib>
@@ -54,6 +54,21 @@ const bool DEBUG_MAPPING =
namespace android {
namespace {
+std::optional<int> parseInt(const char* str) {
+ char* end;
+ errno = 0;
+ const int value = strtol(str, &end, 0);
+ if (end == str) {
+ LOG(ERROR) << "Could not parse " << str;
+ return {};
+ }
+ if (errno == ERANGE) {
+ LOG(ERROR) << "Out of bounds: " << str;
+ return {};
+ }
+ return value;
+}
+
constexpr const char* WHITESPACE = " \t\r";
template <InputDeviceSensorType S>
@@ -82,12 +97,10 @@ static const std::unordered_map<std::string_view, InputDeviceSensorType> SENSOR_
bool kernelConfigsArePresent(const std::set<std::string>& configs) {
#if defined(__ANDROID__)
- std::shared_ptr<const android::vintf::RuntimeInfo> runtimeInfo =
- android::vintf::VintfObject::GetInstance()->getRuntimeInfo(
- vintf::RuntimeInfo::FetchFlag::CONFIG_GZ);
- LOG_ALWAYS_FATAL_IF(runtimeInfo == nullptr, "Kernel configs could not be fetched");
+ std::map<std::string, std::string> kernelConfigs;
+ const status_t result = android::kernelconfigs::LoadKernelConfigs(&kernelConfigs);
+ LOG_ALWAYS_FATAL_IF(result != OK, "Kernel configs could not be fetched");
- const std::map<std::string, std::string>& kernelConfigs = runtimeInfo->kernelConfigs();
for (const std::string& requiredConfig : configs) {
const auto configIt = kernelConfigs.find(requiredConfig);
if (configIt == kernelConfigs.end()) {
@@ -161,7 +174,7 @@ base::Result<std::shared_ptr<KeyLayoutMap>> KeyLayoutMap::load(Tokenizer* tokeni
#if DEBUG_PARSER_PERFORMANCE
nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
ALOGD("Parsed key layout map file '%s' %d lines in %0.3fms.",
- tokenizer->getFilename().string(), tokenizer->getLineNumber(),
+ tokenizer->getFilename().c_str(), tokenizer->getLineNumber(),
elapsedTime / 1000000.0);
#endif
if (!status) {
@@ -192,7 +205,8 @@ status_t KeyLayoutMap::mapKey(int32_t scanCode, int32_t usageCode,
}
// Return pair of sensor type and sensor data index, for the input device abs code
-base::Result<std::pair<InputDeviceSensorType, int32_t>> KeyLayoutMap::mapSensor(int32_t absCode) {
+base::Result<std::pair<InputDeviceSensorType, int32_t>> KeyLayoutMap::mapSensor(
+ int32_t absCode) const {
auto it = mSensorsByAbsCode.find(absCode);
if (it == mSensorsByAbsCode.end()) {
ALOGD_IF(DEBUG_MAPPING, "mapSensor: absCode=%d, ~ Failed.", absCode);
@@ -230,6 +244,16 @@ std::vector<int32_t> KeyLayoutMap::findScanCodesForKey(int32_t keyCode) const {
return scanCodes;
}
+std::vector<int32_t> KeyLayoutMap::findUsageCodesForKey(int32_t keyCode) const {
+ std::vector<int32_t> usageCodes;
+ for (const auto& [usageCode, key] : mKeysByUsageCode) {
+ if (keyCode == key.keyCode && !(key.flags & POLICY_FLAG_FALLBACK_USAGE_MAPPING)) {
+ usageCodes.push_back(usageCode);
+ }
+ }
+ return usageCodes;
+}
+
std::optional<AxisInfo> KeyLayoutMap::mapAxis(int32_t scanCode) const {
auto it = mAxes.find(scanCode);
if (it == mAxes.end()) {
@@ -279,8 +303,8 @@ KeyLayoutMap::Parser::~Parser() {
status_t KeyLayoutMap::Parser::parse() {
while (!mTokenizer->isEof()) {
- ALOGD_IF(DEBUG_PARSER, "Parsing %s: '%s'.", mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ ALOGD_IF(DEBUG_PARSER, "Parsing %s: '%s'.", mTokenizer->getLocation().c_str(),
+ mTokenizer->peekRemainderOfLine().c_str());
mTokenizer->skipDelimiters(WHITESPACE);
@@ -307,16 +331,15 @@ status_t KeyLayoutMap::Parser::parse() {
status_t status = parseRequiredKernelConfig();
if (status) return status;
} else {
- ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().string(),
- keywordToken.string());
+ ALOGE("%s: Expected keyword, got '%s'.", mTokenizer->getLocation().c_str(),
+ keywordToken.c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
ALOGE("%s: Expected end of line or trailing comment, got '%s'.",
- mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ mTokenizer->getLocation().c_str(), mTokenizer->peekRemainderOfLine().c_str());
return BAD_VALUE;
}
}
@@ -335,27 +358,26 @@ status_t KeyLayoutMap::Parser::parseKey() {
codeToken = mTokenizer->nextToken(WHITESPACE);
}
- char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
- if (*end) {
- ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().string(),
- mapUsage ? "usage" : "scan code", codeToken.string());
+ std::optional<int> code = parseInt(codeToken.c_str());
+ if (!code) {
+ ALOGE("%s: Expected key %s number, got '%s'.", mTokenizer->getLocation().c_str(),
+ mapUsage ? "usage" : "scan code", codeToken.c_str());
return BAD_VALUE;
}
std::unordered_map<int32_t, Key>& map =
mapUsage ? mMap->mKeysByUsageCode : mMap->mKeysByScanCode;
- if (map.find(code) != map.end()) {
- ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().string(),
- mapUsage ? "usage" : "scan code", codeToken.string());
+ if (map.find(*code) != map.end()) {
+ ALOGE("%s: Duplicate entry for key %s '%s'.", mTokenizer->getLocation().c_str(),
+ mapUsage ? "usage" : "scan code", codeToken.c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 keyCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.string());
+ std::optional<int> keyCode = InputEventLookup::getKeyCodeByLabel(keyCodeToken.c_str());
if (!keyCode) {
- ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().string(),
- keyCodeToken.string());
+ ALOGE("%s: Expected key code label, got '%s'.", mTokenizer->getLocation().c_str(),
+ keyCodeToken.c_str());
return BAD_VALUE;
}
@@ -365,42 +387,41 @@ status_t KeyLayoutMap::Parser::parseKey() {
if (mTokenizer->isEol() || mTokenizer->peekChar() == '#') break;
String8 flagToken = mTokenizer->nextToken(WHITESPACE);
- uint32_t flag = InputEventLookup::getKeyFlagByLabel(flagToken.string());
+ std::optional<int> flag = InputEventLookup::getKeyFlagByLabel(flagToken.c_str());
if (!flag) {
- ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().string(),
- flagToken.string());
+ ALOGE("%s: Expected key flag label, got '%s'.", mTokenizer->getLocation().c_str(),
+ flagToken.c_str());
return BAD_VALUE;
}
- if (flags & flag) {
- ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().string(),
- flagToken.string());
+ if (flags & *flag) {
+ ALOGE("%s: Duplicate key flag '%s'.", mTokenizer->getLocation().c_str(),
+ flagToken.c_str());
return BAD_VALUE;
}
- flags |= flag;
+ flags |= *flag;
}
ALOGD_IF(DEBUG_PARSER, "Parsed key %s: code=%d, keyCode=%d, flags=0x%08x.",
- mapUsage ? "usage" : "scan code", code, keyCode, flags);
+ mapUsage ? "usage" : "scan code", *code, *keyCode, flags);
Key key;
- key.keyCode = keyCode;
+ key.keyCode = *keyCode;
key.flags = flags;
- map.insert({code, key});
+ map.insert({*code, key});
return NO_ERROR;
}
status_t KeyLayoutMap::Parser::parseAxis() {
String8 scanCodeToken = mTokenizer->nextToken(WHITESPACE);
- char* end;
- int32_t scanCode = int32_t(strtol(scanCodeToken.string(), &end, 0));
- if (*end) {
- ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().string(),
- scanCodeToken.string());
+ std::optional<int> scanCode = parseInt(scanCodeToken.c_str());
+ if (!scanCode) {
+ ALOGE("%s: Expected axis scan code number, got '%s'.", mTokenizer->getLocation().c_str(),
+ scanCodeToken.c_str());
return BAD_VALUE;
}
- if (mMap->mAxes.find(scanCode) != mMap->mAxes.end()) {
- ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().string(),
- scanCodeToken.string());
+ if (mMap->mAxes.find(*scanCode) != mMap->mAxes.end()) {
+ ALOGE("%s: Duplicate entry for axis scan code '%s'.", mTokenizer->getLocation().c_str(),
+ scanCodeToken.c_str());
return BAD_VALUE;
}
@@ -413,48 +434,53 @@ status_t KeyLayoutMap::Parser::parseAxis() {
mTokenizer->skipDelimiters(WHITESPACE);
String8 axisToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.axis = InputEventLookup::getAxisByLabel(axisToken.string());
- if (axisInfo.axis < 0) {
+ std::optional<int> axis = InputEventLookup::getAxisByLabel(axisToken.c_str());
+ if (!axis) {
ALOGE("%s: Expected inverted axis label, got '%s'.",
- mTokenizer->getLocation().string(), axisToken.string());
+ mTokenizer->getLocation().c_str(), axisToken.c_str());
return BAD_VALUE;
}
+ axisInfo.axis = *axis;
} else if (token == "split") {
axisInfo.mode = AxisInfo::MODE_SPLIT;
mTokenizer->skipDelimiters(WHITESPACE);
String8 splitToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.splitValue = int32_t(strtol(splitToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> splitValue = parseInt(splitToken.c_str());
+ if (!splitValue) {
ALOGE("%s: Expected split value, got '%s'.",
- mTokenizer->getLocation().string(), splitToken.string());
+ mTokenizer->getLocation().c_str(), splitToken.c_str());
return BAD_VALUE;
}
+ axisInfo.splitValue = *splitValue;
mTokenizer->skipDelimiters(WHITESPACE);
String8 lowAxisToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.axis = InputEventLookup::getAxisByLabel(lowAxisToken.string());
- if (axisInfo.axis < 0) {
+ std::optional<int> axis = InputEventLookup::getAxisByLabel(lowAxisToken.c_str());
+ if (!axis) {
ALOGE("%s: Expected low axis label, got '%s'.",
- mTokenizer->getLocation().string(), lowAxisToken.string());
+ mTokenizer->getLocation().c_str(), lowAxisToken.c_str());
return BAD_VALUE;
}
+ axisInfo.axis = *axis;
mTokenizer->skipDelimiters(WHITESPACE);
String8 highAxisToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.highAxis = InputEventLookup::getAxisByLabel(highAxisToken.string());
- if (axisInfo.highAxis < 0) {
+ std::optional<int> highAxis = InputEventLookup::getAxisByLabel(highAxisToken.c_str());
+ if (!highAxis) {
ALOGE("%s: Expected high axis label, got '%s'.",
- mTokenizer->getLocation().string(), highAxisToken.string());
+ mTokenizer->getLocation().c_str(), highAxisToken.c_str());
return BAD_VALUE;
}
+ axisInfo.highAxis = *highAxis;
} else {
- axisInfo.axis = InputEventLookup::getAxisByLabel(token.string());
- if (axisInfo.axis < 0) {
+ std::optional<int> axis = InputEventLookup::getAxisByLabel(token.c_str());
+ if (!axis) {
ALOGE("%s: Expected axis label, 'split' or 'invert', got '%s'.",
- mTokenizer->getLocation().string(), token.string());
+ mTokenizer->getLocation().c_str(), token.c_str());
return BAD_VALUE;
}
+ axisInfo.axis = *axis;
}
for (;;) {
@@ -466,15 +492,16 @@ status_t KeyLayoutMap::Parser::parseAxis() {
if (keywordToken == "flat") {
mTokenizer->skipDelimiters(WHITESPACE);
String8 flatToken = mTokenizer->nextToken(WHITESPACE);
- axisInfo.flatOverride = int32_t(strtol(flatToken.string(), &end, 0));
- if (*end) {
+ std::optional<int> flatOverride = parseInt(flatToken.c_str());
+ if (!flatOverride) {
ALOGE("%s: Expected flat value, got '%s'.",
- mTokenizer->getLocation().string(), flatToken.string());
+ mTokenizer->getLocation().c_str(), flatToken.c_str());
return BAD_VALUE;
}
+ axisInfo.flatOverride = *flatOverride;
} else {
- ALOGE("%s: Expected keyword 'flat', got '%s'.",
- mTokenizer->getLocation().string(), keywordToken.string());
+ ALOGE("%s: Expected keyword 'flat', got '%s'.", mTokenizer->getLocation().c_str(),
+ keywordToken.c_str());
return BAD_VALUE;
}
}
@@ -482,9 +509,9 @@ status_t KeyLayoutMap::Parser::parseAxis() {
ALOGD_IF(DEBUG_PARSER,
"Parsed axis: scanCode=%d, mode=%d, axis=%d, highAxis=%d, "
"splitValue=%d, flatOverride=%d.",
- scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue,
+ *scanCode, axisInfo.mode, axisInfo.axis, axisInfo.highAxis, axisInfo.splitValue,
axisInfo.flatOverride);
- mMap->mAxes.insert({scanCode, axisInfo});
+ mMap->mAxes.insert({*scanCode, axisInfo});
return NO_ERROR;
}
@@ -496,37 +523,36 @@ status_t KeyLayoutMap::Parser::parseLed() {
mTokenizer->skipDelimiters(WHITESPACE);
codeToken = mTokenizer->nextToken(WHITESPACE);
}
- char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
- if (*end) {
- ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().string(),
- mapUsage ? "usage" : "scan code", codeToken.string());
+ std::optional<int> code = parseInt(codeToken.c_str());
+ if (!code) {
+ ALOGE("%s: Expected led %s number, got '%s'.", mTokenizer->getLocation().c_str(),
+ mapUsage ? "usage" : "scan code", codeToken.c_str());
return BAD_VALUE;
}
std::unordered_map<int32_t, Led>& map =
mapUsage ? mMap->mLedsByUsageCode : mMap->mLedsByScanCode;
- if (map.find(code) != map.end()) {
- ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().string(),
- mapUsage ? "usage" : "scan code", codeToken.string());
+ if (map.find(*code) != map.end()) {
+ ALOGE("%s: Duplicate entry for led %s '%s'.", mTokenizer->getLocation().c_str(),
+ mapUsage ? "usage" : "scan code", codeToken.c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 ledCodeToken = mTokenizer->nextToken(WHITESPACE);
- int32_t ledCode = InputEventLookup::getLedByLabel(ledCodeToken.string());
- if (ledCode < 0) {
- ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().string(),
- ledCodeToken.string());
+ std::optional<int> ledCode = InputEventLookup::getLedByLabel(ledCodeToken.c_str());
+ if (!ledCode) {
+ ALOGE("%s: Expected LED code label, got '%s'.", mTokenizer->getLocation().c_str(),
+ ledCodeToken.c_str());
return BAD_VALUE;
}
ALOGD_IF(DEBUG_PARSER, "Parsed led %s: code=%d, ledCode=%d.", mapUsage ? "usage" : "scan code",
- code, ledCode);
+ *code, *ledCode);
Led led;
- led.ledCode = ledCode;
- map.insert({code, led});
+ led.ledCode = *ledCode;
+ map.insert({*code, led});
return NO_ERROR;
}
@@ -539,7 +565,7 @@ static std::optional<InputDeviceSensorType> getSensorType(const char* token) {
}
static std::optional<int32_t> getSensorDataIndex(String8 token) {
- std::string tokenStr(token.string());
+ std::string tokenStr(token.c_str());
if (tokenStr == "X") {
return 0;
} else if (tokenStr == "Y") {
@@ -564,27 +590,26 @@ static std::optional<int32_t> getSensorDataIndex(String8 token) {
// sensor 0x05 GYROSCOPE Z
status_t KeyLayoutMap::Parser::parseSensor() {
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
- char* end;
- int32_t code = int32_t(strtol(codeToken.string(), &end, 0));
- if (*end) {
- ALOGE("%s: Expected sensor %s number, got '%s'.", mTokenizer->getLocation().string(),
- "abs code", codeToken.string());
+ std::optional<int> code = parseInt(codeToken.c_str());
+ if (!code) {
+ ALOGE("%s: Expected sensor %s number, got '%s'.", mTokenizer->getLocation().c_str(),
+ "abs code", codeToken.c_str());
return BAD_VALUE;
}
std::unordered_map<int32_t, Sensor>& map = mMap->mSensorsByAbsCode;
- if (map.find(code) != map.end()) {
- ALOGE("%s: Duplicate entry for sensor %s '%s'.", mTokenizer->getLocation().string(),
- "abs code", codeToken.string());
+ if (map.find(*code) != map.end()) {
+ ALOGE("%s: Duplicate entry for sensor %s '%s'.", mTokenizer->getLocation().c_str(),
+ "abs code", codeToken.c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
String8 sensorTypeToken = mTokenizer->nextToken(WHITESPACE);
- std::optional<InputDeviceSensorType> typeOpt = getSensorType(sensorTypeToken.string());
+ std::optional<InputDeviceSensorType> typeOpt = getSensorType(sensorTypeToken.c_str());
if (!typeOpt) {
- ALOGE("%s: Expected sensor code label, got '%s'.", mTokenizer->getLocation().string(),
- sensorTypeToken.string());
+ ALOGE("%s: Expected sensor code label, got '%s'.", mTokenizer->getLocation().c_str(),
+ sensorTypeToken.c_str());
return BAD_VALUE;
}
InputDeviceSensorType sensorType = typeOpt.value();
@@ -592,19 +617,19 @@ status_t KeyLayoutMap::Parser::parseSensor() {
String8 sensorDataIndexToken = mTokenizer->nextToken(WHITESPACE);
std::optional<int32_t> indexOpt = getSensorDataIndex(sensorDataIndexToken);
if (!indexOpt) {
- ALOGE("%s: Expected sensor data index label, got '%s'.", mTokenizer->getLocation().string(),
- sensorDataIndexToken.string());
+ ALOGE("%s: Expected sensor data index label, got '%s'.", mTokenizer->getLocation().c_str(),
+ sensorDataIndexToken.c_str());
return BAD_VALUE;
}
int32_t sensorDataIndex = indexOpt.value();
- ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", code,
+ ALOGD_IF(DEBUG_PARSER, "Parsed sensor: abs code=%d, sensorType=%s, sensorDataIndex=%d.", *code,
ftl::enum_string(sensorType).c_str(), sensorDataIndex);
Sensor sensor;
sensor.sensorType = sensorType;
sensor.sensorDataIndex = sensorDataIndex;
- map.emplace(code, sensor);
+ map.emplace(*code, sensor);
return NO_ERROR;
}
@@ -614,12 +639,12 @@ status_t KeyLayoutMap::Parser::parseSensor() {
// requires_kernel_config CONFIG_HID_PLAYSTATION
status_t KeyLayoutMap::Parser::parseRequiredKernelConfig() {
String8 codeToken = mTokenizer->nextToken(WHITESPACE);
- std::string configName = codeToken.string();
+ std::string configName = codeToken.c_str();
const auto result = mMap->mRequiredKernelConfigs.emplace(configName);
if (!result.second) {
ALOGE("%s: Duplicate entry for required kernel config %s.",
- mTokenizer->getLocation().string(), configName.c_str());
+ mTokenizer->getLocation().c_str(), configName.c_str());
return BAD_VALUE;
}
diff --git a/libs/input/Keyboard.cpp b/libs/input/Keyboard.cpp
index c3f5151fd1..0b5c7ff785 100644
--- a/libs/input/Keyboard.cpp
+++ b/libs/input/Keyboard.cpp
@@ -16,9 +16,10 @@
#define LOG_TAG "Keyboard"
+#include <limits.h>
#include <stdlib.h>
#include <unistd.h>
-#include <limits.h>
+#include <optional>
#include <input/InputDevice.h>
#include <input/InputEventLabels.h>
@@ -49,25 +50,25 @@ status_t KeyMap::load(const InputDeviceIdentifier& deviceIdentifier,
const PropertyMap* deviceConfiguration) {
// Use the configured key layout if available.
if (deviceConfiguration) {
- String8 keyLayoutName;
- if (deviceConfiguration->tryGetProperty(String8("keyboard.layout"),
- keyLayoutName)) {
- status_t status = loadKeyLayout(deviceIdentifier, keyLayoutName.c_str());
+ std::optional<std::string> keyLayoutName =
+ deviceConfiguration->getString("keyboard.layout");
+ if (keyLayoutName.has_value()) {
+ status_t status = loadKeyLayout(deviceIdentifier, *keyLayoutName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard layout '%s' but "
- "it was not found.",
- deviceIdentifier.name.c_str(), keyLayoutName.string());
+ "it was not found.",
+ deviceIdentifier.name.c_str(), keyLayoutName->c_str());
}
}
- String8 keyCharacterMapName;
- if (deviceConfiguration->tryGetProperty(String8("keyboard.characterMap"),
- keyCharacterMapName)) {
- status_t status = loadKeyCharacterMap(deviceIdentifier, keyCharacterMapName.c_str());
+ std::optional<std::string> keyCharacterMapName =
+ deviceConfiguration->getString("keyboard.characterMap");
+ if (keyCharacterMapName.has_value()) {
+ status_t status = loadKeyCharacterMap(deviceIdentifier, *keyCharacterMapName);
if (status == NAME_NOT_FOUND) {
ALOGE("Configuration for keyboard device '%s' requested keyboard character "
- "map '%s' but it was not found.",
- deviceIdentifier.name.c_str(), keyCharacterMapName.string());
+ "map '%s' but it was not found.",
+ deviceIdentifier.name.c_str(), keyCharacterMapName->c_str());
}
}
@@ -164,9 +165,7 @@ bool isKeyboardSpecialFunction(const PropertyMap* config) {
if (config == nullptr) {
return false;
}
- bool isSpecialFunction = false;
- config->tryGetProperty(String8("keyboard.specialFunction"), isSpecialFunction);
- return isSpecialFunction;
+ return config->getBool("keyboard.specialFunction").value_or(false);
}
bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
@@ -179,9 +178,7 @@ bool isEligibleBuiltInKeyboard(const InputDeviceIdentifier& deviceIdentifier,
}
if (deviceConfiguration) {
- bool builtIn = false;
- if (deviceConfiguration->tryGetProperty(String8("keyboard.builtIn"), builtIn)
- && builtIn) {
+ if (deviceConfiguration->getBool("keyboard.builtIn").value_or(false)) {
return true;
}
}
diff --git a/libs/input/MotionPredictor.cpp b/libs/input/MotionPredictor.cpp
new file mode 100644
index 0000000000..c4e3ff6dee
--- /dev/null
+++ b/libs/input/MotionPredictor.cpp
@@ -0,0 +1,266 @@
+/*
+ * 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 "MotionPredictor"
+
+#include <input/MotionPredictor.h>
+
+#include <cinttypes>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <string>
+#include <vector>
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <android/input.h>
+
+#include <attestation/HmacKeyManager.h>
+#include <ftl/enum.h>
+#include <input/TfLiteMotionPredictor.h>
+
+namespace android {
+namespace {
+
+/**
+ * Log debug messages about predictions.
+ * Enable this via "adb shell setprop log.tag.MotionPredictor DEBUG"
+ */
+bool isDebug() {
+ return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
+}
+
+// Converts a prediction of some polar (r, phi) to Cartesian (x, y) when applied to an axis.
+TfLiteMotionPredictorSample::Point convertPrediction(
+ const TfLiteMotionPredictorSample::Point& axisFrom,
+ const TfLiteMotionPredictorSample::Point& axisTo, float r, float phi) {
+ const TfLiteMotionPredictorSample::Point axis = axisTo - axisFrom;
+ const float axis_phi = std::atan2(axis.y, axis.x);
+ const float x_delta = r * std::cos(axis_phi + phi);
+ const float y_delta = r * std::sin(axis_phi + phi);
+ return {.x = axisTo.x + x_delta, .y = axisTo.y + y_delta};
+}
+
+} // namespace
+
+// --- MotionPredictor ---
+
+MotionPredictor::MotionPredictor(nsecs_t predictionTimestampOffsetNanos,
+ std::function<bool()> checkMotionPredictionEnabled,
+ ReportAtomFunction reportAtomFunction)
+ : mPredictionTimestampOffsetNanos(predictionTimestampOffsetNanos),
+ mCheckMotionPredictionEnabled(std::move(checkMotionPredictionEnabled)),
+ mReportAtomFunction(reportAtomFunction) {}
+
+android::base::Result<void> MotionPredictor::record(const MotionEvent& event) {
+ if (mLastEvent && mLastEvent->getDeviceId() != event.getDeviceId()) {
+ // We still have an active gesture for another device. The provided MotionEvent is not
+ // consistent with the previous gesture.
+ LOG(ERROR) << "Inconsistent event stream: last event is " << *mLastEvent << ", but "
+ << __func__ << " is called with " << event;
+ return android::base::Error()
+ << "Inconsistent event stream: still have an active gesture from device "
+ << mLastEvent->getDeviceId() << ", but received " << event;
+ }
+ if (!isPredictionAvailable(event.getDeviceId(), event.getSource())) {
+ ALOGE("Prediction not supported for device %d's %s source", event.getDeviceId(),
+ inputEventSourceToString(event.getSource()).c_str());
+ return {};
+ }
+
+ // Initialise the model now that it's likely to be used.
+ if (!mModel) {
+ mModel = TfLiteMotionPredictorModel::create();
+ LOG_ALWAYS_FATAL_IF(!mModel);
+ }
+
+ if (!mBuffers) {
+ mBuffers = std::make_unique<TfLiteMotionPredictorBuffers>(mModel->inputLength());
+ }
+
+ // Pass input event to the MetricsManager.
+ if (!mMetricsManager) {
+ mMetricsManager.emplace(mModel->config().predictionInterval, mModel->outputLength(),
+ mReportAtomFunction);
+ }
+ mMetricsManager->onRecord(event);
+
+ const int32_t action = event.getActionMasked();
+ if (action == AMOTION_EVENT_ACTION_UP || action == AMOTION_EVENT_ACTION_CANCEL) {
+ ALOGD_IF(isDebug(), "End of event stream");
+ mBuffers->reset();
+ mLastEvent.reset();
+ return {};
+ } else if (action != AMOTION_EVENT_ACTION_DOWN && action != AMOTION_EVENT_ACTION_MOVE) {
+ ALOGD_IF(isDebug(), "Skipping unsupported %s action",
+ MotionEvent::actionToString(action).c_str());
+ return {};
+ }
+
+ if (event.getPointerCount() != 1) {
+ ALOGD_IF(isDebug(), "Prediction not supported for multiple pointers");
+ return {};
+ }
+
+ const ToolType toolType = event.getPointerProperties(0)->toolType;
+ if (toolType != ToolType::STYLUS) {
+ ALOGD_IF(isDebug(), "Prediction not supported for non-stylus tool: %s",
+ ftl::enum_string(toolType).c_str());
+ return {};
+ }
+
+ for (size_t i = 0; i <= event.getHistorySize(); ++i) {
+ if (event.isResampled(0, i)) {
+ continue;
+ }
+ const PointerCoords* coords = event.getHistoricalRawPointerCoords(0, i);
+ mBuffers->pushSample(event.getHistoricalEventTime(i),
+ {
+ .position.x = coords->getAxisValue(AMOTION_EVENT_AXIS_X),
+ .position.y = coords->getAxisValue(AMOTION_EVENT_AXIS_Y),
+ .pressure = event.getHistoricalPressure(0, i),
+ .tilt = event.getHistoricalAxisValue(AMOTION_EVENT_AXIS_TILT,
+ 0, i),
+ .orientation = event.getHistoricalOrientation(0, i),
+ });
+ }
+
+ if (!mLastEvent) {
+ mLastEvent = MotionEvent();
+ }
+ mLastEvent->copyFrom(&event, /*keepHistory=*/false);
+
+ return {};
+}
+
+std::unique_ptr<MotionEvent> MotionPredictor::predict(nsecs_t timestamp) {
+ if (mBuffers == nullptr || !mBuffers->isReady()) {
+ return nullptr;
+ }
+
+ LOG_ALWAYS_FATAL_IF(!mModel);
+ mBuffers->copyTo(*mModel);
+ LOG_ALWAYS_FATAL_IF(!mModel->invoke());
+
+ // Read out the predictions.
+ const std::span<const float> predictedR = mModel->outputR();
+ const std::span<const float> predictedPhi = mModel->outputPhi();
+ const std::span<const float> predictedPressure = mModel->outputPressure();
+
+ TfLiteMotionPredictorSample::Point axisFrom = mBuffers->axisFrom().position;
+ TfLiteMotionPredictorSample::Point axisTo = mBuffers->axisTo().position;
+
+ if (isDebug()) {
+ ALOGD("axisFrom: %f, %f", axisFrom.x, axisFrom.y);
+ ALOGD("axisTo: %f, %f", axisTo.x, axisTo.y);
+ ALOGD("mInputR: %s", base::Join(mModel->inputR(), ", ").c_str());
+ ALOGD("mInputPhi: %s", base::Join(mModel->inputPhi(), ", ").c_str());
+ ALOGD("mInputPressure: %s", base::Join(mModel->inputPressure(), ", ").c_str());
+ ALOGD("mInputTilt: %s", base::Join(mModel->inputTilt(), ", ").c_str());
+ ALOGD("mInputOrientation: %s", base::Join(mModel->inputOrientation(), ", ").c_str());
+ ALOGD("predictedR: %s", base::Join(predictedR, ", ").c_str());
+ ALOGD("predictedPhi: %s", base::Join(predictedPhi, ", ").c_str());
+ ALOGD("predictedPressure: %s", base::Join(predictedPressure, ", ").c_str());
+ }
+
+ LOG_ALWAYS_FATAL_IF(!mLastEvent);
+ const MotionEvent& event = *mLastEvent;
+ bool hasPredictions = false;
+ std::unique_ptr<MotionEvent> prediction = std::make_unique<MotionEvent>();
+ int64_t predictionTime = mBuffers->lastTimestamp();
+ const int64_t futureTime = timestamp + mPredictionTimestampOffsetNanos;
+
+ for (size_t i = 0; i < static_cast<size_t>(predictedR.size()) && predictionTime <= futureTime;
+ ++i) {
+ if (predictedR[i] < mModel->config().distanceNoiseFloor) {
+ // Stop predicting when the predicted output is below the model's noise floor.
+ //
+ // We assume that all subsequent predictions in the batch are unreliable because later
+ // predictions are conditional on earlier predictions, and a state of noise is not a
+ // good basis for prediction.
+ //
+ // The UX trade-off is that this potentially sacrifices some predictions when the input
+ // device starts to speed up, but avoids producing noisy predictions as it slows down.
+ break;
+ }
+ // TODO(b/266747654): Stop predictions if confidence is < some threshold.
+
+ const TfLiteMotionPredictorSample::Point predictedPoint =
+ convertPrediction(axisFrom, axisTo, predictedR[i], predictedPhi[i]);
+
+ ALOGD_IF(isDebug(), "prediction %zu: %f, %f", i, predictedPoint.x, predictedPoint.y);
+ PointerCoords coords;
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, predictedPoint.x);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, predictedPoint.y);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, predictedPressure[i]);
+ // Copy forward tilt and orientation from the last event until they are predicted
+ // (b/291789258).
+ coords.setAxisValue(AMOTION_EVENT_AXIS_TILT,
+ event.getAxisValue(AMOTION_EVENT_AXIS_TILT, 0));
+ coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION,
+ event.getRawPointerCoords(0)->getAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION));
+
+ predictionTime += mModel->config().predictionInterval;
+ if (i == 0) {
+ hasPredictions = true;
+ prediction->initialize(InputEvent::nextId(), event.getDeviceId(), event.getSource(),
+ event.getDisplayId(), INVALID_HMAC, AMOTION_EVENT_ACTION_MOVE,
+ event.getActionButton(), event.getFlags(), event.getEdgeFlags(),
+ event.getMetaState(), event.getButtonState(),
+ event.getClassification(), event.getTransform(),
+ event.getXPrecision(), event.getYPrecision(),
+ event.getRawXCursorPosition(), event.getRawYCursorPosition(),
+ event.getRawTransform(), event.getDownTime(), predictionTime,
+ event.getPointerCount(), event.getPointerProperties(), &coords);
+ } else {
+ prediction->addSample(predictionTime, &coords);
+ }
+
+ axisFrom = axisTo;
+ axisTo = predictedPoint;
+ }
+
+ if (!hasPredictions) {
+ return nullptr;
+ }
+
+ // Pass predictions to the MetricsManager.
+ LOG_ALWAYS_FATAL_IF(!mMetricsManager);
+ mMetricsManager->onPredict(*prediction);
+
+ return prediction;
+}
+
+bool MotionPredictor::isPredictionAvailable(int32_t /*deviceId*/, int32_t source) {
+ // Global flag override
+ if (!mCheckMotionPredictionEnabled()) {
+ ALOGD_IF(isDebug(), "Prediction not available due to flag override");
+ return false;
+ }
+
+ // Prediction is only supported for stylus sources.
+ if (!isFromSource(source, AINPUT_SOURCE_STYLUS)) {
+ ALOGD_IF(isDebug(), "Prediction not available for non-stylus source: %s",
+ inputEventSourceToString(source).c_str());
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/input/MotionPredictorMetricsManager.cpp b/libs/input/MotionPredictorMetricsManager.cpp
new file mode 100644
index 0000000000..0412d08181
--- /dev/null
+++ b/libs/input/MotionPredictorMetricsManager.cpp
@@ -0,0 +1,378 @@
+/*
+ * 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 LOG_TAG "MotionPredictorMetricsManager"
+
+#include <input/MotionPredictorMetricsManager.h>
+
+#include <algorithm>
+
+#include <android-base/logging.h>
+
+#include "Eigen/Core"
+#include "Eigen/Geometry"
+
+#ifdef __ANDROID__
+#include <statslog_libinput.h>
+#endif
+
+namespace android {
+namespace {
+
+inline constexpr int NANOS_PER_SECOND = 1'000'000'000; // nanoseconds per second
+inline constexpr int NANOS_PER_MILLIS = 1'000'000; // nanoseconds per millisecond
+
+// Velocity threshold at which we report "high-velocity" metrics, in pixels per second.
+// This value was selected from manual experimentation, as a threshold that separates "fast"
+// (semi-sloppy) handwriting from more careful medium to slow handwriting.
+inline constexpr float HIGH_VELOCITY_THRESHOLD = 1100.0;
+
+// Small value to add to the path length when computing scale-invariant error to avoid division by
+// zero.
+inline constexpr float PATH_LENGTH_EPSILON = 0.001;
+
+} // namespace
+
+void MotionPredictorMetricsManager::defaultReportAtomFunction(
+ const MotionPredictorMetricsManager::AtomFields& atomFields) {
+ // Call stats_write logging function only on Android targets (not supported on host).
+#ifdef __ANDROID__
+ android::stats::libinput::
+ stats_write(android::stats::libinput::STYLUS_PREDICTION_METRICS_REPORTED,
+ /*stylus_vendor_id=*/0,
+ /*stylus_product_id=*/0,
+ atomFields.deltaTimeBucketMilliseconds,
+ atomFields.alongTrajectoryErrorMeanMillipixels,
+ atomFields.alongTrajectoryErrorStdMillipixels,
+ atomFields.offTrajectoryRmseMillipixels,
+ atomFields.pressureRmseMilliunits,
+ atomFields.highVelocityAlongTrajectoryRmse,
+ atomFields.highVelocityOffTrajectoryRmse,
+ atomFields.scaleInvariantAlongTrajectoryRmse,
+ atomFields.scaleInvariantOffTrajectoryRmse);
+#endif
+}
+
+MotionPredictorMetricsManager::MotionPredictorMetricsManager(
+ nsecs_t predictionInterval,
+ size_t maxNumPredictions,
+ ReportAtomFunction reportAtomFunction)
+ : mPredictionInterval(predictionInterval),
+ mMaxNumPredictions(maxNumPredictions),
+ mRecentGroundTruthPoints(maxNumPredictions + 1),
+ mAggregatedMetrics(maxNumPredictions),
+ mAtomFields(maxNumPredictions),
+ mReportAtomFunction(reportAtomFunction ? reportAtomFunction : defaultReportAtomFunction) {}
+
+void MotionPredictorMetricsManager::onRecord(const MotionEvent& inputEvent) {
+ // Convert MotionEvent to GroundTruthPoint.
+ const PointerCoords* coords = inputEvent.getRawPointerCoords(/*pointerIndex=*/0);
+ LOG_ALWAYS_FATAL_IF(coords == nullptr);
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f{coords->getY(),
+ coords->getX()},
+ .pressure =
+ inputEvent.getPressure(/*pointerIndex=*/0)},
+ .timestamp = inputEvent.getEventTime()};
+
+ // Handle event based on action type.
+ switch (inputEvent.getActionMasked()) {
+ case AMOTION_EVENT_ACTION_DOWN: {
+ clearStrokeData();
+ incorporateNewGroundTruth(groundTruthPoint);
+ break;
+ }
+ case AMOTION_EVENT_ACTION_MOVE: {
+ incorporateNewGroundTruth(groundTruthPoint);
+ break;
+ }
+ case AMOTION_EVENT_ACTION_UP:
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ // Only expect meaningful predictions when given at least two input points.
+ if (mRecentGroundTruthPoints.size() >= 2) {
+ computeAtomFields();
+ reportMetrics();
+ }
+ break;
+ }
+ }
+}
+
+// Adds new predictions to mRecentPredictions and maintains the invariant that elements are
+// sorted in ascending order of targetTimestamp.
+void MotionPredictorMetricsManager::onPredict(const MotionEvent& predictionEvent) {
+ for (size_t i = 0; i < predictionEvent.getHistorySize() + 1; ++i) {
+ // Convert MotionEvent to PredictionPoint.
+ const PointerCoords* coords =
+ predictionEvent.getHistoricalRawPointerCoords(/*pointerIndex=*/0, i);
+ LOG_ALWAYS_FATAL_IF(coords == nullptr);
+ const nsecs_t targetTimestamp = predictionEvent.getHistoricalEventTime(i);
+ mRecentPredictions.push_back(
+ PredictionPoint{{.position = Eigen::Vector2f{coords->getY(), coords->getX()},
+ .pressure =
+ predictionEvent.getHistoricalPressure(/*pointerIndex=*/0,
+ i)},
+ .originTimestamp = mRecentGroundTruthPoints.back().timestamp,
+ .targetTimestamp = targetTimestamp});
+ }
+
+ std::sort(mRecentPredictions.begin(), mRecentPredictions.end());
+}
+
+void MotionPredictorMetricsManager::clearStrokeData() {
+ mRecentGroundTruthPoints.clear();
+ mRecentPredictions.clear();
+ std::fill(mAggregatedMetrics.begin(), mAggregatedMetrics.end(), AggregatedStrokeMetrics{});
+ std::fill(mAtomFields.begin(), mAtomFields.end(), AtomFields{});
+}
+
+void MotionPredictorMetricsManager::incorporateNewGroundTruth(
+ const GroundTruthPoint& groundTruthPoint) {
+ // Note: this removes the oldest point if `mRecentGroundTruthPoints` is already at capacity.
+ mRecentGroundTruthPoints.pushBack(groundTruthPoint);
+
+ // Remove outdated predictions – those that can never be matched with the current or any future
+ // ground truth points. We use fuzzy association for the timestamps here, because ground truth
+ // and prediction timestamps may not be perfectly synchronized.
+ const nsecs_t fuzzy_association_time_delta = mPredictionInterval / 4;
+ const auto firstCurrentIt =
+ std::find_if(mRecentPredictions.begin(), mRecentPredictions.end(),
+ [&groundTruthPoint,
+ fuzzy_association_time_delta](const PredictionPoint& prediction) {
+ return prediction.targetTimestamp >
+ groundTruthPoint.timestamp - fuzzy_association_time_delta;
+ });
+ mRecentPredictions.erase(mRecentPredictions.begin(), firstCurrentIt);
+
+ // Fuzzily match the new ground truth's timestamp to recent predictions' targetTimestamp and
+ // update the corresponding metrics.
+ for (const PredictionPoint& prediction : mRecentPredictions) {
+ if ((prediction.targetTimestamp >
+ groundTruthPoint.timestamp - fuzzy_association_time_delta) &&
+ (prediction.targetTimestamp <
+ groundTruthPoint.timestamp + fuzzy_association_time_delta)) {
+ updateAggregatedMetrics(prediction);
+ }
+ }
+}
+
+void MotionPredictorMetricsManager::updateAggregatedMetrics(
+ const PredictionPoint& predictionPoint) {
+ if (mRecentGroundTruthPoints.size() < 2) {
+ return;
+ }
+
+ const GroundTruthPoint& latestGroundTruthPoint = mRecentGroundTruthPoints.back();
+ const GroundTruthPoint& previousGroundTruthPoint =
+ mRecentGroundTruthPoints[mRecentGroundTruthPoints.size() - 2];
+ // Calculate prediction error vector.
+ const Eigen::Vector2f groundTruthTrajectory =
+ latestGroundTruthPoint.position - previousGroundTruthPoint.position;
+ const Eigen::Vector2f predictionTrajectory =
+ predictionPoint.position - previousGroundTruthPoint.position;
+ const Eigen::Vector2f predictionError = predictionTrajectory - groundTruthTrajectory;
+
+ // By default, prediction error counts fully as both off-trajectory and along-trajectory error.
+ // This serves as the fallback when the two most recent ground truth points are equal.
+ const float predictionErrorNorm = predictionError.norm();
+ float alongTrajectoryError = predictionErrorNorm;
+ float offTrajectoryError = predictionErrorNorm;
+ if (groundTruthTrajectory.squaredNorm() > 0) {
+ // Rotate the prediction error vector by the angle of the ground truth trajectory vector.
+ // This yields a vector whose first component is the along-trajectory error and whose
+ // second component is the off-trajectory error.
+ const float theta = std::atan2(groundTruthTrajectory[1], groundTruthTrajectory[0]);
+ const Eigen::Vector2f rotatedPredictionError = Eigen::Rotation2Df(-theta) * predictionError;
+ alongTrajectoryError = rotatedPredictionError[0];
+ offTrajectoryError = rotatedPredictionError[1];
+ }
+
+ // Compute the multiple of mPredictionInterval nearest to the amount of time into the
+ // future being predicted. This serves as the time bucket index into mAggregatedMetrics.
+ const float timestampDeltaFloat =
+ static_cast<float>(predictionPoint.targetTimestamp - predictionPoint.originTimestamp);
+ const size_t tIndex =
+ static_cast<size_t>(std::round(timestampDeltaFloat / mPredictionInterval - 1));
+
+ // Aggregate values into "general errors".
+ mAggregatedMetrics[tIndex].alongTrajectoryErrorSum += alongTrajectoryError;
+ mAggregatedMetrics[tIndex].alongTrajectorySumSquaredErrors +=
+ alongTrajectoryError * alongTrajectoryError;
+ mAggregatedMetrics[tIndex].offTrajectorySumSquaredErrors +=
+ offTrajectoryError * offTrajectoryError;
+ const float pressureError = predictionPoint.pressure - latestGroundTruthPoint.pressure;
+ mAggregatedMetrics[tIndex].pressureSumSquaredErrors += pressureError * pressureError;
+ ++mAggregatedMetrics[tIndex].generalErrorsCount;
+
+ // Aggregate values into high-velocity metrics, if we are in one of the last two time buckets
+ // and the velocity is above the threshold. Velocity here is measured in pixels per second.
+ const float velocity = groundTruthTrajectory.norm() /
+ (static_cast<float>(latestGroundTruthPoint.timestamp -
+ previousGroundTruthPoint.timestamp) /
+ NANOS_PER_SECOND);
+ if ((tIndex + 2 >= mMaxNumPredictions) && (velocity > HIGH_VELOCITY_THRESHOLD)) {
+ mAggregatedMetrics[tIndex].highVelocityAlongTrajectorySse +=
+ alongTrajectoryError * alongTrajectoryError;
+ mAggregatedMetrics[tIndex].highVelocityOffTrajectorySse +=
+ offTrajectoryError * offTrajectoryError;
+ ++mAggregatedMetrics[tIndex].highVelocityErrorsCount;
+ }
+
+ // Compute path length for scale-invariant errors.
+ float pathLength = 0;
+ for (size_t i = 1; i < mRecentGroundTruthPoints.size(); ++i) {
+ pathLength +=
+ (mRecentGroundTruthPoints[i].position - mRecentGroundTruthPoints[i - 1].position)
+ .norm();
+ }
+ // Avoid overweighting errors at the beginning of a stroke: compute the path length as if there
+ // were a full ground truth history by filling in missing segments with the average length.
+ // Note: the "- 1" is needed to translate from number of endpoints to number of segments.
+ pathLength *= static_cast<float>(mRecentGroundTruthPoints.capacity() - 1) /
+ (mRecentGroundTruthPoints.size() - 1);
+ pathLength += PATH_LENGTH_EPSILON; // Ensure path length is nonzero (>= PATH_LENGTH_EPSILON).
+
+ // Compute and aggregate scale-invariant errors.
+ const float scaleInvariantAlongTrajectoryError = alongTrajectoryError / pathLength;
+ const float scaleInvariantOffTrajectoryError = offTrajectoryError / pathLength;
+ mAggregatedMetrics[tIndex].scaleInvariantAlongTrajectorySse +=
+ scaleInvariantAlongTrajectoryError * scaleInvariantAlongTrajectoryError;
+ mAggregatedMetrics[tIndex].scaleInvariantOffTrajectorySse +=
+ scaleInvariantOffTrajectoryError * scaleInvariantOffTrajectoryError;
+ ++mAggregatedMetrics[tIndex].scaleInvariantErrorsCount;
+}
+
+void MotionPredictorMetricsManager::computeAtomFields() {
+ for (size_t i = 0; i < mAggregatedMetrics.size(); ++i) {
+ if (mAggregatedMetrics[i].generalErrorsCount == 0) {
+ // We have not received data corresponding to metrics for this time bucket.
+ continue;
+ }
+
+ mAtomFields[i].deltaTimeBucketMilliseconds =
+ static_cast<int>(mPredictionInterval / NANOS_PER_MILLIS * (i + 1));
+
+ // Note: we need the "* 1000"s below because we report values in integral milli-units.
+
+ { // General errors: reported for every time bucket.
+ const float alongTrajectoryErrorMean = mAggregatedMetrics[i].alongTrajectoryErrorSum /
+ mAggregatedMetrics[i].generalErrorsCount;
+ mAtomFields[i].alongTrajectoryErrorMeanMillipixels =
+ static_cast<int>(alongTrajectoryErrorMean * 1000);
+
+ const float alongTrajectoryMse = mAggregatedMetrics[i].alongTrajectorySumSquaredErrors /
+ mAggregatedMetrics[i].generalErrorsCount;
+ // Take the max with 0 to avoid negative values caused by numerical instability.
+ const float alongTrajectoryErrorVariance =
+ std::max(0.0f,
+ alongTrajectoryMse -
+ alongTrajectoryErrorMean * alongTrajectoryErrorMean);
+ const float alongTrajectoryErrorStd = std::sqrt(alongTrajectoryErrorVariance);
+ mAtomFields[i].alongTrajectoryErrorStdMillipixels =
+ static_cast<int>(alongTrajectoryErrorStd * 1000);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].offTrajectorySumSquaredErrors < 0,
+ "mAggregatedMetrics[%zu].offTrajectorySumSquaredErrors = %f should "
+ "not be negative",
+ i, mAggregatedMetrics[i].offTrajectorySumSquaredErrors);
+ const float offTrajectoryRmse =
+ std::sqrt(mAggregatedMetrics[i].offTrajectorySumSquaredErrors /
+ mAggregatedMetrics[i].generalErrorsCount);
+ mAtomFields[i].offTrajectoryRmseMillipixels =
+ static_cast<int>(offTrajectoryRmse * 1000);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].pressureSumSquaredErrors < 0,
+ "mAggregatedMetrics[%zu].pressureSumSquaredErrors = %f should not "
+ "be negative",
+ i, mAggregatedMetrics[i].pressureSumSquaredErrors);
+ const float pressureRmse = std::sqrt(mAggregatedMetrics[i].pressureSumSquaredErrors /
+ mAggregatedMetrics[i].generalErrorsCount);
+ mAtomFields[i].pressureRmseMilliunits = static_cast<int>(pressureRmse * 1000);
+ }
+
+ // High-velocity errors: reported only for last two time buckets.
+ // Check if we are in one of the last two time buckets, and there is high-velocity data.
+ if ((i + 2 >= mMaxNumPredictions) && (mAggregatedMetrics[i].highVelocityErrorsCount > 0)) {
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].highVelocityAlongTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].highVelocityAlongTrajectorySse = %f "
+ "should not be negative",
+ i, mAggregatedMetrics[i].highVelocityAlongTrajectorySse);
+ const float alongTrajectoryRmse =
+ std::sqrt(mAggregatedMetrics[i].highVelocityAlongTrajectorySse /
+ mAggregatedMetrics[i].highVelocityErrorsCount);
+ mAtomFields[i].highVelocityAlongTrajectoryRmse =
+ static_cast<int>(alongTrajectoryRmse * 1000);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[i].highVelocityOffTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].highVelocityOffTrajectorySse = %f should "
+ "not be negative",
+ i, mAggregatedMetrics[i].highVelocityOffTrajectorySse);
+ const float offTrajectoryRmse =
+ std::sqrt(mAggregatedMetrics[i].highVelocityOffTrajectorySse /
+ mAggregatedMetrics[i].highVelocityErrorsCount);
+ mAtomFields[i].highVelocityOffTrajectoryRmse =
+ static_cast<int>(offTrajectoryRmse * 1000);
+ }
+
+ // Scale-invariant errors: reported only for the last time bucket, where the values
+ // represent an average across all time buckets.
+ if (i + 1 == mMaxNumPredictions) {
+ // Compute error averages.
+ float alongTrajectoryRmseSum = 0;
+ float offTrajectoryRmseSum = 0;
+ for (size_t j = 0; j < mAggregatedMetrics.size(); ++j) {
+ // If we have general errors (checked above), we should always also have
+ // scale-invariant errors.
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantErrorsCount == 0,
+ "mAggregatedMetrics[%zu].scaleInvariantErrorsCount is 0", j);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].scaleInvariantAlongTrajectorySse = %f "
+ "should not be negative",
+ j, mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse);
+ alongTrajectoryRmseSum +=
+ std::sqrt(mAggregatedMetrics[j].scaleInvariantAlongTrajectorySse /
+ mAggregatedMetrics[j].scaleInvariantErrorsCount);
+
+ LOG_ALWAYS_FATAL_IF(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse < 0,
+ "mAggregatedMetrics[%zu].scaleInvariantOffTrajectorySse = %f "
+ "should not be negative",
+ j, mAggregatedMetrics[j].scaleInvariantOffTrajectorySse);
+ offTrajectoryRmseSum +=
+ std::sqrt(mAggregatedMetrics[j].scaleInvariantOffTrajectorySse /
+ mAggregatedMetrics[j].scaleInvariantErrorsCount);
+ }
+
+ const float averageAlongTrajectoryRmse =
+ alongTrajectoryRmseSum / mAggregatedMetrics.size();
+ mAtomFields.back().scaleInvariantAlongTrajectoryRmse =
+ static_cast<int>(averageAlongTrajectoryRmse * 1000);
+
+ const float averageOffTrajectoryRmse = offTrajectoryRmseSum / mAggregatedMetrics.size();
+ mAtomFields.back().scaleInvariantOffTrajectoryRmse =
+ static_cast<int>(averageOffTrajectoryRmse * 1000);
+ }
+ }
+}
+
+void MotionPredictorMetricsManager::reportMetrics() {
+ LOG_ALWAYS_FATAL_IF(!mReportAtomFunction);
+ // Report one atom for each prediction time bucket.
+ for (size_t i = 0; i < mAtomFields.size(); ++i) {
+ mReportAtomFunction(mAtomFields[i]);
+ }
+}
+
+} // namespace android
diff --git a/libs/input/OWNERS b/libs/input/OWNERS
new file mode 100644
index 0000000000..c88bfe97ca
--- /dev/null
+++ b/libs/input/OWNERS
@@ -0,0 +1 @@
+include platform/frameworks/base:/INPUT_OWNERS
diff --git a/libs/input/PropertyMap.cpp b/libs/input/PropertyMap.cpp
index a842166761..5f6f9e26b6 100644
--- a/libs/input/PropertyMap.cpp
+++ b/libs/input/PropertyMap.cpp
@@ -16,7 +16,10 @@
#define LOG_TAG "PropertyMap"
+#include <cstdlib>
+
#include <input/PropertyMap.h>
+#include <log/log.h>
// Enables debug output for the parser.
#define DEBUG_PARSER 0
@@ -39,71 +42,85 @@ void PropertyMap::clear() {
mProperties.clear();
}
-void PropertyMap::addProperty(const String8& key, const String8& value) {
- mProperties.add(key, value);
+void PropertyMap::addProperty(const std::string& key, const std::string& value) {
+ mProperties.emplace(key, value);
}
-bool PropertyMap::hasProperty(const String8& key) const {
- return mProperties.indexOfKey(key) >= 0;
+std::unordered_set<std::string> PropertyMap::getKeysWithPrefix(const std::string& prefix) const {
+ std::unordered_set<std::string> keys;
+ for (const auto& [key, _] : mProperties) {
+ if (key.starts_with(prefix)) {
+ keys.insert(key);
+ }
+ }
+ return keys;
}
-bool PropertyMap::tryGetProperty(const String8& key, String8& outValue) const {
- ssize_t index = mProperties.indexOfKey(key);
- if (index < 0) {
- return false;
- }
+bool PropertyMap::hasProperty(const std::string& key) const {
+ return mProperties.find(key) != mProperties.end();
+}
+
+std::optional<std::string> PropertyMap::getString(const std::string& key) const {
+ auto it = mProperties.find(key);
+ return it != mProperties.end() ? std::make_optional(it->second) : std::nullopt;
+}
- outValue = mProperties.valueAt(index);
- return true;
+std::optional<bool> PropertyMap::getBool(const std::string& key) const {
+ std::optional<int32_t> intValue = getInt(key);
+ return intValue.has_value() ? std::make_optional(*intValue != 0) : std::nullopt;
}
-bool PropertyMap::tryGetProperty(const String8& key, bool& outValue) const {
- int32_t intValue;
- if (!tryGetProperty(key, intValue)) {
- return false;
+std::optional<int32_t> PropertyMap::getInt(const std::string& key) const {
+ std::optional<std::string> stringValue = getString(key);
+ if (!stringValue.has_value() || stringValue->length() == 0) {
+ return std::nullopt;
}
- outValue = intValue;
- return true;
+ char* end;
+ int32_t value = static_cast<int32_t>(strtol(stringValue->c_str(), &end, 10));
+ if (*end != '\0') {
+ ALOGW("Property key '%s' has invalid value '%s'. Expected an integer.", key.c_str(),
+ stringValue->c_str());
+ return std::nullopt;
+ }
+ return value;
}
-bool PropertyMap::tryGetProperty(const String8& key, int32_t& outValue) const {
- String8 stringValue;
- if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) {
- return false;
+std::optional<float> PropertyMap::getFloat(const std::string& key) const {
+ std::optional<std::string> stringValue = getString(key);
+ if (!stringValue.has_value() || stringValue->length() == 0) {
+ return std::nullopt;
}
char* end;
- int value = strtol(stringValue.string(), &end, 10);
+ float value = strtof(stringValue->c_str(), &end);
if (*end != '\0') {
- ALOGW("Property key '%s' has invalid value '%s'. Expected an integer.", key.string(),
- stringValue.string());
- return false;
+ ALOGW("Property key '%s' has invalid value '%s'. Expected a float.", key.c_str(),
+ stringValue->c_str());
+ return std::nullopt;
}
- outValue = value;
- return true;
+ return value;
}
-bool PropertyMap::tryGetProperty(const String8& key, float& outValue) const {
- String8 stringValue;
- if (!tryGetProperty(key, stringValue) || stringValue.length() == 0) {
- return false;
+std::optional<double> PropertyMap::getDouble(const std::string& key) const {
+ std::optional<std::string> stringValue = getString(key);
+ if (!stringValue.has_value() || stringValue->length() == 0) {
+ return std::nullopt;
}
char* end;
- float value = strtof(stringValue.string(), &end);
+ double value = strtod(stringValue->c_str(), &end);
if (*end != '\0') {
- ALOGW("Property key '%s' has invalid value '%s'. Expected a float.", key.string(),
- stringValue.string());
- return false;
+ ALOGW("Property key '%s' has invalid value '%s'. Expected a double.", key.c_str(),
+ stringValue->c_str());
+ return std::nullopt;
}
- outValue = value;
- return true;
+ return value;
}
void PropertyMap::addAll(const PropertyMap* map) {
- for (size_t i = 0; i < map->mProperties.size(); i++) {
- mProperties.add(map->mProperties.keyAt(i), map->mProperties.valueAt(i));
+ for (const auto& [key, value] : map->mProperties) {
+ mProperties.emplace(key, value);
}
}
@@ -115,25 +132,24 @@ android::base::Result<std::unique_ptr<PropertyMap>> PropertyMap::load(const char
Tokenizer* rawTokenizer;
status_t status = Tokenizer::open(String8(filename), &rawTokenizer);
- std::unique_ptr<Tokenizer> tokenizer(rawTokenizer);
if (status) {
- ALOGE("Error %d opening property file %s.", status, filename);
- } else {
+ return android::base::Error(-status) << "Could not open file: " << filename;
+ }
#if DEBUG_PARSER_PERFORMANCE
- nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ nsecs_t startTime = systemTime(SYSTEM_TIME_MONOTONIC);
#endif
- Parser parser(outMap.get(), tokenizer.get());
- status = parser.parse();
+ std::unique_ptr<Tokenizer> tokenizer(rawTokenizer);
+ Parser parser(outMap.get(), tokenizer.get());
+ status = parser.parse();
#if DEBUG_PARSER_PERFORMANCE
- nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
- ALOGD("Parsed property file '%s' %d lines in %0.3fms.",
- tokenizer->getFilename().string(), tokenizer->getLineNumber(),
- elapsedTime / 1000000.0);
+ nsecs_t elapsedTime = systemTime(SYSTEM_TIME_MONOTONIC) - startTime;
+ ALOGD("Parsed property file '%s' %d lines in %0.3fms.", tokenizer->getFilename().string(),
+ tokenizer->getLineNumber(), elapsedTime / 1000000.0);
#endif
- if (status) {
- return android::base::Error(BAD_VALUE) << "Could not parse " << filename;
- }
+ if (status) {
+ return android::base::Error(BAD_VALUE) << "Could not parse " << filename;
}
+
return std::move(outMap);
}
@@ -147,16 +163,16 @@ PropertyMap::Parser::~Parser() {}
status_t PropertyMap::Parser::parse() {
while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
- ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().c_str(),
+ mTokenizer->peekRemainderOfLine().c_str());
#endif
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol() && mTokenizer->peekChar() != '#') {
String8 keyToken = mTokenizer->nextToken(WHITESPACE_OR_PROPERTY_DELIMITER);
- if (keyToken.isEmpty()) {
- ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().string());
+ if (keyToken.empty()) {
+ ALOGE("%s: Expected non-empty property key.", mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
@@ -164,7 +180,7 @@ status_t PropertyMap::Parser::parse() {
if (mTokenizer->nextChar() != '=') {
ALOGE("%s: Expected '=' between property key and value.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
@@ -173,24 +189,24 @@ status_t PropertyMap::Parser::parse() {
String8 valueToken = mTokenizer->nextToken(WHITESPACE);
if (valueToken.find("\\", 0) >= 0 || valueToken.find("\"", 0) >= 0) {
ALOGE("%s: Found reserved character '\\' or '\"' in property value.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
mTokenizer->skipDelimiters(WHITESPACE);
if (!mTokenizer->isEol()) {
- ALOGE("%s: Expected end of line, got '%s'.", mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ ALOGE("%s: Expected end of line, got '%s'.", mTokenizer->getLocation().c_str(),
+ mTokenizer->peekRemainderOfLine().c_str());
return BAD_VALUE;
}
- if (mMap->hasProperty(keyToken)) {
+ if (mMap->hasProperty(keyToken.c_str())) {
ALOGE("%s: Duplicate property value for key '%s'.",
- mTokenizer->getLocation().string(), keyToken.string());
+ mTokenizer->getLocation().c_str(), keyToken.c_str());
return BAD_VALUE;
}
- mMap->addProperty(keyToken, valueToken);
+ mMap->addProperty(keyToken.c_str(), valueToken.c_str());
}
mTokenizer->nextLine();
diff --git a/libs/input/PropertyMap_fuzz.cpp b/libs/input/PropertyMap_fuzz.cpp
index afb97a1d47..6299ca8ef9 100755..100644
--- a/libs/input/PropertyMap_fuzz.cpp
+++ b/libs/input/PropertyMap_fuzz.cpp
@@ -17,32 +17,21 @@
#include "android-base/file.h"
#include "fuzzer/FuzzedDataProvider.h"
#include "input/PropertyMap.h"
-#include "utils/String8.h"
static constexpr int MAX_FILE_SIZE = 256;
static constexpr int MAX_STR_LEN = 2048;
static constexpr int MAX_OPERATIONS = 1000;
-static const std::vector<std::function<void(FuzzedDataProvider*, android::PropertyMap)>>
+static const std::vector<std::function<void(FuzzedDataProvider*, android::PropertyMap&)>>
operations = {
- [](FuzzedDataProvider*, android::PropertyMap propertyMap) -> void {
- propertyMap.getProperties();
- },
- [](FuzzedDataProvider*, android::PropertyMap propertyMap) -> void {
+ [](FuzzedDataProvider*, android::PropertyMap& propertyMap) -> void {
propertyMap.clear();
},
- [](FuzzedDataProvider* dataProvider, android::PropertyMap propertyMap) -> void {
- std::string keyStr = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
- android::String8 key = android::String8(keyStr.c_str());
- propertyMap.hasProperty(key);
- },
- [](FuzzedDataProvider* dataProvider, android::PropertyMap propertyMap) -> void {
- std::string keyStr = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
- android::String8 key = android::String8(keyStr.c_str());
- android::String8 out;
- propertyMap.tryGetProperty(key, out);
+ [](FuzzedDataProvider* dataProvider, android::PropertyMap& propertyMap) -> void {
+ std::string key = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
+ propertyMap.getString(key);
},
- [](FuzzedDataProvider* dataProvider, android::PropertyMap /*unused*/) -> void {
+ [](FuzzedDataProvider* dataProvider, android::PropertyMap& /*unused*/) -> void {
TemporaryFile tf;
// Generate file contents
std::string contents = dataProvider->ConsumeRandomLengthString(MAX_FILE_SIZE);
@@ -54,17 +43,15 @@ static const std::vector<std::function<void(FuzzedDataProvider*, android::Proper
}
android::PropertyMap::load(tf.path);
},
- [](FuzzedDataProvider* dataProvider, android::PropertyMap propertyMap) -> void {
- std::string keyStr = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
- std::string valStr = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
- android::String8 key = android::String8(keyStr.c_str());
- android::String8 val = android::String8(valStr.c_str());
+ [](FuzzedDataProvider* dataProvider, android::PropertyMap& propertyMap) -> void {
+ std::string key = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
+ std::string val = dataProvider->ConsumeRandomLengthString(MAX_STR_LEN);
propertyMap.addProperty(key, val);
},
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
FuzzedDataProvider dataProvider(data, size);
- android::PropertyMap propertyMap = android::PropertyMap();
+ android::PropertyMap propertyMap;
int opsRun = 0;
while (dataProvider.remaining_bytes() > 0 && opsRun++ < MAX_OPERATIONS) {
diff --git a/libs/input/TfLiteMotionPredictor.cpp b/libs/input/TfLiteMotionPredictor.cpp
new file mode 100644
index 0000000000..d17476e216
--- /dev/null
+++ b/libs/input/TfLiteMotionPredictor.cpp
@@ -0,0 +1,422 @@
+/*
+ * 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 "TfLiteMotionPredictor"
+#include <input/TfLiteMotionPredictor.h>
+
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <memory>
+#include <span>
+#include <type_traits>
+#include <utility>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/mapped_file.h>
+#define ATRACE_TAG ATRACE_TAG_INPUT
+#include <cutils/trace.h>
+#include <log/log.h>
+#include <utils/Timers.h>
+
+#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"
+
+#include "tinyxml2.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.tflite";
+ if (fileExists(oemModel)) {
+ return oemModel;
+ }
+ return "/system/etc/motion_predictor_model.tflite";
+#else
+ return base::GetExecutableDirectory() + "/motion_predictor_model.tflite";
+#endif
+}
+
+std::string getConfigPath() {
+ // The config file should be alongside the model file.
+ return base::Dirname(getModelPath()) + "/motion_predictor_config.xml";
+}
+
+int64_t parseXMLInt64(const tinyxml2::XMLElement& configRoot, const char* elementName) {
+ const tinyxml2::XMLElement* element = configRoot.FirstChildElement(elementName);
+ LOG_ALWAYS_FATAL_IF(!element, "Could not find '%s' element", elementName);
+
+ int64_t value = 0;
+ LOG_ALWAYS_FATAL_IF(element->QueryInt64Text(&value) != tinyxml2::XML_SUCCESS,
+ "Failed to parse %s: %s", elementName, element->GetText());
+ return value;
+}
+
+float parseXMLFloat(const tinyxml2::XMLElement& configRoot, const char* elementName) {
+ const tinyxml2::XMLElement* element = configRoot.FirstChildElement(elementName);
+ LOG_ALWAYS_FATAL_IF(!element, "Could not find '%s' element", elementName);
+
+ float value = 0;
+ LOG_ALWAYS_FATAL_IF(element->QueryFloatText(&value) != tinyxml2::XML_SUCCESS,
+ "Failed to parse %s: %s", elementName, element->GetText());
+ return value;
+}
+
+// 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 <typename T>
+std::span<T> getTensorBuffer(typename std::conditional<std::is_const<T>::value, const TfLiteTensor*,
+ TfLiteTensor*>::type tensor) {
+ LOG_ALWAYS_FATAL_IF(!tensor);
+
+ const TfLiteType type = tflite::typeToTfLiteType<typename std::remove_cv<T>::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 std::span<T>(reinterpret_cast<T*>(tensor->data.data), tensor->bytes / sizeof(T));
+}
+
+// Verifies that a tensor exists and has an underlying buffer of type T.
+template <typename T>
+void checkTensor(const TfLiteTensor* tensor) {
+ LOG_ALWAYS_FATAL_IF(!tensor);
+
+ const auto buffer = getTensorBuffer<const T>(tensor);
+ LOG_ALWAYS_FATAL_IF(buffer.empty(), "No buffer for tensor '%s'", tensor->name);
+}
+
+std::unique_ptr<tflite::OpResolver> createOpResolver() {
+ auto resolver = std::make_unique<tflite::MutableOpResolver>();
+ resolver->AddBuiltin(::tflite::BuiltinOperator_CONCATENATION,
+ ::tflite::ops::builtin::Register_CONCATENATION());
+ resolver->AddBuiltin(::tflite::BuiltinOperator_FULLY_CONNECTED,
+ ::tflite::ops::builtin::Register_FULLY_CONNECTED());
+ resolver->AddBuiltin(::tflite::BuiltinOperator_GELU, ::tflite::ops::builtin::Register_GELU());
+ 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;
+
+ if (!mAxisFrom && r > 0) { // 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.
+ if (r > 0) {
+ 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> 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<android::base::MappedFile> modelBuffer =
+ android::base::MappedFile::FromFd(fd, /*offset=*/0, fdSize, PROT_READ);
+ if (!modelBuffer) {
+ PLOG(FATAL) << "Failed to mmap model";
+ }
+
+ const std::string configPath = getConfigPath();
+ tinyxml2::XMLDocument configDocument;
+ LOG_ALWAYS_FATAL_IF(configDocument.LoadFile(configPath.c_str()) != tinyxml2::XML_SUCCESS,
+ "Failed to load config file from %s", configPath.c_str());
+
+ // Parse configuration file.
+ const tinyxml2::XMLElement* configRoot = configDocument.FirstChildElement("motion-predictor");
+ LOG_ALWAYS_FATAL_IF(!configRoot);
+ Config config{
+ .predictionInterval = parseXMLInt64(*configRoot, "prediction-interval"),
+ .distanceNoiseFloor = parseXMLFloat(*configRoot, "distance-noise-floor"),
+ };
+
+ return std::unique_ptr<TfLiteMotionPredictorModel>(
+ new TfLiteMotionPredictorModel(std::move(modelBuffer), std::move(config)));
+}
+
+TfLiteMotionPredictorModel::TfLiteMotionPredictorModel(
+ std::unique_ptr<android::base::MappedFile> model, Config config)
+ : mFlatBuffer(std::move(model)), mConfig(std::move(config)) {
+ CHECK(mFlatBuffer);
+ mErrorReporter = std::make_unique<LoggingErrorReporter>();
+ 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<float>(mInputR);
+ checkTensor<float>(mInputPhi);
+ checkTensor<float>(mInputPressure);
+ checkTensor<float>(mInputTilt);
+ checkTensor<float>(mInputOrientation);
+ checkTensor<float>(mOutputR);
+ checkTensor<float>(mOutputPhi);
+ checkTensor<float>(mOutputPressure);
+
+ const auto checkInputTensorSize = [this](const TfLiteTensor* tensor) {
+ const size_t size = getTensorBuffer<const float>(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<const float>(mInputR).size();
+}
+
+size_t TfLiteMotionPredictorModel::outputLength() const {
+ return getTensorBuffer<const float>(mOutputR).size();
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputR() {
+ return getTensorBuffer<float>(mInputR);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputPhi() {
+ return getTensorBuffer<float>(mInputPhi);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputPressure() {
+ return getTensorBuffer<float>(mInputPressure);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputTilt() {
+ return getTensorBuffer<float>(mInputTilt);
+}
+
+std::span<float> TfLiteMotionPredictorModel::inputOrientation() {
+ return getTensorBuffer<float>(mInputOrientation);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputR() const {
+ return getTensorBuffer<const float>(mOutputR);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputPhi() const {
+ return getTensorBuffer<const float>(mOutputPhi);
+}
+
+std::span<const float> TfLiteMotionPredictorModel::outputPressure() const {
+ return getTensorBuffer<const float>(mOutputPressure);
+}
+
+} // namespace android
diff --git a/libs/input/TouchVideoFrame.cpp b/libs/input/TouchVideoFrame.cpp
index c62e0985f1..6d7d5614c6 100644
--- a/libs/input/TouchVideoFrame.cpp
+++ b/libs/input/TouchVideoFrame.cpp
@@ -40,16 +40,19 @@ const std::vector<int16_t>& 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 6e991e98bb..c835a081a5 100644
--- a/libs/input/VelocityControl.cpp
+++ b/libs/input/VelocityControl.cpp
@@ -37,6 +37,10 @@ VelocityControl::VelocityControl() {
reset();
}
+const VelocityControlParameters& VelocityControl::getParameters() const{
+ 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<float> vx = mVelocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, 0);
+ std::optional<float> 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 7f427f2364..613a0df040 100644
--- a/libs/input/VelocityTracker.cpp
+++ b/libs/input/VelocityTracker.cpp
@@ -16,17 +16,21 @@
#define LOG_TAG "VelocityTracker"
-#include <array>
+#include <android-base/logging.h>
+#include <ftl/enum.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
+#include <array>
#include <optional>
-#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
#include <input/VelocityTracker.h>
#include <utils/BitSet.h>
#include <utils/Timers.h>
+using std::literals::chrono_literals::operator""ms;
+
namespace android {
/**
@@ -53,12 +57,37 @@ const bool DEBUG_IMPULSE =
// Nanoseconds per milliseconds.
static const nsecs_t NANOS_PER_MS = 1000000;
+// Seconds per nanosecond.
+static const float SECONDS_PER_NANO = 1E-9;
+
+// 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<int32_t, VelocityTracker::Strategy> 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<int32_t> PLANAR_AXES = {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y};
+
+// Axes whose motion values are differential values (i.e. deltas).
+static const std::set<int32_t> 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<float, std::milli>(t).count() << " ms";
+ return stream.str();
+}
static float vectorDot(const float* a, const float* b, uint32_t m) {
float r = 0;
@@ -118,46 +147,35 @@ 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::isAxisSupported(int32_t axis) {
+ return DEFAULT_STRATEGY_BY_AXIS.find(axis) != DEFAULT_STRATEGY_BY_AXIS.end();
}
-bool VelocityTracker::configureStrategy(Strategy strategy) {
- if (strategy == VelocityTracker::Strategy::DEFAULT) {
- mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY);
+void VelocityTracker::configureStrategy(int32_t axis) {
+ const bool isDifferentialAxis = DIFFERENTIAL_AXES.find(axis) != DIFFERENTIAL_AXES.end();
+ if (isDifferentialAxis || mOverrideStrategy == VelocityTracker::Strategy::DEFAULT) {
+ // Do not allow overrides of strategies for differential axes, for now.
+ mConfiguredStrategies[axis] = createStrategy(DEFAULT_STRATEGY_BY_AXIS.at(axis),
+ /*deltaValues=*/isDifferentialAxis);
} else {
- mStrategy = createStrategy(strategy);
+ mConfiguredStrategies[axis] = createStrategy(mOverrideStrategy, /*deltaValues=*/false);
}
- return mStrategy != nullptr;
}
std::unique_ptr<VelocityTrackerStrategy> 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<ImpulseVelocityTrackerStrategy>();
+ ALOGI_IF(DEBUG_STRATEGY, "Initializing impulse strategy");
+ return std::make_unique<ImpulseVelocityTrackerStrategy>(deltaValues);
case VelocityTracker::Strategy::LSQ1:
return std::make_unique<LeastSquaresVelocityTrackerStrategy>(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<LeastSquaresVelocityTrackerStrategy>(2);
case VelocityTracker::Strategy::LSQ3:
@@ -167,17 +185,17 @@ std::unique_ptr<VelocityTrackerStrategy> 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<IntegratingVelocityTrackerStrategy>(1);
@@ -191,201 +209,227 @@ std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
default:
break;
}
+ LOG(FATAL) << "Invalid strategy: " << ftl::enum_string(strategy)
+ << ", deltaValues = " << deltaValues;
+
return nullptr;
}
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<VelocityTracker::Position>& 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 (pointerId < 0 || pointerId > MAX_POINTER_ID) {
+ LOG(FATAL) << "Invalid pointer ID " << pointerId << " for axis "
+ << MotionEvent::getLabel(axis);
}
- 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);
- }
+ 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());
+
// 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);
- }
+ LOG(INFO) << "VelocityTracker: addMovement axis=" << MotionEvent::getLabel(axis)
+ << ", eventTime=" << eventTime << ", pointerId=" << pointerId
+ << ", activePointerId=" << toString(mActivePointerId) << ", position=" << position
+ << ", velocity=" << toString(getVelocity(axis, pointerId));
}
}
-void VelocityTracker::addMovement(const MotionEvent* event) {
- int32_t actionMasked = event->getActionMasked();
+void VelocityTracker::addMovement(const MotionEvent& event) {
+ // Stores data about which axes to process based on the incoming motion event.
+ std::set<int32_t> 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));
- }
+ 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:
+ if (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) {
+ clearPointer(event.getPointerId(event.getActionIndex()));
+ return;
+ }
+ // Continue to ACTION_UP to ensure that the POINTER_STOPPED logic is triggered.
+ [[fallthrough]];
+ 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;
+ case AMOTION_EVENT_ACTION_CANCEL: {
+ clear();
+ return;
+ }
- uint32_t pointerIndex[MAX_POINTERS];
- for (size_t i = 0; i < pointerCount; i++) {
- pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
+ default:
+ // Ignore all other actions.
+ return;
}
- std::vector<Position> 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<float> VelocityTracker::getVelocity(int32_t axis, int32_t pointerId) const {
+ const auto& it = mConfiguredStrategies.find(axis);
+ if (it != mConfiguredStrategies.end()) {
+ return it->second->getVelocity(pointerId);
}
- *outVx = 0;
- *outVy = 0;
- return false;
+ return {};
}
-bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
- return mStrategy->getEstimator(id, outEstimator);
-}
-
-
-// --- LeastSquaresVelocityTrackerStrategy ---
-
-LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
- uint32_t degree, Weighting weighting) :
- mDegree(degree), mWeighting(weighting) {
- clear();
+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<float> velocity = getVelocity(axis, id);
+ if (velocity) {
+ float adjustedVelocity =
+ std::clamp(*velocity * units / 1000, -maxVelocity, maxVelocity);
+ computedVelocity.addVelocity(axis, id, adjustedVelocity);
+ }
+ }
+ }
+ return computedVelocity;
}
-LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
-}
+AccumulatingVelocityTrackerStrategy::AccumulatingVelocityTrackerStrategy(
+ nsecs_t horizonNanos, bool maintainHorizonDuringAdd)
+ : mHorizonNanos(horizonNanos), mMaintainHorizonDuringAdd(maintainHorizonDuringAdd) {}
-void LeastSquaresVelocityTrackerStrategy::clear() {
- mIndex = 0;
- mMovements[0].idBits.clear();
+void AccumulatingVelocityTrackerStrategy::clearPointer(int32_t pointerId) {
+ mMovements.erase(pointerId);
}
-void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
- BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
- mMovements[mIndex].idBits = remainingIdBits;
-}
+void AccumulatingVelocityTrackerStrategy::addMovement(nsecs_t eventTime, int32_t pointerId,
+ float position) {
+ auto [ringBufferIt, _] = mMovements.try_emplace(pointerId, HISTORY_SIZE);
+ RingBuffer<Movement>& movements = ringBufferIt->second;
+ const size_t size = movements.size();
-void LeastSquaresVelocityTrackerStrategy::addMovement(
- nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
- if (mMovements[mIndex].eventTime != eventTime) {
+ if (size != 0 && movements[size - 1].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.
+ // for this time (i.e. pop out the last element, and insert the updated movement).
// We only compare against the last value, as it is likely that addMovement is called
// in chronological order as events occur.
- mIndex++;
- }
- if (mIndex == HISTORY_SIZE) {
- mIndex = 0;
+ movements.popBack();
}
- Movement& movement = mMovements[mIndex];
- movement.eventTime = eventTime;
- movement.idBits = idBits;
- uint32_t count = idBits.count();
- for (uint32_t i = 0; i < count; i++) {
- movement.positions[i] = positions[i];
+ movements.pushBack({eventTime, position});
+
+ // Clear movements that do not fall within `mHorizonNanos` of the latest movement.
+ // Note that, if in the future we decide to use more movements (i.e. increase HISTORY_SIZE),
+ // we can consider making this step binary-search based, which will give us some improvement.
+ if (mMaintainHorizonDuringAdd) {
+ while (eventTime - movements[0].eventTime > mHorizonNanos) {
+ movements.popFront();
+ }
}
}
+// --- LeastSquaresVelocityTrackerStrategy ---
+
+LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(uint32_t degree,
+ Weighting weighting)
+ : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+ true /*maintainHorizonDuringAdd*/),
+ mDegree(degree),
+ mWeighting(weighting) {}
+
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
+
/**
* Solves a linear least squares problem to obtain a N degree polynomial that fits
* the specified input data as nearly as possible.
@@ -435,13 +479,14 @@ void LeastSquaresVelocityTrackerStrategy::addMovement(
* http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
* http://en.wikipedia.org/wiki/Gram-Schmidt
*/
-static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
- const std::vector<float>& w, uint32_t n, float* outB, float* outDet) {
+static std::optional<float> solveLeastSquares(const std::vector<float>& x,
+ const std::vector<float>& y,
+ const std::vector<float>& w, uint32_t n) {
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 +497,9 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
a[i][h] = a[i - 1][h] * x[h];
}
}
- if (DEBUG_STRATEGY) {
- ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
- }
+
+ ALOGD_IF(DEBUG_STRATEGY, " - a=%s",
+ matrixToString(&a[0][0], m, n, /*rowMajor=*/false).c_str());
// Apply the Gram-Schmidt process to A to obtain its QR decomposition.
float q[n][m]; // orthonormal basis, column-major order
@@ -473,10 +518,8 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
float norm = vectorNorm(&q[j][0], m);
if (norm < 0.000001f) {
// vectors are linearly dependent or zero so no solution
- if (DEBUG_STRATEGY) {
- ALOGD(" - no solution, norm=%f", norm);
- }
- return false;
+ ALOGD_IF(DEBUG_STRATEGY, " - no solution, norm=%f", norm);
+ return {};
}
float invNorm = 1.0f / norm;
@@ -488,8 +531,8 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
}
}
if (DEBUG_STRATEGY) {
- ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
- ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
+ ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, /*rowMajor=*/false).c_str());
+ ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, /*rowMajor=*/true).c_str());
// calculate QR, if we factored A correctly then QR should equal A
float qr[n][m];
@@ -501,7 +544,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
}
}
}
- ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
+ ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, /*rowMajor=*/false).c_str());
}
// Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
@@ -510,6 +553,7 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
for (uint32_t h = 0; h < m; h++) {
wy[h] = y[h] * w[h];
}
+ std::array<float, VelocityTracker::MAX_DEGREE + 1> outB;
for (uint32_t i = n; i != 0; ) {
i--;
outB[i] = vectorDot(&q[i][0], wy, m);
@@ -518,9 +562,8 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
}
outB[i] /= r[i][i];
}
- if (DEBUG_STRATEGY) {
- ALOGD(" - b=%s", vectorToString(outB, n).c_str());
- }
+
+ ALOGD_IF(DEBUG_STRATEGY, " - b=%s", vectorToString(outB.data(), n).c_str());
// Calculate the coefficient of determination as 1 - (SSerr / SStot) where
// SSerr is the residual sum of squares (variance of the error),
@@ -532,42 +575,46 @@ static bool solveLeastSquares(const std::vector<float>& x, const std::vector<flo
}
ymean /= m;
- float sserr = 0;
- float sstot = 0;
- for (uint32_t h = 0; h < m; h++) {
- float err = y[h] - outB[0];
- float term = 1;
- for (uint32_t i = 1; i < n; i++) {
- term *= x[h];
- err -= term * outB[i];
- }
- sserr += w[h] * w[h] * err * err;
- float var = y[h] - ymean;
- sstot += w[h] * w[h] * var * var;
- }
- *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
if (DEBUG_STRATEGY) {
+ float sserr = 0;
+ float sstot = 0;
+ for (uint32_t h = 0; h < m; h++) {
+ float err = y[h] - outB[0];
+ float term = 1;
+ for (uint32_t i = 1; i < n; i++) {
+ term *= x[h];
+ err -= term * outB[i];
+ }
+ sserr += w[h] * w[h] * err * err;
+ float var = y[h] - ymean;
+ sstot += w[h] * w[h] * var * var;
+ }
ALOGD(" - sserr=%f", sserr);
ALOGD(" - sstot=%f", sstot);
- ALOGD(" - det=%f", *outDet);
}
- return true;
+
+ return outB[1];
}
/*
* Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
* the default implementation
*/
-static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
- const std::vector<float>& x, const std::vector<float>& y) {
- const size_t count = x.size();
- LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes");
- // Solving y = a*x^2 + b*x + c
+std::optional<float> LeastSquaresVelocityTrackerStrategy::solveUnweightedLeastSquaresDeg2(
+ const RingBuffer<Movement>& movements) const {
+ // Solving y = a*x^2 + b*x + c, where
+ // - "x" is age (i.e. duration since latest movement) of the movemnets
+ // - "y" is positions of the movements.
float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
+ const size_t count = movements.size();
+ const Movement& newestMovement = movements[count - 1];
for (size_t i = 0; i < count; i++) {
- float xi = x[i];
- float yi = y[i];
+ const Movement& movement = movements[i];
+ nsecs_t age = newestMovement.eventTime - movement.eventTime;
+ float xi = -age * SECONDS_PER_NANO;
+ float yi = movement.position;
+
float xi2 = xi*xi;
float xi3 = xi2*xi;
float xi4 = xi3*xi;
@@ -594,171 +641,121 @@ static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
return std::nullopt;
}
- // Compute a
- float numerator = Sx2y*Sxx - Sxy*Sxx2;
- float a = numerator / denominator;
- // Compute b
- numerator = Sxy*Sx2x2 - Sx2y*Sxx2;
- float b = numerator / denominator;
+ return (Sxy * Sx2x2 - Sx2y * Sxx2) / denominator;
+}
- // Compute c
- float c = syi/count - b * sxi/count - a * sxi2/count;
+std::optional<float> LeastSquaresVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
+ const auto movementIt = mMovements.find(pointerId);
+ if (movementIt == mMovements.end()) {
+ return std::nullopt; // no data
+ }
- return std::make_optional(std::array<float, 3>({c, b, a}));
-}
+ const RingBuffer<Movement>& movements = movementIt->second;
+ const size_t size = movements.size();
+ if (size == 0) {
+ return std::nullopt; // no data
+ }
-bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->clear();
+ uint32_t degree = mDegree;
+ if (degree > size - 1) {
+ degree = size - 1;
+ }
+
+ if (degree <= 0) {
+ return std::nullopt;
+ }
+
+ if (degree == 2 && mWeighting == Weighting::NONE) {
+ // Optimize unweighted, quadratic polynomial fit
+ return solveUnweightedLeastSquaresDeg2(movements);
+ }
// Iterate over movement samples in reverse time order and collect samples.
- std::vector<float> x;
- std::vector<float> y;
+ std::vector<float> positions;
std::vector<float> w;
std::vector<float> time;
- uint32_t index = mIndex;
- const Movement& newestMovement = mMovements[mIndex];
- do {
- const Movement& movement = mMovements[index];
- if (!movement.idBits.hasBit(id)) {
- break;
- }
-
+ const Movement& newestMovement = movements[size - 1];
+ for (ssize_t i = size - 1; i >= 0; i--) {
+ const Movement& movement = movements[i];
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));
+ positions.push_back(movement.position);
+ w.push_back(chooseWeight(pointerId, i));
time.push_back(-age * 0.000000001f);
- index = (index == 0 ? HISTORY_SIZE : index) - 1;
- } while (x.size() < HISTORY_SIZE);
-
- const size_t m = x.size();
- if (m == 0) {
- return false; // no data
- }
-
- // Calculate a least squares polynomial fit.
- uint32_t degree = mDegree;
- if (degree > m - 1) {
- degree = m - 1;
- }
-
- if (degree == 2 && mWeighting == WEIGHTING_NONE) {
- // Optimize unweighted, quadratic polynomial fit
- std::optional<std::array<float, 3>> xCoeff = solveUnweightedLeastSquaresDeg2(time, x);
- std::optional<std::array<float, 3>> 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];
- }
- return true;
- }
- } else if (degree >= 1) {
- // General case for an Nth degree polynomial fit
- float xdet, ydet;
- 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;
- }
}
- // 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;
+ // General case for an Nth degree polynomial fit
+ return solveLeastSquares(time, positions, w, degree + 1);
}
-float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
+float LeastSquaresVelocityTrackerStrategy::chooseWeight(int32_t pointerId, uint32_t index) const {
+ const RingBuffer<Movement>& movements = mMovements.at(pointerId);
+ const size_t size = movements.size();
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 == size - 1) {
+ return 1.0f;
+ }
+ float deltaMillis =
+ (movements[index + 1].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[size - 1].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[size - 1].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 +765,42 @@ 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<VelocityTracker::Position>& 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<float> IntegratingVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
+ if (mPointerIdBits.hasBit(pointerId)) {
+ return mPointerState[pointerId].vel;
}
- 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,107 +811,61 @@ 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;
-}
-
-void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
- VelocityTracker::Estimator* outEstimator) const {
- 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;
+ state.pos = pos;
}
-
// --- LegacyVelocityTrackerStrategy ---
-LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
- clear();
-}
+LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy()
+ : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+ false /*maintainHorizonDuringAdd*/) {}
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::addMovement(
- nsecs_t eventTime, BitSet32 idBits,
- const std::vector<VelocityTracker::Position>& positions) {
- if (++mIndex == HISTORY_SIZE) {
- mIndex = 0;
+std::optional<float> LegacyVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
+ const auto movementIt = mMovements.find(pointerId);
+ if (movementIt == mMovements.end()) {
+ return std::nullopt; // no data
}
- Movement& movement = mMovements[mIndex];
- movement.eventTime = eventTime;
- movement.idBits = idBits;
- uint32_t count = idBits.count();
- for (uint32_t i = 0; i < count; i++) {
- movement.positions[i] = positions[i];
+ const RingBuffer<Movement>& movements = movementIt->second;
+ const size_t size = movements.size();
+ if (size == 0) {
+ return std::nullopt; // no data
}
-}
-
-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
- }
+ const Movement& newestMovement = movements[size - 1];
// 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 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) {
+ uint32_t oldestIndex = size - 1;
+ for (ssize_t i = size - 1; i >= 0; i--) {
+ const Movement& nextOldestMovement = movements[i];
+ if (nextOldestMovement.eventTime < minTime) {
break;
}
- oldestIndex = nextOldestIndex;
- } while (++numTouches < HISTORY_SIZE);
+ oldestIndex = i;
+ }
// Calculate an exponentially weighted moving average of the velocity estimate
// at different points in time measured relative to the oldest sample.
@@ -945,96 +878,45 @@ 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;
- uint32_t index = oldestIndex;
+ float accumV = 0;
uint32_t samplesUsed = 0;
- const Movement& oldestMovement = mMovements[oldestIndex];
- const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
+ const Movement& oldestMovement = movements[oldestIndex];
+ float oldestPosition = oldestMovement.position;
nsecs_t lastDuration = 0;
- while (numTouches-- > 1) {
- if (++index == HISTORY_SIZE) {
- index = 0;
- }
- const Movement& movement = mMovements[index];
+ for (size_t i = oldestIndex; i < size; i++) {
+ const Movement& movement = movements[i];
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;
if (samplesUsed) {
- outEstimator->xCoeff[1] = accumVx;
- outEstimator->yCoeff[1] = accumVy;
- outEstimator->degree = 1;
- } else {
- outEstimator->degree = 0;
+ return accumV;
}
- return true;
+ return std::nullopt;
}
// --- ImpulseVelocityTrackerStrategy ---
-ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() {
- clear();
-}
+ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy(bool deltaValues)
+ : AccumulatingVelocityTrackerStrategy(HORIZON /*horizonNanos*/,
+ true /*maintainHorizonDuringAdd*/),
+ mDeltaValues(deltaValues) {}
ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
}
-void ImpulseVelocityTrackerStrategy::clear() {
- mIndex = 0;
- mMovements[0].idBits.clear();
-}
-
-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<VelocityTracker::Position>& positions) {
- if (mMovements[mIndex].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++;
- }
- if (mIndex == HISTORY_SIZE) {
- mIndex = 0;
- }
-
- Movement& movement = mMovements[mIndex];
- movement.eventTime = eventTime;
- movement.idBits = idBits;
- uint32_t count = idBits.count();
- for (uint32_t i = 0; i < count; i++) {
- movement.positions[i] = positions[i];
- }
-}
-
/**
* Calculate the total impulse provided to the screen and the resulting velocity.
*
@@ -1109,104 +991,53 @@ 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) {
- // 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;
-
- if (count < 2) {
- return 0; // if 0 or 1 points, velocity is zero
+std::optional<float> ImpulseVelocityTrackerStrategy::getVelocity(int32_t pointerId) const {
+ const auto movementIt = mMovements.find(pointerId);
+ if (movementIt == mMovements.end()) {
+ return std::nullopt; // no data
}
- if (t[1] > t[0]) { // Algorithm will still work, but not perfectly
- ALOGE("Samples provided to calculateImpulseVelocity in the wrong order");
- }
- 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 RingBuffer<Movement>& movements = movementIt->second;
+ const size_t size = movements.size();
+ if (size == 0) {
+ return std::nullopt; // no data
}
- // Guaranteed to have at least 3 points here
+
float work = 0;
- for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time
- if (t[i] == t[i-1]) {
- ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]);
- 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]
+ for (size_t i = 0; i < size - 1; i++) {
+ const Movement& mvt = movements[i];
+ const Movement& nextMvt = movements[i + 1];
+
+ float vprev = kineticEnergyToVelocity(work);
+ float delta = mDeltaValues ? nextMvt.position : nextMvt.position - mvt.position;
+ float vcurr = delta / (SECONDS_PER_NANO * (nextMvt.eventTime - mvt.eventTime));
work += (vcurr - vprev) * fabsf(vcurr);
- if (i == count - 1) {
+
+ if (i == 0) {
work *= 0.5; // initial condition, case 2) above
}
}
- return kineticEnergyToVelocity(work);
-}
-bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id,
- VelocityTracker::Estimator* outEstimator) const {
- outEstimator->clear();
+ const float velocity = kineticEnergyToVelocity(work);
+ ALOGD_IF(DEBUG_STRATEGY, "velocity: %.1f", velocity);
- // Iterate over movement samples in reverse time order and collect samples.
- float x[HISTORY_SIZE];
- float y[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];
- do {
- const Movement& movement = mMovements[index];
- if (!movement.idBits.hasBit(id)) {
- break;
- }
-
- nsecs_t age = newestMovement.eventTime - movement.eventTime;
- if (age > HORIZON) {
- break;
- }
-
- const VelocityTracker::Position& position = movement.getPosition(id);
- x[m] = position.x;
- y[m] = position.y;
- 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]);
- }
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]}});
+ for (size_t i = 0; i < size; i++) {
+ const Movement& mvt = movements[i];
+ lsq2.addMovement(mvt.eventTime, pointerId, AMOTION_EVENT_AXIS_X, mvt.position);
}
- float outVx = 0, outVy = 0;
- const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy);
- if (computed) {
- ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy);
+ std::optional<float> 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 velocity;
}
} // namespace android
diff --git a/libs/input/VirtualInputDevice.cpp b/libs/input/VirtualInputDevice.cpp
new file mode 100644
index 0000000000..db7031ab03
--- /dev/null
+++ b/libs/input/VirtualInputDevice.cpp
@@ -0,0 +1,396 @@
+/*
+ * 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 <android/input.h>
+#include <android/keycodes.h>
+#include <fcntl.h>
+#include <input/Input.h>
+#include <input/VirtualInputDevice.h>
+#include <linux/uinput.h>
+#include <math.h>
+#include <utils/Log.h>
+
+#include <map>
+#include <string>
+
+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<std::chrono::seconds>(eventTime);
+ std::chrono::microseconds microseconds =
+ std::chrono::duration_cast<std::chrono::microseconds>(eventTime - seconds);
+ struct input_event ev = {.type = type, .code = code, .value = value};
+ ev.input_event_sec = static_cast<decltype(ev.input_event_sec)>(seconds.count());
+ ev.input_event_usec = static_cast<decltype(ev.input_event_usec)>(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<int, int>& evKeyCodeMapping,
+ const std::map<int, UinputAction>& 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<uint16_t>(evKeyCodeIterator->second),
+ static_cast<int32_t>(actionIterator->second), eventTime)) {
+ return false;
+ }
+ if (!writeInputEvent(EV_SYN, SYN_REPORT, 0, eventTime)) {
+ return false;
+ }
+ return true;
+}
+
+// --- VirtualKeyboard ---
+const std::map<int, UinputAction> 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<int, int> 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},
+ {AKEYCODE_LANGUAGE_SWITCH, KEY_LANGUAGE},
+};
+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<int, int> 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<int, UinputAction> 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<int, int> 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<int, UinputAction> 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<int, int> 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<int32_t>(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<int32_t>(-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<int32_t>(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<int32_t>(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<int32_t>(pointerId), eventTime)) {
+ return false;
+ }
+ return true;
+}
+
+} // namespace android
diff --git a/libs/input/VirtualKeyMap.cpp b/libs/input/VirtualKeyMap.cpp
index 865366bcb2..8b8af4290f 100644
--- a/libs/input/VirtualKeyMap.cpp
+++ b/libs/input/VirtualKeyMap.cpp
@@ -79,8 +79,8 @@ VirtualKeyMap::Parser::~Parser() {
status_t VirtualKeyMap::Parser::parse() {
while (!mTokenizer->isEof()) {
#if DEBUG_PARSER
- ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ ALOGD("Parsing %s: '%s'.", mTokenizer->getLocation().c_str(),
+ mTokenizer->peekRemainderOfLine().c_str());
#endif
mTokenizer->skipDelimiters(WHITESPACE);
@@ -91,7 +91,7 @@ status_t VirtualKeyMap::Parser::parse() {
String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
if (token != "0x01") {
ALOGE("%s: Unknown virtual key type, expected 0x01.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
@@ -103,7 +103,7 @@ status_t VirtualKeyMap::Parser::parse() {
&& parseNextIntField(&defn.height);
if (!success) {
ALOGE("%s: Expected 5 colon-delimited integers in virtual key definition.",
- mTokenizer->getLocation().string());
+ mTokenizer->getLocation().c_str());
return BAD_VALUE;
}
@@ -116,9 +116,8 @@ status_t VirtualKeyMap::Parser::parse() {
} while (consumeFieldDelimiterAndSkipWhitespace());
if (!mTokenizer->isEol()) {
- ALOGE("%s: Expected end of line, got '%s'.",
- mTokenizer->getLocation().string(),
- mTokenizer->peekRemainderOfLine().string());
+ ALOGE("%s: Expected end of line, got '%s'.", mTokenizer->getLocation().c_str(),
+ mTokenizer->peekRemainderOfLine().c_str());
return BAD_VALUE;
}
}
@@ -146,9 +145,9 @@ bool VirtualKeyMap::Parser::parseNextIntField(int32_t* outValue) {
String8 token = mTokenizer->nextToken(WHITESPACE_OR_FIELD_DELIMITER);
char* end;
- *outValue = strtol(token.string(), &end, 0);
- if (token.isEmpty() || *end != '\0') {
- ALOGE("Expected an integer, got '%s'.", token.string());
+ *outValue = strtol(token.c_str(), &end, 0);
+ if (token.empty() || *end != '\0') {
+ ALOGE("Expected an integer, got '%s'.", token.c_str());
return false;
}
return true;
diff --git a/libs/input/android/os/IInputConstants.aidl b/libs/input/android/os/IInputConstants.aidl
index 5ce10a4a50..8f6f95b7d1 100644
--- a/libs/input/android/os/IInputConstants.aidl
+++ b/libs/input/android/os/IInputConstants.aidl
@@ -35,6 +35,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.
*/
@@ -49,4 +57,88 @@ interface IInputConstants
/* The default pointer acceleration value. */
const int DEFAULT_POINTER_ACCELERATION = 3;
+
+ /**
+ * Use the default Velocity Tracker Strategy. Different axes may use different default
+ * strategies.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_DEFAULT = -1;
+
+ /**
+ * Velocity Tracker Strategy: Impulse.
+ * Physical model of pushing an object. Quality: VERY GOOD.
+ * Works with duplicate coordinates, unclean finger liftoff.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_IMPULSE = 0;
+
+ /**
+ * Velocity Tracker Strategy: LSQ1.
+ * 1st order least squares. Quality: POOR.
+ * Frequently underfits the touch data especially when the finger accelerates
+ * or changes direction. Often underestimates velocity. The direction
+ * is overly influenced by historical touch points.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LSQ1 = 1;
+
+ /**
+ * Velocity Tracker Strategy: LSQ2.
+ * 2nd order least squares. Quality: VERY GOOD.
+ * Pretty much ideal, but can be confused by certain kinds of touch data,
+ * particularly if the panel has a tendency to generate delayed,
+ * duplicate or jittery touch coordinates when the finger is released.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LSQ2 = 2;
+
+ /**
+ * Velocity Tracker Strategy: LSQ3.
+ * 3rd order least squares. Quality: UNUSABLE.
+ * Frequently overfits the touch data yielding wildly divergent estimates
+ * of the velocity when the finger is released.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LSQ3 = 3;
+
+ /**
+ * Velocity Tracker Strategy: WLSQ2_DELTA.
+ * 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL
+ */
+ const int VELOCITY_TRACKER_STRATEGY_WLSQ2_DELTA = 4;
+
+ /**
+ * Velocity Tracker Strategy: WLSQ2_CENTRAL.
+ * 2nd order weighted least squares, central weighting. Quality: EXPERIMENTALe
+ */
+ const int VELOCITY_TRACKER_STRATEGY_WLSQ2_CENTRAL = 5;
+
+ /**
+ * Velocity Tracker Strategy: WLSQ2_RECENT.
+ * 2nd order weighted least squares, recent weighting. Quality: EXPERIMENTAL
+ */
+ const int VELOCITY_TRACKER_STRATEGY_WLSQ2_RECENT = 6;
+
+ /**
+ * Velocity Tracker Strategy: INT1.
+ * 1st order integrating filter. Quality: GOOD.
+ * Not as good as 'lsq2' because it cannot estimate acceleration but it is
+ * more tolerant of errors. Like 'lsq1', this strategy tends to underestimate
+ * the velocity of a fling but this strategy tends to respond to changes in
+ * direction more quickly and accurately.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_INT1 = 7;
+
+ /**
+ * Velocity Tracker Strategy: INT2.
+ * 2nd order integrating filter. Quality: EXPERIMENTAL.
+ * For comparison purposes only. Unlike 'int1' this strategy can compensate
+ * for acceleration but it typically overestimates the effect.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_INT2 = 8;
+
+ /**
+ * Velocity Tracker Strategy: Legacy.
+ * Legacy velocity tracker algorithm. Quality: POOR.
+ * For comparison purposes only. This algorithm is strongly influenced by
+ * old data points, consistently underestimates velocity and takes a very long
+ * time to adjust to changes in direction.
+ */
+ const int VELOCITY_TRACKER_STRATEGY_LEGACY = 9;
}
diff --git a/libs/input/android/os/InputConfig.aidl b/libs/input/android/os/InputConfig.aidl
index 6d1b3967f7..4e644fff06 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 3bc7068f3c..e80c2a52dc 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 95d24cb443..2d225fa452 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/cmds/installd/view_compiler.h b/libs/input/ffi/FromRustToCpp.h
index aa141ca257..889945c32b 100644
--- a/cmds/installd/view_compiler.h
+++ b/libs/input/ffi/FromRustToCpp.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 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.
@@ -14,16 +14,10 @@
* limitations under the License.
*/
-#ifndef VIEW_COMPILER_H_
-#define VIEW_COMPILER_H_
+#include "rust/cxx.h"
namespace android {
-namespace installd {
-bool view_compiler(const char* apk_path, const char* package_name, const char* out_dex_file,
- int uid);
+bool shouldLog(rust::Str tag);
-} // namespace installd
} // namespace android
-
-#endif // VIEW_COMPILER_H_
diff --git a/libs/input/input_flags.aconfig b/libs/input/input_flags.aconfig
new file mode 100644
index 0000000000..dbc002ccb3
--- /dev/null
+++ b/libs/input/input_flags.aconfig
@@ -0,0 +1,93 @@
+package: "com.android.input.flags"
+container: "system"
+
+flag {
+ name: "enable_outbound_event_verification"
+ namespace: "input"
+ description: "Set to true to enable crashing whenever bad outbound events are detected inside InputTransport"
+ bug: "271455682"
+}
+
+flag {
+ name: "enable_inbound_event_verification"
+ namespace: "input"
+ description: "Set to true to enable crashing whenever bad inbound events are going into InputDispatcher"
+ bug: "271455682"
+}
+
+flag {
+ name: "enable_pointer_choreographer"
+ namespace: "input"
+ description: "Set to true to enable PointerChoreographer: the new pipeline for showing pointer icons"
+ bug: "293587049"
+}
+
+flag {
+ name: "enable_gestures_library_timer_provider"
+ namespace: "input"
+ description: "Set to true to enable timer support for the touchpad Gestures library"
+ bug: "297192727"
+}
+
+flag {
+ name: "enable_multi_device_input"
+ namespace: "input"
+ description: "Set to true to enable multi-device input: touch and stylus can be active at the same time, but in different windows"
+ bug: "211379801"
+}
+
+flag {
+ name: "a11y_crash_on_inconsistent_event_stream"
+ namespace: "accessibility"
+ description: "Brings back fatal logging for inconsistent event streams originating from accessibility."
+ bug: "299977100"
+}
+
+flag {
+ name: "report_palms_to_gestures_library"
+ namespace: "input"
+ description: "Report touches marked as palm by firmware to gestures library"
+ bug: "302505955"
+}
+
+flag {
+ name: "enable_touchpad_typing_palm_rejection"
+ namespace: "input"
+ description: "Enable additional palm rejection on touchpad while typing"
+ bug: "301055381"
+}
+
+flag {
+ name: "remove_app_switch_drops"
+ namespace: "input"
+ description: "Remove the logic of dropping events due to pending app switch"
+ bug: "284808102"
+}
+
+flag {
+ name: "disable_reject_touch_on_stylus_hover"
+ namespace: "input"
+ description: "Disable touch rejection when the stylus hovers the screen"
+ bug: "301216095"
+}
+
+flag {
+ name: "enable_input_filter_rust_impl"
+ namespace: "input"
+ description: "Enable input filter rust implementation"
+ bug: "294546335"
+}
+
+flag {
+ name: "override_key_behavior_permission_apis"
+ namespace: "input"
+ description: "enable override key behavior permission APIs"
+ bug: "309018874"
+}
+
+flag {
+ name: "remove_pointer_event_tracking_in_wm"
+ namespace: "input"
+ description: "Remove pointer event tracking in WM after the Pointer Icon Refactor"
+ bug: "315321016"
+}
diff --git a/libs/input/rust/Android.bp b/libs/input/rust/Android.bp
new file mode 100644
index 0000000000..018d199ce2
--- /dev/null
+++ b/libs/input/rust/Android.bp
@@ -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.
+
+rust_defaults {
+ name: "libinput_rust_defaults",
+ crate_name: "input",
+ srcs: ["lib.rs"],
+ host_supported: true,
+ rustlibs: [
+ "libbitflags",
+ "libcxx",
+ "libinput_bindgen",
+ "liblogger",
+ "liblog_rust",
+ "inputconstants-rust",
+ ],
+ whole_static_libs: [
+ "libinput_from_rust_to_cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
+
+rust_library {
+ name: "libinput_rust",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_ffi_static {
+ name: "libinput_rust_ffi",
+ defaults: ["libinput_rust_defaults"],
+}
+
+rust_test {
+ name: "libinput_rust_test",
+ defaults: ["libinput_rust_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["device_tests"],
+ sanitize: {
+ hwaddress: true,
+ },
+}
+
+genrule {
+ name: "libinput_cxx_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["input_cxx_bridge_generated.cpp"],
+}
+
+genrule {
+ name: "libinput_cxx_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["input_cxx_bridge.rs.h"],
+}
diff --git a/libs/input/rust/input.rs b/libs/input/rust/input.rs
new file mode 100644
index 0000000000..705c959d04
--- /dev/null
+++ b/libs/input/rust/input.rs
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+//! Common definitions of the Android Input Framework in rust.
+
+use bitflags::bitflags;
+use std::fmt;
+
+/// The InputDevice ID.
+#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
+pub struct DeviceId(pub i32);
+
+#[repr(u32)]
+pub enum SourceClass {
+ None = input_bindgen::AINPUT_SOURCE_CLASS_NONE,
+ Button = input_bindgen::AINPUT_SOURCE_CLASS_BUTTON,
+ Pointer = input_bindgen::AINPUT_SOURCE_CLASS_POINTER,
+ Navigation = input_bindgen::AINPUT_SOURCE_CLASS_NAVIGATION,
+ Position = input_bindgen::AINPUT_SOURCE_CLASS_POSITION,
+ Joystick = input_bindgen::AINPUT_SOURCE_CLASS_JOYSTICK,
+}
+
+bitflags! {
+ /// Source of the input device or input events.
+ #[derive(Debug, PartialEq)]
+ pub struct Source: u32 {
+ // Constants from SourceClass, added here for compatibility reasons
+ /// SourceClass::Button
+ const SourceClassButton = SourceClass::Button as u32;
+ /// SourceClass::Pointer
+ const SourceClassPointer = SourceClass::Pointer as u32;
+ /// SourceClass::Navigation
+ const SourceClassNavigation = SourceClass::Navigation as u32;
+ /// SourceClass::Position
+ const SourceClassPosition = SourceClass::Position as u32;
+ /// SourceClass::Joystick
+ const SourceClassJoystick = SourceClass::Joystick as u32;
+
+ /// SOURCE_UNKNOWN
+ const Unknown = input_bindgen::AINPUT_SOURCE_UNKNOWN;
+ /// SOURCE_KEYBOARD
+ const Keyboard = input_bindgen::AINPUT_SOURCE_KEYBOARD;
+ /// SOURCE_DPAD
+ const Dpad = input_bindgen::AINPUT_SOURCE_DPAD;
+ /// SOURCE_GAMEPAD
+ const Gamepad = input_bindgen::AINPUT_SOURCE_GAMEPAD;
+ /// SOURCE_TOUCHSCREEN
+ const Touchscreen = input_bindgen::AINPUT_SOURCE_TOUCHSCREEN;
+ /// SOURCE_MOUSE
+ const Mouse = input_bindgen::AINPUT_SOURCE_MOUSE;
+ /// SOURCE_STYLUS
+ const Stylus = input_bindgen::AINPUT_SOURCE_STYLUS;
+ /// SOURCE_BLUETOOTH_STYLUS
+ const BluetoothStylus = input_bindgen::AINPUT_SOURCE_BLUETOOTH_STYLUS;
+ /// SOURCE_TRACKBALL
+ const Trackball = input_bindgen::AINPUT_SOURCE_TRACKBALL;
+ /// SOURCE_MOUSE_RELATIVE
+ const MouseRelative = input_bindgen::AINPUT_SOURCE_MOUSE_RELATIVE;
+ /// SOURCE_TOUCHPAD
+ const Touchpad = input_bindgen::AINPUT_SOURCE_TOUCHPAD;
+ /// SOURCE_TOUCH_NAVIGATION
+ const TouchNavigation = input_bindgen::AINPUT_SOURCE_TOUCH_NAVIGATION;
+ /// SOURCE_JOYSTICK
+ const Joystick = input_bindgen::AINPUT_SOURCE_JOYSTICK;
+ /// SOURCE_HDMI
+ const HDMI = input_bindgen::AINPUT_SOURCE_HDMI;
+ /// SOURCE_SENSOR
+ const Sensor = input_bindgen::AINPUT_SOURCE_SENSOR;
+ /// SOURCE_ROTARY_ENCODER
+ const RotaryEncoder = input_bindgen::AINPUT_SOURCE_ROTARY_ENCODER;
+ }
+}
+
+/// A rust enum representation of a MotionEvent action.
+#[repr(u32)]
+pub enum MotionAction {
+ /// ACTION_DOWN
+ Down = input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ /// ACTION_UP
+ Up = input_bindgen::AMOTION_EVENT_ACTION_UP,
+ /// ACTION_MOVE
+ Move = input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ /// ACTION_CANCEL
+ Cancel = input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ /// ACTION_OUTSIDE
+ Outside = input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE,
+ /// ACTION_POINTER_DOWN
+ PointerDown {
+ /// The index of the affected pointer.
+ action_index: usize,
+ } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN,
+ /// ACTION_POINTER_UP
+ PointerUp {
+ /// The index of the affected pointer.
+ action_index: usize,
+ } = input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP,
+ /// ACTION_HOVER_ENTER
+ HoverEnter = input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ /// ACTION_HOVER_MOVE
+ HoverMove = input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ /// ACTION_HOVER_EXIT
+ HoverExit = input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ /// ACTION_SCROLL
+ Scroll = input_bindgen::AMOTION_EVENT_ACTION_SCROLL,
+ /// ACTION_BUTTON_PRESS
+ ButtonPress = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ /// ACTION_BUTTON_RELEASE
+ ButtonRelease = input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+}
+
+impl fmt::Display for MotionAction {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ MotionAction::Down => write!(f, "DOWN"),
+ MotionAction::Up => write!(f, "UP"),
+ MotionAction::Move => write!(f, "MOVE"),
+ MotionAction::Cancel => write!(f, "CANCEL"),
+ MotionAction::Outside => write!(f, "OUTSIDE"),
+ MotionAction::PointerDown { action_index } => {
+ write!(f, "POINTER_DOWN({})", action_index)
+ }
+ MotionAction::PointerUp { action_index } => write!(f, "POINTER_UP({})", action_index),
+ MotionAction::HoverMove => write!(f, "HOVER_MOVE"),
+ MotionAction::Scroll => write!(f, "SCROLL"),
+ MotionAction::HoverEnter => write!(f, "HOVER_ENTER"),
+ MotionAction::HoverExit => write!(f, "HOVER_EXIT"),
+ MotionAction::ButtonPress => write!(f, "BUTTON_PRESS"),
+ MotionAction::ButtonRelease => write!(f, "BUTTON_RELEASE"),
+ }
+ }
+}
+
+impl From<u32> for MotionAction {
+ fn from(action: u32) -> Self {
+ let (action_masked, action_index) = MotionAction::breakdown_action(action);
+ match action_masked {
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN => MotionAction::Down,
+ input_bindgen::AMOTION_EVENT_ACTION_UP => MotionAction::Up,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE => MotionAction::Move,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL => MotionAction::Cancel,
+ input_bindgen::AMOTION_EVENT_ACTION_OUTSIDE => MotionAction::Outside,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN => {
+ MotionAction::PointerDown { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP => {
+ MotionAction::PointerUp { action_index }
+ }
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER => MotionAction::HoverEnter,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE => MotionAction::HoverMove,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT => MotionAction::HoverExit,
+ input_bindgen::AMOTION_EVENT_ACTION_SCROLL => MotionAction::Scroll,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_PRESS => MotionAction::ButtonPress,
+ input_bindgen::AMOTION_EVENT_ACTION_BUTTON_RELEASE => MotionAction::ButtonRelease,
+ _ => panic!("Unknown action: {}", action),
+ }
+ }
+}
+
+impl MotionAction {
+ fn breakdown_action(action: u32) -> (u32, usize) {
+ let action_masked = action & input_bindgen::AMOTION_EVENT_ACTION_MASK;
+ let index = (action & input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
+ >> input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+ (action_masked, index.try_into().unwrap())
+ }
+}
+
+bitflags! {
+ /// MotionEvent flags.
+ #[derive(Debug)]
+ pub struct MotionFlags: u32 {
+ /// FLAG_CANCELED
+ const CANCELED = input_bindgen::AMOTION_EVENT_FLAG_CANCELED as u32;
+ /// FLAG_WINDOW_IS_OBSCURED
+ const WINDOW_IS_OBSCURED = input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ /// FLAG_WINDOW_IS_PARTIALLY_OBSCURED
+ const WINDOW_IS_PARTIALLY_OBSCURED =
+ input_bindgen::AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+ /// FLAG_IS_ACCESSIBILITY_EVENT
+ const IS_ACCESSIBILITY_EVENT =
+ input_bindgen::AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
+ /// FLAG_NO_FOCUS_CHANGE
+ const NO_FOCUS_CHANGE = input_bindgen::AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+ }
+}
+
+impl Source {
+ /// Return true if this source is from the provided class
+ pub fn is_from_class(&self, source_class: SourceClass) -> bool {
+ let class_bits = source_class as u32;
+ self.bits() & class_bits == class_bits
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input::SourceClass;
+ use crate::Source;
+ #[test]
+ fn convert_source_class_pointer() {
+ let source = Source::from_bits(input_bindgen::AINPUT_SOURCE_CLASS_POINTER).unwrap();
+ assert!(source.is_from_class(SourceClass::Pointer));
+ }
+}
diff --git a/libs/input/rust/input_verifier.rs b/libs/input/rust/input_verifier.rs
new file mode 100644
index 0000000000..b1d7760682
--- /dev/null
+++ b/libs/input/rust/input_verifier.rs
@@ -0,0 +1,638 @@
+/*
+ * 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.
+ */
+
+//! Contains the InputVerifier, used to validate a stream of input events.
+
+use crate::ffi::RustPointerProperties;
+use crate::input::{DeviceId, MotionAction, MotionFlags, Source, SourceClass};
+use log::info;
+use std::collections::HashMap;
+use std::collections::HashSet;
+
+fn verify_event(
+ action: MotionAction,
+ pointer_properties: &[RustPointerProperties],
+ flags: &MotionFlags,
+) -> Result<(), String> {
+ let pointer_count = pointer_properties.len();
+ if pointer_count < 1 {
+ return Err(format!("Invalid {} event: no pointers", action));
+ }
+ match action {
+ MotionAction::Down
+ | MotionAction::HoverEnter
+ | MotionAction::HoverExit
+ | MotionAction::HoverMove
+ | MotionAction::Up => {
+ if pointer_count != 1 {
+ return Err(format!(
+ "Invalid {} event: there are {} pointers in the event",
+ action, pointer_count
+ ));
+ }
+ }
+
+ MotionAction::Cancel => {
+ if !flags.contains(MotionFlags::CANCELED) {
+ return Err(format!(
+ "For ACTION_CANCEL, must set FLAG_CANCELED. Received flags: {:#?}",
+ flags
+ ));
+ }
+ }
+
+ MotionAction::PointerDown { action_index } | MotionAction::PointerUp { action_index } => {
+ if action_index >= pointer_count {
+ return Err(format!("Got {}, but event has {} pointer(s)", action, pointer_count));
+ }
+ }
+
+ _ => {}
+ }
+ Ok(())
+}
+
+/// The InputVerifier is used to validate a stream of input events.
+pub struct InputVerifier {
+ name: String,
+ should_log: bool,
+ touching_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+ hovering_pointer_ids_by_device: HashMap<DeviceId, HashSet<i32>>,
+}
+
+impl InputVerifier {
+ /// Create a new InputVerifier.
+ pub fn new(name: &str, should_log: bool) -> Self {
+ logger::init(
+ logger::Config::default()
+ .with_tag_on_device("InputVerifier")
+ .with_max_level(log::LevelFilter::Trace),
+ );
+ Self {
+ name: name.to_owned(),
+ should_log,
+ touching_pointer_ids_by_device: HashMap::new(),
+ hovering_pointer_ids_by_device: HashMap::new(),
+ }
+ }
+
+ /// Process a pointer movement event from an InputDevice.
+ /// If the event is not valid, we return an error string that describes the issue.
+ pub fn process_movement(
+ &mut self,
+ device_id: DeviceId,
+ source: Source,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: MotionFlags,
+ ) -> Result<(), String> {
+ if !source.is_from_class(SourceClass::Pointer) {
+ // Skip non-pointer sources like MOUSE_RELATIVE for now
+ return Ok(());
+ }
+ if self.should_log {
+ info!(
+ "Processing {} for device {:?} ({} pointer{}) on {}",
+ MotionAction::from(action).to_string(),
+ device_id,
+ pointer_properties.len(),
+ if pointer_properties.len() == 1 { "" } else { "s" },
+ self.name
+ );
+ }
+
+ verify_event(action.into(), pointer_properties, &flags)?;
+
+ match action.into() {
+ MotionAction::Down => {
+ if self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Invalid DOWN event - pointers already down for device {:?}: {:?}",
+ self.name, device_id, self.touching_pointer_ids_by_device
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.entry(device_id).or_default();
+ it.insert(pointer_properties[0].id);
+ }
+ MotionAction::PointerDown { action_index } => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Received POINTER_DOWN but no pointers are currently down \
+ for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != pointer_properties.len() - 1 {
+ return Err(format!(
+ "{}: There are currently {} touching pointers, but the incoming \
+ POINTER_DOWN event has {}",
+ self.name,
+ it.len(),
+ pointer_properties.len()
+ ));
+ }
+ let pointer_id = pointer_properties[action_index].id;
+ if it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Pointer with id={} already present found in the properties",
+ self.name, pointer_id
+ ));
+ }
+ it.insert(pointer_id);
+ }
+ MotionAction::Move => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: ACTION_MOVE touching pointers don't match",
+ self.name
+ ));
+ }
+ }
+ MotionAction::PointerUp { action_index } => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: ACTION_POINTER_UP touching pointers don't match",
+ self.name
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ let pointer_id = pointer_properties[action_index].id;
+ it.remove(&pointer_id);
+ }
+ MotionAction::Up => {
+ if !self.touching_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{} Received ACTION_UP but no pointers are currently down for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let it = self.touching_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ if it.len() != 1 {
+ return Err(format!(
+ "{}: Got ACTION_UP, but we have pointers: {:?} for device {:?}",
+ self.name, it, device_id
+ ));
+ }
+ let pointer_id = pointer_properties[0].id;
+ if !it.contains(&pointer_id) {
+ return Err(format!(
+ "{}: Got ACTION_UP, but pointerId {} is not touching. Touching pointers:\
+ {:?} for device {:?}",
+ self.name, pointer_id, it, device_id
+ ));
+ }
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ }
+ MotionAction::Cancel => {
+ if !self.ensure_touching_pointers_match(device_id, pointer_properties) {
+ return Err(format!(
+ "{}: Got ACTION_CANCEL, but the pointers don't match. \
+ Existing pointers: {:?}",
+ self.name, self.touching_pointer_ids_by_device
+ ));
+ }
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ }
+ /*
+ * The hovering protocol currently supports a single pointer only, because we do not
+ * have ACTION_HOVER_POINTER_ENTER or ACTION_HOVER_POINTER_EXIT.
+ * Still, we are keeping the infrastructure here pretty general in case that is
+ * eventually supported.
+ */
+ MotionAction::HoverEnter => {
+ if self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Invalid HOVER_ENTER event - pointers already hovering for device {:?}:\
+ {:?}",
+ self.name, device_id, self.hovering_pointer_ids_by_device
+ ));
+ }
+ let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
+ it.insert(pointer_properties[0].id);
+ }
+ MotionAction::HoverMove => {
+ // For compatibility reasons, we allow HOVER_MOVE without a prior HOVER_ENTER.
+ // If there was no prior HOVER_ENTER, just start a new hovering pointer.
+ let it = self.hovering_pointer_ids_by_device.entry(device_id).or_default();
+ it.insert(pointer_properties[0].id);
+ }
+ MotionAction::HoverExit => {
+ if !self.hovering_pointer_ids_by_device.contains_key(&device_id) {
+ return Err(format!(
+ "{}: Invalid HOVER_EXIT event - no pointers are hovering for device {:?}",
+ self.name, device_id
+ ));
+ }
+ let pointer_id = pointer_properties[0].id;
+ let it = self.hovering_pointer_ids_by_device.get_mut(&device_id).unwrap();
+ it.remove(&pointer_id);
+
+ if !it.is_empty() {
+ return Err(format!(
+ "{}: Removed hovering pointer {}, but pointers are still\
+ hovering for device {:?}: {:?}",
+ self.name, pointer_id, device_id, it
+ ));
+ }
+ self.hovering_pointer_ids_by_device.remove(&device_id);
+ }
+ _ => return Ok(()),
+ }
+ Ok(())
+ }
+
+ /// Notify the verifier that the device has been reset, which will cause the verifier to erase
+ /// the current internal state for this device. Subsequent events from this device are expected
+ //// to start a new gesture.
+ pub fn reset_device(&mut self, device_id: DeviceId) {
+ self.touching_pointer_ids_by_device.remove(&device_id);
+ self.hovering_pointer_ids_by_device.remove(&device_id);
+ }
+
+ fn ensure_touching_pointers_match(
+ &self,
+ device_id: DeviceId,
+ pointer_properties: &[RustPointerProperties],
+ ) -> bool {
+ let Some(pointers) = self.touching_pointer_ids_by_device.get(&device_id) else {
+ return false;
+ };
+
+ if pointers.len() != pointer_properties.len() {
+ return false;
+ }
+
+ for pointer_property in pointer_properties.iter() {
+ let pointer_id = pointer_property.id;
+ if !pointers.contains(&pointer_id) {
+ return false;
+ }
+ }
+ true
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input_verifier::InputVerifier;
+ use crate::DeviceId;
+ use crate::MotionFlags;
+ use crate::RustPointerProperties;
+ use crate::Source;
+
+ #[test]
+ /**
+ * Send a DOWN event with 2 pointers and ensure that it's marked as invalid.
+ */
+ fn bad_down_event() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ true);
+ let pointer_properties =
+ Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn single_pointer_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn two_pointer_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // POINTER 1 DOWN
+ let two_pointer_properties =
+ Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
+ | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ &two_pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // POINTER 0 UP
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_UP
+ | (0 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ &two_pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // ACTION_UP for pointer id=1
+ let pointer_1_properties = Vec::from([RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_1_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn multi_device_stream() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn action_cancel() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ &pointer_properties,
+ MotionFlags::CANCELED,
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn invalid_action_cancel() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_CANCEL,
+ &pointer_properties,
+ MotionFlags::empty(), // forgot to set FLAG_CANCELED
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn invalid_up() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_UP,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+
+ #[test]
+ fn correct_hover_sequence() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_EXIT,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ #[test]
+ fn double_hover_enter() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_HOVER_ENTER,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+
+ // Send a MOVE without a preceding DOWN event. This is OK because it's from source
+ // MOUSE_RELATIVE, which is used during pointer capture. The verifier should allow such event.
+ #[test]
+ fn relative_mouse_move() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(2),
+ Source::MouseRelative,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ }
+
+ // Send a MOVE event with incorrect number of pointers (one of the pointers is missing).
+ #[test]
+ fn move_with_wrong_number_of_pointers() {
+ let mut verifier = InputVerifier::new("Test", /*should_log*/ false);
+ let pointer_properties = Vec::from([RustPointerProperties { id: 0 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_DOWN,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // POINTER 1 DOWN
+ let two_pointer_properties =
+ Vec::from([RustPointerProperties { id: 0 }, RustPointerProperties { id: 1 }]);
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_POINTER_DOWN
+ | (1 << input_bindgen::AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ &two_pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_ok());
+ // MOVE event with 1 pointer missing (the pointer with id = 1). It should be rejected
+ assert!(verifier
+ .process_movement(
+ DeviceId(1),
+ Source::Touchscreen,
+ input_bindgen::AMOTION_EVENT_ACTION_MOVE,
+ &pointer_properties,
+ MotionFlags::empty(),
+ )
+ .is_err());
+ }
+}
diff --git a/libs/input/rust/lib.rs b/libs/input/rust/lib.rs
new file mode 100644
index 0000000000..01d959942c
--- /dev/null
+++ b/libs/input/rust/lib.rs
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+//! The rust component of libinput.
+
+mod input;
+mod input_verifier;
+
+pub use input::{DeviceId, MotionAction, MotionFlags, Source};
+pub use input_verifier::InputVerifier;
+
+#[cxx::bridge(namespace = "android::input")]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod ffi {
+ #[namespace = "android"]
+ unsafe extern "C++" {
+ include!("ffi/FromRustToCpp.h");
+ fn shouldLog(tag: &str) -> bool;
+ }
+
+ #[namespace = "android::input::verifier"]
+ extern "Rust" {
+ /// Used to validate the incoming motion stream.
+ /// This class is not thread-safe.
+ /// State is stored in the "InputVerifier" object
+ /// that can be created via the 'create' method.
+ /// Usage:
+ ///
+ /// ```ignore
+ /// Box<InputVerifier> verifier = create("inputChannel name");
+ /// result = process_movement(verifier, ...);
+ /// if (result) {
+ /// crash(result.error_message());
+ /// }
+ /// ```
+ type InputVerifier;
+ fn create(name: String) -> Box<InputVerifier>;
+ fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ source: u32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: u32,
+ ) -> String;
+ fn reset_device(verifier: &mut InputVerifier, device_id: i32);
+ }
+
+ #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
+ pub struct RustPointerProperties {
+ pub id: i32,
+ }
+}
+
+use crate::ffi::RustPointerProperties;
+
+fn create(name: String) -> Box<InputVerifier> {
+ Box::new(InputVerifier::new(&name, ffi::shouldLog("InputVerifierLogEvents")))
+}
+
+fn process_movement(
+ verifier: &mut InputVerifier,
+ device_id: i32,
+ source: u32,
+ action: u32,
+ pointer_properties: &[RustPointerProperties],
+ flags: u32,
+) -> String {
+ let result = verifier.process_movement(
+ DeviceId(device_id),
+ Source::from_bits(source).unwrap(),
+ action,
+ pointer_properties,
+ MotionFlags::from_bits(flags).unwrap(),
+ );
+ match result {
+ Ok(()) => "".to_string(),
+ Err(e) => e,
+ }
+}
+
+fn reset_device(verifier: &mut InputVerifier, device_id: i32) {
+ verifier.reset_device(DeviceId(device_id));
+}
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index d947cd99e8..9137a3468f 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -10,36 +10,85 @@ package {
cc_test {
name: "libinput_tests",
+ cpp_std: "c++20",
+ host_supported: true,
srcs: [
+ "BlockingQueue_test.cpp",
"IdGenerator_test.cpp",
"InputChannel_test.cpp",
"InputDevice_test.cpp",
"InputEvent_test.cpp",
"InputPublisherAndConsumer_test.cpp",
+ "InputVerifier_test.cpp",
+ "MotionPredictor_test.cpp",
+ "MotionPredictorMetricsManager_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",
+ "libkernelconfigs",
+ "libtflite_static",
+ "libui-types",
+ "libz", // needed by libkernelconfigs
],
cflags: [
"-Wall",
"-Wextra",
"-Werror",
+ "-Wno-unused-parameter",
],
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
shared_libs: [
"libbase",
"libbinder",
"libcutils",
"liblog",
- "libui",
+ "libPlatformProperties",
+ "libtinyxml2",
"libutils",
- "libvintf",
+ "server_configurable_flags",
+ ],
+ data: [
+ "data/*",
+ ":motion_predictor_model",
],
- data: ["data/*"],
+ test_options: {
+ unit_test: true,
+ },
test_suites: ["device-tests"],
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ android: {
+ static_libs: [
+ // Stats logging library and its dependencies.
+ "libstatslog_libinput",
+ "libstatsbootstrap",
+ "android.os.statsbootstrap_aidl-cpp",
+ ],
+ },
+ },
}
// NOTE: This is a compile time test, and does not need to be
@@ -59,7 +108,6 @@ cc_library_static {
"libcutils",
"libutils",
"libbinder",
- "libui",
"libbase",
],
}
diff --git a/services/inputflinger/tests/BlockingQueue_test.cpp b/libs/input/tests/BlockingQueue_test.cpp
index fd9d9d5bd3..924b937080 100644
--- a/services/inputflinger/tests/BlockingQueue_test.cpp
+++ b/libs/input/tests/BlockingQueue_test.cpp
@@ -14,14 +14,14 @@
* limitations under the License.
*/
-#include "../BlockingQueue.h"
-
+#include <input/BlockingQueue.h>
#include <gtest/gtest.h>
#include <thread>
namespace android {
+using std::chrono_literals::operator""ns;
// --- BlockingQueueTest ---
@@ -34,6 +34,14 @@ TEST(BlockingQueueTest, Queue_AddAndRemove) {
ASSERT_TRUE(queue.push(1));
ASSERT_EQ(queue.pop(), 1);
+
+ ASSERT_TRUE(queue.emplace(2));
+ ASSERT_EQ(queue.popWithTimeout(0ns), 2);
+
+ ASSERT_TRUE(queue.push(3));
+ ASSERT_EQ(queue.popWithTimeout(100ns), 3);
+
+ ASSERT_EQ(std::nullopt, queue.popWithTimeout(0ns));
}
/**
@@ -87,7 +95,7 @@ TEST(BlockingQueueTest, Queue_Erases) {
queue.push(3);
queue.push(4);
// Erase elements 2 and 4
- queue.erase([](int element) { return element == 2 || element == 4; });
+ queue.erase_if([](int element) { return element == 2 || element == 4; });
// Should no longer receive elements 2 and 4
ASSERT_EQ(1, queue.pop());
ASSERT_EQ(3, queue.pop());
@@ -100,7 +108,7 @@ TEST(BlockingQueueTest, Queue_AllowsMultipleThreads) {
BlockingQueue<int> queue(capacity);
// Fill queue from a different thread
- std::thread fillQueue([&queue](){
+ std::thread fillQueue([&queue]() {
for (size_t i = 0; i < capacity; i++) {
ASSERT_TRUE(queue.push(static_cast<int>(i)));
}
@@ -127,7 +135,7 @@ TEST(BlockingQueueTest, Queue_BlocksWhileWaitingForElements) {
std::atomic_bool hasReceivedElement = false;
// fill queue from a different thread
- std::thread waitUntilHasElements([&queue, &hasReceivedElement](){
+ std::thread waitUntilHasElements([&queue, &hasReceivedElement]() {
queue.pop(); // This should block until an element has been added
hasReceivedElement = true;
});
@@ -138,5 +146,9 @@ TEST(BlockingQueueTest, Queue_BlocksWhileWaitingForElements) {
ASSERT_TRUE(hasReceivedElement);
}
+TEST(BlockingQueueTest, Queue_TimesOut) {
+ BlockingQueue<int> queue;
+ ASSERT_EQ(std::nullopt, queue.popWithTimeout(1ns));
+}
} // namespace android
diff --git a/libs/input/tests/InputDevice_test.cpp b/libs/input/tests/InputDevice_test.cpp
index e872fa442b..fe5490caf5 100644
--- a/libs/input/tests/InputDevice_test.cpp
+++ b/libs/input/tests/InputDevice_test.cpp
@@ -20,6 +20,7 @@
#include <input/InputDevice.h>
#include <input/KeyLayoutMap.h>
#include <input/Keyboard.h>
+#include <linux/uinput.h>
#include "android-base/file.h"
namespace android {
@@ -65,6 +66,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");
}
@@ -94,7 +98,7 @@ TEST_F(InputDeviceKeyMapTest, keyCharacterMapWithOverlayParcelingTest) {
ASSERT_EQ(*map, *mKeyMap.keyCharacterMap);
}
-TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyMultipleOverlaysTest) {
std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
std::string englishOverlayPath = base::GetExecutableDirectory() + "/data/english_us.kcm";
std::string germanOverlayPath = base::GetExecutableDirectory() + "/data/german.kcm";
@@ -130,7 +134,78 @@ TEST_F(InputDeviceKeyMapTest, keyCharacteMapApplyMultipleOverlaysTest) {
ASSERT_EQ(*mKeyMap.keyCharacterMap, *frenchOverlaidKeyCharacterMap);
}
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapApplyOverlayTest) {
+ std::string frenchOverlayPath = base::GetExecutableDirectory() + "/data/french.kcm";
+ base::Result<std::shared_ptr<KeyCharacterMap>> frenchOverlay =
+ KeyCharacterMap::load(frenchOverlayPath, KeyCharacterMap::Format::OVERLAY);
+ ASSERT_TRUE(frenchOverlay.ok()) << "Cannot load KeyCharacterMap at " << frenchOverlayPath;
+
+ // Apply the French overlay
+ mKeyMap.keyCharacterMap->combine(*frenchOverlay->get());
+
+ // Check if mapping for key_Q is correct
+ int32_t outKeyCode;
+ status_t mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_Q, /*usageCode=*/0, &outKeyCode);
+ ASSERT_EQ(mapKeyResult, OK) << "No mapping for KEY_Q for " << frenchOverlayPath;
+ ASSERT_EQ(outKeyCode, AKEYCODE_A);
+
+ mapKeyResult = mKeyMap.keyCharacterMap->mapKey(KEY_E, /*usageCode=*/0, &outKeyCode);
+ ASSERT_NE(mapKeyResult, OK) << "Mapping exists for KEY_E for " << frenchOverlayPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadAxisLabel) {
+ std::string klPath = base::GetExecutableDirectory() + "/data/bad_axis_label.kl";
+
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+ ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
+}
+
+TEST_F(InputDeviceKeyMapTest, keyCharacterMapBadLedLabel) {
+ std::string klPath = base::GetExecutableDirectory() + "/data/bad_led_label.kl";
+
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+ ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
+}
+
+TEST(InputDeviceKeyLayoutTest, HidUsageCodesFallbackMapping) {
+ std::string klPath = base::GetExecutableDirectory() + "/data/hid_fallback_mapping.kl";
+ base::Result<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
+ ASSERT_TRUE(ret.ok()) << "Unable to load KeyLayout at " << klPath;
+ const std::shared_ptr<KeyLayoutMap>& keyLayoutMap = *ret;
+
+ static constexpr std::array<int32_t, 5> hidUsageCodesWithoutFallback = {0x0c0067, 0x0c0070,
+ 0x0c006F, 0x0c0079,
+ 0x0c007A};
+ for (int32_t hidUsageCode : hidUsageCodesWithoutFallback) {
+ int32_t outKeyCode;
+ uint32_t outFlags;
+ keyLayoutMap->mapKey(0, hidUsageCode, &outKeyCode, &outFlags);
+ ASSERT_FALSE(outFlags & POLICY_FLAG_FALLBACK_USAGE_MAPPING)
+ << "HID usage code should not be marked as fallback";
+ std::vector<int32_t> usageCodes = keyLayoutMap->findUsageCodesForKey(outKeyCode);
+ ASSERT_NE(std::find(usageCodes.begin(), usageCodes.end(), hidUsageCode), usageCodes.end())
+ << "Fallback usage code should be mapped to key";
+ }
+
+ static constexpr std::array<int32_t, 6> hidUsageCodesWithFallback = {0x0c007C, 0x0c0173,
+ 0x0c019C, 0x0c01A2,
+ 0x0d0044, 0x0d005a};
+ for (int32_t hidUsageCode : hidUsageCodesWithFallback) {
+ int32_t outKeyCode;
+ uint32_t outFlags;
+ keyLayoutMap->mapKey(0, hidUsageCode, &outKeyCode, &outFlags);
+ ASSERT_TRUE(outFlags & POLICY_FLAG_FALLBACK_USAGE_MAPPING)
+ << "HID usage code should be marked as fallback";
+ std::vector<int32_t> usageCodes = keyLayoutMap->findUsageCodesForKey(outKeyCode);
+ ASSERT_EQ(std::find(usageCodes.begin(), usageCodes.end(), hidUsageCode), usageCodes.end())
+ << "Fallback usage code should not be mapped to key";
+ }
+}
+
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<std::shared_ptr<KeyLayoutMap>> ret = KeyLayoutMap::load(klPath);
ASSERT_FALSE(ret.ok()) << "Should not be able to load KeyLayout at " << klPath;
@@ -139,6 +214,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<std::shared_ptr<KeyLayoutMap>> 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 4b3124636b..a9655730fc 100644
--- a/libs/input/tests/InputEvent_test.cpp
+++ b/libs/input/tests/InputEvent_test.cpp
@@ -29,6 +29,8 @@ namespace android {
// Default display id.
static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+
class BaseTest : public testing::Test {
protected:
static constexpr std::array<uint8_t, 32> HMAC = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
@@ -46,6 +48,7 @@ TEST_F(PointerCoordsTest, ClearSetsBitsToZero) {
coords.clear();
ASSERT_EQ(0ULL, coords.bits);
+ ASSERT_FALSE(coords.isResampled);
}
TEST_F(PointerCoordsTest, AxisValues) {
@@ -158,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);
@@ -171,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);
}
@@ -191,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());
@@ -231,101 +237,116 @@ protected:
static constexpr float RAW_X_OFFSET = 12;
static constexpr float RAW_Y_OFFSET = -41.1;
+ void SetUp() override;
+
int32_t mId;
ui::Transform mTransform;
ui::Transform mRawTransform;
+ PointerProperties mPointerProperties[2];
+ struct Sample {
+ PointerCoords pointerCoords[2];
+ };
+ std::array<Sample, 3> mSamples{};
void initializeEventWithHistory(MotionEvent* event);
void assertEqualsEventWithHistory(const MotionEvent* event);
};
-void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
+void MotionEventTest::SetUp() {
mId = InputEvent::nextId();
mTransform.set({X_SCALE, 0, X_OFFSET, 0, Y_SCALE, Y_OFFSET, 0, 0, 1});
mRawTransform.set({RAW_X_SCALE, 0, RAW_X_OFFSET, 0, RAW_Y_SCALE, RAW_Y_OFFSET, 0, 0, 1});
- PointerProperties pointerProperties[2];
- pointerProperties[0].clear();
- pointerProperties[0].id = 1;
- pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
- pointerProperties[1].clear();
- pointerProperties[1].id = 2;
- pointerProperties[1].toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS;
-
- PointerCoords pointerCoords[2];
- pointerCoords[0].clear();
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 16);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 17);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 18);
- pointerCoords[1].clear();
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 20);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 21);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28);
+ mPointerProperties[0].clear();
+ mPointerProperties[0].id = 1;
+ mPointerProperties[0].toolType = ToolType::FINGER;
+ mPointerProperties[1].clear();
+ mPointerProperties[1].id = 2;
+ mPointerProperties[1].toolType = ToolType::STYLUS;
+
+ mSamples[0].pointerCoords[0].clear();
+ mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 10);
+ mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 11);
+ mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 12);
+ mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 13);
+ mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 14);
+ mSamples[0].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 15);
+ 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);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 22);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 23);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 24);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 25);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 26);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 27);
+ mSamples[0].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 28);
+
+ mSamples[1].pointerCoords[0].clear();
+ mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
+ mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
+ mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
+ mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113);
+ mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114);
+ mSamples[1].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115);
+ 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);
+ mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
+ mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123);
+ mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124);
+ mSamples[1].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125);
+ 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);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
+ mSamples[2].pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
+ mSamples[2].pointerCoords[1].clear();
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227);
+ mSamples[2].pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228);
+}
+
+void MotionEventTest::initializeEventWithHistory(MotionEvent* event) {
event->initialize(mId, 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC,
AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
MotionClassification::NONE, mTransform, 2.0f, 2.1f,
AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
mRawTransform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 2,
- pointerProperties, pointerCoords);
-
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 110);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 111);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 112);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 113);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 114);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 115);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 116);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 117);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 118);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 120);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 121);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 122);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 123);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 124);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 125);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 126);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 127);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 128);
- event->addSample(ARBITRARY_EVENT_TIME + 1, pointerCoords);
-
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 210);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 211);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 212);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 213);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 214);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 215);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 216);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 217);
- pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 218);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_X, 220);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_Y, 221);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 222);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 223);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 224);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, 225);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, 226);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, 227);
- pointerCoords[1].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 228);
- event->addSample(ARBITRARY_EVENT_TIME + 2, pointerCoords);
+ mPointerProperties, mSamples[0].pointerCoords);
+ event->addSample(ARBITRARY_EVENT_TIME + 1, mSamples[1].pointerCoords);
+ event->addSample(ARBITRARY_EVENT_TIME + 2, mSamples[2].pointerCoords);
}
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());
@@ -345,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());
@@ -356,51 +377,65 @@ void MotionEventTest::assertEqualsEventWithHistory(const MotionEvent* event) {
ASSERT_EQ(ARBITRARY_EVENT_TIME + 1, event->getHistoricalEventTime(1));
ASSERT_EQ(ARBITRARY_EVENT_TIME + 2, event->getEventTime());
- ASSERT_EQ(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
- ASSERT_EQ(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
- ASSERT_EQ(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
- ASSERT_EQ(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
- ASSERT_EQ(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y));
- ASSERT_EQ(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y));
-
- ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE,
- event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0));
- ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE,
- event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0));
- ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE,
- event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1));
- ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE,
- event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1));
- ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0));
- ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1));
-
- ASSERT_EQ(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0));
- ASSERT_EQ(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0));
- ASSERT_EQ(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1));
- ASSERT_EQ(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1));
- ASSERT_EQ(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0));
- ASSERT_EQ(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1));
-
- ASSERT_EQ(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0));
- ASSERT_EQ(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0));
- ASSERT_EQ(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1));
- ASSERT_EQ(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1));
- ASSERT_EQ(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0));
- ASSERT_EQ(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1));
-
- ASSERT_EQ(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0));
- ASSERT_EQ(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0));
- ASSERT_EQ(X_OFFSET + 110 * X_SCALE, event->getHistoricalX(0, 1));
- ASSERT_EQ(X_OFFSET + 120 * X_SCALE, event->getHistoricalX(1, 1));
- ASSERT_EQ(X_OFFSET + 210 * X_SCALE, event->getX(0));
- ASSERT_EQ(X_OFFSET + 220 * X_SCALE, event->getX(1));
-
- ASSERT_EQ(Y_OFFSET + 11 * Y_SCALE, event->getHistoricalY(0, 0));
- ASSERT_EQ(Y_OFFSET + 21 * Y_SCALE, event->getHistoricalY(1, 0));
- ASSERT_EQ(Y_OFFSET + 111 * Y_SCALE, event->getHistoricalY(0, 1));
- ASSERT_EQ(Y_OFFSET + 121 * Y_SCALE, event->getHistoricalY(1, 1));
- ASSERT_EQ(Y_OFFSET + 211 * Y_SCALE, event->getY(0));
- ASSERT_EQ(Y_OFFSET + 221 * Y_SCALE, event->getY(1));
+ // Ensure the underlying PointerCoords are identical.
+ for (int sampleIdx = 0; sampleIdx < 3; sampleIdx++) {
+ for (int pointerIdx = 0; pointerIdx < 2; pointerIdx++) {
+ ASSERT_EQ(mSamples[sampleIdx].pointerCoords[pointerIdx],
+ event->getSamplePointerCoords()[sampleIdx * 2 + pointerIdx]);
+ }
+ }
+
+ ASSERT_NEAR(11, event->getHistoricalRawPointerCoords(0, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+ EPSILON);
+ ASSERT_NEAR(21, event->getHistoricalRawPointerCoords(1, 0)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+ EPSILON);
+ ASSERT_NEAR(111, event->getHistoricalRawPointerCoords(0, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+ EPSILON);
+ ASSERT_NEAR(121, event->getHistoricalRawPointerCoords(1, 1)->getAxisValue(AMOTION_EVENT_AXIS_Y),
+ EPSILON);
+ ASSERT_NEAR(211, event->getRawPointerCoords(0)->getAxisValue(AMOTION_EVENT_AXIS_Y), EPSILON);
+ ASSERT_NEAR(221, event->getRawPointerCoords(1)->getAxisValue(AMOTION_EVENT_AXIS_Y), EPSILON);
+
+ ASSERT_NEAR(RAW_Y_OFFSET + 11 * RAW_Y_SCALE,
+ event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 0), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 21 * RAW_Y_SCALE,
+ event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 0), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 111 * RAW_Y_SCALE,
+ event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 0, 1), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 121 * RAW_Y_SCALE,
+ event->getHistoricalRawAxisValue(AMOTION_EVENT_AXIS_Y, 1, 1), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 0),
+ EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawAxisValue(AMOTION_EVENT_AXIS_Y, 1),
+ EPSILON);
+
+ ASSERT_NEAR(RAW_X_OFFSET + 10 * RAW_X_SCALE, event->getHistoricalRawX(0, 0), EPSILON);
+ ASSERT_NEAR(RAW_X_OFFSET + 20 * RAW_X_SCALE, event->getHistoricalRawX(1, 0), EPSILON);
+ ASSERT_NEAR(RAW_X_OFFSET + 110 * RAW_X_SCALE, event->getHistoricalRawX(0, 1), EPSILON);
+ ASSERT_NEAR(RAW_X_OFFSET + 120 * RAW_X_SCALE, event->getHistoricalRawX(1, 1), EPSILON);
+ ASSERT_NEAR(RAW_X_OFFSET + 210 * RAW_X_SCALE, event->getRawX(0), EPSILON);
+ ASSERT_NEAR(RAW_X_OFFSET + 220 * RAW_X_SCALE, event->getRawX(1), EPSILON);
+
+ ASSERT_NEAR(RAW_Y_OFFSET + 11 * RAW_Y_SCALE, event->getHistoricalRawY(0, 0), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 21 * RAW_Y_SCALE, event->getHistoricalRawY(1, 0), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 111 * RAW_Y_SCALE, event->getHistoricalRawY(0, 1), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 121 * RAW_Y_SCALE, event->getHistoricalRawY(1, 1), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 211 * RAW_Y_SCALE, event->getRawY(0), EPSILON);
+ ASSERT_NEAR(RAW_Y_OFFSET + 221 * RAW_Y_SCALE, event->getRawY(1), EPSILON);
+
+ ASSERT_NEAR(X_OFFSET + 10 * X_SCALE, event->getHistoricalX(0, 0), EPSILON);
+ ASSERT_NEAR(X_OFFSET + 20 * X_SCALE, event->getHistoricalX(1, 0), EPSILON);
+ ASSERT_NEAR(X_OFFSET + 110 * X_SCALE, event->getHistoricalX(0, 1), EPSILON);
+ ASSERT_NEAR(X_OFFSET + 120 * X_SCALE, event->getHistoricalX(1, 1), EPSILON);
+ ASSERT_NEAR(X_OFFSET + 210 * X_SCALE, event->getX(0), EPSILON);
+ ASSERT_NEAR(X_OFFSET + 220 * X_SCALE, event->getX(1), EPSILON);
+
+ ASSERT_NEAR(Y_OFFSET + 11 * Y_SCALE, event->getHistoricalY(0, 0), EPSILON);
+ ASSERT_NEAR(Y_OFFSET + 21 * Y_SCALE, event->getHistoricalY(1, 0), EPSILON);
+ ASSERT_NEAR(Y_OFFSET + 111 * Y_SCALE, event->getHistoricalY(0, 1), EPSILON);
+ ASSERT_NEAR(Y_OFFSET + 121 * Y_SCALE, event->getHistoricalY(1, 1), EPSILON);
+ ASSERT_NEAR(Y_OFFSET + 211 * Y_SCALE, event->getY(0), EPSILON);
+ ASSERT_NEAR(Y_OFFSET + 221 * Y_SCALE, event->getY(1), EPSILON);
ASSERT_EQ(12, event->getHistoricalPressure(0, 0));
ASSERT_EQ(22, event->getHistoricalPressure(1, 0));
@@ -457,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) {
@@ -489,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));
}
@@ -499,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());
@@ -532,10 +574,10 @@ TEST_F(MotionEventTest, Scale) {
ASSERT_EQ(X_OFFSET * 2, event.getXOffset());
ASSERT_EQ(Y_OFFSET * 2, event.getYOffset());
- ASSERT_EQ((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0));
- ASSERT_EQ((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0));
- ASSERT_EQ((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0));
- ASSERT_EQ((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0));
+ ASSERT_NEAR((RAW_X_OFFSET + 210 * RAW_X_SCALE) * 2, event.getRawX(0), EPSILON);
+ ASSERT_NEAR((RAW_Y_OFFSET + 211 * RAW_Y_SCALE) * 2, event.getRawY(0), EPSILON);
+ ASSERT_NEAR((X_OFFSET + 210 * X_SCALE) * 2, event.getX(0), EPSILON);
+ ASSERT_NEAR((Y_OFFSET + 211 * Y_SCALE) * 2, event.getY(0), EPSILON);
ASSERT_EQ(212, event.getPressure(0));
ASSERT_EQ(213, event.getSize(0));
ASSERT_EQ(214 * 2, event.getTouchMajor(0));
@@ -600,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;
@@ -650,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;
- pointerProperties.push_back(PointerProperties{/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER});
+ pointerProperties.push_back(PointerProperties{/*id=*/0, ToolType::FINGER});
std::vector<PointerCoords> pointerCoords;
pointerCoords.emplace_back().clear();
pointerCoords.back().setAxisValue(AMOTION_EVENT_AXIS_X, x);
@@ -773,18 +815,18 @@ TEST_F(MotionEventTest, AxesAreCorrectlyTransformed) {
// The x and y axes should have the window transform applied.
const auto newPoint = transform.transform(60, 100);
- ASSERT_EQ(newPoint.x, event.getX(0));
- ASSERT_EQ(newPoint.y, event.getY(0));
+ ASSERT_NEAR(newPoint.x, event.getX(0), EPSILON);
+ ASSERT_NEAR(newPoint.y, event.getY(0), EPSILON);
// The raw values should have the display transform applied.
const auto raw = rawTransform.transform(60, 100);
- ASSERT_EQ(raw.x, event.getRawX(0));
- ASSERT_EQ(raw.y, event.getRawY(0));
+ ASSERT_NEAR(raw.x, event.getRawX(0), EPSILON);
+ ASSERT_NEAR(raw.y, event.getRawY(0), EPSILON);
// Relative values should have the window transform applied without any translation.
const auto rel = transformWithoutTranslation(transform, 42, 96);
- ASSERT_EQ(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0));
- ASSERT_EQ(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0));
+ ASSERT_NEAR(rel.x, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0), EPSILON);
+ ASSERT_NEAR(rel.y, event.getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0), EPSILON);
}
TEST_F(MotionEventTest, Initialize_SetsClassification) {
@@ -806,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());
}
}
@@ -828,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());
@@ -851,4 +893,42 @@ TEST_F(MotionEventTest, SetCursorPosition) {
ASSERT_EQ(4, event.getYCursorPosition());
}
+TEST_F(MotionEventTest, CoordinatesAreRoundedAppropriately) {
+ // These are specifically integral values, since we are testing for rounding.
+ const vec2 EXPECTED{400.f, 700.f};
+
+ // Pick a transform such that transforming the point with its inverse and bringing that
+ // back to the original coordinate space results in a non-zero error amount due to the
+ // nature of floating point arithmetics. This can happen when the display is scaled.
+ // For example, the 'adb shell wm size' command can be used to set an override for the
+ // logical display size, which could result in the display being scaled.
+ constexpr float scale = 720.f / 1080.f;
+ ui::Transform transform;
+ transform.set(scale, 0, 0, scale);
+ ASSERT_NE(EXPECTED, transform.transform(transform.inverse().transform(EXPECTED)));
+
+ // Store the inverse-transformed values in the motion event.
+ const vec2 rawCoords = transform.inverse().transform(EXPECTED);
+ PointerCoords pc{};
+ pc.setAxisValue(AMOTION_EVENT_AXIS_X, rawCoords.x);
+ pc.setAxisValue(AMOTION_EVENT_AXIS_Y, rawCoords.y);
+ PointerProperties pp{};
+ MotionEvent event;
+ event.initialize(InputEvent::nextId(), 2, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID, HMAC,
+ AMOTION_EVENT_ACTION_MOVE, 0, AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
+ AMOTION_EVENT_EDGE_FLAG_TOP, AMETA_ALT_ON, AMOTION_EVENT_BUTTON_PRIMARY,
+ MotionClassification::NONE, transform, 2.0f, 2.1f, rawCoords.x, rawCoords.y,
+ transform, ARBITRARY_DOWN_TIME, ARBITRARY_EVENT_TIME, 1, &pp, &pc);
+
+ // When using the getters from the MotionEvent to obtain the coordinates, the transformed
+ // values should be rounded by an appropriate amount so that they now precisely equal the
+ // original coordinates.
+ ASSERT_EQ(EXPECTED.x, event.getX(0));
+ ASSERT_EQ(EXPECTED.y, event.getY(0));
+ ASSERT_EQ(EXPECTED.x, event.getRawX(0));
+ ASSERT_EQ(EXPECTED.y, event.getRawY(0));
+ ASSERT_EQ(EXPECTED.x, event.getXCursorPosition());
+ ASSERT_EQ(EXPECTED.y, event.getYCursorPosition());
+}
+
} // namespace android
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index 05bc0bcbe8..06b841be0d 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -16,22 +16,32 @@
#include "TestHelpers.h"
-#include <unistd.h>
-#include <sys/mman.h>
-#include <time.h>
-
#include <attestation/HmacKeyManager.h>
-#include <cutils/ashmem.h>
#include <gtest/gtest.h>
#include <gui/constants.h>
#include <input/InputTransport.h>
-#include <utils/StopWatch.h>
-#include <utils/Timers.h>
using android::base::Result;
namespace android {
+namespace {
+
+static constexpr float EPSILON = MotionEvent::ROUNDING_PRECISION;
+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);
+
+struct Pointer {
+ int32_t id;
+ float x;
+ float y;
+ bool isResampled = false;
+};
+
+} // namespace
+
class InputPublisherAndConsumerTest : public testing::Test {
protected:
std::shared_ptr<InputChannel> mServerChannel, mClientChannel;
@@ -51,12 +61,28 @@ protected:
mConsumer = std::make_unique<InputConsumer>(mClientChannel);
}
- void PublishAndConsumeKeyEvent();
- void PublishAndConsumeMotionEvent();
- void PublishAndConsumeFocusEvent();
- void PublishAndConsumeCaptureEvent();
- void PublishAndConsumeDragEvent();
- void PublishAndConsumeTouchModeEvent();
+ void publishAndConsumeKeyEvent();
+ void publishAndConsumeMotionStream();
+ void publishAndConsumeFocusEvent();
+ void publishAndConsumeCaptureEvent();
+ void publishAndConsumeDragEvent();
+ void publishAndConsumeTouchModeEvent();
+ void publishAndConsumeMotionEvent(int32_t action, nsecs_t downTime,
+ const std::vector<Pointer>& pointers);
+
+private:
+ // The sequence number to use when publishing the next event
+ uint32_t mSeq = 1;
+
+ void publishAndConsumeMotionEvent(
+ int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags,
+ int32_t metaState, int32_t buttonState, MotionClassification classification,
+ float xScale, float yScale, float xOffset, float yOffset, float xPrecision,
+ float yPrecision, float xCursorPosition, float yCursorPosition, float rawXScale,
+ float rawYScale, float rawXOffset, float rawYOffset, nsecs_t downTime,
+ nsecs_t eventTime, const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords);
};
TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -68,10 +94,10 @@ TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
mConsumer->getChannel()->getConnectionToken());
}
-void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeKeyEvent() {
status_t status;
- constexpr uint32_t seq = 15;
+ const uint32_t seq = mSeq++;
int32_t eventId = InputEvent::nextId();
constexpr int32_t deviceId = 1;
constexpr uint32_t source = AINPUT_SOURCE_KEYBOARD;
@@ -97,14 +123,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<KeyEvent*>(event);
EXPECT_EQ(seq, consumeSeq);
@@ -138,20 +163,43 @@ void InputPublisherAndConsumerTest::PublishAndConsumeKeyEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
- status_t status;
+void InputPublisherAndConsumerTest::publishAndConsumeMotionStream() {
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
- constexpr uint32_t seq = 15;
- int32_t eventId = InputEvent::nextId();
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 300, .y = 400}});
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+ int32_t action, nsecs_t downTime, const std::vector<Pointer>& pointers) {
constexpr int32_t deviceId = 1;
constexpr uint32_t source = AINPUT_SOURCE_TOUCHSCREEN;
constexpr int32_t displayId = ADISPLAY_ID_DEFAULT;
constexpr std::array<uint8_t, 32> hmac = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21,
22, 23, 24, 25, 26, 27, 28, 29, 30, 31};
- constexpr int32_t action = AMOTION_EVENT_ACTION_MOVE;
constexpr int32_t actionButton = 0;
- constexpr int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ int32_t flags = AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ const size_t pointerCount = pointers.size();
constexpr int32_t edgeFlags = AMOTION_EVENT_EDGE_FLAG_TOP;
constexpr int32_t metaState = AMETA_ALT_LEFT_ON | AMETA_ALT_ON;
constexpr int32_t buttonState = AMOTION_EVENT_BUTTON_PRIMARY;
@@ -168,20 +216,21 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
constexpr float yPrecision = 0.5;
constexpr float xCursorPosition = 1.3;
constexpr float yCursorPosition = 50.6;
- constexpr nsecs_t downTime = 3;
- constexpr size_t pointerCount = 3;
- constexpr nsecs_t eventTime = 4;
- const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
- PointerProperties pointerProperties[pointerCount];
- PointerCoords pointerCoords[pointerCount];
+
+ const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
for (size_t i = 0; i < pointerCount; i++) {
+ pointerProperties.push_back({});
pointerProperties[i].clear();
- pointerProperties[i].id = (i + 2) % pointerCount;
- pointerProperties[i].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ pointerProperties[i].id = pointers[i].id;
+ pointerProperties[i].toolType = ToolType::FINGER;
+ pointerCoords.push_back({});
pointerCoords[i].clear();
- pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, 100 * i);
- pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, 200 * i);
+ pointerCoords[i].isResampled = pointers[i].isResampled;
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X, pointers[i].x);
+ pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y, pointers[i].y);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 0.5 * i);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_SIZE, 0.7 * i);
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, 1.5 * i);
@@ -191,28 +240,50 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, 3.5 * i);
}
+ publishAndConsumeMotionEvent(deviceId, source, displayId, hmac, action, actionButton, flags,
+ edgeFlags, metaState, buttonState, classification, xScale, yScale,
+ xOffset, yOffset, xPrecision, yPrecision, xCursorPosition,
+ yCursorPosition, rawXScale, rawYScale, rawXOffset, rawYOffset,
+ downTime, eventTime, pointerProperties, pointerCoords);
+}
+
+void InputPublisherAndConsumerTest::publishAndConsumeMotionEvent(
+ int32_t deviceId, uint32_t source, int32_t displayId, std::array<uint8_t, 32> hmac,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t edgeFlags, int32_t metaState,
+ int32_t buttonState, MotionClassification classification, float xScale, float yScale,
+ float xOffset, float yOffset, float xPrecision, float yPrecision, float xCursorPosition,
+ float yCursorPosition, float rawXScale, float rawYScale, float rawXOffset, float rawYOffset,
+ nsecs_t downTime, nsecs_t eventTime,
+ const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords) {
+ const uint32_t seq = mSeq++;
+ const int32_t eventId = InputEvent::nextId();
ui::Transform transform;
transform.set({xScale, 0, xOffset, 0, yScale, yOffset, 0, 0, 1});
ui::Transform rawTransform;
rawTransform.set({rawXScale, 0, rawXOffset, 0, rawYScale, rawYOffset, 0, 0, 1});
+
+ status_t status;
+ ASSERT_EQ(pointerProperties.size(), pointerCoords.size());
+ const size_t pointerCount = pointerProperties.size();
+ const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
status = mPublisher->publishMotionEvent(seq, eventId, deviceId, source, displayId, hmac, action,
actionButton, flags, edgeFlags, metaState, buttonState,
classification, transform, xPrecision, yPrecision,
xCursorPosition, yCursorPosition, rawTransform,
- downTime, eventTime, pointerCount, pointerProperties,
- pointerCoords);
- ASSERT_EQ(OK, status)
- << "publisher publishMotionEvent should return OK";
+ downTime, eventTime, pointerCount,
+ pointerProperties.data(), pointerCoords.data());
+ ASSERT_EQ(OK, status) << "publisher publishMotionEvent should return OK";
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<MotionEvent*>(event);
@@ -233,10 +304,10 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
EXPECT_EQ(yOffset, motionEvent->getYOffset());
EXPECT_EQ(xPrecision, motionEvent->getXPrecision());
EXPECT_EQ(yPrecision, motionEvent->getYPrecision());
- EXPECT_EQ(xCursorPosition, motionEvent->getRawXCursorPosition());
- EXPECT_EQ(yCursorPosition, motionEvent->getRawYCursorPosition());
- EXPECT_EQ(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition());
- EXPECT_EQ(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition());
+ EXPECT_NEAR(xCursorPosition, motionEvent->getRawXCursorPosition(), EPSILON);
+ EXPECT_NEAR(yCursorPosition, motionEvent->getRawYCursorPosition(), EPSILON);
+ EXPECT_NEAR(xCursorPosition * xScale + xOffset, motionEvent->getXCursorPosition(), EPSILON);
+ EXPECT_NEAR(yCursorPosition * yScale + yOffset, motionEvent->getYCursorPosition(), EPSILON);
EXPECT_EQ(rawTransform, motionEvent->getRawTransform());
EXPECT_EQ(downTime, motionEvent->getDownTime());
EXPECT_EQ(eventTime, motionEvent->getEventTime());
@@ -249,10 +320,12 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
EXPECT_EQ(pointerProperties[i].toolType, motionEvent->getToolType(i));
const auto& pc = pointerCoords[i];
- EXPECT_EQ(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i));
- EXPECT_EQ(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i));
- EXPECT_EQ(pc.getX() * xScale + xOffset, motionEvent->getX(i));
- EXPECT_EQ(pc.getY() * yScale + yOffset, motionEvent->getY(i));
+ EXPECT_EQ(pc, motionEvent->getSamplePointerCoords()[i]);
+
+ EXPECT_NEAR(pc.getX() * rawXScale + rawXOffset, motionEvent->getRawX(i), EPSILON);
+ EXPECT_NEAR(pc.getY() * rawYScale + rawYOffset, motionEvent->getRawY(i), EPSILON);
+ EXPECT_NEAR(pc.getX() * xScale + xOffset, motionEvent->getX(i), EPSILON);
+ EXPECT_NEAR(pc.getY() * yScale + yOffset, motionEvent->getY(i), EPSILON);
EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE), motionEvent->getPressure(i));
EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_SIZE), motionEvent->getSize(i));
EXPECT_EQ(pc.getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR), motionEvent->getTouchMajor(i));
@@ -284,7 +357,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeMotionEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeFocusEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -297,11 +370,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<FocusEvent*>(event);
@@ -325,7 +398,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeFocusEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeCaptureEvent() {
status_t status;
constexpr uint32_t seq = 42;
@@ -338,11 +411,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<CaptureEvent*>(event);
@@ -365,7 +438,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeCaptureEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeDragEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -380,11 +453,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<const DragEvent&>(*event);
@@ -409,7 +482,7 @@ void InputPublisherAndConsumerTest::PublishAndConsumeDragEvent() {
<< "finished signal's consume time should be greater than publish time";
}
-void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+void InputPublisherAndConsumerTest::publishAndConsumeTouchModeEvent() {
status_t status;
constexpr uint32_t seq = 15;
@@ -422,11 +495,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<const TouchModeEvent&>(*event);
@@ -466,27 +539,27 @@ TEST_F(InputPublisherAndConsumerTest, SendTimeline) {
}
TEST_F(InputPublisherAndConsumerTest, PublishKeyEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeMotionStream());
}
TEST_F(InputPublisherAndConsumerTest, PublishFocusEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishCaptureEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishDragEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
}
TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
@@ -550,17 +623,29 @@ TEST_F(InputPublisherAndConsumerTest,
}
TEST_F(InputPublisherAndConsumerTest, PublishMultipleEvents_EndToEnd) {
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeFocusEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeCaptureEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
- ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+ const nsecs_t downTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ publishAndConsumeMotionEvent(POINTER_1_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeFocusEvent());
+ publishAndConsumeMotionEvent(POINTER_2_DOWN, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeCaptureEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeDragEvent());
+ // Provide a consistent input stream - cancel the gesture that was started above
+ publishAndConsumeMotionEvent(AMOTION_EVENT_ACTION_CANCEL, downTime,
+ {Pointer{.id = 0, .x = 20, .y = 30},
+ Pointer{.id = 1, .x = 200, .y = 300},
+ Pointer{.id = 2, .x = 200, .y = 300}});
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeKeyEvent());
+ ASSERT_NO_FATAL_FAILURE(publishAndConsumeTouchModeEvent());
}
} // namespace android
diff --git a/libs/input/tests/InputVerifier_test.cpp b/libs/input/tests/InputVerifier_test.cpp
new file mode 100644
index 0000000000..e2eb08096b
--- /dev/null
+++ b/libs/input/tests/InputVerifier_test.cpp
@@ -0,0 +1,54 @@
+/*
+ * 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 <gtest/gtest.h>
+#include <input/InputVerifier.h>
+#include <string>
+
+namespace android {
+
+using android::base::Result;
+
+TEST(InputVerifierTest, CreationWithInvalidUtfStringDoesNotCrash) {
+ constexpr char bytes[] = {static_cast<char>(0xC0), static_cast<char>(0x80)};
+ const std::string name(bytes, sizeof(bytes));
+ InputVerifier verifier(name);
+}
+
+TEST(InputVerifierTest, ProcessSourceClassPointer) {
+ InputVerifier verifier("Verify testOnTouchEventScroll");
+
+ std::vector<PointerProperties> properties;
+ properties.push_back({});
+ properties.back().clear();
+ properties.back().id = 0;
+ properties.back().toolType = ToolType::UNKNOWN;
+
+ std::vector<PointerCoords> coords;
+ coords.push_back({});
+ coords.back().clear();
+ coords.back().setAxisValue(AMOTION_EVENT_AXIS_X, 75);
+ coords.back().setAxisValue(AMOTION_EVENT_AXIS_Y, 300);
+
+ const Result<void> result =
+ verifier.processMovement(/*deviceId=*/0, AINPUT_SOURCE_CLASS_POINTER,
+ AMOTION_EVENT_ACTION_DOWN,
+ /*pointerCount=*/properties.size(), properties.data(),
+ coords.data(), /*flags=*/0);
+ ASSERT_TRUE(result.ok());
+}
+
+} // namespace android
diff --git a/libs/input/tests/MotionPredictorMetricsManager_test.cpp b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
new file mode 100644
index 0000000000..31cc1459fc
--- /dev/null
+++ b/libs/input/tests/MotionPredictorMetricsManager_test.cpp
@@ -0,0 +1,981 @@
+/*
+ * 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 <input/MotionPredictor.h>
+
+#include <cmath>
+#include <cstddef>
+#include <cstdint>
+#include <numeric>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/InputEventBuilders.h>
+#include <utils/Timers.h> // for nsecs_t
+
+#include "Eigen/Core"
+#include "Eigen/Geometry"
+
+namespace android {
+namespace {
+
+using ::testing::FloatNear;
+using ::testing::Matches;
+
+using GroundTruthPoint = MotionPredictorMetricsManager::GroundTruthPoint;
+using PredictionPoint = MotionPredictorMetricsManager::PredictionPoint;
+using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
+inline constexpr int NANOS_PER_MILLIS = 1'000'000;
+
+inline constexpr nsecs_t TEST_INITIAL_TIMESTAMP = 1'000'000'000;
+inline constexpr size_t TEST_MAX_NUM_PREDICTIONS = 5;
+inline constexpr nsecs_t TEST_PREDICTION_INTERVAL_NANOS = 12'500'000 / 3; // 1 / (240 hz)
+inline constexpr int NO_DATA_SENTINEL = MotionPredictorMetricsManager::NO_DATA_SENTINEL;
+
+// Parameters:
+// • arg: Eigen::Vector2f
+// • target: Eigen::Vector2f
+// • epsilon: float
+MATCHER_P2(Vector2fNear, target, epsilon, "") {
+ return Matches(FloatNear(target[0], epsilon))(arg[0]) &&
+ Matches(FloatNear(target[1], epsilon))(arg[1]);
+}
+
+// Parameters:
+// • arg: PredictionPoint
+// • target: PredictionPoint
+// • epsilon: float
+MATCHER_P2(PredictionPointNear, target, epsilon, "") {
+ if (!Matches(Vector2fNear(target.position, epsilon))(arg.position)) {
+ *result_listener << "Position mismatch. Actual: (" << arg.position[0] << ", "
+ << arg.position[1] << "), expected: (" << target.position[0] << ", "
+ << target.position[1] << ")";
+ return false;
+ }
+ if (!Matches(FloatNear(target.pressure, epsilon))(arg.pressure)) {
+ *result_listener << "Pressure mismatch. Actual: " << arg.pressure
+ << ", expected: " << target.pressure;
+ return false;
+ }
+ if (arg.originTimestamp != target.originTimestamp) {
+ *result_listener << "Origin timestamp mismatch. Actual: " << arg.originTimestamp
+ << ", expected: " << target.originTimestamp;
+ return false;
+ }
+ if (arg.targetTimestamp != target.targetTimestamp) {
+ *result_listener << "Target timestamp mismatch. Actual: " << arg.targetTimestamp
+ << ", expected: " << target.targetTimestamp;
+ return false;
+ }
+ return true;
+}
+
+// --- Mathematical helper functions. ---
+
+template <typename T>
+T average(std::vector<T> values) {
+ return std::accumulate(values.begin(), values.end(), T{}) / static_cast<T>(values.size());
+}
+
+template <typename T>
+T standardDeviation(std::vector<T> values) {
+ T mean = average(values);
+ T accumulator = {};
+ for (const T value : values) {
+ accumulator += value * value - mean * mean;
+ }
+ // Take the max with 0 to avoid negative values caused by numerical instability.
+ return std::sqrt(std::max(T{}, accumulator) / static_cast<T>(values.size()));
+}
+
+template <typename T>
+T rmse(std::vector<T> errors) {
+ T sse = {};
+ for (const T error : errors) {
+ sse += error * error;
+ }
+ return std::sqrt(sse / static_cast<T>(errors.size()));
+}
+
+TEST(MathematicalHelperFunctionTest, Average) {
+ std::vector<float> values{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
+ EXPECT_EQ(5.5f, average(values));
+}
+
+TEST(MathematicalHelperFunctionTest, StandardDeviation) {
+ // https://www.calculator.net/standard-deviation-calculator.html?numberinputs=10%2C+12%2C+23%2C+23%2C+16%2C+23%2C+21%2C+16
+ std::vector<float> values{10, 12, 23, 23, 16, 23, 21, 16};
+ EXPECT_FLOAT_EQ(4.8989794855664f, standardDeviation(values));
+}
+
+TEST(MathematicalHelperFunctionTest, Rmse) {
+ std::vector<float> errors{1, 5, 7, 7, 8, 20};
+ EXPECT_FLOAT_EQ(9.899494937f, rmse(errors));
+}
+
+// --- MotionEvent-related helper functions. ---
+
+// Creates a MotionEvent corresponding to the given GroundTruthPoint.
+MotionEvent makeMotionEvent(const GroundTruthPoint& groundTruthPoint) {
+ // Build single pointer of type STYLUS, with coordinates from groundTruthPoint.
+ PointerBuilder pointerBuilder =
+ PointerBuilder(/*id=*/0, ToolType::STYLUS)
+ .x(groundTruthPoint.position[1])
+ .y(groundTruthPoint.position[0])
+ .axis(AMOTION_EVENT_AXIS_PRESSURE, groundTruthPoint.pressure);
+ return MotionEventBuilder(/*action=*/AMOTION_EVENT_ACTION_MOVE,
+ /*source=*/AINPUT_SOURCE_CLASS_POINTER)
+ .eventTime(groundTruthPoint.timestamp)
+ .pointer(pointerBuilder)
+ .build();
+}
+
+// Creates a MotionEvent corresponding to the given sequence of PredictionPoints.
+MotionEvent makeMotionEvent(const std::vector<PredictionPoint>& predictionPoints) {
+ // Build single pointer of type STYLUS, with coordinates from first prediction point.
+ PointerBuilder pointerBuilder =
+ PointerBuilder(/*id=*/0, ToolType::STYLUS)
+ .x(predictionPoints[0].position[1])
+ .y(predictionPoints[0].position[0])
+ .axis(AMOTION_EVENT_AXIS_PRESSURE, predictionPoints[0].pressure);
+ MotionEvent predictionEvent =
+ MotionEventBuilder(
+ /*action=*/AMOTION_EVENT_ACTION_MOVE, /*source=*/AINPUT_SOURCE_CLASS_POINTER)
+ .eventTime(predictionPoints[0].targetTimestamp)
+ .pointer(pointerBuilder)
+ .build();
+ for (size_t i = 1; i < predictionPoints.size(); ++i) {
+ PointerCoords coords =
+ PointerBuilder(/*id=*/0, ToolType::STYLUS)
+ .x(predictionPoints[i].position[1])
+ .y(predictionPoints[i].position[0])
+ .axis(AMOTION_EVENT_AXIS_PRESSURE, predictionPoints[i].pressure)
+ .buildCoords();
+ predictionEvent.addSample(predictionPoints[i].targetTimestamp, &coords);
+ }
+ return predictionEvent;
+}
+
+// Creates a MotionEvent corresponding to a stylus lift (UP) ground truth event.
+MotionEvent makeLiftMotionEvent() {
+ return MotionEventBuilder(/*action=*/AMOTION_EVENT_ACTION_UP,
+ /*source=*/AINPUT_SOURCE_CLASS_POINTER)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS))
+ .build();
+}
+
+TEST(MakeMotionEventTest, MakeGroundTruthMotionEvent) {
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f),
+ .pressure = 0.6f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+ const MotionEvent groundTruthMotionEvent = makeMotionEvent(groundTruthPoint);
+
+ ASSERT_EQ(1u, groundTruthMotionEvent.getPointerCount());
+ // Note: a MotionEvent's "history size" is one less than its number of samples.
+ ASSERT_EQ(0u, groundTruthMotionEvent.getHistorySize());
+ EXPECT_EQ(groundTruthPoint.position[0], groundTruthMotionEvent.getRawPointerCoords(0)->getY());
+ EXPECT_EQ(groundTruthPoint.position[1], groundTruthMotionEvent.getRawPointerCoords(0)->getX());
+ EXPECT_EQ(groundTruthPoint.pressure,
+ groundTruthMotionEvent.getRawPointerCoords(0)->getAxisValue(
+ AMOTION_EVENT_AXIS_PRESSURE));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, groundTruthMotionEvent.getAction());
+}
+
+TEST(MakeMotionEventTest, MakePredictionMotionEvent) {
+ const nsecs_t originTimestamp = TEST_INITIAL_TIMESTAMP;
+ const std::vector<PredictionPoint>
+ predictionPoints{{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp + 5 * NANOS_PER_MILLIS},
+ {{.position = Eigen::Vector2f(11.0f, 22.0f), .pressure = 0.5f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp + 10 * NANOS_PER_MILLIS},
+ {{.position = Eigen::Vector2f(12.0f, 24.0f), .pressure = 0.4f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp + 15 * NANOS_PER_MILLIS}};
+ const MotionEvent predictionMotionEvent = makeMotionEvent(predictionPoints);
+
+ ASSERT_EQ(1u, predictionMotionEvent.getPointerCount());
+ // Note: a MotionEvent's "history size" is one less than its number of samples.
+ ASSERT_EQ(predictionPoints.size(), predictionMotionEvent.getHistorySize() + 1);
+ for (size_t i = 0; i < predictionPoints.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const PointerCoords coords = *predictionMotionEvent.getHistoricalRawPointerCoords(
+ /*pointerIndex=*/0, /*historicalIndex=*/i);
+ EXPECT_EQ(predictionPoints[i].position[0], coords.getY());
+ EXPECT_EQ(predictionPoints[i].position[1], coords.getX());
+ EXPECT_EQ(predictionPoints[i].pressure, coords.getAxisValue(AMOTION_EVENT_AXIS_PRESSURE));
+ // Note: originTimestamp is discarded when converting PredictionPoint to MotionEvent.
+ EXPECT_EQ(predictionPoints[i].targetTimestamp,
+ predictionMotionEvent.getHistoricalEventTime(i));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_MOVE, predictionMotionEvent.getAction());
+ }
+}
+
+TEST(MakeMotionEventTest, MakeLiftMotionEvent) {
+ const MotionEvent liftMotionEvent = makeLiftMotionEvent();
+ ASSERT_EQ(1u, liftMotionEvent.getPointerCount());
+ // Note: a MotionEvent's "history size" is one less than its number of samples.
+ ASSERT_EQ(0u, liftMotionEvent.getHistorySize());
+ EXPECT_EQ(AMOTION_EVENT_ACTION_UP, liftMotionEvent.getAction());
+}
+
+// --- Ground-truth-generation helper functions. ---
+
+std::vector<GroundTruthPoint> generateConstantGroundTruthPoints(
+ const GroundTruthPoint& groundTruthPoint, size_t numPoints) {
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ nsecs_t timestamp = groundTruthPoint.timestamp;
+ for (size_t i = 0; i < numPoints; ++i) {
+ groundTruthPoints.emplace_back(groundTruthPoint);
+ groundTruthPoints.back().timestamp = timestamp;
+ timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+ return groundTruthPoints;
+}
+
+// This function uses the coordinate system (y, x), with +y pointing downwards and +x pointing
+// rightwards. Angles are measured counterclockwise from down (+y).
+std::vector<GroundTruthPoint> generateCircularArcGroundTruthPoints(Eigen::Vector2f initialPosition,
+ float initialAngle,
+ float velocity,
+ float turningAngle,
+ size_t numPoints) {
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ // Create first point.
+ if (numPoints > 0) {
+ groundTruthPoints.push_back({{.position = initialPosition, .pressure = 0.0f},
+ .timestamp = TEST_INITIAL_TIMESTAMP});
+ }
+ float trajectoryAngle = initialAngle; // measured counterclockwise from +y axis.
+ for (size_t i = 1; i < numPoints; ++i) {
+ const Eigen::Vector2f trajectory =
+ Eigen::Rotation2D(trajectoryAngle) * Eigen::Vector2f(1, 0);
+ groundTruthPoints.push_back(
+ {{.position = groundTruthPoints.back().position + velocity * trajectory,
+ .pressure = 0.0f},
+ .timestamp = groundTruthPoints.back().timestamp + TEST_PREDICTION_INTERVAL_NANOS});
+ trajectoryAngle += turningAngle;
+ }
+ return groundTruthPoints;
+}
+
+TEST(GenerateConstantGroundTruthPointsTest, BasicTest) {
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+ const std::vector<GroundTruthPoint> groundTruthPoints =
+ generateConstantGroundTruthPoints(groundTruthPoint, /*numPoints=*/3);
+
+ ASSERT_EQ(3u, groundTruthPoints.size());
+ // First point.
+ EXPECT_EQ(groundTruthPoints[0].position, groundTruthPoint.position);
+ EXPECT_EQ(groundTruthPoints[0].pressure, groundTruthPoint.pressure);
+ EXPECT_EQ(groundTruthPoints[0].timestamp, groundTruthPoint.timestamp);
+ // Second point.
+ EXPECT_EQ(groundTruthPoints[1].position, groundTruthPoint.position);
+ EXPECT_EQ(groundTruthPoints[1].pressure, groundTruthPoint.pressure);
+ EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+ // Third point.
+ EXPECT_EQ(groundTruthPoints[2].position, groundTruthPoint.position);
+ EXPECT_EQ(groundTruthPoints[2].pressure, groundTruthPoint.pressure);
+ EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+}
+
+TEST(GenerateCircularArcGroundTruthTest, StraightLineUpwards) {
+ const std::vector<GroundTruthPoint> groundTruthPoints = generateCircularArcGroundTruthPoints(
+ /*initialPosition=*/Eigen::Vector2f(0, 0),
+ /*initialAngle=*/M_PI,
+ /*velocity=*/1.0f,
+ /*turningAngle=*/0.0f,
+ /*numPoints=*/3);
+
+ ASSERT_EQ(3u, groundTruthPoints.size());
+ EXPECT_THAT(groundTruthPoints[0].position, Vector2fNear(Eigen::Vector2f(0, 0), 1e-6));
+ EXPECT_THAT(groundTruthPoints[1].position, Vector2fNear(Eigen::Vector2f(-1, 0), 1e-6));
+ EXPECT_THAT(groundTruthPoints[2].position, Vector2fNear(Eigen::Vector2f(-2, 0), 1e-6));
+ // Check that timestamps are increasing between consecutive ground truth points.
+ EXPECT_GT(groundTruthPoints[1].timestamp, groundTruthPoints[0].timestamp);
+ EXPECT_GT(groundTruthPoints[2].timestamp, groundTruthPoints[1].timestamp);
+}
+
+TEST(GenerateCircularArcGroundTruthTest, CounterclockwiseSquare) {
+ // Generate points in a counterclockwise unit square starting pointing right.
+ const std::vector<GroundTruthPoint> groundTruthPoints = generateCircularArcGroundTruthPoints(
+ /*initialPosition=*/Eigen::Vector2f(10, 100),
+ /*initialAngle=*/M_PI_2,
+ /*velocity=*/1.0f,
+ /*turningAngle=*/M_PI_2,
+ /*numPoints=*/5);
+
+ ASSERT_EQ(5u, groundTruthPoints.size());
+ EXPECT_THAT(groundTruthPoints[0].position, Vector2fNear(Eigen::Vector2f(10, 100), 1e-6));
+ EXPECT_THAT(groundTruthPoints[1].position, Vector2fNear(Eigen::Vector2f(10, 101), 1e-6));
+ EXPECT_THAT(groundTruthPoints[2].position, Vector2fNear(Eigen::Vector2f(9, 101), 1e-6));
+ EXPECT_THAT(groundTruthPoints[3].position, Vector2fNear(Eigen::Vector2f(9, 100), 1e-6));
+ EXPECT_THAT(groundTruthPoints[4].position, Vector2fNear(Eigen::Vector2f(10, 100), 1e-6));
+}
+
+// --- Prediction-generation helper functions. ---
+
+// Creates a sequence of predictions with values equal to those of the given GroundTruthPoint.
+std::vector<PredictionPoint> generateConstantPredictions(const GroundTruthPoint& groundTruthPoint) {
+ std::vector<PredictionPoint> predictions;
+ nsecs_t predictionTimestamp = groundTruthPoint.timestamp + TEST_PREDICTION_INTERVAL_NANOS;
+ for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
+ predictions.push_back(PredictionPoint{{.position = groundTruthPoint.position,
+ .pressure = groundTruthPoint.pressure},
+ .originTimestamp = groundTruthPoint.timestamp,
+ .targetTimestamp = predictionTimestamp});
+ predictionTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+ return predictions;
+}
+
+// Generates TEST_MAX_NUM_PREDICTIONS predictions from the given most recent two ground truth points
+// by linear extrapolation of position and pressure. The interval between consecutive predictions'
+// timestamps is TEST_PREDICTION_INTERVAL_NANOS.
+std::vector<PredictionPoint> generatePredictionsByLinearExtrapolation(
+ const GroundTruthPoint& firstGroundTruth, const GroundTruthPoint& secondGroundTruth) {
+ // Precompute deltas.
+ const Eigen::Vector2f trajectory = secondGroundTruth.position - firstGroundTruth.position;
+ const float deltaPressure = secondGroundTruth.pressure - firstGroundTruth.pressure;
+ // Compute predictions.
+ std::vector<PredictionPoint> predictions;
+ Eigen::Vector2f predictionPosition = secondGroundTruth.position;
+ float predictionPressure = secondGroundTruth.pressure;
+ nsecs_t predictionTargetTimestamp = secondGroundTruth.timestamp;
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS; ++i) {
+ predictionPosition += trajectory;
+ predictionPressure += deltaPressure;
+ predictionTargetTimestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ predictions.push_back(
+ PredictionPoint{{.position = predictionPosition, .pressure = predictionPressure},
+ .originTimestamp = secondGroundTruth.timestamp,
+ .targetTimestamp = predictionTargetTimestamp});
+ }
+ return predictions;
+}
+
+TEST(GeneratePredictionsTest, GenerateConstantPredictions) {
+ const GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10, 20), .pressure = 0.3f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+ const std::vector<PredictionPoint> predictionPoints =
+ generateConstantPredictions(groundTruthPoint);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
+ for (size_t i = 0; i < predictionPoints.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ EXPECT_THAT(predictionPoints[i].position, Vector2fNear(groundTruthPoint.position, 1e-6));
+ EXPECT_THAT(predictionPoints[i].pressure, FloatNear(groundTruthPoint.pressure, 1e-6));
+ EXPECT_EQ(predictionPoints[i].originTimestamp, groundTruthPoint.timestamp);
+ EXPECT_EQ(predictionPoints[i].targetTimestamp,
+ groundTruthPoint.timestamp +
+ static_cast<nsecs_t>(i + 1) * TEST_PREDICTION_INTERVAL_NANOS);
+ }
+}
+
+TEST(GeneratePredictionsTest, LinearExtrapolationFromTwoPoints) {
+ const nsecs_t initialTimestamp = TEST_INITIAL_TIMESTAMP;
+ const std::vector<PredictionPoint> predictionPoints = generatePredictionsByLinearExtrapolation(
+ GroundTruthPoint{{.position = Eigen::Vector2f(100, 200), .pressure = 0.9f},
+ .timestamp = initialTimestamp},
+ GroundTruthPoint{{.position = Eigen::Vector2f(105, 190), .pressure = 0.8f},
+ .timestamp = initialTimestamp + TEST_PREDICTION_INTERVAL_NANOS});
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, predictionPoints.size());
+ const nsecs_t originTimestamp = initialTimestamp + TEST_PREDICTION_INTERVAL_NANOS;
+ EXPECT_THAT(predictionPoints[0],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(110, 180),
+ .pressure = 0.7f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[1],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(115, 170),
+ .pressure = 0.6f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 2 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[2],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(120, 160),
+ .pressure = 0.5f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 3 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[3],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(125, 150),
+ .pressure = 0.4f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 4 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+ EXPECT_THAT(predictionPoints[4],
+ PredictionPointNear(PredictionPoint{{.position = Eigen::Vector2f(130, 140),
+ .pressure = 0.3f},
+ .originTimestamp = originTimestamp,
+ .targetTimestamp = originTimestamp +
+ 5 * TEST_PREDICTION_INTERVAL_NANOS},
+ 0.001));
+}
+
+// Generates predictions by linear extrapolation for each consecutive pair of ground truth points
+// (see the comment for the above function for further explanation). Returns a vector of vectors of
+// prediction points, where the first index is the source ground truth index, and the second is the
+// prediction target index.
+//
+// The returned vector has size equal to the input vector, and the first element of the returned
+// vector is always empty.
+std::vector<std::vector<PredictionPoint>> generateAllPredictionsByLinearExtrapolation(
+ const std::vector<GroundTruthPoint>& groundTruthPoints) {
+ std::vector<std::vector<PredictionPoint>> allPredictions;
+ allPredictions.emplace_back();
+ for (size_t i = 1; i < groundTruthPoints.size(); ++i) {
+ allPredictions.push_back(generatePredictionsByLinearExtrapolation(groundTruthPoints[i - 1],
+ groundTruthPoints[i]));
+ }
+ return allPredictions;
+}
+
+TEST(GeneratePredictionsTest, GenerateAllPredictions) {
+ const nsecs_t initialTimestamp = TEST_INITIAL_TIMESTAMP;
+ std::vector<GroundTruthPoint>
+ groundTruthPoints{GroundTruthPoint{{.position = Eigen::Vector2f(0, 0),
+ .pressure = 0.5f},
+ .timestamp = initialTimestamp},
+ GroundTruthPoint{{.position = Eigen::Vector2f(1, -1),
+ .pressure = 0.51f},
+ .timestamp = initialTimestamp +
+ 2 * TEST_PREDICTION_INTERVAL_NANOS},
+ GroundTruthPoint{{.position = Eigen::Vector2f(2, -2),
+ .pressure = 0.52f},
+ .timestamp = initialTimestamp +
+ 3 * TEST_PREDICTION_INTERVAL_NANOS}};
+
+ const std::vector<std::vector<PredictionPoint>> allPredictions =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ // Check format of allPredictions data.
+ ASSERT_EQ(groundTruthPoints.size(), allPredictions.size());
+ EXPECT_TRUE(allPredictions[0].empty());
+ EXPECT_EQ(TEST_MAX_NUM_PREDICTIONS, allPredictions[1].size());
+ EXPECT_EQ(TEST_MAX_NUM_PREDICTIONS, allPredictions[2].size());
+
+ // Check positions of predictions generated from first pair of ground truth points.
+ EXPECT_THAT(allPredictions[1][0].position, Vector2fNear(Eigen::Vector2f(2, -2), 1e-9));
+ EXPECT_THAT(allPredictions[1][1].position, Vector2fNear(Eigen::Vector2f(3, -3), 1e-9));
+ EXPECT_THAT(allPredictions[1][2].position, Vector2fNear(Eigen::Vector2f(4, -4), 1e-9));
+ EXPECT_THAT(allPredictions[1][3].position, Vector2fNear(Eigen::Vector2f(5, -5), 1e-9));
+ EXPECT_THAT(allPredictions[1][4].position, Vector2fNear(Eigen::Vector2f(6, -6), 1e-9));
+
+ // Check pressures of predictions generated from first pair of ground truth points.
+ EXPECT_FLOAT_EQ(0.52f, allPredictions[1][0].pressure);
+ EXPECT_FLOAT_EQ(0.53f, allPredictions[1][1].pressure);
+ EXPECT_FLOAT_EQ(0.54f, allPredictions[1][2].pressure);
+ EXPECT_FLOAT_EQ(0.55f, allPredictions[1][3].pressure);
+ EXPECT_FLOAT_EQ(0.56f, allPredictions[1][4].pressure);
+}
+
+// --- Prediction error helper functions. ---
+
+struct GeneralPositionErrors {
+ float alongTrajectoryErrorMean;
+ float alongTrajectoryErrorStd;
+ float offTrajectoryRmse;
+};
+
+// Inputs:
+// • Vector of ground truth points
+// • Vector of vectors of prediction points, where the first index is the source ground truth
+// index, and the second is the prediction target index.
+//
+// Returns a vector of GeneralPositionErrors, indexed by prediction time delta bucket.
+std::vector<GeneralPositionErrors> computeGeneralPositionErrors(
+ const std::vector<GroundTruthPoint>& groundTruthPoints,
+ const std::vector<std::vector<PredictionPoint>>& predictionPoints) {
+ // Aggregate errors by time bucket (prediction target index).
+ std::vector<GeneralPositionErrors> generalPostitionErrors;
+ for (size_t predictionTargetIndex = 0; predictionTargetIndex < TEST_MAX_NUM_PREDICTIONS;
+ ++predictionTargetIndex) {
+ std::vector<float> alongTrajectoryErrors;
+ std::vector<float> alongTrajectorySquaredErrors;
+ std::vector<float> offTrajectoryErrors;
+ for (size_t sourceGroundTruthIndex = 1; sourceGroundTruthIndex < groundTruthPoints.size();
+ ++sourceGroundTruthIndex) {
+ const size_t targetGroundTruthIndex =
+ sourceGroundTruthIndex + predictionTargetIndex + 1;
+ // Only include errors for points with a ground truth value.
+ if (targetGroundTruthIndex < groundTruthPoints.size()) {
+ const Eigen::Vector2f trajectory =
+ (groundTruthPoints[targetGroundTruthIndex].position -
+ groundTruthPoints[targetGroundTruthIndex - 1].position)
+ .normalized();
+ const Eigen::Vector2f orthogonalTrajectory =
+ Eigen::Rotation2Df(M_PI_2) * trajectory;
+ const Eigen::Vector2f positionError =
+ predictionPoints[sourceGroundTruthIndex][predictionTargetIndex].position -
+ groundTruthPoints[targetGroundTruthIndex].position;
+ alongTrajectoryErrors.push_back(positionError.dot(trajectory));
+ alongTrajectorySquaredErrors.push_back(alongTrajectoryErrors.back() *
+ alongTrajectoryErrors.back());
+ offTrajectoryErrors.push_back(positionError.dot(orthogonalTrajectory));
+ }
+ }
+ generalPostitionErrors.push_back(
+ {.alongTrajectoryErrorMean = average(alongTrajectoryErrors),
+ .alongTrajectoryErrorStd = standardDeviation(alongTrajectoryErrors),
+ .offTrajectoryRmse = rmse(offTrajectoryErrors)});
+ }
+ return generalPostitionErrors;
+}
+
+// Inputs:
+// • Vector of ground truth points
+// • Vector of vectors of prediction points, where the first index is the source ground truth
+// index, and the second is the prediction target index.
+//
+// Returns a vector of pressure RMSEs, indexed by prediction time delta bucket.
+std::vector<float> computePressureRmses(
+ const std::vector<GroundTruthPoint>& groundTruthPoints,
+ const std::vector<std::vector<PredictionPoint>>& predictionPoints) {
+ // Aggregate errors by time bucket (prediction target index).
+ std::vector<float> pressureRmses;
+ for (size_t predictionTargetIndex = 0; predictionTargetIndex < TEST_MAX_NUM_PREDICTIONS;
+ ++predictionTargetIndex) {
+ std::vector<float> pressureErrors;
+ for (size_t sourceGroundTruthIndex = 1; sourceGroundTruthIndex < groundTruthPoints.size();
+ ++sourceGroundTruthIndex) {
+ const size_t targetGroundTruthIndex =
+ sourceGroundTruthIndex + predictionTargetIndex + 1;
+ // Only include errors for points with a ground truth value.
+ if (targetGroundTruthIndex < groundTruthPoints.size()) {
+ pressureErrors.push_back(
+ predictionPoints[sourceGroundTruthIndex][predictionTargetIndex].pressure -
+ groundTruthPoints[targetGroundTruthIndex].pressure);
+ }
+ }
+ pressureRmses.push_back(rmse(pressureErrors));
+ }
+ return pressureRmses;
+}
+
+TEST(ErrorComputationHelperTest, ComputeGeneralPositionErrorsSimpleTest) {
+ std::vector<GroundTruthPoint> groundTruthPoints =
+ generateConstantGroundTruthPoints(GroundTruthPoint{{.position = Eigen::Vector2f(0, 0),
+ .pressure = 0.0f},
+ .timestamp = TEST_INITIAL_TIMESTAMP},
+ /*numPoints=*/TEST_MAX_NUM_PREDICTIONS + 2);
+ groundTruthPoints[3].position = Eigen::Vector2f(1, 0);
+ groundTruthPoints[4].position = Eigen::Vector2f(1, 1);
+ groundTruthPoints[5].position = Eigen::Vector2f(1, 3);
+ groundTruthPoints[6].position = Eigen::Vector2f(2, 3);
+
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ // The generated predictions look like:
+ //
+ // | Source | Target Ground Truth Index |
+ // | Index | 2 | 3 | 4 | 5 | 6 |
+ // |------------|--------|--------|--------|--------|--------|
+ // | 1 | (0, 0) | (0, 0) | (0, 0) | (0, 0) | (0, 0) |
+ // | 2 | | (0, 0) | (0, 0) | (0, 0) | (0, 0) |
+ // | 3 | | | (2, 0) | (3, 0) | (4, 0) |
+ // | 4 | | | | (1, 2) | (1, 3) |
+ // | 5 | | | | | (1, 5) |
+ // |---------------------------------------------------------|
+ // | Actual Ground Truth Values |
+ // | Position | (0, 0) | (1, 0) | (1, 1) | (1, 3) | (2, 3) |
+ // | Previous | (0, 0) | (0, 0) | (1, 0) | (1, 1) | (1, 3) |
+ //
+ // Note: this table organizes prediction targets by target ground truth index. Metrics are
+ // aggregated across points with the same prediction time bucket index, which is different.
+ // Each down-right diagonal from this table gives us points from a unique time bucket.
+
+ // Initialize expected prediction errors from the table above. The first time bucket corresponds
+ // to the long diagonal of the table, and subsequent time buckets step up-right from there.
+ const std::vector<std::vector<float>> expectedAlongTrajectoryErrors{{0, -1, -1, -1, -1},
+ {-1, -1, -3, -1},
+ {-1, -3, 2},
+ {-3, -2},
+ {-2}};
+ const std::vector<std::vector<float>> expectedOffTrajectoryErrors{{0, 0, 1, 0, 2},
+ {0, 1, 2, 0},
+ {1, 1, 3},
+ {1, 3},
+ {3}};
+
+ std::vector<GeneralPositionErrors> generalPositionErrors =
+ computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, generalPositionErrors.size());
+ for (size_t i = 0; i < generalPositionErrors.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ EXPECT_FLOAT_EQ(average(expectedAlongTrajectoryErrors[i]),
+ generalPositionErrors[i].alongTrajectoryErrorMean);
+ EXPECT_FLOAT_EQ(standardDeviation(expectedAlongTrajectoryErrors[i]),
+ generalPositionErrors[i].alongTrajectoryErrorStd);
+ EXPECT_FLOAT_EQ(rmse(expectedOffTrajectoryErrors[i]),
+ generalPositionErrors[i].offTrajectoryRmse);
+ }
+}
+
+TEST(ErrorComputationHelperTest, ComputePressureRmsesSimpleTest) {
+ // Generate ground truth points with pressures {0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.5}.
+ // (We need TEST_MAX_NUM_PREDICTIONS + 2 to test all prediction time buckets.)
+ std::vector<GroundTruthPoint> groundTruthPoints =
+ generateConstantGroundTruthPoints(GroundTruthPoint{{.position = Eigen::Vector2f(0, 0),
+ .pressure = 0.0f},
+ .timestamp = TEST_INITIAL_TIMESTAMP},
+ /*numPoints=*/TEST_MAX_NUM_PREDICTIONS + 2);
+ for (size_t i = 4; i < groundTruthPoints.size(); ++i) {
+ groundTruthPoints[i].pressure = 0.5f;
+ }
+
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ std::vector<float> pressureRmses = computePressureRmses(groundTruthPoints, predictionPoints);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, pressureRmses.size());
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{0.0f, 0.0f, -0.5f, 0.5f, 0.0f}), pressureRmses[0]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{0.0f, -0.5f, -0.5f, 1.0f}), pressureRmses[1]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{-0.5f, -0.5f, -0.5f}), pressureRmses[2]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{-0.5f, -0.5f}), pressureRmses[3]);
+ EXPECT_FLOAT_EQ(rmse(std::vector<float>{-0.5f}), pressureRmses[4]);
+}
+
+// --- MotionPredictorMetricsManager tests. ---
+
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>& reportedAtomFields) {
+ return [&reportedAtomFields](const AtomFields& atomFields) -> void {
+ reportedAtomFields.push_back(atomFields);
+ };
+}
+
+// Helper function that instantiates a MetricsManager that reports metrics to outReportedAtomFields.
+// Takes vectors of ground truth and prediction points of the same length, and passes these points
+// to the MetricsManager. The format of these vectors is expected to be:
+// • groundTruthPoints: chronologically-ordered ground truth points, with at least 2 elements.
+// • predictionPoints: the first index points to a vector of predictions corresponding to the
+// source ground truth point with the same index.
+// - The first element should be empty, because there are not expected to be predictions until
+// we have received 2 ground truth points.
+// - The last element may be empty, because there will be no future ground truth points to
+// associate with those predictions (if not empty, it will be ignored).
+// - To test all prediction buckets, there should be at least TEST_MAX_NUM_PREDICTIONS non-empty
+// prediction sets (that is, excluding the first and last). Thus, groundTruthPoints and
+// predictionPoints should have size at least TEST_MAX_NUM_PREDICTIONS + 2.
+//
+// When the function returns, outReportedAtomFields will contain the reported AtomFields.
+//
+// This function returns void so that it can use test assertions.
+void runMetricsManager(const std::vector<GroundTruthPoint>& groundTruthPoints,
+ const std::vector<std::vector<PredictionPoint>>& predictionPoints,
+ std::vector<AtomFields>& outReportedAtomFields) {
+ MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
+ TEST_MAX_NUM_PREDICTIONS,
+ createMockReportAtomFunction(
+ outReportedAtomFields));
+
+ // Validate structure of groundTruthPoints and predictionPoints.
+ ASSERT_EQ(predictionPoints.size(), groundTruthPoints.size());
+ ASSERT_GE(groundTruthPoints.size(), 2u);
+ ASSERT_EQ(predictionPoints[0].size(), 0u);
+ for (size_t i = 1; i + 1 < predictionPoints.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ ASSERT_EQ(predictionPoints[i].size(), TEST_MAX_NUM_PREDICTIONS);
+ }
+
+ // Pass ground truth points and predictions (for all except first and last ground truth).
+ for (size_t i = 0; i < groundTruthPoints.size(); ++i) {
+ metricsManager.onRecord(makeMotionEvent(groundTruthPoints[i]));
+ if ((i > 0) && (i + 1 < predictionPoints.size())) {
+ metricsManager.onPredict(makeMotionEvent(predictionPoints[i]));
+ }
+ }
+ // Send a stroke-end event to trigger the logging call.
+ metricsManager.onRecord(makeLiftMotionEvent());
+}
+
+// Vacuous test:
+// • Input: no prediction data.
+// • Expectation: no metrics should be logged.
+TEST(MotionPredictorMetricsManagerTest, NoPredictions) {
+ std::vector<AtomFields> reportedAtomFields;
+ MotionPredictorMetricsManager metricsManager(TEST_PREDICTION_INTERVAL_NANOS,
+ TEST_MAX_NUM_PREDICTIONS,
+ createMockReportAtomFunction(reportedAtomFields));
+
+ metricsManager.onRecord(makeMotionEvent(
+ GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = 0}, .timestamp = 0}));
+ metricsManager.onRecord(makeLiftMotionEvent());
+
+ // Check that reportedAtomFields is still empty (as it was initialized empty), ensuring that
+ // no metrics were logged.
+ EXPECT_EQ(0u, reportedAtomFields.size());
+}
+
+// Perfect predictions test:
+// • Input: constant input events, perfect predictions matching the input events.
+// • Expectation: all error metrics should be zero, or NO_DATA_SENTINEL for "unreported" metrics.
+// (For example, scale-invariant errors are only reported for the final time bucket.)
+TEST(MotionPredictorMetricsManagerTest, ConstantGroundTruthPerfectPredictions) {
+ GroundTruthPoint groundTruthPoint{{.position = Eigen::Vector2f(10.0f, 20.0f), .pressure = 0.6f},
+ .timestamp = TEST_INITIAL_TIMESTAMP};
+
+ // Generate ground truth and prediction points as described by the runMetricsManager comment.
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ std::vector<std::vector<PredictionPoint>> predictionPoints;
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS + 2; ++i) {
+ groundTruthPoints.push_back(groundTruthPoint);
+ predictionPoints.push_back(i > 0 ? generateConstantPredictions(groundTruthPoint)
+ : std::vector<PredictionPoint>{});
+ groundTruthPoint.timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ // Check that errors are all zero, or NO_DATA_SENTINEL for unreported metrics.
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = reportedAtomFields[i];
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+ // General errors: reported for every time bucket.
+ EXPECT_EQ(0, atom.alongTrajectoryErrorMeanMillipixels);
+ EXPECT_EQ(0, atom.alongTrajectoryErrorStdMillipixels);
+ EXPECT_EQ(0, atom.offTrajectoryRmseMillipixels);
+ EXPECT_EQ(0, atom.pressureRmseMilliunits);
+ // High-velocity errors: reported only for the last two time buckets.
+ // However, this data has zero velocity, so these metrics should all be NO_DATA_SENTINEL.
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
+ // Scale-invariant errors: reported only for the last time bucket.
+ if (i + 1 == reportedAtomFields.size()) {
+ EXPECT_EQ(0, atom.scaleInvariantAlongTrajectoryRmse);
+ EXPECT_EQ(0, atom.scaleInvariantOffTrajectoryRmse);
+ } else {
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantOffTrajectoryRmse);
+ }
+ }
+}
+
+TEST(MotionPredictorMetricsManagerTest, QuadraticPressureLinearPredictions) {
+ // Generate ground truth points.
+ //
+ // Ground truth pressures are a quadratically increasing function from some initial value.
+ const float initialPressure = 0.5f;
+ const float quadraticCoefficient = 0.01f;
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ nsecs_t timestamp = TEST_INITIAL_TIMESTAMP;
+ // As described in the runMetricsManager comment, we should have TEST_MAX_NUM_PREDICTIONS + 2
+ // ground truth points.
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS + 2; ++i) {
+ const float pressure = initialPressure + quadraticCoefficient * static_cast<float>(i * i);
+ groundTruthPoints.push_back(
+ GroundTruthPoint{{.position = Eigen::Vector2f(0, 0), .pressure = pressure},
+ .timestamp = timestamp});
+ timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+
+ // Note: the first index is the source ground truth index, and the second is the prediction
+ // target index.
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ const std::vector<float> pressureErrors =
+ computePressureRmses(groundTruthPoints, predictionPoints);
+
+ // Run test.
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+
+ // Check logged metrics match expectations.
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = reportedAtomFields[i];
+ // Check time bucket delta matches expectation based on index and prediction interval.
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+ // Check pressure error matches expectation.
+ EXPECT_NEAR(static_cast<int>(1000 * pressureErrors[i]), atom.pressureRmseMilliunits, 1);
+ }
+}
+
+TEST(MotionPredictorMetricsManagerTest, QuadraticPositionLinearPredictionsGeneralErrors) {
+ // Generate ground truth points.
+ //
+ // Each component of the ground truth positions are an independent quadratically increasing
+ // function from some initial value.
+ const Eigen::Vector2f initialPosition(200, 300);
+ const Eigen::Vector2f quadraticCoefficients(-2, 3);
+ std::vector<GroundTruthPoint> groundTruthPoints;
+ nsecs_t timestamp = TEST_INITIAL_TIMESTAMP;
+ // As described in the runMetricsManager comment, we should have TEST_MAX_NUM_PREDICTIONS + 2
+ // ground truth points.
+ for (size_t i = 0; i < TEST_MAX_NUM_PREDICTIONS + 2; ++i) {
+ const Eigen::Vector2f position =
+ initialPosition + quadraticCoefficients * static_cast<float>(i * i);
+ groundTruthPoints.push_back(
+ GroundTruthPoint{{.position = position, .pressure = 0.5}, .timestamp = timestamp});
+ timestamp += TEST_PREDICTION_INTERVAL_NANOS;
+ }
+
+ // Note: the first index is the source ground truth index, and the second is the prediction
+ // target index.
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ std::vector<GeneralPositionErrors> generalPositionErrors =
+ computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
+
+ // Run test.
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+
+ // Check logged metrics match expectations.
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = reportedAtomFields[i];
+ // Check time bucket delta matches expectation based on index and prediction interval.
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+ // Check general position errors match expectation.
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].alongTrajectoryErrorMean),
+ atom.alongTrajectoryErrorMeanMillipixels, 1);
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].alongTrajectoryErrorStd),
+ atom.alongTrajectoryErrorStdMillipixels, 1);
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].offTrajectoryRmse),
+ atom.offTrajectoryRmseMillipixels, 1);
+ }
+}
+
+// Counterclockwise regular octagonal section test:
+// • Input – ground truth: constantly-spaced input events starting at a trajectory pointing exactly
+// rightwards, and rotating by 45° counterclockwise after each input.
+// • Input – predictions: simple linear extrapolations of previous two ground truth points.
+//
+// The code below uses the following terminology to distinguish references to ground truth events:
+// • Source ground truth: the most recent ground truth point received at the time the prediction
+// was made.
+// • Target ground truth: the ground truth event that the prediction was attempting to match.
+TEST(MotionPredictorMetricsManagerTest, CounterclockwiseOctagonGroundTruthLinearPredictions) {
+ // Select a stroke velocity that exceeds the high-velocity threshold of 1100 px/sec.
+ // For an input rate of 240 hz, 1100 px/sec * (1/240) sec/input ≈ 4.58 pixels per input.
+ const float strokeVelocity = 10; // pixels per input
+
+ // As described in the runMetricsManager comment, we should have TEST_MAX_NUM_PREDICTIONS + 2
+ // ground truth points.
+ std::vector<GroundTruthPoint> groundTruthPoints = generateCircularArcGroundTruthPoints(
+ /*initialPosition=*/Eigen::Vector2f(100, 100),
+ /*initialAngle=*/M_PI_2,
+ /*velocity=*/strokeVelocity,
+ /*turningAngle=*/-M_PI_4,
+ /*numPoints=*/TEST_MAX_NUM_PREDICTIONS + 2);
+
+ std::vector<std::vector<PredictionPoint>> predictionPoints =
+ generateAllPredictionsByLinearExtrapolation(groundTruthPoints);
+
+ std::vector<GeneralPositionErrors> generalPositionErrors =
+ computeGeneralPositionErrors(groundTruthPoints, predictionPoints);
+
+ // Run test.
+ std::vector<AtomFields> reportedAtomFields;
+ runMetricsManager(groundTruthPoints, predictionPoints, reportedAtomFields);
+
+ // Check logged metrics match expectations.
+ ASSERT_EQ(TEST_MAX_NUM_PREDICTIONS, reportedAtomFields.size());
+ for (size_t i = 0; i < reportedAtomFields.size(); ++i) {
+ SCOPED_TRACE(testing::Message() << "i = " << i);
+ const AtomFields& atom = reportedAtomFields[i];
+ const nsecs_t deltaTimeBucketNanos = TEST_PREDICTION_INTERVAL_NANOS * (i + 1);
+ EXPECT_EQ(deltaTimeBucketNanos / NANOS_PER_MILLIS, atom.deltaTimeBucketMilliseconds);
+
+ // General errors: reported for every time bucket.
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].alongTrajectoryErrorMean),
+ atom.alongTrajectoryErrorMeanMillipixels, 1);
+ // We allow for some floating point error in standard deviation (0.02 pixels).
+ EXPECT_NEAR(1000 * generalPositionErrors[i].alongTrajectoryErrorStd,
+ atom.alongTrajectoryErrorStdMillipixels, 20);
+ // All position errors are equal, so the standard deviation should be approximately zero.
+ EXPECT_NEAR(0, atom.alongTrajectoryErrorStdMillipixels, 20);
+ // Absolute value for RMSE, since it must be non-negative.
+ EXPECT_NEAR(static_cast<int>(1000 * generalPositionErrors[i].offTrajectoryRmse),
+ atom.offTrajectoryRmseMillipixels, 1);
+
+ // High-velocity errors: reported only for the last two time buckets.
+ //
+ // Since our input stroke velocity is chosen to be above the high-velocity threshold, all
+ // data contributes to high-velocity errors, and thus high-velocity errors should be equal
+ // to general errors (where reported).
+ //
+ // As above, use absolute value for RMSE, since it must be non-negative.
+ if (i + 2 >= reportedAtomFields.size()) {
+ EXPECT_NEAR(static_cast<int>(
+ 1000 * std::abs(generalPositionErrors[i].alongTrajectoryErrorMean)),
+ atom.highVelocityAlongTrajectoryRmse, 1);
+ EXPECT_NEAR(static_cast<int>(1000 *
+ std::abs(generalPositionErrors[i].offTrajectoryRmse)),
+ atom.highVelocityOffTrajectoryRmse, 1);
+ } else {
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.highVelocityOffTrajectoryRmse);
+ }
+
+ // Scale-invariant errors: reported only for the last time bucket, where the reported value
+ // is the aggregation across all time buckets.
+ //
+ // The MetricsManager stores mMaxNumPredictions recent ground truth segments. Our ground
+ // truth segments here all have a length of strokeVelocity, so we can convert general errors
+ // to scale-invariant errors by dividing by `strokeVelocty * TEST_MAX_NUM_PREDICTIONS`.
+ //
+ // As above, use absolute value for RMSE, since it must be non-negative.
+ if (i + 1 == reportedAtomFields.size()) {
+ const float pathLength = strokeVelocity * TEST_MAX_NUM_PREDICTIONS;
+ std::vector<float> alongTrajectoryAbsoluteErrors;
+ std::vector<float> offTrajectoryAbsoluteErrors;
+ for (size_t j = 0; j < TEST_MAX_NUM_PREDICTIONS; ++j) {
+ alongTrajectoryAbsoluteErrors.push_back(
+ std::abs(generalPositionErrors[j].alongTrajectoryErrorMean));
+ offTrajectoryAbsoluteErrors.push_back(
+ std::abs(generalPositionErrors[j].offTrajectoryRmse));
+ }
+ EXPECT_NEAR(static_cast<int>(1000 * average(alongTrajectoryAbsoluteErrors) /
+ pathLength),
+ atom.scaleInvariantAlongTrajectoryRmse, 1);
+ EXPECT_NEAR(static_cast<int>(1000 * average(offTrajectoryAbsoluteErrors) / pathLength),
+ atom.scaleInvariantOffTrajectoryRmse, 1);
+ } else {
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantAlongTrajectoryRmse);
+ EXPECT_EQ(NO_DATA_SENTINEL, atom.scaleInvariantOffTrajectoryRmse);
+ }
+ }
+}
+
+} // namespace
+} // namespace android
diff --git a/libs/input/tests/MotionPredictor_test.cpp b/libs/input/tests/MotionPredictor_test.cpp
new file mode 100644
index 0000000000..33431146ea
--- /dev/null
+++ b/libs/input/tests/MotionPredictor_test.cpp
@@ -0,0 +1,181 @@
+/*
+ * 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 <chrono>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <input/Input.h>
+#include <input/MotionPredictor.h>
+
+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> pointerProperties;
+ std::vector<PointerCoords> 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, StationaryNoiseFloor) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
+ []() { return true /*enable prediction*/; });
+ predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
+ predictor.record(getMotionEvent(MOVE, 0, 1, 35ms)); // No movement.
+ std::unique_ptr<MotionEvent> predicted = predictor.predict(40 * NSEC_PER_MSEC);
+ ASSERT_EQ(nullptr, predicted);
+}
+
+TEST(MotionPredictorTest, Offset) {
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/1,
+ []() { return true /*enable prediction*/; });
+ predictor.record(getMotionEvent(DOWN, 0, 1, 30ms));
+ predictor.record(getMotionEvent(MOVE, 0, 5, 35ms)); // Move enough to overcome the noise floor.
+ std::unique_ptr<MotionEvent> 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<MotionEvent> 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));
+}
+
+using AtomFields = MotionPredictorMetricsManager::AtomFields;
+using ReportAtomFunction = MotionPredictorMetricsManager::ReportAtomFunction;
+
+// Creates a mock atom reporting function that appends the reported atom to the given vector.
+// The passed-in pointer must not be nullptr.
+ReportAtomFunction createMockReportAtomFunction(std::vector<AtomFields>* reportedAtomFields) {
+ return [reportedAtomFields](const AtomFields& atomFields) -> void {
+ reportedAtomFields->push_back(atomFields);
+ };
+}
+
+TEST(MotionPredictorMetricsManagerIntegrationTest, ReportsMetrics) {
+ std::vector<AtomFields> reportedAtomFields;
+ MotionPredictor predictor(/*predictionTimestampOffsetNanos=*/0,
+ []() { return true /*enable prediction*/; },
+ createMockReportAtomFunction(&reportedAtomFields));
+
+ ASSERT_TRUE(predictor.record(getMotionEvent(DOWN, 1, 1, 0ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 2, 2, 4ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 3, 3, 8ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 4, 4, 12ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 5, 5, 16ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(MOVE, 6, 6, 20ms, /*deviceId=*/0)).ok());
+ ASSERT_TRUE(predictor.record(getMotionEvent(UP, 7, 7, 24ms, /*deviceId=*/0)).ok());
+
+ // The number of atoms reported should equal the number of prediction time buckets, which is
+ // given by the prediction model's output length. For now, this value is always 5, and we
+ // hardcode it because it's not publicly accessible from the MotionPredictor.
+ EXPECT_EQ(5u, reportedAtomFields.size());
+}
+
+} // namespace android
diff --git a/libs/input/tests/RingBuffer_test.cpp b/libs/input/tests/RingBuffer_test.cpp
new file mode 100644
index 0000000000..a2ef658934
--- /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 <algorithm>
+#include <iterator>
+#include <memory>
+#include <vector>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/RingBuffer.h>
+
+namespace android {
+namespace {
+
+using ::testing::ElementsAre;
+using ::testing::ElementsAreArray;
+using ::testing::IsEmpty;
+using ::testing::Not;
+using ::testing::SizeIs;
+
+TEST(RingBufferTest, PushPop) {
+ RingBuffer<int> 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<std::unique_ptr<int>> buffer(/*capacity=*/2);
+ buffer.pushBack(std::make_unique<int>(1));
+ buffer.pushBack(std::make_unique<int>(2));
+ buffer.pushBack(std::make_unique<int>(3));
+
+ EXPECT_EQ(2, *buffer[0]);
+ EXPECT_EQ(3, *buffer[1]);
+}
+
+TEST(RingBufferTest, ConstructConstantValue) {
+ RingBuffer<int> buffer(/*count=*/3, /*value=*/10);
+ EXPECT_THAT(buffer, ElementsAre(10, 10, 10));
+ EXPECT_EQ(3u, buffer.capacity());
+}
+
+TEST(RingBufferTest, Assignment) {
+ RingBuffer<int> a(/*capacity=*/2);
+ a.pushBack(1);
+ a.pushBack(2);
+
+ RingBuffer<int> 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<int> c(b);
+ EXPECT_THAT(c, ElementsAreArray(b));
+
+ RingBuffer<int> 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<int> 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<int> 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<int> 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<int> 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<int> 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 1c8658b69a..024b6d3d43 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 0000000000..c3ac0b7cfb
--- /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 <algorithm>
+#include <cmath>
+#include <fstream>
+#include <ios>
+#include <iterator>
+#include <string>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/TfLiteMotionPredictor.h>
+
+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<TfLiteMotionPredictorModel> 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<TfLiteMotionPredictorModel> model = TfLiteMotionPredictorModel::create();
+ ASSERT_GT(model->inputLength(), 0u);
+
+ const size_t inputLength = model->inputLength();
+ ASSERT_EQ(inputLength, static_cast<size_t>(model->inputR().size()));
+ ASSERT_EQ(inputLength, static_cast<size_t>(model->inputPhi().size()));
+ ASSERT_EQ(inputLength, static_cast<size_t>(model->inputPressure().size()));
+ ASSERT_EQ(inputLength, static_cast<size_t>(model->inputOrientation().size()));
+ ASSERT_EQ(inputLength, static_cast<size_t>(model->inputTilt().size()));
+
+ ASSERT_TRUE(model->invoke());
+
+ const size_t outputLength = model->outputLength();
+ ASSERT_EQ(outputLength, static_cast<size_t>(model->outputR().size()));
+ ASSERT_EQ(outputLength, static_cast<size_t>(model->outputPhi().size()));
+ ASSERT_EQ(outputLength, static_cast<size_t>(model->outputPressure().size()));
+}
+
+TEST(TfLiteMotionPredictorTest, ModelOutput) {
+ std::unique_ptr<TfLiteMotionPredictorModel> 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 0000000000..1cb7f7ba8c
--- /dev/null
+++ b/libs/input/tests/TouchResampling_test.cpp
@@ -0,0 +1,624 @@
+/*
+ * 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 <chrono>
+#include <vector>
+
+#include <attestation/HmacKeyManager.h>
+#include <gtest/gtest.h>
+#include <input/InputTransport.h>
+
+using namespace std::chrono_literals;
+
+namespace android {
+
+namespace {
+
+struct Pointer {
+ int32_t id;
+ float x;
+ float y;
+ ToolType toolType = ToolType::FINGER;
+ bool isResampled = false;
+};
+
+struct InputEventEntry {
+ std::chrono::nanoseconds eventTime;
+ std::vector<Pointer> pointers;
+ int32_t action;
+};
+
+} // namespace
+
+class TouchResamplingTest : public testing::Test {
+protected:
+ std::unique_ptr<InputPublisher> mPublisher;
+ std::unique_ptr<InputConsumer> mConsumer;
+ PreallocatedInputEventFactory mEventFactory;
+
+ uint32_t mSeq = 1;
+
+ void SetUp() override {
+ std::unique_ptr<InputChannel> serverChannel, clientChannel;
+ status_t result =
+ InputChannel::openInputChannelPair("channel name", serverChannel, clientChannel);
+ ASSERT_EQ(OK, result);
+
+ mPublisher = std::make_unique<InputPublisher>(std::move(serverChannel));
+ mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel),
+ /*enableTouchResampling=*/true);
+ }
+
+ status_t publishSimpleMotionEventWithCoords(int32_t action, nsecs_t eventTime,
+ const std::vector<PointerProperties>& properties,
+ const std::vector<PointerCoords>& coords);
+ void publishSimpleMotionEvent(int32_t action, nsecs_t eventTime,
+ const std::vector<Pointer>& pointers);
+ void publishInputEventEntries(const std::vector<InputEventEntry>& entries);
+ void consumeInputEventEntries(const std::vector<InputEventEntry>& entries,
+ std::chrono::nanoseconds frameTime);
+ void receiveResponseUntilSequence(uint32_t seq);
+};
+
+status_t TouchResamplingTest::publishSimpleMotionEventWithCoords(
+ int32_t action, nsecs_t eventTime, const std::vector<PointerProperties>& properties,
+ const std::vector<PointerCoords>& 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<Pointer>& pointers) {
+ std::vector<PointerProperties> properties;
+ std::vector<PointerCoords> coords;
+
+ for (const Pointer& pointer : pointers) {
+ properties.push_back({});
+ properties.back().clear();
+ properties.back().id = pointer.id;
+ properties.back().toolType = pointer.toolType;
+
+ 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<InputEventEntry>& 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<InputPublisher::ConsumerResponse> response =
+ mPublisher->receiveConsumerResponse();
+ ASSERT_TRUE(response.ok());
+ ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*response));
+ const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*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<InputEventEntry>& 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<MotionEvent*>(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<InputEventEntry> 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<InputEventEntry> 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);
+}
+
+/**
+ * Stylus pointer coordinates are not resampled, but an event is still generated for the batch with
+ * a resampled timestamp and should be marked as such.
+ */
+TEST_F(TouchResamplingTest, StylusCoordinatesNotResampledFor) {
+ std::chrono::nanoseconds frameTime;
+ std::vector<InputEventEntry> entries, expectedEntries;
+
+ // Initial ACTION_DOWN should be separate, because the first consume event will only return
+ // InputEvent with a single action.
+ entries = {
+ // id x y
+ {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 5ms;
+ expectedEntries = {
+ // id x y
+ {0ms, {{0, 10, 20, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_DOWN},
+ };
+ consumeInputEventEntries(expectedEntries, frameTime);
+
+ // Two ACTION_MOVE events 10 ms apart that move in X direction and stay still in Y
+ entries = {
+ // id x y
+ {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 35ms;
+ expectedEntries = {
+ // id x y
+ {10ms, {{0, 20, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ {20ms, {{0, 30, 30, .toolType = ToolType::STYLUS}}, AMOTION_EVENT_ACTION_MOVE},
+ // A resampled event is generated, but the stylus coordinates are not resampled.
+ {25ms,
+ {{0, 30, 30, .toolType = ToolType::STYLUS, .isResampled = true}},
+ 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<InputEventEntry> 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<InputEventEntry> 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<InputEventEntry> 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<InputEventEntry> 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, {{0, 120, 120}, {1, 600, 600}}, actionPointer0Up},
+ };
+ publishInputEventEntries(entries);
+ frameTime = 90ms;
+ expectedEntries = {
+ // id x y
+ {80ms, {{0, 120, 120}, {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 654b236bda..081a995a6f 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 a87b1873f0..f9ca28083d 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 <math.h>
#include <array>
#include <chrono>
-#include <math.h>
+#include <limits>
#include <android-base/stringprintf.h>
#include <attestation/HmacKeyManager.h>
@@ -26,7 +27,9 @@
#include <gui/constants.h>
#include <input/VelocityTracker.h>
-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 {
@@ -39,8 +42,8 @@ constexpr int32_t DEFAULT_POINTER_ID = 0; // pointer ID used for manually define
// here EV = expected value, tol = VELOCITY_TOLERANCE
constexpr float VELOCITY_TOLERANCE = 0.2;
-// estimate coefficients must be within 0.001% of the target value
-constexpr float COEFFICIENT_TOLERANCE = 0.00001;
+// quadratic velocity must be within 0.001% of the target value
+constexpr float QUADRATIC_VELOCITY_TOLERANCE = 0.00001;
// --- VelocityTrackerTest ---
class VelocityTrackerTest : public testing::Test { };
@@ -62,18 +65,23 @@ 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 checkCoefficient(float actual, float target) {
- EXPECT_NEAR_BY_FRACTION(actual, target, COEFFICIENT_TOLERANCE);
+static void checkVelocity(std::optional<float> Vactual, std::optional<float> 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";
+ }
}
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 +92,7 @@ struct Position {
}
};
-struct MotionEventEntry {
+struct PlanarMotionEventEntry {
std::chrono::nanoseconds eventTime;
std::vector<Position> positions;
};
@@ -133,15 +141,43 @@ static int32_t resolveAction(const std::vector<Position>& lastPositions,
return AMOTION_EVENT_ACTION_MOVE;
}
-static std::vector<MotionEvent> createMotionEventStream(
- const std::vector<MotionEventEntry>& motions) {
+static std::vector<MotionEvent> createAxisScrollMotionEventStream(
+ const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions) {
+ std::vector<MotionEvent> 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<MotionEvent> createTouchMotionEventStream(
+ const std::vector<PlanarMotionEventEntry>& motions) {
if (motions.empty()) {
ADD_FAILURE() << "Need at least 1 sample to create a MotionEvent. Received empty vector.";
}
std::vector<MotionEvent> 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 +185,11 @@ static std::vector<MotionEvent> 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 +201,26 @@ static std::vector<MotionEvent> 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 +229,240 @@ static std::vector<MotionEvent> createMotionEventStream(
return events;
}
-static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy,
- const std::vector<MotionEventEntry>& motions, int32_t axis,
- float targetVelocity) {
+static std::optional<float> computeVelocity(const VelocityTracker::Strategy strategy,
+ const std::vector<MotionEvent>& events, int32_t axis,
+ uint32_t pointerId = DEFAULT_POINTER_ID) {
VelocityTracker vt(strategy);
- float Vx, Vy;
- std::vector<MotionEvent> events = createMotionEventStream(motions);
- for (MotionEvent event : events) {
- vt.addMovement(&event);
+ for (const MotionEvent& event : events) {
+ vt.addMovement(event);
}
- vt.getVelocity(DEFAULT_POINTER_ID, &Vx, &Vy);
-
- 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";
- }
+ return vt.getVelocity(axis, pointerId);
}
-static void computeAndCheckQuadraticEstimate(const std::vector<MotionEventEntry>& motions,
- const std::array<float, 3>& coefficients) {
- VelocityTracker vt(VelocityTracker::Strategy::LSQ2);
- std::vector<MotionEvent> events = createMotionEventStream(motions);
- for (MotionEvent event : events) {
- vt.addMovement(&event);
- }
- VelocityTracker::Estimator estimator;
- EXPECT_TRUE(vt.getEstimator(0, &estimator));
- for (size_t i = 0; i< coefficients.size(); i++) {
- checkCoefficient(estimator.xCoeff[i], coefficients[i]);
- checkCoefficient(estimator.yCoeff[i], coefficients[i]);
+static std::optional<float> computePlanarVelocity(
+ const VelocityTracker::Strategy strategy,
+ const std::vector<PlanarMotionEventEntry>& motions, int32_t axis, uint32_t pointerId) {
+ std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+ return computeVelocity(strategy, events, axis, pointerId);
+}
+
+static void computeAndCheckVelocity(const VelocityTracker::Strategy strategy,
+ const std::vector<PlanarMotionEventEntry>& motions,
+ int32_t axis, std::optional<float> targetVelocity,
+ uint32_t pointerId = DEFAULT_POINTER_ID) {
+ checkVelocity(computePlanarVelocity(strategy, motions, axis, pointerId), targetVelocity);
+}
+
+static void computeAndCheckAxisScrollVelocity(
+ const VelocityTracker::Strategy strategy,
+ const std::vector<std::pair<std::chrono::nanoseconds, float>>& motions,
+ std::optional<float> targetVelocity) {
+ std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
+ checkVelocity(computeVelocity(strategy, events, AMOTION_EVENT_AXIS_SCROLL), targetVelocity);
+ // The strategy LSQ2 is not compatible with AXIS_SCROLL. In those situations, we should fall
+ // back to a strategy that supports differential axes.
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events,
+ AMOTION_EVENT_AXIS_SCROLL),
+ targetVelocity);
+}
+
+static void computeAndCheckQuadraticVelocity(const std::vector<PlanarMotionEventEntry>& motions,
+ float velocity) {
+ std::optional<float> velocityX =
+ computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
+ DEFAULT_POINTER_ID);
+ std::optional<float> velocityY =
+ computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y,
+ DEFAULT_POINTER_ID);
+ ASSERT_TRUE(velocityX);
+ ASSERT_TRUE(velocityY);
+
+ EXPECT_NEAR_BY_FRACTION(*velocityX, velocity, QUADRATIC_VELOCITY_TOLERANCE);
+ EXPECT_NEAR_BY_FRACTION(*velocityY, velocity, QUADRATIC_VELOCITY_TOLERANCE);
+}
+
+/*
+ *================== 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<PlanarMotionEventEntry> motions = {{10ms, {{2, 4}}},
+ {20ms, {{4, 12}}},
+ {30ms, {{6, 20}}},
+ {40ms, {{10, 30}}}};
+
+ EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X,
+ DEFAULT_POINTER_ID),
+ computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+ AMOTION_EVENT_AXIS_X, DEFAULT_POINTER_ID));
+ EXPECT_EQ(computePlanarVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_Y,
+ DEFAULT_POINTER_ID),
+ computePlanarVelocity(VelocityTracker::Strategy::DEFAULT, motions,
+ AMOTION_EVENT_AXIS_Y, DEFAULT_POINTER_ID));
+}
+
+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<PlanarMotionEventEntry> 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<PlanarMotionEventEntry> 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<PlanarMotionEventEntry> 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<MotionEvent> events = createTouchMotionEventStream(motions);
+ for (const MotionEvent& event : events) {
+ vt.addMovement(event);
+ }
+
+ float maxFloat = std::numeric_limits<float>::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));
+
+ 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<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{0ms, {{273, 0}}},
{12585us, {{293, 0}}},
{14730us, {{293, 0}}},
@@ -252,7 +474,7 @@ TEST_F(VelocityTrackerTest, ThreePointsPositiveVelocityTest) {
TEST_F(VelocityTrackerTest, ThreePointsZeroVelocityTest) {
// Same coordinate is reported 3 times in a row
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{0ms, {{293, 0}}},
{6132us, {{293, 0}}},
{11283us, {{293, 0}}},
@@ -264,13 +486,96 @@ TEST_F(VelocityTrackerTest, ThreePointsZeroVelocityTest) {
TEST_F(VelocityTrackerTest, ThreePointsLinearVelocityTest) {
// Fixed velocity at 5 points per 10 milliseconds
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> 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);
computeAndCheckVelocity(VelocityTracker::Strategy::LSQ2, motions, AMOTION_EVENT_AXIS_X, 500);
}
+/**
+ * When the stream is terminated with ACTION_CANCEL, the resulting velocity should be 0.
+ */
+TEST_F(VelocityTrackerTest, ActionCancelResultsInZeroVelocity) {
+ std::vector<PlanarMotionEventEntry> motions = {
+ {0ms, {{0, 0}}}, // DOWN
+ {10ms, {{5, 10}}}, // MOVE
+ {20ms, {{10, 20}}}, // MOVE
+ {20ms, {{10, 20}}}, // ACTION_UP
+ };
+ std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+ // By default, `createTouchMotionEventStream` produces an event stream that terminates with
+ // ACTION_UP. We need to manually change it to ACTION_CANCEL.
+ MotionEvent& lastEvent = events.back();
+ lastEvent.setAction(AMOTION_EVENT_ACTION_CANCEL);
+ lastEvent.setFlags(lastEvent.getFlags() | AMOTION_EVENT_FLAG_CANCELED);
+ const int32_t pointerId = lastEvent.getPointerId(0);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+}
+
+/**
+ * When the stream is terminated with ACTION_CANCEL, the resulting velocity should be 0.
+ */
+TEST_F(VelocityTrackerTest, ActionPointerCancelResultsInZeroVelocityForThatPointer) {
+ std::vector<PlanarMotionEventEntry> motions = {
+ {0ms, {{0, 5}, {NAN, NAN}}}, // DOWN
+ {0ms, {{0, 5}, {10, 15}}}, // POINTER_DOWN
+ {10ms, {{5, 10}, {15, 20}}}, // MOVE
+ {20ms, {{10, 15}, {20, 25}}}, // MOVE
+ {30ms, {{10, 15}, {20, 25}}}, // POINTER_UP
+ {30ms, {{10, 15}, {NAN, NAN}}}, // UP
+ };
+ std::vector<MotionEvent> events = createTouchMotionEventStream(motions);
+ // Cancel the lifting pointer of the ACTION_POINTER_UP event
+ MotionEvent& pointerUpEvent = events.rbegin()[1];
+ pointerUpEvent.setFlags(pointerUpEvent.getFlags() | AMOTION_EVENT_FLAG_CANCELED);
+ const int32_t pointerId = pointerUpEvent.getPointerId(pointerUpEvent.getActionIndex());
+ // Double check the stream
+ ASSERT_EQ(1, pointerId);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_POINTER_UP, pointerUpEvent.getActionMasked());
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, events.back().getActionMasked());
+
+ // Ensure the velocity of the lifting pointer is zero
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+ pointerId),
+ /*targetVelocity*/ std::nullopt);
+
+ // The remaining pointer should have the correct velocity.
+ const int32_t remainingPointerId = events.back().getPointerId(0);
+ ASSERT_EQ(0, remainingPointerId);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_X,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::IMPULSE, events, AMOTION_EVENT_AXIS_Y,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_X,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+ checkVelocity(computeVelocity(VelocityTracker::Strategy::LSQ2, events, AMOTION_EVENT_AXIS_Y,
+ remainingPointerId),
+ /*targetVelocity*/ 500);
+}
/**
* ================== VelocityTracker tests generated by recording real events =====================
@@ -288,7 +593,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<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{271, 96}} },
{ 16071042ns, {{269.786346, 106.922775}} },
{ 35648403ns, {{267.983063, 156.660034}} },
@@ -323,7 +628,7 @@ TEST_F(VelocityTrackerTest, SwordfishFlingDown) {
TEST_F(VelocityTrackerTest, SailfishFlingUpSlow1) {
// Sailfish - fling up - slow - 1
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235089067457000ns, {{528.00, 983.00}} },
{ 235089084684000ns, {{527.00, 981.00}} },
{ 235089093349000ns, {{527.00, 977.00}} },
@@ -355,7 +660,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow1) {
TEST_F(VelocityTrackerTest, SailfishFlingUpSlow2) {
// Sailfish - fling up - slow - 2
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235110560704000ns, {{522.00, 1107.00}} },
{ 235110575764000ns, {{522.00, 1107.00}} },
{ 235110584385000ns, {{522.00, 1107.00}} },
@@ -384,7 +689,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow2) {
TEST_F(VelocityTrackerTest, SailfishFlingUpSlow3) {
// Sailfish - fling up - slow - 3
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 792536237000ns, {{580.00, 1317.00}} },
{ 792541538987ns, {{580.63, 1311.94}} },
{ 792544613000ns, {{581.00, 1309.00}} },
@@ -418,7 +723,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpSlow3) {
TEST_F(VelocityTrackerTest, SailfishFlingUpFaster1) {
// Sailfish - fling up - faster - 1
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235160420675000ns, {{610.00, 1042.00}} },
{ 235160428220000ns, {{609.00, 1026.00}} },
{ 235160436544000ns, {{609.00, 1024.00}} },
@@ -452,7 +757,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster1) {
TEST_F(VelocityTrackerTest, SailfishFlingUpFaster2) {
// Sailfish - fling up - faster - 2
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 847153808000ns, {{576.00, 1264.00}} },
{ 847171174000ns, {{576.00, 1262.00}} },
{ 847179640000ns, {{576.00, 1257.00}} },
@@ -478,7 +783,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster2) {
TEST_F(VelocityTrackerTest, SailfishFlingUpFaster3) {
// Sailfish - fling up - faster - 3
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235200532789000ns, {{507.00, 1084.00}} },
{ 235200549221000ns, {{507.00, 1083.00}} },
{ 235200557841000ns, {{507.00, 1081.00}} },
@@ -504,7 +809,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFaster3) {
TEST_F(VelocityTrackerTest, SailfishFlingUpFast1) {
// Sailfish - fling up - fast - 1
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 920922149000ns, {{561.00, 1412.00}} },
{ 920930185000ns, {{559.00, 1377.00}} },
{ 920930262463ns, {{558.98, 1376.66}} },
@@ -533,7 +838,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast1) {
TEST_F(VelocityTrackerTest, SailfishFlingUpFast2) {
// Sailfish - fling up - fast - 2
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235247153233000ns, {{518.00, 1168.00}} },
{ 235247170452000ns, {{517.00, 1167.00}} },
{ 235247178908000ns, {{515.00, 1159.00}} },
@@ -556,7 +861,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast2) {
TEST_F(VelocityTrackerTest, SailfishFlingUpFast3) {
// Sailfish - fling up - fast - 3
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235302568736000ns, {{529.00, 1167.00}} },
{ 235302576644000ns, {{523.00, 1140.00}} },
{ 235302579395063ns, {{520.91, 1130.61}} },
@@ -577,7 +882,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingUpFast3) {
TEST_F(VelocityTrackerTest, SailfishFlingDownSlow1) {
// Sailfish - fling down - slow - 1
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235655749552755ns, {{582.00, 432.49}} },
{ 235655750638000ns, {{582.00, 433.00}} },
{ 235655758865000ns, {{582.00, 440.00}} },
@@ -611,7 +916,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow1) {
TEST_F(VelocityTrackerTest, SailfishFlingDownSlow2) {
// Sailfish - fling down - slow - 2
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235671152083370ns, {{485.24, 558.28}} },
{ 235671154126000ns, {{485.00, 559.00}} },
{ 235671162497000ns, {{484.00, 566.00}} },
@@ -645,7 +950,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow2) {
TEST_F(VelocityTrackerTest, SailfishFlingDownSlow3) {
// Sailfish - fling down - slow - 3
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 170983201000ns, {{557.00, 533.00}} },
{ 171000668000ns, {{556.00, 534.00}} },
{ 171007359750ns, {{554.73, 535.27}} },
@@ -672,7 +977,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownSlow3) {
TEST_F(VelocityTrackerTest, SailfishFlingDownFaster1) {
// Sailfish - fling down - faster - 1
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235695280333000ns, {{558.00, 451.00}} },
{ 235695283971237ns, {{558.43, 454.45}} },
{ 235695289038000ns, {{559.00, 462.00}} },
@@ -702,7 +1007,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster1) {
TEST_F(VelocityTrackerTest, SailfishFlingDownFaster2) {
// Sailfish - fling down - faster - 2
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235709624766000ns, {{535.00, 579.00}} },
{ 235709642256000ns, {{534.00, 580.00}} },
{ 235709643350278ns, {{533.94, 580.06}} },
@@ -733,7 +1038,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster2) {
TEST_F(VelocityTrackerTest, SailfishFlingDownFaster3) {
// Sailfish - fling down - faster - 3
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235727628927000ns, {{540.00, 440.00}} },
{ 235727636810000ns, {{537.00, 454.00}} },
{ 235727646176000ns, {{536.00, 454.00}} },
@@ -762,7 +1067,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFaster3) {
TEST_F(VelocityTrackerTest, SailfishFlingDownFast1) {
// Sailfish - fling down - fast - 1
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235762352849000ns, {{467.00, 286.00}} },
{ 235762360250000ns, {{443.00, 344.00}} },
{ 235762362787412ns, {{434.77, 363.89}} },
@@ -783,7 +1088,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast1) {
TEST_F(VelocityTrackerTest, SailfishFlingDownFast2) {
// Sailfish - fling down - fast - 2
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 235772487188000ns, {{576.00, 204.00}} },
{ 235772495159000ns, {{553.00, 236.00}} },
{ 235772503568000ns, {{551.00, 240.00}} },
@@ -804,7 +1109,7 @@ TEST_F(VelocityTrackerTest, SailfishFlingDownFast2) {
TEST_F(VelocityTrackerTest, SailfishFlingDownFast3) {
// Sailfish - fling down - fast - 3
- std::vector<MotionEventEntry> motions = {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 507650295000ns, {{628.00, 233.00}} },
{ 507658234000ns, {{605.00, 269.00}} },
{ 507666784000ns, {{601.00, 274.00}} },
@@ -830,12 +1135,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<MotionEventEntry> motions = {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_ThreeFingerTap) {
+ std::vector<PlanarMotionEventEntry> 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,19 +1149,85 @@ 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<PlanarMotionEventEntry> 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<PlanarMotionEventEntry> 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<PlanarMotionEventEntry> 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);
}
/**
* ================== Tests for least squares fitting ==============================================
*
* Special care must be taken when constructing tests for LeastSquaresVelocityTrackerStrategy
- * getEstimator function. In particular:
+ * getVelocity function. In particular:
* - inside the function, time gets converted from nanoseconds to seconds
* before being used in the fit.
* - any values that are older than 100 ms are being discarded.
@@ -877,8 +1248,8 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_ThreeFi
* The coefficients are (0, 0, 1).
* 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<MotionEventEntry> motions = {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Constant) {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{1, 1}} }, // 0 s
{ 1ms, {{1, 1}} }, // 0.001 s
{ 2ms, {{1, 1}} }, // 0.002 s
@@ -889,14 +1260,14 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Constan
// -0.002, 1
// -0.001, 1
// -0.ms, 1
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({1, 0, 0}));
+ computeAndCheckQuadraticVelocity(motions, 0);
}
/*
* Straight line y = x :: the constant and quadratic coefficients are zero.
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear) {
- std::vector<MotionEventEntry> motions = {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Linear) {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{-2, -2}} },
{ 1ms, {{-1, -1}} },
{ 2ms, {{-0, -0}} },
@@ -907,14 +1278,14 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Linear)
// -0.002, -2
// -0.001, -1
// -0.000, 0
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 1E3, 0}));
+ computeAndCheckQuadraticVelocity(motions, 1E3);
}
/*
* Parabola
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic) {
- std::vector<MotionEventEntry> motions = {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic) {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{1, 1}} },
{ 1ms, {{4, 4}} },
{ 2ms, {{8, 8}} },
@@ -925,14 +1296,14 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol
// -0.002, 1
// -0.001, 4
// -0.000, 8
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({8, 4.5E3, 0.5E6}));
+ computeAndCheckQuadraticVelocity(motions, 4.5E3);
}
/*
* Parabola
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic2) {
- std::vector<MotionEventEntry> motions = {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic2) {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{1, 1}} },
{ 1ms, {{4, 4}} },
{ 2ms, {{9, 9}} },
@@ -943,14 +1314,14 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol
// -0.002, 1
// -0.001, 4
// -0.000, 9
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({9, 6E3, 1E6}));
+ computeAndCheckQuadraticVelocity(motions, 6E3);
}
/*
* Parabola :: y = x^2 :: the constant and linear coefficients are zero.
*/
-TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabolic3) {
- std::vector<MotionEventEntry> motions = {
+TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategy_Parabolic3) {
+ std::vector<PlanarMotionEventEntry> motions = {
{ 0ms, {{4, 4}} },
{ 1ms, {{1, 1}} },
{ 2ms, {{0, 0}} },
@@ -961,7 +1332,118 @@ TEST_F(VelocityTrackerTest, LeastSquaresVelocityTrackerStrategyEstimator_Parabol
// -0.002, 4
// -0.001, 1
// -0.000, 0
- computeAndCheckQuadraticEstimate(motions, std::array<float, 3>({0, 0E3, 1E6}));
+ computeAndCheckQuadraticVelocity(motions, 0E3);
+}
+
+// Recorded by hand on sailfish, but only the diffs are taken to test cumulative axis velocity.
+TEST_F(VelocityTrackerTest, AxisScrollVelocity) {
+ std::vector<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> motions = {
+ {10ms, 20},
+ {20ms, 25},
+ {30ms, 50},
+ {40ms, 100},
+ };
+
+ std::vector<MotionEvent> events = createAxisScrollMotionEventStream(motions);
+ EXPECT_EQ(computeVelocity(VelocityTracker::Strategy::IMPULSE, events,
+ AMOTION_EVENT_AXIS_SCROLL),
+ computeVelocity(VelocityTracker::Strategy::DEFAULT, events,
+ AMOTION_EVENT_AXIS_SCROLL));
+}
+
+TEST_F(VelocityTrackerTest, AxisScrollVelocity_SimilarDifferentialValues) {
+ std::vector<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> motions = {{1ms, 5}, {2ms, 10}};
+
+ computeAndCheckAxisScrollVelocity(VelocityTracker::Strategy::IMPULSE, motions, {10000});
+}
+
+TEST_F(VelocityTrackerTest, AxisScrollVelocity_ConstantVelocity) {
+ std::vector<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> 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<std::pair<std::chrono::nanoseconds, float>> 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 f2b59ea9ab..277d74dd1c 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 0000000000..689738077c
--- /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 0000000000..293c0d2af4
--- /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/input/tests/data/hid_fallback_mapping.kl b/libs/input/tests/data/hid_fallback_mapping.kl
new file mode 100644
index 0000000000..b4ca9ef355
--- /dev/null
+++ b/libs/input/tests/data/hid_fallback_mapping.kl
@@ -0,0 +1,32 @@
+# 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.
+
+#
+# test key layout file for InputDeviceKeyMapTest#HidUsageCodesUseFallbackMapping
+#
+
+# Keys defined by HID usages without fallback mapping flag
+key usage 0x0c0067 WINDOW
+key usage 0x0c006F BRIGHTNESS_UP
+key usage 0x0c0070 BRIGHTNESS_DOWN
+key usage 0x0c0079 KEYBOARD_BACKLIGHT_UP
+key usage 0x0c007A KEYBOARD_BACKLIGHT_DOWN
+
+# Keys defined by HID usages with fallback mapping flag
+key usage 0x0c007C KEYBOARD_BACKLIGHT_TOGGLE FALLBACK_USAGE_MAPPING
+key usage 0x0c0173 MEDIA_AUDIO_TRACK FALLBACK_USAGE_MAPPING
+key usage 0x0c019C PROFILE_SWITCH FALLBACK_USAGE_MAPPING
+key usage 0x0c01A2 ALL_APPS FALLBACK_USAGE_MAPPING
+key usage 0x0d0044 STYLUS_BUTTON_PRIMARY FALLBACK_USAGE_MAPPING
+key usage 0x0d005a STYLUS_BUTTON_SECONDARY FALLBACK_USAGE_MAPPING \ No newline at end of file
diff --git a/libs/math/include/math/mat4.h b/libs/math/include/math/mat4.h
index 6119ba7f68..c630d972b2 100644
--- a/libs/math/include/math/mat4.h
+++ b/libs/math/include/math/mat4.h
@@ -34,6 +34,14 @@
#define CONSTEXPR
#endif
+#ifdef _WIN32
+// windows.h contains obsolete defines of 'near' and 'far' for systems using
+// legacy 16 bit pointers. Undefine them to avoid conflicting with the usage of
+// 'near' and 'far' in this file.
+#undef near
+#undef far
+#endif
+
namespace android {
// -------------------------------------------------------------------------------------
namespace details {
diff --git a/libs/nativedisplay/AChoreographer.cpp b/libs/nativedisplay/AChoreographer.cpp
index 8240b08085..8f005a56f8 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 <android-base/thread_annotations.h>
-#include <gui/DisplayEventDispatcher.h>
-#include <gui/ISurfaceComposer.h>
+#include <android/gui/ISurfaceComposer.h>
+#include <gui/Choreographer.h>
#include <jni.h>
#include <private/android/choreographer.h>
#include <utils/Looper.h>
@@ -31,444 +28,9 @@
#include <queue>
#include <thread>
-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<Choreographer*> ptrs GUARDED_BY(lock);
- std::map<AVsyncId, int64_t> startTimes GUARDED_BY(lock);
- bool registeredToDisplayManager GUARDED_BY(lock) = false;
-
- std::atomic<nsecs_t> mLastKnownVsync = -1;
-} gChoreographers;
-
-class Choreographer : public DisplayEventDispatcher, public MessageHandler {
-public:
- explicit Choreographer(const sp<Looper>& 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<FrameRateOverride> overrides) override;
-
- void scheduleCallbacks();
-
- ChoreographerFrameCallbackDataImpl createFrameCallbackData(nsecs_t timestamp) const;
- void registerStartTime() const;
-
- std::mutex mLock;
- // Protected by mLock
- std::priority_queue<FrameCallback> mFrameCallbacks;
- std::vector<RefreshRateCallback> mRefreshRateCallbacks;
-
- nsecs_t mLatestVsyncPeriod = -1;
- VsyncEventData mLastVsyncEventData;
- bool mInCallback = false;
-
- const sp<Looper> 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 = 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>& looper)
- : DisplayEventDispatcher(looper, ISurfaceComposer::VsyncSource::eVsyncSourceApp),
- mLooper(looper),
- mThreadId(std::this_thread::get_id()) {
- std::lock_guard<std::mutex> _l(gChoreographers.lock);
- gChoreographers.ptrs.push_back(this);
-}
-
-Choreographer::~Choreographer() {
- std::lock_guard<std::mutex> _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<std::mutex> _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<std::mutex> _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<std::mutex> _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<int64_t>(this));
- env->DeleteLocalRef(dmg);
- {
- std::lock_guard<std::mutex> _l2(gChoreographers.lock);
- gChoreographers.registeredToDisplayManager = true;
- }
- }
- } else {
- scheduleLatestConfigRequest();
- }
-}
-
-void Choreographer::unregisterRefreshRateCallback(AChoreographer_refreshRateCallback cb,
- void* data) {
- std::lock_guard<std::mutex> _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<std::mutex> _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<RefreshRateCallback> callbacks{};
- const nsecs_t pendingPeriod = gChoreographers.mLastKnownVsync.load();
- const nsecs_t lastPeriod = mLatestVsyncPeriod;
- if (pendingPeriod > 0) {
- mLatestVsyncPeriod = pendingPeriod;
- }
- {
- std::lock_guard<std::mutex> _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<FrameCallback> callbacks{};
- {
- std::lock_guard<std::mutex> _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<const AChoreographerFrameCallbackData*>(
- &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<FrameRateOverride>) {
- 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<std::mutex> _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<jclass>(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 76b85d6002..e3be3bc8f8 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<IBinder> getToken(ADisplay* display) {
- DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(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<DisplayConfigImpl> modesPerDisplay[size];
+ ui::DisplayConnectionType displayConnectionTypes[size];
int numModes = 0;
for (int i = 0; i < size; ++i) {
- const sp<IBinder> 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;
}
@@ -163,13 +155,11 @@ int ADisplay_acquirePhysicalDisplays(ADisplay*** outDisplays) {
const ui::DisplayMode& mode = modes[j];
modesPerDisplay[i].emplace_back(
DisplayConfigImpl{static_cast<size_t>(mode.id), mode.resolution.getWidth(),
- mode.resolution.getHeight(), mode.refreshRate,
+ mode.resolution.getHeight(), mode.peakRefreshRate,
mode.sfVsyncOffset, mode.appVsyncOffset});
}
}
- const std::optional<PhysicalDisplayId> 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<DisplayConfigImpl>& 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<IBinder> token = getToken(display);
ui::DynamicDisplayInfo info;
- if (const auto status = SurfaceComposerClient::getDynamicDisplayInfo(token, &info);
+ DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(display);
+
+ if (const auto status =
+ SurfaceComposerClient::getDynamicDisplayInfoFromId(impl->id.value, &info);
status != OK) {
return status;
}
- DisplayImpl* impl = reinterpret_cast<DisplayImpl*>(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 4659b96b11..342f5de337 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",
@@ -72,6 +73,8 @@ cc_library_shared {
"libGLESv2",
],
+ static_libs: ["libguiflags"],
+
export_header_lib_headers: ["jni_headers"],
header_libs: [
diff --git a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
index 0f119f3fc1..099f47dbe1 100644
--- a/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
+++ b/libs/nativedisplay/include/surfacetexture/SurfaceTexture.h
@@ -19,6 +19,7 @@
#include <android/hardware_buffer.h>
#include <gui/BufferQueueDefs.h>
#include <gui/ConsumerBase.h>
+
#include <gui/IGraphicBufferProducer.h>
#include <sys/cdefs.h>
#include <system/graphics.h>
@@ -290,6 +291,20 @@ public:
*/
void releaseConsumerOwnership();
+ /**
+ * Interface for SurfaceTexture callback(s).
+ */
+ struct SurfaceTextureListener : public RefBase {
+ virtual void onFrameAvailable(const BufferItem& item) = 0;
+ virtual void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) = 0;
+ };
+
+ /**
+ * setSurfaceTextureListener registers a SurfaceTextureListener.
+ */
+ void setSurfaceTextureListener(const sp<SurfaceTextureListener>&);
+
protected:
/**
* abandonLocked overrides the ConsumerBase method to clear
@@ -335,6 +350,14 @@ protected:
void computeCurrentTransformMatrixLocked();
/**
+ * onSetFrameRate Notifies the consumer of a setFrameRate call from the producer side.
+ */
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+ void onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) override;
+#endif
+
+ /**
* The default consumer usage flags that SurfaceTexture always sets on its
* BufferQueue instance; these will be OR:d with any additional flags passed
* from the SurfaceTexture user. In particular, SurfaceTexture will always
@@ -465,8 +488,30 @@ protected:
*/
ImageConsumer mImageConsumer;
+ /**
+ * mSurfaceTextureListener holds the registered SurfaceTextureListener.
+ * Note that SurfaceTexture holds the lister with an sp<>, which means that the listener
+ * must only hold a wp<> to SurfaceTexture and not an sp<>.
+ */
+ sp<SurfaceTextureListener> mSurfaceTextureListener;
+
friend class ImageConsumer;
friend class EGLConsumer;
+
+private:
+ // Proxy listener to avoid having SurfaceTexture directly implement FrameAvailableListener as it
+ // is extending ConsumerBase which also implements FrameAvailableListener.
+ class FrameAvailableListenerProxy : public ConsumerBase::FrameAvailableListener {
+ public:
+ FrameAvailableListenerProxy(const wp<SurfaceTextureListener>& listener)
+ : mSurfaceTextureListener(listener) {}
+
+ private:
+ void onFrameAvailable(const BufferItem& item) override;
+
+ const wp<SurfaceTextureListener> mSurfaceTextureListener;
+ };
+ sp<FrameAvailableListenerProxy> mFrameAvailableListenerProxy;
};
// ----------------------------------------------------------------------------
diff --git a/libs/nativedisplay/libnativedisplay.map.txt b/libs/nativedisplay/libnativedisplay.map.txt
index 969d9379f0..9172d5ed13 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/nativedisplay/surfacetexture/EGLConsumer.cpp b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
index 0128859ca6..275b7a4888 100644
--- a/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/EGLConsumer.cpp
@@ -38,10 +38,10 @@
namespace android {
// Macros for including the SurfaceTexture name in log messages
-#define EGC_LOGV(x, ...) ALOGV("[%s] " x, st.mName.string(), ##__VA_ARGS__)
-#define EGC_LOGD(x, ...) ALOGD("[%s] " x, st.mName.string(), ##__VA_ARGS__)
-#define EGC_LOGW(x, ...) ALOGW("[%s] " x, st.mName.string(), ##__VA_ARGS__)
-#define EGC_LOGE(x, ...) ALOGE("[%s] " x, st.mName.string(), ##__VA_ARGS__)
+#define EGC_LOGV(x, ...) ALOGV("[%s] " x, st.mName.c_str(), ##__VA_ARGS__)
+#define EGC_LOGD(x, ...) ALOGD("[%s] " x, st.mName.c_str(), ##__VA_ARGS__)
+#define EGC_LOGW(x, ...) ALOGW("[%s] " x, st.mName.c_str(), ##__VA_ARGS__)
+#define EGC_LOGE(x, ...) ALOGE("[%s] " x, st.mName.c_str(), ##__VA_ARGS__)
static const struct {
uint32_t width, height;
diff --git a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
index cf16739e89..32b229d77c 100644
--- a/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
+++ b/libs/nativedisplay/surfacetexture/ImageConsumer.cpp
@@ -19,7 +19,7 @@
#include <surfacetexture/SurfaceTexture.h>
// Macro for including the SurfaceTexture name in log messages
-#define IMG_LOGE(x, ...) ALOGE("[%s] " x, st.mName.string(), ##__VA_ARGS__)
+#define IMG_LOGE(x, ...) ALOGE("[%s] " x, st.mName.c_str(), ##__VA_ARGS__)
namespace android {
diff --git a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
index d3d4cbafdf..3a09204878 100644
--- a/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
+++ b/libs/nativedisplay/surfacetexture/SurfaceTexture.cpp
@@ -23,13 +23,15 @@
#include <system/window.h>
#include <utils/Trace.h>
+#include <com_android_graphics_libgui_flags.h>
+
namespace android {
// Macros for including the SurfaceTexture name in log messages
-#define SFT_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define SFT_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define SFT_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define SFT_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
+#define SFT_LOGV(x, ...) ALOGV("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define SFT_LOGD(x, ...) ALOGD("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define SFT_LOGW(x, ...) ALOGW("[%s] " x, mName.c_str(), ##__VA_ARGS__)
+#define SFT_LOGE(x, ...) ALOGE("[%s] " x, mName.c_str(), ##__VA_ARGS__)
static const mat4 mtxIdentity;
@@ -491,4 +493,42 @@ sp<GraphicBuffer> SurfaceTexture::dequeueBuffer(int* outSlotid, android_dataspac
return buffer;
}
+void SurfaceTexture::setSurfaceTextureListener(
+ const sp<android::SurfaceTexture::SurfaceTextureListener>& listener) {
+ SFT_LOGV("setSurfaceTextureListener");
+
+ Mutex::Autolock _l(mMutex);
+ mSurfaceTextureListener = listener;
+ if (mSurfaceTextureListener != nullptr) {
+ mFrameAvailableListenerProxy =
+ sp<FrameAvailableListenerProxy>::make(mSurfaceTextureListener);
+ setFrameAvailableListener(mFrameAvailableListenerProxy);
+ } else {
+ mFrameAvailableListenerProxy.clear();
+ }
+}
+
+void SurfaceTexture::FrameAvailableListenerProxy::onFrameAvailable(const BufferItem& item) {
+ const auto listener = mSurfaceTextureListener.promote();
+ if (listener) {
+ listener->onFrameAvailable(item);
+ }
+}
+
+#if COM_ANDROID_GRAPHICS_LIBGUI_FLAGS(BQ_SETFRAMERATE)
+void SurfaceTexture::onSetFrameRate(float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ SFT_LOGV("onSetFrameRate: %.2f", frameRate);
+
+ auto listener = [&] {
+ Mutex::Autolock _l(mMutex);
+ return mSurfaceTextureListener;
+ }();
+
+ if (listener) {
+ listener->onSetFrameRate(frameRate, compatibility, changeFrameRateStrategy);
+ }
+}
+#endif
+
} // namespace android
diff --git a/libs/nativewindow/AHardwareBuffer.cpp b/libs/nativewindow/AHardwareBuffer.cpp
index b2e069c4da..e7b2195056 100644
--- a/libs/nativewindow/AHardwareBuffer.cpp
+++ b/libs/nativewindow/AHardwareBuffer.cpp
@@ -18,6 +18,7 @@
#include <android/hardware_buffer.h>
#include <android/hardware_buffer_aidl.h>
+#include <android/binder_libbinder.h>
#include <vndk/hardware_buffer.h>
#include <errno.h>
@@ -34,14 +35,85 @@
#include <android/hardware/graphics/common/1.1/types.h>
#include <aidl/android/hardware/graphics/common/PixelFormat.h>
-// TODO: Better way to handle this
-#include "../binder/ndk/parcel_internal.h"
-
static constexpr int kFdBufferSize = 128 * sizeof(int); // 128 ints
using namespace android;
// ----------------------------------------------------------------------------
+// Validate hardware_buffer.h and PixelFormat.aidl agree
+// ----------------------------------------------------------------------------
+
+static_assert(HAL_PIXEL_FORMAT_RGBA_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBX_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGB_565 == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGB_888 == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBA_FP16 == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RGBA_1010102 == AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_BLOB == AHARDWAREBUFFER_FORMAT_BLOB,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_16 == AHARDWAREBUFFER_FORMAT_D16_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_24 == AHARDWAREBUFFER_FORMAT_D24_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_24_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_32F == AHARDWAREBUFFER_FORMAT_D32_FLOAT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_DEPTH_32F_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_STENCIL_8 == AHARDWAREBUFFER_FORMAT_S8_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_BGRA_8888 == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YV12 == AHARDWAREBUFFER_FORMAT_YV12,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_Y8 == AHARDWAREBUFFER_FORMAT_Y8,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_Y16 == AHARDWAREBUFFER_FORMAT_Y16,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW16 == AHARDWAREBUFFER_FORMAT_RAW16,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW10 == AHARDWAREBUFFER_FORMAT_RAW10,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW12 == AHARDWAREBUFFER_FORMAT_RAW12,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_RAW_OPAQUE == AHARDWAREBUFFER_FORMAT_RAW_OPAQUE,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED ==
+ AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_420_888 == AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_422_SP == AHARDWAREBUFFER_FORMAT_YCbCr_422_SP,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCRCB_420_SP == AHARDWAREBUFFER_FORMAT_YCrCb_420_SP,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_422_I == AHARDWAREBUFFER_FORMAT_YCbCr_422_I,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
+ AHARDWAREBUFFER_FORMAT_R8_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
+ AHARDWAREBUFFER_FORMAT_R16_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+ static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
+ AHARDWAREBUFFER_FORMAT_R16G16_UINT,
+ "HAL and AHardwareBuffer pixel format don't match");
+static_assert(
+ static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
+ AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
+ "HAL and AHardwareBuffer pixel format don't match");
+
+// ----------------------------------------------------------------------------
// Public functions
// ----------------------------------------------------------------------------
@@ -229,11 +301,14 @@ int AHardwareBuffer_lockPlanes(AHardwareBuffer* buffer, uint64_t usage,
}
return result;
} else {
- const uint32_t pixelStride = AHardwareBuffer_bytesPerPixel(format);
+ int32_t bytesPerPixel;
+ int32_t bytesPerStride;
+ int result = gBuffer->lockAsync(usage, usage, bounds, &outPlanes->planes[0].data, fence,
+ &bytesPerPixel, &bytesPerStride);
outPlanes->planeCount = 1;
- outPlanes->planes[0].pixelStride = pixelStride;
- outPlanes->planes[0].rowStride = gBuffer->getStride() * pixelStride;
- return gBuffer->lockAsync(usage, usage, bounds, &outPlanes->planes[0].data, fence);
+ outPlanes->planes[0].pixelStride = bytesPerPixel;
+ outPlanes->planes[0].rowStride = bytesPerStride;
+ return result;
}
}
@@ -362,12 +437,12 @@ int AHardwareBuffer_recvHandleFromUnixSocket(int socketFd, AHardwareBuffer** out
return INVALID_OPERATION;
}
- GraphicBuffer* gBuffer = new GraphicBuffer();
+ sp<GraphicBuffer> 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);
@@ -421,7 +496,7 @@ binder_status_t AHardwareBuffer_readFromParcel(const AParcel* _Nonnull parcel,
AHardwareBuffer* _Nullable* _Nonnull outBuffer) {
if (!parcel || !outBuffer) return STATUS_BAD_VALUE;
auto buffer = sp<GraphicBuffer>::make();
- status_t status = parcel->get()->read(*buffer);
+ status_t status = AParcel_viewPlatformParcel(parcel)->read(*buffer);
if (status != STATUS_OK) return status;
*outBuffer = AHardwareBuffer_from_GraphicBuffer(buffer.get());
AHardwareBuffer_acquire(*outBuffer);
@@ -433,7 +508,7 @@ binder_status_t AHardwareBuffer_writeToParcel(const AHardwareBuffer* _Nonnull bu
const GraphicBuffer* gb = AHardwareBuffer_to_GraphicBuffer(buffer);
if (!gb) return STATUS_BAD_VALUE;
if (!parcel) return STATUS_BAD_VALUE;
- return parcel->get()->write(*gb);
+ return AParcel_viewPlatformParcel(parcel)->write(*gb);
}
// ----------------------------------------------------------------------------
@@ -489,12 +564,6 @@ bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool l
return false;
}
- if (!AHardwareBuffer_isValidPixelFormat(desc->format)) {
- ALOGE_IF(log, "Invalid AHardwareBuffer pixel format %u (%#x))",
- desc->format, desc->format);
- return false;
- }
-
if (desc->rfu0 != 0 || desc->rfu1 != 0) {
ALOGE_IF(log, "AHardwareBuffer_Desc::rfu fields must be 0");
return false;
@@ -559,114 +628,6 @@ bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool l
return true;
}
-bool AHardwareBuffer_isValidPixelFormat(uint32_t format) {
- static_assert(HAL_PIXEL_FORMAT_RGBA_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGBX_8888 == AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGB_565 == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGB_888 == AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGBA_FP16 == AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RGBA_1010102 == AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_BLOB == AHARDWAREBUFFER_FORMAT_BLOB,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_16 == AHARDWAREBUFFER_FORMAT_D16_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_24 == AHARDWAREBUFFER_FORMAT_D24_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_24_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_32F == AHARDWAREBUFFER_FORMAT_D32_FLOAT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_DEPTH_32F_STENCIL_8 == AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_STENCIL_8 == AHARDWAREBUFFER_FORMAT_S8_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_BGRA_8888 == AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YV12 == AHARDWAREBUFFER_FORMAT_YV12,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_Y8 == AHARDWAREBUFFER_FORMAT_Y8,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_Y16 == AHARDWAREBUFFER_FORMAT_Y16,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW16 == AHARDWAREBUFFER_FORMAT_RAW16,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW10 == AHARDWAREBUFFER_FORMAT_RAW10,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW12 == AHARDWAREBUFFER_FORMAT_RAW12,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_RAW_OPAQUE == AHARDWAREBUFFER_FORMAT_RAW_OPAQUE,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED == AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_420_888 == AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_422_SP == AHARDWAREBUFFER_FORMAT_YCbCr_422_SP,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCRCB_420_SP == AHARDWAREBUFFER_FORMAT_YCrCb_420_SP,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_422_I == AHARDWAREBUFFER_FORMAT_YCbCr_422_I,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(HAL_PIXEL_FORMAT_YCBCR_P010 == AHARDWAREBUFFER_FORMAT_YCbCr_P010,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_8) ==
- AHARDWAREBUFFER_FORMAT_R8_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::R_16_UINT) ==
- AHARDWAREBUFFER_FORMAT_R16_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RG_1616_UINT) ==
- AHARDWAREBUFFER_FORMAT_R16G16_UINT,
- "HAL and AHardwareBuffer pixel format don't match");
- static_assert(static_cast<int>(aidl::android::hardware::graphics::common::PixelFormat::RGBA_10101010) ==
- AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM,
- "HAL and AHardwareBuffer pixel format don't match");
-
- switch (format) {
- case AHARDWAREBUFFER_FORMAT_R8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16_UINT:
- case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
- case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
- case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
- case AHARDWAREBUFFER_FORMAT_BLOB:
- case AHARDWAREBUFFER_FORMAT_D16_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
- case AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420:
- // VNDK formats only -- unfortunately we can't differentiate from where we're called
- case AHARDWAREBUFFER_FORMAT_B8G8R8A8_UNORM:
- case AHARDWAREBUFFER_FORMAT_YV12:
- case AHARDWAREBUFFER_FORMAT_Y8:
- case AHARDWAREBUFFER_FORMAT_Y16:
- case AHARDWAREBUFFER_FORMAT_RAW16:
- case AHARDWAREBUFFER_FORMAT_RAW10:
- case AHARDWAREBUFFER_FORMAT_RAW12:
- case AHARDWAREBUFFER_FORMAT_RAW_OPAQUE:
- case AHARDWAREBUFFER_FORMAT_IMPLEMENTATION_DEFINED:
- case AHARDWAREBUFFER_FORMAT_YCbCr_422_SP:
- case AHARDWAREBUFFER_FORMAT_YCrCb_420_SP:
- case AHARDWAREBUFFER_FORMAT_YCbCr_422_I:
- case AHARDWAREBUFFER_FORMAT_YCbCr_P010:
- return true;
-
- default:
- return false;
- }
-}
-
bool AHardwareBuffer_formatIsYuv(uint32_t format) {
switch (format) {
case AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420:
@@ -683,32 +644,6 @@ bool AHardwareBuffer_formatIsYuv(uint32_t format) {
}
}
-uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format) {
- switch (format) {
- case AHARDWAREBUFFER_FORMAT_R8_UNORM:
- return 1;
- case AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM:
- case AHARDWAREBUFFER_FORMAT_D16_UNORM:
- case AHARDWAREBUFFER_FORMAT_R16_UINT:
- return 2;
- case AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM:
- return 3;
- case AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM:
- case AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM:
- case AHARDWAREBUFFER_FORMAT_D32_FLOAT:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM:
- case AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT:
- case AHARDWAREBUFFER_FORMAT_R16G16_UINT:
- return 4;
- case AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT:
- case AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM:
- return 8;
- default:
- return 0;
- }
-}
-
uint32_t AHardwareBuffer_convertFromPixelFormat(uint32_t hal_format) {
return hal_format;
}
@@ -717,6 +652,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<int32_t>(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 3762e66e7f..dd5958de28 100644
--- a/libs/nativewindow/ANativeWindow.cpp
+++ b/libs/nativewindow/ANativeWindow.cpp
@@ -24,14 +24,49 @@
#include <private/android/AHardwareBufferHelpers.h>
+#include <android/binder_libbinder.h>
+#include <dlfcn.h>
#include <log/log.h>
#include <ui/GraphicBuffer.h>
-#include <gui/Surface.h>
-#include <gui/view/Surface.h>
-#include <android/binder_libbinder.h>
using namespace android;
+#if defined(__ANDROID_APEX__) || defined(__ANDROID_VNDK__)
+#error libnativewindow can only be built for system
+#endif
+
+using android_view_Surface_writeToParcel = status_t (*)(ANativeWindow* _Nonnull window,
+ Parcel* _Nonnull parcel);
+
+using android_view_Surface_readFromParcel =
+ status_t (*)(const Parcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow);
+
+struct SurfaceParcelables {
+ android_view_Surface_writeToParcel write = nullptr;
+ android_view_Surface_readFromParcel read = nullptr;
+};
+
+const SurfaceParcelables* getSurfaceParcelFunctions() {
+ static SurfaceParcelables funcs = []() -> SurfaceParcelables {
+ SurfaceParcelables ret;
+ void* dl = dlopen("libgui.so", RTLD_NOW);
+ LOG_ALWAYS_FATAL_IF(!dl, "Failed to find libgui.so");
+ ret.write =
+ (android_view_Surface_writeToParcel)dlsym(dl, "android_view_Surface_writeToParcel");
+ LOG_ALWAYS_FATAL_IF(!ret.write,
+ "libgui.so missing android_view_Surface_writeToParcel; "
+ "loaded wrong libgui?");
+ ret.read =
+ (android_view_Surface_readFromParcel)dlsym(dl,
+ "android_view_Surface_readFromParcel");
+ LOG_ALWAYS_FATAL_IF(!ret.read,
+ "libgui.so missing android_view_Surface_readFromParcel; "
+ "loaded wrong libgui?");
+ return ret;
+ }();
+ return &funcs;
+}
+
static int32_t query(ANativeWindow* window, int what) {
int value;
int res = window->query(window, what, &value);
@@ -44,34 +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;
- }
-}
-static sp<IGraphicBufferProducer> IGraphicBufferProducer_from_ANativeWindow(ANativeWindow* window) {
- return Surface::getIGraphicBufferProducer(window);
-}
-
-static sp<IBinder> SurfaceControlHandle_from_ANativeWindow(ANativeWindow* window) {
- return Surface::getSurfaceControlHandle(window);
-}
-
/**************************************************************************************************
* NDK
**************************************************************************************************/
@@ -188,11 +195,10 @@ int32_t ANativeWindow_setBuffersDataSpace(ANativeWindow* window, int32_t dataSpa
static_cast<int>(HAL_DATASPACE_BT2020_HLG));
static_assert(static_cast<int>(ADATASPACE_BT2020_ITU_HLG) ==
static_cast<int>(HAL_DATASPACE_BT2020_ITU_HLG));
- static_assert(static_cast<int>(DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH));
- static_assert(static_cast<int>(DYNAMIC_DEPTH) == static_cast<int>(HAL_DATASPACE_DYNAMIC_DEPTH));
+ static_assert(static_cast<int>(ADATASPACE_DEPTH) == static_cast<int>(HAL_DATASPACE_DEPTH));
+ static_assert(static_cast<int>(ADATASPACE_DYNAMIC_DEPTH) == static_cast<int>(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,
@@ -205,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);
@@ -348,38 +361,24 @@ int ANativeWindow_setAutoPrerotation(ANativeWindow* window, bool autoPrerotation
binder_status_t ANativeWindow_readFromParcel(
const AParcel* _Nonnull parcel, ANativeWindow* _Nullable* _Nonnull outWindow) {
- const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
-
- // Use a android::view::Surface to unparcel the window
- std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
- status_t ret = shimSurface->readFromParcel(nativeParcel);
- if (ret != OK) {
- ALOGE("%s: Error: Failed to create android::view::Surface from AParcel", __FUNCTION__);
- return STATUS_BAD_VALUE;
+ auto funcs = getSurfaceParcelFunctions();
+ if (funcs->read == nullptr) {
+ ALOGE("Failed to load Surface_readFromParcel implementation");
+ return STATUS_FAILED_TRANSACTION;
}
- sp<Surface> surface = sp<Surface>::make(
- shimSurface->graphicBufferProducer, false, shimSurface->surfaceControlHandle);
- ANativeWindow* anw = surface.get();
- ANativeWindow_acquire(anw);
- *outWindow = anw;
- return STATUS_OK;
+ const Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
+ return funcs->read(nativeParcel, outWindow);
}
binder_status_t ANativeWindow_writeToParcel(
ANativeWindow* _Nonnull window, AParcel* _Nonnull parcel) {
- int value;
- int err = (*window->query)(window, NATIVE_WINDOW_CONCRETE_TYPE, &value);
- if (err != OK || value != NATIVE_WINDOW_SURFACE) {
- ALOGE("Error: ANativeWindow is not backed by Surface");
- return STATUS_BAD_VALUE;
+ auto funcs = getSurfaceParcelFunctions();
+ if (funcs->write == nullptr) {
+ ALOGE("Failed to load Surface_writeToParcel implementation");
+ return STATUS_FAILED_TRANSACTION;
}
- // Use a android::view::Surface to parcelize the window
- std::shared_ptr<android::view::Surface> shimSurface = std::shared_ptr<android::view::Surface>();
- shimSurface->graphicBufferProducer = IGraphicBufferProducer_from_ANativeWindow(window);
- shimSurface->surfaceControlHandle = SurfaceControlHandle_from_ANativeWindow(window);
-
Parcel* nativeParcel = AParcel_viewPlatformParcel(parcel);
- return shimSurface->writeToParcel(nativeParcel);
+ return funcs->write(window, nativeParcel);
}
/**************************************************************************************************
diff --git a/libs/nativewindow/TEST_MAPPING b/libs/nativewindow/TEST_MAPPING
index 3d7f3c28f4..9d6425bfe0 100644
--- a/libs/nativewindow/TEST_MAPPING
+++ b/libs/nativewindow/TEST_MAPPING
@@ -1,7 +1,13 @@
{
"presubmit": [
{
+ "name": "libnativewindow_bindgen_test"
+ },
+ {
"name": "libnativewindow_test"
+ },
+ {
+ "name": "libnativewindow_rs-internal_test"
}
]
}
diff --git a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
index ddfd1d1918..880c694934 100644
--- a/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
+++ b/libs/nativewindow/include-private/private/android/AHardwareBufferHelpers.h
@@ -37,21 +37,20 @@ namespace android {
// parameters. Note: this does not verify any platform-specific contraints.
bool AHardwareBuffer_isValidDescription(const AHardwareBuffer_Desc* desc, bool log);
-// whether this AHardwareBuffer format is valid
-bool AHardwareBuffer_isValidPixelFormat(uint32_t ahardwarebuffer_format);
-
// whether this is a YUV type format
bool AHardwareBuffer_formatIsYuv(uint32_t format);
-// number of bytes per pixel or 0 if unknown or multi-planar
-uint32_t AHardwareBuffer_bytesPerPixel(uint32_t format);
-
// convert AHardwareBuffer format to HAL format (note: this is a no-op)
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 771844f4fe..9f8ae863db 100644
--- a/libs/nativewindow/include/android/data_space.h
+++ b/libs/nativewindow/include/android/data_space.h
@@ -29,6 +29,7 @@
#define ANDROID_DATA_SPACE_H
#include <inttypes.h>
+#include <stdint.h>
#include <sys/cdefs.h>
@@ -37,7 +38,7 @@ __BEGIN_DECLS
/**
* ADataSpace.
*/
-enum ADataSpace {
+enum ADataSpace : int32_t {
/**
* Default-assumption data space, when not explicitly specified.
*
@@ -81,11 +82,12 @@ enum ADataSpace {
STANDARD_UNSPECIFIED = 0 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.300 0.600
* blue 0.150 0.060
* red 0.640 0.330
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* Use the unadjusted KR = 0.2126, KB = 0.0722 luminance interpretation
* for RGB conversion.
@@ -93,11 +95,12 @@ enum ADataSpace {
STANDARD_BT709 = 1 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.290 0.600
* blue 0.150 0.060
* red 0.640 0.330
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* KR = 0.299, KB = 0.114. This adjusts the luminance interpretation
* for RGB conversion from the one purely determined by the primaries
@@ -107,11 +110,12 @@ enum ADataSpace {
STANDARD_BT601_625 = 2 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.290 0.600
* blue 0.150 0.060
* red 0.640 0.330
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* Use the unadjusted KR = 0.222, KB = 0.071 luminance interpretation
* for RGB conversion.
@@ -119,11 +123,12 @@ enum ADataSpace {
STANDARD_BT601_625_UNADJUSTED = 3 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.310 0.595
* blue 0.155 0.070
* red 0.630 0.340
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* KR = 0.299, KB = 0.114. This adjusts the luminance interpretation
* for RGB conversion from the one purely determined by the primaries
@@ -133,11 +138,12 @@ enum ADataSpace {
STANDARD_BT601_525 = 4 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.310 0.595
* blue 0.155 0.070
* red 0.630 0.340
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* Use the unadjusted KR = 0.212, KB = 0.087 luminance interpretation
* for RGB conversion (as in SMPTE 240M).
@@ -145,11 +151,12 @@ enum ADataSpace {
STANDARD_BT601_525_UNADJUSTED = 5 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.170 0.797
* blue 0.131 0.046
* red 0.708 0.292
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
* for RGB conversion.
@@ -157,11 +164,12 @@ enum ADataSpace {
STANDARD_BT2020 = 6 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.170 0.797
* blue 0.131 0.046
* red 0.708 0.292
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*
* Use the unadjusted KR = 0.2627, KB = 0.0593 luminance interpretation
* for RGB conversion using the linear domain.
@@ -169,11 +177,12 @@ enum ADataSpace {
STANDARD_BT2020_CONSTANT_LUMINANCE = 7 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.21 0.71
* blue 0.14 0.08
* red 0.67 0.33
- * white (C) 0.310 0.316
+ * white (C) 0.310 0.316</pre>
*
* Use the unadjusted KR = 0.30, KB = 0.11 luminance interpretation
* for RGB conversion.
@@ -181,11 +190,12 @@ enum ADataSpace {
STANDARD_BT470M = 8 << 16,
/**
+ * <pre>
* Primaries: x y
* green 0.243 0.692
* blue 0.145 0.049
* red 0.681 0.319
- * white (C) 0.310 0.316
+ * white (C) 0.310 0.316</pre>
*
* Use the unadjusted KR = 0.254, KB = 0.068 luminance interpretation
* for RGB conversion.
@@ -194,21 +204,23 @@ enum ADataSpace {
/**
* SMPTE EG 432-1 and SMPTE RP 431-2. (DCI-P3)
+ * <pre>
* Primaries: x y
* green 0.265 0.690
* blue 0.150 0.060
* red 0.680 0.320
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*/
STANDARD_DCI_P3 = 10 << 16,
/**
* Adobe RGB
+ * <pre>
* Primaries: x y
* green 0.210 0.710
* blue 0.150 0.060
* red 0.640 0.330
- * white (D65) 0.3127 0.3290
+ * white (D65) 0.3127 0.3290</pre>
*/
STANDARD_ADOBE_RGB = 11 << 16,
@@ -242,83 +254,86 @@ enum ADataSpace {
TRANSFER_UNSPECIFIED = 0 << 22,
/**
+ * Linear transfer.
+ * <pre>
* Transfer characteristic curve:
- * E = L
- * L - luminance of image 0 <= L <= 1 for conventional colorimetry
- * E - corresponding electrical signal
+ * E = L
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_LINEAR = 1 << 22,
/**
+ * sRGB transfer.
+ * <pre>
* Transfer characteristic curve:
- *
* E = 1.055 * L^(1/2.4) - 0.055 for 0.0031308 <= L <= 1
* = 12.92 * L for 0 <= L < 0.0031308
* L - luminance of image 0 <= L <= 1 for conventional colorimetry
- * E - corresponding electrical signal
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_SRGB = 2 << 22,
/**
- * BT.601 525, BT.601 625, BT.709, BT.2020
- *
+ * SMPTE 170M transfer.
+ * <pre>
* Transfer characteristic curve:
- * E = 1.099 * L ^ 0.45 - 0.099 for 0.018 <= L <= 1
- * = 4.500 * L for 0 <= L < 0.018
- * L - luminance of image 0 <= L <= 1 for conventional colorimetry
- * E - corresponding electrical signal
+ * E = 1.099 * L ^ 0.45 - 0.099 for 0.018 <= L <= 1
+ * = 4.500 * L for 0 <= L < 0.018
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_SMPTE_170M = 3 << 22,
/**
- * Assumed display gamma 2.2.
- *
+ * Display gamma 2.2.
+ * <pre>
* Transfer characteristic curve:
- * E = L ^ (1/2.2)
- * L - luminance of image 0 <= L <= 1 for conventional colorimetry
- * E - corresponding electrical signal
+ * E = L ^ (1/2.2)
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_GAMMA2_2 = 4 << 22,
/**
- * display gamma 2.6.
- *
+ * Display gamma 2.6.
+ * <pre>
* Transfer characteristic curve:
- * E = L ^ (1/2.6)
- * L - luminance of image 0 <= L <= 1 for conventional colorimetry
- * E - corresponding electrical signal
+ * E = L ^ (1/2.6)
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_GAMMA2_6 = 5 << 22,
/**
- * display gamma 2.8.
- *
+ * Display gamma 2.8.
+ * <pre>
* Transfer characteristic curve:
- * E = L ^ (1/2.8)
- * L - luminance of image 0 <= L <= 1 for conventional colorimetry
- * E - corresponding electrical signal
+ * E = L ^ (1/2.8)
+ * L - luminance of image 0 <= L <= 1 for conventional colorimetry
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_GAMMA2_8 = 6 << 22,
/**
- * SMPTE ST 2084 (Dolby Perceptual Quantizer)
- *
+ * SMPTE ST 2084 (Dolby Perceptual Quantizer).
+ * <pre>
* Transfer characteristic curve:
- * E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m
- * c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375
- * c2 = 32 * 2413 / 4096 = 18.8515625
- * c3 = 32 * 2392 / 4096 = 18.6875
- * m = 128 * 2523 / 4096 = 78.84375
- * n = 0.25 * 2610 / 4096 = 0.1593017578125
- * L - luminance of image 0 <= L <= 1 for HDR colorimetry.
- * L = 1 corresponds to 10000 cd/m2
- * E - corresponding electrical signal
+ * E = ((c1 + c2 * L^n) / (1 + c3 * L^n)) ^ m
+ * c1 = c3 - c2 + 1 = 3424 / 4096 = 0.8359375
+ * c2 = 32 * 2413 / 4096 = 18.8515625
+ * c3 = 32 * 2392 / 4096 = 18.6875
+ * m = 128 * 2523 / 4096 = 78.84375
+ * n = 0.25 * 2610 / 4096 = 0.1593017578125
+ * L - luminance of image 0 <= L <= 1 for HDR colorimetry.
+ * L = 1 corresponds to 10000 cd/m2
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_ST2084 = 7 << 22,
/**
- * ARIB STD-B67 Hybrid Log Gamma
- *
+ * ARIB STD-B67 Hybrid Log Gamma.
+ * <pre>
* Transfer characteristic curve:
* E = r * L^0.5 for 0 <= L <= 1
* = a * ln(L - b) + c for 1 < L
@@ -328,7 +343,7 @@ enum ADataSpace {
* r = 0.5
* L - luminance of image 0 <= L for HDR colorimetry. L = 1 corresponds
* to reference white level of 100 cd/m2
- * E - corresponding electrical signal
+ * E - corresponding electrical signal</pre>
*/
TRANSFER_HLG = 8 << 22,
@@ -384,21 +399,22 @@ enum ADataSpace {
RANGE_EXTENDED = 3 << 27,
/**
- * scRGB linear encoding:
+ * scRGB linear encoding
*
* The red, green, and blue components are stored in extended sRGB space,
* but are linear, not gamma-encoded.
- * The RGB primaries and the white point are the same as BT.709.
*
* The values are floating point.
* A pixel value of 1.0, 1.0, 1.0 corresponds to sRGB white (D65) at 80 nits.
* Values beyond the range [0.0 - 1.0] would correspond to other colors
* spaces and/or HDR content.
+ *
+ * Uses extended range, linear transfer and BT.709 standard.
*/
ADATASPACE_SCRGB_LINEAR = 406913024, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_EXTENDED
/**
- * sRGB gamma encoding:
+ * sRGB gamma encoding
*
* The red, green and blue components are stored in sRGB space, and
* converted to linear space when read, using the SRGB transfer function
@@ -408,29 +424,29 @@ enum ADataSpace {
* The alpha component, if present, is always stored in linear space and
* is left unmodified when read or written.
*
- * Use full range and BT.709 standard.
+ * Uses full range, sRGB transfer BT.709 standard.
*/
ADATASPACE_SRGB = 142671872, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_FULL
/**
- * scRGB:
+ * scRGB
*
* The red, green, and blue components are stored in extended sRGB space,
* and gamma-encoded using the SRGB transfer function.
- * The RGB primaries and the white point are the same as BT.709.
*
* The values are floating point.
* A pixel value of 1.0, 1.0, 1.0 corresponds to sRGB white (D65) at 80 nits.
* Values beyond the range [0.0 - 1.0] would correspond to other colors
* spaces and/or HDR content.
+ *
+ * Uses extended range, sRGB transfer and BT.709 standard.
*/
ADATASPACE_SCRGB = 411107328, // STANDARD_BT709 | TRANSFER_SRGB | RANGE_EXTENDED
/**
* Display P3
*
- * Use same primaries and white-point as DCI-P3
- * but sRGB transfer function.
+ * Uses full range, sRGB transfer and D65 DCI-P3 standard.
*/
ADATASPACE_DISPLAY_P3 = 143261696, // STANDARD_DCI_P3 | TRANSFER_SRGB | RANGE_FULL
@@ -439,7 +455,7 @@ enum ADataSpace {
*
* Ultra High-definition television
*
- * Use full range, SMPTE 2084 (PQ) transfer and BT2020 standard
+ * Uses full range, SMPTE 2084 (PQ) transfer and BT2020 standard.
*/
ADATASPACE_BT2020_PQ = 163971072, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_FULL
@@ -448,14 +464,15 @@ enum ADataSpace {
*
* Ultra High-definition television
*
- * Use limited range, SMPTE 2084 (PQ) transfer and BT2020 standard
+ * Uses limited range, SMPTE 2084 (PQ) transfer and BT2020 standard.
*/
- ADATASPACE_BT2020_ITU_PQ = 298188800, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED
+ ADATASPACE_BT2020_ITU_PQ = 298188800, // STANDARD_BT2020 | TRANSFER_ST2084 | RANGE_LIMITED
/**
* Adobe RGB
*
- * Use full range, gamma 2.2 transfer and Adobe RGB primaries
+ * Uses full range, gamma 2.2 transfer and Adobe RGB standard.
+ *
* Note: Application is responsible for gamma encoding the data as
* a 2.2 gamma encoding is not supported in HW.
*/
@@ -464,27 +481,27 @@ enum ADataSpace {
/**
* JPEG File Interchange Format (JFIF)
*
- * Same model as BT.601-625, but all values (Y, Cb, Cr) range from 0 to 255
+ * Same model as BT.601-625, but all values (Y, Cb, Cr) range from 0 to 255.
*
- * Use full range, SMPTE 170M transfer and BT.601_625 standard.
+ * Uses full range, SMPTE 170M transfer and BT.601_625 standard.
*/
ADATASPACE_JFIF = 146931712, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_FULL
/**
- * ITU-R Recommendation 601 (BT.601) - 525-line
+ * ITU-R Recommendation 601 (BT.601) - 625-line
*
- * Standard-definition television, 525 Lines (NTSC)
+ * Standard-definition television, 625 Lines (PAL)
*
- * Use limited range, SMPTE 170M transfer and BT.601_525 standard.
+ * Uses limited range, SMPTE 170M transfer and BT.601_625 standard.
*/
ADATASPACE_BT601_625 = 281149440, // STANDARD_BT601_625 | TRANSFER_SMPTE_170M | RANGE_LIMITED
/**
- * ITU-R Recommendation 709 (BT.709)
+ * ITU-R Recommendation 601 (BT.601) - 525-line
*
- * High-definition television
+ * Standard-definition television, 525 Lines (NTSC)
*
- * Use limited range, SMPTE 170M transfer and BT.709 standard.
+ * Uses limited range, SMPTE 170M transfer and BT.601_525 standard.
*/
ADATASPACE_BT601_525 = 281280512, // STANDARD_BT601_525 | TRANSFER_SMPTE_170M | RANGE_LIMITED
@@ -493,7 +510,7 @@ enum ADataSpace {
*
* Ultra High-definition television
*
- * Use full range, BT.709 transfer and BT2020 standard
+ * Uses full range, SMPTE 170M transfer and BT2020 standard.
*/
ADATASPACE_BT2020 = 147193856, // STANDARD_BT2020 | TRANSFER_SMPTE_170M | RANGE_FULL
@@ -502,23 +519,24 @@ enum ADataSpace {
*
* High-definition television
*
- * Use limited range, BT.709 transfer and BT.709 standard.
+ * Uses limited range, SMPTE 170M transfer and BT.709 standard.
*/
ADATASPACE_BT709 = 281083904, // STANDARD_BT709 | TRANSFER_SMPTE_170M | RANGE_LIMITED
/**
- * SMPTE EG 432-1 and SMPTE RP 431-2.
+ * SMPTE EG 432-1 and SMPTE RP 431-2
*
* Digital Cinema DCI-P3
*
- * Use full range, gamma 2.6 transfer and D65 DCI-P3 standard
+ * Uses full range, gamma 2.6 transfer and D65 DCI-P3 standard.
+ *
* Note: Application is responsible for gamma encoding the data as
* a 2.6 gamma encoding is not supported in HW.
*/
ADATASPACE_DCI_P3 = 155844608, // STANDARD_DCI_P3 | TRANSFER_GAMMA2_6 | RANGE_FULL
/**
- * sRGB linear encoding:
+ * sRGB linear encoding
*
* The red, green, and blue components are stored in sRGB space, but
* are linear, not gamma-encoded.
@@ -526,36 +544,38 @@ enum ADataSpace {
*
* The values are encoded using the full range ([0,255] for 8-bit) for all
* components.
+ *
+ * Uses full range, linear transfer and BT.709 standard.
*/
ADATASPACE_SRGB_LINEAR = 138477568, // STANDARD_BT709 | TRANSFER_LINEAR | RANGE_FULL
/**
- * Hybrid Log Gamma encoding:
+ * Hybrid Log Gamma encoding
*
- * Use full range, hybrid log gamma transfer and BT2020 standard.
+ * Uses full range, hybrid log gamma transfer and BT2020 standard.
*/
ADATASPACE_BT2020_HLG = 168165376, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_FULL
/**
- * ITU Hybrid Log Gamma encoding:
+ * ITU Hybrid Log Gamma encoding
*
- * Use limited range, hybrid log gamma transfer and BT2020 standard.
+ * Uses limited range, hybrid log gamma transfer and BT2020 standard.
*/
ADATASPACE_BT2020_ITU_HLG = 302383104, // STANDARD_BT2020 | TRANSFER_HLG | RANGE_LIMITED
/**
- * Depth:
+ * Depth
*
* 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:
+ * 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/hardware_buffer.h b/libs/nativewindow/include/android/hardware_buffer.h
index 85a524988d..21798d0e29 100644
--- a/libs/nativewindow/include/android/hardware_buffer.h
+++ b/libs/nativewindow/include/android/hardware_buffer.h
@@ -177,14 +177,14 @@ enum AHardwareBuffer_Format {
/**
* Corresponding formats:
* Vulkan: VK_FORMAT_R16_UINT
- * OpenGL ES: GR_GL_R16UI
+ * OpenGL ES: GL_R16UI
*/
AHARDWAREBUFFER_FORMAT_R16_UINT = 0x39,
/**
* Corresponding formats:
* Vulkan: VK_FORMAT_R16G16_UINT
- * OpenGL ES: GR_GL_RG16UI
+ * OpenGL ES: GL_RG16UI
*/
AHARDWAREBUFFER_FORMAT_R16G16_UINT = 0x3a,
diff --git a/libs/nativewindow/include/android/hardware_buffer_aidl.h b/libs/nativewindow/include/android/hardware_buffer_aidl.h
index 9fea21e34e..3f77c78096 100644
--- a/libs/nativewindow/include/android/hardware_buffer_aidl.h
+++ b/libs/nativewindow/include/android/hardware_buffer_aidl.h
@@ -34,6 +34,10 @@
#include <android/hardware_buffer.h>
#include <sys/cdefs.h>
+#ifdef __cplusplus
+#include <string>
+#endif
+
__BEGIN_DECLS
/**
@@ -91,14 +95,22 @@ public:
binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
reset();
- return AHardwareBuffer_readFromParcel(parcel, &mBuffer);
+ if (__builtin_available(android __ANDROID_API_U__, *)) {
+ return AHardwareBuffer_readFromParcel(parcel, &mBuffer);
+ } else {
+ return STATUS_FAILED_TRANSACTION;
+ }
}
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
if (!mBuffer) {
return STATUS_BAD_VALUE;
}
- return AHardwareBuffer_writeToParcel(mBuffer, parcel);
+ if (__builtin_available(android __ANDROID_API_U__, *)) {
+ return AHardwareBuffer_writeToParcel(mBuffer, parcel);
+ } else {
+ return STATUS_FAILED_TRANSACTION;
+ }
}
/**
@@ -119,6 +131,13 @@ public:
inline AHardwareBuffer* _Nullable get() const { return mBuffer; }
inline explicit operator bool () const { return mBuffer != nullptr; }
+ inline bool operator!=(const HardwareBuffer& rhs) const { return get() != rhs.get(); }
+ inline bool operator<(const HardwareBuffer& rhs) const { return get() < rhs.get(); }
+ inline bool operator<=(const HardwareBuffer& rhs) const { return get() <= rhs.get(); }
+ inline bool operator==(const HardwareBuffer& rhs) const { return get() == rhs.get(); }
+ inline bool operator>(const HardwareBuffer& rhs) const { return get() > rhs.get(); }
+ inline bool operator>=(const HardwareBuffer& rhs) const { return get() >= rhs.get(); }
+
HardwareBuffer& operator=(HardwareBuffer&& other) noexcept {
reset(other.release());
return *this;
@@ -135,6 +154,19 @@ public:
return ret;
}
+ inline std::string toString() const {
+ if (!mBuffer) {
+ return "<HardwareBuffer: Invalid>";
+ }
+ if (__builtin_available(android __ANDROID_API_S__, *)) {
+ uint64_t id = 0;
+ AHardwareBuffer_getId(mBuffer, &id);
+ return "<HardwareBuffer " + std::to_string(id) + ">";
+ } else {
+ return "<HardwareBuffer (unknown)>";
+ }
+ }
+
private:
HardwareBuffer(const HardwareBuffer& other) = delete;
HardwareBuffer& operator=(const HardwareBuffer& other) = delete;
diff --git a/libs/nativewindow/include/android/native_window.h b/libs/nativewindow/include/android/native_window.h
index a593cd47d3..be6623ee75 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/android/native_window_aidl.h b/libs/nativewindow/include/android/native_window_aidl.h
index a252245a10..e496c453f2 100644
--- a/libs/nativewindow/include/android/native_window_aidl.h
+++ b/libs/nativewindow/include/android/native_window_aidl.h
@@ -34,6 +34,12 @@
#include <android/native_window.h>
#include <sys/cdefs.h>
+// Only required by the AIDL glue helper
+#ifdef __cplusplus
+#include <sstream>
+#include <string>
+#endif // __cplusplus
+
__BEGIN_DECLS
/**
@@ -80,7 +86,7 @@ namespace aidl::android::hardware {
* Takes ownership of the ANativeWindow* given to it in reset() and will automatically
* destroy it in the destructor, similar to a smart pointer container
*/
-class NativeWindow {
+class NativeWindow final {
public:
NativeWindow() noexcept {}
explicit NativeWindow(ANativeWindow* _Nullable window) {
@@ -97,14 +103,22 @@ public:
binder_status_t readFromParcel(const AParcel* _Nonnull parcel) {
reset();
- return ANativeWindow_readFromParcel(parcel, &mWindow);
+ if (__builtin_available(android __ANDROID_API_U__, *)) {
+ return ANativeWindow_readFromParcel(parcel, &mWindow);
+ } else {
+ return STATUS_INVALID_OPERATION;
+ }
}
binder_status_t writeToParcel(AParcel* _Nonnull parcel) const {
if (!mWindow) {
return STATUS_BAD_VALUE;
}
- return ANativeWindow_writeToParcel(mWindow, parcel);
+ if (__builtin_available(android __ANDROID_API_U__, *)) {
+ return ANativeWindow_writeToParcel(mWindow, parcel);
+ } else {
+ return STATUS_INVALID_OPERATION;
+ }
}
/**
@@ -123,15 +137,29 @@ public:
}
mWindow = window;
}
- inline ANativeWindow* _Nullable operator-> () const { return mWindow; }
+
inline ANativeWindow* _Nullable get() const { return mWindow; }
- inline explicit operator bool () const { return mWindow != nullptr; }
NativeWindow& operator=(NativeWindow&& other) noexcept {
mWindow = other.release(); // steal ownership from r-value
return *this;
}
+ inline ANativeWindow* _Nullable operator->() const { return mWindow; }
+ inline explicit operator bool() const { return mWindow != nullptr; }
+ inline bool operator==(const NativeWindow& rhs) const { return mWindow == rhs.mWindow; }
+ inline bool operator!=(const NativeWindow& rhs) const { return !(*this == rhs); }
+ inline bool operator<(const NativeWindow& rhs) const { return mWindow < rhs.mWindow; }
+ inline bool operator>(const NativeWindow& rhs) const { return rhs < *this; }
+ inline bool operator>=(const NativeWindow& rhs) const { return !(*this < rhs); }
+ inline bool operator<=(const NativeWindow& rhs) const { return !(*this > rhs); }
+
+ std::string toString() const {
+ std::ostringstream ss;
+ ss << "NativeWindow: " << mWindow;
+ return ss.str();
+ }
+
/**
* Stops managing any contained ANativeWindow*, returning it to the caller. Ownership
* is released.
diff --git a/libs/nativewindow/include/system/window.h b/libs/nativewindow/include/system/window.h
index a54af1fa62..a98ea86073 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,89 @@ 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,
+
+ /**
+ * The surface requests a frame rate that is greater than or equal to `frameRate`.
+ */
+ ANATIVEWINDOW_FRAME_RATE_GTE
+};
+
+/*
+ * Frame rate category values that can be used in Transaction::setFrameRateCategory.
+ */
+enum {
+ /**
+ * Default value. This value can also be set to return to default behavior, such as layers
+ * without animations.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT = 0,
+
+ /**
+ * The layer will explicitly not influence the frame rate.
+ * This may indicate a frame rate suitable for no animation updates (such as a cursor blinking
+ * or a sporadic update).
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE = 1,
+
+ /**
+ * Indicates a frame rate suitable for animations that looks fine even if played at a low frame
+ * rate.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_LOW = 2,
+
+ /**
+ * Indicates a middle frame rate suitable for animations that do not require higher frame
+ * rates, or do not benefit from high smoothness. This is normally 60 Hz or close to it.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL = 3,
+
+ /**
+ * Indicates a frame rate suitable for animations that require a high frame rate, which may
+ * increase smoothness but may also increase power usage.
+ */
+ ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH = 4
+};
+
+/*
+ * Frame rate selection strategy values that can be used in
+ * Transaction::setFrameRateSelectionStrategy.
+ */
+enum {
+ /**
+ * Default value. The layer uses its own frame rate specifications, assuming it has any
+ * specifications, instead of its parent's. If it does not have its own frame rate
+ * specifications, it will try to use its parent's. It will propagate its specifications to any
+ * descendants that do not have their own.
+ *
+ * However, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN on an ancestor layer
+ * supersedes this behavior, meaning that this layer will inherit frame rate specifications
+ * regardless of whether it has its own.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE = 0,
+
+ /**
+ * The layer's frame rate specifications will propagate to and override those of its descendant
+ * layers.
+ *
+ * The layer itself has the FRAME_RATE_SELECTION_STRATEGY_PROPAGATE behavior.
+ * Thus, ancestor layer that also has the strategy
+ * FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN will override this layer's
+ * frame rate specifications.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN = 1,
+
+ /**
+ * The layer's frame rate specifications will not propagate to its descendant
+ * layers, even if the descendant layer has no frame rate specifications.
+ * However, FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN on an ancestor
+ * layer supersedes this behavior.
+ */
+ ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF = 2,
};
static inline int native_window_set_frame_rate(struct ANativeWindow* window, float frameRate,
@@ -1042,12 +1144,33 @@ 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,
- int64_t frameTimelineVsyncId,
- int32_t inputEventId,
- int64_t startTimeNanos) {
- return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineVsyncId,
- inputEventId, startTimeNanos);
+struct ANativeWindowFrameTimelineInfo {
+ // Frame Id received from ANativeWindow_getNextFrameId.
+ uint64_t frameNumber;
+
+ // VsyncId received from the Choreographer callback that started this frame.
+ int64_t frameTimelineVsyncId;
+
+ // Input Event ID received from the input event that started this frame.
+ int32_t inputEventId;
+
+ // The time which this frame rendering started (i.e. when Choreographer callback actually run)
+ int64_t startTimeNanos;
+
+ // Whether or not to use the vsyncId to determine the refresh rate. Used for TextureView only.
+ int32_t useForRefreshRateSelection;
+
+ // The VsyncId of a frame that was not drawn and squashed into this frame.
+ // Used for UI thread updates that were not picked up by RenderThread on time.
+ int64_t skippedFrameVsyncId;
+
+ // The start time of a frame that was not drawn and squashed into this frame.
+ int64_t skippedFrameStartTimeNanos;
+};
+
+static inline int native_window_set_frame_timeline_info(
+ struct ANativeWindow* window, struct ANativeWindowFrameTimelineInfo frameTimelineInfo) {
+ return window->perform(window, NATIVE_WINDOW_SET_FRAME_TIMELINE_INFO, frameTimelineInfo);
}
// ------------------------------------------------------------------------------------------------
diff --git a/libs/nativewindow/libnativewindow.map.txt b/libs/nativewindow/libnativewindow.map.txt
index d9ac568187..95fc920da1 100644
--- a/libs/nativewindow/libnativewindow.map.txt
+++ b/libs/nativewindow/libnativewindow.map.txt
@@ -2,10 +2,10 @@ LIBNATIVEWINDOW {
global:
AHardwareBuffer_acquire;
AHardwareBuffer_allocate;
- AHardwareBuffer_createFromHandle; # llndk # systemapi
+ AHardwareBuffer_createFromHandle; # llndk systemapi
AHardwareBuffer_describe;
AHardwareBuffer_getId; # introduced=31
- AHardwareBuffer_getNativeHandle; # llndk # systemapi
+ AHardwareBuffer_getNativeHandle; # llndk systemapi
AHardwareBuffer_isSupported; # introduced=29
AHardwareBuffer_lock;
AHardwareBuffer_lockAndGetInfo; # introduced=29
@@ -23,20 +23,21 @@ LIBNATIVEWINDOW {
ANativeWindow_cancelBuffer; # llndk
ANativeWindow_dequeueBuffer; # llndk
ANativeWindow_getBuffersDataSpace; # introduced=28
+ ANativeWindow_getBuffersDefaultDataSpace; # introduced=34
ANativeWindow_getFormat;
ANativeWindow_getHeight;
- ANativeWindow_getLastDequeueDuration; # systemapi # introduced=30
- ANativeWindow_getLastDequeueStartTime; # systemapi # introduced=30
- ANativeWindow_getLastQueueDuration; # systemapi # introduced=30
+ ANativeWindow_getLastDequeueDuration; # systemapi introduced=30
+ ANativeWindow_getLastDequeueStartTime; # systemapi introduced=30
+ ANativeWindow_getLastQueueDuration; # systemapi introduced=30
ANativeWindow_getWidth;
ANativeWindow_lock;
ANativeWindow_query; # llndk
ANativeWindow_queryf; # llndk
ANativeWindow_queueBuffer; # llndk
- ANativeWindow_setCancelBufferInterceptor; # systemapi # introduced=30
- ANativeWindow_setDequeueBufferInterceptor; # systemapi # introduced=30
- ANativeWindow_setPerformInterceptor; # systemapi # introduced=30
- ANativeWindow_setQueueBufferInterceptor; # systemapi # introduced=30
+ ANativeWindow_setCancelBufferInterceptor; # systemapi introduced=30
+ ANativeWindow_setDequeueBufferInterceptor; # systemapi introduced=30
+ ANativeWindow_setPerformInterceptor; # systemapi introduced=30
+ ANativeWindow_setQueueBufferInterceptor; # systemapi introduced=30
ANativeWindow_release;
ANativeWindow_setAutoPrerotation; # llndk
ANativeWindow_setAutoRefresh; # llndk
@@ -47,7 +48,7 @@ LIBNATIVEWINDOW {
ANativeWindow_setBuffersGeometry;
ANativeWindow_setBuffersTimestamp; # llndk
ANativeWindow_setBuffersTransform;
- ANativeWindow_setDequeueTimeout; # systemapi # introduced=30
+ ANativeWindow_setDequeueTimeout; # systemapi introduced=30
ANativeWindow_setFrameRate; # introduced=30
ANativeWindow_setFrameRateWithChangeStrategy; # introduced=31
ANativeWindow_setSharedBufferMode; # llndk
@@ -64,11 +65,11 @@ LIBNATIVEWINDOW {
LIBNATIVEWINDOW_PLATFORM {
global:
extern "C++" {
- android::AHardwareBuffer_isValidPixelFormat*;
android::AHardwareBuffer_convertFromPixelFormat*;
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/nativewindow/rust/Android.bp b/libs/nativewindow/rust/Android.bp
new file mode 100644
index 0000000000..798d804275
--- /dev/null
+++ b/libs/nativewindow/rust/Android.bp
@@ -0,0 +1,137 @@
+// 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 {
+ default_applicable_licenses: [
+ "frameworks_native_libs_nativewindow_license",
+ ],
+}
+
+rust_bindgen {
+ name: "libnativewindow_bindgen_internal",
+ crate_name: "nativewindow_bindgen",
+ wrapper_src: "sys/nativewindow_bindings.h",
+ source_stem: "bindings",
+ bindgen_flags: [
+ "--constified-enum-module=AHardwareBuffer_Format",
+ "--bitfield-enum=AHardwareBuffer_UsageFlags",
+
+ "--allowlist-file=.*/nativewindow/include/.*\\.h",
+ "--blocklist-type",
+ "AParcel",
+ "--raw-line",
+ "use binder::unstable_api::AParcel;",
+
+ "--with-derive-eq",
+ "--with-derive-partialeq",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libnativewindow",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ ],
+
+ // Currently necessary for host builds
+ // TODO(b/31559095): bionic on host should define this
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ min_sdk_version: "VanillaIceCream",
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
+
+rust_library {
+ name: "libnativewindow_bindgen",
+ crate_name: "nativewindow_bindgen",
+ srcs: [":libnativewindow_bindgen_internal"],
+ shared_libs: [
+ "libbinder_ndk",
+ "libnativewindow",
+ ],
+ rustlibs: [
+ "libbinder_rs",
+ ],
+ lints: "none",
+ clippy_lints: "none",
+ // Currently necessary for host builds
+ // TODO(b/31559095): bionic on host should define this
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ min_sdk_version: "VanillaIceCream",
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
+
+rust_test {
+ name: "libnativewindow_bindgen_test",
+ srcs: [":libnativewindow_bindgen_internal"],
+ crate_name: "nativewindow_bindgen_test",
+ rustlibs: [
+ "libbinder_rs",
+ ],
+ test_suites: ["general-tests"],
+ auto_gen_config: true,
+ clippy_lints: "none",
+ lints: "none",
+}
+
+rust_defaults {
+ name: "libnativewindow_defaults",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libbinder_rs",
+ "libnativewindow_bindgen",
+ ],
+}
+
+rust_library {
+ name: "libnativewindow_rs",
+ crate_name: "nativewindow",
+ defaults: ["libnativewindow_defaults"],
+
+ // Currently necessary for host builds
+ // TODO(b/31559095): bionic on host should define this
+ target: {
+ darwin: {
+ enabled: false,
+ },
+ },
+ min_sdk_version: "VanillaIceCream",
+ vendor_available: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+}
+
+rust_test {
+ name: "libnativewindow_rs-internal_test",
+ crate_name: "nativewindow",
+ defaults: ["libnativewindow_defaults"],
+ test_suites: ["general-tests"],
+}
diff --git a/libs/nativewindow/rust/src/lib.rs b/libs/nativewindow/rust/src/lib.rs
new file mode 100644
index 0000000000..dc3f51f7fd
--- /dev/null
+++ b/libs/nativewindow/rust/src/lib.rs
@@ -0,0 +1,393 @@
+// 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.
+
+//! Pleasant Rust bindings for libnativewindow, including AHardwareBuffer
+
+extern crate nativewindow_bindgen as ffi;
+
+mod surface;
+pub use surface::Surface;
+
+pub use ffi::{AHardwareBuffer_Format, AHardwareBuffer_UsageFlags};
+
+use binder::{
+ binder_impl::{BorrowedParcel, UnstructuredParcelable},
+ impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+ unstable_api::{status_result, AsNative},
+ StatusCode,
+};
+use ffi::{AHardwareBuffer, AHardwareBuffer_readFromParcel, AHardwareBuffer_writeToParcel};
+use std::fmt::{self, Debug, Formatter};
+use std::mem::ManuallyDrop;
+use std::ptr::{self, null_mut, NonNull};
+
+/// Wrapper around an opaque C `AHardwareBuffer`.
+#[derive(PartialEq, Eq)]
+pub struct HardwareBuffer(NonNull<AHardwareBuffer>);
+
+impl HardwareBuffer {
+ /// Test whether the given format and usage flag combination is allocatable. If this function
+ /// returns true, it means that a buffer with the given description can be allocated on this
+ /// implementation, unless resource exhaustion occurs. If this function returns false, it means
+ /// that the allocation of the given description will never succeed.
+ ///
+ /// Available since API 29
+ pub fn is_supported(
+ width: u32,
+ height: u32,
+ layers: u32,
+ format: AHardwareBuffer_Format::Type,
+ usage: AHardwareBuffer_UsageFlags,
+ stride: u32,
+ ) -> bool {
+ let buffer_desc = ffi::AHardwareBuffer_Desc {
+ width,
+ height,
+ layers,
+ format,
+ usage: usage.0,
+ stride,
+ rfu0: 0,
+ rfu1: 0,
+ };
+ // SAFETY: *buffer_desc will never be null.
+ let status = unsafe { ffi::AHardwareBuffer_isSupported(&buffer_desc) };
+
+ status == 1
+ }
+
+ /// Allocates a buffer that matches the passed AHardwareBuffer_Desc. If allocation succeeds, the
+ /// buffer can be used according to the usage flags specified in its description. If a buffer is
+ /// used in ways not compatible with its usage flags, the results are undefined and may include
+ /// program termination.
+ ///
+ /// Available since API level 26.
+ #[inline]
+ pub fn new(
+ width: u32,
+ height: u32,
+ layers: u32,
+ format: AHardwareBuffer_Format::Type,
+ usage: AHardwareBuffer_UsageFlags,
+ ) -> Option<Self> {
+ let buffer_desc = ffi::AHardwareBuffer_Desc {
+ width,
+ height,
+ layers,
+ format,
+ usage: usage.0,
+ stride: 0,
+ rfu0: 0,
+ rfu1: 0,
+ };
+ let mut ptr = ptr::null_mut();
+ // SAFETY: The returned pointer is valid until we drop/deallocate it. The function may fail
+ // and return a status, but we check it later.
+ let status = unsafe { ffi::AHardwareBuffer_allocate(&buffer_desc, &mut ptr) };
+
+ if status == 0 {
+ Some(Self(NonNull::new(ptr).expect("Allocated AHardwareBuffer was null")))
+ } else {
+ None
+ }
+ }
+
+ /// Adopts the given raw pointer and wraps it in a Rust HardwareBuffer.
+ ///
+ /// # Safety
+ ///
+ /// This function takes ownership of the pointer and does NOT increment the refcount on the
+ /// buffer. If the caller uses the pointer after the created object is dropped it will cause
+ /// undefined behaviour. If the caller wants to continue using the pointer after calling this
+ /// then use [`clone_from_raw`](Self::clone_from_raw) instead.
+ pub unsafe fn from_raw(buffer_ptr: NonNull<AHardwareBuffer>) -> Self {
+ Self(buffer_ptr)
+ }
+
+ /// Creates a new Rust HardwareBuffer to wrap the given AHardwareBuffer without taking ownership
+ /// of it.
+ ///
+ /// Unlike [`from_raw`](Self::from_raw) this method will increment the refcount on the buffer.
+ /// This means that the caller can continue to use the raw buffer it passed in, and must call
+ /// [`AHardwareBuffer_release`](ffi::AHardwareBuffer_release) when it is finished with it to
+ /// avoid a memory leak.
+ ///
+ /// # Safety
+ ///
+ /// The buffer pointer must point to a valid `AHardwareBuffer`.
+ pub unsafe fn clone_from_raw(buffer: NonNull<AHardwareBuffer>) -> Self {
+ // SAFETY: The caller guarantees that the AHardwareBuffer pointer is valid.
+ unsafe { ffi::AHardwareBuffer_acquire(buffer.as_ptr()) };
+ Self(buffer)
+ }
+
+ /// Get the internal |AHardwareBuffer| pointer without decrementing the refcount. This can
+ /// be used to provide a pointer to the AHB for a C/C++ API over the FFI.
+ pub fn into_raw(self) -> NonNull<AHardwareBuffer> {
+ let buffer = ManuallyDrop::new(self);
+ buffer.0
+ }
+
+ /// Get the system wide unique id for an AHardwareBuffer. This function may panic in extreme
+ /// and undocumented circumstances.
+ ///
+ /// Available since API level 31.
+ pub fn id(&self) -> u64 {
+ let mut out_id = 0;
+ // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+ // because it must have been allocated by `AHardwareBuffer_allocate`,
+ // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+ // released it. The id pointer must be valid because it comes from a reference.
+ let status = unsafe { ffi::AHardwareBuffer_getId(self.0.as_ptr(), &mut out_id) };
+ assert_eq!(status, 0, "id() failed for AHardwareBuffer with error code: {status}");
+
+ out_id
+ }
+
+ /// Get the width of this buffer
+ pub fn width(&self) -> u32 {
+ self.description().width
+ }
+
+ /// Get the height of this buffer
+ pub fn height(&self) -> u32 {
+ self.description().height
+ }
+
+ /// Get the number of layers of this buffer
+ pub fn layers(&self) -> u32 {
+ self.description().layers
+ }
+
+ /// Get the format of this buffer
+ pub fn format(&self) -> AHardwareBuffer_Format::Type {
+ self.description().format
+ }
+
+ /// Get the usage bitvector of this buffer
+ pub fn usage(&self) -> AHardwareBuffer_UsageFlags {
+ AHardwareBuffer_UsageFlags(self.description().usage)
+ }
+
+ /// Get the stride of this buffer
+ pub fn stride(&self) -> u32 {
+ self.description().stride
+ }
+
+ fn description(&self) -> ffi::AHardwareBuffer_Desc {
+ let mut buffer_desc = ffi::AHardwareBuffer_Desc {
+ width: 0,
+ height: 0,
+ layers: 0,
+ format: 0,
+ usage: 0,
+ stride: 0,
+ rfu0: 0,
+ rfu1: 0,
+ };
+ // SAFETY: neither the buffer nor AHardwareBuffer_Desc pointers will be null.
+ unsafe { ffi::AHardwareBuffer_describe(self.0.as_ref(), &mut buffer_desc) };
+ buffer_desc
+ }
+}
+
+impl Drop for HardwareBuffer {
+ fn drop(&mut self) {
+ // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+ // because it must have been allocated by `AHardwareBuffer_allocate`,
+ // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+ // released it.
+ unsafe { ffi::AHardwareBuffer_release(self.0.as_ptr()) }
+ }
+}
+
+impl Debug for HardwareBuffer {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("HardwareBuffer").field("id", &self.id()).finish()
+ }
+}
+
+impl Clone for HardwareBuffer {
+ fn clone(&self) -> Self {
+ // SAFETY: ptr is guaranteed to be non-null and the acquire can not fail.
+ unsafe { ffi::AHardwareBuffer_acquire(self.0.as_ptr()) };
+ Self(self.0)
+ }
+}
+
+impl UnstructuredParcelable for HardwareBuffer {
+ fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+ let status =
+ // SAFETY: The AHardwareBuffer pointer we pass is guaranteed to be non-null and valid
+ // because it must have been allocated by `AHardwareBuffer_allocate`,
+ // `AHardwareBuffer_readFromParcel` or the caller of `from_raw` and we have not yet
+ // released it.
+ unsafe { AHardwareBuffer_writeToParcel(self.0.as_ptr(), parcel.as_native_mut()) };
+ status_result(status)
+ }
+
+ fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+ let mut buffer = null_mut();
+
+ let status =
+ // SAFETY: Both pointers must be valid because they are obtained from references.
+ // `AHardwareBuffer_readFromParcel` doesn't store them or do anything else special
+ // with them. If it returns success then it will have allocated a new
+ // `AHardwareBuffer` and incremented the reference count, so we can use it until we
+ // release it.
+ unsafe { AHardwareBuffer_readFromParcel(parcel.as_native(), &mut buffer) };
+
+ status_result(status)?;
+
+ Ok(Self(
+ NonNull::new(buffer).expect(
+ "AHardwareBuffer_readFromParcel returned success but didn't allocate buffer",
+ ),
+ ))
+ }
+}
+
+impl_deserialize_for_unstructured_parcelable!(HardwareBuffer);
+impl_serialize_for_unstructured_parcelable!(HardwareBuffer);
+
+// SAFETY: The underlying *AHardwareBuffers can be moved between threads.
+unsafe impl Send for HardwareBuffer {}
+
+// SAFETY: The underlying *AHardwareBuffers can be used from multiple threads.
+//
+// AHardwareBuffers are backed by C++ GraphicBuffers, which are mostly immutable. The only cases
+// where they are not immutable are:
+//
+// - reallocation (which is never actually done across the codebase and requires special
+// privileges/platform code access to do)
+// - "locking" for reading/writing (which is explicitly allowed to be done across multiple threads
+// according to the docs on the underlying gralloc calls)
+unsafe impl Sync for HardwareBuffer {}
+
+#[cfg(test)]
+mod test {
+ use super::*;
+
+ #[test]
+ fn create_valid_buffer_returns_ok() {
+ let buffer = HardwareBuffer::new(
+ 512,
+ 512,
+ 1,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ );
+ assert!(buffer.is_some());
+ }
+
+ #[test]
+ fn create_invalid_buffer_returns_err() {
+ let buffer = HardwareBuffer::new(512, 512, 1, 0, AHardwareBuffer_UsageFlags(0));
+ assert!(buffer.is_none());
+ }
+
+ #[test]
+ fn from_raw_allows_getters() {
+ let buffer_desc = ffi::AHardwareBuffer_Desc {
+ width: 1024,
+ height: 512,
+ layers: 1,
+ format: AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ usage: AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN.0,
+ stride: 0,
+ rfu0: 0,
+ rfu1: 0,
+ };
+ let mut raw_buffer_ptr = ptr::null_mut();
+
+ // SAFETY: The pointers are valid because they come from references, and
+ // `AHardwareBuffer_allocate` doesn't retain them after it returns.
+ let status = unsafe { ffi::AHardwareBuffer_allocate(&buffer_desc, &mut raw_buffer_ptr) };
+ assert_eq!(status, 0);
+
+ // SAFETY: The pointer must be valid because it was just allocated successfully, and we
+ // don't use it after calling this.
+ let buffer = unsafe { HardwareBuffer::from_raw(NonNull::new(raw_buffer_ptr).unwrap()) };
+ assert_eq!(buffer.width(), 1024);
+ }
+
+ #[test]
+ fn basic_getters() {
+ let buffer = HardwareBuffer::new(
+ 1024,
+ 512,
+ 1,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ )
+ .expect("Buffer with some basic parameters was not created successfully");
+
+ assert_eq!(buffer.width(), 1024);
+ assert_eq!(buffer.height(), 512);
+ assert_eq!(buffer.layers(), 1);
+ assert_eq!(buffer.format(), AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM);
+ assert_eq!(
+ buffer.usage(),
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN
+ );
+ }
+
+ #[test]
+ fn id_getter() {
+ let buffer = HardwareBuffer::new(
+ 1024,
+ 512,
+ 1,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ )
+ .expect("Buffer with some basic parameters was not created successfully");
+
+ assert_ne!(0, buffer.id());
+ }
+
+ #[test]
+ fn clone() {
+ let buffer = HardwareBuffer::new(
+ 1024,
+ 512,
+ 1,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ )
+ .expect("Buffer with some basic parameters was not created successfully");
+ let buffer2 = buffer.clone();
+
+ assert_eq!(buffer, buffer2);
+ }
+
+ #[test]
+ fn into_raw() {
+ let buffer = HardwareBuffer::new(
+ 1024,
+ 512,
+ 1,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ )
+ .expect("Buffer with some basic parameters was not created successfully");
+ let buffer2 = buffer.clone();
+
+ let raw_buffer = buffer.into_raw();
+ // SAFETY: This is the same pointer we had before.
+ let remade_buffer = unsafe { HardwareBuffer::from_raw(raw_buffer) };
+
+ assert_eq!(remade_buffer, buffer2);
+ }
+}
diff --git a/libs/nativewindow/rust/src/surface.rs b/libs/nativewindow/rust/src/surface.rs
new file mode 100644
index 0000000000..25fea807b5
--- /dev/null
+++ b/libs/nativewindow/rust/src/surface.rs
@@ -0,0 +1,143 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Rust wrapper for `ANativeWindow` and related types.
+
+use binder::{
+ binder_impl::{BorrowedParcel, UnstructuredParcelable},
+ impl_deserialize_for_unstructured_parcelable, impl_serialize_for_unstructured_parcelable,
+ unstable_api::{status_result, AsNative},
+ StatusCode,
+};
+use nativewindow_bindgen::{
+ AHardwareBuffer_Format, ANativeWindow, ANativeWindow_acquire, ANativeWindow_getFormat,
+ ANativeWindow_getHeight, ANativeWindow_getWidth, ANativeWindow_readFromParcel,
+ ANativeWindow_release, ANativeWindow_writeToParcel,
+};
+use std::error::Error;
+use std::fmt::{self, Debug, Display, Formatter};
+use std::ptr::{null_mut, NonNull};
+
+/// Wrapper around an opaque C `ANativeWindow`.
+#[derive(PartialEq, Eq)]
+pub struct Surface(NonNull<ANativeWindow>);
+
+impl Surface {
+ /// Returns the current width in pixels of the window surface.
+ pub fn width(&self) -> Result<u32, ErrorCode> {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ let width = unsafe { ANativeWindow_getWidth(self.0.as_ptr()) };
+ width.try_into().map_err(|_| ErrorCode(width))
+ }
+
+ /// Returns the current height in pixels of the window surface.
+ pub fn height(&self) -> Result<u32, ErrorCode> {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ let height = unsafe { ANativeWindow_getHeight(self.0.as_ptr()) };
+ height.try_into().map_err(|_| ErrorCode(height))
+ }
+
+ /// Returns the current pixel format of the window surface.
+ pub fn format(&self) -> Result<AHardwareBuffer_Format::Type, ErrorCode> {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ let format = unsafe { ANativeWindow_getFormat(self.0.as_ptr()) };
+ format.try_into().map_err(|_| ErrorCode(format))
+ }
+}
+
+impl Drop for Surface {
+ fn drop(&mut self) {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ unsafe { ANativeWindow_release(self.0.as_ptr()) }
+ }
+}
+
+impl Debug for Surface {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ f.debug_struct("Surface")
+ .field("width", &self.width())
+ .field("height", &self.height())
+ .field("format", &self.format())
+ .finish()
+ }
+}
+
+impl Clone for Surface {
+ fn clone(&self) -> Self {
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ unsafe { ANativeWindow_acquire(self.0.as_ptr()) };
+ Self(self.0)
+ }
+}
+
+impl UnstructuredParcelable for Surface {
+ fn write_to_parcel(&self, parcel: &mut BorrowedParcel) -> Result<(), StatusCode> {
+ let status =
+ // SAFETY: The ANativeWindow pointer we pass is guaranteed to be non-null and valid because
+ // it must have been allocated by `ANativeWindow_allocate` or `ANativeWindow_readFromParcel`
+ // and we have not yet released it.
+ unsafe { ANativeWindow_writeToParcel(self.0.as_ptr(), parcel.as_native_mut()) };
+ status_result(status)
+ }
+
+ fn from_parcel(parcel: &BorrowedParcel) -> Result<Self, StatusCode> {
+ let mut buffer = null_mut();
+
+ let status =
+ // SAFETY: Both pointers must be valid because they are obtained from references.
+ // `ANativeWindow_readFromParcel` doesn't store them or do anything else special
+ // with them. If it returns success then it will have allocated a new
+ // `ANativeWindow` and incremented the reference count, so we can use it until we
+ // release it.
+ unsafe { ANativeWindow_readFromParcel(parcel.as_native(), &mut buffer) };
+
+ status_result(status)?;
+
+ Ok(Self(
+ NonNull::new(buffer)
+ .expect("ANativeWindow_readFromParcel returned success but didn't allocate buffer"),
+ ))
+ }
+}
+
+impl_deserialize_for_unstructured_parcelable!(Surface);
+impl_serialize_for_unstructured_parcelable!(Surface);
+
+// SAFETY: The underlying *ANativeWindow can be moved between threads.
+unsafe impl Send for Surface {}
+
+// SAFETY: The underlying *ANativeWindow can be used from multiple threads concurrently.
+unsafe impl Sync for Surface {}
+
+/// An error code returned by methods on [`Surface`].
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub struct ErrorCode(i32);
+
+impl Error for ErrorCode {}
+
+impl Display for ErrorCode {
+ fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+ write!(f, "Error {}", self.0)
+ }
+}
diff --git a/libs/nativewindow/rust/sys/nativewindow_bindings.h b/libs/nativewindow/rust/sys/nativewindow_bindings.h
new file mode 100644
index 0000000000..5689f7df94
--- /dev/null
+++ b/libs/nativewindow/rust/sys/nativewindow_bindings.h
@@ -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.
+ */
+
+#include <android/data_space.h>
+#include <android/hardware_buffer.h>
+#include <android/hardware_buffer_aidl.h>
+#include <android/hdr_metadata.h>
+#include <android/native_window.h>
+#include <android/native_window_aidl.h>
diff --git a/libs/nativewindow/tests/benchmark/Android.bp b/libs/nativewindow/tests/benchmark/Android.bp
new file mode 100644
index 0000000000..6f844cf864
--- /dev/null
+++ b/libs/nativewindow/tests/benchmark/Android.bp
@@ -0,0 +1,50 @@
+// 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.
+
+cc_defaults {
+ name: "nativewindow_benchmark_defaults_cc",
+ shared_libs: ["libnativewindow"],
+ static_libs: [
+ "libbase",
+ "libgoogle-benchmark-main",
+ ],
+ test_suites: [
+ "device-tests",
+ "NativeWindowBenchmarks",
+ ],
+}
+
+cc_benchmark {
+ name: "nativewindow_buffer_benchmarks_cc",
+ srcs: ["buffer_benchmarks.cc"],
+ defaults: ["nativewindow_benchmark_defaults_cc"],
+}
+
+rust_defaults {
+ name: "nativewindow_benchmark_defaults_rs",
+ rustlibs: [
+ "libnativewindow_rs",
+ "libcriterion",
+ ],
+ test_suites: [
+ "device-tests",
+ "NativeWindowBenchmarks",
+ ],
+}
+
+rust_benchmark {
+ name: "nativewindow_buffer_benchmarks_rs",
+ srcs: ["buffer_benchmarks.rs"],
+ defaults: ["nativewindow_benchmark_defaults_rs"],
+}
diff --git a/libs/nativewindow/tests/benchmark/README.md b/libs/nativewindow/tests/benchmark/README.md
new file mode 100644
index 0000000000..7eae538dd2
--- /dev/null
+++ b/libs/nativewindow/tests/benchmark/README.md
@@ -0,0 +1,22 @@
+# libnativewindow Benchmarks
+
+This directory contains benchmarks for the C++ and Rust variants of
+libnativewindow.
+
+## Running
+
+It is currently a little tricky to get statistics from Rust benchmarks directly
+from tradefed. But we can hack it by using atest to build/push, then running
+the benchmarks by hand to get stats.
+
+```
+ $ atest nativewindow_buffer_benchmarks_rs nativewindow_buffer_benchmarks_cc -d
+ $ adb shell /data/local/tmp/nativewindow_buffer_benchmarks_cc/x86_64/nativewindow_buffer_benchmarks_cc
+ $ adb shell /data/local/tmp/nativewindow_buffer_benchmarks_rs/x86_64/nativewindow_buffer_benchmarks_rs --bench
+```
+
+## Results
+
+On a remote emulator, the results we see from the benchmarks from Rust and C++
+seem to be roughly equivalent! Allocating/deallocating a 720p buffer takes
+~2.3ms on each.
diff --git a/libs/nativewindow/tests/benchmark/buffer_benchmarks.cc b/libs/nativewindow/tests/benchmark/buffer_benchmarks.cc
new file mode 100644
index 0000000000..9b31993809
--- /dev/null
+++ b/libs/nativewindow/tests/benchmark/buffer_benchmarks.cc
@@ -0,0 +1,74 @@
+// 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 <android-base/macros.h>
+#include <android/hardware_buffer.h>
+#include <benchmark/benchmark.h>
+
+constexpr AHardwareBuffer_Desc k720pDesc = {.width = 1280,
+ .height = 720,
+ .layers = 1,
+ .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ .usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ .stride = 0};
+
+static void BM_BufferAllocationDeallocation(benchmark::State& state) {
+ AHardwareBuffer* buffer = nullptr;
+ for (auto _ : state) {
+ int status = AHardwareBuffer_allocate(&k720pDesc, &buffer);
+ if (UNLIKELY(status != 0)) {
+ state.SkipWithError("Unable to allocate buffer.");
+ }
+ AHardwareBuffer_release(buffer);
+ buffer = nullptr;
+ }
+}
+BENCHMARK(BM_BufferAllocationDeallocation);
+
+static void BM_AHardwareBuffer_Id(benchmark::State& state) {
+ AHardwareBuffer* buffer = nullptr;
+ int status = AHardwareBuffer_allocate(&k720pDesc, &buffer);
+ if (UNLIKELY(status != 0)) {
+ state.SkipWithError("Unable to allocate buffer.");
+ }
+
+ for (auto _ : state) {
+ uint64_t id = 0;
+ int status = AHardwareBuffer_getId(buffer, &id);
+ if (UNLIKELY(status != 0)) {
+ state.SkipWithError("Unable to get ID.");
+ }
+ }
+
+ AHardwareBuffer_release(buffer);
+}
+BENCHMARK(BM_AHardwareBuffer_Id);
+
+static void BM_AHardwareBuffer_Desc(benchmark::State& state) {
+ AHardwareBuffer* buffer = nullptr;
+ int status = AHardwareBuffer_allocate(&k720pDesc, &buffer);
+ if (UNLIKELY(status != 0)) {
+ state.SkipWithError("Unable to allocate buffer.");
+ }
+
+ for (auto _ : state) {
+ AHardwareBuffer_Desc desc = {};
+ AHardwareBuffer_describe(buffer, &desc);
+ }
+
+ AHardwareBuffer_release(buffer);
+}
+BENCHMARK(BM_AHardwareBuffer_Desc);
+
+BENCHMARK_MAIN();
diff --git a/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs b/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs
new file mode 100644
index 0000000000..876f6c8e26
--- /dev/null
+++ b/libs/nativewindow/tests/benchmark/buffer_benchmarks.rs
@@ -0,0 +1,60 @@
+// 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.
+
+//! Benchmark for libnativewindow AHardwareBuffer bindings
+
+#![allow(dead_code)]
+#![allow(missing_docs)]
+
+use criterion::*;
+use nativewindow::*;
+
+#[inline]
+fn create_720p_buffer() -> HardwareBuffer {
+ HardwareBuffer::new(
+ 1280,
+ 720,
+ 1,
+ AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+ AHardwareBuffer_UsageFlags::AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN,
+ )
+ .unwrap()
+}
+
+fn criterion_benchmark(c: &mut Criterion) {
+ c.bench_function("allocate_deallocate", |b| {
+ b.iter(|| {
+ let buffer = create_720p_buffer();
+ drop(buffer);
+ })
+ });
+
+ let buffer = create_720p_buffer();
+ c.bench_with_input(BenchmarkId::new("id", "buffer"), &buffer, |b, buffer| {
+ b.iter(|| {
+ buffer.id();
+ })
+ });
+
+ // This benchmark exercises getters that need to fetch data via an
+ // underlying call to AHardwareBuffer_describe.
+ c.bench_with_input(BenchmarkId::new("desc", "buffer"), &buffer, |b, buffer| {
+ b.iter(|| {
+ buffer.width();
+ })
+ });
+}
+
+criterion_group!(benches, criterion_benchmark);
+criterion_main!(benches);
diff --git a/libs/permission/AppOpsManager.cpp b/libs/permission/AppOpsManager.cpp
index baa9d75116..695927418d 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<IAppOpsCallback>& callback) {
+ sp<IAppOpsService> service = getService();
+ if (service != nullptr) {
+ service->startWatchingModeWithFlags(op, packageName, flags, callback);
+ }
+}
+
void AppOpsManager::stopWatchingMode(const sp<IAppOpsCallback>& callback) {
sp<IAppOpsService> service = getService();
if (service != nullptr) {
diff --git a/libs/permission/IAppOpsService.cpp b/libs/permission/IAppOpsService.cpp
index d59f44562e..7f235a4541 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<IAppOpsCallback>& 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/aidl/android/content/AttributionSourceState.aidl b/libs/permission/aidl/android/content/AttributionSourceState.aidl
index ed1b37dc0b..b3fb7a7719 100644
--- a/libs/permission/aidl/android/content/AttributionSourceState.aidl
+++ b/libs/permission/aidl/android/content/AttributionSourceState.aidl
@@ -27,6 +27,10 @@ parcelable AttributionSourceState {
int pid = -1;
/** The UID that is accessing the permission protected data. */
int uid = -1;
+ /** The default device ID from where the permission protected data is read.
+ * @see Context#DEVICE_ID_DEFAULT
+ */
+ int deviceId = 0;
/** The package that is accessing the permission protected data. */
@nullable @utf8InCpp String packageName;
/** The attribution tag of the app accessing the permission protected data. */
diff --git a/libs/permission/include/binder/AppOpsManager.h b/libs/permission/include/binder/AppOpsManager.h
index abcd527966..243532bc4d 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<String16>& attributionTag);
void startWatchingMode(int32_t op, const String16& packageName,
const sp<IAppOpsCallback>& callback);
+ void startWatchingMode(int32_t op, const String16& packageName, int32_t flags,
+ const sp<IAppOpsCallback>& callback);
void stopWatchingMode(const sp<IAppOpsCallback>& 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 22f056b235..918fcdbce1 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<IAppOpsCallback>& 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 f6f57dde7d..ba2eb7d224 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: [
@@ -53,30 +56,8 @@ cc_defaults {
filegroup {
name: "librenderengine_sources",
srcs: [
- "Description.cpp",
"ExternalTexture.cpp",
- "Mesh.cpp",
"RenderEngine.cpp",
- "Texture.cpp",
- ],
-}
-
-filegroup {
- name: "librenderengine_gl_sources",
- srcs: [
- "gl/GLESRenderEngine.cpp",
- "gl/GLExtensions.cpp",
- "gl/GLFramebuffer.cpp",
- "gl/GLImage.cpp",
- "gl/GLShadowTexture.cpp",
- "gl/GLShadowVertexGenerator.cpp",
- "gl/GLSkiaShadowPort.cpp",
- "gl/GLVertexBuffer.cpp",
- "gl/ImageManager.cpp",
- "gl/Program.cpp",
- "gl/ProgramCache.cpp",
- "gl/filters/BlurFilter.cpp",
- "gl/filters/GenericProgram.cpp",
],
}
@@ -93,8 +74,10 @@ filegroup {
"skia/AutoBackendTexture.cpp",
"skia/Cache.cpp",
"skia/ColorSpaces.cpp",
+ "skia/GLExtensions.cpp",
"skia/SkiaRenderEngine.cpp",
"skia/SkiaGLRenderEngine.cpp",
+ "skia/SkiaVkRenderEngine.cpp",
"skia/debug/CaptureTimer.cpp",
"skia/debug/CommonPool.cpp",
"skia/debug/SkiaCapture.cpp",
@@ -107,9 +90,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",
@@ -118,7 +115,6 @@ cc_library_static {
],
srcs: [
":librenderengine_sources",
- ":librenderengine_gl_sources",
":librenderengine_threaded_sources",
":librenderengine_skia_sources",
],
@@ -128,7 +124,6 @@ cc_library_static {
include_dirs: [
"external/skia/src/gpu",
],
- whole_static_libs: ["libskia_renderengine"],
lto: {
thin: true,
},
@@ -138,8 +133,6 @@ cc_library_static {
name: "librenderengine_mocks",
defaults: ["librenderengine_defaults"],
srcs: [
- "mock/Framebuffer.cpp",
- "mock/Image.cpp",
"mock/RenderEngine.cpp",
],
static_libs: [
diff --git a/libs/renderengine/Description.cpp b/libs/renderengine/Description.cpp
deleted file mode 100644
index 245c9e1e30..0000000000
--- a/libs/renderengine/Description.cpp
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/private/Description.h>
-
-#include <stdint.h>
-
-#include <utils/TypeHelpers.h>
-
-namespace android {
-namespace renderengine {
-
-Description::TransferFunction Description::dataSpaceToTransferFunction(ui::Dataspace dataSpace) {
- ui::Dataspace transfer = static_cast<ui::Dataspace>(dataSpace & ui::Dataspace::TRANSFER_MASK);
- switch (transfer) {
- case ui::Dataspace::TRANSFER_ST2084:
- return Description::TransferFunction::ST2084;
- case ui::Dataspace::TRANSFER_HLG:
- return Description::TransferFunction::HLG;
- case ui::Dataspace::TRANSFER_LINEAR:
- return Description::TransferFunction::LINEAR;
- default:
- return Description::TransferFunction::SRGB;
- }
-}
-
-bool Description::hasInputTransformMatrix() const {
- const mat4 identity;
- return inputTransformMatrix != identity;
-}
-
-bool Description::hasOutputTransformMatrix() const {
- const mat4 identity;
- return outputTransformMatrix != identity;
-}
-
-bool Description::hasColorMatrix() const {
- const mat4 identity;
- return colorMatrix != identity;
-}
-
-bool Description::hasDisplayColorMatrix() const {
- const mat4 identity;
- return displayColorMatrix != identity;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/ExternalTexture.cpp b/libs/renderengine/ExternalTexture.cpp
index 84771c0917..6f2a96a87b 100644
--- a/libs/renderengine/ExternalTexture.cpp
+++ b/libs/renderengine/ExternalTexture.cpp
@@ -14,32 +14,33 @@
* limitations under the License.
*/
+#include <log/log.h>
#include <renderengine/RenderEngine.h>
#include <renderengine/impl/ExternalTexture.h>
#include <ui/GraphicBuffer.h>
-
-#include "log/log_main.h"
+#include <utils/Trace.h>
namespace android::renderengine::impl {
ExternalTexture::ExternalTexture(const sp<GraphicBuffer>& 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,
- if (usage == WRITEABLE &&
- (mRenderEngine.getRenderEngineType() ==
- renderengine::RenderEngine::RenderEngineType::GLES ||
- mRenderEngine.getRenderEngineType() ==
- 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/Mesh.cpp b/libs/renderengine/Mesh.cpp
deleted file mode 100644
index ed2f45fdf5..0000000000
--- a/libs/renderengine/Mesh.cpp
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/Mesh.h>
-
-#include <utils/Log.h>
-
-namespace android {
-namespace renderengine {
-
-Mesh::Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
- size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize,
- size_t indexCount)
- : mVertexCount(vertexCount),
- mVertexSize(vertexSize),
- mTexCoordsSize(texCoordSize),
- mCropCoordsSize(cropCoordsSize),
- mShadowColorSize(shadowColorSize),
- mShadowParamsSize(shadowParamsSize),
- mPrimitive(primitive),
- mIndexCount(indexCount) {
- if (vertexCount == 0) {
- mVertices.resize(1);
- mVertices[0] = 0.0f;
- mStride = 0;
- return;
- }
- size_t stride = vertexSize + texCoordSize + cropCoordsSize + shadowColorSize + shadowParamsSize;
- size_t remainder = (stride * vertexCount) / vertexCount;
- // Since all of the input parameters are unsigned, if stride is less than
- // either vertexSize or texCoordSize, it must have overflowed. remainder
- // will be equal to stride as long as stride * vertexCount doesn't overflow.
- if ((stride < vertexSize) || (remainder != stride)) {
- ALOGE("Overflow in Mesh(..., %zu, %zu, %zu, %zu, %zu, %zu)", vertexCount, vertexSize,
- texCoordSize, cropCoordsSize, shadowColorSize, shadowParamsSize);
- mVertices.resize(1);
- mVertices[0] = 0.0f;
- mVertexCount = 0;
- mVertexSize = 0;
- mTexCoordsSize = 0;
- mCropCoordsSize = 0;
- mShadowColorSize = 0;
- mShadowParamsSize = 0;
- mStride = 0;
- return;
- }
-
- mVertices.resize(stride * vertexCount);
- mStride = stride;
- mIndices.resize(indexCount);
-}
-
-Mesh::Primitive Mesh::getPrimitive() const {
- return mPrimitive;
-}
-
-float const* Mesh::getPositions() const {
- return mVertices.data();
-}
-float* Mesh::getPositions() {
- return mVertices.data();
-}
-
-float const* Mesh::getTexCoords() const {
- return mVertices.data() + mVertexSize;
-}
-float* Mesh::getTexCoords() {
- return mVertices.data() + mVertexSize;
-}
-
-float const* Mesh::getCropCoords() const {
- return mVertices.data() + mVertexSize + mTexCoordsSize;
-}
-float* Mesh::getCropCoords() {
- return mVertices.data() + mVertexSize + mTexCoordsSize;
-}
-
-float const* Mesh::getShadowColor() const {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
-}
-float* Mesh::getShadowColor() {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize;
-}
-
-float const* Mesh::getShadowParams() const {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
-}
-float* Mesh::getShadowParams() {
- return mVertices.data() + mVertexSize + mTexCoordsSize + mCropCoordsSize + mShadowColorSize;
-}
-
-uint16_t const* Mesh::getIndices() const {
- return mIndices.data();
-}
-
-uint16_t* Mesh::getIndices() {
- return mIndices.data();
-}
-
-size_t Mesh::getVertexCount() const {
- return mVertexCount;
-}
-
-size_t Mesh::getVertexSize() const {
- return mVertexSize;
-}
-
-size_t Mesh::getTexCoordsSize() const {
- return mTexCoordsSize;
-}
-
-size_t Mesh::getShadowColorSize() const {
- return mShadowColorSize;
-}
-
-size_t Mesh::getShadowParamsSize() const {
- return mShadowParamsSize;
-}
-
-size_t Mesh::getByteStride() const {
- return mStride * sizeof(float);
-}
-
-size_t Mesh::getStride() const {
- return mStride;
-}
-
-size_t Mesh::getIndexCount() const {
- return mIndexCount;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/OWNERS b/libs/renderengine/OWNERS
index 5d23a5ef8d..66e1aa1ca1 100644
--- a/libs/renderengine/OWNERS
+++ b/libs/renderengine/OWNERS
@@ -1,3 +1,5 @@
+# Bug component: 1075131
+
adyabr@google.com
alecmouri@google.com
djsollen@google.com
diff --git a/libs/renderengine/RenderEngine.cpp b/libs/renderengine/RenderEngine.cpp
index c7ad058ab9..3e1ac33d57 100644
--- a/libs/renderengine/RenderEngine.cpp
+++ b/libs/renderengine/RenderEngine.cpp
@@ -18,24 +18,23 @@
#include <cutils/properties.h>
#include <log/log.h>
-#include "gl/GLESRenderEngine.h"
+#include "renderengine/ExternalTexture.h"
#include "threaded/RenderEngineThreaded.h"
#include "skia/SkiaGLRenderEngine.h"
+#include "skia/SkiaVkRenderEngine.h"
namespace android {
namespace renderengine {
std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArgs& args) {
switch (args.renderEngineType) {
- case RenderEngineType::THREADED:
- ALOGD("Threaded RenderEngine with GLES Backend");
- return renderengine::threaded::RenderEngineThreaded::create(
- [args]() { return android::renderengine::gl::GLESRenderEngine::create(args); },
- args.renderEngineType);
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,10 +43,13 @@ std::unique_ptr<RenderEngine> RenderEngine::create(const RenderEngineCreationArg
},
args.renderEngineType);
}
- case RenderEngineType::GLES:
- default:
- ALOGD("RenderEngine with GLES Backend");
- return renderengine::gl::GLESRenderEngine::create(args);
+ 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);
}
}
@@ -63,16 +65,27 @@ void RenderEngine::validateOutputBufferUsage(const sp<GraphicBuffer>& buffer) {
"output buffer not gpu writeable");
}
-std::future<RenderEngineResult> RenderEngine::drawLayers(
- const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
- const auto resultPromise = std::make_shared<std::promise<RenderEngineResult>>();
- std::future<RenderEngineResult> resultFuture = resultPromise->get_future();
- drawLayersInternal(std::move(resultPromise), display, layers, buffer, useFramebufferCache,
- std::move(bufferFence));
+ftl::Future<FenceResult> RenderEngine::drawLayers(const DisplaySettings& display,
+ const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer,
+ base::unique_fd&& bufferFence) {
+ const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
+ std::future<FenceResult> resultFuture = resultPromise->get_future();
+ updateProtectedContext(layers, buffer);
+ drawLayersInternal(std::move(resultPromise), display, layers, buffer, std::move(bufferFence));
return resultFuture;
}
+void RenderEngine::updateProtectedContext(const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer) {
+ const bool needsProtectedContext =
+ (buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED)) ||
+ std::any_of(layers.begin(), layers.end(), [](const LayerSettings& layer) {
+ const std::shared_ptr<ExternalTexture>& buffer = layer.source.buffer.buffer;
+ return buffer && (buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
+ });
+ useProtectedContext(needsProtectedContext);
+}
+
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/Texture.cpp b/libs/renderengine/Texture.cpp
deleted file mode 100644
index 154cde80b9..0000000000
--- a/libs/renderengine/Texture.cpp
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <renderengine/Texture.h>
-
-namespace android {
-namespace renderengine {
-
-Texture::Texture()
- : mTextureName(0), mTextureTarget(TEXTURE_2D), mWidth(0), mHeight(0), mFiltering(false) {}
-
-Texture::Texture(Target textureTarget, uint32_t textureName)
- : mTextureName(textureName),
- mTextureTarget(textureTarget),
- mWidth(0),
- mHeight(0),
- mFiltering(false) {}
-
-void Texture::init(Target textureTarget, uint32_t textureName) {
- mTextureName = textureName;
- mTextureTarget = textureTarget;
-}
-
-Texture::~Texture() {}
-
-void Texture::setMatrix(float const* matrix) {
- mTextureMatrix = mat4(matrix);
-}
-
-void Texture::setFiltering(bool enabled) {
- mFiltering = enabled;
-}
-
-void Texture::setDimensions(size_t width, size_t height) {
- mWidth = width;
- mHeight = height;
-}
-
-uint32_t Texture::getTextureName() const {
- return mTextureName;
-}
-
-uint32_t Texture::getTextureTarget() const {
- return mTextureTarget;
-}
-
-const mat4& Texture::getMatrix() const {
- return mTextureMatrix;
-}
-
-bool Texture::getFiltering() const {
- return mFiltering;
-}
-
-size_t Texture::getWidth() const {
- return mWidth;
-}
-
-size_t Texture::getHeight() const {
- return mHeight;
-}
-
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/benchmark/Android.bp b/libs/renderengine/benchmark/Android.bp
index 249fec5866..55c34cd059 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 ead97cf5df..a7f1df9a9b 100644
--- a/libs/renderengine/benchmark/RenderEngineBench.cpp
+++ b/libs/renderengine/benchmark/RenderEngineBench.cpp
@@ -39,10 +39,10 @@ std::string RenderEngineTypeName(RenderEngine::RenderEngineType type) {
return "skiaglthreaded";
case RenderEngine::RenderEngineType::SKIA_GL:
return "skiagl";
- case RenderEngine::RenderEngineType::GLES:
- case RenderEngine::RenderEngineType::THREADED:
- LOG_ALWAYS_FATAL("GLESRenderEngine is deprecated - why time it?");
- return "unused";
+ case RenderEngine::RenderEngineType::SKIA_VK:
+ return "skiavk";
+ case RenderEngine::RenderEngineType::SKIA_VK_THREADED:
+ return "skiavkthreaded";
}
}
@@ -80,24 +80,30 @@ std::pair<uint32_t, uint32_t> 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<uint32_t>(w);
- height = static_cast<uint32_t>(h);
+ width = static_cast<uint32_t>(resolution.width);
+ height = static_cast<uint32_t>(resolution.height);
});
return std::pair<uint32_t, uint32_t>(width, height);
}
-// This value doesn't matter, as it's not read. TODO(b/199918329): Once we remove
-// GLESRenderEngine we can remove this, too.
-static constexpr const bool kUseFrameBufferCache = false;
-
static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngineType type) {
auto args = RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
@@ -107,7 +113,6 @@ static std::unique_ptr<RenderEngine> createRenderEngine(RenderEngine::RenderEngi
.setSupportsBackgroundBlur(true)
.setContextPriority(RenderEngine::ContextPriority::REALTIME)
.setRenderEngineType(type)
- .setUseColorManagerment(true)
.build();
return RenderEngine::create(args);
}
@@ -117,11 +122,12 @@ static std::shared_ptr<ExternalTexture> 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<GraphicBuffer>::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 +164,7 @@ static std::shared_ptr<ExternalTexture> copyBuffer(RenderEngine& re,
};
auto layers = std::vector<LayerSettings>{layer};
- auto [status, drawFence] =
- re.drawLayers(display, layers, texture, kUseFrameBufferCache, base::unique_fd()).get();
- sp<Fence> waitFence = sp<Fence>::make(std::move(drawFence));
+ sp<Fence> waitFence = re.drawLayers(display, layers, texture, base::unique_fd()).get().value();
waitFence->waitForever(LOG_TAG);
return texture;
}
@@ -189,10 +193,8 @@ static void benchDrawLayers(RenderEngine& re, const std::vector<LayerSettings>&
// This loop starts and stops the timer.
for (auto _ : benchState) {
- auto [status, drawFence] = re.drawLayers(display, layers, outputBuffer,
- kUseFrameBufferCache, base::unique_fd())
- .get();
- sp<Fence> waitFence = sp<Fence>::make(std::move(drawFence));
+ sp<Fence> waitFence =
+ re.drawLayers(display, layers, outputBuffer, base::unique_fd()).get().value();
waitFence->waitForever(LOG_TAG);
}
diff --git a/libs/renderengine/gl/GLESRenderEngine.cpp b/libs/renderengine/gl/GLESRenderEngine.cpp
deleted file mode 100644
index 22dd86698b..0000000000
--- a/libs/renderengine/gl/GLESRenderEngine.cpp
+++ /dev/null
@@ -1,1864 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "EGL/egl.h"
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <sched.h>
-#include <cmath>
-#include <fstream>
-#include <sstream>
-#include <unordered_set>
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <android-base/stringprintf.h>
-#include <cutils/compiler.h>
-#include <cutils/properties.h>
-#include <gui/DebugEGLImageTracker.h>
-#include <renderengine/Mesh.h>
-#include <renderengine/Texture.h>
-#include <renderengine/private/Description.h>
-#include <sync/sync.h>
-#include <ui/ColorSpace.h>
-#include <ui/DebugUtils.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/Rect.h>
-#include <ui/Region.h>
-#include <utils/KeyedVector.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "GLExtensions.h"
-#include "GLFramebuffer.h"
-#include "GLImage.h"
-#include "GLShadowVertexGenerator.h"
-#include "Program.h"
-#include "ProgramCache.h"
-#include "filters/BlurFilter.h"
-
-bool checkGlError(const char* op, int lineNumber) {
- bool errorFound = false;
- GLint error = glGetError();
- while (error != GL_NO_ERROR) {
- errorFound = true;
- error = glGetError();
- ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
- }
- return errorFound;
-}
-
-static constexpr bool outputDebugPPMs = false;
-
-void writePPM(const char* basename, GLuint width, GLuint height) {
- ALOGV("writePPM #%s: %d x %d", basename, width, height);
-
- std::vector<GLubyte> pixels(width * height * 4);
- std::vector<GLubyte> outBuffer(width * height * 3);
-
- // TODO(courtneygo): We can now have float formats, need
- // to remove this code or update to support.
- // Make returned pixels fit in uint32_t, one byte per component
- glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data());
- if (checkGlError(__FUNCTION__, __LINE__)) {
- return;
- }
-
- std::string filename(basename);
- filename.append(".ppm");
- std::ofstream file(filename.c_str(), std::ios::binary);
- if (!file.is_open()) {
- ALOGE("Unable to open file: %s", filename.c_str());
- ALOGE("You may need to do: \"adb shell setenforce 0\" to enable "
- "surfaceflinger to write debug images");
- return;
- }
-
- file << "P6\n";
- file << width << "\n";
- file << height << "\n";
- file << 255 << "\n";
-
- auto ptr = reinterpret_cast<char*>(pixels.data());
- auto outPtr = reinterpret_cast<char*>(outBuffer.data());
- for (int y = height - 1; y >= 0; y--) {
- char* data = ptr + y * width * sizeof(uint32_t);
-
- for (GLuint x = 0; x < width; x++) {
- // Only copy R, G and B components
- outPtr[0] = data[0];
- outPtr[1] = data[1];
- outPtr[2] = data[2];
- data += sizeof(uint32_t);
- outPtr += 3;
- }
- }
- file.write(reinterpret_cast<char*>(outBuffer.data()), outBuffer.size());
-}
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class BindNativeBufferAsFramebuffer {
-public:
- BindNativeBufferAsFramebuffer(GLESRenderEngine& engine, ANativeWindowBuffer* buffer,
- const bool useFramebufferCache)
- : mEngine(engine), mFramebuffer(mEngine.getFramebufferForDrawing()), mStatus(NO_ERROR) {
- mStatus = mFramebuffer->setNativeWindowBuffer(buffer, mEngine.isProtected(),
- useFramebufferCache)
- ? mEngine.bindFrameBuffer(mFramebuffer)
- : NO_MEMORY;
- }
- ~BindNativeBufferAsFramebuffer() {
- mFramebuffer->setNativeWindowBuffer(nullptr, false, /*arbitrary*/ true);
- mEngine.unbindFrameBuffer(mFramebuffer);
- }
- status_t getStatus() const { return mStatus; }
-
-private:
- GLESRenderEngine& mEngine;
- Framebuffer* mFramebuffer;
- status_t mStatus;
-};
-
-using base::StringAppendF;
-using ui::Dataspace;
-
-static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute,
- EGLint wanted, EGLConfig* outConfig) {
- EGLint numConfigs = -1, n = 0;
- eglGetConfigs(dpy, nullptr, 0, &numConfigs);
- std::vector<EGLConfig> configs(numConfigs, EGL_NO_CONFIG_KHR);
- eglChooseConfig(dpy, attrs, configs.data(), configs.size(), &n);
- configs.resize(n);
-
- if (!configs.empty()) {
- if (attribute != EGL_NONE) {
- for (EGLConfig config : configs) {
- EGLint value = 0;
- eglGetConfigAttrib(dpy, config, attribute, &value);
- if (wanted == value) {
- *outConfig = config;
- return NO_ERROR;
- }
- }
- } else {
- // just pick the first one
- *outConfig = configs[0];
- return NO_ERROR;
- }
- }
-
- return NAME_NOT_FOUND;
-}
-
-static status_t selectEGLConfig(EGLDisplay display, EGLint format, EGLint renderableType,
- EGLConfig* config) {
- // select our EGLConfig. It must support EGL_RECORDABLE_ANDROID if
- // it is to be used with WIFI displays
- status_t err;
- EGLint wantedAttribute;
- EGLint wantedAttributeValue;
-
- std::vector<EGLint> attribs;
- if (renderableType) {
- const ui::PixelFormat pixelFormat = static_cast<ui::PixelFormat>(format);
- const bool is1010102 = pixelFormat == ui::PixelFormat::RGBA_1010102;
-
- // Default to 8 bits per channel.
- const EGLint tmpAttribs[] = {
- EGL_RENDERABLE_TYPE,
- renderableType,
- EGL_RECORDABLE_ANDROID,
- EGL_TRUE,
- EGL_SURFACE_TYPE,
- EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
- EGL_FRAMEBUFFER_TARGET_ANDROID,
- EGL_TRUE,
- EGL_RED_SIZE,
- is1010102 ? 10 : 8,
- EGL_GREEN_SIZE,
- is1010102 ? 10 : 8,
- EGL_BLUE_SIZE,
- is1010102 ? 10 : 8,
- EGL_ALPHA_SIZE,
- is1010102 ? 2 : 8,
- EGL_NONE,
- };
- std::copy(tmpAttribs, tmpAttribs + (sizeof(tmpAttribs) / sizeof(EGLint)),
- std::back_inserter(attribs));
- wantedAttribute = EGL_NONE;
- wantedAttributeValue = EGL_NONE;
- } else {
- // if no renderable type specified, fallback to a simplified query
- wantedAttribute = EGL_NATIVE_VISUAL_ID;
- wantedAttributeValue = format;
- }
-
- err = selectConfigForAttribute(display, attribs.data(), wantedAttribute, wantedAttributeValue,
- config);
- if (err == NO_ERROR) {
- EGLint caveat;
- if (eglGetConfigAttrib(display, *config, EGL_CONFIG_CAVEAT, &caveat))
- ALOGW_IF(caveat == EGL_SLOW_CONFIG, "EGL_SLOW_CONFIG selected!");
- }
-
- return err;
-}
-
-std::optional<RenderEngine::ContextPriority> GLESRenderEngine::createContextPriority(
- const RenderEngineCreationArgs& args) {
- if (!GLExtensions::getInstance().hasContextPriority()) {
- return std::nullopt;
- }
-
- switch (args.contextPriority) {
- case RenderEngine::ContextPriority::REALTIME:
- if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
- return RenderEngine::ContextPriority::REALTIME;
- } else {
- ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
- return RenderEngine::ContextPriority::HIGH;
- }
- case RenderEngine::ContextPriority::HIGH:
- case RenderEngine::ContextPriority::MEDIUM:
- case RenderEngine::ContextPriority::LOW:
- return args.contextPriority;
- default:
- return std::nullopt;
- }
-}
-
-std::unique_ptr<GLESRenderEngine> GLESRenderEngine::create(const RenderEngineCreationArgs& args) {
- // initialize EGL for the default display
- EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
- if (!eglInitialize(display, nullptr, nullptr)) {
- LOG_ALWAYS_FATAL("failed to initialize EGL. EGL error=0x%x", eglGetError());
- }
-
- const auto eglVersion = eglQueryString(display, EGL_VERSION);
- if (!eglVersion) {
- checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryString(EGL_VERSION) failed");
- }
-
- // Use the Android impl to grab EGL_NV_context_priority_realtime
- const auto eglExtensions = eglQueryString(display, EGL_EXTENSIONS);
- if (!eglExtensions) {
- checkGlError(__FUNCTION__, __LINE__);
- LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
- }
-
- GLExtensions& extensions = GLExtensions::getInstance();
- extensions.initWithEGLStrings(eglVersion, eglExtensions);
-
- // The code assumes that ES2 or later is available if this extension is
- // supported.
- EGLConfig config = EGL_NO_CONFIG;
- if (!extensions.hasNoConfigContext()) {
- config = chooseEglConfig(display, args.pixelFormat, /*logConfig*/ true);
- }
-
- const std::optional<RenderEngine::ContextPriority> priority = createContextPriority(args);
- EGLContext protectedContext = EGL_NO_CONTEXT;
- if (args.enableProtectedContext && extensions.hasProtectedContent()) {
- protectedContext =
- createEglContext(display, config, nullptr, priority, Protection::PROTECTED);
- ALOGE_IF(protectedContext == EGL_NO_CONTEXT, "Can't create protected context");
- }
-
- EGLContext ctxt =
- createEglContext(display, config, protectedContext, priority, Protection::UNPROTECTED);
-
- // if can't create a GL context, we can only abort.
- LOG_ALWAYS_FATAL_IF(ctxt == EGL_NO_CONTEXT, "EGLContext creation failed");
-
- EGLSurface stub = EGL_NO_SURFACE;
- if (!extensions.hasSurfacelessContext()) {
- stub = createStubEglPbufferSurface(display, config, args.pixelFormat,
- Protection::UNPROTECTED);
- LOG_ALWAYS_FATAL_IF(stub == EGL_NO_SURFACE, "can't create stub pbuffer");
- }
- EGLBoolean success = eglMakeCurrent(display, stub, stub, ctxt);
- LOG_ALWAYS_FATAL_IF(!success, "can't make stub pbuffer current");
- extensions.initWithGLStrings(glGetString(GL_VENDOR), glGetString(GL_RENDERER),
- glGetString(GL_VERSION), glGetString(GL_EXTENSIONS));
-
- EGLSurface protectedStub = EGL_NO_SURFACE;
- if (protectedContext != EGL_NO_CONTEXT && !extensions.hasSurfacelessContext()) {
- protectedStub = createStubEglPbufferSurface(display, config, args.pixelFormat,
- Protection::PROTECTED);
- ALOGE_IF(protectedStub == EGL_NO_SURFACE, "can't create protected stub pbuffer");
- }
-
- // now figure out what version of GL did we actually get
- GlesVersion version = parseGlesVersion(extensions.getVersion());
-
- LOG_ALWAYS_FATAL_IF(args.supportsBackgroundBlur && version < GLES_VERSION_3_0,
- "Blurs require OpenGL ES 3.0. Please unset ro.surface_flinger.supports_background_blur");
-
- // initialize the renderer while GL is current
- std::unique_ptr<GLESRenderEngine> engine;
- switch (version) {
- case GLES_VERSION_1_0:
- case GLES_VERSION_1_1:
- LOG_ALWAYS_FATAL("SurfaceFlinger requires OpenGL ES 2.0 minimum to run.");
- break;
- case GLES_VERSION_2_0:
- case GLES_VERSION_3_0:
- engine = std::make_unique<GLESRenderEngine>(args, display, config, ctxt, stub,
- protectedContext, protectedStub);
- break;
- }
-
- ALOGI("OpenGL ES informations:");
- ALOGI("vendor : %s", extensions.getVendor());
- ALOGI("renderer : %s", extensions.getRenderer());
- ALOGI("version : %s", extensions.getVersion());
- ALOGI("extensions: %s", extensions.getExtensions());
- ALOGI("GL_MAX_TEXTURE_SIZE = %zu", engine->getMaxTextureSize());
- ALOGI("GL_MAX_VIEWPORT_DIMS = %zu", engine->getMaxViewportDims());
- return engine;
-}
-
-EGLConfig GLESRenderEngine::chooseEglConfig(EGLDisplay display, int format, bool logConfig) {
- status_t err;
- EGLConfig config;
-
- // First try to get an ES3 config
- err = selectEGLConfig(display, format, EGL_OPENGL_ES3_BIT, &config);
- if (err != NO_ERROR) {
- // If ES3 fails, try to get an ES2 config
- err = selectEGLConfig(display, format, EGL_OPENGL_ES2_BIT, &config);
- if (err != NO_ERROR) {
- // If ES2 still doesn't work, probably because we're on the emulator.
- // try a simplified query
- ALOGW("no suitable EGLConfig found, trying a simpler query");
- err = selectEGLConfig(display, format, 0, &config);
- if (err != NO_ERROR) {
- // this EGL is too lame for android
- LOG_ALWAYS_FATAL("no suitable EGLConfig found, giving up");
- }
- }
- }
-
- if (logConfig) {
- // print some debugging info
- EGLint r, g, b, a;
- eglGetConfigAttrib(display, config, EGL_RED_SIZE, &r);
- eglGetConfigAttrib(display, config, EGL_GREEN_SIZE, &g);
- eglGetConfigAttrib(display, config, EGL_BLUE_SIZE, &b);
- eglGetConfigAttrib(display, config, EGL_ALPHA_SIZE, &a);
- ALOGI("EGL information:");
- ALOGI("vendor : %s", eglQueryString(display, EGL_VENDOR));
- ALOGI("version : %s", eglQueryString(display, EGL_VERSION));
- ALOGI("extensions: %s", eglQueryString(display, EGL_EXTENSIONS));
- ALOGI("Client API: %s", eglQueryString(display, EGL_CLIENT_APIS) ?: "Not Supported");
- ALOGI("EGLSurface: %d-%d-%d-%d, config=%p", r, g, b, a, config);
- }
-
- return config;
-}
-
-GLESRenderEngine::GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display,
- EGLConfig config, EGLContext ctxt, EGLSurface stub,
- EGLContext protectedContext, EGLSurface protectedStub)
- : RenderEngine(args.renderEngineType),
- mEGLDisplay(display),
- mEGLConfig(config),
- mEGLContext(ctxt),
- mStubSurface(stub),
- mProtectedEGLContext(protectedContext),
- mProtectedStubSurface(protectedStub),
- mVpWidth(0),
- mVpHeight(0),
- mFramebufferImageCacheSize(args.imageCacheSize),
- mUseColorManagement(args.useColorManagement),
- mPrecacheToneMapperShaderOnly(args.precacheToneMapperShaderOnly) {
- glGetIntegerv(GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
- glGetIntegerv(GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
-
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glPixelStorei(GL_PACK_ALIGNMENT, 4);
-
- // Initialize protected EGL Context.
- if (mProtectedEGLContext != EGL_NO_CONTEXT) {
- EGLBoolean success = eglMakeCurrent(display, mProtectedStubSurface, mProtectedStubSurface,
- mProtectedEGLContext);
- ALOGE_IF(!success, "can't make protected context current");
- glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
- glPixelStorei(GL_PACK_ALIGNMENT, 4);
- success = eglMakeCurrent(display, mStubSurface, mStubSurface, mEGLContext);
- LOG_ALWAYS_FATAL_IF(!success, "can't make default context current");
- }
-
- // mColorBlindnessCorrection = M;
-
- if (mUseColorManagement) {
- const ColorSpace srgb(ColorSpace::sRGB());
- const ColorSpace displayP3(ColorSpace::DisplayP3());
- const ColorSpace bt2020(ColorSpace::BT2020());
-
- // no chromatic adaptation needed since all color spaces use D65 for their white points.
- mSrgbToXyz = mat4(srgb.getRGBtoXYZ());
- mDisplayP3ToXyz = mat4(displayP3.getRGBtoXYZ());
- mBt2020ToXyz = mat4(bt2020.getRGBtoXYZ());
- mXyzToSrgb = mat4(srgb.getXYZtoRGB());
- mXyzToDisplayP3 = mat4(displayP3.getXYZtoRGB());
- mXyzToBt2020 = mat4(bt2020.getXYZtoRGB());
-
- // Compute sRGB to Display P3 and BT2020 transform matrix.
- // NOTE: For now, we are limiting output wide color space support to
- // Display-P3 and BT2020 only.
- mSrgbToDisplayP3 = mXyzToDisplayP3 * mSrgbToXyz;
- mSrgbToBt2020 = mXyzToBt2020 * mSrgbToXyz;
-
- // Compute Display P3 to sRGB and BT2020 transform matrix.
- mDisplayP3ToSrgb = mXyzToSrgb * mDisplayP3ToXyz;
- mDisplayP3ToBt2020 = mXyzToBt2020 * mDisplayP3ToXyz;
-
- // Compute BT2020 to sRGB and Display P3 transform matrix
- mBt2020ToSrgb = mXyzToSrgb * mBt2020ToXyz;
- mBt2020ToDisplayP3 = mXyzToDisplayP3 * mBt2020ToXyz;
- }
-
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.egl.traceGpuCompletion", value, "0");
- if (atoi(value)) {
- mTraceGpuCompletion = true;
- mFlushTracer = std::make_unique<FlushTracer>(this);
- }
-
- if (args.supportsBackgroundBlur) {
- mBlurFilter = new BlurFilter(*this);
- checkErrors("BlurFilter creation");
- }
-
- mImageManager = std::make_unique<ImageManager>(this);
- mImageManager->initThread();
- mDrawingBuffer = createFramebuffer();
- sp<GraphicBuffer> buf =
- new GraphicBuffer(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) {
- ALOGE("Error allocating placeholder buffer: %d", err);
- return;
- }
- mPlaceholderBuffer = buf.get();
- EGLint attributes[] = {
- EGL_NONE,
- };
- mPlaceholderImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
- mPlaceholderBuffer, attributes);
- ALOGE_IF(mPlaceholderImage == EGL_NO_IMAGE_KHR, "Failed to create placeholder image: %#x",
- eglGetError());
-
- mShadowTexture = std::make_unique<GLShadowTexture>();
-}
-
-GLESRenderEngine::~GLESRenderEngine() {
- // Destroy the image manager first.
- mImageManager = nullptr;
- mShadowTexture = nullptr;
- cleanFramebufferCache();
- ProgramCache::getInstance().purgeCaches();
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- glDisableVertexAttribArray(Program::position);
- unbindFrameBuffer(mDrawingBuffer.get());
- mDrawingBuffer = nullptr;
- eglDestroyImageKHR(mEGLDisplay, mPlaceholderImage);
- mImageCache.clear();
- if (mStubSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEGLDisplay, mStubSurface);
- }
- if (mProtectedStubSurface != EGL_NO_SURFACE) {
- eglDestroySurface(mEGLDisplay, mProtectedStubSurface);
- }
- if (mEGLContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEGLDisplay, mEGLContext);
- }
- if (mProtectedEGLContext != EGL_NO_CONTEXT) {
- eglDestroyContext(mEGLDisplay, mProtectedEGLContext);
- }
- eglMakeCurrent(mEGLDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
- eglTerminate(mEGLDisplay);
- eglReleaseThread();
-}
-
-std::unique_ptr<Framebuffer> GLESRenderEngine::createFramebuffer() {
- return std::make_unique<GLFramebuffer>(*this);
-}
-
-std::unique_ptr<Image> GLESRenderEngine::createImage() {
- return std::make_unique<GLImage>(*this);
-}
-
-Framebuffer* GLESRenderEngine::getFramebufferForDrawing() {
- return mDrawingBuffer.get();
-}
-
-std::future<void> GLESRenderEngine::primeCache() {
- ProgramCache::getInstance().primeCache(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
- mUseColorManagement, mPrecacheToneMapperShaderOnly);
- return {};
-}
-
-base::unique_fd GLESRenderEngine::flush() {
- ATRACE_CALL();
- if (!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();
- }
-
- // native fence fd will not be populated until flush() is done.
- glFlush();
-
- // 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());
- }
-
- // Only trace if we have a valid fence, as current usage falls back to
- // calling finish() if the fence fd is invalid.
- if (CC_UNLIKELY(mTraceGpuCompletion && mFlushTracer) && fenceFd.get() >= 0) {
- mFlushTracer->queueSync(eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr));
- }
-
- return fenceFd;
-}
-
-bool GLESRenderEngine::finish() {
- ATRACE_CALL();
- if (!GLExtensions::getInstance().hasFenceSync()) {
- ALOGW("no synchronization support");
- return false;
- }
-
- EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr);
- if (sync == EGL_NO_SYNC_KHR) {
- ALOGW("failed to create EGL fence sync: %#x", eglGetError());
- return false;
- }
-
- if (CC_UNLIKELY(mTraceGpuCompletion && mFlushTracer)) {
- mFlushTracer->queueSync(eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_FENCE_KHR, nullptr));
- }
-
- return waitSync(sync, EGL_SYNC_FLUSH_COMMANDS_BIT_KHR);
-}
-
-bool GLESRenderEngine::waitSync(EGLSyncKHR sync, EGLint flags) {
- EGLint result = eglClientWaitSyncKHR(mEGLDisplay, sync, flags, 2000000000 /*2 sec*/);
- EGLint error = eglGetError();
- eglDestroySyncKHR(mEGLDisplay, sync);
- if (result != EGL_CONDITION_SATISFIED_KHR) {
- if (result == EGL_TIMEOUT_EXPIRED_KHR) {
- ALOGW("fence wait timed out");
- } else {
- ALOGW("error waiting on EGL fence: %#x", error);
- }
- return false;
- }
-
- return true;
-}
-
-bool GLESRenderEngine::waitFence(base::unique_fd fenceFd) {
- if (!GLExtensions::getInstance().hasNativeFenceSync() ||
- !GLExtensions::getInstance().hasWaitSync()) {
- return false;
- }
-
- // release the fd and transfer the ownership to EGLSync
- EGLint attribs[] = {EGL_SYNC_NATIVE_FENCE_FD_ANDROID, fenceFd.release(), EGL_NONE};
- EGLSyncKHR sync = eglCreateSyncKHR(mEGLDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
- if (sync == EGL_NO_SYNC_KHR) {
- ALOGE("failed to create EGL native fence sync: %#x", eglGetError());
- return false;
- }
-
- // XXX: The spec draft is inconsistent as to whether this should return an
- // EGLint or void. Ignore the return value for now, as it's not strictly
- // needed.
- eglWaitSyncKHR(mEGLDisplay, sync, 0);
- EGLint error = eglGetError();
- eglDestroySyncKHR(mEGLDisplay, sync);
- if (error != EGL_SUCCESS) {
- ALOGE("failed to wait for EGL native fence sync: %#x", error);
- return false;
- }
-
- return true;
-}
-
-void GLESRenderEngine::clearWithColor(float red, float green, float blue, float alpha) {
- ATRACE_CALL();
- glDisable(GL_BLEND);
- glClearColor(red, green, blue, alpha);
- glClear(GL_COLOR_BUFFER_BIT);
-}
-
-void GLESRenderEngine::fillRegionWithColor(const Region& region, float red, float green, float blue,
- float alpha) {
- size_t c;
- Rect const* r = region.getArray(&c);
- Mesh mesh = Mesh::Builder()
- .setPrimitive(Mesh::TRIANGLES)
- .setVertices(c * 6 /* count */, 2 /* size */)
- .build();
- Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
- for (size_t i = 0; i < c; i++, r++) {
- position[i * 6 + 0].x = r->left;
- position[i * 6 + 0].y = r->top;
- position[i * 6 + 1].x = r->left;
- position[i * 6 + 1].y = r->bottom;
- position[i * 6 + 2].x = r->right;
- position[i * 6 + 2].y = r->bottom;
- position[i * 6 + 3].x = r->left;
- position[i * 6 + 3].y = r->top;
- position[i * 6 + 4].x = r->right;
- position[i * 6 + 4].y = r->bottom;
- position[i * 6 + 5].x = r->right;
- position[i * 6 + 5].y = r->top;
- }
- setupFillWithColor(red, green, blue, alpha);
- drawMesh(mesh);
-}
-
-void GLESRenderEngine::setScissor(const Rect& region) {
- glScissor(region.left, region.top, region.getWidth(), region.getHeight());
- glEnable(GL_SCISSOR_TEST);
-}
-
-void GLESRenderEngine::disableScissor() {
- glDisable(GL_SCISSOR_TEST);
-}
-
-void GLESRenderEngine::genTextures(size_t count, uint32_t* names) {
- glGenTextures(count, names);
-}
-
-void GLESRenderEngine::deleteTextures(size_t count, uint32_t const* names) {
- for (int i = 0; i < count; ++i) {
- mTextureView.erase(names[i]);
- }
- glDeleteTextures(count, names);
-}
-
-void GLESRenderEngine::bindExternalTextureImage(uint32_t texName, const Image& image) {
- ATRACE_CALL();
- const GLImage& glImage = static_cast<const GLImage&>(image);
- const GLenum target = GL_TEXTURE_EXTERNAL_OES;
-
- glBindTexture(target, texName);
- if (glImage.getEGLImage() != EGL_NO_IMAGE_KHR) {
- glEGLImageTargetTexture2DOES(target, static_cast<GLeglImageOES>(glImage.getEGLImage()));
- }
-}
-
-void GLESRenderEngine::bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
- const sp<Fence>& bufferFence) {
- ATRACE_CALL();
-
- bool found = false;
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- auto cachedImage = mImageCache.find(buffer->getId());
- found = (cachedImage != mImageCache.end());
- }
-
- // If we couldn't find the image in the cache at this time, then either
- // SurfaceFlinger messed up registering the buffer ahead of time or we got
- // backed up creating other EGLImages.
- if (!found) {
- status_t cacheResult = mImageManager->cache(buffer);
- if (cacheResult != NO_ERROR) {
- ALOGE("Error with caching buffer: %d", cacheResult);
- return;
- }
- }
-
- // Whether or not we needed to cache, re-check mImageCache to make sure that
- // there's an EGLImage. The current threading model guarantees that we don't
- // destroy a cached image until it's really not needed anymore (i.e. this
- // function should not be called), so the only possibility is that something
- // terrible went wrong and we should just bind something and move on.
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- auto cachedImage = mImageCache.find(buffer->getId());
-
- if (cachedImage == mImageCache.end()) {
- // We failed creating the image if we got here, so bail out.
- ALOGE("Failed to create an EGLImage when rendering");
- bindExternalTextureImage(texName, *createImage());
- return;
- }
-
- bindExternalTextureImage(texName, *cachedImage->second);
- mTextureView.insert_or_assign(texName, buffer->getId());
- }
-
- // Wait for the new buffer to be ready.
- if (bufferFence != nullptr && bufferFence->isValid()) {
- if (GLExtensions::getInstance().hasWaitSync()) {
- base::unique_fd fenceFd(bufferFence->dup());
- if (fenceFd == -1) {
- ALOGE("error dup'ing fence fd: %d", errno);
- return;
- }
- if (!waitFence(std::move(fenceFd))) {
- ALOGE("failed to wait on fence fd");
- return;
- }
- } else {
- status_t err = bufferFence->waitForever("RenderEngine::bindExternalTextureBuffer");
- if (err != NO_ERROR) {
- ALOGE("error waiting for fence: %d", err);
- return;
- }
- }
- }
-
- return;
-}
-
-void GLESRenderEngine::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
- bool /*isRenderable*/) {
- ATRACE_CALL();
- mImageManager->cacheAsync(buffer, nullptr);
-}
-
-std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::cacheExternalTextureBufferForTesting(
- const sp<GraphicBuffer>& buffer) {
- auto barrier = std::make_shared<ImageManager::Barrier>();
- mImageManager->cacheAsync(buffer, barrier);
- return barrier;
-}
-
-status_t GLESRenderEngine::cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer) {
- if (buffer == nullptr) {
- return BAD_VALUE;
- }
-
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- if (mImageCache.count(buffer->getId()) > 0) {
- // If there's already an image then fail fast here.
- return NO_ERROR;
- }
- }
- ATRACE_CALL();
-
- // Create the image without holding a lock so that we don't block anything.
- std::unique_ptr<Image> newImage = createImage();
-
- bool created = newImage->setNativeWindowBuffer(buffer->getNativeBuffer(),
- buffer->getUsage() & GRALLOC_USAGE_PROTECTED);
- if (!created) {
- ALOGE("Failed to create image. id=%" PRIx64 " size=%ux%u st=%u usage=%#" PRIx64 " fmt=%d",
- buffer->getId(), buffer->getWidth(), buffer->getHeight(), buffer->getStride(),
- buffer->getUsage(), buffer->getPixelFormat());
- return NO_INIT;
- }
-
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- if (mImageCache.count(buffer->getId()) > 0) {
- // In theory it's possible for another thread to recache the image,
- // so bail out if another thread won.
- return NO_ERROR;
- }
- mImageCache.insert(std::make_pair(buffer->getId(), std::move(newImage)));
- }
-
- return NO_ERROR;
-}
-
-void GLESRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
- mImageManager->releaseAsync(buffer->getId(), nullptr);
-}
-
-std::shared_ptr<ImageManager::Barrier> GLESRenderEngine::unbindExternalTextureBufferForTesting(
- uint64_t bufferId) {
- auto barrier = std::make_shared<ImageManager::Barrier>();
- mImageManager->releaseAsync(bufferId, barrier);
- return barrier;
-}
-
-void GLESRenderEngine::unbindExternalTextureBufferInternal(uint64_t bufferId) {
- std::unique_ptr<Image> image;
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- const auto& cachedImage = mImageCache.find(bufferId);
-
- if (cachedImage != mImageCache.end()) {
- ALOGV("Destroying image for buffer: %" PRIu64, bufferId);
- // Move the buffer out of cache first, so that we can destroy
- // without holding the cache's lock.
- image = std::move(cachedImage->second);
- mImageCache.erase(bufferId);
- return;
- }
- }
- ALOGV("Failed to find image for buffer: %" PRIu64, bufferId);
-}
-
-int GLESRenderEngine::getContextPriority() {
- int value;
- eglQueryContext(mEGLDisplay, mEGLContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &value);
- return value;
-}
-
-FloatRect GLESRenderEngine::setupLayerCropping(const LayerSettings& layer, Mesh& mesh) {
- // Translate win by the rounded corners rect coordinates, to have all values in
- // layer coordinate space.
- FloatRect cropWin = layer.geometry.boundaries;
- const FloatRect& roundedCornersCrop = layer.geometry.roundedCornersCrop;
- cropWin.left -= roundedCornersCrop.left;
- cropWin.right -= roundedCornersCrop.left;
- cropWin.top -= roundedCornersCrop.top;
- cropWin.bottom -= roundedCornersCrop.top;
- Mesh::VertexArray<vec2> cropCoords(mesh.getCropCoordArray<vec2>());
- cropCoords[0] = vec2(cropWin.left, cropWin.top);
- cropCoords[1] = vec2(cropWin.left, cropWin.top + cropWin.getHeight());
- cropCoords[2] = vec2(cropWin.right, cropWin.top + cropWin.getHeight());
- cropCoords[3] = vec2(cropWin.right, cropWin.top);
-
- setupCornerRadiusCropSize(roundedCornersCrop.getWidth(), roundedCornersCrop.getHeight());
- return cropWin;
-}
-
-void GLESRenderEngine::handleRoundedCorners(const DisplaySettings& display,
- const LayerSettings& layer, const Mesh& mesh) {
- // We separate the layer into 3 parts essentially, such that we only turn on blending for the
- // top rectangle and the bottom rectangle, and turn off blending for the middle rectangle.
- FloatRect bounds = layer.geometry.roundedCornersCrop;
-
- // Explicitly compute the transform from the clip rectangle to the physical
- // display. Normally, this is done in glViewport but we explicitly compute
- // it here so that we can get the scissor bounds correct.
- const Rect& source = display.clip;
- const Rect& destination = display.physicalDisplay;
- // Here we compute the following transform:
- // 1. Translate the top left corner of the source clip to (0, 0)
- // 2. Rotate the clip rectangle about the origin in accordance with the
- // orientation flag
- // 3. Translate the top left corner back to the origin.
- // 4. Scale the clip rectangle to the destination rectangle dimensions
- // 5. Translate the top left corner to the destination rectangle's top left
- // corner.
- const mat4 translateSource = mat4::translate(vec4(-source.left, -source.top, 0, 1));
- mat4 rotation;
- int displacementX = 0;
- int displacementY = 0;
- float destinationWidth = static_cast<float>(destination.getWidth());
- float destinationHeight = static_cast<float>(destination.getHeight());
- float sourceWidth = static_cast<float>(source.getWidth());
- float sourceHeight = static_cast<float>(source.getHeight());
- const float rot90InRadians = 2.0f * static_cast<float>(M_PI) / 4.0f;
- switch (display.orientation) {
- case ui::Transform::ROT_90:
- rotation = mat4::rotate(rot90InRadians, vec3(0, 0, 1));
- displacementX = source.getHeight();
- std::swap(sourceHeight, sourceWidth);
- break;
- case ui::Transform::ROT_180:
- rotation = mat4::rotate(rot90InRadians * 2.0f, vec3(0, 0, 1));
- displacementY = source.getHeight();
- displacementX = source.getWidth();
- break;
- case ui::Transform::ROT_270:
- rotation = mat4::rotate(rot90InRadians * 3.0f, vec3(0, 0, 1));
- displacementY = source.getWidth();
- std::swap(sourceHeight, sourceWidth);
- break;
- default:
- break;
- }
-
- const mat4 intermediateTranslation = mat4::translate(vec4(displacementX, displacementY, 0, 1));
- const mat4 scale = mat4::scale(
- vec4(destinationWidth / sourceWidth, destinationHeight / sourceHeight, 1, 1));
- const mat4 translateDestination =
- mat4::translate(vec4(destination.left, destination.top, 0, 1));
- const mat4 globalTransform =
- translateDestination * scale * intermediateTranslation * rotation * translateSource;
-
- const mat4 transformMatrix = globalTransform * layer.geometry.positionTransform;
- const vec4 leftTopCoordinate(bounds.left, bounds.top, 1.0, 1.0);
- const vec4 rightBottomCoordinate(bounds.right, bounds.bottom, 1.0, 1.0);
- const vec4 leftTopCoordinateInBuffer = transformMatrix * leftTopCoordinate;
- const vec4 rightBottomCoordinateInBuffer = transformMatrix * rightBottomCoordinate;
- bounds = FloatRect(std::min(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
- std::min(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]),
- std::max(leftTopCoordinateInBuffer[0], rightBottomCoordinateInBuffer[0]),
- std::max(leftTopCoordinateInBuffer[1], rightBottomCoordinateInBuffer[1]));
-
- // Finally, we cut the layer into 3 parts, with top and bottom parts having rounded corners
- // and the middle part without rounded corners.
- const int32_t radius = ceil(layer.geometry.roundedCornersRadius);
- const Rect topRect(bounds.left, bounds.top, bounds.right, bounds.top + radius);
- setScissor(topRect);
- drawMesh(mesh);
- const Rect bottomRect(bounds.left, bounds.bottom - radius, bounds.right, bounds.bottom);
- setScissor(bottomRect);
- drawMesh(mesh);
-
- // The middle part of the layer can turn off blending.
- if (topRect.bottom < bottomRect.top) {
- const Rect middleRect(bounds.left, bounds.top + radius, bounds.right,
- bounds.bottom - radius);
- setScissor(middleRect);
- mState.cornerRadius = 0.0;
- disableBlending();
- drawMesh(mesh);
- }
- disableScissor();
-}
-
-status_t GLESRenderEngine::bindFrameBuffer(Framebuffer* framebuffer) {
- ATRACE_CALL();
- GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(framebuffer);
- EGLImageKHR eglImage = glFramebuffer->getEGLImage();
- uint32_t textureName = glFramebuffer->getTextureName();
- uint32_t framebufferName = glFramebuffer->getFramebufferName();
-
- // Bind the texture and turn our EGLImage into a texture
- glBindTexture(GL_TEXTURE_2D, textureName);
- glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)eglImage);
-
- // Bind the Framebuffer to render into
- glBindFramebuffer(GL_FRAMEBUFFER, framebufferName);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureName, 0);
-
- uint32_t glStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- ALOGE_IF(glStatus != GL_FRAMEBUFFER_COMPLETE_OES, "glCheckFramebufferStatusOES error %d",
- glStatus);
-
- return glStatus == GL_FRAMEBUFFER_COMPLETE_OES ? NO_ERROR : BAD_VALUE;
-}
-
-void GLESRenderEngine::unbindFrameBuffer(Framebuffer* /*framebuffer*/) {
- ATRACE_CALL();
-
- // back to main framebuffer
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-bool GLESRenderEngine::canSkipPostRenderCleanup() const {
- return mPriorResourcesCleaned ||
- (mLastDrawFence != nullptr && mLastDrawFence->getStatus() != Fence::Status::Signaled);
-}
-
-void GLESRenderEngine::cleanupPostRender() {
- ATRACE_CALL();
-
- if (canSkipPostRenderCleanup()) {
- // If we don't have a prior frame needing cleanup, then don't do anything.
- return;
- }
-
- // Bind the texture to placeholder so that backing image data can be freed.
- GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(getFramebufferForDrawing());
- glFramebuffer->allocateBuffers(1, 1, mPlaceholderDrawBuffer);
-
- // Release the cached fence here, so that we don't churn reallocations when
- // we could no-op repeated calls of this method instead.
- mLastDrawFence = nullptr;
- mPriorResourcesCleaned = true;
-}
-
-void GLESRenderEngine::cleanFramebufferCache() {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- // Bind the texture to placeholder so that backing image data can be freed.
- GLFramebuffer* glFramebuffer = static_cast<GLFramebuffer*>(getFramebufferForDrawing());
- glFramebuffer->allocateBuffers(1, 1, mPlaceholderDrawBuffer);
-
- while (!mFramebufferImageCache.empty()) {
- EGLImageKHR expired = mFramebufferImageCache.front().second;
- mFramebufferImageCache.pop_front();
- eglDestroyImageKHR(mEGLDisplay, expired);
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- }
-}
-
-void GLESRenderEngine::checkErrors() const {
- checkErrors(nullptr);
-}
-
-void GLESRenderEngine::checkErrors(const char* tag) const {
- do {
- // there could be more than one error flag
- GLenum error = glGetError();
- if (error == GL_NO_ERROR) break;
- if (tag == nullptr) {
- ALOGE("GL error 0x%04x", int(error));
- } else {
- ALOGE("GL error: %s -> 0x%04x", tag, int(error));
- }
- } while (true);
-}
-
-bool GLESRenderEngine::supportsProtectedContent() const {
- return mProtectedEGLContext != EGL_NO_CONTEXT;
-}
-
-void GLESRenderEngine::useProtectedContext(bool useProtectedContext) {
- if (useProtectedContext == mInProtectedContext ||
- (useProtectedContext && !supportsProtectedContent())) {
- return;
- }
-
- const EGLSurface surface = useProtectedContext ? mProtectedStubSurface : mStubSurface;
- const EGLContext context = useProtectedContext ? mProtectedEGLContext : mEGLContext;
- if (eglMakeCurrent(mEGLDisplay, surface, surface, context) == EGL_TRUE) {
- mInProtectedContext = useProtectedContext;
- }
-}
-EGLImageKHR GLESRenderEngine::createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer,
- bool isProtected,
- bool useFramebufferCache) {
- sp<GraphicBuffer> graphicBuffer = GraphicBuffer::from(nativeBuffer);
- if (useFramebufferCache) {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- for (const auto& image : mFramebufferImageCache) {
- if (image.first == graphicBuffer->getId()) {
- return image.second;
- }
- }
- }
- EGLint attributes[] = {
- isProtected ? EGL_PROTECTED_CONTENT_EXT : EGL_NONE,
- isProtected ? EGL_TRUE : EGL_NONE,
- EGL_NONE,
- };
- EGLImageKHR image = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
- nativeBuffer, attributes);
- if (useFramebufferCache) {
- if (image != EGL_NO_IMAGE_KHR) {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- if (mFramebufferImageCache.size() >= mFramebufferImageCacheSize) {
- EGLImageKHR expired = mFramebufferImageCache.front().second;
- mFramebufferImageCache.pop_front();
- eglDestroyImageKHR(mEGLDisplay, expired);
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- }
- mFramebufferImageCache.push_back({graphicBuffer->getId(), image});
- }
- }
-
- if (image != EGL_NO_IMAGE_KHR) {
- DEBUG_EGL_IMAGE_TRACKER_CREATE();
- }
- return image;
-}
-
-void GLESRenderEngine::drawLayersInternal(
- const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
- const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& 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()});
- return;
- }
-
- if (bufferFence.get() >= 0) {
- // Duplicate the fence for passing to waitFence.
- base::unique_fd bufferFenceDup(dup(bufferFence.get()));
- if (bufferFenceDup < 0 || !waitFence(std::move(bufferFenceDup))) {
- ATRACE_NAME("Waiting before draw");
- sync_wait(bufferFence.get(), -1);
- }
- }
-
- if (buffer == nullptr) {
- ALOGE("No output buffer provided. Aborting GPU composition.");
- resultPromise->set_value({BAD_VALUE, base::unique_fd()});
- return;
- }
-
- validateOutputBufferUsage(buffer->getBuffer());
-
- std::unique_ptr<BindNativeBufferAsFramebuffer> fbo;
- // Gathering layers that requested blur, we'll need them to decide when to render to an
- // offscreen buffer, and when to render to the native buffer.
- std::deque<const LayerSettings> blurLayers;
- if (CC_LIKELY(mBlurFilter != nullptr)) {
- for (const auto& layer : layers) {
- if (layer.backgroundBlurRadius > 0) {
- blurLayers.push_back(layer);
- }
- }
- }
- const auto blurLayersSize = blurLayers.size();
-
- if (blurLayersSize == 0) {
- fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
- buffer->getBuffer()
- .get()
- ->getNativeBuffer(),
- useFramebufferCache);
- if (fbo->getStatus() != NO_ERROR) {
- ALOGE("Failed to bind framebuffer! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors();
- resultPromise->set_value({fbo->getStatus(), base::unique_fd()});
- return;
- }
- setViewportAndProjection(display.physicalDisplay, display.clip);
- } else {
- setViewportAndProjection(display.physicalDisplay, display.clip);
- auto status =
- mBlurFilter->setAsDrawTarget(display, blurLayers.front().backgroundBlurRadius);
- if (status != NO_ERROR) {
- ALOGE("Failed to prepare blur filter! Aborting GPU composition for buffer (%p).",
- buffer->getBuffer()->handle);
- checkErrors();
- resultPromise->set_value({status, base::unique_fd()});
- return;
- }
- }
-
- // clear the entire buffer, sometimes when we reuse buffers we'd persist
- // ghost images otherwise.
- // we also require a full transparent framebuffer for overlays. This is
- // probably not quite efficient on all GPUs, since we could filter out
- // opaque layers.
- clearWithColor(0.0, 0.0, 0.0, 0.0);
-
- setOutputDataSpace(display.outputDataspace);
- setDisplayMaxLuminance(display.maxLuminance);
- setDisplayColorTransform(display.colorTransform);
-
- const mat4 projectionMatrix =
- ui::Transform(display.orientation).asMatrix4() * mState.projectionMatrix;
-
- Mesh mesh = Mesh::Builder()
- .setPrimitive(Mesh::TRIANGLE_FAN)
- .setVertices(4 /* count */, 2 /* size */)
- .setTexCoords(2 /* size */)
- .setCropCoords(2 /* size */)
- .build();
- for (const auto& layer : layers) {
- if (blurLayers.size() > 0 && blurLayers.front() == layer) {
- blurLayers.pop_front();
-
- auto status = mBlurFilter->prepare();
- if (status != NO_ERROR) {
- 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()});
- return;
- }
-
- if (blurLayers.size() == 0) {
- // Done blurring, time to bind the native FBO and render our blur onto it.
- fbo = std::make_unique<BindNativeBufferAsFramebuffer>(*this,
- buffer.get()
- ->getBuffer()
- ->getNativeBuffer(),
- useFramebufferCache);
- status = fbo->getStatus();
- setViewportAndProjection(display.physicalDisplay, display.clip);
- } else {
- // There's still something else to blur, so let's keep rendering to our FBO
- // instead of to the display.
- status = mBlurFilter->setAsDrawTarget(display,
- blurLayers.front().backgroundBlurRadius);
- }
- if (status != NO_ERROR) {
- 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()});
- return;
- }
-
- status = mBlurFilter->render(blurLayersSize > 1);
- if (status != NO_ERROR) {
- 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()});
- return;
- }
- }
-
- // Ensure luminance is at least 100 nits to avoid div-by-zero
- const float maxLuminance = std::max(100.f, layer.source.buffer.maxLuminanceNits);
- mState.maxMasteringLuminance = maxLuminance;
- mState.maxContentLuminance = maxLuminance;
- mState.projectionMatrix = projectionMatrix * layer.geometry.positionTransform;
-
- const FloatRect bounds = layer.geometry.boundaries;
- Mesh::VertexArray<vec2> position(mesh.getPositionArray<vec2>());
- position[0] = vec2(bounds.left, bounds.top);
- position[1] = vec2(bounds.left, bounds.bottom);
- position[2] = vec2(bounds.right, bounds.bottom);
- position[3] = vec2(bounds.right, bounds.top);
-
- setupLayerCropping(layer, mesh);
- setColorTransform(layer.colorTransform);
-
- bool usePremultipliedAlpha = true;
- bool disableTexture = true;
- bool isOpaque = false;
- if (layer.source.buffer.buffer != nullptr) {
- disableTexture = false;
- isOpaque = layer.source.buffer.isOpaque;
-
- sp<GraphicBuffer> gBuf = layer.source.buffer.buffer->getBuffer();
- validateInputBufferUsage(gBuf);
- bindExternalTextureBuffer(layer.source.buffer.textureName, gBuf,
- layer.source.buffer.fence);
-
- usePremultipliedAlpha = layer.source.buffer.usePremultipliedAlpha;
- Texture texture(Texture::TEXTURE_EXTERNAL, layer.source.buffer.textureName);
- mat4 texMatrix = layer.source.buffer.textureTransform;
-
- texture.setMatrix(texMatrix.asArray());
- texture.setFiltering(layer.source.buffer.useTextureFiltering);
-
- texture.setDimensions(gBuf->getWidth(), gBuf->getHeight());
- setSourceY410BT2020(layer.source.buffer.isY410BT2020);
-
- renderengine::Mesh::VertexArray<vec2> texCoords(mesh.getTexCoordArray<vec2>());
- texCoords[0] = vec2(0.0, 0.0);
- texCoords[1] = vec2(0.0, 1.0);
- texCoords[2] = vec2(1.0, 1.0);
- texCoords[3] = vec2(1.0, 0.0);
- setupLayerTexturing(texture);
-
- // Do not cache protected EGLImage, protected memory is limited.
- if (gBuf->getUsage() & GRALLOC_USAGE_PROTECTED) {
- unmapExternalTextureBuffer(gBuf);
- }
- }
-
- const half3 solidColor = layer.source.solidColor;
- const half4 color = half4(solidColor.r, solidColor.g, solidColor.b, layer.alpha);
- // Buffer sources will have a black solid color ignored in the shader,
- // so in that scenario the solid color passed here is arbitrary.
- setupLayerBlending(usePremultipliedAlpha, isOpaque, disableTexture, color,
- layer.geometry.roundedCornersRadius);
- if (layer.disableBlending) {
- glDisable(GL_BLEND);
- }
- setSourceDataSpace(layer.sourceDataspace);
-
- if (layer.shadow.length > 0.0f) {
- handleShadow(layer.geometry.boundaries, layer.geometry.roundedCornersRadius,
- layer.shadow);
- }
- // We only want to do a special handling for rounded corners when having rounded corners
- // is the only reason it needs to turn on blending, otherwise, we handle it like the
- // usual way since it needs to turn on blending anyway.
- else if (layer.geometry.roundedCornersRadius > 0.0 && color.a >= 1.0f && isOpaque) {
- handleRoundedCorners(display, layer, mesh);
- } else {
- drawMesh(mesh);
- }
-
- // Cleanup if there's a buffer source
- if (layer.source.buffer.buffer != nullptr) {
- disableBlending();
- setSourceY410BT2020(false);
- disableTexturing();
- }
- }
-
- 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.
- if (drawFence.get() < 0) {
- bool success = finish();
- if (!success) {
- ALOGE("Failed to flush RenderEngine commands");
- 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)});
- return;
- }
- mLastDrawFence = nullptr;
- } else {
- // The caller takes ownership of drawFence, so we need to duplicate the
- // fd here.
- mLastDrawFence = new Fence(dup(drawFence.get()));
- }
- mPriorResourcesCleaned = false;
-
- checkErrors();
- resultPromise->set_value({NO_ERROR, std::move(drawFence)});
- return;
-}
-
-void GLESRenderEngine::setViewportAndProjection(Rect viewport, Rect clip) {
- ATRACE_CALL();
- mVpWidth = viewport.getWidth();
- mVpHeight = viewport.getHeight();
-
- // We pass the the top left corner instead of the bottom left corner,
- // because since we're rendering off-screen first.
- glViewport(viewport.left, viewport.top, mVpWidth, mVpHeight);
-
- mState.projectionMatrix = mat4::ortho(clip.left, clip.right, clip.top, clip.bottom, 0, 1);
-}
-
-void GLESRenderEngine::setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
- const half4& color, float cornerRadius) {
- mState.isPremultipliedAlpha = premultipliedAlpha;
- mState.isOpaque = opaque;
- mState.color = color;
- mState.cornerRadius = cornerRadius;
-
- if (disableTexture) {
- mState.textureEnabled = false;
- }
-
- if (color.a < 1.0f || !opaque || cornerRadius > 0.0f) {
- glEnable(GL_BLEND);
- glBlendFuncSeparate(premultipliedAlpha ? GL_ONE : GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
- GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
- } else {
- glDisable(GL_BLEND);
- }
-}
-
-void GLESRenderEngine::setSourceY410BT2020(bool enable) {
- mState.isY410BT2020 = enable;
-}
-
-void GLESRenderEngine::setSourceDataSpace(Dataspace source) {
- mDataSpace = source;
-}
-
-void GLESRenderEngine::setOutputDataSpace(Dataspace dataspace) {
- mOutputDataSpace = dataspace;
-}
-
-void GLESRenderEngine::setDisplayMaxLuminance(const float maxLuminance) {
- mState.displayMaxLuminance = maxLuminance;
-}
-
-void GLESRenderEngine::setupLayerTexturing(const Texture& texture) {
- GLuint target = texture.getTextureTarget();
- glBindTexture(target, texture.getTextureName());
- GLenum filter = GL_NEAREST;
- if (texture.getFiltering()) {
- filter = GL_LINEAR;
- }
- glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
- glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
-
- mState.texture = texture;
- mState.textureEnabled = true;
-}
-
-void GLESRenderEngine::setColorTransform(const mat4& colorTransform) {
- mState.colorMatrix = colorTransform;
-}
-
-void GLESRenderEngine::setDisplayColorTransform(const mat4& colorTransform) {
- mState.displayColorMatrix = colorTransform;
-}
-
-void GLESRenderEngine::disableTexturing() {
- mState.textureEnabled = false;
-}
-
-void GLESRenderEngine::disableBlending() {
- glDisable(GL_BLEND);
-}
-
-void GLESRenderEngine::setupFillWithColor(float r, float g, float b, float a) {
- mState.isPremultipliedAlpha = true;
- mState.isOpaque = false;
- mState.color = half4(r, g, b, a);
- mState.textureEnabled = false;
- glDisable(GL_BLEND);
-}
-
-void GLESRenderEngine::setupCornerRadiusCropSize(float width, float height) {
- mState.cropSize = half2(width, height);
-}
-
-void GLESRenderEngine::drawMesh(const Mesh& mesh) {
- ATRACE_CALL();
- if (mesh.getTexCoordsSize()) {
- glEnableVertexAttribArray(Program::texCoords);
- glVertexAttribPointer(Program::texCoords, mesh.getTexCoordsSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getTexCoords());
- }
-
- glVertexAttribPointer(Program::position, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getPositions());
-
- if (mState.cornerRadius > 0.0f) {
- glEnableVertexAttribArray(Program::cropCoords);
- glVertexAttribPointer(Program::cropCoords, mesh.getVertexSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getCropCoords());
- }
-
- if (mState.drawShadows) {
- glEnableVertexAttribArray(Program::shadowColor);
- glVertexAttribPointer(Program::shadowColor, mesh.getShadowColorSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getShadowColor());
-
- glEnableVertexAttribArray(Program::shadowParams);
- glVertexAttribPointer(Program::shadowParams, mesh.getShadowParamsSize(), GL_FLOAT, GL_FALSE,
- mesh.getByteStride(), mesh.getShadowParams());
- }
-
- Description managedState = mState;
- // By default, DISPLAY_P3 is the only supported wide color output. However,
- // when HDR content is present, hardware composer may be able to handle
- // BT2020 data space, in that case, the output data space is set to be
- // BT2020_HLG or BT2020_PQ respectively. In GPU fall back we need
- // to respect this and convert non-HDR content to HDR format.
- if (mUseColorManagement) {
- Dataspace inputStandard = static_cast<Dataspace>(mDataSpace & Dataspace::STANDARD_MASK);
- Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
- Dataspace outputStandard =
- static_cast<Dataspace>(mOutputDataSpace & Dataspace::STANDARD_MASK);
- Dataspace outputTransfer =
- static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
- bool needsXYZConversion = needsXYZTransformMatrix();
-
- // NOTE: if the input standard of the input dataspace is not STANDARD_DCI_P3 or
- // STANDARD_BT2020, it will be treated as STANDARD_BT709
- if (inputStandard != Dataspace::STANDARD_DCI_P3 &&
- inputStandard != Dataspace::STANDARD_BT2020) {
- inputStandard = Dataspace::STANDARD_BT709;
- }
-
- if (needsXYZConversion) {
- // The supported input color spaces are standard RGB, Display P3 and BT2020.
- switch (inputStandard) {
- case Dataspace::STANDARD_DCI_P3:
- managedState.inputTransformMatrix = mDisplayP3ToXyz;
- break;
- case Dataspace::STANDARD_BT2020:
- managedState.inputTransformMatrix = mBt2020ToXyz;
- break;
- default:
- managedState.inputTransformMatrix = mSrgbToXyz;
- break;
- }
-
- // The supported output color spaces are BT2020, Display P3 and standard RGB.
- switch (outputStandard) {
- case Dataspace::STANDARD_BT2020:
- managedState.outputTransformMatrix = mXyzToBt2020;
- break;
- case Dataspace::STANDARD_DCI_P3:
- managedState.outputTransformMatrix = mXyzToDisplayP3;
- break;
- default:
- managedState.outputTransformMatrix = mXyzToSrgb;
- break;
- }
- } else if (inputStandard != outputStandard) {
- // At this point, the input data space and output data space could be both
- // HDR data spaces, but they match each other, we do nothing in this case.
- // In addition to the case above, the input data space could be
- // - scRGB linear
- // - scRGB non-linear
- // - sRGB
- // - Display P3
- // - BT2020
- // The output data spaces could be
- // - sRGB
- // - Display P3
- // - BT2020
- switch (outputStandard) {
- case Dataspace::STANDARD_BT2020:
- if (inputStandard == Dataspace::STANDARD_BT709) {
- managedState.outputTransformMatrix = mSrgbToBt2020;
- } else if (inputStandard == Dataspace::STANDARD_DCI_P3) {
- managedState.outputTransformMatrix = mDisplayP3ToBt2020;
- }
- break;
- case Dataspace::STANDARD_DCI_P3:
- if (inputStandard == Dataspace::STANDARD_BT709) {
- managedState.outputTransformMatrix = mSrgbToDisplayP3;
- } else if (inputStandard == Dataspace::STANDARD_BT2020) {
- managedState.outputTransformMatrix = mBt2020ToDisplayP3;
- }
- break;
- default:
- if (inputStandard == Dataspace::STANDARD_DCI_P3) {
- managedState.outputTransformMatrix = mDisplayP3ToSrgb;
- } else if (inputStandard == Dataspace::STANDARD_BT2020) {
- managedState.outputTransformMatrix = mBt2020ToSrgb;
- }
- break;
- }
- }
-
- // we need to convert the RGB value to linear space and convert it back when:
- // - there is a color matrix that is not an identity matrix, or
- // - there is an output transform matrix that is not an identity matrix, or
- // - the input transfer function doesn't match the output transfer function.
- if (managedState.hasColorMatrix() || managedState.hasOutputTransformMatrix() ||
- inputTransfer != outputTransfer) {
- managedState.inputTransferFunction =
- Description::dataSpaceToTransferFunction(inputTransfer);
- managedState.outputTransferFunction =
- Description::dataSpaceToTransferFunction(outputTransfer);
- }
- }
-
- ProgramCache::getInstance().useProgram(mInProtectedContext ? mProtectedEGLContext : mEGLContext,
- managedState);
-
- if (mState.drawShadows) {
- glDrawElements(mesh.getPrimitive(), mesh.getIndexCount(), GL_UNSIGNED_SHORT,
- mesh.getIndices());
- } else {
- glDrawArrays(mesh.getPrimitive(), 0, mesh.getVertexCount());
- }
-
- if (mUseColorManagement && outputDebugPPMs) {
- static uint64_t managedColorFrameCount = 0;
- std::ostringstream out;
- out << "/data/texture_out" << managedColorFrameCount++;
- writePPM(out.str().c_str(), mVpWidth, mVpHeight);
- }
-
- if (mesh.getTexCoordsSize()) {
- glDisableVertexAttribArray(Program::texCoords);
- }
-
- if (mState.cornerRadius > 0.0f) {
- glDisableVertexAttribArray(Program::cropCoords);
- }
-
- if (mState.drawShadows) {
- glDisableVertexAttribArray(Program::shadowColor);
- glDisableVertexAttribArray(Program::shadowParams);
- }
-}
-
-size_t GLESRenderEngine::getMaxTextureSize() const {
- return mMaxTextureSize;
-}
-
-size_t GLESRenderEngine::getMaxViewportDims() const {
- return mMaxViewportDims[0] < mMaxViewportDims[1] ? mMaxViewportDims[0] : mMaxViewportDims[1];
-}
-
-void GLESRenderEngine::dump(std::string& result) {
- const GLExtensions& extensions = GLExtensions::getInstance();
- ProgramCache& cache = ProgramCache::getInstance();
-
- 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 program cache size for unprotected context: %zu\n",
- cache.getSize(mEGLContext));
- StringAppendF(&result, "RenderEngine program cache size for protected context: %zu\n",
- cache.getSize(mProtectedEGLContext));
- StringAppendF(&result, "RenderEngine last dataspace conversion: (%s) to (%s)\n",
- dataspaceDetails(static_cast<android_dataspace>(mDataSpace)).c_str(),
- dataspaceDetails(static_cast<android_dataspace>(mOutputDataSpace)).c_str());
- {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- StringAppendF(&result, "RenderEngine image cache size: %zu\n", mImageCache.size());
- StringAppendF(&result, "Dumping buffer ids...\n");
- for (const auto& [id, unused] : mImageCache) {
- StringAppendF(&result, "0x%" PRIx64 "\n", id);
- }
- }
- {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- StringAppendF(&result, "RenderEngine framebuffer image cache size: %zu\n",
- mFramebufferImageCache.size());
- StringAppendF(&result, "Dumping buffer ids...\n");
- for (const auto& [id, unused] : mFramebufferImageCache) {
- StringAppendF(&result, "0x%" PRIx64 "\n", id);
- }
- }
-}
-
-GLESRenderEngine::GlesVersion GLESRenderEngine::parseGlesVersion(const char* str) {
- int major, minor;
- if (sscanf(str, "OpenGL ES-CM %d.%d", &major, &minor) != 2) {
- if (sscanf(str, "OpenGL ES %d.%d", &major, &minor) != 2) {
- ALOGW("Unable to parse GL_VERSION string: \"%s\"", str);
- return GLES_VERSION_1_0;
- }
- }
-
- if (major == 1 && minor == 0) return GLES_VERSION_1_0;
- if (major == 1 && minor >= 1) return GLES_VERSION_1_1;
- if (major == 2 && minor >= 0) return GLES_VERSION_2_0;
- if (major == 3 && minor >= 0) return GLES_VERSION_3_0;
-
- ALOGW("Unrecognized OpenGL ES version: %d.%d", major, minor);
- return GLES_VERSION_1_0;
-}
-
-EGLContext GLESRenderEngine::createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext,
- std::optional<ContextPriority> contextPriority,
- Protection protection) {
- EGLint renderableType = 0;
- if (config == EGL_NO_CONFIG) {
- renderableType = EGL_OPENGL_ES3_BIT;
- } else if (!eglGetConfigAttrib(display, config, EGL_RENDERABLE_TYPE, &renderableType)) {
- LOG_ALWAYS_FATAL("can't query EGLConfig RENDERABLE_TYPE");
- }
- EGLint contextClientVersion = 0;
- if (renderableType & EGL_OPENGL_ES3_BIT) {
- contextClientVersion = 3;
- } else if (renderableType & EGL_OPENGL_ES2_BIT) {
- contextClientVersion = 2;
- } else if (renderableType & EGL_OPENGL_ES_BIT) {
- contextClientVersion = 1;
- } else {
- LOG_ALWAYS_FATAL("no supported EGL_RENDERABLE_TYPEs");
- }
-
- std::vector<EGLint> contextAttributes;
- contextAttributes.reserve(7);
- contextAttributes.push_back(EGL_CONTEXT_CLIENT_VERSION);
- contextAttributes.push_back(contextClientVersion);
- if (contextPriority) {
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
- switch (*contextPriority) {
- case ContextPriority::REALTIME:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_REALTIME_NV);
- break;
- case ContextPriority::MEDIUM:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_MEDIUM_IMG);
- break;
- case ContextPriority::LOW:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_LOW_IMG);
- break;
- case ContextPriority::HIGH:
- default:
- contextAttributes.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
- break;
- }
- }
- if (protection == Protection::PROTECTED) {
- contextAttributes.push_back(EGL_PROTECTED_CONTENT_EXT);
- contextAttributes.push_back(EGL_TRUE);
- }
- contextAttributes.push_back(EGL_NONE);
-
- EGLContext context = eglCreateContext(display, config, shareContext, contextAttributes.data());
-
- if (contextClientVersion == 3 && context == EGL_NO_CONTEXT) {
- // eglGetConfigAttrib indicated we can create GLES 3 context, but we failed, thus
- // EGL_NO_CONTEXT so that we can abort.
- if (config != EGL_NO_CONFIG) {
- return context;
- }
- // If |config| is EGL_NO_CONFIG, we speculatively try to create GLES 3 context, so we should
- // try to fall back to GLES 2.
- contextAttributes[1] = 2;
- context = eglCreateContext(display, config, shareContext, contextAttributes.data());
- }
-
- return context;
-}
-
-EGLSurface GLESRenderEngine::createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
- int hwcFormat, Protection protection) {
- EGLConfig stubConfig = config;
- if (stubConfig == EGL_NO_CONFIG) {
- stubConfig = chooseEglConfig(display, hwcFormat, /*logConfig*/ true);
- }
- std::vector<EGLint> attributes;
- attributes.reserve(7);
- attributes.push_back(EGL_WIDTH);
- attributes.push_back(1);
- attributes.push_back(EGL_HEIGHT);
- attributes.push_back(1);
- if (protection == Protection::PROTECTED) {
- attributes.push_back(EGL_PROTECTED_CONTENT_EXT);
- attributes.push_back(EGL_TRUE);
- }
- attributes.push_back(EGL_NONE);
-
- return eglCreatePbufferSurface(display, stubConfig, attributes.data());
-}
-
-bool GLESRenderEngine::isHdrDataSpace(const Dataspace dataSpace) const {
- const Dataspace standard = static_cast<Dataspace>(dataSpace & Dataspace::STANDARD_MASK);
- const Dataspace transfer = static_cast<Dataspace>(dataSpace & Dataspace::TRANSFER_MASK);
- return standard == Dataspace::STANDARD_BT2020 &&
- (transfer == Dataspace::TRANSFER_ST2084 || transfer == Dataspace::TRANSFER_HLG);
-}
-
-// For convenience, we want to convert the input color space to XYZ color space first,
-// and then convert from XYZ color space to output color space when
-// - SDR and HDR contents are mixed, either SDR content will be converted to HDR or
-// HDR content will be tone-mapped to SDR; Or,
-// - there are HDR PQ and HLG contents presented at the same time, where we want to convert
-// HLG content to PQ content.
-// In either case above, we need to operate the Y value in XYZ color space. Thus, when either
-// input data space or output data space is HDR data space, and the input transfer function
-// doesn't match the output transfer function, we would enable an intermediate transfrom to
-// XYZ color space.
-bool GLESRenderEngine::needsXYZTransformMatrix() const {
- const bool isInputHdrDataSpace = isHdrDataSpace(mDataSpace);
- const bool isOutputHdrDataSpace = isHdrDataSpace(mOutputDataSpace);
- const Dataspace inputTransfer = static_cast<Dataspace>(mDataSpace & Dataspace::TRANSFER_MASK);
- const Dataspace outputTransfer =
- static_cast<Dataspace>(mOutputDataSpace & Dataspace::TRANSFER_MASK);
-
- return (isInputHdrDataSpace || isOutputHdrDataSpace) && inputTransfer != outputTransfer;
-}
-
-bool GLESRenderEngine::isImageCachedForTesting(uint64_t bufferId) {
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- const auto& cachedImage = mImageCache.find(bufferId);
- return cachedImage != mImageCache.end();
-}
-
-bool GLESRenderEngine::isTextureNameKnownForTesting(uint32_t texName) {
- const auto& entry = mTextureView.find(texName);
- return entry != mTextureView.end();
-}
-
-std::optional<uint64_t> GLESRenderEngine::getBufferIdForTextureNameForTesting(uint32_t texName) {
- const auto& entry = mTextureView.find(texName);
- return entry != mTextureView.end() ? entry->second : std::nullopt;
-}
-
-bool GLESRenderEngine::isFramebufferImageCachedForTesting(uint64_t bufferId) {
- std::lock_guard<std::mutex> lock(mFramebufferImageCacheMutex);
- return std::any_of(mFramebufferImageCache.cbegin(), mFramebufferImageCache.cend(),
- [=](std::pair<uint64_t, EGLImageKHR> image) {
- return image.first == bufferId;
- });
-}
-
-// FlushTracer implementation
-GLESRenderEngine::FlushTracer::FlushTracer(GLESRenderEngine* engine) : mEngine(engine) {
- mThread = std::thread(&GLESRenderEngine::FlushTracer::loop, this);
-}
-
-GLESRenderEngine::FlushTracer::~FlushTracer() {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mRunning = false;
- }
- mCondition.notify_all();
- if (mThread.joinable()) {
- mThread.join();
- }
-}
-
-void GLESRenderEngine::FlushTracer::queueSync(EGLSyncKHR sync) {
- std::lock_guard<std::mutex> lock(mMutex);
- char name[64];
- const uint64_t frameNum = mFramesQueued++;
- snprintf(name, sizeof(name), "Queueing sync for frame: %lu",
- static_cast<unsigned long>(frameNum));
- ATRACE_NAME(name);
- mQueue.push({sync, frameNum});
- ATRACE_INT("GPU Frames Outstanding", mQueue.size());
- mCondition.notify_one();
-}
-
-void GLESRenderEngine::FlushTracer::loop() {
- while (mRunning) {
- QueueEntry entry;
- {
- std::lock_guard<std::mutex> lock(mMutex);
-
- mCondition.wait(mMutex,
- [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
-
- if (!mRunning) {
- // if mRunning is false, then FlushTracer is being destroyed, so
- // bail out now.
- break;
- }
- entry = mQueue.front();
- mQueue.pop();
- }
- {
- char name[64];
- snprintf(name, sizeof(name), "waiting for frame %lu",
- static_cast<unsigned long>(entry.mFrameNum));
- ATRACE_NAME(name);
- mEngine->waitSync(entry.mSync, 0);
- }
- }
-}
-
-void GLESRenderEngine::handleShadow(const FloatRect& casterRect, float casterCornerRadius,
- const ShadowSettings& settings) {
- ATRACE_CALL();
- const float casterZ = settings.length / 2.0f;
- const GLShadowVertexGenerator shadows(casterRect, casterCornerRadius, casterZ,
- settings.casterIsTranslucent, settings.ambientColor,
- settings.spotColor, settings.lightPos,
- settings.lightRadius);
-
- // setup mesh for both shadows
- Mesh mesh = Mesh::Builder()
- .setPrimitive(Mesh::TRIANGLES)
- .setVertices(shadows.getVertexCount(), 2 /* size */)
- .setShadowAttrs()
- .setIndices(shadows.getIndexCount())
- .build();
-
- Mesh::VertexArray<vec2> position = mesh.getPositionArray<vec2>();
- Mesh::VertexArray<vec4> shadowColor = mesh.getShadowColorArray<vec4>();
- Mesh::VertexArray<vec3> shadowParams = mesh.getShadowParamsArray<vec3>();
- shadows.fillVertices(position, shadowColor, shadowParams);
- shadows.fillIndices(mesh.getIndicesArray());
-
- mState.cornerRadius = 0.0f;
- mState.drawShadows = true;
- setupLayerTexturing(mShadowTexture->getTexture());
- drawMesh(mesh);
- mState.drawShadows = false;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLESRenderEngine.h b/libs/renderengine/gl/GLESRenderEngine.h
deleted file mode 100644
index 1d7c2cafb5..0000000000
--- a/libs/renderengine/gl/GLESRenderEngine.h
+++ /dev/null
@@ -1,312 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_GLESRENDERENGINE_H_
-#define SF_GLESRENDERENGINE_H_
-
-#include <condition_variable>
-#include <deque>
-#include <mutex>
-#include <queue>
-#include <thread>
-#include <unordered_map>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <android-base/thread_annotations.h>
-#include <renderengine/RenderEngine.h>
-#include <renderengine/private/Description.h>
-#include <sys/types.h>
-#include "GLShadowTexture.h"
-#include "ImageManager.h"
-
-#define EGL_NO_CONFIG ((EGLConfig)0)
-
-namespace android {
-
-namespace renderengine {
-
-class Mesh;
-class Texture;
-
-namespace gl {
-
-class GLImage;
-class BlurFilter;
-
-class GLESRenderEngine : public RenderEngine {
-public:
- static std::unique_ptr<GLESRenderEngine> create(const RenderEngineCreationArgs& args);
-
- GLESRenderEngine(const RenderEngineCreationArgs& args, EGLDisplay display, EGLConfig config,
- EGLContext ctxt, EGLSurface stub, EGLContext protectedContext,
- EGLSurface protectedStub);
- ~GLESRenderEngine() override EXCLUDES(mRenderingMutex);
-
- std::future<void> 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 supportsProtectedContent() const override;
- void useProtectedContext(bool useProtectedContext) override;
- void cleanupPostRender() override;
- int getContextPriority() override;
- bool supportsBackgroundBlur() override { return mBlurFilter != nullptr; }
- void onActiveDisplaySizeChanged(ui::Size size) override {}
-
- EGLDisplay getEGLDisplay() const { return mEGLDisplay; }
- // Creates an output image for rendering to
- EGLImageKHR createFramebufferImageIfNeeded(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- bool useFramebufferCache)
- EXCLUDES(mFramebufferImageCacheMutex);
-
- // Test-only methods
- // Returns true iff mImageCache contains an image keyed by bufferId
- bool isImageCachedForTesting(uint64_t bufferId) EXCLUDES(mRenderingMutex);
- // Returns true iff texName was previously generated by RenderEngine and was
- // not destroyed.
- bool isTextureNameKnownForTesting(uint32_t texName);
- // Returns the buffer ID of the content bound to texName, or nullopt if no
- // such mapping exists.
- std::optional<uint64_t> getBufferIdForTextureNameForTesting(uint32_t texName);
- // Returns true iff mFramebufferImageCache contains an image keyed by bufferId
- bool isFramebufferImageCachedForTesting(uint64_t bufferId)
- EXCLUDES(mFramebufferImageCacheMutex);
- // These are wrappers around public methods above, but exposing Barrier
- // objects so that tests can block.
- std::shared_ptr<ImageManager::Barrier> cacheExternalTextureBufferForTesting(
- const sp<GraphicBuffer>& buffer);
- std::shared_ptr<ImageManager::Barrier> unbindExternalTextureBufferForTesting(uint64_t bufferId);
-
-protected:
- Framebuffer* getFramebufferForDrawing();
- void dump(std::string& result) override EXCLUDES(mRenderingMutex)
- EXCLUDES(mFramebufferImageCacheMutex);
- size_t getMaxTextureSize() const override;
- size_t getMaxViewportDims() const override;
- void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable)
- EXCLUDES(mRenderingMutex);
- void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) EXCLUDES(mRenderingMutex);
- bool canSkipPostRenderCleanup() const override;
- void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
- const DisplaySettings& display,
- const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
-
-private:
- friend class BindNativeBufferAsFramebuffer;
-
- enum GlesVersion {
- GLES_VERSION_1_0 = 0x10000,
- GLES_VERSION_1_1 = 0x10001,
- GLES_VERSION_2_0 = 0x20000,
- GLES_VERSION_3_0 = 0x30000,
- };
-
- static EGLConfig chooseEglConfig(EGLDisplay display, int format, bool logConfig);
- static GlesVersion parseGlesVersion(const char* str);
- static EGLContext createEglContext(EGLDisplay display, EGLConfig config,
- EGLContext shareContext,
- std::optional<ContextPriority> contextPriority,
- Protection protection);
- static std::optional<RenderEngine::ContextPriority> createContextPriority(
- const RenderEngineCreationArgs& args);
- static EGLSurface createStubEglPbufferSurface(EGLDisplay display, EGLConfig config,
- int hwcFormat, Protection protection);
- std::unique_ptr<Framebuffer> createFramebuffer();
- std::unique_ptr<Image> createImage();
- void checkErrors() const;
- void checkErrors(const char* tag) const;
- void setScissor(const Rect& region);
- void disableScissor();
- bool waitSync(EGLSyncKHR sync, EGLint flags);
- status_t cacheExternalTextureBufferInternal(const sp<GraphicBuffer>& buffer)
- EXCLUDES(mRenderingMutex);
- void unbindExternalTextureBufferInternal(uint64_t bufferId) EXCLUDES(mRenderingMutex);
- status_t bindFrameBuffer(Framebuffer* framebuffer);
- void unbindFrameBuffer(Framebuffer* framebuffer);
- void bindExternalTextureImage(uint32_t texName, const Image& image);
- void bindExternalTextureBuffer(uint32_t texName, const sp<GraphicBuffer>& buffer,
- const sp<Fence>& fence) EXCLUDES(mRenderingMutex);
- void cleanFramebufferCache() EXCLUDES(mFramebufferImageCacheMutex) override;
-
- // A data space is considered HDR data space if it has BT2020 color space
- // with PQ or HLG transfer function.
- bool isHdrDataSpace(const ui::Dataspace dataSpace) const;
- bool needsXYZTransformMatrix() const;
- // Defines the viewport, and sets the projection matrix to the projection
- // defined by the clip.
- void setViewportAndProjection(Rect viewport, Rect clip);
- // Evicts stale images from the buffer cache.
- void evictImages(const std::vector<LayerSettings>& layers);
- // Computes the cropping window for the layer and sets up cropping
- // coordinates for the mesh.
- FloatRect setupLayerCropping(const LayerSettings& layer, Mesh& mesh);
-
- // We do a special handling for rounded corners when it's possible to turn off blending
- // for the majority of the layer. The rounded corners needs to turn on blending such that
- // we can set the alpha value correctly, however, only the corners need this, and since
- // blending is an expensive operation, we want to turn off blending when it's not necessary.
- void handleRoundedCorners(const DisplaySettings& display, const LayerSettings& layer,
- const Mesh& mesh);
- base::unique_fd flush();
- bool finish();
- bool waitFence(base::unique_fd fenceFd);
- void clearWithColor(float red, float green, float blue, float alpha);
- void fillRegionWithColor(const Region& region, float red, float green, float blue, float alpha);
- void handleShadow(const FloatRect& casterRect, float casterCornerRadius,
- const ShadowSettings& shadowSettings);
- void setupLayerBlending(bool premultipliedAlpha, bool opaque, bool disableTexture,
- const half4& color, float cornerRadius);
- void setupLayerTexturing(const Texture& texture);
- void setupFillWithColor(float r, float g, float b, float a);
- void setColorTransform(const mat4& colorTransform);
- void setDisplayColorTransform(const mat4& colorTransform);
- void disableTexturing();
- void disableBlending();
- void setupCornerRadiusCropSize(float width, float height);
-
- // HDR and color management related functions and state
- void setSourceY410BT2020(bool enable);
- void setSourceDataSpace(ui::Dataspace source);
- void setOutputDataSpace(ui::Dataspace dataspace);
- void setDisplayMaxLuminance(const float maxLuminance);
-
- // drawing
- void drawMesh(const Mesh& mesh);
-
- EGLDisplay mEGLDisplay;
- EGLConfig mEGLConfig;
- EGLContext mEGLContext;
- EGLSurface mStubSurface;
- EGLContext mProtectedEGLContext;
- EGLSurface mProtectedStubSurface;
- GLint mMaxViewportDims[2];
- GLint mMaxTextureSize;
- GLuint mVpWidth;
- GLuint mVpHeight;
- Description mState;
- std::unique_ptr<GLShadowTexture> mShadowTexture = nullptr;
-
- mat4 mSrgbToXyz;
- mat4 mDisplayP3ToXyz;
- mat4 mBt2020ToXyz;
- mat4 mXyzToSrgb;
- mat4 mXyzToDisplayP3;
- mat4 mXyzToBt2020;
- mat4 mSrgbToDisplayP3;
- mat4 mSrgbToBt2020;
- mat4 mDisplayP3ToSrgb;
- mat4 mDisplayP3ToBt2020;
- mat4 mBt2020ToSrgb;
- mat4 mBt2020ToDisplayP3;
-
- bool mInProtectedContext = false;
- // If set to true, then enables tracing flush() and finish() to systrace.
- bool mTraceGpuCompletion = false;
- // Maximum size of mFramebufferImageCache. If more images would be cached, then (approximately)
- // the last recently used buffer should be kicked out.
- uint32_t mFramebufferImageCacheSize = 0;
-
- // Cache of output images, keyed by corresponding GraphicBuffer ID.
- std::deque<std::pair<uint64_t, EGLImageKHR>> mFramebufferImageCache
- GUARDED_BY(mFramebufferImageCacheMutex);
- // The only reason why we have this mutex is so that we don't segfault when
- // dumping info.
- std::mutex mFramebufferImageCacheMutex;
-
- // Current dataspace of layer being rendered
- ui::Dataspace mDataSpace = ui::Dataspace::UNKNOWN;
-
- // Current output dataspace of the render engine
- ui::Dataspace mOutputDataSpace = ui::Dataspace::UNKNOWN;
-
- // Whether device supports color management, currently color management
- // supports sRGB, DisplayP3 color spaces.
- const bool mUseColorManagement = false;
-
- // Whether only shaders performing tone mapping from HDR to SDR will be generated on
- // primeCache().
- const bool mPrecacheToneMapperShaderOnly = false;
-
- // Cache of GL images that we'll store per GraphicBuffer ID
- std::unordered_map<uint64_t, std::unique_ptr<Image>> mImageCache GUARDED_BY(mRenderingMutex);
- std::unordered_map<uint32_t, std::optional<uint64_t>> mTextureView;
-
- // 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.
- std::mutex mRenderingMutex;
-
- std::unique_ptr<Framebuffer> mDrawingBuffer;
- // this is a 1x1 RGB buffer, but over-allocate in case a driver wants more
- // memory or if it needs to satisfy alignment requirements. In this case:
- // assume that each channel requires 4 bytes, and add 3 additional bytes to
- // ensure that we align on a word. Allocating 16 bytes will provide a
- // guarantee that we don't clobber memory.
- uint32_t mPlaceholderDrawBuffer[4];
- // Placeholder buffer and image, similar to mPlaceholderDrawBuffer, but
- // instead these are intended for cleaning up texture memory with the
- // GL_TEXTURE_EXTERNAL_OES target.
- ANativeWindowBuffer* mPlaceholderBuffer = nullptr;
- EGLImage mPlaceholderImage = EGL_NO_IMAGE_KHR;
- sp<Fence> mLastDrawFence;
- // Store a separate boolean checking if prior resources were cleaned up, as
- // devices that don't support native sync fences can't rely on a last draw
- // fence that doesn't exist.
- bool mPriorResourcesCleaned = true;
-
- // Blur effect processor, only instantiated when a layer requests it.
- BlurFilter* mBlurFilter = nullptr;
-
- class FlushTracer {
- public:
- FlushTracer(GLESRenderEngine* engine);
- ~FlushTracer();
- void queueSync(EGLSyncKHR sync) EXCLUDES(mMutex);
-
- struct QueueEntry {
- EGLSyncKHR mSync = nullptr;
- uint64_t mFrameNum = 0;
- };
-
- private:
- void loop();
- GLESRenderEngine* const mEngine;
- std::thread mThread;
- std::condition_variable_any mCondition;
- std::mutex mMutex;
- std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);
- uint64_t mFramesQueued GUARDED_BY(mMutex) = 0;
- bool mRunning = true;
- };
- friend class FlushTracer;
- friend class ImageManager;
- friend class GLFramebuffer;
- friend class BlurFilter;
- friend class GenericProgram;
- std::unique_ptr<FlushTracer> mFlushTracer;
- std::unique_ptr<ImageManager> mImageManager;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_GLESRENDERENGINE_H_ */
diff --git a/libs/renderengine/gl/GLFramebuffer.cpp b/libs/renderengine/gl/GLFramebuffer.cpp
deleted file mode 100644
index 58d6caa48a..0000000000
--- a/libs/renderengine/gl/GLFramebuffer.cpp
+++ /dev/null
@@ -1,115 +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 "GLFramebuffer.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-#include <gui/DebugEGLImageTracker.h>
-#include <nativebase/nativebase.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLFramebuffer::GLFramebuffer(GLESRenderEngine& engine)
- : mEngine(engine), mEGLDisplay(engine.getEGLDisplay()), mEGLImage(EGL_NO_IMAGE_KHR) {
- glGenTextures(1, &mTextureName);
- glGenFramebuffers(1, &mFramebufferName);
-}
-
-GLFramebuffer::~GLFramebuffer() {
- setNativeWindowBuffer(nullptr, false, false);
- glDeleteFramebuffers(1, &mFramebufferName);
- glDeleteTextures(1, &mTextureName);
-}
-
-bool GLFramebuffer::setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- const bool useFramebufferCache) {
- ATRACE_CALL();
- if (mEGLImage != EGL_NO_IMAGE_KHR) {
- if (!usingFramebufferCache) {
- eglDestroyImageKHR(mEGLDisplay, mEGLImage);
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- }
- mEGLImage = EGL_NO_IMAGE_KHR;
- mBufferWidth = 0;
- mBufferHeight = 0;
- }
-
- if (nativeBuffer) {
- mEGLImage = mEngine.createFramebufferImageIfNeeded(nativeBuffer, isProtected,
- useFramebufferCache);
- if (mEGLImage == EGL_NO_IMAGE_KHR) {
- return false;
- }
- usingFramebufferCache = useFramebufferCache;
- mBufferWidth = nativeBuffer->width;
- mBufferHeight = nativeBuffer->height;
- }
- return true;
-}
-
-void GLFramebuffer::allocateBuffers(uint32_t width, uint32_t height, void* data) {
- ATRACE_CALL();
-
- glBindTexture(GL_TEXTURE_2D, mTextureName);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
-
- mBufferHeight = height;
- mBufferWidth = width;
- mEngine.checkErrors("Allocating Fbo texture");
-
- bind();
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, mTextureName, 0);
- mStatus = glCheckFramebufferStatus(GL_FRAMEBUFFER);
- unbind();
- glBindTexture(GL_TEXTURE_2D, 0);
-
- if (mStatus != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Frame buffer is not complete. Error %d", mStatus);
- }
-}
-
-void GLFramebuffer::bind() const {
- glBindFramebuffer(GL_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::bindAsReadBuffer() const {
- glBindFramebuffer(GL_READ_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::bindAsDrawBuffer() const {
- glBindFramebuffer(GL_DRAW_FRAMEBUFFER, mFramebufferName);
-}
-
-void GLFramebuffer::unbind() const {
- glBindFramebuffer(GL_FRAMEBUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLFramebuffer.h b/libs/renderengine/gl/GLFramebuffer.h
deleted file mode 100644
index 6757695ddb..0000000000
--- a/libs/renderengine/gl/GLFramebuffer.h
+++ /dev/null
@@ -1,68 +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 <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-#include <renderengine/Framebuffer.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLFramebuffer : public renderengine::Framebuffer {
-public:
- explicit GLFramebuffer(GLESRenderEngine& engine);
- explicit GLFramebuffer(GLESRenderEngine& engine, bool multiTarget);
- ~GLFramebuffer() override;
-
- bool setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- const bool useFramebufferCache) override;
- void allocateBuffers(uint32_t width, uint32_t height, void* data = nullptr);
- EGLImageKHR getEGLImage() const { return mEGLImage; }
- uint32_t getTextureName() const { return mTextureName; }
- uint32_t getFramebufferName() const { return mFramebufferName; }
- int32_t getBufferHeight() const { return mBufferHeight; }
- int32_t getBufferWidth() const { return mBufferWidth; }
- GLenum getStatus() const { return mStatus; }
- void bind() const;
- void bindAsReadBuffer() const;
- void bindAsDrawBuffer() const;
- void unbind() const;
-
-private:
- GLESRenderEngine& mEngine;
- EGLDisplay mEGLDisplay;
- EGLImageKHR mEGLImage;
- bool usingFramebufferCache = false;
- GLenum mStatus = GL_FRAMEBUFFER_UNSUPPORTED;
- uint32_t mTextureName, mFramebufferName;
-
- int32_t mBufferHeight = 0;
- int32_t mBufferWidth = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLImage.cpp b/libs/renderengine/gl/GLImage.cpp
deleted file mode 100644
index 8497721956..0000000000
--- a/libs/renderengine/gl/GLImage.cpp
+++ /dev/null
@@ -1,83 +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 "GLImage.h"
-
-#include <vector>
-
-#include <gui/DebugEGLImageTracker.h>
-#include <log/log.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "GLExtensions.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-static std::vector<EGLint> buildAttributeList(bool isProtected) {
- std::vector<EGLint> attrs;
- attrs.reserve(16);
-
- attrs.push_back(EGL_IMAGE_PRESERVED_KHR);
- attrs.push_back(EGL_TRUE);
-
- if (isProtected && GLExtensions::getInstance().hasProtectedContent()) {
- attrs.push_back(EGL_PROTECTED_CONTENT_EXT);
- attrs.push_back(EGL_TRUE);
- }
-
- attrs.push_back(EGL_NONE);
-
- return attrs;
-}
-
-GLImage::GLImage(const GLESRenderEngine& engine) : mEGLDisplay(engine.getEGLDisplay()) {}
-
-GLImage::~GLImage() {
- setNativeWindowBuffer(nullptr, false);
-}
-
-bool GLImage::setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) {
- ATRACE_CALL();
- if (mEGLImage != EGL_NO_IMAGE_KHR) {
- if (!eglDestroyImageKHR(mEGLDisplay, mEGLImage)) {
- ALOGE("failed to destroy image: %#x", eglGetError());
- }
- DEBUG_EGL_IMAGE_TRACKER_DESTROY();
- mEGLImage = EGL_NO_IMAGE_KHR;
- }
-
- if (buffer) {
- std::vector<EGLint> attrs = buildAttributeList(isProtected);
- mEGLImage = eglCreateImageKHR(mEGLDisplay, EGL_NO_CONTEXT, EGL_NATIVE_BUFFER_ANDROID,
- static_cast<EGLClientBuffer>(buffer), attrs.data());
- if (mEGLImage == EGL_NO_IMAGE_KHR) {
- ALOGE("failed to create EGLImage: %#x", eglGetError());
- return false;
- }
- DEBUG_EGL_IMAGE_TRACKER_CREATE();
- mProtected = isProtected;
- }
-
- return true;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLImage.h b/libs/renderengine/gl/GLImage.h
deleted file mode 100644
index 59d6ce3549..0000000000
--- a/libs/renderengine/gl/GLImage.h
+++ /dev/null
@@ -1,54 +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 <cstdint>
-
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <android-base/macros.h>
-#include <renderengine/Image.h>
-
-struct ANativeWindowBuffer;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLImage : public renderengine::Image {
-public:
- explicit GLImage(const GLESRenderEngine& engine);
- ~GLImage() override;
-
- bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) override;
-
- EGLImageKHR getEGLImage() const { return mEGLImage; }
- bool isProtected() const { return mProtected; }
-
-private:
- EGLDisplay mEGLDisplay;
- EGLImageKHR mEGLImage = EGL_NO_IMAGE_KHR;
- bool mProtected = false;
-
- DISALLOW_COPY_AND_ASSIGN(GLImage);
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowTexture.cpp b/libs/renderengine/gl/GLShadowTexture.cpp
deleted file mode 100644
index 2423a3467e..0000000000
--- a/libs/renderengine/gl/GLShadowTexture.cpp
+++ /dev/null
@@ -1,52 +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 <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <GLES3/gl3.h>
-
-#include "GLShadowTexture.h"
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLShadowTexture::GLShadowTexture() {
- fillShadowTextureData(mTextureData, SHADOW_TEXTURE_WIDTH);
-
- glGenTextures(1, &mName);
- glBindTexture(GL_TEXTURE_2D, mName);
- glTexImage2D(GL_TEXTURE_2D, 0 /* base image level */, GL_ALPHA, SHADOW_TEXTURE_WIDTH,
- SHADOW_TEXTURE_HEIGHT, 0 /* border */, GL_ALPHA, GL_UNSIGNED_BYTE, mTextureData);
- mTexture.init(Texture::TEXTURE_2D, mName);
- mTexture.setFiltering(true);
- mTexture.setDimensions(SHADOW_TEXTURE_WIDTH, 1);
-}
-
-GLShadowTexture::~GLShadowTexture() {
- glDeleteTextures(1, &mName);
-}
-
-const Texture& GLShadowTexture::getTexture() {
- return mTexture;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.cpp b/libs/renderengine/gl/GLShadowVertexGenerator.cpp
deleted file mode 100644
index 3181f9bebb..0000000000
--- a/libs/renderengine/gl/GLShadowVertexGenerator.cpp
+++ /dev/null
@@ -1,98 +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.
- */
-
-#include <renderengine/Mesh.h>
-
-#include <math/vec4.h>
-
-#include <ui/Rect.h>
-#include <ui/Transform.h>
-
-#include "GLShadowVertexGenerator.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLShadowVertexGenerator::GLShadowVertexGenerator(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& ambientColor,
- const vec4& spotColor, const vec3& lightPosition,
- float lightRadius) {
- mDrawAmbientShadow = ambientColor.a > 0.f;
- mDrawSpotShadow = spotColor.a > 0.f;
-
- // Generate geometries and find number of vertices to generate
- if (mDrawAmbientShadow) {
- mAmbientShadowGeometry = getAmbientShadowGeometry(casterRect, casterCornerRadius, casterZ,
- casterIsTranslucent, ambientColor);
- mAmbientShadowVertexCount = getVertexCountForGeometry(*mAmbientShadowGeometry.get());
- mAmbientShadowIndexCount = getIndexCountForGeometry(*mAmbientShadowGeometry.get());
- } else {
- mAmbientShadowVertexCount = 0;
- mAmbientShadowIndexCount = 0;
- }
-
- if (mDrawSpotShadow) {
- mSpotShadowGeometry =
- getSpotShadowGeometry(casterRect, casterCornerRadius, casterZ, casterIsTranslucent,
- spotColor, lightPosition, lightRadius);
- mSpotShadowVertexCount = getVertexCountForGeometry(*mSpotShadowGeometry.get());
- mSpotShadowIndexCount = getIndexCountForGeometry(*mSpotShadowGeometry.get());
- } else {
- mSpotShadowVertexCount = 0;
- mSpotShadowIndexCount = 0;
- }
-}
-
-size_t GLShadowVertexGenerator::getVertexCount() const {
- return mAmbientShadowVertexCount + mSpotShadowVertexCount;
-}
-
-size_t GLShadowVertexGenerator::getIndexCount() const {
- return mAmbientShadowIndexCount + mSpotShadowIndexCount;
-}
-
-void GLShadowVertexGenerator::fillVertices(Mesh::VertexArray<vec2>& position,
- Mesh::VertexArray<vec4>& color,
- Mesh::VertexArray<vec3>& params) const {
- if (mDrawAmbientShadow) {
- fillVerticesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowVertexCount, position,
- color, params);
- }
- if (mDrawSpotShadow) {
- fillVerticesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowVertexCount,
- Mesh::VertexArray<vec2>(position, mAmbientShadowVertexCount),
- Mesh::VertexArray<vec4>(color, mAmbientShadowVertexCount),
- Mesh::VertexArray<vec3>(params, mAmbientShadowVertexCount));
- }
-}
-
-void GLShadowVertexGenerator::fillIndices(uint16_t* indices) const {
- if (mDrawAmbientShadow) {
- fillIndicesForGeometry(*mAmbientShadowGeometry.get(), mAmbientShadowIndexCount,
- 0 /* starting vertex offset */, indices);
- }
- if (mDrawSpotShadow) {
- fillIndicesForGeometry(*mSpotShadowGeometry.get(), mSpotShadowIndexCount,
- mAmbientShadowVertexCount /* starting vertex offset */,
- &(indices[mAmbientShadowIndexCount]));
- }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLShadowVertexGenerator.h b/libs/renderengine/gl/GLShadowVertexGenerator.h
deleted file mode 100644
index 112f97623b..0000000000
--- a/libs/renderengine/gl/GLShadowVertexGenerator.h
+++ /dev/null
@@ -1,65 +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.
- */
-
-#pragma once
-
-#include <math/vec4.h>
-#include <ui/Rect.h>
-
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-
-class Mesh;
-
-namespace gl {
-
-/**
- * Generates gl attributes required to draw shadow spot and/or ambient shadows.
- *
- * Each shadow can support different colors. This class generates three vertex attributes for
- * each shadow, its position, color and shadow params(offset and distance). These can be sent
- * using a single glDrawElements call.
- */
-class GLShadowVertexGenerator {
-public:
- GLShadowVertexGenerator(const FloatRect& casterRect, float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& ambientColor,
- const vec4& spotColor, const vec3& lightPosition, float lightRadius);
- ~GLShadowVertexGenerator() = default;
-
- size_t getVertexCount() const;
- size_t getIndexCount() const;
- void fillVertices(Mesh::VertexArray<vec2>& position, Mesh::VertexArray<vec4>& color,
- Mesh::VertexArray<vec3>& params) const;
- void fillIndices(uint16_t* indices) const;
-
-private:
- bool mDrawAmbientShadow;
- std::unique_ptr<Geometry> mAmbientShadowGeometry;
- int mAmbientShadowVertexCount = 0;
- int mAmbientShadowIndexCount = 0;
-
- bool mDrawSpotShadow;
- std::unique_ptr<Geometry> mSpotShadowGeometry;
- int mSpotShadowVertexCount = 0;
- int mSpotShadowIndexCount = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.cpp b/libs/renderengine/gl/GLSkiaShadowPort.cpp
deleted file mode 100644
index da8b435854..0000000000
--- a/libs/renderengine/gl/GLSkiaShadowPort.cpp
+++ /dev/null
@@ -1,656 +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.
- */
-
-#include <math/vec4.h>
-
-#include <renderengine/Mesh.h>
-
-#include <ui/Rect.h>
-#include <ui/Transform.h>
-
-#include <utils/Log.h>
-
-#include "GLSkiaShadowPort.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * The shadow geometry logic and vertex generation code has been ported from skia shadow
- * fast path OpenGL implementation to draw shadows around rects and rounded rects including
- * circles.
- *
- * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
- *
- * Modifications made:
- * - Switched to using std lib math functions
- * - Fall off function is implemented in vertex shader rather than a shadow texture
- * - Removed transformations applied on the caster rect since the caster will be in local
- * coordinate space and will be transformed by the vertex shader.
- */
-
-static inline float divide_and_pin(float numer, float denom, float min, float max) {
- if (denom == 0.0f) return min;
- return std::clamp(numer / denom, min, max);
-}
-
-static constexpr auto SK_ScalarSqrt2 = 1.41421356f;
-static constexpr auto kAmbientHeightFactor = 1.0f / 128.0f;
-static constexpr auto kAmbientGeomFactor = 64.0f;
-// Assuming that we have a light height of 600 for the spot shadow,
-// the spot values will reach their maximum at a height of approximately 292.3077.
-// We'll round up to 300 to keep it simple.
-static constexpr auto kMaxAmbientRadius = 300 * kAmbientHeightFactor * kAmbientGeomFactor;
-
-inline float AmbientBlurRadius(float height) {
- return std::min(height * kAmbientHeightFactor * kAmbientGeomFactor, kMaxAmbientRadius);
-}
-inline float AmbientRecipAlpha(float height) {
- return 1.0f + std::max(height * kAmbientHeightFactor, 0.0f);
-}
-
-//////////////////////////////////////////////////////////////////////////////
-// Circle Data
-//
-// We have two possible cases for geometry for a circle:
-
-// In the case of a normal fill, we draw geometry for the circle as an octagon.
-static const uint16_t gFillCircleIndices[] = {
- // enter the octagon
- // clang-format off
- 0, 1, 8, 1, 2, 8,
- 2, 3, 8, 3, 4, 8,
- 4, 5, 8, 5, 6, 8,
- 6, 7, 8, 7, 0, 8,
- // clang-format on
-};
-
-// For stroked circles, we use two nested octagons.
-static const uint16_t gStrokeCircleIndices[] = {
- // enter the octagon
- // clang-format off
- 0, 1, 9, 0, 9, 8,
- 1, 2, 10, 1, 10, 9,
- 2, 3, 11, 2, 11, 10,
- 3, 4, 12, 3, 12, 11,
- 4, 5, 13, 4, 13, 12,
- 5, 6, 14, 5, 14, 13,
- 6, 7, 15, 6, 15, 14,
- 7, 0, 8, 7, 8, 15,
- // clang-format on
-};
-
-#define SK_ARRAY_COUNT(a) (sizeof(a) / sizeof((a)[0]))
-static const int kIndicesPerFillCircle = SK_ARRAY_COUNT(gFillCircleIndices);
-static const int kIndicesPerStrokeCircle = SK_ARRAY_COUNT(gStrokeCircleIndices);
-static const int kVertsPerStrokeCircle = 16;
-static const int kVertsPerFillCircle = 9;
-
-static int circle_type_to_vert_count(bool stroked) {
- return stroked ? kVertsPerStrokeCircle : kVertsPerFillCircle;
-}
-
-static int circle_type_to_index_count(bool stroked) {
- return stroked ? kIndicesPerStrokeCircle : kIndicesPerFillCircle;
-}
-
-static const uint16_t* circle_type_to_indices(bool stroked) {
- return stroked ? gStrokeCircleIndices : gFillCircleIndices;
-}
-
-///////////////////////////////////////////////////////////////////////////////
-// RoundRect Data
-//
-// The geometry for a shadow roundrect is similar to a 9-patch:
-// ____________
-// |_|________|_|
-// | | | |
-// | | | |
-// | | | |
-// |_|________|_|
-// |_|________|_|
-//
-// However, each corner is rendered as a fan rather than a simple quad, as below. (The diagram
-// shows the upper part of the upper left corner. The bottom triangle would similarly be split
-// into two triangles.)
-// ________
-// |\ \ |
-// | \ \ |
-// | \\ |
-// | \|
-// --------
-//
-// The center of the fan handles the curve of the corner. For roundrects where the stroke width
-// is greater than the corner radius, the outer triangles blend from the curve to the straight
-// sides. Otherwise these triangles will be degenerate.
-//
-// In the case where the stroke width is greater than the corner radius and the
-// blur radius (overstroke), we add additional geometry to mark out the rectangle in the center.
-// This rectangle extends the coverage values of the center edges of the 9-patch.
-// ____________
-// |_|________|_|
-// | |\ ____ /| |
-// | | | | | |
-// | | |____| | |
-// |_|/______\|_|
-// |_|________|_|
-//
-// For filled rrects we reuse the stroke geometry but add an additional quad to the center.
-
-static const uint16_t gRRectIndices[] = {
- // clang-format off
- // overstroke quads
- // we place this at the beginning so that we can skip these indices when rendering as filled
- 0, 6, 25, 0, 25, 24,
- 6, 18, 27, 6, 27, 25,
- 18, 12, 26, 18, 26, 27,
- 12, 0, 24, 12, 24, 26,
-
- // corners
- 0, 1, 2, 0, 2, 3, 0, 3, 4, 0, 4, 5,
- 6, 11, 10, 6, 10, 9, 6, 9, 8, 6, 8, 7,
- 12, 17, 16, 12, 16, 15, 12, 15, 14, 12, 14, 13,
- 18, 19, 20, 18, 20, 21, 18, 21, 22, 18, 22, 23,
-
- // edges
- 0, 5, 11, 0, 11, 6,
- 6, 7, 19, 6, 19, 18,
- 18, 23, 17, 18, 17, 12,
- 12, 13, 1, 12, 1, 0,
-
- // fill quad
- // we place this at the end so that we can skip these indices when rendering as stroked
- 0, 6, 18, 0, 18, 12,
- // clang-format on
-};
-
-// overstroke count
-static const int kIndicesPerOverstrokeRRect = SK_ARRAY_COUNT(gRRectIndices) - 6;
-// simple stroke count skips overstroke indices
-static const int kIndicesPerStrokeRRect = kIndicesPerOverstrokeRRect - 6 * 4;
-// fill count adds final quad to stroke count
-static const int kIndicesPerFillRRect = kIndicesPerStrokeRRect + 6;
-static const int kVertsPerStrokeRRect = 24;
-static const int kVertsPerOverstrokeRRect = 28;
-static const int kVertsPerFillRRect = 24;
-
-static int rrect_type_to_vert_count(RRectType type) {
- switch (type) {
- case kFill_RRectType:
- return kVertsPerFillRRect;
- case kStroke_RRectType:
- return kVertsPerStrokeRRect;
- case kOverstroke_RRectType:
- return kVertsPerOverstrokeRRect;
- }
- ALOGE("Invalid rect type: %d", type);
- return -1;
-}
-
-static int rrect_type_to_index_count(RRectType type) {
- switch (type) {
- case kFill_RRectType:
- return kIndicesPerFillRRect;
- case kStroke_RRectType:
- return kIndicesPerStrokeRRect;
- case kOverstroke_RRectType:
- return kIndicesPerOverstrokeRRect;
- }
- ALOGE("Invalid rect type: %d", type);
- return -1;
-}
-
-static const uint16_t* rrect_type_to_indices(RRectType type) {
- switch (type) {
- case kFill_RRectType:
- case kStroke_RRectType:
- return gRRectIndices + 6 * 4;
- case kOverstroke_RRectType:
- return gRRectIndices;
- }
- ALOGE("Invalid rect type: %d", type);
- return nullptr;
-}
-
-static void fillInCircleVerts(const Geometry& args, bool isStroked,
- Mesh::VertexArray<vec2>& position,
- Mesh::VertexArray<vec4>& shadowColor,
- Mesh::VertexArray<vec3>& shadowParams) {
- vec4 color = args.fColor;
- float outerRadius = args.fOuterRadius;
- float innerRadius = args.fInnerRadius;
- float blurRadius = args.fBlurRadius;
- float distanceCorrection = outerRadius / blurRadius;
-
- const FloatRect& bounds = args.fDevBounds;
-
- // The inner radius in the vertex data must be specified in normalized space.
- innerRadius = innerRadius / outerRadius;
-
- vec2 center = vec2(bounds.getWidth() / 2.0f, bounds.getHeight() / 2.0f);
- float halfWidth = 0.5f * bounds.getWidth();
- float octOffset = 0.41421356237f; // sqrt(2) - 1
- int vertexCount = 0;
-
- position[vertexCount] = center + vec2(-octOffset * halfWidth, -halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-octOffset, -1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(octOffset * halfWidth, -halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(octOffset, -1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(halfWidth, -octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(1, -octOffset, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(halfWidth, octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(1, octOffset, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(octOffset * halfWidth, halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(octOffset, 1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-octOffset * halfWidth, halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-octOffset, 1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-halfWidth, octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-1, octOffset, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-halfWidth, -octOffset * halfWidth);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-1, -octOffset, distanceCorrection);
- vertexCount++;
-
- if (isStroked) {
- // compute the inner ring
-
- // cosine and sine of pi/8
- float c = 0.923579533f;
- float s = 0.382683432f;
- float r = args.fInnerRadius;
-
- position[vertexCount] = center + vec2(-s * r, -c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-s * innerRadius, -c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(s * r, -c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(s * innerRadius, -c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(c * r, -s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(c * innerRadius, -s * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(c * r, s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(c * innerRadius, s * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(s * r, c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(s * innerRadius, c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-s * r, c * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-s * innerRadius, c * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-c * r, s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-c * innerRadius, s * innerRadius, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = center + vec2(-c * r, -s * r);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(-c * innerRadius, -s * innerRadius, distanceCorrection);
- vertexCount++;
- } else {
- // filled
- position[vertexCount] = center;
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
- }
-}
-
-static void fillInRRectVerts(const Geometry& args, Mesh::VertexArray<vec2>& position,
- Mesh::VertexArray<vec4>& shadowColor,
- Mesh::VertexArray<vec3>& shadowParams) {
- vec4 color = args.fColor;
- float outerRadius = args.fOuterRadius;
-
- const FloatRect& bounds = args.fDevBounds;
-
- float umbraInset = args.fUmbraInset;
- float minDim = 0.5f * std::min(bounds.getWidth(), bounds.getHeight());
- if (umbraInset > minDim) {
- umbraInset = minDim;
- }
-
- float xInner[4] = {bounds.left + umbraInset, bounds.right - umbraInset,
- bounds.left + umbraInset, bounds.right - umbraInset};
- float xMid[4] = {bounds.left + outerRadius, bounds.right - outerRadius,
- bounds.left + outerRadius, bounds.right - outerRadius};
- float xOuter[4] = {bounds.left, bounds.right, bounds.left, bounds.right};
- float yInner[4] = {bounds.top + umbraInset, bounds.top + umbraInset, bounds.bottom - umbraInset,
- bounds.bottom - umbraInset};
- float yMid[4] = {bounds.top + outerRadius, bounds.top + outerRadius,
- bounds.bottom - outerRadius, bounds.bottom - outerRadius};
- float yOuter[4] = {bounds.top, bounds.top, bounds.bottom, bounds.bottom};
-
- float blurRadius = args.fBlurRadius;
-
- // In the case where we have to inset more for the umbra, our two triangles in the
- // corner get skewed to a diamond rather than a square. To correct for that,
- // we also skew the vectors we send to the shader that help define the circle.
- // By doing so, we end up with a quarter circle in the corner rather than the
- // elliptical curve.
-
- // This is a bit magical, but it gives us the correct results at extrema:
- // a) umbraInset == outerRadius produces an orthogonal vector
- // b) outerRadius == 0 produces a diagonal vector
- // And visually the corner looks correct.
- vec2 outerVec = vec2(outerRadius - umbraInset, -outerRadius - umbraInset);
- outerVec = normalize(outerVec);
- // We want the circle edge to fall fractionally along the diagonal at
- // (sqrt(2)*(umbraInset - outerRadius) + outerRadius)/sqrt(2)*umbraInset
- //
- // Setting the components of the diagonal offset to the following value will give us that.
- float diagVal = umbraInset / (SK_ScalarSqrt2 * (outerRadius - umbraInset) - outerRadius);
- vec2 diagVec = vec2(diagVal, diagVal);
- float distanceCorrection = umbraInset / blurRadius;
-
- int vertexCount = 0;
- // build corner by corner
- for (int i = 0; i < 4; ++i) {
- // inner point
- position[vertexCount] = vec2(xInner[i], yInner[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // outer points
- position[vertexCount] = vec2(xOuter[i], yInner[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xOuter[i], yMid[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xOuter[i], yOuter[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(diagVec.x, diagVec.y, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xMid[i], yOuter[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(outerVec.x, outerVec.y, distanceCorrection);
- vertexCount++;
-
- position[vertexCount] = vec2(xInner[i], yOuter[i]);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, -1, distanceCorrection);
- vertexCount++;
- }
-
- // Add the additional vertices for overstroked rrects.
- // Effectively this is an additional stroked rrect, with its
- // parameters equal to those in the center of the 9-patch. This will
- // give constant values across this inner ring.
- if (kOverstroke_RRectType == args.fType) {
- float inset = umbraInset + args.fInnerRadius;
-
- // TL
- position[vertexCount] = vec2(bounds.left + inset, bounds.top + inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // TR
- position[vertexCount] = vec2(bounds.right - inset, bounds.top + inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // BL
- position[vertexCount] = vec2(bounds.left + inset, bounds.bottom - inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
-
- // BR
- position[vertexCount] = vec2(bounds.right - inset, bounds.bottom - inset);
- shadowColor[vertexCount] = color;
- shadowParams[vertexCount] = vec3(0, 0, distanceCorrection);
- vertexCount++;
- }
-}
-
-int getVertexCountForGeometry(const Geometry& shadowGeometry) {
- if (shadowGeometry.fIsCircle) {
- return circle_type_to_vert_count(shadowGeometry.fType);
- }
-
- return rrect_type_to_vert_count(shadowGeometry.fType);
-}
-
-int getIndexCountForGeometry(const Geometry& shadowGeometry) {
- if (shadowGeometry.fIsCircle) {
- return circle_type_to_index_count(kStroke_RRectType == shadowGeometry.fType);
- }
-
- return rrect_type_to_index_count(shadowGeometry.fType);
-}
-
-void fillVerticesForGeometry(const Geometry& shadowGeometry, int /* vertexCount */,
- Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
- Mesh::VertexArray<vec3> shadowParams) {
- if (shadowGeometry.fIsCircle) {
- fillInCircleVerts(shadowGeometry, shadowGeometry.fIsStroked, position, shadowColor,
- shadowParams);
- } else {
- fillInRRectVerts(shadowGeometry, position, shadowColor, shadowParams);
- }
-}
-
-void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
- int startingVertexOffset, uint16_t* indices) {
- if (shadowGeometry.fIsCircle) {
- const uint16_t* primIndices = circle_type_to_indices(shadowGeometry.fIsStroked);
- for (int i = 0; i < indexCount; ++i) {
- indices[i] = primIndices[i] + startingVertexOffset;
- }
- } else {
- const uint16_t* primIndices = rrect_type_to_indices(shadowGeometry.fType);
- for (int i = 0; i < indexCount; ++i) {
- indices[i] = primIndices[i] + startingVertexOffset;
- }
- }
-}
-
-inline void GetSpotParams(float occluderZ, float lightX, float lightY, float lightZ,
- float lightRadius, float& blurRadius, float& scale, vec2& translate) {
- float zRatio = divide_and_pin(occluderZ, lightZ - occluderZ, 0.0f, 0.95f);
- blurRadius = lightRadius * zRatio;
- scale = divide_and_pin(lightZ, lightZ - occluderZ, 1.0f, 1.95f);
- translate.x = -zRatio * lightX;
- translate.y = -zRatio * lightY;
-}
-
-static std::unique_ptr<Geometry> getShadowGeometry(const vec4& color, const FloatRect& devRect,
- float devRadius, float blurRadius,
- float insetWidth) {
- // An insetWidth > 1/2 rect width or height indicates a simple fill.
- const bool isCircle = ((devRadius >= devRect.getWidth()) && (devRadius >= devRect.getHeight()));
-
- FloatRect bounds = devRect;
- float innerRadius = 0.0f;
- float outerRadius = devRadius;
- float umbraInset;
-
- RRectType type = kFill_RRectType;
- if (isCircle) {
- umbraInset = 0;
- } else {
- umbraInset = std::max(outerRadius, blurRadius);
- }
-
- // If stroke is greater than width or height, this is still a fill,
- // otherwise we compute stroke params.
- if (isCircle) {
- innerRadius = devRadius - insetWidth;
- type = innerRadius > 0 ? kStroke_RRectType : kFill_RRectType;
- } else {
- if (insetWidth <= 0.5f * std::min(devRect.getWidth(), devRect.getHeight())) {
- // We don't worry about a real inner radius, we just need to know if we
- // need to create overstroke vertices.
- innerRadius = std::max(insetWidth - umbraInset, 0.0f);
- type = innerRadius > 0 ? kOverstroke_RRectType : kStroke_RRectType;
- }
- }
- const bool isStroked = (kStroke_RRectType == type);
- return std::make_unique<Geometry>(Geometry{color, outerRadius, umbraInset, innerRadius,
- blurRadius, bounds, type, isCircle, isStroked});
-}
-
-std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent,
- const vec4& ambientColor) {
- float devSpaceInsetWidth = AmbientBlurRadius(casterZ);
- const float umbraRecipAlpha = AmbientRecipAlpha(casterZ);
- const float devSpaceAmbientBlur = devSpaceInsetWidth * umbraRecipAlpha;
-
- // Outset the shadow rrect to the border of the penumbra
- float ambientPathOutset = devSpaceInsetWidth;
- FloatRect outsetRect(casterRect);
- outsetRect.left -= ambientPathOutset;
- outsetRect.top -= ambientPathOutset;
- outsetRect.right += ambientPathOutset;
- outsetRect.bottom += ambientPathOutset;
-
- float outsetRad = casterCornerRadius + ambientPathOutset;
- if (casterIsTranslucent) {
- // set a large inset to force a fill
- devSpaceInsetWidth = outsetRect.getWidth();
- }
-
- return getShadowGeometry(ambientColor, outsetRect, std::abs(outsetRad), devSpaceAmbientBlur,
- std::abs(devSpaceInsetWidth));
-}
-
-std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& spotColor,
- const vec3& lightPosition, float lightRadius) {
- float devSpaceSpotBlur;
- float spotScale;
- vec2 spotOffset;
- GetSpotParams(casterZ, lightPosition.x, lightPosition.y, lightPosition.z, lightRadius,
- devSpaceSpotBlur, spotScale, spotOffset);
- // handle scale of radius due to CTM
- const float srcSpaceSpotBlur = devSpaceSpotBlur;
-
- // Adjust translate for the effect of the scale.
- spotOffset.x += spotScale;
- spotOffset.y += spotScale;
-
- // Compute the transformed shadow rect
- ui::Transform shadowTransform;
- shadowTransform.set(spotOffset.x, spotOffset.y);
- shadowTransform.set(spotScale, 0, 0, spotScale);
- FloatRect spotShadowRect = shadowTransform.transform(casterRect);
- float spotShadowRadius = casterCornerRadius * spotScale;
-
- // Compute the insetWidth
- float blurOutset = srcSpaceSpotBlur;
- float insetWidth = blurOutset;
- if (casterIsTranslucent) {
- // If transparent, just do a fill
- insetWidth += spotShadowRect.getWidth();
- } else {
- // For shadows, instead of using a stroke we specify an inset from the penumbra
- // border. We want to extend this inset area so that it meets up with the caster
- // geometry. The inset geometry will by default already be inset by the blur width.
- //
- // We compare the min and max corners inset by the radius between the original
- // rrect and the shadow rrect. The distance between the two plus the difference
- // between the scaled radius and the original radius gives the distance from the
- // transformed shadow shape to the original shape in that corner. The max
- // of these gives the maximum distance we need to cover.
- //
- // Since we are outsetting by 1/2 the blur distance, we just add the maxOffset to
- // that to get the full insetWidth.
- float maxOffset;
- if (casterCornerRadius <= 0.f) {
- // Manhattan distance works better for rects
- maxOffset = std::max(std::max(std::abs(spotShadowRect.left - casterRect.left),
- std::abs(spotShadowRect.top - casterRect.top)),
- std::max(std::abs(spotShadowRect.right - casterRect.right),
- std::abs(spotShadowRect.bottom - casterRect.bottom)));
- } else {
- float dr = spotShadowRadius - casterCornerRadius;
- vec2 upperLeftOffset = vec2(spotShadowRect.left - casterRect.left + dr,
- spotShadowRect.top - casterRect.top + dr);
- vec2 lowerRightOffset = vec2(spotShadowRect.right - casterRect.right - dr,
- spotShadowRect.bottom - casterRect.bottom - dr);
- maxOffset = sqrt(std::max(dot(upperLeftOffset, lowerRightOffset),
- dot(lowerRightOffset, lowerRightOffset))) +
- dr;
- }
- insetWidth += std::max(blurOutset, maxOffset);
- }
-
- // Outset the shadow rrect to the border of the penumbra
- spotShadowRadius += blurOutset;
- spotShadowRect.left -= blurOutset;
- spotShadowRect.top -= blurOutset;
- spotShadowRect.right += blurOutset;
- spotShadowRect.bottom += blurOutset;
-
- return getShadowGeometry(spotColor, spotShadowRect, std::abs(spotShadowRadius),
- 2.0f * devSpaceSpotBlur, std::abs(insetWidth));
-}
-
-void fillShadowTextureData(uint8_t* data, size_t shadowTextureWidth) {
- for (int i = 0; i < shadowTextureWidth; i++) {
- const float d = 1 - i / ((shadowTextureWidth * 1.0f) - 1.0f);
- data[i] = static_cast<uint8_t>((exp(-4.0f * d * d) - 0.018f) * 255);
- }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLSkiaShadowPort.h b/libs/renderengine/gl/GLSkiaShadowPort.h
deleted file mode 100644
index 912c8bb7b3..0000000000
--- a/libs/renderengine/gl/GLSkiaShadowPort.h
+++ /dev/null
@@ -1,96 +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.
- */
-
-#pragma once
-
-#include <math/vec4.h>
-#include <renderengine/Mesh.h>
-#include <ui/Rect.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * The shadow geometry logic and vertex generation code has been ported from skia shadow
- * fast path OpenGL implementation to draw shadows around rects and rounded rects including
- * circles.
- *
- * path: skia/src/gpu/GrRenderTargetContext.cpp GrRenderTargetContext::drawFastShadow
- *
- * Modifications made:
- * - Switched to using std lib math functions
- * - Fall off function is implemented in vertex shader rather than a shadow texture
- * - Removed transformations applied on the caster rect since the caster will be in local
- * coordinate space and will be transformed by the vertex shader.
- */
-
-enum RRectType {
- kFill_RRectType,
- kStroke_RRectType,
- kOverstroke_RRectType,
-};
-
-struct Geometry {
- vec4 fColor;
- float fOuterRadius;
- float fUmbraInset;
- float fInnerRadius;
- float fBlurRadius;
- FloatRect fDevBounds;
- RRectType fType;
- bool fIsCircle;
- bool fIsStroked;
-};
-
-std::unique_ptr<Geometry> getSpotShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent, const vec4& spotColor,
- const vec3& lightPosition, float lightRadius);
-
-std::unique_ptr<Geometry> getAmbientShadowGeometry(const FloatRect& casterRect,
- float casterCornerRadius, float casterZ,
- bool casterIsTranslucent,
- const vec4& ambientColor);
-
-int getVertexCountForGeometry(const Geometry& shadowGeometry);
-
-int getIndexCountForGeometry(const Geometry& shadowGeometry);
-
-void fillVerticesForGeometry(const Geometry& shadowGeometry, int vertexCount,
- Mesh::VertexArray<vec2> position, Mesh::VertexArray<vec4> shadowColor,
- Mesh::VertexArray<vec3> shadowParams);
-
-void fillIndicesForGeometry(const Geometry& shadowGeometry, int indexCount,
- int startingVertexOffset, uint16_t* indices);
-
-/**
- * Maps shadow geometry 'alpha' varying (1 for darkest, 0 for transparent) to
- * darkness at that spot. Values are determined by an exponential falloff
- * function provided by UX.
- *
- * The texture is used for quick lookup in theshadow shader.
- *
- * textureData - filled with shadow texture data that needs to be at least of
- * size textureWidth
- *
- * textureWidth - width of the texture, height is always 1
- */
-void fillShadowTextureData(uint8_t* textureData, size_t textureWidth);
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/GLVertexBuffer.cpp b/libs/renderengine/gl/GLVertexBuffer.cpp
deleted file mode 100644
index e50c471b6d..0000000000
--- a/libs/renderengine/gl/GLVertexBuffer.cpp
+++ /dev/null
@@ -1,55 +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.
- */
-
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "GLVertexBuffer.h"
-
-#include <GLES/gl.h>
-#include <GLES2/gl2.h>
-#include <nativebase/nativebase.h>
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GLVertexBuffer::GLVertexBuffer() {
- glGenBuffers(1, &mBufferName);
-}
-
-GLVertexBuffer::~GLVertexBuffer() {
- glDeleteBuffers(1, &mBufferName);
-}
-
-void GLVertexBuffer::allocateBuffers(const GLfloat data[], const GLuint size) {
- ATRACE_CALL();
- bind();
- glBufferData(GL_ARRAY_BUFFER, size * sizeof(GLfloat), data, GL_STATIC_DRAW);
- unbind();
-}
-
-void GLVertexBuffer::bind() const {
- glBindBuffer(GL_ARRAY_BUFFER, mBufferName);
-}
-
-void GLVertexBuffer::unbind() const {
- glBindBuffer(GL_ARRAY_BUFFER, 0);
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ImageManager.cpp b/libs/renderengine/gl/ImageManager.cpp
deleted file mode 100644
index 62566494f0..0000000000
--- a/libs/renderengine/gl/ImageManager.cpp
+++ /dev/null
@@ -1,148 +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
-#undef LOG_TAG
-#define LOG_TAG "RenderEngine"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include <pthread.h>
-
-#include <processgroup/sched_policy.h>
-#include <utils/Trace.h>
-#include "GLESRenderEngine.h"
-#include "ImageManager.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-ImageManager::ImageManager(GLESRenderEngine* engine) : mEngine(engine) {}
-
-void ImageManager::initThread() {
- mThread = std::thread([this]() { threadMain(); });
- pthread_setname_np(mThread.native_handle(), "ImageManager");
- // Use SCHED_FIFO to minimize jitter
- struct sched_param param = {0};
- param.sched_priority = 2;
- if (pthread_setschedparam(mThread.native_handle(), SCHED_FIFO, &param) != 0) {
- ALOGE("Couldn't set SCHED_FIFO for ImageManager");
- }
-}
-
-ImageManager::~ImageManager() {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mRunning = false;
- }
- mCondition.notify_all();
- if (mThread.joinable()) {
- mThread.join();
- }
-}
-
-void ImageManager::cacheAsync(const sp<GraphicBuffer>& buffer,
- const std::shared_ptr<Barrier>& barrier) {
- if (buffer == nullptr) {
- {
- std::lock_guard<std::mutex> lock(barrier->mutex);
- barrier->isOpen = true;
- barrier->result = BAD_VALUE;
- }
- barrier->condition.notify_one();
- return;
- }
- ATRACE_CALL();
- QueueEntry entry = {QueueEntry::Operation::Insert, buffer, buffer->getId(), barrier};
- queueOperation(std::move(entry));
-}
-
-status_t ImageManager::cache(const sp<GraphicBuffer>& buffer) {
- ATRACE_CALL();
- auto barrier = std::make_shared<Barrier>();
- cacheAsync(buffer, barrier);
- std::lock_guard<std::mutex> lock(barrier->mutex);
- barrier->condition.wait(barrier->mutex,
- [&]() REQUIRES(barrier->mutex) { return barrier->isOpen; });
- return barrier->result;
-}
-
-void ImageManager::releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) {
- ATRACE_CALL();
- QueueEntry entry = {QueueEntry::Operation::Delete, nullptr, bufferId, barrier};
- queueOperation(std::move(entry));
-}
-
-void ImageManager::queueOperation(const QueueEntry&& entry) {
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mQueue.emplace(entry);
- ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
- }
- mCondition.notify_one();
-}
-
-void ImageManager::threadMain() {
- set_sched_policy(0, SP_FOREGROUND);
- bool run;
- {
- std::lock_guard<std::mutex> lock(mMutex);
- run = mRunning;
- }
- while (run) {
- QueueEntry entry;
- {
- std::lock_guard<std::mutex> lock(mMutex);
- mCondition.wait(mMutex,
- [&]() REQUIRES(mMutex) { return !mQueue.empty() || !mRunning; });
- run = mRunning;
-
- if (!mRunning) {
- // if mRunning is false, then ImageManager is being destroyed, so
- // bail out now.
- break;
- }
-
- entry = mQueue.front();
- mQueue.pop();
- ATRACE_INT("ImageManagerQueueDepth", mQueue.size());
- }
-
- status_t result = NO_ERROR;
- switch (entry.op) {
- case QueueEntry::Operation::Delete:
- mEngine->unbindExternalTextureBufferInternal(entry.bufferId);
- break;
- case QueueEntry::Operation::Insert:
- result = mEngine->cacheExternalTextureBufferInternal(entry.buffer);
- break;
- }
- if (entry.barrier != nullptr) {
- {
- std::lock_guard<std::mutex> entryLock(entry.barrier->mutex);
- entry.barrier->result = result;
- entry.barrier->isOpen = true;
- }
- entry.barrier->condition.notify_one();
- }
- }
-
- ALOGD("Reached end of threadMain, terminating ImageManager thread!");
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ImageManager.h b/libs/renderengine/gl/ImageManager.h
deleted file mode 100644
index be67de8367..0000000000
--- a/libs/renderengine/gl/ImageManager.h
+++ /dev/null
@@ -1,74 +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.
- */
-
-#pragma once
-
-#include <condition_variable>
-#include <mutex>
-#include <queue>
-#include <thread>
-
-#include <ui/GraphicBuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class ImageManager {
-public:
- struct Barrier {
- std::mutex mutex;
- std::condition_variable_any condition;
- bool isOpen GUARDED_BY(mutex) = false;
- status_t result GUARDED_BY(mutex) = NO_ERROR;
- };
- ImageManager(GLESRenderEngine* engine);
- ~ImageManager();
- // Starts the background thread for the ImageManager
- // We need this to guarantee that the class is fully-constructed before the
- // thread begins running.
- void initThread();
- void cacheAsync(const sp<GraphicBuffer>& buffer, const std::shared_ptr<Barrier>& barrier)
- EXCLUDES(mMutex);
- status_t cache(const sp<GraphicBuffer>& buffer);
- void releaseAsync(uint64_t bufferId, const std::shared_ptr<Barrier>& barrier) EXCLUDES(mMutex);
-
-private:
- struct QueueEntry {
- enum class Operation { Delete, Insert };
-
- Operation op = Operation::Delete;
- sp<GraphicBuffer> buffer = nullptr;
- uint64_t bufferId = 0;
- std::shared_ptr<Barrier> barrier = nullptr;
- };
-
- void queueOperation(const QueueEntry&& entry);
- void threadMain();
- GLESRenderEngine* const mEngine;
- std::thread mThread;
- std::condition_variable_any mCondition;
- std::mutex mMutex;
- std::queue<QueueEntry> mQueue GUARDED_BY(mMutex);
-
- bool mRunning GUARDED_BY(mMutex) = true;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/Program.cpp b/libs/renderengine/gl/Program.cpp
deleted file mode 100644
index 26f6166761..0000000000
--- a/libs/renderengine/gl/Program.cpp
+++ /dev/null
@@ -1,175 +0,0 @@
-/*Gluint
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "Program.h"
-
-#include <stdint.h>
-
-#include <log/log.h>
-#include <math/mat4.h>
-#include <utils/String8.h>
-#include "ProgramCache.h"
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-Program::Program(const ProgramCache::Key& /*needs*/, const char* vertex, const char* fragment)
- : mInitialized(false) {
- GLuint vertexId = buildShader(vertex, GL_VERTEX_SHADER);
- GLuint fragmentId = buildShader(fragment, GL_FRAGMENT_SHADER);
- GLuint programId = glCreateProgram();
- glAttachShader(programId, vertexId);
- glAttachShader(programId, fragmentId);
- glBindAttribLocation(programId, position, "position");
- glBindAttribLocation(programId, texCoords, "texCoords");
- glBindAttribLocation(programId, cropCoords, "cropCoords");
- glBindAttribLocation(programId, shadowColor, "shadowColor");
- glBindAttribLocation(programId, shadowParams, "shadowParams");
- glLinkProgram(programId);
-
- GLint status;
- glGetProgramiv(programId, GL_LINK_STATUS, &status);
- if (status != GL_TRUE) {
- ALOGE("Error while linking shaders:");
- GLint infoLen = 0;
- glGetProgramiv(programId, GL_INFO_LOG_LENGTH, &infoLen);
- if (infoLen > 1) {
- GLchar log[infoLen];
- glGetProgramInfoLog(programId, infoLen, 0, &log[0]);
- ALOGE("%s", log);
- }
- glDetachShader(programId, vertexId);
- glDetachShader(programId, fragmentId);
- glDeleteShader(vertexId);
- glDeleteShader(fragmentId);
- glDeleteProgram(programId);
- } else {
- mProgram = programId;
- mVertexShader = vertexId;
- mFragmentShader = fragmentId;
- mInitialized = true;
- mProjectionMatrixLoc = glGetUniformLocation(programId, "projection");
- mTextureMatrixLoc = glGetUniformLocation(programId, "texture");
- mSamplerLoc = glGetUniformLocation(programId, "sampler");
- mColorLoc = glGetUniformLocation(programId, "color");
- mDisplayColorMatrixLoc = glGetUniformLocation(programId, "displayColorMatrix");
- mDisplayMaxLuminanceLoc = glGetUniformLocation(programId, "displayMaxLuminance");
- mMaxMasteringLuminanceLoc = glGetUniformLocation(programId, "maxMasteringLuminance");
- mMaxContentLuminanceLoc = glGetUniformLocation(programId, "maxContentLuminance");
- mInputTransformMatrixLoc = glGetUniformLocation(programId, "inputTransformMatrix");
- mOutputTransformMatrixLoc = glGetUniformLocation(programId, "outputTransformMatrix");
- mCornerRadiusLoc = glGetUniformLocation(programId, "cornerRadius");
- mCropCenterLoc = glGetUniformLocation(programId, "cropCenter");
-
- // set-up the default values for our uniforms
- glUseProgram(programId);
- glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, mat4().asArray());
- glEnableVertexAttribArray(0);
- }
-}
-
-Program::~Program() {
- glDetachShader(mProgram, mVertexShader);
- glDetachShader(mProgram, mFragmentShader);
- glDeleteShader(mVertexShader);
- glDeleteShader(mFragmentShader);
- glDeleteProgram(mProgram);
-}
-
-bool Program::isValid() const {
- return mInitialized;
-}
-
-void Program::use() {
- glUseProgram(mProgram);
-}
-
-GLuint Program::getAttrib(const char* name) const {
- // TODO: maybe use a local cache
- return glGetAttribLocation(mProgram, name);
-}
-
-GLint Program::getUniform(const char* name) const {
- // TODO: maybe use a local cache
- return glGetUniformLocation(mProgram, name);
-}
-
-GLuint Program::buildShader(const char* source, GLenum type) {
- GLuint shader = glCreateShader(type);
- glShaderSource(shader, 1, &source, 0);
- glCompileShader(shader);
- GLint status;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
- if (status != GL_TRUE) {
- // Some drivers return wrong values for GL_INFO_LOG_LENGTH
- // use a fixed size instead
- GLchar log[512];
- glGetShaderInfoLog(shader, sizeof(log), 0, log);
- ALOGE("Error while compiling shader: \n%s\n%s", source, log);
- glDeleteShader(shader);
- return 0;
- }
- return shader;
-}
-
-void Program::setUniforms(const Description& desc) {
- // TODO: we should have a mechanism here to not always reset uniforms that
- // didn't change for this program.
-
- if (mSamplerLoc >= 0) {
- glUniform1i(mSamplerLoc, 0);
- glUniformMatrix4fv(mTextureMatrixLoc, 1, GL_FALSE, desc.texture.getMatrix().asArray());
- }
- if (mColorLoc >= 0) {
- const float color[4] = {desc.color.r, desc.color.g, desc.color.b, desc.color.a};
- glUniform4fv(mColorLoc, 1, color);
- }
- if (mDisplayColorMatrixLoc >= 0) {
- glUniformMatrix4fv(mDisplayColorMatrixLoc, 1, GL_FALSE, desc.displayColorMatrix.asArray());
- }
- if (mInputTransformMatrixLoc >= 0) {
- mat4 inputTransformMatrix = desc.inputTransformMatrix;
- glUniformMatrix4fv(mInputTransformMatrixLoc, 1, GL_FALSE, inputTransformMatrix.asArray());
- }
- if (mOutputTransformMatrixLoc >= 0) {
- // The output transform matrix and color matrix can be combined as one matrix
- // that is applied right before applying OETF.
- mat4 outputTransformMatrix = desc.colorMatrix * desc.outputTransformMatrix;
- glUniformMatrix4fv(mOutputTransformMatrixLoc, 1, GL_FALSE, outputTransformMatrix.asArray());
- }
- if (mDisplayMaxLuminanceLoc >= 0) {
- glUniform1f(mDisplayMaxLuminanceLoc, desc.displayMaxLuminance);
- }
- if (mMaxMasteringLuminanceLoc >= 0) {
- glUniform1f(mMaxMasteringLuminanceLoc, desc.maxMasteringLuminance);
- }
- if (mMaxContentLuminanceLoc >= 0) {
- glUniform1f(mMaxContentLuminanceLoc, desc.maxContentLuminance);
- }
- if (mCornerRadiusLoc >= 0) {
- glUniform1f(mCornerRadiusLoc, desc.cornerRadius);
- }
- if (mCropCenterLoc >= 0) {
- glUniform2f(mCropCenterLoc, desc.cropSize.x / 2.0f, desc.cropSize.y / 2.0f);
- }
- // these uniforms are always present
- glUniformMatrix4fv(mProjectionMatrixLoc, 1, GL_FALSE, desc.projectionMatrix.asArray());
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/Program.h b/libs/renderengine/gl/Program.h
deleted file mode 100644
index 41f1bf865e..0000000000
--- a/libs/renderengine/gl/Program.h
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_RENDER_ENGINE_PROGRAM_H
-#define SF_RENDER_ENGINE_PROGRAM_H
-
-#include <stdint.h>
-
-#include <GLES2/gl2.h>
-#include <renderengine/private/Description.h>
-#include "ProgramCache.h"
-
-namespace android {
-
-class String8;
-
-namespace renderengine {
-namespace gl {
-
-/*
- * Abstracts a GLSL program comprising a vertex and fragment shader
- */
-class Program {
-public:
- // known locations for position and texture coordinates
- enum {
- /* position of each vertex for vertex shader */
- position = 0,
-
- /* UV coordinates for texture mapping */
- texCoords = 1,
-
- /* Crop coordinates, in pixels */
- cropCoords = 2,
-
- /* Shadow color */
- shadowColor = 3,
-
- /* Shadow params */
- shadowParams = 4,
- };
-
- Program(const ProgramCache::Key& needs, const char* vertex, const char* fragment);
- ~Program();
-
- /* whether this object is usable */
- bool isValid() const;
-
- /* Binds this program to the GLES context */
- void use();
-
- /* Returns the location of the specified attribute */
- GLuint getAttrib(const char* name) const;
-
- /* Returns the location of the specified uniform */
- GLint getUniform(const char* name) const;
-
- /* set-up uniforms from the description */
- void setUniforms(const Description& desc);
-
-private:
- GLuint buildShader(const char* source, GLenum type);
-
- // whether the initialization succeeded
- bool mInitialized;
-
- // Name of the OpenGL program and shaders
- GLuint mProgram;
- GLuint mVertexShader;
- GLuint mFragmentShader;
-
- /* location of the projection matrix uniform */
- GLint mProjectionMatrixLoc;
-
- /* location of the texture matrix uniform */
- GLint mTextureMatrixLoc;
-
- /* location of the sampler uniform */
- GLint mSamplerLoc;
-
- /* location of the color uniform */
- GLint mColorLoc;
-
- /* location of display luminance uniform */
- GLint mDisplayMaxLuminanceLoc;
- /* location of max mastering luminance uniform */
- GLint mMaxMasteringLuminanceLoc;
- /* location of max content luminance uniform */
- GLint mMaxContentLuminanceLoc;
-
- /* location of transform matrix */
- GLint mInputTransformMatrixLoc;
- GLint mOutputTransformMatrixLoc;
- GLint mDisplayColorMatrixLoc;
-
- /* location of corner radius uniform */
- GLint mCornerRadiusLoc;
-
- /* location of surface crop origin uniform, for rounded corner clipping */
- GLint mCropCenterLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_PROGRAM_H */
diff --git a/libs/renderengine/gl/ProgramCache.cpp b/libs/renderengine/gl/ProgramCache.cpp
deleted file mode 100644
index 5ff92402dc..0000000000
--- a/libs/renderengine/gl/ProgramCache.cpp
+++ /dev/null
@@ -1,830 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "ProgramCache.h"
-
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <log/log.h>
-#include <renderengine/private/Description.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-#include "Program.h"
-
-ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::gl::ProgramCache)
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/*
- * A simple formatter class to automatically add the endl and
- * manage the indentation.
- */
-
-class Formatter;
-static Formatter& indent(Formatter& f);
-static Formatter& dedent(Formatter& f);
-
-class Formatter {
- String8 mString;
- int mIndent;
- typedef Formatter& (*FormaterManipFunc)(Formatter&);
- friend Formatter& indent(Formatter& f);
- friend Formatter& dedent(Formatter& f);
-
-public:
- Formatter() : mIndent(0) {}
-
- String8 getString() const { return mString; }
-
- friend Formatter& operator<<(Formatter& out, const char* in) {
- for (int i = 0; i < out.mIndent; i++) {
- out.mString.append(" ");
- }
- out.mString.append(in);
- out.mString.append("\n");
- return out;
- }
- friend inline Formatter& operator<<(Formatter& out, const String8& in) {
- return operator<<(out, in.string());
- }
- friend inline Formatter& operator<<(Formatter& to, FormaterManipFunc func) {
- return (*func)(to);
- }
-};
-Formatter& indent(Formatter& f) {
- f.mIndent++;
- return f;
-}
-Formatter& dedent(Formatter& f) {
- f.mIndent--;
- return f;
-}
-
-void ProgramCache::primeCache(
- EGLContext context, bool useColorManagement, bool toneMapperShaderOnly) {
- auto& cache = mCaches[context];
- uint32_t shaderCount = 0;
-
- if (toneMapperShaderOnly) {
- Key shaderKey;
- // base settings used by HDR->SDR tonemap only
- shaderKey.set(Key::BLEND_MASK | Key::INPUT_TRANSFORM_MATRIX_MASK |
- Key::OUTPUT_TRANSFORM_MATRIX_MASK | Key::OUTPUT_TF_MASK |
- Key::OPACITY_MASK | Key::ALPHA_MASK |
- Key::ROUNDED_CORNERS_MASK | Key::TEXTURE_MASK,
- Key::BLEND_NORMAL | Key::INPUT_TRANSFORM_MATRIX_ON |
- Key::OUTPUT_TRANSFORM_MATRIX_ON | Key::OUTPUT_TF_SRGB |
- Key::OPACITY_OPAQUE | Key::ALPHA_EQ_ONE |
- Key::ROUNDED_CORNERS_OFF | Key::TEXTURE_EXT);
- for (int i = 0; i < 4; i++) {
- // Cache input transfer for HLG & ST2084
- shaderKey.set(Key::INPUT_TF_MASK, (i & 1) ?
- Key::INPUT_TF_HLG : Key::INPUT_TF_ST2084);
-
- // Cache Y410 input on or off
- shaderKey.set(Key::Y410_BT2020_MASK, (i & 2) ?
- Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
- if (cache.count(shaderKey) == 0) {
- cache.emplace(shaderKey, generateProgram(shaderKey));
- shaderCount++;
- }
- }
- return;
- }
-
- uint32_t keyMask = Key::BLEND_MASK | Key::OPACITY_MASK | Key::ALPHA_MASK | Key::TEXTURE_MASK
- | Key::ROUNDED_CORNERS_MASK;
- // Prime the cache for all combinations of the above masks,
- // leaving off the experimental color matrix mask options.
-
- nsecs_t timeBefore = systemTime();
- for (uint32_t keyVal = 0; keyVal <= keyMask; keyVal++) {
- Key shaderKey;
- shaderKey.set(keyMask, keyVal);
- uint32_t tex = shaderKey.getTextureTarget();
- if (tex != Key::TEXTURE_OFF && tex != Key::TEXTURE_EXT && tex != Key::TEXTURE_2D) {
- continue;
- }
- if (cache.count(shaderKey) == 0) {
- cache.emplace(shaderKey, generateProgram(shaderKey));
- shaderCount++;
- }
- }
-
- // Prime for sRGB->P3 conversion
- if (useColorManagement) {
- Key shaderKey;
- shaderKey.set(Key::BLEND_MASK | Key::OUTPUT_TRANSFORM_MATRIX_MASK | Key::INPUT_TF_MASK |
- Key::OUTPUT_TF_MASK,
- Key::BLEND_PREMULT | Key::OUTPUT_TRANSFORM_MATRIX_ON | Key::INPUT_TF_SRGB |
- Key::OUTPUT_TF_SRGB);
- for (int i = 0; i < 16; i++) {
- shaderKey.set(Key::OPACITY_MASK,
- (i & 1) ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT);
- shaderKey.set(Key::ALPHA_MASK, (i & 2) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE);
-
- // Cache rounded corners
- shaderKey.set(Key::ROUNDED_CORNERS_MASK,
- (i & 4) ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF);
-
- // Cache texture off option for window transition
- shaderKey.set(Key::TEXTURE_MASK, (i & 8) ? Key::TEXTURE_EXT : Key::TEXTURE_OFF);
- if (cache.count(shaderKey) == 0) {
- cache.emplace(shaderKey, generateProgram(shaderKey));
- shaderCount++;
- }
- }
- }
-
- nsecs_t timeAfter = systemTime();
- float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
- ALOGD("shader cache generated - %u shaders in %f ms\n", shaderCount, compileTimeMs);
-}
-
-ProgramCache::Key ProgramCache::computeKey(const Description& description) {
- Key needs;
- needs.set(Key::TEXTURE_MASK,
- !description.textureEnabled
- ? Key::TEXTURE_OFF
- : description.texture.getTextureTarget() == GL_TEXTURE_EXTERNAL_OES
- ? Key::TEXTURE_EXT
- : description.texture.getTextureTarget() == GL_TEXTURE_2D
- ? Key::TEXTURE_2D
- : Key::TEXTURE_OFF)
- .set(Key::ALPHA_MASK, (description.color.a < 1) ? Key::ALPHA_LT_ONE : Key::ALPHA_EQ_ONE)
- .set(Key::BLEND_MASK,
- description.isPremultipliedAlpha ? Key::BLEND_PREMULT : Key::BLEND_NORMAL)
- .set(Key::OPACITY_MASK,
- description.isOpaque ? Key::OPACITY_OPAQUE : Key::OPACITY_TRANSLUCENT)
- .set(Key::Key::INPUT_TRANSFORM_MATRIX_MASK,
- description.hasInputTransformMatrix() ? Key::INPUT_TRANSFORM_MATRIX_ON
- : Key::INPUT_TRANSFORM_MATRIX_OFF)
- .set(Key::Key::OUTPUT_TRANSFORM_MATRIX_MASK,
- description.hasOutputTransformMatrix() || description.hasColorMatrix()
- ? Key::OUTPUT_TRANSFORM_MATRIX_ON
- : Key::OUTPUT_TRANSFORM_MATRIX_OFF)
- .set(Key::Key::DISPLAY_COLOR_TRANSFORM_MATRIX_MASK,
- description.hasDisplayColorMatrix() ? Key::DISPLAY_COLOR_TRANSFORM_MATRIX_ON
- : Key::DISPLAY_COLOR_TRANSFORM_MATRIX_OFF)
- .set(Key::ROUNDED_CORNERS_MASK,
- description.cornerRadius > 0 ? Key::ROUNDED_CORNERS_ON : Key::ROUNDED_CORNERS_OFF)
- .set(Key::SHADOW_MASK, description.drawShadows ? Key::SHADOW_ON : Key::SHADOW_OFF);
- needs.set(Key::Y410_BT2020_MASK,
- description.isY410BT2020 ? Key::Y410_BT2020_ON : Key::Y410_BT2020_OFF);
-
- if (needs.hasTransformMatrix() ||
- (description.inputTransferFunction != description.outputTransferFunction)) {
- switch (description.inputTransferFunction) {
- case Description::TransferFunction::LINEAR:
- default:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_LINEAR);
- break;
- case Description::TransferFunction::SRGB:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_SRGB);
- break;
- case Description::TransferFunction::ST2084:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_ST2084);
- break;
- case Description::TransferFunction::HLG:
- needs.set(Key::INPUT_TF_MASK, Key::INPUT_TF_HLG);
- break;
- }
-
- switch (description.outputTransferFunction) {
- case Description::TransferFunction::LINEAR:
- default:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_LINEAR);
- break;
- case Description::TransferFunction::SRGB:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_SRGB);
- break;
- case Description::TransferFunction::ST2084:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_ST2084);
- break;
- case Description::TransferFunction::HLG:
- needs.set(Key::OUTPUT_TF_MASK, Key::OUTPUT_TF_HLG);
- break;
- }
- }
-
- return needs;
-}
-
-// Generate EOTF that converts signal values to relative display light,
-// both normalized to [0, 1].
-void ProgramCache::generateEOTF(Formatter& fs, const Key& needs) {
- switch (needs.getInputTF()) {
- case Key::INPUT_TF_SRGB:
- fs << R"__SHADER__(
- float EOTF_sRGB(float srgb) {
- return srgb <= 0.04045 ? srgb / 12.92 : pow((srgb + 0.055) / 1.055, 2.4);
- }
-
- vec3 EOTF_sRGB(const vec3 srgb) {
- return vec3(EOTF_sRGB(srgb.r), EOTF_sRGB(srgb.g), EOTF_sRGB(srgb.b));
- }
-
- vec3 EOTF(const vec3 srgb) {
- return sign(srgb.rgb) * EOTF_sRGB(abs(srgb.rgb));
- }
- )__SHADER__";
- break;
- case Key::INPUT_TF_ST2084:
- fs << R"__SHADER__(
- vec3 EOTF(const highp vec3 color) {
- const highp float m1 = (2610.0 / 4096.0) / 4.0;
- const highp float m2 = (2523.0 / 4096.0) * 128.0;
- const highp float c1 = (3424.0 / 4096.0);
- const highp float c2 = (2413.0 / 4096.0) * 32.0;
- const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
- highp vec3 tmp = pow(clamp(color, 0.0, 1.0), 1.0 / vec3(m2));
- tmp = max(tmp - c1, 0.0) / (c2 - c3 * tmp);
- return pow(tmp, 1.0 / vec3(m1));
- }
- )__SHADER__";
- break;
- case Key::INPUT_TF_HLG:
- fs << R"__SHADER__(
- highp float EOTF_channel(const highp float channel) {
- const highp float a = 0.17883277;
- const highp float b = 0.28466892;
- const highp float c = 0.55991073;
- return channel <= 0.5 ? channel * channel / 3.0 :
- (exp((channel - c) / a) + b) / 12.0;
- }
-
- vec3 EOTF(const highp vec3 color) {
- return vec3(EOTF_channel(color.r), EOTF_channel(color.g),
- EOTF_channel(color.b));
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- vec3 EOTF(const vec3 linear) {
- return linear;
- }
- )__SHADER__";
- break;
- }
-}
-
-void ProgramCache::generateToneMappingProcess(Formatter& fs, const Key& needs) {
- // Convert relative light to absolute light.
- switch (needs.getInputTF()) {
- case Key::INPUT_TF_ST2084:
- fs << R"__SHADER__(
- highp vec3 ScaleLuminance(highp vec3 color) {
- return color * 10000.0;
- }
- )__SHADER__";
- break;
- case Key::INPUT_TF_HLG:
- fs << R"__SHADER__(
- highp vec3 ScaleLuminance(highp vec3 color) {
- // The formula is:
- // alpha * pow(Y, gamma - 1.0) * color + beta;
- // where alpha is 1000.0, gamma is 1.2, beta is 0.0.
- return color * 1000.0 * pow(color.y, 0.2);
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- highp vec3 ScaleLuminance(highp vec3 color) {
- return color * displayMaxLuminance;
- }
- )__SHADER__";
- break;
- }
-
- // Tone map absolute light to display luminance range.
- switch (needs.getInputTF()) {
- case Key::INPUT_TF_ST2084:
- case Key::INPUT_TF_HLG:
- switch (needs.getOutputTF()) {
- case Key::OUTPUT_TF_HLG:
- // Right now when mixed PQ and HLG contents are presented,
- // HLG content will always be converted to PQ. However, for
- // completeness, we simply clamp the value to [0.0, 1000.0].
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- return clamp(color, 0.0, 1000.0);
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_ST2084:
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- return color;
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- float maxMasteringLumi = maxMasteringLuminance;
- float maxContentLumi = maxContentLuminance;
- float maxInLumi = min(maxMasteringLumi, maxContentLumi);
- float maxOutLumi = displayMaxLuminance;
-
- float nits = color.y;
-
- // clamp to max input luminance
- nits = clamp(nits, 0.0, maxInLumi);
-
- // scale [0.0, maxInLumi] to [0.0, maxOutLumi]
- if (maxInLumi <= maxOutLumi) {
- return color * (maxOutLumi / maxInLumi);
- } else {
- // three control points
- const float x0 = 10.0;
- const float y0 = 17.0;
- float x1 = maxOutLumi * 0.75;
- float y1 = x1;
- float x2 = x1 + (maxInLumi - x1) / 2.0;
- float y2 = y1 + (maxOutLumi - y1) * 0.75;
-
- // horizontal distances between the last three control points
- float h12 = x2 - x1;
- float h23 = maxInLumi - x2;
- // tangents at the last three control points
- float m1 = (y2 - y1) / h12;
- float m3 = (maxOutLumi - y2) / h23;
- float m2 = (m1 + m3) / 2.0;
-
- if (nits < x0) {
- // scale [0.0, x0] to [0.0, y0] linearly
- float slope = y0 / x0;
- return color * slope;
- } else if (nits < x1) {
- // scale [x0, x1] to [y0, y1] linearly
- float slope = (y1 - y0) / (x1 - x0);
- nits = y0 + (nits - x0) * slope;
- } else if (nits < x2) {
- // scale [x1, x2] to [y1, y2] using Hermite interp
- float t = (nits - x1) / h12;
- nits = (y1 * (1.0 + 2.0 * t) + h12 * m1 * t) * (1.0 - t) * (1.0 - t) +
- (y2 * (3.0 - 2.0 * t) + h12 * m2 * (t - 1.0)) * t * t;
- } else {
- // scale [x2, maxInLumi] to [y2, maxOutLumi] using Hermite interp
- float t = (nits - x2) / h23;
- nits = (y2 * (1.0 + 2.0 * t) + h23 * m2 * t) * (1.0 - t) * (1.0 - t) +
- (maxOutLumi * (3.0 - 2.0 * t) + h23 * m3 * (t - 1.0)) * t * t;
- }
- }
-
- // color.y is greater than x0 and is thus non-zero
- return color * (nits / color.y);
- }
- )__SHADER__";
- break;
- }
- break;
- default:
- // inverse tone map; the output luminance can be up to maxOutLumi.
- fs << R"__SHADER__(
- highp vec3 ToneMap(highp vec3 color) {
- const float maxOutLumi = 3000.0;
-
- const float x0 = 5.0;
- const float y0 = 2.5;
- float x1 = displayMaxLuminance * 0.7;
- float y1 = maxOutLumi * 0.15;
- float x2 = displayMaxLuminance * 0.9;
- float y2 = maxOutLumi * 0.45;
- float x3 = displayMaxLuminance;
- float y3 = maxOutLumi;
-
- float c1 = y1 / 3.0;
- float c2 = y2 / 2.0;
- float c3 = y3 / 1.5;
-
- float nits = color.y;
-
- float scale;
- if (nits <= x0) {
- // scale [0.0, x0] to [0.0, y0] linearly
- const float slope = y0 / x0;
- return color * slope;
- } else if (nits <= x1) {
- // scale [x0, x1] to [y0, y1] using a curve
- float t = (nits - x0) / (x1 - x0);
- nits = (1.0 - t) * (1.0 - t) * y0 + 2.0 * (1.0 - t) * t * c1 + t * t * y1;
- } else if (nits <= x2) {
- // scale [x1, x2] to [y1, y2] using a curve
- float t = (nits - x1) / (x2 - x1);
- nits = (1.0 - t) * (1.0 - t) * y1 + 2.0 * (1.0 - t) * t * c2 + t * t * y2;
- } else {
- // scale [x2, x3] to [y2, y3] using a curve
- float t = (nits - x2) / (x3 - x2);
- nits = (1.0 - t) * (1.0 - t) * y2 + 2.0 * (1.0 - t) * t * c3 + t * t * y3;
- }
-
- // color.y is greater than x0 and is thus non-zero
- return color * (nits / color.y);
- }
- )__SHADER__";
- break;
- }
-
- // convert absolute light to relative light.
- switch (needs.getOutputTF()) {
- case Key::OUTPUT_TF_ST2084:
- fs << R"__SHADER__(
- highp vec3 NormalizeLuminance(highp vec3 color) {
- return color / 10000.0;
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_HLG:
- fs << R"__SHADER__(
- highp vec3 NormalizeLuminance(highp vec3 color) {
- return color / 1000.0 * pow(color.y / 1000.0, -0.2 / 1.2);
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- highp vec3 NormalizeLuminance(highp vec3 color) {
- return color / displayMaxLuminance;
- }
- )__SHADER__";
- break;
- }
-}
-
-// Generate OOTF that modifies the relative scence light to relative display light.
-void ProgramCache::generateOOTF(Formatter& fs, const ProgramCache::Key& needs) {
- if (!needs.needsToneMapping()) {
- fs << R"__SHADER__(
- highp vec3 OOTF(const highp vec3 color) {
- return color;
- }
- )__SHADER__";
- } else {
- generateToneMappingProcess(fs, needs);
- fs << R"__SHADER__(
- highp vec3 OOTF(const highp vec3 color) {
- return NormalizeLuminance(ToneMap(ScaleLuminance(color)));
- }
- )__SHADER__";
- }
-}
-
-// Generate OETF that converts relative display light to signal values,
-// both normalized to [0, 1]
-void ProgramCache::generateOETF(Formatter& fs, const Key& needs) {
- switch (needs.getOutputTF()) {
- case Key::OUTPUT_TF_SRGB:
- fs << R"__SHADER__(
- float OETF_sRGB(const float linear) {
- return linear <= 0.0031308 ?
- linear * 12.92 : (pow(linear, 1.0 / 2.4) * 1.055) - 0.055;
- }
-
- vec3 OETF_sRGB(const vec3 linear) {
- return vec3(OETF_sRGB(linear.r), OETF_sRGB(linear.g), OETF_sRGB(linear.b));
- }
-
- vec3 OETF(const vec3 linear) {
- return sign(linear.rgb) * OETF_sRGB(abs(linear.rgb));
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_ST2084:
- fs << R"__SHADER__(
- vec3 OETF(const vec3 linear) {
- const highp float m1 = (2610.0 / 4096.0) / 4.0;
- const highp float m2 = (2523.0 / 4096.0) * 128.0;
- const highp float c1 = (3424.0 / 4096.0);
- const highp float c2 = (2413.0 / 4096.0) * 32.0;
- const highp float c3 = (2392.0 / 4096.0) * 32.0;
-
- highp vec3 tmp = pow(linear, vec3(m1));
- tmp = (c1 + c2 * tmp) / (1.0 + c3 * tmp);
- return pow(tmp, vec3(m2));
- }
- )__SHADER__";
- break;
- case Key::OUTPUT_TF_HLG:
- fs << R"__SHADER__(
- highp float OETF_channel(const highp float channel) {
- const highp float a = 0.17883277;
- const highp float b = 0.28466892;
- const highp float c = 0.55991073;
- return channel <= 1.0 / 12.0 ? sqrt(3.0 * channel) :
- a * log(12.0 * channel - b) + c;
- }
-
- vec3 OETF(const highp vec3 color) {
- return vec3(OETF_channel(color.r), OETF_channel(color.g),
- OETF_channel(color.b));
- }
- )__SHADER__";
- break;
- default:
- fs << R"__SHADER__(
- vec3 OETF(const vec3 linear) {
- return linear;
- }
- )__SHADER__";
- break;
- }
-}
-
-String8 ProgramCache::generateVertexShader(const Key& needs) {
- Formatter vs;
- if (needs.hasTextureCoords()) {
- vs << "attribute vec4 texCoords;"
- << "varying vec2 outTexCoords;";
- }
- if (needs.hasRoundedCorners()) {
- vs << "attribute lowp vec4 cropCoords;";
- vs << "varying lowp vec2 outCropCoords;";
- }
- if (needs.drawShadows()) {
- vs << "attribute lowp vec4 shadowColor;";
- vs << "varying lowp vec4 outShadowColor;";
- vs << "attribute lowp vec4 shadowParams;";
- vs << "varying lowp vec3 outShadowParams;";
- }
- vs << "attribute vec4 position;"
- << "uniform mat4 projection;"
- << "uniform mat4 texture;"
- << "void main(void) {" << indent << "gl_Position = projection * position;";
- if (needs.hasTextureCoords()) {
- vs << "outTexCoords = (texture * texCoords).st;";
- }
- if (needs.hasRoundedCorners()) {
- vs << "outCropCoords = cropCoords.st;";
- }
- if (needs.drawShadows()) {
- vs << "outShadowColor = shadowColor;";
- vs << "outShadowParams = shadowParams.xyz;";
- }
- vs << dedent << "}";
- return vs.getString();
-}
-
-String8 ProgramCache::generateFragmentShader(const Key& needs) {
- Formatter fs;
- if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
- fs << "#extension GL_OES_EGL_image_external : require";
- }
-
- // default precision is required-ish in fragment shaders
- fs << "precision mediump float;";
-
- if (needs.getTextureTarget() == Key::TEXTURE_EXT) {
- fs << "uniform samplerExternalOES sampler;";
- } else if (needs.getTextureTarget() == Key::TEXTURE_2D) {
- fs << "uniform sampler2D sampler;";
- }
-
- if (needs.hasTextureCoords()) {
- fs << "varying vec2 outTexCoords;";
- }
-
- if (needs.hasRoundedCorners()) {
- // Rounded corners implementation using a signed distance function.
- fs << R"__SHADER__(
- uniform float cornerRadius;
- uniform vec2 cropCenter;
- varying vec2 outCropCoords;
-
- /**
- * This function takes the current crop coordinates and calculates an alpha value based
- * on the corner radius and distance from the crop center.
- */
- float applyCornerRadius(vec2 cropCoords)
- {
- vec2 position = cropCoords - cropCenter;
- // Scale down the dist vector here, as otherwise large corner
- // radii can cause floating point issues when computing the norm
- vec2 dist = (abs(position) - cropCenter + vec2(cornerRadius)) / 16.0;
- // Once we've found the norm, then scale back up.
- float plane = length(max(dist, vec2(0.0))) * 16.0;
- return 1.0 - clamp(plane - cornerRadius, 0.0, 1.0);
- }
- )__SHADER__";
- }
-
- if (needs.drawShadows()) {
- fs << R"__SHADER__(
- varying lowp vec4 outShadowColor;
- varying lowp vec3 outShadowParams;
-
- /**
- * Returns the shadow color.
- */
- vec4 getShadowColor()
- {
- lowp float d = length(outShadowParams.xy);
- vec2 uv = vec2(outShadowParams.z * (1.0 - d), 0.5);
- lowp float factor = texture2D(sampler, uv).a;
- return outShadowColor * factor;
- }
- )__SHADER__";
- }
-
- if (needs.getTextureTarget() == Key::TEXTURE_OFF || needs.hasAlpha()) {
- fs << "uniform vec4 color;";
- }
-
- if (needs.isY410BT2020()) {
- fs << R"__SHADER__(
- vec3 convertY410BT2020(const vec3 color) {
- const vec3 offset = vec3(0.0625, 0.5, 0.5);
- const mat3 transform = mat3(
- vec3(1.1678, 1.1678, 1.1678),
- vec3( 0.0, -0.1878, 2.1481),
- vec3(1.6836, -0.6523, 0.0));
- // Y is in G, U is in R, and V is in B
- return clamp(transform * (color.grb - offset), 0.0, 1.0);
- }
- )__SHADER__";
- }
-
- if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
- needs.hasDisplayColorMatrix()) {
- if (needs.needsToneMapping()) {
- fs << "uniform float displayMaxLuminance;";
- fs << "uniform float maxMasteringLuminance;";
- fs << "uniform float maxContentLuminance;";
- }
-
- if (needs.hasInputTransformMatrix()) {
- fs << "uniform mat4 inputTransformMatrix;";
- fs << R"__SHADER__(
- highp vec3 InputTransform(const highp vec3 color) {
- return clamp(vec3(inputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
- }
- )__SHADER__";
- } else {
- fs << R"__SHADER__(
- highp vec3 InputTransform(const highp vec3 color) {
- return color;
- }
- )__SHADER__";
- }
-
- // the transformation from a wider colorspace to a narrower one can
- // result in >1.0 or <0.0 pixel values
- if (needs.hasOutputTransformMatrix()) {
- fs << "uniform mat4 outputTransformMatrix;";
- fs << R"__SHADER__(
- highp vec3 OutputTransform(const highp vec3 color) {
- return clamp(vec3(outputTransformMatrix * vec4(color, 1.0)), 0.0, 1.0);
- }
- )__SHADER__";
- } else {
- fs << R"__SHADER__(
- highp vec3 OutputTransform(const highp vec3 color) {
- return clamp(color, 0.0, 1.0);
- }
- )__SHADER__";
- }
-
- if (needs.hasDisplayColorMatrix()) {
- fs << "uniform mat4 displayColorMatrix;";
- fs << R"__SHADER__(
- highp vec3 DisplayColorMatrix(const highp vec3 color) {
- return clamp(vec3(displayColorMatrix * vec4(color, 1.0)), 0.0, 1.0);
- }
- )__SHADER__";
- } else {
- fs << R"__SHADER__(
- highp vec3 DisplayColorMatrix(const highp vec3 color) {
- return color;
- }
- )__SHADER__";
- }
-
- generateEOTF(fs, needs);
- generateOOTF(fs, needs);
- generateOETF(fs, needs);
- }
-
- fs << "void main(void) {" << indent;
- if (needs.drawShadows()) {
- fs << "gl_FragColor = getShadowColor();";
- } else {
- if (needs.isTexturing()) {
- fs << "gl_FragColor = texture2D(sampler, outTexCoords);";
- if (needs.isY410BT2020()) {
- fs << "gl_FragColor.rgb = convertY410BT2020(gl_FragColor.rgb);";
- }
- } else {
- fs << "gl_FragColor.rgb = color.rgb;";
- fs << "gl_FragColor.a = 1.0;";
- }
- if (needs.isOpaque()) {
- fs << "gl_FragColor.a = 1.0;";
- }
- }
-
- if (needs.hasTransformMatrix() || (needs.getInputTF() != needs.getOutputTF()) ||
- needs.hasDisplayColorMatrix()) {
- if (!needs.isOpaque() && needs.isPremultiplied()) {
- // un-premultiply if needed before linearization
- // avoid divide by 0 by adding 0.5/256 to the alpha channel
- fs << "gl_FragColor.rgb = gl_FragColor.rgb / (gl_FragColor.a + 0.0019);";
- }
- fs << "gl_FragColor.rgb = "
- "DisplayColorMatrix(OETF(OutputTransform(OOTF(InputTransform(EOTF(gl_FragColor.rgb)))"
- ")));";
-
- if (!needs.isOpaque() && needs.isPremultiplied()) {
- // and re-premultiply if needed after gamma correction
- fs << "gl_FragColor.rgb = gl_FragColor.rgb * (gl_FragColor.a + 0.0019);";
- }
- }
-
- /*
- * Whether applying layer alpha before or after color transform doesn't matter,
- * as long as we can undo premultiplication. But we cannot un-premultiply
- * for color transform if the layer alpha = 0, e.g. 0 / (0 + 0.0019) = 0.
- */
- if (!needs.drawShadows()) {
- if (needs.hasAlpha()) {
- // modulate the current alpha value with alpha set
- if (needs.isPremultiplied()) {
- // ... and the color too if we're premultiplied
- fs << "gl_FragColor *= color.a;";
- } else {
- fs << "gl_FragColor.a *= color.a;";
- }
- }
- }
-
- if (needs.hasRoundedCorners()) {
- if (needs.isPremultiplied()) {
- fs << "gl_FragColor *= vec4(applyCornerRadius(outCropCoords));";
- } else {
- fs << "gl_FragColor.a *= applyCornerRadius(outCropCoords);";
- }
- }
-
- fs << dedent << "}";
- return fs.getString();
-}
-
-std::unique_ptr<Program> ProgramCache::generateProgram(const Key& needs) {
- ATRACE_CALL();
-
- // vertex shader
- String8 vs = generateVertexShader(needs);
-
- // fragment shader
- String8 fs = generateFragmentShader(needs);
-
- return std::make_unique<Program>(needs, vs.string(), fs.string());
-}
-
-void ProgramCache::useProgram(EGLContext context, const Description& description) {
- // generate the key for the shader based on the description
- Key needs(computeKey(description));
-
- // look-up the program in the cache
- auto& cache = mCaches[context];
- auto it = cache.find(needs);
- if (it == cache.end()) {
- // we didn't find our program, so generate one...
- nsecs_t time = systemTime();
- it = cache.emplace(needs, generateProgram(needs)).first;
- time = systemTime() - time;
-
- ALOGV(">>> generated new program for context %p: needs=%08X, time=%u ms (%zu programs)",
- context, needs.mKey, uint32_t(ns2ms(time)), cache.size());
- }
-
- // here we have a suitable program for this description
- std::unique_ptr<Program>& program = it->second;
- if (program->isValid()) {
- program->use();
- program->setUniforms(description);
- }
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/ProgramCache.h b/libs/renderengine/gl/ProgramCache.h
deleted file mode 100644
index 535d21cd52..0000000000
--- a/libs/renderengine/gl/ProgramCache.h
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_RENDER_ENGINE_PROGRAMCACHE_H
-#define SF_RENDER_ENGINE_PROGRAMCACHE_H
-
-#include <memory>
-#include <unordered_map>
-
-#include <EGL/egl.h>
-#include <GLES2/gl2.h>
-#include <renderengine/private/Description.h>
-#include <utils/Singleton.h>
-#include <utils/TypeHelpers.h>
-
-namespace android {
-
-class String8;
-
-namespace renderengine {
-
-struct Description;
-
-namespace gl {
-
-class Formatter;
-class Program;
-
-/*
- * This class generates GLSL programs suitable to handle a given
- * Description. It's responsible for figuring out what to
- * generate from a Description.
- * It also maintains a cache of these Programs.
- */
-class ProgramCache : public Singleton<ProgramCache> {
-public:
- /*
- * Key is used to retrieve a Program in the cache.
- * A Key is generated from a Description.
- */
- class Key {
- friend class ProgramCache;
- typedef uint32_t key_t;
- key_t mKey;
-
- public:
- enum {
- BLEND_SHIFT = 0,
- BLEND_MASK = 1 << BLEND_SHIFT,
- BLEND_PREMULT = 1 << BLEND_SHIFT,
- BLEND_NORMAL = 0 << BLEND_SHIFT,
-
- OPACITY_SHIFT = 1,
- OPACITY_MASK = 1 << OPACITY_SHIFT,
- OPACITY_OPAQUE = 1 << OPACITY_SHIFT,
- OPACITY_TRANSLUCENT = 0 << OPACITY_SHIFT,
-
- ALPHA_SHIFT = 2,
- ALPHA_MASK = 1 << ALPHA_SHIFT,
- ALPHA_LT_ONE = 1 << ALPHA_SHIFT,
- ALPHA_EQ_ONE = 0 << ALPHA_SHIFT,
-
- TEXTURE_SHIFT = 3,
- TEXTURE_MASK = 3 << TEXTURE_SHIFT,
- TEXTURE_OFF = 0 << TEXTURE_SHIFT,
- TEXTURE_EXT = 1 << TEXTURE_SHIFT,
- TEXTURE_2D = 2 << TEXTURE_SHIFT,
-
- ROUNDED_CORNERS_SHIFT = 5,
- ROUNDED_CORNERS_MASK = 1 << ROUNDED_CORNERS_SHIFT,
- ROUNDED_CORNERS_OFF = 0 << ROUNDED_CORNERS_SHIFT,
- ROUNDED_CORNERS_ON = 1 << ROUNDED_CORNERS_SHIFT,
-
- INPUT_TRANSFORM_MATRIX_SHIFT = 6,
- INPUT_TRANSFORM_MATRIX_MASK = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
- INPUT_TRANSFORM_MATRIX_OFF = 0 << INPUT_TRANSFORM_MATRIX_SHIFT,
- INPUT_TRANSFORM_MATRIX_ON = 1 << INPUT_TRANSFORM_MATRIX_SHIFT,
-
- OUTPUT_TRANSFORM_MATRIX_SHIFT = 7,
- OUTPUT_TRANSFORM_MATRIX_MASK = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
- OUTPUT_TRANSFORM_MATRIX_OFF = 0 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
- OUTPUT_TRANSFORM_MATRIX_ON = 1 << OUTPUT_TRANSFORM_MATRIX_SHIFT,
-
- INPUT_TF_SHIFT = 8,
- INPUT_TF_MASK = 3 << INPUT_TF_SHIFT,
- INPUT_TF_LINEAR = 0 << INPUT_TF_SHIFT,
- INPUT_TF_SRGB = 1 << INPUT_TF_SHIFT,
- INPUT_TF_ST2084 = 2 << INPUT_TF_SHIFT,
- INPUT_TF_HLG = 3 << INPUT_TF_SHIFT,
-
- OUTPUT_TF_SHIFT = 10,
- OUTPUT_TF_MASK = 3 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_LINEAR = 0 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_SRGB = 1 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_ST2084 = 2 << OUTPUT_TF_SHIFT,
- OUTPUT_TF_HLG = 3 << OUTPUT_TF_SHIFT,
-
- Y410_BT2020_SHIFT = 12,
- Y410_BT2020_MASK = 1 << Y410_BT2020_SHIFT,
- Y410_BT2020_OFF = 0 << Y410_BT2020_SHIFT,
- Y410_BT2020_ON = 1 << Y410_BT2020_SHIFT,
-
- SHADOW_SHIFT = 13,
- SHADOW_MASK = 1 << SHADOW_SHIFT,
- SHADOW_OFF = 0 << SHADOW_SHIFT,
- SHADOW_ON = 1 << SHADOW_SHIFT,
-
- DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT = 14,
- DISPLAY_COLOR_TRANSFORM_MATRIX_MASK = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
- DISPLAY_COLOR_TRANSFORM_MATRIX_OFF = 0 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
- DISPLAY_COLOR_TRANSFORM_MATRIX_ON = 1 << DISPLAY_COLOR_TRANSFORM_MATRIX_SHIFT,
- };
-
- inline Key() : mKey(0) {}
- inline Key(const Key& rhs) : mKey(rhs.mKey) {}
-
- inline Key& set(key_t mask, key_t value) {
- mKey = (mKey & ~mask) | value;
- return *this;
- }
-
- inline bool isTexturing() const { return (mKey & TEXTURE_MASK) != TEXTURE_OFF; }
- inline bool hasTextureCoords() const { return isTexturing() && !drawShadows(); }
- inline int getTextureTarget() const { return (mKey & TEXTURE_MASK); }
- inline bool isPremultiplied() const { return (mKey & BLEND_MASK) == BLEND_PREMULT; }
- inline bool isOpaque() const { return (mKey & OPACITY_MASK) == OPACITY_OPAQUE; }
- inline bool hasAlpha() const { return (mKey & ALPHA_MASK) == ALPHA_LT_ONE; }
- inline bool hasRoundedCorners() const {
- return (mKey & ROUNDED_CORNERS_MASK) == ROUNDED_CORNERS_ON;
- }
- inline bool drawShadows() const { return (mKey & SHADOW_MASK) == SHADOW_ON; }
- inline bool hasInputTransformMatrix() const {
- return (mKey & INPUT_TRANSFORM_MATRIX_MASK) == INPUT_TRANSFORM_MATRIX_ON;
- }
- inline bool hasOutputTransformMatrix() const {
- return (mKey & OUTPUT_TRANSFORM_MATRIX_MASK) == OUTPUT_TRANSFORM_MATRIX_ON;
- }
- inline bool hasDisplayColorMatrix() const {
- return (mKey & DISPLAY_COLOR_TRANSFORM_MATRIX_MASK) ==
- DISPLAY_COLOR_TRANSFORM_MATRIX_ON;
- }
- inline bool hasTransformMatrix() const {
- return hasInputTransformMatrix() || hasOutputTransformMatrix();
- }
- inline int getInputTF() const { return (mKey & INPUT_TF_MASK); }
- inline int getOutputTF() const { return (mKey & OUTPUT_TF_MASK); }
-
- // When HDR and non-HDR contents are mixed, or different types of HDR contents are
- // mixed, we will do a tone mapping process to tone map the input content to output
- // content. Currently, the following conversions handled, they are:
- // * SDR -> HLG
- // * SDR -> PQ
- // * HLG -> PQ
- inline bool needsToneMapping() const {
- int inputTF = getInputTF();
- int outputTF = getOutputTF();
-
- // Return false when converting from SDR to SDR.
- if (inputTF == Key::INPUT_TF_SRGB && outputTF == Key::OUTPUT_TF_LINEAR) {
- return false;
- }
- if (inputTF == Key::INPUT_TF_LINEAR && outputTF == Key::OUTPUT_TF_SRGB) {
- return false;
- }
-
- inputTF >>= Key::INPUT_TF_SHIFT;
- outputTF >>= Key::OUTPUT_TF_SHIFT;
- return inputTF != outputTF;
- }
- inline bool isY410BT2020() const { return (mKey & Y410_BT2020_MASK) == Y410_BT2020_ON; }
-
- // for use by std::unordered_map
-
- bool operator==(const Key& other) const { return mKey == other.mKey; }
-
- struct Hash {
- size_t operator()(const Key& key) const { return static_cast<size_t>(key.mKey); }
- };
- };
-
- ProgramCache() = default;
- ~ProgramCache() = default;
-
- // Generate shaders to populate the cache
- void primeCache(const EGLContext context, bool useColorManagement, bool toneMapperShaderOnly);
-
- size_t getSize(const EGLContext context) { return mCaches[context].size(); }
-
- // useProgram lookup a suitable program in the cache or generates one
- // if none can be found.
- void useProgram(const EGLContext context, const Description& description);
-
- void purgeCaches() { mCaches.clear(); }
-
-private:
- // compute a cache Key from a Description
- static Key computeKey(const Description& description);
- // Generate EOTF based from Key.
- static void generateEOTF(Formatter& fs, const Key& needs);
- // Generate necessary tone mapping methods for OOTF.
- static void generateToneMappingProcess(Formatter& fs, const Key& needs);
- // Generate OOTF based from Key.
- static void generateOOTF(Formatter& fs, const Key& needs);
- // Generate OETF based from Key.
- static void generateOETF(Formatter& fs, const Key& needs);
- // generates a program from the Key
- static std::unique_ptr<Program> generateProgram(const Key& needs);
- // generates the vertex shader from the Key
- static String8 generateVertexShader(const Key& needs);
- // generates the fragment shader from the Key
- static String8 generateFragmentShader(const Key& needs);
-
- // Key/Value map used for caching Programs. Currently the cache
- // is never shrunk (and the GL program objects are never deleted).
- std::unordered_map<EGLContext, std::unordered_map<Key, std::unique_ptr<Program>, Key::Hash>>
- mCaches;
-};
-
-} // namespace gl
-} // namespace renderengine
-
-ANDROID_BASIC_TYPES_TRAITS(renderengine::gl::ProgramCache::Key)
-
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_PROGRAMCACHE_H */
diff --git a/libs/renderengine/gl/filters/BlurFilter.cpp b/libs/renderengine/gl/filters/BlurFilter.cpp
deleted file mode 100644
index 3455e08cfe..0000000000
--- a/libs/renderengine/gl/filters/BlurFilter.cpp
+++ /dev/null
@@ -1,268 +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 ATRACE_TAG ATRACE_TAG_GRAPHICS
-
-#include "BlurFilter.h"
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES3/gl3.h>
-#include <GLES3/gl3ext.h>
-#include <ui/GraphicTypes.h>
-#include <cstdint>
-
-#include <utils/Trace.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-BlurFilter::BlurFilter(GLESRenderEngine& engine)
- : mEngine(engine),
- mCompositionFbo(engine),
- mPingFbo(engine),
- mPongFbo(engine),
- mMixProgram(engine),
- mBlurProgram(engine) {
- mMixProgram.compile(getVertexShader(), getMixFragShader());
- mMPosLoc = mMixProgram.getAttributeLocation("aPosition");
- mMUvLoc = mMixProgram.getAttributeLocation("aUV");
- mMTextureLoc = mMixProgram.getUniformLocation("uTexture");
- mMCompositionTextureLoc = mMixProgram.getUniformLocation("uCompositionTexture");
- mMMixLoc = mMixProgram.getUniformLocation("uMix");
-
- mBlurProgram.compile(getVertexShader(), getFragmentShader());
- mBPosLoc = mBlurProgram.getAttributeLocation("aPosition");
- mBUvLoc = mBlurProgram.getAttributeLocation("aUV");
- mBTextureLoc = mBlurProgram.getUniformLocation("uTexture");
- mBOffsetLoc = mBlurProgram.getUniformLocation("uOffset");
-
- static constexpr auto size = 2.0f;
- static constexpr auto translation = 1.0f;
- const GLfloat vboData[] = {
- // Vertex data
- translation - size, -translation - size,
- translation - size, -translation + size,
- translation + size, -translation + size,
- // UV data
- 0.0f, 0.0f - translation,
- 0.0f, size - translation,
- size, size - translation
- };
- mMeshBuffer.allocateBuffers(vboData, 12 /* size */);
-}
-
-status_t BlurFilter::setAsDrawTarget(const DisplaySettings& display, uint32_t radius) {
- ATRACE_NAME("BlurFilter::setAsDrawTarget");
- mRadius = radius;
- mDisplayX = display.physicalDisplay.left;
- mDisplayY = display.physicalDisplay.top;
-
- if (mDisplayWidth < display.physicalDisplay.width() ||
- mDisplayHeight < display.physicalDisplay.height()) {
- ATRACE_NAME("BlurFilter::allocatingTextures");
-
- mDisplayWidth = display.physicalDisplay.width();
- mDisplayHeight = display.physicalDisplay.height();
- mCompositionFbo.allocateBuffers(mDisplayWidth, mDisplayHeight);
-
- const uint32_t fboWidth = floorf(mDisplayWidth * kFboScale);
- const uint32_t fboHeight = floorf(mDisplayHeight * kFboScale);
- mPingFbo.allocateBuffers(fboWidth, fboHeight);
- mPongFbo.allocateBuffers(fboWidth, fboHeight);
-
- if (mPingFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Invalid ping buffer");
- return mPingFbo.getStatus();
- }
- if (mPongFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Invalid pong buffer");
- return mPongFbo.getStatus();
- }
- if (mCompositionFbo.getStatus() != GL_FRAMEBUFFER_COMPLETE) {
- ALOGE("Invalid composition buffer");
- return mCompositionFbo.getStatus();
- }
- if (!mBlurProgram.isValid()) {
- ALOGE("Invalid shader");
- return GL_INVALID_OPERATION;
- }
- }
-
- mCompositionFbo.bind();
- glViewport(0, 0, mCompositionFbo.getBufferWidth(), mCompositionFbo.getBufferHeight());
- return NO_ERROR;
-}
-
-void BlurFilter::drawMesh(GLuint uv, GLuint position) {
-
- glEnableVertexAttribArray(uv);
- glEnableVertexAttribArray(position);
- mMeshBuffer.bind();
- glVertexAttribPointer(position, 2 /* size */, GL_FLOAT, GL_FALSE,
- 2 * sizeof(GLfloat) /* stride */, 0 /* offset */);
- glVertexAttribPointer(uv, 2 /* size */, GL_FLOAT, GL_FALSE, 0 /* stride */,
- (GLvoid*)(6 * sizeof(GLfloat)) /* offset */);
- mMeshBuffer.unbind();
-
- // draw mesh
- glDrawArrays(GL_TRIANGLES, 0 /* first */, 3 /* count */);
-}
-
-status_t BlurFilter::prepare() {
- ATRACE_NAME("BlurFilter::prepare");
-
- // Kawase is an approximation of Gaussian, but it behaves differently from it.
- // A radius transformation is required for approximating them, and also to introduce
- // non-integer steps, necessary to smoothly interpolate large radii.
- const auto radius = mRadius / 6.0f;
-
- // Calculate how many passes we'll do, based on the radius.
- // Too many passes will make the operation expensive.
- const auto passes = min(kMaxPasses, (uint32_t)ceil(radius));
-
- const float radiusByPasses = radius / (float)passes;
- const float stepX = radiusByPasses / (float)mCompositionFbo.getBufferWidth();
- const float stepY = radiusByPasses / (float)mCompositionFbo.getBufferHeight();
-
- // Let's start by downsampling and blurring the composited frame simultaneously.
- mBlurProgram.useProgram();
- glActiveTexture(GL_TEXTURE0);
- glUniform1i(mBTextureLoc, 0);
- glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
- glUniform2f(mBOffsetLoc, stepX, stepY);
- glViewport(0, 0, mPingFbo.getBufferWidth(), mPingFbo.getBufferHeight());
- mPingFbo.bind();
- drawMesh(mBUvLoc, mBPosLoc);
-
- // And now we'll ping pong between our textures, to accumulate the result of various offsets.
- GLFramebuffer* read = &mPingFbo;
- GLFramebuffer* draw = &mPongFbo;
- glViewport(0, 0, draw->getBufferWidth(), draw->getBufferHeight());
- for (auto i = 1; i < passes; i++) {
- ATRACE_NAME("BlurFilter::renderPass");
- draw->bind();
-
- glBindTexture(GL_TEXTURE_2D, read->getTextureName());
- glUniform2f(mBOffsetLoc, stepX * i, stepY * i);
-
- drawMesh(mBUvLoc, mBPosLoc);
-
- // Swap buffers for next iteration
- auto tmp = draw;
- draw = read;
- read = tmp;
- }
- mLastDrawTarget = read;
-
- return NO_ERROR;
-}
-
-status_t BlurFilter::render(bool multiPass) {
- ATRACE_NAME("BlurFilter::render");
-
- // Now let's scale our blur up. It will be interpolated with the larger composited
- // texture for the first frames, to hide downscaling artifacts.
- GLfloat mix = fmin(1.0, mRadius / kMaxCrossFadeRadius);
-
- // When doing multiple passes, we cannot try to read mCompositionFbo, given that we'll
- // be writing onto it. Let's disable the crossfade, otherwise we'd need 1 extra frame buffer,
- // as large as the screen size.
- if (mix >= 1 || multiPass) {
- mLastDrawTarget->bindAsReadBuffer();
- glBlitFramebuffer(0, 0, mLastDrawTarget->getBufferWidth(),
- mLastDrawTarget->getBufferHeight(), mDisplayX, mDisplayY, mDisplayWidth,
- mDisplayHeight, GL_COLOR_BUFFER_BIT, GL_LINEAR);
- return NO_ERROR;
- }
-
- mMixProgram.useProgram();
- glUniform1f(mMMixLoc, mix);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, mLastDrawTarget->getTextureName());
- glUniform1i(mMTextureLoc, 0);
- glActiveTexture(GL_TEXTURE1);
- glBindTexture(GL_TEXTURE_2D, mCompositionFbo.getTextureName());
- glUniform1i(mMCompositionTextureLoc, 1);
-
- drawMesh(mMUvLoc, mMPosLoc);
-
- glUseProgram(0);
- glActiveTexture(GL_TEXTURE0);
- mEngine.checkErrors("Drawing blur mesh");
- return NO_ERROR;
-}
-
-string BlurFilter::getVertexShader() const {
- return R"SHADER(#version 300 es
- precision mediump float;
-
- in vec2 aPosition;
- in highp vec2 aUV;
- out highp vec2 vUV;
-
- void main() {
- vUV = aUV;
- gl_Position = vec4(aPosition, 0.0, 1.0);
- }
- )SHADER";
-}
-
-string BlurFilter::getFragmentShader() const {
- return R"SHADER(#version 300 es
- precision mediump float;
-
- uniform sampler2D uTexture;
- uniform vec2 uOffset;
-
- in highp vec2 vUV;
- out vec4 fragColor;
-
- void main() {
- fragColor = texture(uTexture, vUV, 0.0);
- fragColor += texture(uTexture, vUV + vec2( uOffset.x, uOffset.y), 0.0);
- fragColor += texture(uTexture, vUV + vec2( uOffset.x, -uOffset.y), 0.0);
- fragColor += texture(uTexture, vUV + vec2(-uOffset.x, uOffset.y), 0.0);
- fragColor += texture(uTexture, vUV + vec2(-uOffset.x, -uOffset.y), 0.0);
-
- fragColor = vec4(fragColor.rgb * 0.2, 1.0);
- }
- )SHADER";
-}
-
-string BlurFilter::getMixFragShader() const {
- string shader = R"SHADER(#version 300 es
- precision mediump float;
-
- in highp vec2 vUV;
- out vec4 fragColor;
-
- uniform sampler2D uCompositionTexture;
- uniform sampler2D uTexture;
- uniform float uMix;
-
- void main() {
- vec4 blurred = texture(uTexture, vUV);
- vec4 composition = texture(uCompositionTexture, vUV);
- fragColor = mix(composition, blurred, uMix);
- }
- )SHADER";
- return shader;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/BlurFilter.h b/libs/renderengine/gl/filters/BlurFilter.h
deleted file mode 100644
index 593a8fd54e..0000000000
--- a/libs/renderengine/gl/filters/BlurFilter.h
+++ /dev/null
@@ -1,95 +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.
- */
-
-#pragma once
-
-#include <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-#include "../GLVertexBuffer.h"
-#include "GenericProgram.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-/**
- * This is an implementation of a Kawase blur, as described in here:
- * https://community.arm.com/cfs-file/__key/communityserver-blogs-components-weblogfiles/
- * 00-00-00-20-66/siggraph2015_2D00_mmg_2D00_marius_2D00_notes.pdf
- */
-class BlurFilter {
-public:
- // Downsample FBO to improve performance
- static constexpr float kFboScale = 0.25f;
- // Maximum number of render passes
- static constexpr uint32_t kMaxPasses = 4;
- // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited
- // image, up to this radius.
- static constexpr float kMaxCrossFadeRadius = 30.0f;
-
- explicit BlurFilter(GLESRenderEngine& engine);
- virtual ~BlurFilter(){};
-
- // Set up render targets, redirecting output to offscreen texture.
- status_t setAsDrawTarget(const DisplaySettings&, uint32_t radius);
- // Execute blur passes, rendering to offscreen texture.
- status_t prepare();
- // Render blur to the bound framebuffer (screen).
- status_t render(bool multiPass);
-
-private:
- uint32_t mRadius;
- void drawMesh(GLuint uv, GLuint position);
- string getVertexShader() const;
- string getFragmentShader() const;
- string getMixFragShader() const;
-
- GLESRenderEngine& mEngine;
- // Frame buffer holding the composited background.
- GLFramebuffer mCompositionFbo;
- // Frame buffers holding the blur passes.
- GLFramebuffer mPingFbo;
- GLFramebuffer mPongFbo;
- uint32_t mDisplayWidth = 0;
- uint32_t mDisplayHeight = 0;
- uint32_t mDisplayX = 0;
- uint32_t mDisplayY = 0;
- // Buffer holding the final blur pass.
- GLFramebuffer* mLastDrawTarget;
-
- // VBO containing vertex and uv data of a fullscreen triangle.
- GLVertexBuffer mMeshBuffer;
-
- GenericProgram mMixProgram;
- GLuint mMPosLoc;
- GLuint mMUvLoc;
- GLuint mMMixLoc;
- GLuint mMTextureLoc;
- GLuint mMCompositionTextureLoc;
-
- GenericProgram mBlurProgram;
- GLuint mBPosLoc;
- GLuint mBUvLoc;
- GLuint mBTextureLoc;
- GLuint mBOffsetLoc;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GenericProgram.cpp b/libs/renderengine/gl/filters/GenericProgram.cpp
deleted file mode 100644
index bb35889665..0000000000
--- a/libs/renderengine/gl/filters/GenericProgram.cpp
+++ /dev/null
@@ -1,122 +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.
- */
-
-#include "GenericProgram.h"
-
-#include <GLES/gl.h>
-#include <GLES/glext.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-GenericProgram::GenericProgram(GLESRenderEngine& engine) : mEngine(engine) {}
-
-GenericProgram::~GenericProgram() {
- if (mVertexShaderHandle != 0) {
- if (mProgramHandle != 0) {
- glDetachShader(mProgramHandle, mVertexShaderHandle);
- }
- glDeleteShader(mVertexShaderHandle);
- }
-
- if (mFragmentShaderHandle != 0) {
- if (mProgramHandle != 0) {
- glDetachShader(mProgramHandle, mFragmentShaderHandle);
- }
- glDeleteShader(mFragmentShaderHandle);
- }
-
- if (mProgramHandle != 0) {
- glDeleteProgram(mProgramHandle);
- }
-}
-
-void GenericProgram::compile(string vertexShader, string fragmentShader) {
- mVertexShaderHandle = compileShader(GL_VERTEX_SHADER, vertexShader);
- mFragmentShaderHandle = compileShader(GL_FRAGMENT_SHADER, fragmentShader);
- if (mVertexShaderHandle == 0 || mFragmentShaderHandle == 0) {
- ALOGE("Aborting program creation.");
- return;
- }
- mProgramHandle = createAndLink(mVertexShaderHandle, mFragmentShaderHandle);
- mEngine.checkErrors("Linking program");
-}
-
-void GenericProgram::useProgram() const {
- glUseProgram(mProgramHandle);
-}
-
-GLuint GenericProgram::compileShader(GLuint type, string src) const {
- const GLuint shader = glCreateShader(type);
- if (shader == 0) {
- mEngine.checkErrors("Creating shader");
- return 0;
- }
- const GLchar* charSrc = (const GLchar*)src.c_str();
- glShaderSource(shader, 1, &charSrc, nullptr);
- glCompileShader(shader);
-
- GLint isCompiled = 0;
- glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
- if (isCompiled == GL_FALSE) {
- GLint maxLength = 0;
- glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
- string errorLog;
- errorLog.reserve(maxLength);
- glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data());
- glDeleteShader(shader);
- ALOGE("Error compiling shader: %s", errorLog.c_str());
- return 0;
- }
- return shader;
-}
-GLuint GenericProgram::createAndLink(GLuint vertexShader, GLuint fragmentShader) const {
- const GLuint program = glCreateProgram();
- mEngine.checkErrors("Creating program");
-
- glAttachShader(program, vertexShader);
- glAttachShader(program, fragmentShader);
- glLinkProgram(program);
- mEngine.checkErrors("Linking program");
- return program;
-}
-
-GLuint GenericProgram::getUniformLocation(const string name) const {
- if (mProgramHandle == 0) {
- ALOGE("Can't get location of %s on an invalid program.", name.c_str());
- return -1;
- }
- return glGetUniformLocation(mProgramHandle, (const GLchar*)name.c_str());
-}
-
-GLuint GenericProgram::getAttributeLocation(const string name) const {
- if (mProgramHandle == 0) {
- ALOGE("Can't get location of %s on an invalid program.", name.c_str());
- return -1;
- }
- return glGetAttribLocation(mProgramHandle, (const GLchar*)name.c_str());
-}
-
-bool GenericProgram::isValid() const {
- return mProgramHandle != 0;
-}
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/gl/filters/GenericProgram.h b/libs/renderengine/gl/filters/GenericProgram.h
deleted file mode 100644
index 6da2a5af58..0000000000
--- a/libs/renderengine/gl/filters/GenericProgram.h
+++ /dev/null
@@ -1,51 +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.
- */
-
-#pragma once
-
-#include <ui/GraphicTypes.h>
-#include "../GLESRenderEngine.h"
-#include "../GLFramebuffer.h"
-
-using namespace std;
-
-namespace android {
-namespace renderengine {
-namespace gl {
-
-class GenericProgram {
-public:
- explicit GenericProgram(GLESRenderEngine& renderEngine);
- ~GenericProgram();
- void compile(string vertexShader, string fragmentShader);
- bool isValid() const;
- void useProgram() const;
- GLuint getAttributeLocation(const string name) const;
- GLuint getUniformLocation(const string name) const;
-
-private:
- GLuint compileShader(GLuint type, const string src) const;
- GLuint createAndLink(GLuint vertexShader, GLuint fragmentShader) const;
-
- GLESRenderEngine& mEngine;
- GLuint mVertexShaderHandle = 0;
- GLuint mFragmentShaderHandle = 0;
- GLuint mProgramHandle = 0;
-};
-
-} // namespace gl
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/Image.h b/libs/renderengine/include/renderengine/BorderRenderInfo.h
index 2b0eed1173..0ee6661f33 100644
--- a/libs/renderengine/include/renderengine/mock/Image.h
+++ b/libs/renderengine/include/renderengine/BorderRenderInfo.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 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.
@@ -15,22 +15,22 @@
*/
#pragma once
-
-#include <gmock/gmock.h>
-#include <renderengine/Image.h>
+#include <math/mat4.h>
+#include <ui/Region.h>
namespace android {
namespace renderengine {
-namespace mock {
-class Image : public renderengine::Image {
-public:
- Image();
- ~Image() override;
+struct BorderRenderInfo {
+ float width = 0;
+ half4 color;
+ Region combinedRegion;
- MOCK_METHOD2(setNativeWindowBuffer, bool(ANativeWindowBuffer* buffer, bool isProtected));
+ bool operator==(const BorderRenderInfo& rhs) const {
+ return (width == rhs.width && color == rhs.color &&
+ combinedRegion.hasSameRects(rhs.combinedRegion));
+ }
};
-} // namespace mock
} // namespace renderengine
-} // namespace android
+} // 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 59ef991eec..8d7c13cb18 100644
--- a/libs/renderengine/include/renderengine/DisplaySettings.h
+++ b/libs/renderengine/include/renderengine/DisplaySettings.h
@@ -22,17 +22,25 @@
#include <math/mat4.h>
#include <renderengine/PrintMatrix.h>
+#include <renderengine/BorderRenderInfo.h>
+#include <ui/DisplayId.h>
#include <ui/GraphicTypes.h>
#include <ui/Rect.h>
#include <ui/Region.h>
#include <ui/Transform.h>
+#include <optional>
+
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<renderengine::BorderRenderInfo> 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 621a209afa..82e5d83c30 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<GraphicBuffer>& getBuffer() const = 0;
+ virtual void remapBuffer() = 0;
+
Rect getBounds() const {
return {0, 0, static_cast<int32_t>(getWidth()), static_cast<int32_t>(getHeight())};
}
diff --git a/libs/renderengine/include/renderengine/LayerSettings.h b/libs/renderengine/include/renderengine/LayerSettings.h
index 154e5269f0..8ac0af47c6 100644
--- a/libs/renderengine/include/renderengine/LayerSettings.h
+++ b/libs/renderengine/include/renderengine/LayerSettings.h
@@ -28,6 +28,7 @@
#include <ui/GraphicTypes.h>
#include <ui/Rect.h>
#include <ui/Region.h>
+#include <ui/ShadowSettings.h>
#include <ui/StretchEffect.h>
#include <ui/Transform.h>
@@ -46,10 +47,6 @@ struct Buffer {
// Fence that will fire when the buffer is ready to be bound.
sp<Fence> fence = nullptr;
- // Texture identifier to bind the external texture to.
- // TODO(alecmouri): This is GL-specific...make the type backend-agnostic.
- uint32_t textureName = 0;
-
// Whether to use filtering when rendering the texture.
bool useTextureFiltering = false;
@@ -64,9 +61,6 @@ struct Buffer {
// overrides the alpha channel of the buffer.
bool isOpaque = false;
- // HDR color-space setting for Y410.
- bool isY410BT2020 = false;
-
float maxLuminanceNits = 0.0;
};
@@ -87,7 +81,7 @@ struct Geometry {
// rectangle to figure out how to apply the radius for this layer. The crop rectangle will be
// in local layer coordinate space, so we have to take the layer transform into account when
// walking up the tree.
- float roundedCornersRadius = 0.0;
+ vec2 roundedCornersRadius = vec2(0.0f, 0.0f);
// Rectangle within which corners will be rounded.
FloatRect roundedCornersCrop = FloatRect();
@@ -104,36 +98,6 @@ struct PixelSource {
half3 solidColor = half3(0.0f, 0.0f, 0.0f);
};
-/*
- * Contains the configuration for the shadows drawn by single layer. Shadow follows
- * material design guidelines.
- */
-struct ShadowSettings {
- // Boundaries of the shadow.
- FloatRect boundaries = FloatRect();
-
- // Color to the ambient shadow. The alpha is premultiplied.
- vec4 ambientColor = vec4();
-
- // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
- // depends on the light position.
- vec4 spotColor = vec4();
-
- // Position of the light source used to cast the spot shadow.
- vec3 lightPos = vec3();
-
- // Radius of the spot light source. Smaller radius will have sharper edges,
- // larger radius will have softer shadows
- float lightRadius = 0.f;
-
- // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
- float length = 0.f;
-
- // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
- // Otherwise the shadow will only be drawn around the edges of the casting layer.
- bool casterIsTranslucent = false;
-};
-
// The settings that RenderEngine requires for correctly rendering a Layer.
struct LayerSettings {
// Geometry information
@@ -185,12 +149,10 @@ struct LayerSettings {
// compositionengine/impl/ClientCompositionRequestCache.cpp
static inline bool operator==(const Buffer& lhs, const Buffer& rhs) {
return lhs.buffer == rhs.buffer && lhs.fence == rhs.fence &&
- lhs.textureName == rhs.textureName &&
lhs.useTextureFiltering == rhs.useTextureFiltering &&
lhs.textureTransform == rhs.textureTransform &&
lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
- lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
- lhs.maxLuminanceNits == rhs.maxLuminanceNits;
+ lhs.isOpaque == rhs.isOpaque && lhs.maxLuminanceNits == rhs.maxLuminanceNits;
}
static inline bool operator==(const Geometry& lhs, const Geometry& rhs) {
@@ -203,17 +165,6 @@ static inline bool operator==(const PixelSource& lhs, const PixelSource& rhs) {
return lhs.buffer == rhs.buffer && lhs.solidColor == rhs.solidColor;
}
-static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
- return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
- lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
- lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
- lhs.casterIsTranslucent == rhs.casterIsTranslucent;
-}
-
-static inline bool operator!=(const ShadowSettings& lhs, const ShadowSettings& rhs) {
- return !(operator==(lhs, rhs));
-}
-
static inline bool operator==(const LayerSettings& lhs, const LayerSettings& rhs) {
if (lhs.blurRegions.size() != rhs.blurRegions.size()) {
return false;
@@ -241,13 +192,11 @@ static inline void PrintTo(const Buffer& settings, ::std::ostream* os) {
<< (settings.buffer.get() ? decodePixelFormat(settings.buffer->getPixelFormat()).c_str()
: "");
*os << "\n .fence = " << settings.fence.get();
- *os << "\n .textureName = " << settings.textureName;
*os << "\n .useTextureFiltering = " << settings.useTextureFiltering;
*os << "\n .textureTransform = ";
PrintMatrix(settings.textureTransform, os);
*os << "\n .usePremultipliedAlpha = " << settings.usePremultipliedAlpha;
*os << "\n .isOpaque = " << settings.isOpaque;
- *os << "\n .isY410BT2020 = " << settings.isY410BT2020;
*os << "\n .maxLuminanceNits = " << settings.maxLuminanceNits;
*os << "\n}";
}
@@ -258,7 +207,8 @@ static inline void PrintTo(const Geometry& settings, ::std::ostream* os) {
PrintTo(settings.boundaries, os);
*os << "\n .positionTransform = ";
PrintMatrix(settings.positionTransform, os);
- *os << "\n .roundedCornersRadius = " << settings.roundedCornersRadius;
+ *os << "\n .roundedCornersRadiusX = " << settings.roundedCornersRadius.x;
+ *os << "\n .roundedCornersRadiusY = " << settings.roundedCornersRadius.y;
*os << "\n .roundedCornersCrop = ";
PrintTo(settings.roundedCornersCrop, os);
*os << "\n}";
diff --git a/libs/renderengine/include/renderengine/Mesh.h b/libs/renderengine/include/renderengine/Mesh.h
deleted file mode 100644
index 167f13f1bc..0000000000
--- a/libs/renderengine/include/renderengine/Mesh.h
+++ /dev/null
@@ -1,205 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_RENDER_ENGINE_MESH_H
-#define SF_RENDER_ENGINE_MESH_H
-
-#include <vector>
-
-#include <stdint.h>
-
-namespace android {
-namespace renderengine {
-
-class Mesh {
-public:
- class Builder;
-
- enum Primitive {
- TRIANGLES = 0x0004, // GL_TRIANGLES
- TRIANGLE_STRIP = 0x0005, // GL_TRIANGLE_STRIP
- TRIANGLE_FAN = 0x0006 // GL_TRIANGLE_FAN
- };
-
- ~Mesh() = default;
-
- /*
- * VertexArray handles the stride automatically.
- */
- template <typename TYPE>
- class VertexArray {
- friend class Mesh;
- float* mData;
- size_t mStride;
- size_t mOffset = 0;
- VertexArray(float* data, size_t stride) : mData(data), mStride(stride) {}
-
- public:
- // Returns a vertex array at an offset so its easier to append attributes from
- // multiple sources.
- VertexArray(VertexArray<TYPE>& other, size_t offset)
- : mData(other.mData), mStride(other.mStride), mOffset(offset) {}
-
- TYPE& operator[](size_t index) {
- return *reinterpret_cast<TYPE*>(&mData[(index + mOffset) * mStride]);
- }
- TYPE const& operator[](size_t index) const {
- return *reinterpret_cast<TYPE const*>(&mData[(index + mOffset) * mStride]);
- }
- };
-
- template <typename TYPE>
- VertexArray<TYPE> getPositionArray() {
- return VertexArray<TYPE>(getPositions(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getTexCoordArray() {
- return VertexArray<TYPE>(getTexCoords(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getCropCoordArray() {
- return VertexArray<TYPE>(getCropCoords(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getShadowColorArray() {
- return VertexArray<TYPE>(getShadowColor(), mStride);
- }
-
- template <typename TYPE>
- VertexArray<TYPE> getShadowParamsArray() {
- return VertexArray<TYPE>(getShadowParams(), mStride);
- }
-
- uint16_t* getIndicesArray() { return getIndices(); }
-
- Primitive getPrimitive() const;
-
- // returns a pointer to the vertices positions
- float const* getPositions() const;
-
- // returns a pointer to the vertices texture coordinates
- float const* getTexCoords() const;
-
- // returns a pointer to the vertices crop coordinates
- float const* getCropCoords() const;
-
- // returns a pointer to colors
- float const* getShadowColor() const;
-
- // returns a pointer to the shadow params
- float const* getShadowParams() const;
-
- // returns a pointer to indices
- uint16_t const* getIndices() const;
-
- // number of vertices in this mesh
- size_t getVertexCount() const;
-
- // dimension of vertices
- size_t getVertexSize() const;
-
- // dimension of texture coordinates
- size_t getTexCoordsSize() const;
-
- size_t getShadowParamsSize() const;
-
- size_t getShadowColorSize() const;
-
- size_t getIndexCount() const;
-
- // return stride in bytes
- size_t getByteStride() const;
-
- // return stride in floats
- size_t getStride() const;
-
-private:
- Mesh(Primitive primitive, size_t vertexCount, size_t vertexSize, size_t texCoordSize,
- size_t cropCoordsSize, size_t shadowColorSize, size_t shadowParamsSize, size_t indexCount);
- Mesh(const Mesh&);
- Mesh& operator=(const Mesh&);
- Mesh const& operator=(const Mesh&) const;
-
- float* getPositions();
- float* getTexCoords();
- float* getCropCoords();
- float* getShadowColor();
- float* getShadowParams();
- uint16_t* getIndices();
-
- std::vector<float> mVertices;
- size_t mVertexCount;
- size_t mVertexSize;
- size_t mTexCoordsSize;
- size_t mCropCoordsSize;
- size_t mShadowColorSize;
- size_t mShadowParamsSize;
- size_t mStride;
- Primitive mPrimitive;
- std::vector<uint16_t> mIndices;
- size_t mIndexCount;
-};
-
-class Mesh::Builder {
-public:
- Builder& setPrimitive(Primitive primitive) {
- mPrimitive = primitive;
- return *this;
- };
- Builder& setVertices(size_t vertexCount, size_t vertexSize) {
- mVertexCount = vertexCount;
- mVertexSize = vertexSize;
- return *this;
- };
- Builder& setTexCoords(size_t texCoordsSize) {
- mTexCoordsSize = texCoordsSize;
- return *this;
- };
- Builder& setCropCoords(size_t cropCoordsSize) {
- mCropCoordsSize = cropCoordsSize;
- return *this;
- };
- Builder& setShadowAttrs() {
- mShadowParamsSize = 3;
- mShadowColorSize = 4;
- return *this;
- };
- Builder& setIndices(size_t indexCount) {
- mIndexCount = indexCount;
- return *this;
- };
- Mesh build() const {
- return Mesh{mPrimitive, mVertexCount, mVertexSize, mTexCoordsSize,
- mCropCoordsSize, mShadowColorSize, mShadowParamsSize, mIndexCount};
- }
-
-private:
- size_t mVertexCount = 0;
- size_t mVertexSize = 0;
- size_t mTexCoordsSize = 0;
- size_t mCropCoordsSize = 0;
- size_t mShadowColorSize = 0;
- size_t mShadowParamsSize = 0;
- size_t mIndexCount = 0;
- Primitive mPrimitive;
-};
-
-} // namespace renderengine
-} // namespace android
-#endif /* SF_RENDER_ENGINE_MESH_H */
diff --git a/libs/renderengine/include/renderengine/RenderEngine.h b/libs/renderengine/include/renderengine/RenderEngine.h
index 3e7f69ce02..b41fa1dd69 100644
--- a/libs/renderengine/include/renderengine/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/RenderEngine.h
@@ -18,14 +18,14 @@
#define SF_RENDERENGINE_H_
#include <android-base/unique_fd.h>
+#include <ftl/future.h>
#include <math/mat4.h>
#include <renderengine/DisplaySettings.h>
#include <renderengine/ExternalTexture.h>
-#include <renderengine/Framebuffer.h>
-#include <renderengine/Image.h>
#include <renderengine/LayerSettings.h>
#include <stdint.h>
#include <sys/types.h>
+#include <ui/FenceResult.h>
#include <ui/GraphicTypes.h>
#include <ui/Transform.h>
@@ -33,7 +33,7 @@
#include <memory>
/**
- * Allows to set RenderEngine backend to GLES (default) or SkiaGL (NOT yet supported).
+ * Allows to override the RenderEngine backend.
*/
#define PROPERTY_DEBUG_RENDERENGINE_BACKEND "debug.renderengine.backend"
@@ -68,7 +68,6 @@ class Image;
class Mesh;
class Texture;
struct RenderEngineCreationArgs;
-struct RenderEngineResult;
namespace threaded {
class RenderEngineThreaded;
@@ -94,10 +93,10 @@ public:
};
enum class RenderEngineType {
- GLES = 1,
- THREADED = 2,
SKIA_GL = 3,
SKIA_GL_THREADED = 4,
+ SKIA_VK = 5,
+ SKIA_VK_THREADED = 6,
};
static std::unique_ptr<RenderEngine> create(const RenderEngineCreationArgs& args);
@@ -108,14 +107,11 @@ public:
// This interface, while still in use until a suitable replacement is built,
// should be considered deprecated, minus some methods which still may be
// used to support legacy behavior.
- virtual std::future<void> primeCache() = 0;
+ virtual std::future<void> primeCache(bool shouldPrimeUltraHDR) = 0;
// dump the extension strings. always call the base class.
virtual void dump(std::string& result) = 0;
- virtual void genTextures(size_t count, uint32_t* names) = 0;
- virtual void deleteTextures(size_t count, uint32_t const* names) = 0;
-
// queries that are required to be thread safe
virtual size_t getMaxTextureSize() const = 0;
virtual size_t getMaxViewportDims() const = 0;
@@ -125,12 +121,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;
@@ -153,17 +145,14 @@ public:
// @param layers The layers to draw onto the display, in Z-order.
// @param buffer The buffer which will be drawn to. This buffer will be
// ready once drawFence fires.
- // @param useFramebufferCache True if the framebuffer cache should be used.
- // If an implementation does not cache output framebuffers, then this
- // 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<RenderEngineResult> drawLayers(
- const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& 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<FenceResult> drawLayers(const DisplaySettings& display,
+ const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer,
+ 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
@@ -171,10 +160,15 @@ public:
// being drawn, then the implementation is free to silently ignore this call.
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
@@ -197,7 +191,7 @@ public:
virtual void setEnableTracing(bool /*tracingEnabled*/) {}
protected:
- RenderEngine() : RenderEngine(RenderEngineType::GLES) {}
+ RenderEngine() : RenderEngine(RenderEngineType::SKIA_GL) {}
RenderEngine(RenderEngineType type) : mRenderEngineType(type) {}
@@ -224,7 +218,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<GraphicBuffer>& buffer) = 0;
+ virtual void unmapExternalTextureBuffer(sp<GraphicBuffer>&& 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,11 +230,17 @@ 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<LayerSettings>&,
+ const std::shared_ptr<ExternalTexture>&);
+
+ // Attempt to switch RenderEngine into and out of protectedContext mode
+ virtual void useProtectedContext(bool useProtectedContext) = 0;
+
virtual void drawLayersInternal(
- const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+ const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) = 0;
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) = 0;
};
struct RenderEngineCreationArgs {
@@ -257,14 +257,13 @@ struct RenderEngineCreationArgs {
private:
// must be created by Builder via constructor with full argument list
- RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize, bool _useColorManagement,
+ RenderEngineCreationArgs(int _pixelFormat, uint32_t _imageCacheSize,
bool _enableProtectedContext, bool _precacheToneMapperShaderOnly,
bool _supportsBackgroundBlur,
RenderEngine::ContextPriority _contextPriority,
RenderEngine::RenderEngineType _renderEngineType)
: pixelFormat(_pixelFormat),
imageCacheSize(_imageCacheSize),
- useColorManagement(_useColorManagement),
enableProtectedContext(_enableProtectedContext),
precacheToneMapperShaderOnly(_precacheToneMapperShaderOnly),
supportsBackgroundBlur(_supportsBackgroundBlur),
@@ -284,10 +283,6 @@ struct RenderEngineCreationArgs::Builder {
this->imageCacheSize = imageCacheSize;
return *this;
}
- Builder& setUseColorManagerment(bool useColorManagement) {
- this->useColorManagement = useColorManagement;
- return *this;
- }
Builder& setEnableProtectedContext(bool enableProtectedContext) {
this->enableProtectedContext = enableProtectedContext;
return *this;
@@ -309,16 +304,15 @@ struct RenderEngineCreationArgs::Builder {
return *this;
}
RenderEngineCreationArgs build() const {
- return RenderEngineCreationArgs(pixelFormat, imageCacheSize, useColorManagement,
- enableProtectedContext, precacheToneMapperShaderOnly,
- supportsBackgroundBlur, contextPriority, renderEngineType);
+ return RenderEngineCreationArgs(pixelFormat, imageCacheSize, enableProtectedContext,
+ precacheToneMapperShaderOnly, supportsBackgroundBlur,
+ contextPriority, renderEngineType);
}
private:
// 1 means RGBA_8888
int pixelFormat = 1;
uint32_t imageCacheSize = 0;
- bool useColorManagement = true;
bool enableProtectedContext = false;
bool precacheToneMapperShaderOnly = false;
bool supportsBackgroundBlur = false;
@@ -327,13 +321,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/Texture.h b/libs/renderengine/include/renderengine/Texture.h
deleted file mode 100644
index c69ace0603..0000000000
--- a/libs/renderengine/include/renderengine/Texture.h
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_RENDER_ENGINE_TEXTURE_H
-#define SF_RENDER_ENGINE_TEXTURE_H
-
-#include <stdint.h>
-
-#include <math/mat4.h>
-
-namespace android {
-namespace renderengine {
-
-class Texture {
-public:
- enum Target { TEXTURE_2D = 0x0DE1, TEXTURE_EXTERNAL = 0x8D65 };
-
- Texture();
- Texture(Target textureTarget, uint32_t textureName);
- ~Texture();
-
- void init(Target textureTarget, uint32_t textureName);
-
- void setMatrix(float const* matrix);
- void setFiltering(bool enabled);
- void setDimensions(size_t width, size_t height);
-
- uint32_t getTextureName() const;
- uint32_t getTextureTarget() const;
-
- const mat4& getMatrix() const;
- bool getFiltering() const;
- size_t getWidth() const;
- size_t getHeight() const;
-
-private:
- uint32_t mTextureName;
- uint32_t mTextureTarget;
- size_t mWidth;
- size_t mHeight;
- bool mFiltering;
- mat4 mTextureMatrix;
-};
-
-} // namespace renderengine
-} // namespace android
-#endif /* SF_RENDER_ENGINE_TEXTURE_H */
diff --git a/libs/renderengine/include/renderengine/impl/ExternalTexture.h b/libs/renderengine/include/renderengine/impl/ExternalTexture.h
index c0e24f0c10..d30262d985 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<GraphicBuffer> 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 974e0fddde..474e2e72c8 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<GraphicBuffer> mNullBuffer = nullptr;
+ const sp<GraphicBuffer> mEmptyBuffer =
+ sp<GraphicBuffer>::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<GraphicBuffer>& getBuffer() const { return mNullBuffer; }
+ const sp<GraphicBuffer>& 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 248bd652c0..a58a65ca9f 100644
--- a/libs/renderengine/include/renderengine/mock/RenderEngine.h
+++ b/libs/renderengine/include/renderengine/mock/RenderEngine.h
@@ -19,9 +19,7 @@
#include <gmock/gmock.h>
#include <renderengine/DisplaySettings.h>
#include <renderengine/LayerSettings.h>
-#include <renderengine/Mesh.h>
#include <renderengine/RenderEngine.h>
-#include <renderengine/Texture.h>
#include <ui/Fence.h>
#include <ui/GraphicBuffer.h>
#include <ui/Region.h>
@@ -35,11 +33,8 @@ public:
RenderEngine();
~RenderEngine() override;
- MOCK_METHOD0(primeCache, std::future<void>());
+ MOCK_METHOD1(primeCache, std::future<void>(bool));
MOCK_METHOD1(dump, void(std::string&));
- MOCK_METHOD2(genTextures, void(size_t, uint32_t*));
- MOCK_METHOD2(deleteTextures, void(size_t, uint32_t const*));
- MOCK_METHOD1(drawMesh, void(const renderengine::Mesh&));
MOCK_CONST_METHOD0(getMaxTextureSize, size_t());
MOCK_CONST_METHOD0(getMaxViewportDims, size_t());
MOCK_CONST_METHOD0(isProtected, bool());
@@ -47,16 +42,14 @@ public:
MOCK_METHOD1(useProtectedContext, void(bool));
MOCK_METHOD0(cleanupPostRender, void());
MOCK_CONST_METHOD0(canSkipPostRenderCleanup, bool());
- MOCK_METHOD5(drawLayers,
- std::future<RenderEngineResult>(const DisplaySettings&,
- const std::vector<LayerSettings>&,
- const std::shared_ptr<ExternalTexture>&,
- const bool, base::unique_fd&&));
- MOCK_METHOD6(drawLayersInternal,
- void(const std::shared_ptr<std::promise<RenderEngineResult>>&&,
- const DisplaySettings&, const std::vector<LayerSettings>&,
- const std::shared_ptr<ExternalTexture>&, const bool, base::unique_fd&&));
- MOCK_METHOD0(cleanFramebufferCache, void());
+ MOCK_METHOD4(drawLayers,
+ ftl::Future<FenceResult>(const DisplaySettings&, const std::vector<LayerSettings>&,
+ const std::shared_ptr<ExternalTexture>&,
+ base::unique_fd&&));
+ MOCK_METHOD5(drawLayersInternal,
+ void(const std::shared_ptr<std::promise<FenceResult>>&&, const DisplaySettings&,
+ const std::vector<LayerSettings>&, const std::shared_ptr<ExternalTexture>&,
+ base::unique_fd&&));
MOCK_METHOD0(getContextPriority, int());
MOCK_METHOD0(supportsBackgroundBlur, bool());
MOCK_METHOD1(onActiveDisplaySizeChanged, void(ui::Size));
@@ -64,7 +57,7 @@ public:
protected:
// mock renderengine still needs to implement these, but callers should never need to call them.
void mapExternalTextureBuffer(const sp<GraphicBuffer>&, bool) {}
- void unmapExternalTextureBuffer(const sp<GraphicBuffer>&) {}
+ void unmapExternalTextureBuffer(sp<GraphicBuffer>&&) {}
};
} // namespace mock
diff --git a/libs/renderengine/include/renderengine/private/Description.h b/libs/renderengine/include/renderengine/private/Description.h
deleted file mode 100644
index fa6ec10b6e..0000000000
--- a/libs/renderengine/include/renderengine/private/Description.h
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * Copyright 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_RENDER_ENGINE_DESCRIPTION_H_
-#define SF_RENDER_ENGINE_DESCRIPTION_H_
-
-#include <renderengine/Texture.h>
-#include <ui/GraphicTypes.h>
-
-namespace android {
-namespace renderengine {
-
-/*
- * This is the structure that holds the state of the rendering engine.
- * This class is used to generate a corresponding GLSL program and set the
- * appropriate uniform.
- */
-struct Description {
- enum class TransferFunction : int {
- LINEAR,
- SRGB,
- ST2084,
- HLG, // Hybrid Log-Gamma for HDR.
- };
-
- static TransferFunction dataSpaceToTransferFunction(ui::Dataspace dataSpace);
-
- Description() = default;
- ~Description() = default;
-
- bool hasInputTransformMatrix() const;
- bool hasOutputTransformMatrix() const;
- bool hasColorMatrix() const;
- bool hasDisplayColorMatrix() const;
-
- // whether textures are premultiplied
- bool isPremultipliedAlpha = false;
- // whether this layer is marked as opaque
- bool isOpaque = true;
-
- // corner radius of the layer
- float cornerRadius = 0;
-
- // Size of the rounded rectangle we are cropping to
- half2 cropSize;
-
- // Texture this layer uses
- Texture texture;
- bool textureEnabled = false;
-
- // color used when texturing is disabled or when setting alpha.
- half4 color;
-
- // true if the sampled pixel values are in Y410/BT2020 rather than RGBA
- bool isY410BT2020 = false;
-
- // transfer functions for the input/output
- TransferFunction inputTransferFunction = TransferFunction::LINEAR;
- TransferFunction outputTransferFunction = TransferFunction::LINEAR;
-
- float displayMaxLuminance;
- float maxMasteringLuminance;
- float maxContentLuminance;
-
- // projection matrix
- mat4 projectionMatrix;
-
- // The color matrix will be applied in linear space right before OETF.
- mat4 colorMatrix;
- // The display color matrix will be applied in gamma space after OETF
- mat4 displayColorMatrix;
- mat4 inputTransformMatrix;
- mat4 outputTransformMatrix;
-
- // True if this layer will draw a shadow.
- bool drawShadows = false;
-};
-
-} // namespace renderengine
-} // namespace android
-
-#endif /* SF_RENDER_ENGINE_DESCRIPTION_H_ */
diff --git a/libs/renderengine/mock/Framebuffer.cpp b/libs/renderengine/mock/Framebuffer.cpp
deleted file mode 100644
index fbdcaab697..0000000000
--- a/libs/renderengine/mock/Framebuffer.cpp
+++ /dev/null
@@ -1,30 +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.
- */
-
-#include <renderengine/mock/Framebuffer.h>
-
-namespace android {
-namespace renderengine {
-namespace mock {
-
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Framebuffer::Framebuffer() = default;
-Framebuffer::~Framebuffer() = default;
-
-} // namespace mock
-} // namespace renderengine
-} // namespace android
diff --git a/libs/renderengine/skia/AutoBackendTexture.cpp b/libs/renderengine/skia/AutoBackendTexture.cpp
index 5c122d4154..ee95e59d90 100644
--- a/libs/renderengine/skia/AutoBackendTexture.cpp
+++ b/libs/renderengine/skia/AutoBackendTexture.cpp
@@ -20,6 +20,13 @@
#define LOG_TAG "RenderEngine"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <SkImage.h>
+#include <include/gpu/ganesh/SkImageGanesh.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/gpu/ganesh/gl/GrGLBackendSurface.h>
+#include <include/gpu/ganesh/vk/GrVkBackendSurface.h>
+#include <include/gpu/vk/GrVkTypes.h>
+#include <android/hardware_buffer.h>
#include "ColorSpaces.h"
#include "log/log_main.h"
#include "utils/Trace.h"
@@ -35,18 +42,51 @@ AutoBackendTexture::AutoBackendTexture(GrDirectContext* context, AHardwareBuffer
AHardwareBuffer_Desc desc;
AHardwareBuffer_describe(buffer, &desc);
bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
- GrBackendFormat backendFormat =
- GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
- mBackendTexture =
- GrAHardwareBufferUtils::MakeBackendTexture(context, buffer, desc.width, desc.height,
- &mDeleteProc, &mUpdateProc, &mImageCtx,
- createProtectedImage, backendFormat,
- isOutputBuffer);
+ GrBackendFormat backendFormat;
+
+ GrBackendApi backend = context->backend();
+ if (backend == GrBackendApi::kOpenGL) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetGLBackendFormat(context, desc.format, false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeGLBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ isOutputBuffer);
+ } else if (backend == GrBackendApi::kVulkan) {
+ backendFormat =
+ GrAHardwareBufferUtils::GetVulkanBackendFormat(context,
+ buffer,
+ desc.format,
+ false);
+ mBackendTexture =
+ GrAHardwareBufferUtils::MakeVulkanBackendTexture(context,
+ buffer,
+ desc.width,
+ desc.height,
+ &mDeleteProc,
+ &mUpdateProc,
+ &mImageCtx,
+ createProtectedImage,
+ backendFormat,
+ isOutputBuffer);
+ } else {
+ LOG_ALWAYS_FATAL("Unexpected backend %u", static_cast<unsigned>(backend));
+ }
+
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() {
@@ -77,11 +117,47 @@ void AutoBackendTexture::releaseSurfaceProc(SkSurface::ReleaseContext releaseCon
// releaseImageProc is invoked by SkImage, when the texture is no longer in use.
// "releaseContext" contains an "AutoBackendTexture*".
-void AutoBackendTexture::releaseImageProc(SkImage::ReleaseContext releaseContext) {
+void AutoBackendTexture::releaseImageProc(SkImages::ReleaseContext releaseContext) {
AutoBackendTexture* textureRelease = reinterpret_cast<AutoBackendTexture*>(releaseContext);
textureRelease->unref(false);
}
+void logFatalTexture(const char* msg, const GrBackendTexture& tex, ui::Dataspace dataspace,
+ SkColorType colorType) {
+ switch (tex.backend()) {
+ case GrBackendApi::kOpenGL: {
+ GrGLTextureInfo textureInfo;
+ bool retrievedTextureInfo = GrBackendTextures::GetGLTextureInfo(tex, &textureInfo);
+ LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+ "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+ "texType: %i\n\t\tGrGLTextureInfo: success: %i fTarget: %u fFormat: %u"
+ " colorType %i",
+ msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
+ tex.height(), tex.hasMipmaps(), tex.isProtected(),
+ static_cast<int>(tex.textureType()), retrievedTextureInfo,
+ textureInfo.fTarget, textureInfo.fFormat, colorType);
+ break;
+ }
+ case GrBackendApi::kVulkan: {
+ GrVkImageInfo imageInfo;
+ bool retrievedImageInfo = GrBackendTextures::GetVkImageInfo(tex, &imageInfo);
+ LOG_ALWAYS_FATAL("%s isTextureValid:%d dataspace:%d"
+ "\n\tGrBackendTexture: (%i x %i) hasMipmaps: %i isProtected: %i "
+ "texType: %i\n\t\tVkImageInfo: success: %i fFormat: %i "
+ "fSampleCount: %u fLevelCount: %u colorType %i",
+ msg, tex.isValid(), static_cast<int32_t>(dataspace), tex.width(),
+ tex.height(), tex.hasMipmaps(), tex.isProtected(),
+ static_cast<int>(tex.textureType()), retrievedImageInfo,
+ imageInfo.fFormat, imageInfo.fSampleCount, imageInfo.fLevelCount,
+ colorType);
+ break;
+ }
+ default:
+ LOG_ALWAYS_FATAL("%s Unexpected backend %u", msg, static_cast<unsigned>(tex.backend()));
+ break;
+ }
+}
+
sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaType alphaType,
GrDirectContext* context) {
ATRACE_CALL();
@@ -98,8 +174,9 @@ sk_sp<SkImage> AutoBackendTexture::makeImage(ui::Dataspace dataspace, SkAlphaTyp
}
sk_sp<SkImage> image =
- SkImage::MakeFromTexture(context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType,
- alphaType, toSkColorSpace(dataspace), releaseImageProc, this);
+ SkImages::BorrowTextureFrom(context, mBackendTexture, kTopLeft_GrSurfaceOrigin,
+ colorType, alphaType, toSkColorSpace(dataspace),
+ releaseImageProc, this);
if (image.get()) {
// The following ref will be counteracted by releaseProc, when SkImage is discarded.
ref();
@@ -107,9 +184,9 @@ sk_sp<SkImage> 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;
}
@@ -119,10 +196,10 @@ sk_sp<SkSurface> AutoBackendTexture::getOrCreateSurface(ui::Dataspace dataspace,
LOG_ALWAYS_FATAL_IF(!mIsOutputBuffer, "You can't generate a SkSurface for a read-only texture");
if (!mSurface.get() || mDataspace != dataspace) {
sk_sp<SkSurface> surface =
- SkSurface::MakeFromBackendTexture(context, mBackendTexture,
- kTopLeft_GrSurfaceOrigin, 0, mColorType,
- toSkColorSpace(dataspace), nullptr,
- releaseSurfaceProc, this);
+ SkSurfaces::WrapBackendTexture(context, mBackendTexture,
+ kTopLeft_GrSurfaceOrigin, 0, mColorType,
+ toSkColorSpace(dataspace), nullptr,
+ releaseSurfaceProc, this);
if (surface.get()) {
// The following ref will be counteracted by releaseProc, when SkSurface is discarded.
ref();
@@ -131,9 +208,9 @@ sk_sp<SkSurface> 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/AutoBackendTexture.h b/libs/renderengine/skia/AutoBackendTexture.h
index 00b901be11..509ac40f77 100644
--- a/libs/renderengine/skia/AutoBackendTexture.h
+++ b/libs/renderengine/skia/AutoBackendTexture.h
@@ -144,7 +144,7 @@ private:
CleanupManager& mCleanupMgr;
static void releaseSurfaceProc(SkSurface::ReleaseContext releaseContext);
- static void releaseImageProc(SkImage::ReleaseContext releaseContext);
+ static void releaseImageProc(SkImages::ReleaseContext releaseContext);
int mUsageCount = 0;
diff --git a/libs/renderengine/skia/Cache.cpp b/libs/renderengine/skia/Cache.cpp
index f3064f3c69..abe0d9b0a6 100644
--- a/libs/renderengine/skia/Cache.cpp
+++ b/libs/renderengine/skia/Cache.cpp
@@ -17,6 +17,7 @@
#include "AutoBackendTexture.h"
#include "SkiaRenderEngine.h"
#include "android-base/unique_fd.h"
+#include "cutils/properties.h"
#include "renderengine/DisplaySettings.h"
#include "renderengine/LayerSettings.h"
#include "renderengine/impl/ExternalTexture.h"
@@ -29,8 +30,6 @@
namespace android::renderengine::skia {
namespace {
-// Warming shader cache, not framebuffer cache.
-constexpr bool kUseFrameBufferCache = false;
// clang-format off
// Any non-identity matrix will do.
@@ -51,6 +50,15 @@ const auto kFlip = mat4(1.1f, -0.1f, 0.f, 0.f,
// a color correction effect is added to the shader.
constexpr auto kDestDataSpace = ui::Dataspace::SRGB;
constexpr auto kOtherDataSpace = ui::Dataspace::DISPLAY_P3;
+constexpr auto kBT2020DataSpace = ui::Dataspace::BT2020_ITU_PQ;
+constexpr auto kExtendedHdrDataSpce =
+ static_cast<ui::Dataspace>(ui::Dataspace::RANGE_EXTENDED | ui::Dataspace::TRANSFER_SRGB |
+ ui::Dataspace::STANDARD_DCI_P3);
+// Dimming is needed to trigger linear effects for some dataspace pairs
+const std::array<float, 3> kLayerWhitePoints = {
+ 1000.0f, 500.0f,
+ 100.0f, // trigger dithering by dimming below 20%
+};
} // namespace
static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -65,9 +73,12 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin
.geometry =
Geometry{
.boundaries = rect,
+ .roundedCornersRadius = {50.f, 50.f},
.roundedCornersCrop = rect,
- .roundedCornersRadius = 50.f,
},
+ .alpha = 1,
+ // setting this is mandatory for shadows and blurs
+ .skipContentDraw = true,
// drawShadow ignores alpha
.shadow =
ShadowSettings{
@@ -78,16 +89,13 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin
.lightRadius = 2500.0f,
.length = 15.f,
},
- // setting this is mandatory for shadows and blurs
- .skipContentDraw = true,
- .alpha = 1,
};
LayerSettings caster{
.geometry =
Geometry{
.boundaries = smallerRect,
+ .roundedCornersRadius = {50.f, 50.f},
.roundedCornersCrop = rect,
- .roundedCornersRadius = 50.f,
},
.source =
PixelSource{
@@ -116,8 +124,7 @@ static void drawShadowLayers(SkiaRenderEngine* renderengine, const DisplaySettin
caster.geometry.positionTransform = transform;
auto layers = std::vector<LayerSettings>{layer, caster};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
@@ -129,10 +136,10 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting
LayerSettings layer{
.geometry =
Geometry{
+ .boundaries = rect,
// The position transform doesn't matter when the reduced shader mode
// in in effect. A matrix transform stage is always included.
.positionTransform = mat4(),
- .boundaries = rect,
.roundedCornersCrop = rect,
},
.source = PixelSource{.buffer =
@@ -148,14 +155,13 @@ static void drawImageLayers(SkiaRenderEngine* renderengine, const DisplaySetting
// In reduced shader mode, all non-zero round rect radii get the same code path.
for (float roundedCornersRadius : {0.0f, 50.0f}) {
// roundedCornersCrop is always set, but the radius triggers the behavior
- layer.geometry.roundedCornersRadius = roundedCornersRadius;
+ layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
for (bool isOpaque : {true, false}) {
layer.source.buffer.isOpaque = isOpaque;
for (auto alpha : {half(.2f), half(1.0f)}) {
layer.alpha = alpha;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -181,10 +187,9 @@ static void drawSolidLayers(SkiaRenderEngine* renderengine, const DisplaySetting
for (auto transform : {mat4(), kScaleAndTranslate}) {
layer.geometry.positionTransform = transform;
for (float roundedCornersRadius : {0.0f, 50.f}) {
- layer.geometry.roundedCornersRadius = roundedCornersRadius;
+ layer.geometry.roundedCornersRadius = {roundedCornersRadius, roundedCornersRadius};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -207,8 +212,7 @@ static void drawBlurLayers(SkiaRenderEngine* renderengine, const DisplaySettings
for (int radius : {9, 60}) {
layer.backgroundBlurRadius = radius;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
@@ -238,7 +242,7 @@ static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySetti
.geometry =
Geometry{
.boundaries = rect,
- .roundedCornersRadius = 27, // larger than the 20 above.
+ .roundedCornersRadius = {27.f, 27.f},
.roundedCornersCrop =
FloatRect(0, 0, displayRect.width(), displayRect.height()),
},
@@ -254,8 +258,7 @@ static void drawClippedLayers(SkiaRenderEngine* renderengine, const DisplaySetti
for (float alpha : {0.5f, 1.f}) {
layer.alpha = alpha;
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache,
- base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
}
}
@@ -270,29 +273,29 @@ static void drawPIPImageLayer(SkiaRenderEngine* renderengine, const DisplaySetti
LayerSettings layer{
.geometry =
Geometry{
+ .boundaries = rect,
// Note that this flip matrix only makes a difference when clipping,
// which happens in this layer because the roundrect crop is just a bit
// larger than the layer bounds.
.positionTransform = kFlip,
- .boundaries = rect,
- .roundedCornersRadius = 94.2551,
- .roundedCornersCrop = FloatRect(
- -93.75, 0, displayRect.width() + 93.75, displayRect.height()),
+ .roundedCornersRadius = {94.2551f, 94.2551f},
+ .roundedCornersCrop = FloatRect(-93.75, 0, displayRect.width() + 93.75,
+ displayRect.height()),
},
.source = PixelSource{.buffer =
Buffer{
.buffer = srcTexture,
- .maxLuminanceNits = 1000.f,
- .isOpaque = 0,
.usePremultipliedAlpha = 1,
+ .isOpaque = 0,
+ .maxLuminanceNits = 1000.f,
}},
- .sourceDataspace = kOtherDataSpace,
.alpha = 1,
+ .sourceDataspace = kOtherDataSpace,
};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
}
static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySettings& display,
@@ -303,24 +306,317 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett
LayerSettings layer{
.geometry =
Geometry{
- .positionTransform = kScaleAndTranslate,
// the boundaries have to be smaller than the rounded crop so that
// clipRRect is used instead of drawRRect
.boundaries = small,
- .roundedCornersRadius = 50.f,
+ .positionTransform = kScaleAndTranslate,
+ .roundedCornersRadius = {50.f, 50.f},
.roundedCornersCrop = rect,
},
- .source = PixelSource{
+ .source =
+ PixelSource{
.solidColor = half3(0.f, 0.f, 0.f),
},
- .sourceDataspace = kDestDataSpace,
.alpha = 0,
+ .sourceDataspace = kDestDataSpace,
.disableBlending = true,
};
auto layers = std::vector<LayerSettings>{layer};
- renderengine->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd());
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawImageDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {0.f, 0.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true}},
+ .alpha = 1.f,
+ .sourceDataspace = kDestDataSpace,
+ };
+
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawTransparentImageDimmedLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ },
+ .source = PixelSource{.buffer =
+ Buffer{
+ .buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = false,
+ }},
+ .sourceDataspace = kDestDataSpace,
+ };
+
+ for (auto roundedCornerRadius : {0.f, 50.f}) {
+ layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+ for (auto alpha : {0.5f, 1.0f}) {
+ layer.alpha = alpha;
+ for (auto isOpaque : {true, false}) {
+ if (roundedCornerRadius == 0.f && isOpaque) {
+ // already covered in drawImageDimmedLayers
+ continue;
+ }
+
+ layer.source.buffer.isOpaque = isOpaque;
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+ }
+ }
+}
+
+static void drawClippedDimmedImageLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+
+ // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+ // blending instead of EllipticalRRect, so enlarge them a bit.
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ FloatRect boundary(0, 0, displayRect.width(),
+ displayRect.height() - 20); // boundary is smaller
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .positionTransform = mat4(),
+ .boundaries = boundary,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {27.f, 27.f},
+ },
+ .source = PixelSource{.buffer =
+ Buffer{
+ .buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = false,
+ }},
+ .alpha = 1.f,
+ .sourceDataspace = kDestDataSpace,
+ };
+
+ std::array<mat4, 2> transforms = {kScaleAndTranslate, kScaleAsymmetric};
+
+ constexpr float radius = 27.f;
+
+ for (size_t i = 0; i < transforms.size(); i++) {
+ layer.geometry.positionTransform = transforms[i];
+ layer.geometry.roundedCornersRadius = {radius, radius};
+
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+}
+
+static void drawSolidDimmedLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ },
+ .source =
+ PixelSource{
+ .solidColor = half3(0.1f, 0.2f, 0.3f),
+ },
+ .alpha = 1.f,
+ };
+
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawBT2020ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {0.f, 0.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true}},
+ .alpha = 1.f,
+ .sourceDataspace = kBT2020DataSpace,
+ };
+
+ for (auto alpha : {0.5f, 1.f}) {
+ layer.alpha = alpha;
+ std::vector<LayerSettings> layers;
+ layer.whitePointNits = -1.f;
+ layers.push_back(layer);
+
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+}
+static void drawBT2020ClippedImageLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+
+ // If rect and boundary is too small compared to roundedCornersRadius, Skia will switch to
+ // blending instead of EllipticalRRect, so enlarge them a bit.
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ FloatRect boundary(0, 0, displayRect.width(),
+ displayRect.height() - 10); // boundary is smaller
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ .positionTransform = kScaleAsymmetric,
+ .boundaries = boundary,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {64.1f, 64.1f},
+ },
+ .source = PixelSource{.buffer =
+ Buffer{
+ .buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true,
+ }},
+ .alpha = 0.5f,
+ .sourceDataspace = kBT2020DataSpace,
+ };
+
+ std::vector<LayerSettings> layers = {layer};
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+}
+
+static void drawExtendedHDRImageLayers(SkiaRenderEngine* renderengine,
+ const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {50.f, 50.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = true}},
+ .alpha = 0.5f,
+ .sourceDataspace = kExtendedHdrDataSpce,
+ };
+
+ for (auto roundedCornerRadius : {0.f, 50.f}) {
+ layer.geometry.roundedCornersRadius = {roundedCornerRadius, roundedCornerRadius};
+ for (auto alpha : {0.5f, 1.f}) {
+ layer.alpha = alpha;
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
+ }
+}
+
+static void drawP3ImageLayers(SkiaRenderEngine* renderengine, const DisplaySettings& display,
+ const std::shared_ptr<ExternalTexture>& dstTexture,
+ const std::shared_ptr<ExternalTexture>& srcTexture) {
+ const Rect& displayRect = display.physicalDisplay;
+ FloatRect rect(0, 0, displayRect.width(), displayRect.height());
+ LayerSettings layer{
+ .geometry =
+ Geometry{
+ // The position transform doesn't matter when the reduced shader mode
+ // in in effect. A matrix transform stage is always included.
+ .positionTransform = mat4(),
+ .boundaries = rect,
+ .roundedCornersCrop = rect,
+ .roundedCornersRadius = {50.f, 50.f},
+ },
+ .source = PixelSource{.buffer = Buffer{.buffer = srcTexture,
+ .maxLuminanceNits = 1000.f,
+ .usePremultipliedAlpha = true,
+ .isOpaque = false}},
+ .alpha = 0.5f,
+ .sourceDataspace = kOtherDataSpace,
+ };
+
+ for (auto alpha : {0.5f, 1.f}) {
+ layer.alpha = alpha;
+ std::vector<LayerSettings> layers;
+
+ for (auto layerWhitePoint : kLayerWhitePoints) {
+ layer.whitePointNits = layerWhitePoint;
+ layers.push_back(layer);
+ }
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd());
+ }
}
//
@@ -334,7 +630,7 @@ static void drawHolePunchLayer(SkiaRenderEngine* renderengine, const DisplaySett
// kFlushAfterEveryLayer = true
// in external/skia/src/gpu/gl/builders/GrGLShaderStringBuilder.cpp
// gPrintSKSL = true
-void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
+void Cache::primeShaderCache(SkiaRenderEngine* renderengine, bool shouldPrimeUltraHDR) {
const int previousCount = renderengine->reportShadersCompiled();
if (previousCount) {
ALOGD("%d Shaders already compiled before Cache::primeShaderCache ran\n", previousCount);
@@ -359,12 +655,29 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
.maxLuminance = 500,
.outputDataspace = kOtherDataSpace,
};
+ DisplaySettings p3DisplayEnhance{.physicalDisplay = displayRect,
+ .clip = displayRect,
+ .maxLuminance = 500,
+ .outputDataspace = kOtherDataSpace,
+ .dimmingStage = aidl::android::hardware::graphics::
+ composer3::DimmingStage::GAMMA_OETF,
+ .renderIntent = aidl::android::hardware::graphics::
+ composer3::RenderIntent::ENHANCE};
+ DisplaySettings bt2020Display{.physicalDisplay = displayRect,
+ .clip = displayRect,
+ .maxLuminance = 500,
+ .outputDataspace = ui::Dataspace::BT2020,
+ .deviceHandlesColorTransform = true,
+ .dimmingStage = aidl::android::hardware::graphics::composer3::
+ DimmingStage::GAMMA_OETF,
+ .renderIntent = aidl::android::hardware::graphics::composer3::
+ RenderIntent::TONE_MAP_ENHANCE};
const int64_t usage = GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
sp<GraphicBuffer> dstBuffer =
- new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888,
- 1, usage, "primeShaderCache_dst");
+ sp<GraphicBuffer>::make(displayRect.width(), displayRect.height(),
+ PIXEL_FORMAT_RGBA_8888, 1, usage, "primeShaderCache_dst");
const auto dstTexture =
std::make_shared<impl::ExternalTexture>(dstBuffer, *renderengine,
@@ -374,8 +687,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<GraphicBuffer> srcBuffer =
- new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888,
- 1, usage, "drawImageLayer_src");
+ sp<GraphicBuffer>::make(displayRect.width(), displayRect.height(),
+ PIXEL_FORMAT_RGBA_8888, 1, usage, "drawImageLayer_src");
const auto srcTexture = std::make_shared<
impl::ExternalTexture>(srcBuffer, *renderengine,
@@ -383,6 +696,8 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
impl::ExternalTexture::Usage::WRITEABLE);
drawHolePunchLayer(renderengine, display, dstTexture);
drawSolidLayers(renderengine, display, dstTexture);
+ drawSolidLayers(renderengine, p3Display, dstTexture);
+ drawSolidDimmedLayers(renderengine, display, dstTexture);
drawShadowLayers(renderengine, display, srcTexture);
drawShadowLayers(renderengine, p3Display, srcTexture);
@@ -397,8 +712,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<GraphicBuffer> externalBuffer =
- new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_8888,
- 1, usageExternal, "primeShaderCache_external");
+ sp<GraphicBuffer>::make(displayRect.width(), displayRect.height(),
+ PIXEL_FORMAT_RGBA_8888, 1, usageExternal,
+ "primeShaderCache_external");
const auto externalTexture =
std::make_shared<impl::ExternalTexture>(externalBuffer, *renderengine,
impl::ExternalTexture::Usage::READABLE);
@@ -408,8 +724,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<GraphicBuffer> f16ExternalBuffer =
- new GraphicBuffer(displayRect.width(), displayRect.height(), PIXEL_FORMAT_RGBA_FP16,
- 1, usageExternal, "primeShaderCache_external_f16");
+ sp<GraphicBuffer>::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) {
@@ -421,21 +738,45 @@ void Cache::primeShaderCache(SkiaRenderEngine* renderengine) {
for (auto texture : textures) {
drawImageLayers(renderengine, display, dstTexture, texture);
+
+ drawImageDimmedLayers(renderengine, display, dstTexture, texture);
+ drawImageDimmedLayers(renderengine, p3Display, dstTexture, texture);
+ drawImageDimmedLayers(renderengine, bt2020Display, dstTexture, texture);
+
// Draw layers for b/185569240.
drawClippedLayers(renderengine, display, dstTexture, texture);
}
drawPIPImageLayer(renderengine, display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, p3Display, dstTexture, externalTexture);
+ drawTransparentImageDimmedLayers(renderengine, p3DisplayEnhance, dstTexture,
+ externalTexture);
+
+ drawClippedDimmedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+ if (shouldPrimeUltraHDR) {
+ drawBT2020ClippedImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+
+ drawBT2020ImageLayers(renderengine, bt2020Display, dstTexture, externalTexture);
+ drawBT2020ImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+
+ drawExtendedHDRImageLayers(renderengine, display, dstTexture, externalTexture);
+ drawExtendedHDRImageLayers(renderengine, p3Display, dstTexture, externalTexture);
+ drawExtendedHDRImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+
+ drawP3ImageLayers(renderengine, p3DisplayEnhance, dstTexture, externalTexture);
+ }
+
// draw one final layer synchronously to force GL submit
LayerSettings layer{
.source = PixelSource{.solidColor = half3(0.f, 0.f, 0.f)},
};
auto layers = std::vector<LayerSettings>{layer};
// call get() to make it synchronous
- renderengine
- ->drawLayers(display, layers, dstTexture, kUseFrameBufferCache, base::unique_fd())
- .get();
+ renderengine->drawLayers(display, layers, dstTexture, base::unique_fd()).get();
const nsecs_t timeAfter = systemTime();
const float compileTimeMs = static_cast<float>(timeAfter - timeBefore) / 1.0E6;
diff --git a/libs/renderengine/skia/Cache.h b/libs/renderengine/skia/Cache.h
index 437571e616..62f6705c89 100644
--- a/libs/renderengine/skia/Cache.h
+++ b/libs/renderengine/skia/Cache.h
@@ -22,7 +22,7 @@ class SkiaRenderEngine;
class Cache {
public:
- static void primeShaderCache(SkiaRenderEngine*);
+ static void primeShaderCache(SkiaRenderEngine*, bool shouldPrimeUltraHDR);
private:
Cache() = default;
diff --git a/libs/renderengine/skia/ColorSpaces.cpp b/libs/renderengine/skia/ColorSpaces.cpp
index 37ff5dfede..92b01e07e6 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<SkColorSpace> toSkColorSpace(ui::Dataspace dataspace) {
skcms_Matrix3x3 gamut;
switch (dataspace & HAL_DATASPACE_STANDARD_MASK) {
@@ -61,13 +63,14 @@ sk_sp<SkColorSpace> 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/gl/GLExtensions.cpp b/libs/renderengine/skia/GLExtensions.cpp
index 3dd534e602..1d4d35fb54 100644
--- a/libs/renderengine/gl/GLExtensions.cpp
+++ b/libs/renderengine/skia/GLExtensions.cpp
@@ -23,11 +23,11 @@
#include <stdio.h>
#include <stdlib.h>
-ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::gl::GLExtensions)
+ANDROID_SINGLETON_STATIC_INSTANCE(android::renderengine::skia::GLExtensions)
namespace android {
namespace renderengine {
-namespace gl {
+namespace skia {
namespace {
@@ -68,19 +68,19 @@ void GLExtensions::initWithGLStrings(GLubyte const* vendor, GLubyte const* rende
}
char const* GLExtensions::getVendor() const {
- return mVendor.string();
+ return mVendor.c_str();
}
char const* GLExtensions::getRenderer() const {
- return mRenderer.string();
+ return mRenderer.c_str();
}
char const* GLExtensions::getVersion() const {
- return mVersion.string();
+ return mVersion.c_str();
}
char const* GLExtensions::getExtensions() const {
- return mExtensions.string();
+ return mExtensions.c_str();
}
void GLExtensions::initWithEGLStrings(char const* eglVersion, char const* eglExtensions) {
@@ -127,13 +127,13 @@ void GLExtensions::initWithEGLStrings(char const* eglVersion, char const* eglExt
}
char const* GLExtensions::getEGLVersion() const {
- return mEGLVersion.string();
+ return mEGLVersion.c_str();
}
char const* GLExtensions::getEGLExtensions() const {
- return mEGLExtensions.string();
+ return mEGLExtensions.c_str();
}
-} // namespace gl
+} // namespace skia
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/gl/GLExtensions.h b/libs/renderengine/skia/GLExtensions.h
index e415ff304a..0cb1bda0df 100644
--- a/libs/renderengine/gl/GLExtensions.h
+++ b/libs/renderengine/skia/GLExtensions.h
@@ -29,7 +29,7 @@
namespace android {
namespace renderengine {
-namespace gl {
+namespace skia {
class GLExtensions : public Singleton<GLExtensions> {
public:
@@ -81,7 +81,7 @@ private:
GLExtensions& operator=(const GLExtensions&);
};
-} // namespace gl
+} // namespace skia
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/skia/SkiaGLRenderEngine.cpp b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
index 97271cb32d..2053c6a34f 100644
--- a/libs/renderengine/skia/SkiaGLRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaGLRenderEngine.cpp
@@ -24,24 +24,13 @@
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GrContextOptions.h>
-#include <SkCanvas.h>
-#include <SkColorFilter.h>
-#include <SkColorMatrix.h>
-#include <SkColorSpace.h>
-#include <SkGraphics.h>
-#include <SkImage.h>
-#include <SkImageFilters.h>
-#include <SkRegion.h>
-#include <SkShadowUtils.h>
-#include <SkSurface.h>
+#include <GrTypes.h>
#include <android-base/stringprintf.h>
#include <gl/GrGLInterface.h>
+#include <include/gpu/ganesh/gl/GrGLDirectContext.h>
#include <gui/TraceUtils.h>
#include <sync/sync.h>
-#include <ui/BlurRegion.h>
-#include <ui/DataspaceUtils.h>
#include <ui/DebugUtils.h>
-#include <ui/GraphicBuffer.h>
#include <utils/Trace.h>
#include <cmath>
@@ -49,28 +38,8 @@
#include <memory>
#include <numeric>
-#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 "GLExtensions.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);
namespace android {
namespace renderengine {
@@ -78,6 +47,17 @@ namespace skia {
using base::StringAppendF;
+static bool checkGlError(const char* op, int lineNumber) {
+ bool errorFound = false;
+ GLint error = glGetError();
+ while (error != GL_NO_ERROR) {
+ errorFound = true;
+ error = glGetError();
+ ALOGV("after %s() (line # %d) glError (0x%x)\n", op, lineNumber, error);
+ }
+ return errorFound;
+}
+
static status_t selectConfigForAttribute(EGLDisplay dpy, EGLint const* attrs, EGLint attribute,
EGLint wanted, EGLConfig* outConfig) {
EGLint numConfigs = -1, n = 0;
@@ -180,7 +160,7 @@ std::unique_ptr<SkiaGLRenderEngine> SkiaGLRenderEngine::create(
LOG_ALWAYS_FATAL("eglQueryString(EGL_EXTENSIONS) failed");
}
- auto& extensions = gl::GLExtensions::getInstance();
+ auto& extensions = GLExtensions::getInstance();
extensions.initWithEGLStrings(eglVersion, eglExtensions);
// The code assumes that ES2 or later is available if this extension is
@@ -224,9 +204,10 @@ std::unique_ptr<SkiaGLRenderEngine> SkiaGLRenderEngine::create(
}
// initialize the renderer while GL is current
- std::unique_ptr<SkiaGLRenderEngine> engine =
- std::make_unique<SkiaGLRenderEngine>(args, display, ctxt, placeholder, protectedContext,
- protectedPlaceholder);
+ std::unique_ptr<SkiaGLRenderEngine> engine(new SkiaGLRenderEngine(args, display, ctxt,
+ placeholder, protectedContext,
+ protectedPlaceholder));
+ engine->ensureGrContextsCreated();
ALOGI("OpenGL ES informations:");
ALOGI("vendor : %s", extensions.getVendor());
@@ -239,11 +220,6 @@ std::unique_ptr<SkiaGLRenderEngine> SkiaGLRenderEngine::create(
return engine;
}
-std::future<void> SkiaGLRenderEngine::primeCache() {
- Cache::primeShaderCache(this);
- return {};
-}
-
EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bool logConfig) {
status_t err;
EGLConfig config;
@@ -283,72 +259,19 @@ EGLConfig SkiaGLRenderEngine::chooseEglConfig(EGLDisplay display, int format, bo
return config;
}
-sk_sp<SkData> 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<PixelFormat>(args.pixelFormat),
+ args.supportsBackgroundBlur),
mEGLDisplay(display),
mEGLContext(ctxt),
mPlaceholderSurface(placeholder),
mProtectedEGLContext(protectedContext),
- mProtectedPlaceholderSurface(protectedPlaceholder),
- mDefaultPixelFormat(static_cast<PixelFormat>(args.pixelFormat)),
- mUseColorManagement(args.useColorManagement) {
- sk_sp<const GrGLInterface> 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<SkiaCapture>();
-}
+ mProtectedPlaceholderSurface(protectedPlaceholder) {}
SkiaGLRenderEngine::~SkiaGLRenderEngine() {
- std::lock_guard<std::mutex> 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,74 +289,73 @@ 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<const GrGLInterface> 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 = GrDirectContexts::MakeGL(glInterface, options);
+ if (supportsProtectedContentImpl()) {
+ useProtectedContextImpl(GrProtected::kYes);
+ contexts.second = GrDirectContexts::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 ? GrSyncCpu::kYes :
+ GrSyncCpu::kNo);
+ 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()) {
+ if (!GLExtensions::getInstance().hasNativeFenceSync() ||
+ !GLExtensions::getInstance().hasWaitSync()) {
return false;
}
@@ -466,960 +388,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<GraphicBuffer>& 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<std::mutex> lock(mRenderingMutex);
- mGraphicBufferExternalRefs[buffer->getId()]++;
-
- if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
- std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
- std::make_shared<AutoBackendTexture::LocalRef>(grContext,
- buffer->toAHardwareBuffer(),
- isRenderable, mTextureCleanupMgr);
- cache.insert({buffer->getId(), imageTextureRef});
- }
-}
-
-void SkiaGLRenderEngine::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
- ATRACE_CALL();
- std::lock_guard<std::mutex> lock(mRenderingMutex);
- if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
- iter != mGraphicBufferExternalRefs.end()) {
- if (iter->second == 0) {
- ALOGW("Attempted to unmap GraphicBuffer <id: %" PRId64
- "> 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<std::mutex> lock(mRenderingMutex);
- return mTextureCleanupMgr.isEmpty();
-}
-
-void SkiaGLRenderEngine::cleanupPostRender() {
+base::unique_fd SkiaGLRenderEngine::flush() {
ATRACE_CALL();
- std::lock_guard<std::mutex> 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<SkShader> 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<SkRuntimeEffect> 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<SkScalar>(display.physicalDisplay.width()) /
- static_cast<SkScalar>(rotatedClipWidth);
- const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
- static_cast<SkScalar>(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 <typename T>
-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<std::promise<RenderEngineResult>>&& resultPromise,
- const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool /*useFramebufferCache*/,
- base::unique_fd&& bufferFence) {
- ATRACE_NAME("SkiaGL::drawLayers");
-
- std::lock_guard<std::mutex> 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<AutoBackendTexture::LocalRef> surfaceTextureRef;
- if (const auto& it = cache.find(buffer->getBuffer()->getId()); it != cache.end()) {
- surfaceTextureRef = it->second;
- } else {
- surfaceTextureRef =
- std::make_shared<AutoBackendTexture::LocalRef>(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<SkSurface> 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<SkColorFilter> 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<SkSurface> 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<SkImage> 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<uint32_t, sk_sp<SkImage>> 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<AutoBackendTexture::LocalRef> 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<SkImage> 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<SkShader> 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<SkShader> 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, float 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 * 2);
- const bool requiredHeight = bounds.height() > (cornerRadius * 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, cornerRadius);
- } 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, cornerRadius);
- } 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, cornerRadius);
- } 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, cornerRadius);
- } else if ((leftEqual && bounds.fBottom <= insetCrop.fBottom) ||
- (bottomEqual && bounds.fLeft >= insetCrop.fLeft)) {
- radii[3].set(0, 0);
- } else {
- return false;
+ if (!GLExtensions::getInstance().hasNativeFenceSync()) {
+ return base::unique_fd();
}
- return true;
-}
-
-inline std::pair<SkRRect, SkRRect> SkiaGLRenderEngine::getBoundsAndClip(const FloatRect& boundsRect,
- const FloatRect& cropRect,
- const float cornerRadius) {
- const SkRect bounds = getSkRect(boundsRect);
- const SkRect crop = getSkRect(cropRect);
-
- SkRRect clip;
- if (cornerRadius > 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, cornerRadius), 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, cornerRadius);
- 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, cornerRadius);
+ 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,
@@ -1490,13 +481,13 @@ EGLContext SkiaGLRenderEngine::createEglContext(EGLDisplay display, EGLConfig co
std::optional<RenderEngine::ContextPriority> SkiaGLRenderEngine::createContextPriority(
const RenderEngineCreationArgs& args) {
- if (!gl::GLExtensions::getInstance().hasContextPriority()) {
+ if (!GLExtensions::getInstance().hasContextPriority()) {
return std::nullopt;
}
switch (args.contextPriority) {
case RenderEngine::ContextPriority::REALTIME:
- if (gl::GLExtensions::getInstance().hasRealtimePriority()) {
+ if (GLExtensions::getInstance().hasRealtimePriority()) {
return RenderEngine::ContextPriority::REALTIME;
} else {
ALOGI("Realtime priority unsupported, degrading gracefully to high priority");
@@ -1539,114 +530,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) {
- const gl::GLExtensions& extensions = gl::GLExtensions::getInstance();
-
- StringAppendF(&result, "\n ------------RE-----------------\n");
+void SkiaGLRenderEngine::appendBackendSpecificInfoToDump(std::string& result) {
+ const GLExtensions& extensions = GLExtensions::getInstance();
+ 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<ResourcePair> 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<std::mutex> lock(mRenderingMutex);
-
- std::vector<ResourcePair> 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<android_dataspace>(linearEffect.inputDataspace))
- .c_str());
- StringAppendF(&result, "- outputDataspace: %s\n",
- dataspaceDetails(
- static_cast<android_dataspace>(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 5ef9944b14..af3311041d 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<SkiaGLRenderEngine> 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<void> 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<GraphicBuffer>& buffer, bool isRenderable) override;
- void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
- bool canSkipPostRenderCleanup() const override;
- void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
- const DisplaySettings& display,
- const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& 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,106 +79,14 @@ private:
Protection protection);
static std::optional<RenderEngine::ContextPriority> 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<SkRRect, SkRRect> getBoundsAndClip(const FloatRect& bounds,
- const FloatRect& crop, float 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<SkShader> shader;
- const LayerSettings& layer;
- const DisplaySettings& display;
- bool undoPremultipliedAlpha;
- bool requiresLinearEffect;
- float layerDimmingRatio;
- };
- sk_sp<SkShader> 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<GraphicBufferId, int32_t> mGraphicBufferExternalRefs
- GUARDED_BY(mRenderingMutex);
- // Cache of GL textures that we'll store per GraphicBuffer ID, shared between GPU contexts.
- std::unordered_map<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
- GUARDED_BY(mRenderingMutex);
- std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, 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<Fence> mLastDrawFence;
-
- // Graphics context used for creating surfaces and submitting commands
- sk_sp<GrDirectContext> mGrContext;
- // Same as above, but for protected content (eg. DRM)
- sk_sp<GrDirectContext> mProtectedGrContext;
-
- bool mInProtectedContext = false;
- // Object to capture commands send to Skia.
- std::unique_ptr<SkiaCapture> 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<SkData> 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 1fb24f5041..88326e77d7 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.cpp
+++ b/libs/renderengine/skia/SkiaRenderEngine.cpp
@@ -20,16 +20,1278 @@
#include "SkiaRenderEngine.h"
+#include <GrBackendSemaphore.h>
+#include <GrContextOptions.h>
+#include <GrTypes.h>
+#include <SkBlendMode.h>
+#include <SkCanvas.h>
+#include <SkColor.h>
+#include <SkColorFilter.h>
+#include <SkColorMatrix.h>
+#include <SkColorSpace.h>
+#include <SkData.h>
+#include <SkGraphics.h>
+#include <SkImage.h>
+#include <SkImageFilters.h>
+#include <SkImageInfo.h>
+#include <SkM44.h>
+#include <SkMatrix.h>
+#include <SkPaint.h>
+#include <SkPath.h>
+#include <SkPoint.h>
+#include <SkPoint3.h>
+#include <SkRRect.h>
+#include <SkRect.h>
+#include <SkRefCnt.h>
+#include <SkRegion.h>
+#include <SkRuntimeEffect.h>
+#include <SkSamplingOptions.h>
+#include <SkScalar.h>
+#include <SkShader.h>
+#include <SkShadowUtils.h>
+#include <SkString.h>
+#include <SkSurface.h>
+#include <SkTileMode.h>
+#include <android-base/stringprintf.h>
+#include <gui/FenceMonitor.h>
+#include <gui/TraceUtils.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <pthread.h>
#include <src/core/SkTraceEventCommon.h>
+#include <sync/sync.h>
+#include <ui/BlurRegion.h>
+#include <ui/DebugUtils.h>
+#include <ui/GraphicBuffer.h>
+#include <ui/HdrRenderTypeUtils.h>
+#include <utils/Trace.h>
+
+#include <cmath>
+#include <cstdint>
+#include <deque>
+#include <memory>
+#include <numeric>
+
+#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<SkRRect, SkRRect> 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<void> SkiaRenderEngine::primeCache(bool shouldPrimeUltraHDR) {
+ Cache::primeShaderCache(this, shouldPrimeUltraHDR);
+ return {};
+}
+
+sk_sp<SkData> 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 supportsBackgroundBlur)
+ : RenderEngine(type), mDefaultPixelFormat(pixelFormat) {
+ if (supportsBackgroundBlur) {
+ ALOGD("Background Blurs Enabled");
+ mBlurFilter = new KawaseBlurFilter();
+ }
+ mCapture = std::make_unique<SkiaCapture>();
+}
+
+SkiaRenderEngine::~SkiaRenderEngine() { }
+
+// To be called from backend dtors.
+void SkiaRenderEngine::finishRenderingAndAbandonContext() {
+ std::lock_guard<std::mutex> lock(mRenderingMutex);
+
+ if (mBlurFilter) {
+ delete mBlurFilter;
+ }
+
+ if (mGrContext) {
+ mGrContext->flushAndSubmit(GrSyncCpu::kYes);
+ mGrContext->abandonContext();
+ }
+
+ if (mProtectedGrContext) {
+ mProtectedGrContext->flushAndSubmit(GrSyncCpu::kYes);
+ 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(GrPurgeResourceOptions::kScratchResourcesOnly);
+ }
+
+ // 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<GraphicBuffer>& 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.) We also never cache any
+ // buffers while in a protected context.
+ const bool isProtectedBuffer = buffer->getUsage() & GRALLOC_USAGE_PROTECTED;
+ // Don't attempt to map buffers if we're not gpu sampleable. Callers shouldn't send a buffer
+ // over to RenderEngine.
+ const bool isGpuSampleable = buffer->getUsage() & GRALLOC_USAGE_HW_TEXTURE;
+ if (isProtectedBuffer || isProtected() || !isGpuSampleable) {
+ 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<std::mutex> lock(mRenderingMutex);
+ mGraphicBufferExternalRefs[buffer->getId()]++;
+
+ if (const auto& iter = cache.find(buffer->getId()); iter == cache.end()) {
+ std::shared_ptr<AutoBackendTexture::LocalRef> imageTextureRef =
+ std::make_shared<AutoBackendTexture::LocalRef>(grContext,
+ buffer->toAHardwareBuffer(),
+ isRenderable, mTextureCleanupMgr);
+ cache.insert({buffer->getId(), imageTextureRef});
+ }
+}
+
+void SkiaRenderEngine::unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) {
+ ATRACE_CALL();
+ std::lock_guard<std::mutex> lock(mRenderingMutex);
+ if (const auto& iter = mGraphicBufferExternalRefs.find(buffer->getId());
+ iter != mGraphicBufferExternalRefs.end()) {
+ if (iter->second == 0) {
+ ALOGW("Attempted to unmap GraphicBuffer <id: %" PRId64
+ "> 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<AutoBackendTexture::LocalRef> SkiaRenderEngine::getOrCreateBackendTexture(
+ const sp<GraphicBuffer>& buffer, bool isOutputBuffer) {
+ // Do not lookup the buffer in the cache for protected contexts
+ if (!isProtected()) {
+ if (const auto& it = mTextureCache.find(buffer->getId()); it != mTextureCache.end()) {
+ return it->second;
+ }
+ }
+ return std::make_shared<AutoBackendTexture::LocalRef>(getActiveGrContext(),
+ buffer->toAHardwareBuffer(),
+ isOutputBuffer, mTextureCleanupMgr);
+}
+
+bool SkiaRenderEngine::canSkipPostRenderCleanup() const {
+ std::lock_guard<std::mutex> lock(mRenderingMutex);
+ return mTextureCleanupMgr.isEmpty();
+}
+
+void SkiaRenderEngine::cleanupPostRender() {
+ ATRACE_CALL();
+ std::lock_guard<std::mutex> lock(mRenderingMutex);
+ mTextureCleanupMgr.cleanup();
+}
+
+sk_sp<SkShader> 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,
+ .fakeOutputDataspace = parameters.fakeOutputDataspace};
+
+ auto effectIter = mRuntimeEffects.find(effect);
+ sk_sp<SkRuntimeEffect> 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<SkScalar>(display.physicalDisplay.width()) /
+ static_cast<SkScalar>(rotatedClipWidth);
+ const auto scaleY = static_cast<SkScalar>(display.physicalDisplay.height()) /
+ static_cast<SkScalar>(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 <typename T>
+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<std::promise<FenceResult>>&& resultPromise,
+ const DisplaySettings& display, const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
+ ATRACE_FORMAT("%s for %s", __func__, display.namePlusId.c_str());
+
+ std::lock_guard<std::mutex> 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();
+ LOG_ALWAYS_FATAL_IF(grContext->abandoned(), "GrContext is abandoned/device lost at start of %s",
+ __func__);
+
+ // 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<SkSurface> 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<SkColorFilter> 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<SkSurface> 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<SkImage> 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<uint32_t, sk_sp<SkImage>> 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<int32_t>(ui::Dataspace::RANGE_EXTENDED) &&
+ (display.outputDataspace & ui::Dataspace::TRANSFER_MASK) ==
+ static_cast<int32_t>(ui::Dataspace::TRANSFER_SRGB);
+
+ const bool useFakeOutputDataspaceForRuntimeEffect = !dimInLinearSpace && isExtendedHdr;
+
+ const ui::Dataspace fakeDataspace = useFakeOutputDataspaceForRuntimeEffect
+ ? static_cast<ui::Dataspace>(
+ (display.outputDataspace & ui::Dataspace::STANDARD_MASK) |
+ ui::Dataspace::TRANSFER_GAMMA2_2 |
+ (display.outputDataspace & ui::Dataspace::RANGE_MASK))
+ : ui::Dataspace::UNKNOWN;
+
+ // 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() ||
+ (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;
+ }
+
+ const ui::Dataspace layerDataspace = 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<SkImage> 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<SkShader> 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 = display.outputDataspace,
+ .fakeOutputDataspace = fakeDataspace}));
+
+ // Turn on dithering when dimming beyond this (arbitrary) threshold...
+ static constexpr float kDimmingThreshold = 0.9f;
+ // ...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 =
+ getHdrRenderType(layer.sourceDataspace,
+ std::optional<ui::PixelFormat>(static_cast<ui::PixelFormat>(
+ buffer->getPixelFormat()))) != HdrRenderType::SDR &&
+ 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<SkShader> 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 = display.outputDataspace,
+ .fakeOutputDataspace = fakeDataspace}));
+ }
+
+ 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");
+ skgpu::ganesh::Flush(activeSurface);
+ }
+ }
+ for (const auto& borderRenderInfo : display.borderInfoList) {
+ SkPaint p;
+ p.setColor(SkColor4f{borderRenderInfo.color.r, borderRenderInfo.color.g,
+ borderRenderInfo.color.b, borderRenderInfo.color.a});
+ p.setAntiAlias(true);
+ p.setStyle(SkPaint::kStroke_Style);
+ p.setStrokeWidth(borderRenderInfo.width);
+ SkRegion sk_region;
+ SkPath path;
+
+ // Construct a final SkRegion using Regions
+ for (const auto& r : borderRenderInfo.combinedRegion) {
+ sk_region.op({r.left, r.top, r.right, r.bottom}, SkRegion::kUnion_Op);
+ }
+
+ sk_region.getBoundaryPath(&path);
+ canvas->drawPath(path, p);
+ path.close();
+ }
+
+ surfaceAutoSaveRestore.restore();
+ mCapture->endCapture();
+ {
+ ATRACE_NAME("flush surface");
+ LOG_ALWAYS_FATAL_IF(activeSurface != dstSurface);
+ skgpu::ganesh::Flush(activeSurface);
+ }
+
+ auto drawFence = sp<Fence>::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<ResourcePair> 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<std::mutex> lock(mRenderingMutex);
+
+ std::vector<ResourcePair> 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<android_dataspace>(linearEffect.inputDataspace))
+ .c_str());
+ StringAppendF(&result, "- outputDataspace: %s\n",
+ dataspaceDetails(
+ static_cast<android_dataspace>(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 160a18698e..ac134afa64 100644
--- a/libs/renderengine/skia/SkiaRenderEngine.h
+++ b/libs/renderengine/skia/SkiaRenderEngine.h
@@ -20,6 +20,31 @@
#include <renderengine/RenderEngine.h>
#include <sys/types.h>
+#include <GrBackendSemaphore.h>
+#include <GrDirectContext.h>
+#include <SkSurface.h>
+#include <android-base/thread_annotations.h>
+#include <renderengine/ExternalTexture.h>
+#include <renderengine/RenderEngine.h>
+#include <sys/types.h>
+
+#include <mutex>
+#include <unordered_map>
+
+#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,140 @@ 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<SkiaRenderEngine> create(const RenderEngineCreationArgs& args);
- SkiaRenderEngine(RenderEngineType type);
- ~SkiaRenderEngine() override {}
-
- virtual std::future<void> 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 supportsBackgroundBlur);
+ ~SkiaRenderEngine() override;
+
+ std::future<void> primeCache(bool shouldPrimeUltraHDR) override final;
+ void cleanupPostRender() override final;
+ bool supportsBackgroundBlur() override final {
+ return mBlurFilter != nullptr;
+ }
+ void onActiveDisplaySizeChanged(ui::Size size) override final;
+ int reportShadersCompiled();
+ 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<GraphicBuffer>& /*buffer*/,
- bool /*isRenderable*/) override = 0;
- virtual void unmapExternalTextureBuffer(const sp<GraphicBuffer>& /*buffer*/) override = 0;
-
- virtual void drawLayersInternal(
- const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
- const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& 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<GrDirectContext>, sk_sp<GrDirectContext>>;
+ 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<SkData> 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<GraphicBuffer>& buffer,
+ bool isRenderable) override final;
+ void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override final;
+ bool canSkipPostRenderCleanup() const override final;
+
+ std::shared_ptr<AutoBackendTexture::LocalRef> getOrCreateBackendTexture(
+ const sp<GraphicBuffer>& 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<std::promise<FenceResult>>&& resultPromise,
+ const DisplaySettings& display,
+ const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer,
+ 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<SkShader> shader;
+ const LayerSettings& layer;
+ const DisplaySettings& display;
+ bool undoPremultipliedAlpha;
+ bool requiresLinearEffect;
+ float layerDimmingRatio;
+ const ui::Dataspace outputDataSpace;
+ const ui::Dataspace fakeOutputDataspace;
};
+ sk_sp<SkShader> createRuntimeEffectShader(const RuntimeEffectShaderParameters&);
+
+ const PixelFormat mDefaultPixelFormat;
+
+ // 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<GraphicBufferId, int32_t> 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<GraphicBufferId, std::shared_ptr<AutoBackendTexture::LocalRef>> mTextureCache
+ GUARDED_BY(mRenderingMutex);
+ std::unordered_map<shaders::LinearEffect, sk_sp<SkRuntimeEffect>, shaders::LinearEffectHasher>
+ mRuntimeEffects;
+ AutoBackendTexture::CleanupManager mTextureCleanupMgr GUARDED_BY(mRenderingMutex);
+
+ StretchShaderFactory mStretchShaderFactory;
+
+ sp<Fence> mLastDrawFence;
+ BlurFilter* mBlurFilter = nullptr;
+
+ // Object to capture commands send to Skia.
+ std::unique_ptr<SkiaCapture> 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<GrDirectContext> mGrContext;
+ // Same as above, but for protected content (eg. DRM)
+ sk_sp<GrDirectContext> 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 0000000000..ba20d1f223
--- /dev/null
+++ b/libs/renderengine/skia/SkiaVkRenderEngine.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
+#undef LOG_TAG
+#define LOG_TAG "RenderEngine"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include "SkiaVkRenderEngine.h"
+
+#include <GrBackendSemaphore.h>
+#include <GrContextOptions.h>
+#include <vk/GrVkExtensions.h>
+#include <vk/GrVkTypes.h>
+#include <include/gpu/ganesh/vk/GrVkDirectContext.h>
+
+#include <android-base/stringprintf.h>
+#include <gui/TraceUtils.h>
+#include <sync/sync.h>
+#include <utils/Trace.h>
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include <vulkan/vulkan.h>
+#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<std::string> instanceExtensionNames;
+ std::vector<std::string> 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<VkExtensionProperties> instanceExtensions(extensionCount);
+ VK_CHECK(vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount,
+ instanceExtensions.data()));
+ std::vector<const char*> enabledInstanceExtensionNames;
+ enabledInstanceExtensionNames.reserve(instanceExtensions.size());
+ interface.instanceExtensionNames.reserve(instanceExtensions.size());
+ for (const auto& instExt : instanceExtensions) {
+ enabledInstanceExtensionNames.push_back(instExt.extensionName);
+ interface.instanceExtensionNames.push_back(instExt.extensionName);
+ }
+
+ const VkInstanceCreateInfo instanceCreateInfo = {
+ VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+ nullptr,
+ 0,
+ &appInfo,
+ 0,
+ nullptr,
+ (uint32_t)enabledInstanceExtensionNames.size(),
+ enabledInstanceExtensionNames.data(),
+ };
+
+ VK_GET_PROC(CreateInstance);
+ VkInstance instance;
+ VK_CHECK(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
+
+ VK_GET_INST_PROC(instance, DestroyInstance);
+ interface.funcs.vkDestroyInstance = vkDestroyInstance;
+ VK_GET_INST_PROC(instance, EnumeratePhysicalDevices);
+ VK_GET_INST_PROC(instance, EnumerateDeviceExtensionProperties);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceProperties2);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceExternalSemaphoreProperties);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceQueueFamilyProperties2);
+ VK_GET_INST_PROC(instance, GetPhysicalDeviceFeatures2);
+ VK_GET_INST_PROC(instance, CreateDevice);
+
+ uint32_t physdevCount;
+ VK_CHECK(vkEnumeratePhysicalDevices(instance, &physdevCount, nullptr));
+ if (physdevCount == 0) {
+ BAIL("Could not find any physical devices");
+ }
+
+ physdevCount = 1;
+ VkPhysicalDevice physicalDevice;
+ VkResult enumeratePhysDevsErr =
+ vkEnumeratePhysicalDevices(instance, &physdevCount, &physicalDevice);
+ if (enumeratePhysDevsErr != VK_SUCCESS && VK_INCOMPLETE != enumeratePhysDevsErr) {
+ BAIL("vkEnumeratePhysicalDevices failed with non-VK_INCOMPLETE error: %d",
+ enumeratePhysDevsErr);
+ }
+
+ VkPhysicalDeviceProperties2 physDevProps = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2,
+ 0,
+ {},
+ };
+ VkPhysicalDeviceProtectedMemoryProperties protMemProps = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROTECTED_MEMORY_PROPERTIES,
+ 0,
+ {},
+ };
+
+ if (protectedContent) {
+ physDevProps.pNext = &protMemProps;
+ }
+
+ vkGetPhysicalDeviceProperties2(physicalDevice, &physDevProps);
+ if (physDevProps.properties.apiVersion < VK_MAKE_VERSION(1, 1, 0)) {
+ BAIL("Could not find a Vulkan 1.1+ physical device");
+ }
+
+ // Check for syncfd support. Bail if we cannot both import and export them.
+ VkPhysicalDeviceExternalSemaphoreInfo semInfo = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_SEMAPHORE_INFO,
+ nullptr,
+ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ };
+ VkExternalSemaphoreProperties semProps = {
+ VK_STRUCTURE_TYPE_EXTERNAL_SEMAPHORE_PROPERTIES, nullptr, 0, 0, 0,
+ };
+ vkGetPhysicalDeviceExternalSemaphoreProperties(physicalDevice, &semInfo, &semProps);
+
+ bool sufficientSemaphoreSyncFdSupport = (semProps.exportFromImportedHandleTypes &
+ VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+ (semProps.compatibleHandleTypes & VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT) &&
+ (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT) &&
+ (semProps.externalSemaphoreFeatures & VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+
+ if (!sufficientSemaphoreSyncFdSupport) {
+ BAIL("Vulkan device does not support sufficient external semaphore sync fd features. "
+ "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+ "compatibleHandleTypes 0x%x (needed 0x%x) "
+ "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+ semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.externalSemaphoreFeatures,
+ VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+ VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+ } else {
+ ALOGD("Vulkan device supports sufficient external semaphore sync fd features. "
+ "exportFromImportedHandleTypes 0x%x (needed 0x%x) "
+ "compatibleHandleTypes 0x%x (needed 0x%x) "
+ "externalSemaphoreFeatures 0x%x (needed 0x%x) ",
+ semProps.exportFromImportedHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.compatibleHandleTypes, VK_EXTERNAL_SEMAPHORE_HANDLE_TYPE_SYNC_FD_BIT,
+ semProps.externalSemaphoreFeatures,
+ VK_EXTERNAL_SEMAPHORE_FEATURE_EXPORTABLE_BIT |
+ VK_EXTERNAL_SEMAPHORE_FEATURE_IMPORTABLE_BIT);
+ }
+
+ uint32_t queueCount;
+ vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, nullptr);
+ if (queueCount == 0) {
+ BAIL("Could not find queues for physical device");
+ }
+
+ std::vector<VkQueueFamilyProperties2> queueProps(queueCount);
+ std::vector<VkQueueFamilyGlobalPriorityPropertiesEXT> queuePriorityProps(queueCount);
+ VkQueueGlobalPriorityKHR queuePriority = VK_QUEUE_GLOBAL_PRIORITY_MEDIUM_KHR;
+ // Even though we don't yet know if the VK_EXT_global_priority extension is available,
+ // we can safely add the request to the pNext chain, and if the extension is not
+ // available, it will be ignored.
+ for (uint32_t i = 0; i < queueCount; ++i) {
+ queuePriorityProps[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_GLOBAL_PRIORITY_PROPERTIES_EXT;
+ queuePriorityProps[i].pNext = nullptr;
+ queueProps[i].pNext = &queuePriorityProps[i];
+ }
+ vkGetPhysicalDeviceQueueFamilyProperties2(physicalDevice, &queueCount, queueProps.data());
+
+ int graphicsQueueIndex = -1;
+ for (uint32_t i = 0; i < queueCount; ++i) {
+ // Look at potential answers to the VK_EXT_global_priority query. If answers were
+ // provided, we may adjust the queuePriority.
+ if (queueProps[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+ for (uint32_t j = 0; j < queuePriorityProps[i].priorityCount; j++) {
+ if (queuePriorityProps[i].priorities[j] > queuePriority) {
+ queuePriority = queuePriorityProps[i].priorities[j];
+ }
+ }
+ if (queuePriority == VK_QUEUE_GLOBAL_PRIORITY_REALTIME_KHR) {
+ interface.isRealtimePriority = true;
+ }
+ graphicsQueueIndex = i;
+ break;
+ }
+ }
+
+ if (graphicsQueueIndex == -1) {
+ BAIL("Could not find a graphics queue family");
+ }
+
+ uint32_t deviceExtensionCount;
+ VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+ nullptr));
+ std::vector<VkExtensionProperties> deviceExtensions(deviceExtensionCount);
+ VK_CHECK(vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &deviceExtensionCount,
+ deviceExtensions.data()));
+
+ std::vector<const char*> 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;
+
+ if (protectedContent && !interface.protectedMemoryFeatures->protectedMemory) {
+ BAIL("Protected memory not supported");
+ }
+
+ float queuePriorities[1] = {0.0f};
+ void* queueNextPtr = nullptr;
+
+ VkDeviceQueueGlobalPriorityCreateInfoEXT queuePriorityCreateInfo = {
+ VK_STRUCTURE_TYPE_DEVICE_QUEUE_GLOBAL_PRIORITY_CREATE_INFO_EXT,
+ nullptr,
+ // If queue priority is supported, RE should always have realtime priority.
+ queuePriority,
+ };
+
+ if (interface.grExtensions.hasExtension(VK_EXT_GLOBAL_PRIORITY_EXTENSION_NAME, 2)) {
+ queueNextPtr = &queuePriorityCreateInfo;
+ }
+
+ VkDeviceQueueCreateFlags deviceQueueCreateFlags =
+ (VkDeviceQueueCreateFlags)(protectedContent ? VK_DEVICE_QUEUE_CREATE_PROTECTED_BIT : 0);
+
+ const VkDeviceQueueCreateInfo queueInfo = {
+ VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+ queueNextPtr,
+ deviceQueueCreateFlags,
+ (uint32_t)graphicsQueueIndex,
+ 1,
+ queuePriorities,
+ };
+
+ const VkDeviceCreateInfo deviceInfo = {
+ VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+ interface.physicalDeviceFeatures2,
+ 0,
+ 1,
+ &queueInfo,
+ 0,
+ nullptr,
+ (uint32_t)enabledDeviceExtensionNames.size(),
+ enabledDeviceExtensionNames.data(),
+ nullptr,
+ };
+
+ ALOGD("Trying to create Vk device with protectedContent=%d", protectedContent);
+ VkDevice device;
+ VK_CHECK(vkCreateDevice(physicalDevice, &deviceInfo, nullptr, &device));
+ ALOGD("Trying to create Vk device with protectedContent=%d (success)", protectedContent);
+
+ VkQueue graphicsQueue;
+ VK_GET_DEV_PROC(device, GetDeviceQueue2);
+ const VkDeviceQueueInfo2 deviceQueueInfo2 = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_INFO_2, nullptr,
+ deviceQueueCreateFlags,
+ (uint32_t)graphicsQueueIndex, 0};
+ vkGetDeviceQueue2(device, &deviceQueueInfo2, &graphicsQueue);
+
+ VK_GET_DEV_PROC(device, DeviceWaitIdle);
+ VK_GET_DEV_PROC(device, DestroyDevice);
+ interface.funcs.vkDeviceWaitIdle = vkDeviceWaitIdle;
+ interface.funcs.vkDestroyDevice = vkDestroyDevice;
+
+ VK_GET_DEV_PROC(device, CreateSemaphore);
+ VK_GET_DEV_PROC(device, ImportSemaphoreFdKHR);
+ VK_GET_DEV_PROC(device, GetSemaphoreFdKHR);
+ VK_GET_DEV_PROC(device, DestroySemaphore);
+ interface.funcs.vkCreateSemaphore = vkCreateSemaphore;
+ interface.funcs.vkImportSemaphoreFdKHR = vkImportSemaphoreFdKHR;
+ interface.funcs.vkGetSemaphoreFdKHR = vkGetSemaphoreFdKHR;
+ interface.funcs.vkDestroySemaphore = vkDestroySemaphore;
+
+ // At this point, everything's succeeded and we can continue
+ interface.initialized = true;
+ interface.instance = instance;
+ interface.physicalDevice = physicalDevice;
+ interface.device = device;
+ interface.queue = graphicsQueue;
+ interface.queueIndex = graphicsQueueIndex;
+ interface.apiVersion = physDevProps.properties.apiVersion;
+ // grExtensions already constructed
+ // feature pointers already constructed
+ interface.grGetProc = sGetProc;
+ interface.isProtected = protectedContent;
+ // funcs already initialized
+
+ 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> SkiaVkRenderEngine::create(
+ const RenderEngineCreationArgs& args) {
+ std::unique_ptr<SkiaVkRenderEngine> 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<PixelFormat>(args.pixelFormat),
+ args.supportsBackgroundBlur) {}
+
+SkiaVkRenderEngine::~SkiaVkRenderEngine() {
+ finishRenderingAndAbandonContext();
+}
+
+SkiaRenderEngine::Contexts SkiaVkRenderEngine::createDirectContexts(
+ const GrContextOptions& options) {
+ sSetupVulkanInterface();
+
+ SkiaRenderEngine::Contexts contexts;
+ contexts.first = GrDirectContexts::MakeVulkan(sVulkanInterface.getBackendContext(), options);
+ if (supportsProtectedContentImpl()) {
+ contexts.second =
+ GrDirectContexts::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<DestroySemaphoreInfo*>(semaphore);
+ --info->mRefs;
+ if (!info->mRefs) {
+ sVulkanInterface.destroySemaphore(info->mSemaphore);
+ delete info;
+ }
+}
+
+static void delete_semaphore_protected(void* semaphore) {
+ DestroySemaphoreInfo* info = reinterpret_cast<DestroySemaphoreInfo*>(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(GrSyncCpu::kNo);
+ int drawFenceFd = -1;
+ if (semaphore != VK_NULL_HANDLE) {
+ if (GrSemaphoresSubmitted::kYes == submitted) {
+ drawFenceFd = vi.exportSemaphoreSyncFd(semaphore);
+ }
+ // Now that drawFenceFd has been created, we can delete our reference to this semaphore
+ flushInfo.fFinishedProc(destroySemaphoreInfo);
+ }
+ base::unique_fd res(drawFenceFd);
+ return res;
+}
+
+int SkiaVkRenderEngine::getContextPriority() {
+ // EGL_CONTEXT_PRIORITY_REALTIME_NV
+ constexpr int kRealtimePriority = 0x3357;
+ 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 0000000000..2e0cf45220
--- /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 <vk/GrVkBackendContext.h>
+
+#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<SkiaVkRenderEngine> 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 856fff4b01..48dc77eeec 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.cpp
+++ b/libs/renderengine/skia/debug/SkiaCapture.cpp
@@ -27,15 +27,21 @@
#include <utils/Trace.h>
#include "CommonPool.h"
+#include "SkCanvas.h"
+#include "SkRect.h"
+#include "SkTypeface.h"
#include "src/utils/SkMultiPictureDocument.h"
+#include <sys/stat.h>
namespace android {
namespace renderengine {
namespace skia {
// The root of the filename to write a recorded SKP to. In order for this file to
-// be written to /data/user/, user must run 'adb shell setenforce 0' on the device.
-static const std::string CAPTURED_FILENAME_BASE = "/data/user/re_skiacapture";
+// be written, user must run 'adb shell setenforce 0' on the device. Note: This
+// is handled by record.sh. FIXME(b/296282988): With updated selinux policies,
+// 'adb shell setenforce 0' should be unnecessary.
+static const std::string CAPTURED_FILE_DIR = "/data/misc/mskps";
SkiaCapture::~SkiaCapture() {
mTimer.stop();
@@ -166,11 +172,12 @@ bool SkiaCapture::setupMultiFrameCapture() {
ATRACE_CALL();
ALOGD("Set up multi-frame capture, ms = %llu", mTimerInterval.count());
base::SetProperty(PROPERTY_DEBUG_RENDERENGINE_CAPTURE_FILENAME, "");
- const std::scoped_lock lock(mMutex);
- // Attach a timestamp to the file.
+ mkdir(CAPTURED_FILE_DIR.c_str(), 0700);
+
+ const std::scoped_lock lock(mMutex);
mCaptureFile.clear();
- base::StringAppendF(&mCaptureFile, "%s_%lld.mskp", CAPTURED_FILENAME_BASE.c_str(),
+ base::StringAppendF(&mCaptureFile, "%s/re_skiacapture_%lld.mskp", CAPTURED_FILE_DIR.c_str(),
std::chrono::steady_clock::now().time_since_epoch().count());
auto stream = std::make_unique<SkFILEWStream>(mCaptureFile.c_str());
// We own this stream and need to hold it until close() finishes.
diff --git a/libs/renderengine/skia/debug/SkiaCapture.h b/libs/renderengine/skia/debug/SkiaCapture.h
index f1946290ca..d65a579916 100644
--- a/libs/renderengine/skia/debug/SkiaCapture.h
+++ b/libs/renderengine/skia/debug/SkiaCapture.h
@@ -19,13 +19,15 @@
#include <SkDocument.h>
#include <SkNWayCanvas.h>
#include <SkPictureRecorder.h>
+#include <SkRefCnt.h>
+#include <SkStream.h>
#include <SkSurface.h>
+#include "tools/SkSharingProc.h"
#include <chrono>
#include <mutex>
#include "CaptureTimer.h"
-#include "tools/SkSharingProc.h"
namespace android {
namespace renderengine {
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
index f24a4f1c05..5bf6560cad 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.cpp
@@ -18,7 +18,6 @@
#include "SkiaMemoryReporter.h"
-#include <SkString.h>
#include <android-base/stringprintf.h>
#include <log/log_main.h>
@@ -142,7 +141,7 @@ void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
TraceValue traceValue = convertUnits(result->second);
const char* entry = (traceValue.count > 1) ? "entries" : "entry";
StringAppendF(&log, " %s: %.2f %s (%d %s)\n", categoryItem->first.c_str(),
- traceValue.value, traceValue.units, traceValue.count, entry);
+ traceValue.value, traceValue.units.c_str(), traceValue.count, entry);
}
if (mItemize) {
for (const auto& individualItem : resultsMap) {
@@ -153,7 +152,7 @@ void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
auto result = individualItem.second.find("size");
TraceValue size = convertUnits(result->second);
StringAppendF(&log, " %s: size[%.2f %s]", individualItem.first.c_str(),
- size.value, size.units);
+ size.value, size.units.c_str());
if (!wrappedResources) {
for (const auto& itemValues : individualItem.second) {
if (strcmp("size", itemValues.first) == 0) {
@@ -162,10 +161,10 @@ void SkiaMemoryReporter::logOutput(std::string& log, bool wrappedResources) {
TraceValue traceValue = convertUnits(itemValues.second);
if (traceValue.value == 0.0f) {
StringAppendF(&log, " %s[%s]", itemValues.first,
- traceValue.units);
+ traceValue.units.c_str());
} else {
StringAppendF(&log, " %s[%.2f %s]", itemValues.first,
- traceValue.value, traceValue.units);
+ traceValue.value, traceValue.units.c_str());
}
}
}
@@ -184,16 +183,16 @@ void SkiaMemoryReporter::logTotals(std::string& log) {
TraceValue total = convertUnits(mTotalSize);
TraceValue purgeable = convertUnits(mPurgeableSize);
StringAppendF(&log, " %.0f bytes, %.2f %s (%.2f %s is purgeable)\n", mTotalSize.value,
- total.value, total.units, purgeable.value, purgeable.units);
+ total.value, total.units.c_str(), purgeable.value, purgeable.units.c_str());
}
SkiaMemoryReporter::TraceValue SkiaMemoryReporter::convertUnits(const TraceValue& value) {
TraceValue output(value);
- if (SkString("bytes") == SkString(output.units) && output.value >= 1024) {
+ if (SkString("bytes") == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "KB";
}
- if (SkString("KB") == SkString(output.units) && output.value >= 1024) {
+ if (SkString("KB") == output.units && output.value >= 1024) {
output.value = output.value / 1024.0f;
output.units = "MB";
}
diff --git a/libs/renderengine/skia/debug/SkiaMemoryReporter.h b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
index dbbd65b3da..da91674e1f 100644
--- a/libs/renderengine/skia/debug/SkiaMemoryReporter.h
+++ b/libs/renderengine/skia/debug/SkiaMemoryReporter.h
@@ -16,6 +16,7 @@
#pragma once
+#include <SkString.h>
#include <SkTraceMemoryDump.h>
#include <string>
@@ -75,7 +76,7 @@ private:
TraceValue(const char* units, uint64_t value) : units(units), value(value), count(1) {}
TraceValue(const TraceValue& v) : units(v.units), value(v.value), count(v.count) {}
- const char* units;
+ SkString units;
float value;
int count;
};
@@ -104,4 +105,4 @@ private:
} /* namespace skia */
} /* namespace renderengine */
-} /* namespace android */ \ No newline at end of file
+} /* namespace android */
diff --git a/libs/renderengine/skia/debug/record.sh b/libs/renderengine/skia/debug/record.sh
index e99b7ae390..88d8b09bd0 100755
--- a/libs/renderengine/skia/debug/record.sh
+++ b/libs/renderengine/skia/debug/record.sh
@@ -16,7 +16,6 @@ elif [ "$1" == "rootandsetup" ]; then
# first time use requires these changes
adb root
adb shell setenforce 0
- adb shell setprop debug.renderengine.backend "skiaglthreaded"
adb shell stop
adb shell start
exit 1;
@@ -44,7 +43,6 @@ sleep $(($1 / 1000 + 4));
# There is no guarantee that at least one frame passed through renderengine during that time
# but as far as I know it always at least writes a 0-byte file with a new name, unless it crashes
# the process it is recording.
-# /data/user/re_skiacapture_56204430551705.mskp
spin() {
case "$spin" in
diff --git a/libs/renderengine/skia/filters/BlurFilter.cpp b/libs/renderengine/skia/filters/BlurFilter.cpp
index 63cc02b7ea..1e0c4cf9d0 100644
--- a/libs/renderengine/skia/filters/BlurFilter.cpp
+++ b/libs/renderengine/skia/filters/BlurFilter.cpp
@@ -16,14 +16,15 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "BlurFilter.h"
+#include <SkBlendMode.h>
#include <SkCanvas.h>
-#include <SkData.h>
#include <SkPaint.h>
#include <SkRRect.h>
#include <SkRuntimeEffect.h>
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include <SkTileMode.h>
#include <log/log.h>
#include <utils/Trace.h>
diff --git a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
index 55867a95cc..e72c501336 100644
--- a/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/GaussianBlurFilter.cpp
@@ -17,8 +17,8 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "GaussianBlurFilter.h"
+#include <SkBlendMode.h>
#include <SkCanvas.h>
-#include <SkData.h>
#include <SkPaint.h>
#include <SkRRect.h>
#include <SkRuntimeEffect.h>
@@ -26,6 +26,9 @@
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include "include/gpu/GpuTypes.h" // from Skia
#include <log/log.h>
#include <utils/Trace.h>
@@ -45,7 +48,8 @@ sk_sp<SkImage> 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<SkSurface> surface = SkSurface::MakeRenderTarget(context, SkBudgeted::kNo, scaledInfo);
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(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 bfde06fd9a..5c9820cdc5 100644
--- a/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
+++ b/libs/renderengine/skia/filters/KawaseBlurFilter.cpp
@@ -17,14 +17,20 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include "KawaseBlurFilter.h"
+#include <SkAlphaType.h>
+#include <SkBlendMode.h>
#include <SkCanvas.h>
-#include <SkData.h>
+#include <SkImageInfo.h>
#include <SkPaint.h>
#include <SkRRect.h>
#include <SkRuntimeEffect.h>
+#include <SkShader.h>
#include <SkSize.h>
#include <SkString.h>
#include <SkSurface.h>
+#include <SkTileMode.h>
+#include <include/gpu/GpuTypes.h>
+#include <include/gpu/ganesh/SkSurfaceGanesh.h>
#include <log/log.h>
#include <utils/Trace.h>
@@ -33,19 +39,18 @@ namespace renderengine {
namespace skia {
KawaseBlurFilter::KawaseBlurFilter(): BlurFilter() {
- SkString blurString(R"(
- uniform shader child;
- uniform float in_blurOffset;
-
- half4 main(float2 xy) {
- half4 c = child.eval(xy);
- c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));
- c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));
- c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));
- c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));
- return half4(c.rgb * 0.2, 1.0);
- }
- )");
+ SkString blurString(
+ "uniform shader child;"
+ "uniform float in_blurOffset;"
+
+ "half4 main(float2 xy) {"
+ "half4 c = child.eval(xy);"
+ "c += child.eval(xy + float2(+in_blurOffset, +in_blurOffset));"
+ "c += child.eval(xy + float2(+in_blurOffset, -in_blurOffset));"
+ "c += child.eval(xy + float2(-in_blurOffset, -in_blurOffset));"
+ "c += child.eval(xy + float2(-in_blurOffset, +in_blurOffset));"
+ "return half4(c.rgb * 0.2, 1.0);"
+ "}");
auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
if (!blurEffect) {
@@ -54,14 +59,36 @@ KawaseBlurFilter::KawaseBlurFilter(): BlurFilter() {
mBlurEffect = std::move(blurEffect);
}
-sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
- const sk_sp<SkImage> input, const SkRect& blurRect)
- const {
+// Draws the given runtime shader on a GPU (Ganesh) surface and returns the result as an
+// SkImage.
+static sk_sp<SkImage> makeImage(SkSurface* surface, SkRuntimeShaderBuilder* builder) {
+ sk_sp<SkShader> shader = builder->makeShader(nullptr);
+ if (!shader) {
+ return nullptr;
+ }
+ SkPaint paint;
+ paint.setShader(std::move(shader));
+ paint.setBlendMode(SkBlendMode::kSrc);
+ surface->getCanvas()->drawPaint(paint);
+ return surface->makeImageSnapshot();
+}
+
+sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context,
+ const uint32_t blurRadius,
+ const sk_sp<SkImage> input,
+ const SkRect& blurRect) const {
+ LOG_ALWAYS_FATAL_IF(context == nullptr, "%s: Needs GPU context", __func__);
+ LOG_ALWAYS_FATAL_IF(input == nullptr, "%s: Invalid input image", __func__);
+
+ if (blurRadius == 0) {
+ return input;
+ }
+
// Kawase is an approximation of Gaussian, but it behaves differently from it.
// A radius transformation is required for approximating them, and also to introduce
// non-integer steps, necessary to smoothly interpolate large radii.
float tmpRadius = (float)blurRadius / 2.0f;
- float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
+ uint32_t numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
float radiusByPasses = tmpRadius / (float)numberOfPasses;
// create blur surface with the bit depth and colorspace of the original surface
@@ -81,14 +108,33 @@ sk_sp<SkImage> KawaseBlurFilter::generate(GrRecordingContext* context, const uin
input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
blurBuilder.uniform("in_blurOffset") = radiusByPasses * kInputScale;
- sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
+ constexpr int kSampleCount = 1;
+ constexpr bool kMipmapped = false;
+ constexpr SkSurfaceProps* kProps = nullptr;
+ sk_sp<SkSurface> surface = SkSurfaces::RenderTarget(context, skgpu::Budgeted::kYes, scaledInfo,
+ kSampleCount, kTopLeft_GrSurfaceOrigin,
+ kProps, kMipmapped, input->isProtected());
+ LOG_ALWAYS_FATAL_IF(!surface, "%s: Failed to create surface for blurring!", __func__);
+ sk_sp<SkImage> tmpBlur = makeImage(surface.get(), &blurBuilder);
- // And now we'll build our chain of scaled blur stages
- for (auto i = 1; i < numberOfPasses; i++) {
- blurBuilder.child("child") =
- tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
- blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
- tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
+ // And now we'll build our chain of scaled blur stages. If there is more than one pass,
+ // create a second surface and ping pong between them.
+ sk_sp<SkSurface> surfaceTwo;
+ if (numberOfPasses <= 1) {
+ LOG_ALWAYS_FATAL_IF(tmpBlur == nullptr, "%s: tmpBlur is null", __func__);
+ } else {
+ surfaceTwo = surface->makeSurface(scaledInfo);
+ LOG_ALWAYS_FATAL_IF(!surfaceTwo, "%s: Failed to create second blur surface!", __func__);
+
+ for (auto i = 1; i < numberOfPasses; i++) {
+ LOG_ALWAYS_FATAL_IF(tmpBlur == nullptr, "%s: tmpBlur is null for pass %d", __func__, i);
+ blurBuilder.child("child") =
+ tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
+ blurBuilder.uniform("in_blurOffset") = (float) i * radiusByPasses * kInputScale;
+ tmpBlur = makeImage(surfaceTwo.get(), &blurBuilder);
+ using std::swap;
+ swap(surface, surfaceTwo);
+ }
}
return tmpBlur;
diff --git a/libs/renderengine/tests/Android.bp b/libs/renderengine/tests/Android.bp
index bbab792dba..50e166d2a7 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 7c70a748b5..11d4fdebdc 100644
--- a/libs/renderengine/tests/RenderEngineTest.cpp
+++ b/libs/renderengine/tests/RenderEngineTest.cpp
@@ -37,8 +37,8 @@
#include <condition_variable>
#include <fstream>
-#include "../gl/GLESRenderEngine.h"
#include "../skia/SkiaGLRenderEngine.h"
+#include "../skia/SkiaVkRenderEngine.h"
#include "../threaded/RenderEngineThreaded.h"
constexpr int DEFAULT_DISPLAY_WIDTH = 128;
@@ -108,71 +108,40 @@ public:
virtual std::string name() = 0;
virtual renderengine::RenderEngine::RenderEngineType type() = 0;
virtual std::unique_ptr<renderengine::RenderEngine> createRenderEngine() = 0;
- virtual std::unique_ptr<renderengine::gl::GLESRenderEngine> createGLESRenderEngine() {
- return nullptr;
- }
- virtual bool useColorManagement() const = 0;
+ virtual bool typeSupported() = 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<renderengine::RenderEngine> createRenderEngine() override {
- return createGLESRenderEngine();
+ std::unique_ptr<renderengine::RenderEngine> re = createSkiaVkRenderEngine();
+ return re;
}
- std::unique_ptr<renderengine::gl::GLESRenderEngine> createGLESRenderEngine() {
+ std::unique_ptr<renderengine::skia::SkiaVkRenderEngine> createSkiaVkRenderEngine() {
renderengine::RenderEngineCreationArgs reCreationArgs =
renderengine::RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int>(ui::PixelFormat::RGBA_8888))
.setImageCacheSize(1)
- .setUseColorManagerment(false)
.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 false; }
-};
-
-class GLESCMRenderEngineFactory : public RenderEngineFactory {
-public:
- std::string name() override { return "GLESCMRenderEngineFactory"; }
-
- renderengine::RenderEngine::RenderEngineType type() {
- return renderengine::RenderEngine::RenderEngineType::GLES;
- }
-
- std::unique_ptr<renderengine::RenderEngine> createRenderEngine() override {
- return createGLESRenderEngine();
+ return renderengine::skia::SkiaVkRenderEngine::create(reCreationArgs);
}
- std::unique_ptr<renderengine::gl::GLESRenderEngine> createGLESRenderEngine() override {
- renderengine::RenderEngineCreationArgs reCreationArgs =
- renderengine::RenderEngineCreationArgs::Builder()
- .setPixelFormat(static_cast<int>(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 typeSupported() override {
+ return skia::SkiaVkRenderEngine::canSupportSkiaVkRenderEngine();
}
-
- bool useColorManagement() const override { return true; }
+ void skip() { GTEST_SKIP(); }
};
class SkiaGLESRenderEngineFactory : public RenderEngineFactory {
@@ -193,12 +162,11 @@ public:
.setSupportsBackgroundBlur(true)
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setRenderEngineType(type())
- .setUseColorManagerment(useColorManagement())
.build();
return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
}
- bool useColorManagement() const override { return false; }
+ bool typeSupported() override { return true; }
};
class SkiaGLESCMRenderEngineFactory : public RenderEngineFactory {
@@ -219,12 +187,11 @@ public:
.setSupportsBackgroundBlur(true)
.setContextPriority(renderengine::RenderEngine::ContextPriority::MEDIUM)
.setRenderEngineType(type())
- .setUseColorManagerment(useColorManagement())
.build();
return renderengine::skia::SkiaGLRenderEngine::create(reCreationArgs);
}
- bool useColorManagement() const override { return true; }
+ bool typeSupported() override { return true; }
};
class RenderEngineTest : public ::testing::TestWithParam<std::shared_ptr<RenderEngineFactory>> {
@@ -232,14 +199,14 @@ public:
std::shared_ptr<renderengine::ExternalTexture> 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<GraphicBuffer>::
+ 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 +218,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<GraphicBuffer>::
+ 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 +252,12 @@ public:
}
std::shared_ptr<renderengine::ExternalTexture> 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<uint64_t>(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_HW_TEXTURE);
+ auto buffer =
+ sp<GraphicBuffer>::make(static_cast<uint32_t>(width), static_cast<uint32_t>(height),
+ android::PIXEL_FORMAT_R_8, 1u, kUsageFlags, "r8");
if (buffer->initCheck() != 0) {
// Devices are not required to support R8.
return nullptr;
@@ -309,12 +278,6 @@ public:
if (WRITE_BUFFER_TO_FILE_ON_FAILURE && ::testing::Test::HasFailure()) {
writeBufferToFile("/data/texture_out_");
}
- 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();
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
@@ -440,11 +403,13 @@ public:
}
void expectShadowColor(const renderengine::LayerSettings& castingLayer,
- const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
+ const ShadowSettings& shadow, const ubyte4& casterColor,
const ubyte4& backgroundColor) {
const Rect casterRect(castingLayer.geometry.boundaries);
Region casterRegion = Region(casterRect);
- const float casterCornerRadius = castingLayer.geometry.roundedCornersRadius;
+ const float casterCornerRadius = (castingLayer.geometry.roundedCornersRadius.x +
+ castingLayer.geometry.roundedCornersRadius.y) /
+ 2.0;
if (casterCornerRadius > 0.0f) {
// ignore the corners if a corner radius is set
Rect cornerRect(casterCornerRadius, casterCornerRadius);
@@ -478,8 +443,7 @@ public:
backgroundColor.a);
}
- void expectShadowColorWithoutCaster(const FloatRect& casterBounds,
- const renderengine::ShadowSettings& shadow,
+ void expectShadowColorWithoutCaster(const FloatRect& casterBounds, const ShadowSettings& shadow,
const ubyte4& backgroundColor) {
const float shadowInset = shadow.length * -1.0f;
const Rect casterRect(casterBounds);
@@ -498,9 +462,9 @@ public:
backgroundColor.a);
}
- static renderengine::ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
- bool casterIsTranslucent) {
- renderengine::ShadowSettings shadow;
+ static ShadowSettings getShadowSettings(const vec2& casterPos, float shadowLength,
+ bool casterIsTranslucent) {
+ ShadowSettings shadow;
shadow.ambientColor = {0.0f, 0.0f, 0.0f, 0.039f};
shadow.spotColor = {0.0f, 0.0f, 0.0f, 0.19f};
shadow.lightPos = vec3(casterPos.x, casterPos.y, 0);
@@ -524,20 +488,15 @@ public:
void invokeDraw(const renderengine::DisplaySettings& settings,
const std::vector<renderengine::LayerSettings>& layers) {
- std::future<renderengine::RenderEngineResult> result =
- mRE->drawLayers(settings, layers, mBuffer, true, base::unique_fd());
+ ftl::Future<FenceResult> future =
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
+ ASSERT_TRUE(future.valid());
- ASSERT_TRUE(result.valid());
- auto [status, fence] = result.get();
-
- ASSERT_EQ(NO_ERROR, status);
- if (fence.ok()) {
- sync_wait(fence.get(), -1);
- }
+ auto result = future.get();
+ ASSERT_TRUE(result.ok());
- if (layers.size() > 0 && mGLESRE != nullptr) {
- ASSERT_TRUE(mGLESRE->isFramebufferImageCachedForTesting(mBuffer->getBuffer()->getId()));
- }
+ auto fence = result.value();
+ fence->waitForever(LOG_TAG);
}
void drawEmptyLayers() {
@@ -642,12 +601,10 @@ public:
void fillGreenColorBufferThenClearRegion();
template <typename SourceVariant>
- void drawShadow(const renderengine::LayerSettings& castingLayer,
- const renderengine::ShadowSettings& shadow, const ubyte4& casterColor,
- const ubyte4& backgroundColor);
+ void drawShadow(const renderengine::LayerSettings& castingLayer, const ShadowSettings& shadow,
+ const ubyte4& casterColor, const ubyte4& backgroundColor);
- void drawShadowWithoutCaster(const FloatRect& castingBounds,
- const renderengine::ShadowSettings& shadow,
+ void drawShadowWithoutCaster(const FloatRect& castingBounds, const ShadowSettings& shadow,
const ubyte4& backgroundColor);
// Tonemaps grey values from sourceDataspace -> Display P3 and checks that GPU and CPU
@@ -660,26 +617,11 @@ public:
std::unique_ptr<renderengine::RenderEngine> mRE;
std::shared_ptr<renderengine::ExternalTexture> mBuffer;
- // GLESRenderEngine for testing GLES-specific behavior.
- // Owened by mRE, but this is downcasted.
- renderengine::gl::GLESRenderEngine* mGLESRE = nullptr;
-
- std::vector<uint32_t> 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::gl::GLESRenderEngine> renderEngine =
- renderEngineFactory->createGLESRenderEngine();
- mGLESRE = renderEngine.get();
- mRE = std::move(renderEngine);
- } else {
- mRE = renderEngineFactory->createRenderEngine();
- }
+ mRE = renderEngineFactory->createRenderEngine();
mBuffer = allocateDefaultBuffer();
}
@@ -718,9 +660,6 @@ struct BufferSourceVariant {
static void fillColor(renderengine::LayerSettings& layer, half r, half g, half b,
RenderEngineTest* fixture) {
const auto buf = fixture->allocateSourceBuffer(1, 1);
- uint32_t texName;
- fixture->mRE->genTextures(1, &texName);
- fixture->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -740,7 +679,6 @@ struct BufferSourceVariant {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
OpaquenessVariant::setOpaqueBit(layer);
}
@@ -1000,9 +938,9 @@ void RenderEngineTest::fillBufferWithColorTransformAndSourceDataspace(
std::vector<renderengine::LayerSettings> 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
@@ -1028,13 +966,13 @@ void RenderEngineTest::fillBufferColorTransform() {
template <typename SourceVariant>
void RenderEngineTest::fillBufferColorTransformAndSourceDataspace() {
unordered_map<ui::Dataspace, ubyte4> 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>(
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<SourceVariant>(sourceDataspace);
expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1);
@@ -1074,13 +1012,13 @@ void RenderEngineTest::fillBufferWithColorTransformAndOutputDataspace(
template <typename SourceVariant>
void RenderEngineTest::fillBufferColorTransformAndOutputDataspace() {
unordered_map<ui::Dataspace, ubyte4> 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>(
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<SourceVariant>(outputDataspace);
expectBufferColor(fullscreenRect(), color.r, color.g, color.b, color.a, 1);
@@ -1129,7 +1067,7 @@ void RenderEngineTest::fillRedBufferWithRoundedCorners() {
renderengine::LayerSettings layer;
layer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
layer.geometry.boundaries = fullscreenRect().toFloatRect();
- layer.geometry.roundedCornersRadius = 5.0f;
+ layer.geometry.roundedCornersRadius = {5.0f, 5.0f};
layer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
SourceVariant::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
layer.alpha = 1.0f;
@@ -1289,9 +1227,6 @@ void RenderEngineTest::fillRedBufferTextureTransform() {
// Here will allocate a checker board texture, but transform texture
// coordinates so that only the upper left is applied.
const auto buf = allocateSourceBuffer(2, 2);
- uint32_t texName;
- RenderEngineTest::mRE->genTextures(1, &texName);
- this->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1312,7 +1247,6 @@ void RenderEngineTest::fillRedBufferTextureTransform() {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
// Transform coordinates to only be inside the red quadrant.
layer.source.buffer.textureTransform = mat4::scale(vec4(0.2f, 0.2f, 1.f, 1.f));
layer.alpha = 1.0f;
@@ -1338,9 +1272,6 @@ void RenderEngineTest::fillRedBufferWithPremultiplyAlpha() {
renderengine::LayerSettings layer;
const auto buf = allocateSourceBuffer(1, 1);
- uint32_t texName;
- RenderEngineTest::mRE->genTextures(1, &texName);
- this->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1352,7 +1283,6 @@ void RenderEngineTest::fillRedBufferWithPremultiplyAlpha() {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
layer.source.buffer.usePremultipliedAlpha = true;
layer.alpha = 0.5f;
layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -1377,9 +1307,6 @@ void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
renderengine::LayerSettings layer;
const auto buf = allocateSourceBuffer(1, 1);
- uint32_t texName;
- RenderEngineTest::mRE->genTextures(1, &texName);
- this->mTexNames.push_back(texName);
uint8_t* pixels;
buf->getBuffer()->lock(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN,
@@ -1391,7 +1318,6 @@ void RenderEngineTest::fillRedBufferWithoutPremultiplyAlpha() {
buf->getBuffer()->unlock();
layer.source.buffer.buffer = buf;
- layer.source.buffer.textureName = texName;
layer.source.buffer.usePremultipliedAlpha = false;
layer.alpha = 0.5f;
layer.geometry.boundaries = Rect(1, 1).toFloatRect();
@@ -1408,8 +1334,8 @@ void RenderEngineTest::fillBufferWithoutPremultiplyAlpha() {
template <typename SourceVariant>
void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLayer,
- const renderengine::ShadowSettings& shadow,
- const ubyte4& casterColor, const ubyte4& backgroundColor) {
+ const ShadowSettings& shadow, const ubyte4& casterColor,
+ const ubyte4& backgroundColor) {
renderengine::DisplaySettings settings;
settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
settings.physicalDisplay = fullscreenRect();
@@ -1445,7 +1371,7 @@ void RenderEngineTest::drawShadow(const renderengine::LayerSettings& castingLaye
}
void RenderEngineTest::drawShadowWithoutCaster(const FloatRect& castingBounds,
- const renderengine::ShadowSettings& shadow,
+ const ShadowSettings& shadow,
const ubyte4& backgroundColor) {
renderengine::DisplaySettings settings;
settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
@@ -1494,13 +1420,13 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3
auto buf = std::make_shared<
renderengine::impl::
- ExternalTexture>(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<GraphicBuffer>::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);
@@ -1527,13 +1453,13 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3
mBuffer = std::make_shared<
renderengine::impl::
- ExternalTexture>(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<GraphicBuffer>::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);
@@ -1596,17 +1522,48 @@ void RenderEngineTest::tonemap(ui::Dataspace sourceDataspace, std::function<vec3
}
INSTANTIATE_TEST_SUITE_P(PerRenderEngineType, RenderEngineTest,
- testing::Values(std::make_shared<GLESRenderEngineFactory>(),
- std::make_shared<GLESCMRenderEngineFactory>(),
- std::make_shared<SkiaGLESRenderEngineFactory>(),
- std::make_shared<SkiaGLESCMRenderEngineFactory>()));
+ testing::Values(std::make_shared<SkiaGLESRenderEngineFactory>(),
+ std::make_shared<SkiaVkRenderEngineFactory>()));
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<renderengine::LayerSettings> 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<renderengine::LayerSettings> 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;
@@ -1638,111 +1595,110 @@ TEST_P(RenderEngineTest, drawLayers_withoutBuffers_withColorTransform) {
}
TEST_P(RenderEngineTest, drawLayers_nullOutputBuffer) {
- initializeRenderEngine();
-
- renderengine::DisplaySettings settings;
- settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
- std::vector<renderengine::LayerSettings> layers;
- renderengine::LayerSettings layer;
- layer.geometry.boundaries = fullscreenRect().toFloatRect();
- BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
- layers.push_back(layer);
- std::future<renderengine::RenderEngineResult> 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<renderengine::LayerSettings> layers;
renderengine::LayerSettings layer;
layer.geometry.boundaries = fullscreenRect().toFloatRect();
BufferSourceVariant<ForceOpaqueBufferVariant>::fillColor(layer, 1.0f, 0.0f, 0.0f, this);
- layer.alpha = 1.0;
layers.push_back(layer);
+ ftl::Future<FenceResult> future = mRE->drawLayers(settings, layers, nullptr, base::unique_fd());
- std::future<renderengine::RenderEngineResult> 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<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillGreenBuffer<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBlueBuffer<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillRedTransparentBuffer<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferPhysicalOffset<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate0<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate90<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate180<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate270<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferLayerTransform<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferColorTransform<ColorSourceVariant>();
}
@@ -1750,12 +1706,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()) {
+ GTEST_SKIP();
}
initializeRenderEngine();
@@ -1765,12 +1717,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()) {
+ GTEST_SKIP();
}
initializeRenderEngine();
@@ -1778,81 +1726,129 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_outputDataspace) {
}
TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferWithRoundedCorners<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferColorTransformZeroLayerAlpha<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferAndBlurBackground<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillSmallLayerAndBlurBackground<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_overlayCorners_colorSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
overlayCorners<ColorSourceVariant>();
}
TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillRedBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillGreenBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBlueBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillRedTransparentBuffer<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferPhysicalOffset<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate0<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate90<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate180<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate270<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferLayerTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferColorTransform<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
@@ -1860,12 +1856,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()) {
+ GTEST_SKIP();
}
initializeRenderEngine();
@@ -1875,12 +1867,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()) {
+ GTEST_SKIP();
}
initializeRenderEngine();
@@ -1888,81 +1876,129 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_o
}
TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferWithRoundedCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillSmallLayerAndBlurBackground<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_overlayCorners_opaqueBufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
overlayCorners<BufferSourceVariant<ForceOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillRedBuffer_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillRedBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillGreenBuffer_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillGreenBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBlueBuffer_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBlueBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillRedTransparentBuffer_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillRedTransparentBuffer<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferPhysicalOffset_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferPhysicalOffset<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate0_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate0<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate90_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate90<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate180_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate180<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferCheckersRotate270_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferCheckersRotate270<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferLayerTransform_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferLayerTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransform_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferColorTransform<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
@@ -1970,12 +2006,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()) {
+ GTEST_SKIP();
}
initializeRenderEngine();
@@ -1985,12 +2017,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()) {
+ GTEST_SKIP();
}
initializeRenderEngine();
@@ -1998,46 +2026,73 @@ TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformAndOutputDataspace_b
}
TEST_P(RenderEngineTest, drawLayers_fillBufferRoundedCorners_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferWithRoundedCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferColorTransformZeroLayerAlpha_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferColorTransformZeroLayerAlpha<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillBufferAndBlurBackground_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillBufferAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_fillSmallLayerAndBlurBackground_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
fillSmallLayerAndBlurBackground<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
TEST_P(RenderEngineTest, drawLayers_overlayCorners_bufferSource) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
overlayCorners<BufferSourceVariant<RelaxOpaqueBufferVariant>>();
}
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<uint8_t>(255), static_cast<uint8_t>(255),
@@ -2045,15 +2100,17 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_castsWithoutCasterLayer) {
const float shadowLength = 5.0f;
Rect casterBounds(DEFAULT_DISPLAY_WIDTH / 3.0f, DEFAULT_DISPLAY_HEIGHT / 3.0f);
casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadowWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
expectShadowColorWithoutCaster(casterBounds.toFloatRect(), settings, backgroundColor);
}
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -2066,15 +2123,17 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterLayerMinSize) {
renderengine::LayerSettings castingLayer;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
}
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -2088,15 +2147,17 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterColorLayer) {
castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<ColorSourceVariant>(castingLayer, settings, casterColor, backgroundColor);
expectShadowColor(castingLayer, settings, casterColor, backgroundColor);
}
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -2110,9 +2171,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
castingLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
backgroundColor);
@@ -2120,6 +2180,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterOpaqueBufferLayer) {
}
TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
const ubyte4 casterColor(static_cast<uint8_t>(255), static_cast<uint8_t>(0),
@@ -2131,12 +2194,11 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_casterWithRoundedCorner) {
casterBounds.offsetBy(shadowLength + 1, shadowLength + 1);
renderengine::LayerSettings castingLayer;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
- castingLayer.geometry.roundedCornersRadius = 3.0f;
+ castingLayer.geometry.roundedCornersRadius = {3.0f, 3.0f};
castingLayer.geometry.roundedCornersCrop = casterBounds.toFloatRect();
castingLayer.alpha = 1.0f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- false /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, false /* casterIsTranslucent */);
drawShadow<BufferSourceVariant<ForceOpaqueBufferVariant>>(castingLayer, settings, casterColor,
backgroundColor);
@@ -2144,6 +2206,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);
@@ -2154,9 +2219,8 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
renderengine::LayerSettings castingLayer;
castingLayer.geometry.boundaries = casterBounds.toFloatRect();
castingLayer.alpha = 0.5f;
- renderengine::ShadowSettings settings =
- getShadowSettings(vec2(casterBounds.left, casterBounds.top), shadowLength,
- true /* casterIsTranslucent */);
+ ShadowSettings settings = getShadowSettings(vec2(casterBounds.left, casterBounds.top),
+ shadowLength, true /* casterIsTranslucent */);
drawShadow<BufferSourceVariant<RelaxOpaqueBufferVariant>>(castingLayer, settings, casterColor,
backgroundColor);
@@ -2171,6 +2235,9 @@ TEST_P(RenderEngineTest, drawLayers_fillShadow_translucentCasterWithAlpha) {
}
TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
renderengine::DisplaySettings settings;
@@ -2185,28 +2252,36 @@ TEST_P(RenderEngineTest, cleanupPostRender_cleansUpOnce) {
layer.alpha = 1.0;
layers.push_back(layer);
- std::future<renderengine::RenderEngineResult> resultOne =
- 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<renderengine::RenderEngineResult> 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);
- }
+ ftl::Future<FenceResult> futureOne =
+ mRE->drawLayers(settings, layers, mBuffer, base::unique_fd());
+ ASSERT_TRUE(futureOne.valid());
+ auto resultOne = futureOne.get();
+ ASSERT_TRUE(resultOne.ok());
+ auto fenceOne = resultOne.value();
+
+ ftl::Future<FenceResult> futureTwo =
+ mRE->drawLayers(settings, layers, mBuffer, 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;
@@ -2219,7 +2294,8 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) {
renderengine::LayerSettings redLayer;
redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
- redLayer.geometry.roundedCornersRadius = 5.0f;
+ redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f};
+
redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
// Red background.
redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
@@ -2231,7 +2307,7 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) {
renderengine::LayerSettings greenLayer;
greenLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
greenLayer.geometry.boundaries = fullscreenRect().toFloatRect();
- greenLayer.geometry.roundedCornersRadius = 5.0f;
+ greenLayer.geometry.roundedCornersRadius = {5.0f, 5.0f};
// Bottom right corner is not going to be rounded.
greenLayer.geometry.roundedCornersCrop =
Rect(DEFAULT_DISPLAY_WIDTH / 3, DEFAULT_DISPLAY_HEIGHT / 3, DEFAULT_DISPLAY_HEIGHT,
@@ -2256,6 +2332,9 @@ TEST_P(RenderEngineTest, testRoundedCornersCrop) {
}
TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
renderengine::DisplaySettings settings;
@@ -2268,7 +2347,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
renderengine::LayerSettings redLayer;
redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
- redLayer.geometry.roundedCornersRadius = 5.0f;
+ redLayer.geometry.roundedCornersRadius = {5.0f, 5.0f};
redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
// Red background.
redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
@@ -2301,6 +2380,9 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCrop) {
}
TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
renderengine::DisplaySettings settings;
@@ -2313,7 +2395,7 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
renderengine::LayerSettings redLayer;
redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
redLayer.geometry.boundaries = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 32);
- redLayer.geometry.roundedCornersRadius = 64;
+ redLayer.geometry.roundedCornersRadius = {64.0f, 64.0f};
redLayer.geometry.roundedCornersCrop = FloatRect(0, 0, DEFAULT_DISPLAY_WIDTH, 128);
// Red background.
redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
@@ -2334,7 +2416,53 @@ TEST_P(RenderEngineTest, testRoundedCornersParentCropSmallBounds) {
expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, 31), 255, 0, 0, 255);
}
+TEST_P(RenderEngineTest, testRoundedCornersXY) {
+ if (GetParam()->type() != renderengine::RenderEngine::RenderEngineType::SKIA_GL) {
+ GTEST_SKIP();
+ }
+
+ initializeRenderEngine();
+
+ renderengine::DisplaySettings settings;
+ settings.physicalDisplay = fullscreenRect();
+ settings.clip = fullscreenRect();
+ settings.outputDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+
+ std::vector<renderengine::LayerSettings> layers;
+
+ renderengine::LayerSettings redLayer;
+ redLayer.sourceDataspace = ui::Dataspace::V0_SRGB_LINEAR;
+ redLayer.geometry.boundaries = fullscreenRect().toFloatRect();
+ redLayer.geometry.roundedCornersRadius = {5.0f, 20.0f};
+ redLayer.geometry.roundedCornersCrop = fullscreenRect().toFloatRect();
+ // Red background.
+ redLayer.source.solidColor = half3(1.0f, 0.0f, 0.0f);
+ redLayer.alpha = 1.0f;
+
+ layers.push_back(redLayer);
+
+ invokeDraw(settings, layers);
+
+ // Due to roundedCornersRadius, the corners are untouched.
+ expectBufferColor(Point(0, 0), 0, 0, 0, 0);
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 0), 0, 0, 0, 0);
+ expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0);
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 1), 0, 0, 0, 0);
+
+ // Y-axis draws a larger radius, check that its untouched as well
+ expectBufferColor(Point(0, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0);
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, DEFAULT_DISPLAY_HEIGHT - 5), 0, 0, 0, 0);
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH - 1, 5), 0, 0, 0, 0);
+ expectBufferColor(Point(0, 5), 0, 0, 0, 0);
+
+ // middle should be red
+ expectBufferColor(Point(DEFAULT_DISPLAY_WIDTH / 2, DEFAULT_DISPLAY_HEIGHT / 2), 255, 0, 0, 255);
+}
+
TEST_P(RenderEngineTest, testClear) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
const auto rect = fullscreenRect();
@@ -2364,6 +2492,9 @@ TEST_P(RenderEngineTest, testClear) {
}
TEST_P(RenderEngineTest, testDisableBlendingBuffer) {
+ if (!GetParam()->typeSupported()) {
+ GTEST_SKIP();
+ }
initializeRenderEngine();
const auto rect = Rect(0, 0, 1, 1);
@@ -2411,13 +2542,57 @@ 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();
}
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<renderengine::LayerSettings> 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;
const auto displayRect = Rect(3, 1);
@@ -2488,7 +2663,7 @@ TEST_P(RenderEngineTest, testDimming) {
}
TEST_P(RenderEngineTest, testDimming_inGammaSpace) {
- if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+ if (!GetParam()->typeSupported()) {
GTEST_SKIP();
}
initializeRenderEngine();
@@ -2566,7 +2741,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();
@@ -2629,7 +2804,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();
@@ -2693,10 +2868,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{
@@ -2747,6 +2922,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);
@@ -2790,19 +2968,11 @@ TEST_P(RenderEngineTest, test_isOpaque) {
std::vector<renderengine::LayerSettings> layers{greenLayer};
invokeDraw(display, layers);
- if (GetParam()->useColorManagement()) {
- expectBufferColor(rect, 117, 251, 76, 255);
- } else {
- expectBufferColor(rect, 0, 255, 0, 255);
- }
+ expectBufferColor(rect, 117, 251, 76, 255);
}
TEST_P(RenderEngineTest, test_tonemapPQMatches) {
- if (!GetParam()->useColorManagement()) {
- GTEST_SKIP();
- }
-
- if (GetParam()->type() == renderengine::RenderEngine::RenderEngineType::GLES) {
+ if (!GetParam()->typeSupported()) {
GTEST_SKIP();
}
@@ -2819,11 +2989,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()) {
GTEST_SKIP();
}
@@ -2840,10 +3006,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);
@@ -2901,10 +3066,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);
@@ -2967,10 +3131,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);
@@ -3036,20 +3199,19 @@ 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();
+ auto fut = mRE->primeCache(false);
if (fut.valid()) {
fut.wait();
}
- const int minimumExpectedShadersCompiled = GetParam()->useColorManagement() ? 60 : 30;
+ static constexpr int kMinimumExpectedShadersCompiled = 60;
ASSERT_GT(static_cast<skia::SkiaGLRenderEngine*>(mRE.get())->reportShadersCompiled(),
- minimumExpectedShadersCompiled);
+ kMinimumExpectedShadersCompiled);
}
} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/tests/RenderEngineThreadedTest.cpp b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
index 96851892b4..1b9adba063 100644
--- a/libs/renderengine/tests/RenderEngineThreadedTest.cpp
+++ b/libs/renderengine/tests/RenderEngineThreadedTest.cpp
@@ -17,8 +17,10 @@
#include <cutils/properties.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <hardware/gralloc.h>
#include <renderengine/impl/ExternalTexture.h>
#include <renderengine/mock/RenderEngine.h>
+#include <ui/PixelFormat.h>
#include "../threaded/RenderEngineThreaded.h"
namespace android {
@@ -34,7 +36,7 @@ struct RenderEngineThreadedTest : public ::testing::Test {
void SetUp() override {
mThreadedRE = renderengine::threaded::RenderEngineThreaded::create(
[this]() { return std::unique_ptr<renderengine::RenderEngine>(mRenderEngine); },
- renderengine::RenderEngine::RenderEngineType::THREADED);
+ renderengine::RenderEngine::RenderEngineType::SKIA_GL_THREADED);
}
std::unique_ptr<renderengine::threaded::RenderEngineThreaded> mThreadedRE;
@@ -48,25 +50,13 @@ TEST_F(RenderEngineThreadedTest, dump) {
}
TEST_F(RenderEngineThreadedTest, primeCache) {
- EXPECT_CALL(*mRenderEngine, primeCache());
- mThreadedRE->primeCache();
+ EXPECT_CALL(*mRenderEngine, primeCache(false));
+ mThreadedRE->primeCache(false);
// need to call ANY synchronous function after primeCache to ensure that primeCache has
// completed asynchronously before the test completes execution.
mThreadedRE->getContextPriority();
}
-TEST_F(RenderEngineThreadedTest, genTextures) {
- uint32_t texName;
- EXPECT_CALL(*mRenderEngine, genTextures(1, &texName));
- mThreadedRE->genTextures(1, &texName);
-}
-
-TEST_F(RenderEngineThreadedTest, deleteTextures) {
- uint32_t texName;
- EXPECT_CALL(*mRenderEngine, deleteTextures(1, &texName));
- mThreadedRE->deleteTextures(1, &texName);
-}
-
TEST_F(RenderEngineThreadedTest, getMaxTextureSize_returns20) {
size_t size = 20;
EXPECT_CALL(*mRenderEngine, getMaxTextureSize()).WillOnce(Return(size));
@@ -95,18 +85,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,30 +97,7 @@ 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);
mThreadedRE->cleanupPostRender();
@@ -151,8 +106,25 @@ TEST_F(RenderEngineThreadedTest, PostRenderCleanup_skipped) {
}
TEST_F(RenderEngineThreadedTest, PostRenderCleanup_notSkipped) {
- EXPECT_CALL(*mRenderEngine, canSkipPostRenderCleanup()).WillOnce(Return(false));
+ renderengine::DisplaySettings settings;
+ std::vector<renderengine::LayerSettings> layers;
+ std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::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<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
EXPECT_CALL(*mRenderEngine, cleanupPostRender()).WillOnce(Return());
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
mThreadedRE->cleanupPostRender();
// call ANY synchronous function to ensure that cleanupPostRender has completed.
@@ -176,27 +148,86 @@ TEST_F(RenderEngineThreadedTest, drawLayers) {
std::vector<renderengine::LayerSettings> layers;
std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
renderengine::impl::
- ExternalTexture>(new GraphicBuffer(), *mRenderEngine,
+ ExternalTexture>(sp<GraphicBuffer>::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<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, 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<GraphicBuffer>::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<renderengine::LayerSettings> layers = {std::move(layer)};
+ std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::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<std::promise<FenceResult>>&& resultPromise,
+ const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, 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<renderengine::LayerSettings> layers;
+ auto graphicBuffer = sp<GraphicBuffer>::make();
+ graphicBuffer->usage |= GRALLOC_USAGE_PROTECTED;
+ std::shared_ptr<renderengine::ExternalTexture> 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<std::promise<renderengine::RenderEngineResult>>&&
- resultPromise,
+ .WillOnce([&](const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> void {
- resultPromise->set_value({NO_ERROR, base::unique_fd()});
- });
-
- std::future<renderengine::RenderEngineResult> result =
- mThreadedRE->drawLayers(settings, layers, buffer, false, std::move(bufferFence));
- ASSERT_TRUE(result.valid());
- auto [status, _] = result.get();
- ASSERT_EQ(NO_ERROR, status);
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) { resultPromise->set_value(Fence::NO_FENCE); });
+
+ ftl::Future<FenceResult> future =
+ mThreadedRE->drawLayers(settings, layers, buffer, std::move(bufferFence));
+ 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 203bb54701..367bee89f9 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.cpp
+++ b/libs/renderengine/threaded/RenderEngineThreaded.cpp
@@ -27,8 +27,6 @@
#include <processgroup/processgroup.h>
#include <utils/Trace.h>
-#include "gl/GLESRenderEngine.h"
-
using namespace std::chrono_literals;
namespace android {
@@ -90,7 +88,6 @@ void RenderEngineThreaded::threadMain(CreateInstanceFactory factory) NO_THREAD_S
}
mRenderEngine = factory();
- mIsProtected = mRenderEngine->isProtected();
pthread_setname_np(pthread_self(), mThreadName);
@@ -132,7 +129,7 @@ void RenderEngineThreaded::waitUntilInitialized() const {
mInitializedCondition.wait(lock, [=] { return mIsInitialized; });
}
-std::future<void> RenderEngineThreaded::primeCache() {
+std::future<void> RenderEngineThreaded::primeCache(bool shouldPrimeUltraHDR) {
const auto resultPromise = std::make_shared<std::promise<void>>();
std::future<void> resultFuture = resultPromise->get_future();
ATRACE_CALL();
@@ -140,19 +137,20 @@ std::future<void> RenderEngineThreaded::primeCache() {
// for the futures.
{
std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([resultPromise](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::primeCache");
- if (setSchedFifo(false) != NO_ERROR) {
- ALOGW("Couldn't set SCHED_OTHER for primeCache");
- }
-
- instance.primeCache();
- resultPromise->set_value();
-
- if (setSchedFifo(true) != NO_ERROR) {
- ALOGW("Couldn't set SCHED_FIFO for primeCache");
- }
- });
+ mFunctionCalls.push(
+ [resultPromise, shouldPrimeUltraHDR](renderengine::RenderEngine& instance) {
+ ATRACE_NAME("REThreaded::primeCache");
+ if (setSchedFifo(false) != NO_ERROR) {
+ ALOGW("Couldn't set SCHED_OTHER for primeCache");
+ }
+
+ instance.primeCache(shouldPrimeUltraHDR);
+ resultPromise->set_value();
+
+ if (setSchedFifo(true) != NO_ERROR) {
+ ALOGW("Couldn't set SCHED_FIFO for primeCache");
+ }
+ });
}
mCondition.notify_one();
@@ -176,46 +174,6 @@ void RenderEngineThreaded::dump(std::string& result) {
result.assign(resultFuture.get());
}
-void RenderEngineThreaded::genTextures(size_t count, uint32_t* names) {
- ATRACE_CALL();
- // This is a no-op in SkiaRenderEngine.
- if (getRenderEngineType() != RenderEngineType::THREADED) {
- return;
- }
- std::promise<void> resultPromise;
- std::future<void> resultFuture = resultPromise.get_future();
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([&resultPromise, count, names](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::genTextures");
- instance.genTextures(count, names);
- resultPromise.set_value();
- });
- }
- mCondition.notify_one();
- resultFuture.wait();
-}
-
-void RenderEngineThreaded::deleteTextures(size_t count, uint32_t const* names) {
- ATRACE_CALL();
- // This is a no-op in SkiaRenderEngine.
- if (getRenderEngineType() != RenderEngineType::THREADED) {
- return;
- }
- std::promise<void> resultPromise;
- std::future<void> resultFuture = resultPromise.get_future();
- {
- std::lock_guard lock(mThreadMutex);
- mFunctionCalls.push([&resultPromise, count, &names](renderengine::RenderEngine& instance) {
- ATRACE_NAME("REThreaded::deleteTextures");
- instance.deleteTextures(count, names);
- resultPromise.set_value();
- });
- }
- mCondition.notify_one();
- resultFuture.wait();
-}
-
void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer,
bool isRenderable) {
ATRACE_CALL();
@@ -231,16 +189,17 @@ void RenderEngineThreaded::mapExternalTextureBuffer(const sp<GraphicBuffer>& buf
mCondition.notify_one();
}
-void RenderEngineThreaded::unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) {
+void RenderEngineThreaded::unmapExternalTextureBuffer(sp<GraphicBuffer>&& 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 +214,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;
@@ -303,59 +232,45 @@ void RenderEngineThreaded::cleanupPostRender() {
ATRACE_NAME("REThreaded::cleanupPostRender");
instance.cleanupPostRender();
});
+ mNeedsPostRenderCleanup = false;
}
mCondition.notify_one();
}
bool RenderEngineThreaded::canSkipPostRenderCleanup() const {
- waitUntilInitialized();
- return mRenderEngine->canSkipPostRenderCleanup();
+ return !mNeedsPostRenderCleanup;
}
void RenderEngineThreaded::drawLayersInternal(
- const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+ const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
- resultPromise->set_value({NO_ERROR, base::unique_fd()});
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
+ resultPromise->set_value(Fence::NO_FENCE);
return;
}
-std::future<RenderEngineResult> RenderEngineThreaded::drawLayers(
+ftl::Future<FenceResult> RenderEngineThreaded::drawLayers(
const DisplaySettings& display, const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer, const bool useFramebufferCache,
- base::unique_fd&& bufferFence) {
+ const std::shared_ptr<ExternalTexture>& buffer, base::unique_fd&& bufferFence) {
ATRACE_CALL();
- const auto resultPromise = std::make_shared<std::promise<RenderEngineResult>>();
- std::future<RenderEngineResult> resultFuture = resultPromise->get_future();
+ const auto resultPromise = std::make_shared<std::promise<FenceResult>>();
+ std::future<FenceResult> 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.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
- useFramebufferCache, base::unique_fd(fd));
- });
+ mNeedsPostRenderCleanup = true;
+ mFunctionCalls.push(
+ [resultPromise, display, layers, buffer, fd](renderengine::RenderEngine& instance) {
+ ATRACE_NAME("REThreaded::drawLayers");
+ instance.updateProtectedContext(layers, buffer);
+ instance.drawLayersInternal(std::move(resultPromise), display, layers, buffer,
+ base::unique_fd(fd));
+ });
}
mCondition.notify_one();
return resultFuture;
}
-void RenderEngineThreaded::cleanFramebufferCache() {
- 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::cleanFramebufferCache");
- instance.cleanFramebufferCache();
- });
- }
- mCondition.notify_one();
-}
-
int RenderEngineThreaded::getContextPriority() {
std::promise<int> resultPromise;
std::future<int> resultFuture = resultPromise.get_future();
diff --git a/libs/renderengine/threaded/RenderEngineThreaded.h b/libs/renderengine/threaded/RenderEngineThreaded.h
index 1340902126..74af2bd776 100644
--- a/libs/renderengine/threaded/RenderEngineThreaded.h
+++ b/libs/renderengine/threaded/RenderEngineThreaded.h
@@ -42,27 +42,21 @@ public:
RenderEngineThreaded(CreateInstanceFactory factory, RenderEngineType type);
~RenderEngineThreaded() override;
- std::future<void> primeCache() override;
+ std::future<void> primeCache(bool shouldPrimeUltraHDR) override;
void dump(std::string& result) override;
- void genTextures(size_t count, uint32_t* names) override;
- void deleteTextures(size_t count, uint32_t const* names) override;
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<RenderEngineResult> drawLayers(const DisplaySettings& display,
- const std::vector<LayerSettings>& layers,
- const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache,
- base::unique_fd&& bufferFence) override;
+ ftl::Future<FenceResult> drawLayers(const DisplaySettings& display,
+ const std::vector<LayerSettings>& layers,
+ const std::shared_ptr<ExternalTexture>& buffer,
+ base::unique_fd&& bufferFence) override;
- void cleanFramebufferCache() override;
int getContextPriority() override;
bool supportsBackgroundBlur() override;
void onActiveDisplaySizeChanged(ui::Size size) override;
@@ -71,19 +65,22 @@ public:
protected:
void mapExternalTextureBuffer(const sp<GraphicBuffer>& buffer, bool isRenderable) override;
- void unmapExternalTextureBuffer(const sp<GraphicBuffer>& buffer) override;
+ void unmapExternalTextureBuffer(sp<GraphicBuffer>&& buffer) override;
bool canSkipPostRenderCleanup() const override;
- void drawLayersInternal(const std::shared_ptr<std::promise<RenderEngineResult>>&& resultPromise,
+ void drawLayersInternal(const std::shared_ptr<std::promise<FenceResult>>&& resultPromise,
const DisplaySettings& display,
const std::vector<LayerSettings>& layers,
const std::shared_ptr<ExternalTexture>& buffer,
- const bool useFramebufferCache, base::unique_fd&& bufferFence) override;
+ base::unique_fd&& bufferFence) override;
private:
void threadMain(CreateInstanceFactory factory);
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
*/
@@ -92,6 +89,7 @@ private:
mutable std::mutex mThreadMutex;
std::thread mThread GUARDED_BY(mThreadMutex);
std::atomic<bool> mRunning = true;
+ std::atomic<bool> mNeedsPostRenderCleanup = false;
using Work = std::function<void(renderengine::RenderEngine&)>;
mutable std::queue<Work> mFunctionCalls GUARDED_BY(mThreadMutex);
@@ -107,7 +105,6 @@ private:
* Render Engine
*/
std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
- std::atomic<bool> mIsProtected = false;
};
} // namespace threaded
} // namespace renderengine
diff --git a/libs/sensor/Android.bp b/libs/sensor/Android.bp
index b6b9cc4099..d992aa5105 100644
--- a/libs/sensor/Android.bp
+++ b/libs/sensor/Android.bp
@@ -49,6 +49,7 @@ cc_library {
"liblog",
"libhardware",
"libpermission",
+ "android.companion.virtual.virtualdevice_aidl-cpp",
],
export_include_dirs: ["include"],
diff --git a/libs/sensor/ISensorServer.cpp b/libs/sensor/ISensorServer.cpp
index a6cacad374..12f600e97e 100644
--- a/libs/sensor/ISensorServer.cpp
+++ b/libs/sensor/ISensorServer.cpp
@@ -22,12 +22,12 @@
#include <cutils/native_handle.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
-#include <utils/Vector.h>
#include <utils/Timers.h>
+#include <utils/Vector.h>
-#include <binder/Parcel.h>
#include <binder/IInterface.h>
#include <binder/IResultReceiver.h>
+#include <binder/Parcel.h>
#include <sensor/Sensor.h>
#include <sensor/ISensorEventConnection.h>
@@ -42,6 +42,9 @@ enum {
GET_DYNAMIC_SENSOR_LIST,
CREATE_SENSOR_DIRECT_CONNECTION,
SET_OPERATION_PARAMETER,
+ GET_RUNTIME_SENSOR_LIST,
+ ENABLE_REPLAY_DATA_INJECTION,
+ ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION,
};
class BpSensorServer : public BpInterface<ISensorServer>
@@ -63,10 +66,22 @@ public:
Sensor s;
Vector<Sensor> v;
uint32_t n = reply.readUint32();
+ // The size of the n Sensor elements on the wire is what we really want, but
+ // this is better than nothing.
+ if (n > reply.dataAvail()) {
+ ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+ "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+ n, reply.dataAvail());
+ return v;
+ }
v.setCapacity(n);
while (n) {
n--;
- reply.read(s);
+ if(reply.read(s) != OK) {
+ ALOGE("Failed to read reply from getSensorList");
+ v.clear();
+ break;
+ }
v.add(s);
}
return v;
@@ -81,6 +96,45 @@ public:
Sensor s;
Vector<Sensor> v;
uint32_t n = reply.readUint32();
+ // The size of the n Sensor elements on the wire is what we really want, but
+ // this is better than nothing.
+ if (n > reply.dataAvail()) {
+ ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+ "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+ n, reply.dataAvail());
+ return v;
+ }
+ v.setCapacity(n);
+ while (n) {
+ n--;
+ if(reply.read(s) != OK) {
+ ALOGE("Failed to read reply from getDynamicSensorList");
+ v.clear();
+ break;
+ }
+ v.add(s);
+ }
+ return v;
+ }
+
+ virtual Vector<Sensor> 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<Sensor> v;
+ uint32_t n = reply.readUint32();
+ // The size of the n Sensor elements on the wire is what we really want, but
+ // this is better than nothing.
+ if (n > reply.dataAvail()) {
+ ALOGE("Failed to get a reasonable size of the sensor list. This is likely a "
+ "malformed reply parcel. Number of elements: %d, data available in reply: %zu",
+ n, reply.dataAvail());
+ return v;
+ }
v.setCapacity(n);
while (n) {
n--;
@@ -110,11 +164,27 @@ public:
return reply.readInt32();
}
+ virtual int isReplayDataInjectionEnabled() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+ remote()->transact(ENABLE_REPLAY_DATA_INJECTION, data, &reply);
+ return reply.readInt32();
+ }
+
+ virtual int isHalBypassReplayDataInjectionEnabled() {
+ Parcel data, reply;
+ data.writeInterfaceToken(ISensorServer::getInterfaceDescriptor());
+ remote()->transact(ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION, data, &reply);
+ return reply.readInt32();
+ }
+
virtual sp<ISensorEventConnection> 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);
@@ -183,6 +253,18 @@ status_t BnSensorServer::onTransact(
reply->writeInt32(static_cast<int32_t>(ret));
return NO_ERROR;
}
+ case ENABLE_REPLAY_DATA_INJECTION: {
+ CHECK_INTERFACE(ISensorServer, data, reply);
+ int32_t ret = isReplayDataInjectionEnabled();
+ reply->writeInt32(static_cast<int32_t>(ret));
+ return NO_ERROR;
+ }
+ case ENABLE_HAL_BYPASS_REPLAY_DATA_INJECTION: {
+ CHECK_INTERFACE(ISensorServer, data, reply);
+ int32_t ret = isHalBypassReplayDataInjectionEnabled();
+ reply->writeInt32(static_cast<int32_t>(ret));
+ return NO_ERROR;
+ }
case GET_DYNAMIC_SENSOR_LIST: {
CHECK_INTERFACE(ISensorServer, data, reply);
const String16& opPackageName = data.readString16();
@@ -194,9 +276,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<Sensor> v(getRuntimeSensorList(opPackageName, deviceId));
+ size_t n = v.size();
+ reply->writeUint32(static_cast<uint32_t>(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();
@@ -205,9 +300,10 @@ status_t BnSensorServer::onTransact(
if (resource == nullptr) {
return BAD_VALUE;
}
- sp<ISensorEventConnection> ch =
- createSensorDirectConnection(opPackageName, size, type, format, resource);
- native_handle_close(resource);
+ native_handle_set_fdsan_tag(resource);
+ sp<ISensorEventConnection> 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/OWNERS b/libs/sensor/OWNERS
index 90c233030e..7347ac74e6 100644
--- a/libs/sensor/OWNERS
+++ b/libs/sensor/OWNERS
@@ -1,3 +1 @@
-arthuri@google.com
bduddie@google.com
-stange@google.com
diff --git a/libs/sensor/Sensor.cpp b/libs/sensor/Sensor.cpp
index ec0ced8663..a1549ea385 100644
--- a/libs/sensor/Sensor.cpp
+++ b/libs/sensor/Sensor.cpp
@@ -74,7 +74,7 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi
if (hwSensor.maxDelay > INT_MAX) {
// Max delay is declared as a 64 bit integer for 64 bit architectures. But it should
// always fit in a 32 bit integer, log error and cap it to INT_MAX.
- ALOGE("Sensor maxDelay overflow error %s %" PRId64, mName.string(),
+ ALOGE("Sensor maxDelay overflow error %s %" PRId64, mName.c_str(),
static_cast<int64_t>(hwSensor.maxDelay));
mMaxDelay = INT_MAX;
} else {
@@ -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;
@@ -339,7 +335,7 @@ Sensor::Sensor(struct sensor_t const& hwSensor, const uuid_t& uuid, int halVersi
if (actualReportingMode != expectedReportingMode) {
ALOGE("Reporting Mode incorrect: sensor %s handle=%#010" PRIx32 " type=%" PRId32 " "
"actual=%d expected=%d",
- mName.string(), mHandle, mType, actualReportingMode, expectedReportingMode);
+ mName.c_str(), mHandle, mType, actualReportingMode, expectedReportingMode);
}
}
@@ -617,7 +613,7 @@ void Sensor::flattenString8(void*& buffer, size_t& size,
const String8& string8) {
uint32_t len = static_cast<uint32_t>(string8.length());
FlattenableUtils::write(buffer, size, len);
- memcpy(static_cast<char*>(buffer), string8.string(), len);
+ memcpy(static_cast<char*>(buffer), string8.c_str(), len);
FlattenableUtils::advance(buffer, size, len);
size -= FlattenableUtils::align<4>(buffer);
}
@@ -631,8 +627,14 @@ bool Sensor::unflattenString8(void const*& buffer, size_t& size, String8& output
if (size < len) {
return false;
}
- outputString8.setTo(static_cast<char const*>(buffer), len);
+ outputString8 = String8(static_cast<char const*>(buffer), len);
+
+ if (size < FlattenableUtils::align<4>(len)) {
+ ALOGE("Malformed Sensor String8 field. Should be in a 4-byte aligned buffer but is not.");
+ return false;
+ }
FlattenableUtils::advance(buffer, size, FlattenableUtils::align<4>(len));
+
return true;
}
diff --git a/libs/sensor/SensorManager.cpp b/libs/sensor/SensorManager.cpp
index 0ba9704263..d112a1265c 100644
--- a/libs/sensor/SensorManager.cpp
+++ b/libs/sensor/SensorManager.cpp
@@ -26,6 +26,8 @@
#include <utils/RefBase.h>
#include <utils/Singleton.h>
+#include <android/companion/virtualnative/IVirtualDeviceManagerNative.h>
+
#include <binder/IBinder.h>
#include <binder/IPermissionController.h>
#include <binder/IServiceManager.h>
@@ -39,6 +41,43 @@
namespace android {
// ----------------------------------------------------------------------------
+namespace {
+
+using ::android::companion::virtualnative::IVirtualDeviceManagerNative;
+
+static constexpr int DEVICE_ID_DEFAULT = 0;
+
+// Returns the deviceId of the device where this uid is observed. If the uid is present on more than
+// one devices, return the default deviceId.
+int getDeviceIdForUid(uid_t uid) {
+ sp<IBinder> binder =
+ defaultServiceManager()->checkService(String16("virtualdevice_native"));
+ if (binder != nullptr) {
+ auto vdm = interface_cast<IVirtualDeviceManagerNative>(binder);
+ std::vector<int> deviceIds;
+ vdm->getDeviceIdsForUid(uid, &deviceIds);
+ // If the UID is associated with multiple virtual devices, use the default device's
+ // sensors as we cannot disambiguate here. This effectively means that the app has
+ // activities on different devices at the same time, so it must handle the device
+ // awareness by itself.
+ if (deviceIds.size() == 1) {
+ const int deviceId = deviceIds.at(0);
+ int devicePolicy = IVirtualDeviceManagerNative::DEVICE_POLICY_DEFAULT;
+ vdm->getDevicePolicy(deviceId,
+ IVirtualDeviceManagerNative::POLICY_TYPE_SENSORS,
+ &devicePolicy);
+ if (devicePolicy == IVirtualDeviceManagerNative::DEVICE_POLICY_CUSTOM) {
+ return deviceId;
+ }
+ }
+ } else {
+ ALOGW("Cannot get virtualdevice_native service");
+ }
+ return DEVICE_ID_DEFAULT;
+}
+
+} // namespace
+
Mutex SensorManager::sLock;
std::map<String16, SensorManager*> SensorManager::sPackageInstances;
@@ -53,6 +92,7 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
sensorManager = iterator->second;
} else {
String16 opPackageName = packageName;
+ const uid_t uid = IPCThreadState::self()->getCallingUid();
// It is possible that the calling code has no access to the package name.
// In this case we will get the packages for the calling UID and pick the
@@ -63,7 +103,6 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
if (opPackageName.size() <= 0) {
sp<IBinder> binder = defaultServiceManager()->getService(String16("permission"));
if (binder != nullptr) {
- const uid_t uid = IPCThreadState::self()->getCallingUid();
Vector<String16> packages;
interface_cast<IPermissionController>(binder)->getPackagesForUid(uid, packages);
if (!packages.isEmpty()) {
@@ -76,7 +115,10 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
}
}
- sensorManager = new SensorManager(opPackageName);
+ // Check if the calling UID is observed on a virtual device. If so, provide that device's
+ // sensors by default instead of the default device's sensors.
+ const int deviceId = getDeviceIdForUid(uid);
+ sensorManager = new SensorManager(opPackageName, deviceId);
// If we had no package name, we looked it up from the UID and the sensor
// manager instance we created should also be mapped to the empty package
@@ -92,8 +134,19 @@ SensorManager& SensorManager::getInstanceForPackage(const String16& packageName)
return *sensorManager;
}
-SensorManager::SensorManager(const String16& opPackageName)
- : mSensorList(nullptr), mOpPackageName(opPackageName), mDirectConnectionHandle(1) {
+void SensorManager::removeInstanceForPackage(const String16& packageName) {
+ Mutex::Autolock _l(sLock);
+ auto iterator = sPackageInstances.find(packageName);
+ if (iterator != sPackageInstances.end()) {
+ SensorManager* sensorManager = iterator->second;
+ delete sensorManager;
+ sPackageInstances.erase(iterator);
+ }
+}
+
+SensorManager::SensorManager(const String16& opPackageName, int deviceId)
+ : mSensorList(nullptr), mOpPackageName(opPackageName), mDeviceId(deviceId),
+ mDirectConnectionHandle(1) {
Mutex::Autolock _l(mLock);
assertStateLocked();
}
@@ -164,8 +217,15 @@ status_t SensorManager::assertStateLocked() {
mDeathObserver = new DeathObserver(*const_cast<SensorManager *>(this));
IInterface::asBinder(mSensorServer)->linkToDeath(mDeathObserver);
- mSensors = mSensorServer->getSensorList(mOpPackageName);
+ if (mDeviceId == DEVICE_ID_DEFAULT) {
+ mSensors = mSensorServer->getSensorList(mOpPackageName);
+ } else {
+ mSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, mDeviceId);
+ }
+
size_t count = mSensors.size();
+ // If count is 0, mSensorList will be non-null. This is old
+ // existing behavior and callers expect this.
mSensorList =
static_cast<Sensor const**>(malloc(count * sizeof(Sensor*)));
LOG_ALWAYS_FATAL_IF(mSensorList == nullptr, "mSensorList NULL");
@@ -201,6 +261,19 @@ ssize_t SensorManager::getDynamicSensorList(Vector<Sensor> & dynamicSensors) {
return static_cast<ssize_t>(count);
}
+ssize_t SensorManager::getRuntimeSensorList(int deviceId, Vector<Sensor>& runtimeSensors) {
+ Mutex::Autolock _l(mLock);
+ status_t err = assertStateLocked();
+ if (err < 0) {
+ return static_cast<ssize_t>(err);
+ }
+
+ runtimeSensors = mSensorServer->getRuntimeSensorList(mOpPackageName, deviceId);
+ size_t count = runtimeSensors.size();
+
+ return static_cast<ssize_t>(count);
+}
+
ssize_t SensorManager::getDynamicSensorList(Sensor const* const** list) {
Mutex::Autolock _l(mLock);
status_t err = assertStateLocked();
@@ -285,8 +358,30 @@ bool SensorManager::isDataInjectionEnabled() {
return false;
}
+bool SensorManager::isReplayDataInjectionEnabled() {
+ Mutex::Autolock _l(mLock);
+ if (assertStateLocked() == NO_ERROR) {
+ return mSensorServer->isReplayDataInjectionEnabled();
+ }
+ return false;
+}
+
+bool SensorManager::isHalBypassReplayDataInjectionEnabled() {
+ Mutex::Autolock _l(mLock);
+ if (assertStateLocked() == NO_ERROR) {
+ return mSensorServer->isHalBypassReplayDataInjectionEnabled();
+ }
+ return false;
+}
+
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;
@@ -299,7 +394,7 @@ int SensorManager::createDirectChannel(
}
sp<ISensorEventConnection> conn =
- mSensorServer->createSensorDirectConnection(mOpPackageName,
+ mSensorServer->createSensorDirectConnection(mOpPackageName, deviceId,
static_cast<uint32_t>(size),
static_cast<int32_t>(channelType),
SENSOR_DIRECT_FMT_SENSORS_EVENT, resourceHandle);
diff --git a/libs/sensor/include/sensor/ISensorServer.h b/libs/sensor/include/sensor/ISensorServer.h
index ce5c672da0..00bc1d68b8 100644
--- a/libs/sensor/include/sensor/ISensorServer.h
+++ b/libs/sensor/include/sensor/ISensorServer.h
@@ -43,13 +43,17 @@ public:
virtual Vector<Sensor> getSensorList(const String16& opPackageName) = 0;
virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName) = 0;
+ virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId) = 0;
virtual sp<ISensorEventConnection> createSensorEventConnection(const String8& packageName,
int mode, const String16& opPackageName, const String16& attributionTag) = 0;
virtual int32_t isDataInjectionEnabled() = 0;
+ virtual int32_t isReplayDataInjectionEnabled() = 0;
+ virtual int32_t isHalBypassReplayDataInjectionEnabled() = 0;
virtual sp<ISensorEventConnection> 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<float> &floats, const Vector<int32_t> &ints) = 0;
diff --git a/libs/sensor/include/sensor/SensorManager.h b/libs/sensor/include/sensor/SensorManager.h
index 8d0a8a45d9..e67fac7617 100644
--- a/libs/sensor/include/sensor/SensorManager.h
+++ b/libs/sensor/include/sensor/SensorManager.h
@@ -54,16 +54,22 @@ class SensorManager : public ASensorManager
{
public:
static SensorManager& getInstanceForPackage(const String16& packageName);
+ static void removeInstanceForPackage(const String16& packageName);
~SensorManager();
ssize_t getSensorList(Sensor const* const** list);
ssize_t getDynamicSensorList(Vector<Sensor>& list);
ssize_t getDynamicSensorList(Sensor const* const** list);
+ ssize_t getRuntimeSensorList(int deviceId, Vector<Sensor>& list);
Sensor const* getDefaultSensor(int type);
sp<SensorEventQueue> createEventQueue(
String8 packageName = String8(""), int mode = 0, String16 attributionTag = String16(""));
bool isDataInjectionEnabled();
+ bool isReplayDataInjectionEnabled();
+ bool isHalBypassReplayDataInjectionEnabled();
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<float> &floats, const Vector<int32_t> &ints);
@@ -73,7 +79,7 @@ private:
void sensorManagerDied();
static status_t waitForSensorService(sp<ISensorServer> *server);
- explicit SensorManager(const String16& opPackageName);
+ explicit SensorManager(const String16& opPackageName, int deviceId);
status_t assertStateLocked();
private:
@@ -88,6 +94,7 @@ private:
Vector<Sensor> mDynamicSensors;
sp<IBinder::DeathRecipient> mDeathObserver;
const String16 mOpPackageName;
+ const int mDeviceId;
std::unordered_map<int, sp<ISensorEventConnection>> mDirectConnection;
int32_t mDirectConnectionHandle;
};
diff --git a/libs/sensorprivacy/SensorPrivacyManager.cpp b/libs/sensorprivacy/SensorPrivacyManager.cpp
index 2be98e7281..57c74ee565 100644
--- a/libs/sensorprivacy/SensorPrivacyManager.cpp
+++ b/libs/sensorprivacy/SensorPrivacyManager.cpp
@@ -32,27 +32,12 @@ SensorPrivacyManager::SensorPrivacyManager()
sp<hardware::ISensorPrivacyManager> SensorPrivacyManager::getService()
{
std::lock_guard<Mutex> scoped_lock(mLock);
- int64_t startTime = 0;
sp<hardware::ISensorPrivacyManager> service = mService;
- while (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
- sp<IBinder> binder = defaultServiceManager()->checkService(String16("sensor_privacy"));
- if (binder == nullptr) {
- // Wait for the sensor privacy service to come back...
- if (startTime == 0) {
- startTime = uptimeMillis();
- ALOGI("Waiting for sensor privacy service");
- } else if ((uptimeMillis() - startTime) > 1000000) {
- ALOGW("Waiting too long for sensor privacy service, giving up");
- service = nullptr;
- break;
- }
- usleep(25000);
- } else {
- service = interface_cast<hardware::ISensorPrivacyManager>(binder);
- mService = service;
- }
+ if (service == nullptr || !IInterface::asBinder(service)->isBinderAlive()) {
+ sp<IBinder> binder = defaultServiceManager()->waitForService(String16("sensor_privacy"));
+ mService = interface_cast<hardware::ISensorPrivacyManager>(binder);
}
- return service;
+ return mService;
}
bool SensorPrivacyManager::supportsSensorToggle(int toggleType, int sensor) {
diff --git a/libs/shaders/Android.bp b/libs/shaders/Android.bp
index 847747948a..960f845488 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 2a4a370078..5a4aaab851 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<ui::Dataspace>{}(le.inputDataspace);
result = HashCombine(result, std::hash<ui::Dataspace>{}(le.outputDataspace));
result = HashCombine(result, std::hash<bool>{}(le.undoPremultipliedAlpha));
- return HashCombine(result, std::hash<ui::Dataspace>{}(le.fakeInputDataspace));
+ return HashCombine(result, std::hash<ui::Dataspace>{}(le.fakeOutputDataspace));
}
};
diff --git a/libs/shaders/shaders.cpp b/libs/shaders/shaders.cpp
index f80e93f6f8..ef039e5c36 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<aidl::android::hardware::graphics::common::Dataspace>(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"(
+ float3 OETF(float3 linear) {
+ return sign(linear) * pow(abs(linear), float3(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 <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
+std::vector<uint8_t> buildUniformValue(T value) {
+ std::vector<uint8_t> 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 <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
-std::vector<uint8_t> buildUniformValue(T value) {
- std::vector<uint8_t> 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<tonemap::ShaderUniform> buildLinearEffectUniforms(
const LinearEffect& linearEffect, const mat4& colorTransform, float maxDisplayLuminance,
@@ -470,29 +278,29 @@ std::vector<tonemap::ShaderUniform> buildLinearEffectUniforms(
aidl::android::hardware::graphics::composer3::RenderIntent renderIntent) {
std::vector<tonemap::ShaderUniform> 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>(mat4())});
- uniforms.push_back(
- {.name = "in_xyzToRgb", .value = buildUniformValue<mat4>(colorTransform)});
- } else {
- ColorSpace inputColorSpace = toColorSpace(inputDataspace);
- ColorSpace outputColorSpace = toColorSpace(linearEffect.outputDataspace);
- uniforms.push_back({.name = "in_rgbToXyz",
- .value = buildUniformValue<mat4>(mat4(inputColorSpace.getRGBtoXYZ()))});
- uniforms.push_back({.name = "in_xyzToRgb",
- .value = buildUniformValue<mat4>(
- colorTransform * mat4(outputColorSpace.getXYZtoRGB()))});
- }
+ auto inputColorSpace = toColorSpace(linearEffect.inputDataspace);
+ auto outputColorSpace = toColorSpace(linearEffect.outputDataspace);
+
+ uniforms.push_back(
+ {.name = "in_rgbToXyz",
+ .value = buildUniformValue<mat3>(ColorSpace::linearExtendedSRGB().getRGBtoXYZ())});
+ uniforms.push_back({.name = "in_xyzToSrcRgb",
+ .value = buildUniformValue<mat3>(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>(
+ 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 718d37bc38..5639d744df 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,10 +35,9 @@ 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",
+ "libbase",
],
static_libs: [
"libarect",
diff --git a/libs/shaders/tests/shaders_test.cpp b/libs/shaders/tests/shaders_test.cpp
index d45fb246c7..ba8bed23e3 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 <typename T, std::enable_if_t<std::is_trivially_copyable<T>::value, bool> = true>
std::vector<uint8_t> buildUniformValue(T value) {
std::vector<uint8_t> 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>(mat4()))));
EXPECT_THAT(uniforms,
- Contains(UniformEq("in_xyzToRgb", buildUniformValue<mat4>(colorTransform))));
+ Contains(UniformEq("in_rgbToXyz",
+ buildUniformValue<mat3>(
+ ColorSpace::linearExtendedSRGB().getRGBtoXYZ()))));
+ EXPECT_THAT(uniforms,
+ Contains(UniformEq("in_xyzToSrcRgb",
+ buildUniformValue<mat3>(
+ 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>(mat4(inputColorSpace.getRGBtoXYZ())))));
+ buildUniformValue<mat3>(
+ ColorSpace::linearExtendedSRGB().getRGBtoXYZ()))));
EXPECT_THAT(uniforms,
- Contains(UniformEq("in_xyzToRgb",
- buildUniformValue<mat4>(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>(mat4()))));
- EXPECT_THAT(uniforms, Contains(UniformEq("in_xyzToRgb", buildUniformValue<mat4>(mat4()))));
+ Contains(UniformEq("in_xyzToSrcRgb",
+ buildUniformValue<mat3>(inputColorSpace.getXYZtoRGB()))));
+ EXPECT_THAT(uniforms, Contains(UniformNameEq("in_colorTransform")));
}
} // namespace android
diff --git a/libs/tonemap/Android.bp b/libs/tonemap/Android.bp
index eca051d215..8c8815d0e4 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 0002d3a935..5c5fc6c8b3 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,9 +35,8 @@ cc_test {
"libtonemap_headers",
],
shared_libs: [
- "android.hardware.graphics.common-V4-ndk",
- "android.hardware.graphics.composer3-V1-ndk",
"libnativewindow",
+ "libbase",
],
static_libs: [
"libmath",
diff --git a/libs/ui/Android.bp b/libs/ui/Android.bp
index 98d9b94322..f84d14548f 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 {
@@ -105,9 +104,6 @@ cc_library_static {
cc_library_shared {
name: "libui",
vendor_available: true,
- vndk: {
- enabled: true,
- },
double_loadable: true,
cflags: [
@@ -127,7 +123,6 @@ cc_library_shared {
"DebugUtils.cpp",
"DeviceProductInfo.cpp",
"DisplayIdentification.cpp",
- "DisplayMode.cpp",
"DynamicDisplayInfo.cpp",
"Fence.cpp",
"FenceTime.cpp",
@@ -136,14 +131,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 +173,7 @@ cc_library_shared {
"libsync",
"libutils",
"liblog",
+ "libvndksupport",
],
export_shared_lib_headers: [
@@ -217,6 +212,8 @@ cc_library_shared {
"libnativewindow_headers",
"libhardware_headers",
"libui_headers",
+ "libimapper_stablec",
+ "libimapper_providerutils",
],
export_static_lib_headers: [
@@ -240,10 +237,6 @@ cc_library_shared {
],
afdo: true,
-
- header_abi_checker: {
- diff_flags: ["-allow-adding-removing-weak-symbols"],
- },
}
cc_library_headers {
diff --git a/libs/ui/DebugUtils.cpp b/libs/ui/DebugUtils.cpp
index 073da89758..8675f14d43 100644
--- a/libs/ui/DebugUtils.cpp
+++ b/libs/ui/DebugUtils.cpp
@@ -304,6 +304,12 @@ std::string decodePixelFormat(android::PixelFormat format) {
return std::string("BGRA_8888");
case android::PIXEL_FORMAT_R_8:
return std::string("R_8");
+ case android::PIXEL_FORMAT_R_16_UINT:
+ return std::string("R_16_UINT");
+ case android::PIXEL_FORMAT_RG_1616_UINT:
+ return std::string("RG_1616_UINT");
+ case android::PIXEL_FORMAT_RGBA_10101010:
+ return std::string("RGBA_10101010");
default:
return StringPrintf("Unknown %#08x", format);
}
diff --git a/libs/ui/DeviceProductInfo.cpp b/libs/ui/DeviceProductInfo.cpp
index 04d9d3c989..6ae27dee1d 100644
--- a/libs/ui/DeviceProductInfo.cpp
+++ b/libs/ui/DeviceProductInfo.cpp
@@ -14,74 +14,43 @@
* limitations under the License.
*/
+#include <ftl/match.h>
#include <ui/DeviceProductInfo.h>
#include <android-base/stringprintf.h>
-#include <ui/FlattenableHelpers.h>
-#include <utils/Log.h>
-
-#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<ModelYear>(&manufactureOrModelDate)) {
- StringAppendF(&result, "modelYear=%u, ", model->year);
- } else if (const auto* manufactureWeekAndYear =
- std::get_if<ManufactureWeekAndYear>(&manufactureOrModelDate)) {
- StringAppendF(&result, "manufactureWeek=%u, ", manufactureWeekAndYear->week);
- StringAppendF(&result, "manufactureYear=%d, ", manufactureWeekAndYear->year);
- } else if (const auto* manufactureYear =
- std::get_if<ManufactureYear>(&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/DisplayIdentification.cpp b/libs/ui/DisplayIdentification.cpp
index 16ed82af7c..82e5427317 100644
--- a/libs/ui/DisplayIdentification.cpp
+++ b/libs/ui/DisplayIdentification.cpp
@@ -21,6 +21,7 @@
#include <cctype>
#include <numeric>
#include <optional>
+#include <span>
#include <log/log.h>
@@ -46,6 +47,7 @@ uint64_t shiftMix(uint64_t val) {
return val ^ (val >> 47);
}
+__attribute__((no_sanitize("unsigned-integer-overflow")))
uint64_t hash64Len16(uint64_t u, uint64_t v) {
constexpr uint64_t kMul = 0x9ddfea08eb382d69;
uint64_t a = (u ^ v) * kMul;
@@ -56,6 +58,7 @@ uint64_t hash64Len16(uint64_t u, uint64_t v) {
return b;
}
+__attribute__((no_sanitize("unsigned-integer-overflow")))
uint64_t hash64Len0To16(const char* s, uint64_t len) {
constexpr uint64_t k2 = 0x9ae16a3b2f90404f;
constexpr uint64_t k3 = 0xc949d7c7509e6557;
@@ -81,7 +84,7 @@ uint64_t hash64Len0To16(const char* s, uint64_t len) {
return k2;
}
-using byte_view = std::basic_string_view<uint8_t>;
+using byte_view = std::span<const uint8_t>;
constexpr size_t kEdidBlockSize = 128;
constexpr size_t kEdidHeaderLength = 5;
@@ -89,7 +92,8 @@ constexpr size_t kEdidHeaderLength = 5;
constexpr uint16_t kVirtualEdidManufacturerId = 0xffffu;
std::optional<uint8_t> getEdidDescriptorType(const byte_view& view) {
- if (view.size() < kEdidHeaderLength || view[0] || view[1] || view[2] || view[4]) {
+ if (static_cast<size_t>(view.size()) < kEdidHeaderLength || view[0] || view[1] || view[2] ||
+ view[4]) {
return {};
}
@@ -164,7 +168,7 @@ Cea861ExtensionBlock parseCea861Block(const byte_view& block) {
constexpr size_t kDataBlockHeaderSize = 1;
const size_t dataBlockSize = bodyLength + kDataBlockHeaderSize;
- if (block.size() < dataBlockOffset + dataBlockSize) {
+ if (static_cast<size_t>(block.size()) < dataBlockOffset + dataBlockSize) {
ALOGW("Invalid EDID: CEA 861 data block is truncated.");
break;
}
@@ -264,7 +268,7 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
}
byte_view view(edid.data(), edid.size());
- view.remove_prefix(kDescriptorOffset);
+ view = view.subspan(kDescriptorOffset);
std::string_view displayName;
std::string_view serialNumber;
@@ -274,13 +278,13 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
constexpr size_t kDescriptorLength = 18;
for (size_t i = 0; i < kDescriptorCount; i++) {
- if (view.size() < kDescriptorLength) {
+ if (static_cast<size_t>(view.size()) < kDescriptorLength) {
break;
}
if (const auto type = getEdidDescriptorType(view)) {
byte_view descriptor(view.data(), kDescriptorLength);
- descriptor.remove_prefix(kEdidHeaderLength);
+ descriptor = descriptor.subspan(kEdidHeaderLength);
switch (*type) {
case 0xfc:
@@ -295,7 +299,7 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
}
}
- view.remove_prefix(kDescriptorLength);
+ view = view.subspan(kDescriptorLength);
}
std::string_view modelString = displayName;
@@ -327,8 +331,8 @@ std::optional<Edid> parseEdid(const DisplayIdentificationData& edid) {
const size_t numExtensions = edid[kNumExtensionsOffset];
view = byte_view(edid.data(), edid.size());
for (size_t blockNumber = 1; blockNumber <= numExtensions; blockNumber++) {
- view.remove_prefix(kEdidBlockSize);
- if (view.size() < kEdidBlockSize) {
+ view = view.subspan(kEdidBlockSize);
+ if (static_cast<size_t>(view.size()) < kEdidBlockSize) {
ALOGW("Invalid EDID: block %zu is truncated.", blockNumber);
break;
}
@@ -399,4 +403,4 @@ uint64_t cityHash64Len0To16(std::string_view sv) {
return hash64Len0To16(sv.data(), len);
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/libs/ui/DisplayMode.cpp b/libs/ui/DisplayMode.cpp
deleted file mode 100644
index cf05dbfb05..0000000000
--- 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 <ui/DisplayMode.h>
-
-#include <cstdint>
-
-#include <ui/FlattenableHelpers.h>
-
-#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 78ba9965b6..f5feea925d 100644
--- a/libs/ui/DynamicDisplayInfo.cpp
+++ b/libs/ui/DynamicDisplayInfo.cpp
@@ -18,11 +18,6 @@
#include <cstdint>
-#include <ui/FlattenableHelpers.h>
-
-#define RETURN_IF_ERROR(op) \
- if (const status_t status = (op); status != OK) return status;
-
namespace android::ui {
std::optional<ui::DisplayMode> DynamicDisplayInfo::getActiveDisplayMode() const {
@@ -34,42 +29,4 @@ std::optional<ui::DisplayMode> 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/Fence.cpp b/libs/ui/Fence.cpp
index cc96f83578..4be0a3a9d9 100644
--- a/libs/ui/Fence.cpp
+++ b/libs/ui/Fence.cpp
@@ -115,7 +115,7 @@ sp<Fence> Fence::merge(const char* name, const sp<Fence>& f1,
sp<Fence> Fence::merge(const String8& name, const sp<Fence>& f1,
const sp<Fence>& f2) {
- return merge(name.string(), f1, f2);
+ return merge(name.c_str(), f1, f2);
}
int Fence::dup() const {
diff --git a/libs/ui/FenceTime.cpp b/libs/ui/FenceTime.cpp
index 538c1d2a42..4246c40f64 100644
--- a/libs/ui/FenceTime.cpp
+++ b/libs/ui/FenceTime.cpp
@@ -363,9 +363,9 @@ void FenceToFenceTimeMap::signalAllForTest(
}
void FenceToFenceTimeMap::garbageCollectLocked() {
- for (auto& it : mMap) {
+ for (auto it = mMap.begin(); it != mMap.end();) {
// Erase all expired weak pointers from the vector.
- auto& vect = it.second;
+ auto& vect = it->second;
vect.erase(
std::remove_if(vect.begin(), vect.end(),
[](const std::weak_ptr<FenceTime>& ft) {
@@ -375,7 +375,9 @@ void FenceToFenceTimeMap::garbageCollectLocked() {
// Also erase the map entry if the vector is now empty.
if (vect.empty()) {
- mMap.erase(it.first);
+ it = mMap.erase(it);
+ } else {
+ it++;
}
}
}
diff --git a/libs/ui/Gralloc2.cpp b/libs/ui/Gralloc2.cpp
index f23f10a1a9..e9b5decee8 100644
--- a/libs/ui/Gralloc2.cpp
+++ b/libs/ui/Gralloc2.cpp
@@ -161,7 +161,7 @@ status_t Gralloc2Mapper::createDescriptor(void* bufferDescriptorInfo,
return static_cast<status_t>((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 15c60bcadf..474d381dbb 100644
--- a/libs/ui/Gralloc3.cpp
+++ b/libs/ui/Gralloc3.cpp
@@ -138,7 +138,7 @@ status_t Gralloc3Mapper::createDescriptor(void* bufferDescriptorInfo,
return static_cast<status_t>((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 53372c9866..03ff58a76c 100644
--- a/libs/ui/Gralloc4.cpp
+++ b/libs/ui/Gralloc4.cpp
@@ -198,7 +198,7 @@ status_t Gralloc4Mapper::createDescriptor(void* bufferDescriptorInfo,
return static_cast<status_t>((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) {
@@ -468,8 +468,8 @@ status_t Gralloc4Mapper::isSupported(uint32_t width, uint32_t height, PixelForma
uint32_t layerCount, uint64_t usage,
bool* outSupported) const {
IMapper::BufferDescriptorInfo descriptorInfo;
- if (auto error = sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
- &descriptorInfo) != OK) {
+ if (sBufferDescriptorInfo("isSupported", width, height, format, layerCount, usage,
+ &descriptorInfo) != OK) {
// Usage isn't known to the HAL or otherwise failed validation.
*outSupported = false;
return OK;
@@ -766,162 +766,6 @@ status_t Gralloc4Mapper::setSmpte2094_10(buffer_handle_t bufferHandle,
gralloc4::encodeSmpte2094_10);
}
-template <class T>
-status_t Gralloc4Mapper::getDefault(uint32_t width, uint32_t height, PixelFormat format,
- uint32_t layerCount, uint64_t usage,
- const MetadataType& metadataType,
- DecodeFunction<T> 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<uint8_t> vec;
- Error error;
- auto ret = mMapper->getFromBufferDescriptorInfo(descriptorInfo, metadataType,
- [&](const auto& tmpError,
- const hidl_vec<uint8_t>& 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<status_t>(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<ui::PlaneLayout>* outPlaneLayouts) const {
- return getDefault(width, height, format, layerCount, usage, gralloc4::MetadataType_PlaneLayouts,
- gralloc4::decodePlaneLayouts, outPlaneLayouts);
-}
-
std::vector<MetadataTypeDescription> Gralloc4Mapper::listSupportedMetadataTypes() const {
hidl_vec<MetadataTypeDescription> 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 0000000000..303043a35a
--- /dev/null
+++ b/libs/ui/Gralloc5.cpp
@@ -0,0 +1,1038 @@
+/*
+ * 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 <ui/Gralloc5.h>
+
+#include <aidlcommonsupport/NativeHandle.h>
+#include <android/binder_manager.h>
+#include <android/hardware/graphics/mapper/utils/IMapperMetadataTypes.h>
+#include <android/llndk-versioning.h>
+#include <binder/IPCThreadState.h>
+#include <dlfcn.h>
+#include <ui/FatVector.h>
+#include <vndksupport/linker.h>
+
+using namespace aidl::android::hardware::graphics::allocator;
+using namespace aidl::android::hardware::graphics::common;
+using namespace ::android::hardware::graphics::mapper;
+
+using ADataspace = aidl::android::hardware::graphics::common::Dataspace;
+using APixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+
+namespace android {
+
+static const auto kIAllocatorServiceName = IAllocator::descriptor + std::string("/default");
+static const auto kIAllocatorMinimumVersion = 2;
+constexpr const char* kStandardMetadataName =
+ "android.hardware.graphics.common.StandardMetadataType";
+
+// TODO(b/72323293, b/72703005): Remove these invalid bits from callers
+static constexpr uint64_t kRemovedUsageBits = static_cast<uint64_t>((1 << 10) | (1 << 13));
+
+typedef AIMapper_Error (*AIMapper_loadIMapperFn)(AIMapper *_Nullable *_Nonnull outImplementation);
+
+struct Gralloc5 {
+ std::shared_ptr<IAllocator> allocator;
+ AIMapper *mapper = nullptr;
+};
+
+static std::shared_ptr<IAllocator> 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;
+ }
+
+ void* so = nullptr;
+ // TODO(b/322384429) switch this to __ANDROID_API_V__ when V is finalized
+ if API_LEVEL_AT_LEAST(__ANDROID_API_FUTURE__, 202404) {
+ so = AServiceManager_openDeclaredPassthroughHal("mapper", mapperSuffix.c_str(),
+ RTLD_LOCAL | RTLD_NOW);
+ } else {
+ std::string lib_name = "mapper." + mapperSuffix + ".so";
+ so = android_load_sphal_library(lib_name.c_str(), RTLD_LOCAL | RTLD_NOW);
+ }
+ if (!so) {
+ ALOGE("Failed to load mapper.%s.so", mapperSuffix.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 <StandardMetadataType T>
+static auto getStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle)
+ -> decltype(StandardMetadata<T>::value::decode(nullptr, 0)) {
+ using Value = typename StandardMetadata<T>::value;
+ // TODO: Tune for common-case better
+ FatVector<uint8_t, 128> buffer;
+ int32_t sizeRequired = mapper->v5.getStandardMetadata(bufferHandle, static_cast<int64_t>(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<int64_t>(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 <StandardMetadataType T>
+static AIMapper_Error setStandardMetadata(AIMapper *mapper, buffer_handle_t bufferHandle,
+ const typename StandardMetadata<T>::value_type &value) {
+ using Value = typename StandardMetadata<T>::value;
+ int32_t sizeRequired = Value::encode(value, nullptr, 0);
+ if (sizeRequired < 0) {
+ ALOGW("Failed to calculate required size");
+ return static_cast<AIMapper_Error>(-sizeRequired);
+ }
+ FatVector<uint8_t, 128> 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<AIMapper_Error>(-sizeRequired);
+ }
+ return mapper->v5.setStandardMetadata(bufferHandle, static_cast<int64_t>(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<BufferUsage>{}) {
+ bits |= static_cast<int64_t>(bit);
+ }
+ return bits;
+ }();
+ return validUsageBits | kRemovedUsageBits;
+}
+
+static std::optional<BufferDescriptorInfo> 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<int32_t>(width),
+ .height = static_cast<int32_t>(height),
+ .layerCount = static_cast<int32_t>(layerCount),
+ .format = static_cast<::aidl::android::hardware::graphics::common::PixelFormat>(format),
+ .usage = static_cast<BufferUsage>(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<native_handle_t *>(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;
+}
+
+static bool isStandardMetadata(AIMapper_MetadataType metadataType) {
+ return strcmp(kStandardMetadataName, metadataType.name) == 0;
+}
+
+struct DumpBufferResult {
+ uint64_t bufferId;
+ std::string name;
+ uint64_t width;
+ uint64_t height;
+ uint64_t layerCount;
+ APixelFormat pixelFormatRequested;
+ uint32_t pixelFormatFourCC;
+ uint64_t pixelFormatModifier;
+ BufferUsage usage;
+ ADataspace dataspace;
+ uint64_t allocationSize;
+ uint64_t protectedContent;
+ ExtendableType compression;
+ ExtendableType interlaced;
+ ExtendableType chromaSiting;
+ std::vector<ui::PlaneLayout> planeLayouts;
+};
+
+#define DECODE_TO(name, output) \
+ case StandardMetadataType::name: \
+ output = StandardMetadata<StandardMetadataType::name>::value ::decode(value, valueSize) \
+ .value(); \
+ break
+
+static void dumpBufferCommon(DumpBufferResult* outResult, AIMapper_MetadataType metadataType,
+ const void* value, size_t valueSize) {
+ if (!isStandardMetadata(metadataType)) {
+ return;
+ }
+ StandardMetadataType type = (StandardMetadataType)metadataType.value;
+ switch (type) {
+ DECODE_TO(BUFFER_ID, outResult->bufferId);
+ DECODE_TO(NAME, outResult->name);
+ DECODE_TO(WIDTH, outResult->width);
+ DECODE_TO(HEIGHT, outResult->height);
+ DECODE_TO(LAYER_COUNT, outResult->layerCount);
+ DECODE_TO(PIXEL_FORMAT_REQUESTED, outResult->pixelFormatRequested);
+ DECODE_TO(PIXEL_FORMAT_FOURCC, outResult->pixelFormatFourCC);
+ DECODE_TO(PIXEL_FORMAT_MODIFIER, outResult->pixelFormatModifier);
+ DECODE_TO(USAGE, outResult->usage);
+ DECODE_TO(DATASPACE, outResult->dataspace);
+ DECODE_TO(ALLOCATION_SIZE, outResult->allocationSize);
+ DECODE_TO(PROTECTED_CONTENT, outResult->protectedContent);
+ DECODE_TO(COMPRESSION, outResult->compression);
+ DECODE_TO(INTERLACED, outResult->interlaced);
+ DECODE_TO(CHROMA_SITING, outResult->chromaSiting);
+ DECODE_TO(PLANE_LAYOUTS, outResult->planeLayouts);
+ default:
+ break;
+ }
+}
+
+#undef DECODE_TO
+
+template <typename EnumT, typename = std::enable_if_t<std::is_enum<EnumT>{}>>
+constexpr std::underlying_type_t<EnumT> to_underlying(EnumT e) noexcept {
+ return static_cast<std::underlying_type_t<EnumT>>(e);
+}
+
+static void writeDumpToStream(const DumpBufferResult& bufferDump, std::ostream& outDump,
+ bool less) {
+ double allocationSizeKiB = static_cast<double>(bufferDump.allocationSize) / 1024;
+
+ outDump << "+ name:" << bufferDump.name << ", id:" << bufferDump.bufferId
+ << ", size:" << std::fixed << allocationSizeKiB << "KiB, w/h:" << bufferDump.width
+ << "x" << bufferDump.height << ", usage: 0x" << std::hex
+ << to_underlying(bufferDump.usage) << std::dec
+ << ", req fmt:" << to_underlying(bufferDump.pixelFormatRequested)
+ << ", fourcc/mod:" << bufferDump.pixelFormatFourCC << "/"
+ << bufferDump.pixelFormatModifier << ", dataspace: 0x" << std::hex
+ << to_underlying(bufferDump.dataspace) << std::dec << ", compressed: ";
+
+ if (less) {
+ bool isCompressed = !gralloc4::isStandardCompression(bufferDump.compression) ||
+ (gralloc4::getStandardCompressionValue(bufferDump.compression) !=
+ ui::Compression::NONE);
+ outDump << std::boolalpha << isCompressed << "\n";
+ } else {
+ outDump << gralloc4::getCompressionName(bufferDump.compression) << "\n";
+ }
+
+ if (!less) {
+ bool firstPlane = true;
+ for (const auto& planeLayout : bufferDump.planeLayouts) {
+ if (firstPlane) {
+ firstPlane = false;
+ outDump << "\tplanes: ";
+ } else {
+ outDump << "\t ";
+ }
+
+ for (size_t i = 0; i < planeLayout.components.size(); i++) {
+ const auto& planeLayoutComponent = planeLayout.components[i];
+ outDump << gralloc4::getPlaneLayoutComponentTypeName(planeLayoutComponent.type);
+ if (i < planeLayout.components.size() - 1) {
+ outDump << "/";
+ } else {
+ outDump << ":\t";
+ }
+ }
+ outDump << " w/h:" << planeLayout.widthInSamples << "x" << planeLayout.heightInSamples
+ << ", stride:" << planeLayout.strideInBytes
+ << " bytes, size:" << planeLayout.totalSizeInBytes;
+ outDump << ", inc:" << planeLayout.sampleIncrementInBits
+ << " bits, subsampling w/h:" << planeLayout.horizontalSubsampling << "x"
+ << planeLayout.verticalSubsampling;
+ outDump << "\n";
+ }
+
+ outDump << "\tlayer cnt: " << bufferDump.layerCount
+ << ", protected content: " << bufferDump.protectedContent
+ << ", interlaced: " << gralloc4::getInterlacedName(bufferDump.interlaced)
+ << ", chroma siting:" << gralloc4::getChromaSitingName(bufferDump.chromaSiting)
+ << "\n";
+ }
+}
+
+std::string Gralloc5Mapper::dumpBuffer(buffer_handle_t bufferHandle, bool less) const {
+ DumpBufferResult bufferInfo;
+ AIMapper_DumpBufferCallback dumpBuffer = [](void* contextPtr,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull value, size_t valueSize) {
+ DumpBufferResult* context = reinterpret_cast<DumpBufferResult*>(contextPtr);
+ dumpBufferCommon(context, metadataType, value, valueSize);
+ };
+ AIMapper_Error error = mMapper->v5.dumpBuffer(bufferHandle, dumpBuffer, &bufferInfo);
+ if (error != AIMAPPER_ERROR_NONE) {
+ ALOGE("Error dumping buffer: %d", error);
+ return std::string{};
+ }
+ std::ostringstream stream;
+ stream.precision(2);
+ writeDumpToStream(bufferInfo, stream, less);
+ return stream.str();
+}
+
+std::string Gralloc5Mapper::dumpBuffers(bool less) const {
+ class DumpAllBuffersContext {
+ private:
+ bool mHasPending = false;
+ DumpBufferResult mPending;
+ std::vector<DumpBufferResult> mResults;
+
+ public:
+ DumpAllBuffersContext() { mResults.reserve(10); }
+
+ void commit() {
+ if (mHasPending) {
+ mResults.push_back(mPending);
+ mHasPending = false;
+ }
+ }
+
+ DumpBufferResult* write() {
+ mHasPending = true;
+ return &mPending;
+ }
+
+ const std::vector<DumpBufferResult>& results() {
+ commit();
+ return mResults;
+ }
+ } context;
+
+ AIMapper_BeginDumpBufferCallback beginCallback = [](void* contextPtr) {
+ DumpAllBuffersContext* context = reinterpret_cast<DumpAllBuffersContext*>(contextPtr);
+ context->commit();
+ };
+
+ AIMapper_DumpBufferCallback dumpBuffer = [](void* contextPtr,
+ AIMapper_MetadataType metadataType,
+ const void* _Nonnull value, size_t valueSize) {
+ DumpAllBuffersContext* context = reinterpret_cast<DumpAllBuffersContext*>(contextPtr);
+ dumpBufferCommon(context->write(), metadataType, value, valueSize);
+ };
+
+ AIMapper_Error error = mMapper->v5.dumpAllBuffers(beginCallback, dumpBuffer, &context);
+ if (error != AIMAPPER_ERROR_NONE) {
+ ALOGE("Error dumping buffers: %d", error);
+ return std::string{};
+ }
+ uint64_t totalAllocationSize = 0;
+ std::ostringstream stream;
+ stream.precision(2);
+ stream << "Imported gralloc buffers:\n";
+
+ for (const auto& bufferDump : context.results()) {
+ writeDumpToStream(bufferDump, stream, less);
+ totalAllocationSize += bufferDump.allocationSize;
+ }
+
+ double totalAllocationSizeKiB = static_cast<double>(totalAllocationSize) / 1024;
+ stream << "Total imported by gralloc: " << totalAllocationSizeKiB << "KiB\n";
+ return stream.str();
+}
+
+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<StandardMetadataType::WIDTH>(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<StandardMetadataType::HEIGHT>(mMapper, bufferHandle);
+ if (height != value) {
+ ALOGW("Height didn't match, expected %d got %" PRId64, height, value.value_or(-1));
+ return BAD_VALUE;
+ }
+ }
+ {
+ auto expected = static_cast<APixelFormat>(format);
+ if (expected != APixelFormat::IMPLEMENTATION_DEFINED) {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+ bufferHandle);
+ if (expected != value) {
+ ALOGW("Format didn't match, expected %d got %s", format,
+ value.has_value() ? toString(*value).c_str() : "<null>");
+ return BAD_VALUE;
+ }
+ }
+ }
+ {
+ auto value = getStandardMetadata<StandardMetadataType::LAYER_COUNT>(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<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+ // if (static_cast<BufferUsage>(usage) != value) {
+ // ALOGW("Usage didn't match, expected %" PRIu64 " got %" PRId64, usage,
+ // static_cast<int64_t>(value.value_or(BufferUsage::CPU_READ_NEVER)));
+ // return BAD_VALUE;
+ // }
+ // }
+ {
+ auto value = getStandardMetadata<StandardMetadataType::STRIDE>(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<ui::PlaneLayout> 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_t>(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<ui::PlaneLayout> 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<uint8_t *>(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<PlaneLayoutComponentType>(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<int64_t>(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<StandardMetadataType::BUFFER_ID>(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<StandardMetadataType::NAME>(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<StandardMetadataType::WIDTH>(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<StandardMetadataType::HEIGHT>(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<StandardMetadataType::LAYER_COUNT>(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<StandardMetadataType::PIXEL_FORMAT_REQUESTED>(mMapper,
+ bufferHandle);
+ if (value.has_value()) {
+ *outPixelFormatRequested = static_cast<ui::PixelFormat>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getPixelFormatFourCC(buffer_handle_t bufferHandle,
+ uint32_t *outPixelFormatFourCC) const {
+ auto value =
+ getStandardMetadata<StandardMetadataType::PIXEL_FORMAT_FOURCC>(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<StandardMetadataType::PIXEL_FORMAT_MODIFIER>(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<StandardMetadataType::USAGE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outUsage = static_cast<uint64_t>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getAllocationSize(buffer_handle_t bufferHandle,
+ uint64_t *outAllocationSize) const {
+ auto value = getStandardMetadata<StandardMetadataType::ALLOCATION_SIZE>(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<StandardMetadataType::PROTECTED_CONTENT>(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<StandardMetadataType::COMPRESSION>(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<StandardMetadataType::COMPRESSION>(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<StandardMetadataType::INTERLACED>(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<StandardMetadataType::CHROMA_SITING>(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<ui::PlaneLayout> *outPlaneLayouts) const {
+ auto value = getStandardMetadata<StandardMetadataType::PLANE_LAYOUTS>(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<StandardMetadataType::DATASPACE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outDataspace = static_cast<ui::Dataspace>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setDataspace(buffer_handle_t bufferHandle, ui::Dataspace dataspace) const {
+ return setStandardMetadata<StandardMetadataType::DATASPACE>(mMapper, bufferHandle,
+ static_cast<Dataspace>(dataspace));
+}
+
+status_t Gralloc5Mapper::getBlendMode(buffer_handle_t bufferHandle,
+ ui::BlendMode *outBlendMode) const {
+ auto value = getStandardMetadata<StandardMetadataType::BLEND_MODE>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outBlendMode = static_cast<ui::BlendMode>(*value);
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::getSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> *outSmpte2086) const {
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2086>(mMapper, bufferHandle);
+ if (value.has_value()) {
+ *outSmpte2086 = *value;
+ return OK;
+ }
+ return UNKNOWN_TRANSACTION;
+}
+
+status_t Gralloc5Mapper::setSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> smpte2086) const {
+ return setStandardMetadata<StandardMetadataType::SMPTE2086>(mMapper, bufferHandle, smpte2086);
+}
+
+status_t Gralloc5Mapper::getCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> *outCta861_3) const {
+ auto value = getStandardMetadata<StandardMetadataType::CTA861_3>(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<ui::Cta861_3> cta861_3) const {
+ return setStandardMetadata<StandardMetadataType::CTA861_3>(mMapper, bufferHandle, cta861_3);
+}
+
+status_t Gralloc5Mapper::getSmpte2094_40(
+ buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>> *outSmpte2094_40) const {
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_40>(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<std::vector<uint8_t>> smpte2094_40) const {
+ return setStandardMetadata<StandardMetadataType::SMPTE2094_40>(mMapper, bufferHandle,
+ smpte2094_40);
+}
+
+status_t Gralloc5Mapper::getSmpte2094_10(
+ buffer_handle_t bufferHandle, std::optional<std::vector<uint8_t>> *outSmpte2094_10) const {
+ auto value = getStandardMetadata<StandardMetadataType::SMPTE2094_10>(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<std::vector<uint8_t>> smpte2094_10) const {
+ return setStandardMetadata<StandardMetadataType::SMPTE2094_10>(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 3f958ba68f..eb0bd4ed0a 100644
--- a/libs/ui/GraphicBufferAllocator.cpp
+++ b/libs/ui/GraphicBufferAllocator.cpp
@@ -34,6 +34,7 @@
#include <ui/Gralloc2.h>
#include <ui/Gralloc3.h>
#include <ui/Gralloc4.h>
+#include <ui/Gralloc5.h>
#include <ui/GraphicBufferMapper.h>
namespace android {
@@ -48,23 +49,27 @@ KeyedVector<buffer_handle_t,
GraphicBufferAllocator::alloc_rec_t> GraphicBufferAllocator::sAllocList;
GraphicBufferAllocator::GraphicBufferAllocator() : mMapper(GraphicBufferMapper::getInstance()) {
- mAllocator = std::make_unique<const Gralloc4Allocator>(
- reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
- if (mAllocator->isLoaded()) {
- return;
+ switch (mMapper.getMapperVersion()) {
+ case GraphicBufferMapper::GRALLOC_5:
+ mAllocator = std::make_unique<const Gralloc5Allocator>(
+ reinterpret_cast<const Gralloc5Mapper&>(mMapper.getGrallocMapper()));
+ break;
+ case GraphicBufferMapper::GRALLOC_4:
+ mAllocator = std::make_unique<const Gralloc4Allocator>(
+ reinterpret_cast<const Gralloc4Mapper&>(mMapper.getGrallocMapper()));
+ break;
+ case GraphicBufferMapper::GRALLOC_3:
+ mAllocator = std::make_unique<const Gralloc3Allocator>(
+ reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
+ break;
+ case GraphicBufferMapper::GRALLOC_2:
+ mAllocator = std::make_unique<const Gralloc2Allocator>(
+ reinterpret_cast<const Gralloc2Mapper&>(mMapper.getGrallocMapper()));
+ break;
}
- mAllocator = std::make_unique<const Gralloc3Allocator>(
- reinterpret_cast<const Gralloc3Mapper&>(mMapper.getGrallocMapper()));
- if (mAllocator->isLoaded()) {
- return;
- }
- mAllocator = std::make_unique<const Gralloc2Allocator>(
- reinterpret_cast<const Gralloc2Mapper&>(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() {}
@@ -84,14 +89,14 @@ void GraphicBufferAllocator::dump(std::string& result, bool less) const {
uint64_t total = 0;
result.append("GraphicBufferAllocator buffers:\n");
const size_t count = list.size();
- StringAppendF(&result, "%10s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
+ StringAppendF(&result, "%14s | %11s | %18s | %s | %8s | %10s | %s\n", "Handle", "Size",
"W (Stride) x H", "Layers", "Format", "Usage", "Requestor");
for (size_t i = 0; i < count; i++) {
const alloc_rec_t& rec(list.valueAt(i));
std::string sizeStr = (rec.size)
? base::StringPrintf("%7.2f KiB", static_cast<double>(rec.size) / 1024.0)
: "unknown";
- StringAppendF(&result, "%10p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
+ StringAppendF(&result, "%14p | %11s | %4u (%4u) x %4u | %6u | %8X | 0x%8" PRIx64 " | %s\n",
list.keyAt(i), sizeStr.c_str(), rec.width, rec.stride, rec.height,
rec.layerCount, rec.format, rec.usage, rec.requestorName.c_str());
total += rec.size;
diff --git a/libs/ui/GraphicBufferMapper.cpp b/libs/ui/GraphicBufferMapper.cpp
index f5824233e9..7086e0470c 100644
--- a/libs/ui/GraphicBufferMapper.cpp
+++ b/libs/ui/GraphicBufferMapper.cpp
@@ -36,6 +36,7 @@
#include <ui/Gralloc2.h>
#include <ui/Gralloc3.h>
#include <ui/Gralloc4.h>
+#include <ui/Gralloc5.h>
#include <ui/GraphicBuffer.h>
#include <system/graphics.h>
@@ -49,9 +50,15 @@ void GraphicBufferMapper::preloadHal() {
Gralloc2Mapper::preload();
Gralloc3Mapper::preload();
Gralloc4Mapper::preload();
+ Gralloc5Mapper::preload();
}
GraphicBufferMapper::GraphicBufferMapper() {
+ mMapper = std::make_unique<const Gralloc5Mapper>();
+ if (mMapper->isLoaded()) {
+ mMapperVersion = Version::GRALLOC_5;
+ return;
+ }
mMapper = std::make_unique<const Gralloc4Mapper>();
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<ui::PlaneLayout>* 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 aec2fac780..0000000000
--- 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 <ui/HdrCapabilities.h>
-
-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<int32_t*>(buffer);
- reinterpret_cast<float&>(buf[0]) = mMaxLuminance;
- reinterpret_cast<float&>(buf[1]) = mMaxAverageLuminance;
- reinterpret_cast<float&>(buf[2]) = mMinLuminance;
- buf[3] = static_cast<int32_t>(mSupportedHdrTypes.size());
- for (size_t i = 0, c = mSupportedHdrTypes.size(); i < c; ++i) {
- buf[4 + i] = static_cast<int32_t>(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<int32_t const *>(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<float const&>(buf[0]);
- mMaxAverageLuminance = reinterpret_cast<float const&>(buf[1]);
- mMinLuminance = reinterpret_cast<float const&>(buf[2]);
- if (itemCount) {
- mSupportedHdrTypes.resize(itemCount);
- for (size_t i = 0; i < itemCount; ++i) {
- mSupportedHdrTypes[i] = static_cast<ui::Hdr>(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 78e82dab39..c9663edd7a 100644
--- a/libs/ui/PublicFormat.cpp
+++ b/libs/ui/PublicFormat.cpp
@@ -14,14 +14,15 @@
* limitations under the License.
*/
-#include <ui/GraphicTypes.h> // ui::Dataspace
+#include "aidl/android/hardware/graphics/common/Dataspace.h"
#include <ui/PublicFormat.h>
+
// ----------------------------------------------------------------------------
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<android_dataspace>(HAL_DATASPACE_DYNAMIC_DEPTH)) {
return PublicFormat::DEPTH_JPEG;
- } else {
+ } else if (dataSpace == static_cast<android_dataspace>(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 03d15e4694..0000000000
--- 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 <ui/StaticDisplayInfo.h>
-
-#include <cstdint>
-
-#include <ui/FlattenableHelpers.h>
-
-#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 0000000000..a47eaed171
--- /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 <vector>
+
+#include <ui/GraphicTypes.h>
+
+namespace android::ui {
+
+using ColorModes = std::vector<ColorMode>;
+
+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 807a5d96a3..4229cf1507 100644
--- a/libs/ui/include/ui/DeviceProductInfo.h
+++ b/libs/ui/include/ui/DeviceProductInfo.h
@@ -24,8 +24,6 @@
#include <variant>
#include <vector>
-#include <utils/Flattenable.h>
-
namespace android {
// NUL-terminated plug and play ID.
@@ -34,7 +32,7 @@ using PnpId = std::array<char, 4>;
// 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<DeviceProductInfo> {
+struct DeviceProductInfo {
struct ModelYear {
uint32_t year;
};
@@ -63,13 +61,8 @@ struct DeviceProductInfo : LightFlattenable<DeviceProductInfo> {
// address is unavailable.
// For example, for HDMI connected device this will be the physical address.
std::vector<uint8_t> 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 9120972a42..3a31fa0848 100644
--- a/libs/ui/include/ui/DisplayId.h
+++ b/libs/ui/include/ui/DisplayId.h
@@ -17,9 +17,11 @@
#pragma once
#include <cstdint>
-#include <optional>
+#include <ostream>
#include <string>
+#include <ftl/optional.h>
+
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<PhysicalDisplayId> tryCast(DisplayId id) {
+ static constexpr ftl::Optional<PhysicalDisplayId> tryCast(DisplayId id) {
if (id.value & FLAG_VIRTUAL) {
return std::nullopt;
}
diff --git a/libs/ui/include/ui/DisplayMap.h b/libs/ui/include/ui/DisplayMap.h
new file mode 100644
index 0000000000..65d2b8fa31
--- /dev/null
+++ b/libs/ui/include/ui/DisplayMap.h
@@ -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.
+ */
+
+#pragma once
+
+#include <ftl/small_map.h>
+#include <ftl/small_vector.h>
+
+namespace android::ui {
+
+// The static capacities were chosen to exceed a typical number of physical and/or virtual displays.
+
+constexpr size_t kDisplayCapacity = 5;
+template <typename Key, typename Value>
+using DisplayMap = ftl::SmallMap<Key, Value, kDisplayCapacity>;
+
+constexpr size_t kPhysicalDisplayCapacity = 3;
+template <typename Key, typename Value>
+using PhysicalDisplayMap = ftl::SmallMap<Key, Value, kPhysicalDisplayCapacity>;
+
+template <typename T>
+using DisplayVector = ftl::SmallVector<T, kDisplayCapacity>;
+
+template <typename T>
+using PhysicalDisplayVector = ftl::SmallVector<T, kPhysicalDisplayCapacity>;
+
+} // namespace android::ui
diff --git a/libs/ui/include/ui/DisplayMode.h b/libs/ui/include/ui/DisplayMode.h
index 56f68e7bb2..ddb9bbd4bc 100644
--- a/libs/ui/include/ui/DisplayMode.h
+++ b/libs/ui/include/ui/DisplayMode.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <type_traits>
+#include <ui/GraphicTypes.h>
#include <ui/Size.h>
#include <utils/Flattenable.h>
#include <utils/Timers.h>
@@ -29,22 +30,20 @@ namespace android::ui {
using DisplayModeId = int32_t;
// Mode supported by physical display.
-struct DisplayMode : LightFlattenable<DisplayMode> {
+struct DisplayMode {
DisplayModeId id;
ui::Size resolution;
float xDpi = 0;
float yDpi = 0;
+ std::vector<ui::Hdr> supportedHdrTypes;
- float refreshRate = 0;
+ // Some modes have peak refresh rate lower than the panel vsync rate.
+ float peakRefreshRate = 0.f;
+ float vsyncRate = 0.f;
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 ce75a65214..0b77754455 100644
--- a/libs/ui/include/ui/DynamicDisplayInfo.h
+++ b/libs/ui/include/ui/DynamicDisplayInfo.h
@@ -24,18 +24,18 @@
#include <ui/GraphicTypes.h>
#include <ui/HdrCapabilities.h>
-#include <utils/Flattenable.h>
namespace android::ui {
// Information about a physical display which may change on hotplug reconnect.
-struct DynamicDisplayInfo : LightFlattenable<DynamicDisplayInfo> {
+struct DynamicDisplayInfo {
std::vector<ui::DisplayMode> 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<ui::ColorMode> supportedColorModes;
ui::ColorMode activeColorMode;
@@ -53,11 +53,6 @@ struct DynamicDisplayInfo : LightFlattenable<DynamicDisplayInfo> {
ui::DisplayModeId preferredBootDisplayMode;
std::optional<ui::DisplayMode> 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/libs/ui/include/ui/FatVector.h b/libs/ui/include/ui/FatVector.h
index cb61e6a320..494272b1a8 100644
--- a/libs/ui/include/ui/FatVector.h
+++ b/libs/ui/include/ui/FatVector.h
@@ -65,6 +65,17 @@ public:
free(p);
}
}
+
+ // The STL checks that this member type is present so that
+ // std::allocator_traits<InlineStdAllocator<T, SIZE>>::rebind_alloc<Other>
+ // works. std::vector won't be able to construct an
+ // InlineStdAllocator<Other, SIZE>, because InlineStdAllocator has no
+ // default constructor, but vector presumably doesn't rebind the allocator
+ // because it doesn't allocate internal node types.
+ template <class Other>
+ struct rebind {
+ typedef InlineStdAllocator<Other, SIZE> other;
+ };
Allocation& mAllocation;
};
diff --git a/libs/ui/include/ui/FenceResult.h b/libs/ui/include/ui/FenceResult.h
new file mode 100644
index 0000000000..6d63fc9973
--- /dev/null
+++ b/libs/ui/include/ui/FenceResult.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 <android-base/expected.h>
+#include <utils/Errors.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class Fence;
+
+using FenceResult = base::expected<sp<Fence>, status_t>;
+
+inline status_t fenceStatus(const FenceResult& fenceResult) {
+ return fenceResult.ok() ? NO_ERROR : fenceResult.error();
+}
+
+} // namespace android
diff --git a/libs/ui/include/ui/FenceTime.h b/libs/ui/include/ui/FenceTime.h
index ac75f431a0..334106f0cf 100644
--- a/libs/ui/include/ui/FenceTime.h
+++ b/libs/ui/include/ui/FenceTime.h
@@ -142,6 +142,8 @@ private:
std::atomic<nsecs_t> mSignalTime{Fence::SIGNAL_TIME_INVALID};
};
+using FenceTimePtr = std::shared_ptr<FenceTime>;
+
// A queue of FenceTimes that are expected to signal in FIFO order.
// Only maintains a queue of weak pointers so it doesn't keep references
// to Fences on its own.
@@ -190,8 +192,15 @@ private:
// before the new one is added.
class FenceToFenceTimeMap {
public:
- // Create a new FenceTime with that wraps the provided Fence.
- std::shared_ptr<FenceTime> createFenceTimeForTest(const sp<Fence>& fence);
+ using FencePair = std::pair<sp<Fence>, FenceTimePtr>;
+
+ FencePair makePendingFenceForTest() {
+ const auto fence = sp<Fence>::make();
+ return {fence, createFenceTimeForTest(fence)};
+ }
+
+ // Create a new FenceTime that wraps the provided Fence.
+ FenceTimePtr createFenceTimeForTest(const sp<Fence>&);
// Signals all FenceTimes created through this class that are wrappers
// around |fence|.
@@ -205,7 +214,6 @@ private:
std::unordered_map<Fence*, std::vector<std::weak_ptr<FenceTime>>> mMap;
};
-
-}; // namespace android
+} // namespace android
#endif // ANDROID_FENCE_TIME_H
diff --git a/libs/ui/include/ui/Gralloc.h b/libs/ui/include/ui/Gralloc.h
index 6101d4b43b..496ba57789 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<std::vector<uint8_t>> /*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<ui::PlaneLayout>* /*outPlaneLayouts*/) const {
- return INVALID_OPERATION;
- }
-
- virtual std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
- listSupportedMetadataTypes() const {
- return {};
- }
};
// A wrapper to IAllocator
diff --git a/libs/ui/include/ui/Gralloc2.h b/libs/ui/include/ui/Gralloc2.h
index f570c428b6..a7b6f49206 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 93a5077313..7367549964 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 cf023c9bcc..df43be87cd 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<std::vector<uint8_t>>* outSmpte2094_10) const override;
status_t setSmpte2094_10(buffer_handle_t bufferHandle,
std::optional<std::vector<uint8_t>> 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<ui::PlaneLayout>* outPlaneLayouts) const override;
std::vector<android::hardware::graphics::mapper::V4_0::IMapper::MetadataTypeDescription>
listSupportedMetadataTypes() const;
diff --git a/libs/ui/include/ui/Gralloc5.h b/libs/ui/include/ui/Gralloc5.h
new file mode 100644
index 0000000000..44b97d1a6f
--- /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 <aidl/android/hardware/graphics/allocator/IAllocator.h>
+#include <android/hardware/graphics/mapper/IMapper.h>
+#include <ui/Gralloc.h>
+
+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<ui::PlaneLayout> *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<ui::Smpte2086> *outSmpte2086) const override;
+
+ [[nodiscard]] status_t setSmpte2086(buffer_handle_t bufferHandle,
+ std::optional<ui::Smpte2086> smpte2086) const override;
+
+ [[nodiscard]] status_t getCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> *outCta861_3) const override;
+
+ [[nodiscard]] status_t setCta861_3(buffer_handle_t bufferHandle,
+ std::optional<ui::Cta861_3> cta861_3) const override;
+
+ [[nodiscard]] status_t getSmpte2094_40(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> *outSmpte2094_40) const override;
+
+ [[nodiscard]] status_t setSmpte2094_40(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> smpte2094_40) const override;
+
+ [[nodiscard]] status_t getSmpte2094_10(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> *outSmpte2094_10) const override;
+
+ [[nodiscard]] status_t setSmpte2094_10(
+ buffer_handle_t bufferHandle,
+ std::optional<std::vector<uint8_t>> 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<aidl::android::hardware::graphics::allocator::IAllocator> mAllocator;
+};
+
+} // namespace android
diff --git a/libs/ui/include/ui/GraphicBuffer.h b/libs/ui/include/ui/GraphicBuffer.h
index 57be686592..f859848b0c 100644
--- a/libs/ui/include/ui/GraphicBuffer.h
+++ b/libs/ui/include/ui/GraphicBuffer.h
@@ -36,6 +36,15 @@
#include <hardware/gralloc.h>
+#if defined(__ANDROID_APEX__) || defined(__ANDROID_VNDK__)
+// TODO: Provide alternatives that aren't broken
+#define AHB_CONVERSION \
+ [[deprecated("WARNING: VNDK casts beteween GraphicBuffer & AHardwareBuffer are UNSAFE and " \
+ "will be removed in the future")]]
+#else
+#define AHB_CONVERSION
+#endif
+
namespace android {
class GraphicBufferMapper;
@@ -80,10 +89,10 @@ public:
static sp<GraphicBuffer> from(ANativeWindowBuffer *);
- static GraphicBuffer* fromAHardwareBuffer(AHardwareBuffer*);
- static GraphicBuffer const* fromAHardwareBuffer(AHardwareBuffer const*);
- AHardwareBuffer* toAHardwareBuffer();
- AHardwareBuffer const* toAHardwareBuffer() const;
+ AHB_CONVERSION static GraphicBuffer* fromAHardwareBuffer(AHardwareBuffer*);
+ AHB_CONVERSION static GraphicBuffer const* fromAHardwareBuffer(AHardwareBuffer const*);
+ AHB_CONVERSION AHardwareBuffer* toAHardwareBuffer();
+ AHB_CONVERSION AHardwareBuffer const* toAHardwareBuffer() const;
// Create a GraphicBuffer to be unflatten'ed into or be reallocated.
GraphicBuffer();
@@ -270,7 +279,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 37a2e1cc9f..3a5167ab25 100644
--- a/libs/ui/include/ui/GraphicBufferMapper.h
+++ b/libs/ui/include/ui/GraphicBufferMapper.h
@@ -42,9 +42,10 @@ class GraphicBufferMapper : public Singleton<GraphicBufferMapper>
{
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<std::vector<uint8_t>> 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<ui::PlaneLayout>* outPlaneLayouts);
-
const GrallocMapper& getGrallocMapper() const {
return reinterpret_cast<const GrallocMapper&>(*mMapper);
}
diff --git a/libs/ui/include/ui/GraphicTypes.h b/libs/ui/include/ui/GraphicTypes.h
index 8661c36676..1775d390f0 100644
--- a/libs/ui/include/ui/GraphicTypes.h
+++ b/libs/ui/include/ui/GraphicTypes.h
@@ -20,6 +20,7 @@
#include <aidl/android/hardware/graphics/common/ChromaSiting.h>
#include <aidl/android/hardware/graphics/common/Compression.h>
#include <aidl/android/hardware/graphics/common/Cta861_3.h>
+#include <aidl/android/hardware/graphics/common/Hdr.h>
#include <aidl/android/hardware/graphics/common/Interlaced.h>
#include <aidl/android/hardware/graphics/common/PlaneLayout.h>
#include <aidl/android/hardware/graphics/common/Smpte2086.h>
@@ -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 813addeca6..ae54223585 100644
--- a/libs/ui/include/ui/HdrCapabilities.h
+++ b/libs/ui/include/ui/HdrCapabilities.h
@@ -22,12 +22,10 @@
#include <vector>
#include <ui/GraphicTypes.h>
-#include <utils/Flattenable.h>
namespace android {
-class HdrCapabilities : public LightFlattenable<HdrCapabilities>
-{
+class HdrCapabilities {
public:
HdrCapabilities(const std::vector<ui::Hdr>& 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<ui::Hdr> mSupportedHdrTypes;
float mMaxLuminance;
diff --git a/libs/ui/include/ui/PublicFormat.h b/libs/ui/include/ui/PublicFormat.h
index aa58805718..2248ccab0c 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 83d431dea3..c1d60f4f6c 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/ShadowSettings.h b/libs/ui/include/ui/ShadowSettings.h
new file mode 100644
index 0000000000..c0b83b8691
--- /dev/null
+++ b/libs/ui/include/ui/ShadowSettings.h
@@ -0,0 +1,65 @@
+/*
+ * 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 <math/vec4.h>
+#include "FloatRect.h"
+
+namespace android {
+
+/*
+ * Contains the configuration for the shadows drawn by single layer. Shadow follows
+ * material design guidelines.
+ */
+struct ShadowSettings {
+ // Boundaries of the shadow.
+ FloatRect boundaries = FloatRect();
+
+ // Color to the ambient shadow. The alpha is premultiplied.
+ vec4 ambientColor = vec4();
+
+ // Color to the spot shadow. The alpha is premultiplied. The position of the spot shadow
+ // depends on the light position.
+ vec4 spotColor = vec4();
+
+ // Position of the light source used to cast the spot shadow.
+ vec3 lightPos = vec3();
+
+ // Radius of the spot light source. Smaller radius will have sharper edges,
+ // larger radius will have softer shadows
+ float lightRadius = 0.f;
+
+ // Length of the cast shadow. If length is <= 0.f no shadows will be drawn.
+ float length = 0.f;
+
+ // If true fill in the casting layer is translucent and the shadow needs to fill the bounds.
+ // Otherwise the shadow will only be drawn around the edges of the casting layer.
+ bool casterIsTranslucent = false;
+};
+
+static inline bool operator==(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+ return lhs.boundaries == rhs.boundaries && lhs.ambientColor == rhs.ambientColor &&
+ lhs.spotColor == rhs.spotColor && lhs.lightPos == rhs.lightPos &&
+ lhs.lightRadius == rhs.lightRadius && lhs.length == rhs.length &&
+ lhs.casterIsTranslucent == rhs.casterIsTranslucent;
+}
+
+static inline bool operator!=(const ShadowSettings& lhs, const ShadowSettings& rhs) {
+ return !(operator==(lhs, rhs));
+}
+
+} // namespace android \ No newline at end of file
diff --git a/libs/ui/include/ui/StaticDisplayInfo.h b/libs/ui/include/ui/StaticDisplayInfo.h
index cc7c869b3b..83da821f37 100644
--- a/libs/ui/include/ui/StaticDisplayInfo.h
+++ b/libs/ui/include/ui/StaticDisplayInfo.h
@@ -20,24 +20,18 @@
#include <ui/DeviceProductInfo.h>
#include <ui/Rotation.h>
-#include <utils/Flattenable.h>
namespace android::ui {
-enum class DisplayConnectionType { Internal, External };
+enum class DisplayConnectionType { Internal, External, ftl_last = External };
// Immutable information about physical display.
-struct StaticDisplayInfo : LightFlattenable<StaticDisplayInfo> {
+struct StaticDisplayInfo {
DisplayConnectionType connectionType = DisplayConnectionType::Internal;
float density = 0.f;
bool secure = false;
std::optional<DeviceProductInfo> 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/ui/include_types/ui/HdrRenderTypeUtils.h b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
new file mode 100644
index 0000000000..b0af878cdb
--- /dev/null
+++ b/libs/ui/include_types/ui/HdrRenderTypeUtils.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <ui/GraphicTypes.h>
+
+namespace android {
+
+enum class HdrRenderType {
+ SDR, // just render to SDR
+ DISPLAY_HDR, // HDR by extended brightness
+ GENERIC_HDR // tonemapped HDR
+};
+
+/***
+ * A helper function to classify how we treat the result based on params.
+ *
+ * @param dataspace the dataspace
+ * @param pixelFormat optional, in case there is no source buffer.
+ * @param hdrSdrRatio default is 1.f, render engine side doesn't take care of it.
+ * @return HdrRenderType
+ */
+inline HdrRenderType getHdrRenderType(ui::Dataspace dataspace,
+ std::optional<ui::PixelFormat> pixelFormat,
+ float hdrSdrRatio = 1.f) {
+ const auto transfer = dataspace & HAL_DATASPACE_TRANSFER_MASK;
+ const auto range = dataspace & HAL_DATASPACE_RANGE_MASK;
+
+ if (transfer == HAL_DATASPACE_TRANSFER_ST2084 || transfer == HAL_DATASPACE_TRANSFER_HLG) {
+ return HdrRenderType::GENERIC_HDR;
+ }
+
+ static const auto BT2020_LINEAR_EXT = static_cast<ui::Dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
+ HAL_DATASPACE_TRANSFER_LINEAR |
+ HAL_DATASPACE_RANGE_EXTENDED);
+
+ if ((dataspace == BT2020_LINEAR_EXT || dataspace == ui::Dataspace::V0_SCRGB) &&
+ pixelFormat.has_value() && pixelFormat.value() == ui::PixelFormat::RGBA_FP16) {
+ return HdrRenderType::GENERIC_HDR;
+ }
+
+ // Extended range layer with an hdr/sdr ratio of > 1.01f can "self-promote" to HDR.
+ if (range == HAL_DATASPACE_RANGE_EXTENDED && hdrSdrRatio > 1.01f) {
+ return HdrRenderType::DISPLAY_HDR;
+ }
+
+ return HdrRenderType::SDR;
+}
+
+} // namespace android \ No newline at end of file
diff --git a/libs/ui/tests/Android.bp b/libs/ui/tests/Android.bp
index 831b64d877..8ce017d7a3 100644
--- a/libs/ui/tests/Android.bp
+++ b/libs/ui/tests/Android.bp
@@ -164,9 +164,9 @@ cc_test {
}
cc_test {
- name: "DataspaceUtils_test",
+ name: "HdrRenderTypeUtils_test",
shared_libs: ["libui"],
- srcs: ["DataspaceUtils_test.cpp"],
+ srcs: ["HdrRenderTypeUtils_test.cpp"],
cflags: [
"-Wall",
"-Werror",
diff --git a/libs/ui/tests/DataspaceUtils_test.cpp b/libs/ui/tests/DataspaceUtils_test.cpp
deleted file mode 100644
index 3e0967182b..0000000000
--- a/libs/ui/tests/DataspaceUtils_test.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "DataspaceUtilsTest"
-
-#include <gtest/gtest.h>
-#include <ui/DataspaceUtils.h>
-
-namespace android {
-
-class DataspaceUtilsTest : public testing::Test {};
-
-TEST_F(DataspaceUtilsTest, isHdrDataspace) {
- EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_HLG));
- EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_ITU_PQ));
- EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_PQ));
- EXPECT_TRUE(isHdrDataspace(ui::Dataspace::BT2020_HLG));
-
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB_LINEAR));
- // scRGB defines a very wide gamut but not an expanded luminance range
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB_LINEAR));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SRGB));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_SCRGB));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_JFIF));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_625));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT601_525));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::V0_BT709));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DCI_P3_LINEAR));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DCI_P3));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DISPLAY_P3_LINEAR));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DISPLAY_P3));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::ADOBE_RGB));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::BT2020_LINEAR));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::BT2020));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::BT2020_ITU));
- EXPECT_FALSE(isHdrDataspace(ui::Dataspace::DISPLAY_BT2020));
-}
-
-} // namespace android
diff --git a/libs/ui/tests/HdrRenderTypeUtils_test.cpp b/libs/ui/tests/HdrRenderTypeUtils_test.cpp
new file mode 100644
index 0000000000..efe819db76
--- /dev/null
+++ b/libs/ui/tests/HdrRenderTypeUtils_test.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "HdrRenderTypeUtilsTest"
+
+#include <gtest/gtest.h>
+#include <ui/HdrRenderTypeUtils.h>
+
+namespace android {
+
+class HdrRenderTypeUtilsTest : public testing::Test {};
+
+TEST_F(HdrRenderTypeUtilsTest, getHdrRenderType) {
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020_ITU_HLG, std::nullopt),
+ HdrRenderType::GENERIC_HDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020_ITU_PQ, std::nullopt),
+ HdrRenderType::GENERIC_HDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020_PQ, std::nullopt), HdrRenderType::GENERIC_HDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020_HLG, std::nullopt),
+ HdrRenderType::GENERIC_HDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SCRGB,
+ std::optional<ui::PixelFormat>(ui::PixelFormat::RGBA_FP16)),
+ HdrRenderType::GENERIC_HDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SCRGB,
+ std::optional<ui::PixelFormat>(ui::PixelFormat::RGBA_8888), 2.f),
+ HdrRenderType::DISPLAY_HDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SCRGB_LINEAR,
+ std::optional<ui::PixelFormat>(ui::PixelFormat::RGBA_8888), 2.f),
+ HdrRenderType::DISPLAY_HDR);
+
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SRGB_LINEAR, std::nullopt), HdrRenderType::SDR);
+ // scRGB defines a very wide gamut but not an expanded luminance range
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SCRGB_LINEAR, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SCRGB, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_SRGB, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_JFIF, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_BT601_625, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_BT601_525, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::V0_BT709, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::DCI_P3_LINEAR, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::DCI_P3, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::DISPLAY_P3_LINEAR, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::DISPLAY_P3, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::ADOBE_RGB, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020_LINEAR, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::BT2020_ITU, std::nullopt), HdrRenderType::SDR);
+ EXPECT_EQ(getHdrRenderType(ui::Dataspace::DISPLAY_BT2020, std::nullopt), HdrRenderType::SDR);
+}
+
+} // namespace android
diff --git a/libs/ultrahdr/Android.bp b/libs/ultrahdr/Android.bp
new file mode 100644
index 0000000000..eda5ea4578
--- /dev/null
+++ b/libs/ultrahdr/Android.bp
@@ -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.
+
+package {
+ // See: http://go/android-license-faq
+ default_applicable_licenses: [
+ "frameworks_native_license",
+ "adobe_hdr_gain_map_license",
+ ],
+}
+
+cc_library {
+ name: "libultrahdr-deprecated",
+ enabled: false,
+ 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-deprecated",
+ enabled: false,
+ host_supported: true,
+ vendor_available: true,
+
+ shared_libs: [
+ "libjpeg",
+ "liblog",
+ "libutils",
+ ],
+
+ export_include_dirs: ["include"],
+
+ srcs: [
+ "jpegencoderhelper.cpp",
+ ],
+}
+
+cc_library {
+ name: "libjpegdecoder-deprecated",
+ enabled: false,
+ 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 0000000000..6ace354d0b
--- /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 0000000000..2fa361f0b7
--- /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-deprecated",
+ license_kinds: ["legacy_by_exception_only"],
+ license_text: ["NOTICE"],
+}
diff --git a/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE b/libs/ultrahdr/adobe-hdr-gain-map-license/NOTICE
new file mode 100644
index 0000000000..3f6c5944c7
--- /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 0000000000..8d9132fd4d
--- /dev/null
+++ b/libs/ultrahdr/fuzzer/Android.bp
@@ -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.
+
+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-deprecated",
+ enabled: false,
+ 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-deprecated",
+ enabled: false,
+ defaults: ["ultrahdr_fuzzer_defaults"],
+ srcs: [
+ "ultrahdr_enc_fuzzer.cpp",
+ ],
+}
+
+cc_fuzz {
+ name: "ultrahdr_dec_fuzzer-deprecated",
+ enabled: false,
+ 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 0000000000..f1f403548d
--- /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 <fuzzer/FuzzedDataProvider.h>
+#include <iostream>
+#include <vector>
+
+// 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<ultrahdr_output_format>(mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
+ auto buffer = mFdp.ConsumeRemainingBytes<uint8_t>();
+ jpegr_compressed_struct jpegImgR{buffer.data(), (int)buffer.size(), (int)buffer.size(),
+ ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ std::vector<uint8_t> iccData(0);
+ std::vector<uint8_t> exifData(0);
+ jpegr_info_struct info{0, 0, &iccData, &exifData};
+ JpegR jpegHdr;
+ (void)jpegHdr.getJPEGRInfo(&jpegImgR, &info);
+//#define DUMP_PARAM
+#ifdef DUMP_PARAM
+ std::cout << "input buffer size " << jpegImgR.length << std::endl;
+ std::cout << "image dimensions " << info.width << " x " << info.width << std::endl;
+#endif
+ size_t outSize = info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
+ jpegr_uncompressed_struct decodedJpegR;
+ auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
+ decodedJpegR.data = decodedRaw.get();
+ ultrahdr_metadata_struct metadata;
+ jpegr_uncompressed_struct decodedGainMap{};
+ (void)jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
+ mFdp.ConsumeFloatingPointInRange<float>(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 0000000000..2d59e8bb88
--- /dev/null
+++ b/libs/ultrahdr/fuzzer/ultrahdr_enc_fuzzer.cpp
@@ -0,0 +1,333 @@
+/*
+ * 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 <fuzzer/FuzzedDataProvider.h>
+#include <algorithm>
+#include <iostream>
+#include <random>
+#include <vector>
+
+// User include files
+#include "ultrahdr/gainmapmath.h"
+#include "ultrahdr/jpegdecoderhelper.h"
+#include "ultrahdr/jpegencoderhelper.h"
+#include "utils/Log.h"
+
+using namespace android::ultrahdr;
+
+// Color gamuts for image data, sync with ultrahdr.h
+const int kCgMin = ULTRAHDR_COLORGAMUT_UNSPECIFIED + 1;
+const int kCgMax = ULTRAHDR_COLORGAMUT_MAX;
+
+// Transfer functions for image data, sync with ultrahdr.h
+const int kTfMin = ULTRAHDR_TF_UNSPECIFIED + 1;
+const int kTfMax = ULTRAHDR_TF_PQ;
+
+// Transfer functions for image data, sync with ultrahdr.h
+const int kOfMin = ULTRAHDR_OUTPUT_UNSPECIFIED + 1;
+const int kOfMax = ULTRAHDR_OUTPUT_MAX;
+
+// quality factor
+const int kQfMin = 0;
+const int kQfMax = 100;
+
+class UltraHdrEncFuzzer {
+public:
+ UltraHdrEncFuzzer(const uint8_t* data, size_t size) : mFdp(data, size){};
+ void process();
+ void fillP010Buffer(uint16_t* data, int width, int height, int stride);
+ void fill420Buffer(uint8_t* data, int width, int height, int stride);
+
+private:
+ FuzzedDataProvider mFdp;
+};
+
+void UltraHdrEncFuzzer::fillP010Buffer(uint16_t* data, int width, int height, int stride) {
+ uint16_t* tmp = data;
+ std::vector<uint16_t> buffer(16);
+ for (int i = 0; i < buffer.size(); i++) {
+ buffer[i] = (mFdp.ConsumeIntegralInRange<int>(0, (1 << 10) - 1)) << 6;
+ }
+ for (int j = 0; j < height; j++) {
+ for (int i = 0; i < width; i += buffer.size()) {
+ memcpy(tmp + i, buffer.data(),
+ std::min((int)buffer.size(), (width - i)) * sizeof(*data));
+ std::shuffle(buffer.begin(), buffer.end(),
+ std::default_random_engine(std::random_device{}()));
+ }
+ tmp += stride;
+ }
+}
+
+void UltraHdrEncFuzzer::fill420Buffer(uint8_t* data, int width, int height, int stride) {
+ uint8_t* tmp = data;
+ std::vector<uint8_t> buffer(16);
+ mFdp.ConsumeData(buffer.data(), buffer.size());
+ for (int j = 0; j < height; j++) {
+ for (int i = 0; i < width; i += buffer.size()) {
+ memcpy(tmp + i, buffer.data(),
+ std::min((int)buffer.size(), (width - i)) * sizeof(*data));
+ std::shuffle(buffer.begin(), buffer.end(),
+ std::default_random_engine(std::random_device{}()));
+ }
+ tmp += stride;
+ }
+}
+
+void UltraHdrEncFuzzer::process() {
+ while (mFdp.remaining_bytes()) {
+ struct jpegr_uncompressed_struct p010Img {};
+ struct jpegr_uncompressed_struct yuv420Img {};
+ struct jpegr_uncompressed_struct grayImg {};
+ struct jpegr_compressed_struct jpegImgR {};
+ struct jpegr_compressed_struct jpegImg {};
+ struct jpegr_compressed_struct jpegGainMap {};
+
+ // which encode api to select
+ int muxSwitch = mFdp.ConsumeIntegralInRange<int>(0, 4);
+
+ // quality factor
+ int quality = mFdp.ConsumeIntegralInRange<int>(kQfMin, kQfMax);
+
+ // hdr_tf
+ auto tf = static_cast<ultrahdr_transfer_function>(
+ mFdp.ConsumeIntegralInRange<int>(kTfMin, kTfMax));
+
+ // p010 Cg
+ auto p010Cg =
+ static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
+
+ // 420 Cg
+ auto yuv420Cg =
+ static_cast<ultrahdr_color_gamut>(mFdp.ConsumeIntegralInRange<int>(kCgMin, kCgMax));
+
+ // hdr_of
+ auto of = static_cast<ultrahdr_output_format>(
+ mFdp.ConsumeIntegralInRange<int>(kOfMin, kOfMax));
+
+ int width = mFdp.ConsumeIntegralInRange<int>(kMinWidth, kMaxWidth);
+ width = (width >> 1) << 1;
+
+ int height = mFdp.ConsumeIntegralInRange<int>(kMinHeight, kMaxHeight);
+ height = (height >> 1) << 1;
+
+ std::unique_ptr<uint16_t[]> bufferYHdr = nullptr;
+ std::unique_ptr<uint16_t[]> bufferUVHdr = nullptr;
+ std::unique_ptr<uint8_t[]> bufferYSdr = nullptr;
+ std::unique_ptr<uint8_t[]> bufferUVSdr = nullptr;
+ std::unique_ptr<uint8_t[]> grayImgRaw = nullptr;
+ if (muxSwitch != 4) {
+ // init p010 image
+ bool isUVContiguous = mFdp.ConsumeBool();
+ bool hasYStride = mFdp.ConsumeBool();
+ int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
+ p010Img.width = width;
+ p010Img.height = height;
+ p010Img.colorGamut = p010Cg;
+ p010Img.luma_stride = hasYStride ? yStride : 0;
+ int bppP010 = 2;
+ if (isUVContiguous) {
+ size_t p010Size = yStride * height * 3 / 2;
+ bufferYHdr = std::make_unique<uint16_t[]>(p010Size);
+ p010Img.data = bufferYHdr.get();
+ p010Img.chroma_data = nullptr;
+ p010Img.chroma_stride = 0;
+ fillP010Buffer(bufferYHdr.get(), width, height, yStride);
+ fillP010Buffer(bufferYHdr.get() + yStride * height, width, height / 2, yStride);
+ } else {
+ int uvStride = mFdp.ConsumeIntegralInRange<int>(width, width + 128);
+ size_t p010YSize = yStride * height;
+ bufferYHdr = std::make_unique<uint16_t[]>(p010YSize);
+ p010Img.data = bufferYHdr.get();
+ fillP010Buffer(bufferYHdr.get(), width, height, yStride);
+ size_t p010UVSize = uvStride * p010Img.height / 2;
+ bufferUVHdr = std::make_unique<uint16_t[]>(p010UVSize);
+ p010Img.chroma_data = bufferUVHdr.get();
+ p010Img.chroma_stride = uvStride;
+ fillP010Buffer(bufferUVHdr.get(), width, height / 2, uvStride);
+ }
+ } else {
+ size_t map_width = width / kMapDimensionScaleFactor;
+ size_t map_height = height / kMapDimensionScaleFactor;
+ // init 400 image
+ grayImg.width = map_width;
+ grayImg.height = map_height;
+ grayImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+
+ const size_t graySize = map_width * map_height;
+ grayImgRaw = std::make_unique<uint8_t[]>(graySize);
+ grayImg.data = grayImgRaw.get();
+ fill420Buffer(grayImgRaw.get(), map_width, map_height, map_width);
+ grayImg.chroma_data = nullptr;
+ grayImg.luma_stride = 0;
+ grayImg.chroma_stride = 0;
+ }
+
+ if (muxSwitch > 0) {
+ // init 420 image
+ bool isUVContiguous = mFdp.ConsumeBool();
+ bool hasYStride = mFdp.ConsumeBool();
+ int yStride = hasYStride ? mFdp.ConsumeIntegralInRange<int>(width, width + 128) : width;
+ yuv420Img.width = width;
+ yuv420Img.height = height;
+ yuv420Img.colorGamut = yuv420Cg;
+ yuv420Img.luma_stride = hasYStride ? yStride : 0;
+ if (isUVContiguous) {
+ size_t yuv420Size = yStride * height * 3 / 2;
+ bufferYSdr = std::make_unique<uint8_t[]>(yuv420Size);
+ yuv420Img.data = bufferYSdr.get();
+ yuv420Img.chroma_data = nullptr;
+ yuv420Img.chroma_stride = 0;
+ fill420Buffer(bufferYSdr.get(), width, height, yStride);
+ fill420Buffer(bufferYSdr.get() + yStride * height, width / 2, height / 2,
+ yStride / 2);
+ fill420Buffer(bufferYSdr.get() + yStride * height * 5 / 4, width / 2, height / 2,
+ yStride / 2);
+ } else {
+ int uvStride = mFdp.ConsumeIntegralInRange<int>(width / 2, width / 2 + 128);
+ size_t yuv420YSize = yStride * height;
+ bufferYSdr = std::make_unique<uint8_t[]>(yuv420YSize);
+ yuv420Img.data = bufferYSdr.get();
+ fill420Buffer(bufferYSdr.get(), width, height, yStride);
+ size_t yuv420UVSize = uvStride * yuv420Img.height / 2 * 2;
+ bufferUVSdr = std::make_unique<uint8_t[]>(yuv420UVSize);
+ yuv420Img.chroma_data = bufferUVSdr.get();
+ yuv420Img.chroma_stride = uvStride;
+ fill420Buffer(bufferUVSdr.get(), width / 2, height / 2, uvStride);
+ fill420Buffer(bufferUVSdr.get() + uvStride * height / 2, width / 2, height / 2,
+ uvStride);
+ }
+ }
+
+ // dest
+ // 2 * p010 size as input data is random, DCT compression might not behave as expected
+ jpegImgR.maxLength = std::max(8 * 1024 /* min size 8kb */, width * height * 3 * 2);
+ auto jpegImgRaw = std::make_unique<uint8_t[]>(jpegImgR.maxLength);
+ jpegImgR.data = jpegImgRaw.get();
+
+//#define DUMP_PARAM
+#ifdef DUMP_PARAM
+ std::cout << "Api Select " << muxSwitch << std::endl;
+ std::cout << "image dimensions " << width << " x " << height << std::endl;
+ std::cout << "p010 color gamut " << p010Img.colorGamut << std::endl;
+ std::cout << "p010 luma stride " << p010Img.luma_stride << std::endl;
+ std::cout << "p010 chroma stride " << p010Img.chroma_stride << std::endl;
+ std::cout << "420 color gamut " << yuv420Img.colorGamut << std::endl;
+ std::cout << "420 luma stride " << yuv420Img.luma_stride << std::endl;
+ std::cout << "420 chroma stride " << yuv420Img.chroma_stride << std::endl;
+ std::cout << "quality factor " << quality << std::endl;
+#endif
+
+ JpegR jpegHdr;
+ android::status_t status = android::UNKNOWN_ERROR;
+ if (muxSwitch == 0) { // api 0
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, tf, &jpegImgR, quality, nullptr);
+ } else if (muxSwitch == 1) { // api 1
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, tf, &jpegImgR, quality, nullptr);
+ } else {
+ // compressed img
+ JpegEncoderHelper encoder;
+ struct jpegr_uncompressed_struct yuv420ImgCopy = yuv420Img;
+ if (yuv420ImgCopy.luma_stride == 0) yuv420ImgCopy.luma_stride = yuv420Img.width;
+ if (!yuv420ImgCopy.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420Img.data);
+ yuv420ImgCopy.chroma_data = data + yuv420Img.luma_stride * yuv420Img.height;
+ yuv420ImgCopy.chroma_stride = yuv420Img.luma_stride >> 1;
+ }
+
+ if (encoder.compressImage(reinterpret_cast<uint8_t*>(yuv420ImgCopy.data),
+ reinterpret_cast<uint8_t*>(yuv420ImgCopy.chroma_data),
+ yuv420ImgCopy.width, yuv420ImgCopy.height,
+ yuv420ImgCopy.luma_stride, yuv420ImgCopy.chroma_stride,
+ quality, nullptr, 0)) {
+ jpegImg.length = encoder.getCompressedImageSize();
+ jpegImg.maxLength = jpegImg.length;
+ jpegImg.data = encoder.getCompressedImagePtr();
+ jpegImg.colorGamut = yuv420Cg;
+
+ if (muxSwitch == 2) { // api 2
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, &yuv420Img, &jpegImg, tf, &jpegImgR);
+ } else if (muxSwitch == 3) { // api 3
+ jpegImgR.length = 0;
+ status = jpegHdr.encodeJPEGR(&p010Img, &jpegImg, tf, &jpegImgR);
+ } else if (muxSwitch == 4) { // api 4
+ jpegImgR.length = 0;
+ JpegEncoderHelper gainMapEncoder;
+ if (gainMapEncoder.compressImage(reinterpret_cast<uint8_t*>(grayImg.data),
+ nullptr, grayImg.width, grayImg.height,
+ grayImg.width, 0, quality, nullptr, 0)) {
+ jpegGainMap.length = gainMapEncoder.getCompressedImageSize();
+ jpegGainMap.maxLength = jpegImg.length;
+ jpegGainMap.data = gainMapEncoder.getCompressedImagePtr();
+ jpegGainMap.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ultrahdr_metadata_struct metadata;
+ metadata.version = kJpegrVersion;
+ if (tf == ULTRAHDR_TF_HLG) {
+ metadata.maxContentBoost = kHlgMaxNits / kSdrWhiteNits;
+ } else if (tf == ULTRAHDR_TF_PQ) {
+ metadata.maxContentBoost = kPqMaxNits / kSdrWhiteNits;
+ } else {
+ metadata.maxContentBoost = 1.0f;
+ }
+ metadata.minContentBoost = 1.0f;
+ metadata.gamma = 1.0f;
+ metadata.offsetSdr = 0.0f;
+ metadata.offsetHdr = 0.0f;
+ metadata.hdrCapacityMin = 1.0f;
+ metadata.hdrCapacityMax = metadata.maxContentBoost;
+ status = jpegHdr.encodeJPEGR(&jpegImg, &jpegGainMap, &metadata, &jpegImgR);
+ }
+ }
+ }
+ }
+ if (status == android::OK) {
+ std::vector<uint8_t> iccData(0);
+ std::vector<uint8_t> exifData(0);
+ jpegr_info_struct info{0, 0, &iccData, &exifData};
+ status = jpegHdr.getJPEGRInfo(&jpegImgR, &info);
+ if (status == android::OK) {
+ size_t outSize =
+ info.width * info.height * ((of == ULTRAHDR_OUTPUT_HDR_LINEAR) ? 8 : 4);
+ jpegr_uncompressed_struct decodedJpegR;
+ auto decodedRaw = std::make_unique<uint8_t[]>(outSize);
+ decodedJpegR.data = decodedRaw.get();
+ ultrahdr_metadata_struct metadata;
+ jpegr_uncompressed_struct decodedGainMap{};
+ status = jpegHdr.decodeJPEGR(&jpegImgR, &decodedJpegR,
+ mFdp.ConsumeFloatingPointInRange<float>(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 0000000000..ae9c4ca338
--- /dev/null
+++ b/libs/ultrahdr/gainmapmath.cpp
@@ -0,0 +1,775 @@
+/*
+ * 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 <cmath>
+#include <vector>
+#include <ultrahdr/gainmapmath.h>
+
+namespace android::ultrahdr {
+
+static const std::vector<float> kPqOETF = [] {
+ std::vector<float> result;
+ for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
+ result.push_back(pqOetf(value));
+ }
+ return result;
+}();
+
+static const std::vector<float> kPqInvOETF = [] {
+ std::vector<float> result;
+ for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
+ result.push_back(pqInvOetf(value));
+ }
+ return result;
+}();
+
+static const std::vector<float> kHlgOETF = [] {
+ std::vector<float> result;
+ for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
+ result.push_back(hlgOetf(value));
+ }
+ return result;
+}();
+
+static const std::vector<float> kHlgInvOETF = [] {
+ std::vector<float> result;
+ for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
+ result.push_back(hlgInvOetf(value));
+ }
+ return result;
+}();
+
+static const std::vector<float> kSrgbInvOETF = [] {
+ std::vector<float> result;
+ for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(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<uint32_t>(e_gamma * (kSrgbInvOETFNumEntries - 1) + 0.5);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kSrgbInvOETFNumEntries - 1);
+ return kSrgbInvOETF[value];
+}
+
+Color srgbInvOetfLUT(Color e_gamma) {
+ return {{{ srgbInvOetfLUT(e_gamma.r),
+ srgbInvOetfLUT(e_gamma.g),
+ srgbInvOetfLUT(e_gamma.b) }}};
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Display-P3 transformations
+
+// See SMPTE EG 432-1, Equation 7-8.
+static const float kP3R = 0.20949f, kP3G = 0.72160f, kP3B = 0.06891f;
+
+float p3Luminance(Color e) {
+ return kP3R * e.r + kP3G * e.g + kP3B * e.b;
+}
+
+// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
+// Unfortunately, calculation of luma signal differs from calculation of
+// luminance for Display-P3, so we can't reuse p3Luminance here.
+static const float kP3YR = 0.299f, kP3YG = 0.587f, kP3YB = 0.114f;
+static const float kP3Cb = 1.772f, kP3Cr = 1.402f;
+
+Color p3RgbToYuv(Color e_gamma) {
+ float y_gamma = kP3YR * e_gamma.r + kP3YG * e_gamma.g + kP3YB * e_gamma.b;
+ return {{{ y_gamma,
+ (e_gamma.b - y_gamma) / kP3Cb,
+ (e_gamma.r - y_gamma) / kP3Cr }}};
+}
+
+// See ITU-R BT.601-7, Sections 2.5.1 and 2.5.2.
+// Same derivation to BT.2100's YUV->RGB, below. Similar to p3RgbToYuv, we must
+// use luma signal coefficients rather than the luminance coefficients.
+static const float kP3GCb = kP3YB * kP3Cb / kP3YG;
+static const float kP3GCr = kP3YR * kP3Cr / kP3YG;
+
+Color p3YuvToRgb(Color e_gamma) {
+ return {{{ clampPixelFloat(e_gamma.y + kP3Cr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kP3GCb * e_gamma.u - kP3GCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kP3Cb * e_gamma.u) }}};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BT.2100 transformations - according to ITU-R BT.2100-2
+
+// See ITU-R BT.2100-2, Table 5, HLG Reference OOTF
+static const float kBt2100R = 0.2627f, kBt2100G = 0.6780f, kBt2100B = 0.0593f;
+
+float bt2100Luminance(Color e) {
+ return kBt2100R * e.r + kBt2100G * e.g + kBt2100B * e.b;
+}
+
+// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
+// BT.2100 uses the same coefficients for calculating luma signal and luminance,
+// so we reuse the luminance function here.
+static const float kBt2100Cb = 1.8814f, kBt2100Cr = 1.4746f;
+
+Color bt2100RgbToYuv(Color e_gamma) {
+ float y_gamma = bt2100Luminance(e_gamma);
+ return {{{ y_gamma,
+ (e_gamma.b - y_gamma) / kBt2100Cb,
+ (e_gamma.r - y_gamma) / kBt2100Cr }}};
+}
+
+// See ITU-R BT.2100-2, Table 6, Derivation of colour difference signals.
+//
+// Similar to bt2100RgbToYuv above, we can reuse the luminance coefficients.
+//
+// Derived by inversing bt2100RgbToYuv. The derivation for R and B are pretty
+// straight forward; we just invert the formulas for U and V above. But deriving
+// the formula for G is a bit more complicated:
+//
+// Start with equation for luminance:
+// Y = kBt2100R * R + kBt2100G * G + kBt2100B * B
+// Solve for G:
+// G = (Y - kBt2100R * R - kBt2100B * B) / kBt2100B
+// Substitute equations for R and B in terms YUV:
+// G = (Y - kBt2100R * (Y + kBt2100Cr * V) - kBt2100B * (Y + kBt2100Cb * U)) / kBt2100B
+// Simplify:
+// G = Y * ((1 - kBt2100R - kBt2100B) / kBt2100G)
+// + U * (kBt2100B * kBt2100Cb / kBt2100G)
+// + V * (kBt2100R * kBt2100Cr / kBt2100G)
+//
+// We then get the following coeficients for calculating G from YUV:
+//
+// Coef for Y = (1 - kBt2100R - kBt2100B) / kBt2100G = 1
+// Coef for U = kBt2100B * kBt2100Cb / kBt2100G = kBt2100GCb = ~0.1645
+// Coef for V = kBt2100R * kBt2100Cr / kBt2100G = kBt2100GCr = ~0.5713
+
+static const float kBt2100GCb = kBt2100B * kBt2100Cb / kBt2100G;
+static const float kBt2100GCr = kBt2100R * kBt2100Cr / kBt2100G;
+
+Color bt2100YuvToRgb(Color e_gamma) {
+ return {{{ clampPixelFloat(e_gamma.y + kBt2100Cr * e_gamma.v),
+ clampPixelFloat(e_gamma.y - kBt2100GCb * e_gamma.u - kBt2100GCr * e_gamma.v),
+ clampPixelFloat(e_gamma.y + kBt2100Cb * e_gamma.u) }}};
+}
+
+// See ITU-R BT.2100-2, Table 5, HLG Reference OETF.
+static const float kHlgA = 0.17883277f, kHlgB = 0.28466892f, kHlgC = 0.55991073;
+
+float hlgOetf(float e) {
+ if (e <= 1.0f/12.0f) {
+ return sqrt(3.0f * e);
+ } else {
+ return kHlgA * log(12.0f * e - kHlgB) + kHlgC;
+ }
+}
+
+Color hlgOetf(Color e) {
+ return {{{ hlgOetf(e.r), hlgOetf(e.g), hlgOetf(e.b) }}};
+}
+
+float hlgOetfLUT(float e) {
+ uint32_t value = static_cast<uint32_t>(e * (kHlgOETFNumEntries - 1) + 0.5);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kHlgOETFNumEntries - 1);
+
+ return kHlgOETF[value];
+}
+
+Color hlgOetfLUT(Color e) {
+ return {{{ hlgOetfLUT(e.r), hlgOetfLUT(e.g), hlgOetfLUT(e.b) }}};
+}
+
+// See ITU-R BT.2100-2, Table 5, HLG Reference EOTF.
+float hlgInvOetf(float e_gamma) {
+ if (e_gamma <= 0.5f) {
+ return pow(e_gamma, 2.0f) / 3.0f;
+ } else {
+ return (exp((e_gamma - kHlgC) / kHlgA) + kHlgB) / 12.0f;
+ }
+}
+
+Color hlgInvOetf(Color e_gamma) {
+ return {{{ hlgInvOetf(e_gamma.r),
+ hlgInvOetf(e_gamma.g),
+ hlgInvOetf(e_gamma.b) }}};
+}
+
+float hlgInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * (kHlgInvOETFNumEntries - 1) + 0.5);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kHlgInvOETFNumEntries - 1);
+
+ return kHlgInvOETF[value];
+}
+
+Color hlgInvOetfLUT(Color e_gamma) {
+ return {{{ hlgInvOetfLUT(e_gamma.r),
+ hlgInvOetfLUT(e_gamma.g),
+ hlgInvOetfLUT(e_gamma.b) }}};
+}
+
+// See ITU-R BT.2100-2, Table 4, Reference PQ OETF.
+static const float kPqM1 = 2610.0f / 16384.0f, kPqM2 = 2523.0f / 4096.0f * 128.0f;
+static const float kPqC1 = 3424.0f / 4096.0f, kPqC2 = 2413.0f / 4096.0f * 32.0f,
+ kPqC3 = 2392.0f / 4096.0f * 32.0f;
+
+float pqOetf(float e) {
+ if (e <= 0.0f) return 0.0f;
+ return pow((kPqC1 + kPqC2 * pow(e, kPqM1)) / (1 + kPqC3 * pow(e, kPqM1)),
+ kPqM2);
+}
+
+Color pqOetf(Color e) {
+ return {{{ pqOetf(e.r), pqOetf(e.g), pqOetf(e.b) }}};
+}
+
+float pqOetfLUT(float e) {
+ uint32_t value = static_cast<uint32_t>(e * (kPqOETFNumEntries - 1) + 0.5);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kPqOETFNumEntries - 1);
+
+ return kPqOETF[value];
+}
+
+Color pqOetfLUT(Color e) {
+ return {{{ pqOetfLUT(e.r), pqOetfLUT(e.g), pqOetfLUT(e.b) }}};
+}
+
+// Derived from the inverse of the Reference PQ OETF.
+static const float kPqInvA = 128.0f, kPqInvB = 107.0f, kPqInvC = 2413.0f, kPqInvD = 2392.0f,
+ kPqInvE = 6.2773946361f, kPqInvF = 0.0126833f;
+
+float pqInvOetf(float e_gamma) {
+ // This equation blows up if e_gamma is 0.0, and checking on <= 0.0 doesn't
+ // always catch 0.0. So, check on 0.0001, since anything this small will
+ // effectively be crushed to zero anyways.
+ if (e_gamma <= 0.0001f) return 0.0f;
+ return pow((kPqInvA * pow(e_gamma, kPqInvF) - kPqInvB)
+ / (kPqInvC - kPqInvD * pow(e_gamma, kPqInvF)),
+ kPqInvE);
+}
+
+Color pqInvOetf(Color e_gamma) {
+ return {{{ pqInvOetf(e_gamma.r),
+ pqInvOetf(e_gamma.g),
+ pqInvOetf(e_gamma.b) }}};
+}
+
+float pqInvOetfLUT(float e_gamma) {
+ uint32_t value = static_cast<uint32_t>(e_gamma * (kPqInvOETFNumEntries - 1) + 0.5);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ value = CLIP3(value, 0, kPqInvOETFNumEntries - 1);
+
+ return kPqInvOETF[value];
+}
+
+Color pqInvOetfLUT(Color e_gamma) {
+ return {{{ pqInvOetfLUT(e_gamma.r),
+ pqInvOetfLUT(e_gamma.g),
+ pqInvOetfLUT(e_gamma.b) }}};
+}
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Color conversions
+
+Color bt709ToP3(Color e) {
+ return {{{ 0.82254f * e.r + 0.17755f * e.g + 0.00006f * e.b,
+ 0.03312f * e.r + 0.96684f * e.g + -0.00001f * e.b,
+ 0.01706f * e.r + 0.07240f * e.g + 0.91049f * e.b }}};
+}
+
+Color bt709ToBt2100(Color e) {
+ return {{{ 0.62740f * e.r + 0.32930f * e.g + 0.04332f * e.b,
+ 0.06904f * e.r + 0.91958f * e.g + 0.01138f * e.b,
+ 0.01636f * e.r + 0.08799f * e.g + 0.89555f * e.b }}};
+}
+
+Color p3ToBt709(Color e) {
+ return {{{ 1.22482f * e.r + -0.22490f * e.g + -0.00007f * e.b,
+ -0.04196f * e.r + 1.04199f * e.g + 0.00001f * e.b,
+ -0.01961f * e.r + -0.07865f * e.g + 1.09831f * e.b }}};
+}
+
+Color p3ToBt2100(Color e) {
+ return {{{ 0.75378f * e.r + 0.19862f * e.g + 0.04754f * e.b,
+ 0.04576f * e.r + 0.94177f * e.g + 0.01250f * e.b,
+ -0.00121f * e.r + 0.01757f * e.g + 0.98359f * e.b }}};
+}
+
+Color bt2100ToBt709(Color e) {
+ return {{{ 1.66045f * e.r + -0.58764f * e.g + -0.07286f * e.b,
+ -0.12445f * e.r + 1.13282f * e.g + -0.00837f * e.b,
+ -0.01811f * e.r + -0.10057f * e.g + 1.11878f * e.b }}};
+}
+
+Color bt2100ToP3(Color e) {
+ return {{{ 1.34369f * e.r + -0.28223f * e.g + -0.06135f * e.b,
+ -0.06533f * e.r + 1.07580f * e.g + -0.01051f * e.b,
+ 0.00283f * e.r + -0.01957f * e.g + 1.01679f * e.b
+ }}};
+}
+
+// TODO: confirm we always want to convert like this before calculating
+// luminance.
+ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut,
+ ultrahdr_color_gamut hdr_gamut) {
+ switch (sdr_gamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ switch (hdr_gamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ return identityConversion;
+ case ULTRAHDR_COLORGAMUT_P3:
+ return p3ToBt709;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ return bt2100ToBt709;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ switch (hdr_gamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ return bt709ToP3;
+ case ULTRAHDR_COLORGAMUT_P3:
+ return identityConversion;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ return bt2100ToP3;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ switch (hdr_gamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ return bt709ToBt2100;
+ case ULTRAHDR_COLORGAMUT_P3:
+ return p3ToBt2100;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ return identityConversion;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ return nullptr;
+ }
+}
+
+// All of these conversions are derived from the respective input YUV->RGB conversion followed by
+// the RGB->YUV for the receiving encoding. They are consistent with the RGB<->YUV functions in this
+// file, given that we uses BT.709 encoding for sRGB and BT.601 encoding for Display-P3, to match
+// DataSpace.
+
+Color yuv709To601(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + 0.101579f * e_gamma.u + 0.196076f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.989854f * e_gamma.u + -0.110653f * e_gamma.v,
+ 0.0f * e_gamma.y + -0.072453f * e_gamma.u + 0.983398f * e_gamma.v }}};
+}
+
+Color yuv709To2100(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + -0.016969f * e_gamma.u + 0.096312f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.995306f * e_gamma.u + -0.051192f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.011507f * e_gamma.u + 1.002637f * e_gamma.v }}};
+}
+
+Color yuv601To709(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + -0.118188f * e_gamma.u + -0.212685f * e_gamma.v,
+ 0.0f * e_gamma.y + 1.018640f * e_gamma.u + 0.114618f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.075049f * e_gamma.u + 1.025327f * e_gamma.v }}};
+}
+
+Color yuv601To2100(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + -0.128245f * e_gamma.u + -0.115879f * e_gamma.v,
+ 0.0f * e_gamma.y + 1.010016f * e_gamma.u + 0.061592f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.086969f * e_gamma.u + 1.029350f * e_gamma.v }}};
+}
+
+Color yuv2100To709(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + 0.018149f * e_gamma.u + -0.095132f * e_gamma.v,
+ 0.0f * e_gamma.y + 1.004123f * e_gamma.u + 0.051267f * e_gamma.v,
+ 0.0f * e_gamma.y + -0.011524f * e_gamma.u + 0.996782f * e_gamma.v }}};
+}
+
+Color yuv2100To601(Color e_gamma) {
+ return {{{ 1.0f * e_gamma.y + 0.117887f * e_gamma.u + 0.105521f * e_gamma.v,
+ 0.0f * e_gamma.y + 0.995211f * e_gamma.u + -0.059549f * e_gamma.v,
+ 0.0f * e_gamma.y + -0.084085f * e_gamma.u + 0.976518f * e_gamma.v }}};
+}
+
+void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
+ ColorTransformFn fn) {
+ Color yuv1 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 );
+ Color yuv2 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 );
+ Color yuv3 = getYuv420Pixel(image, x_chroma * 2, y_chroma * 2 + 1);
+ Color yuv4 = getYuv420Pixel(image, x_chroma * 2 + 1, y_chroma * 2 + 1);
+
+ yuv1 = fn(yuv1);
+ yuv2 = fn(yuv2);
+ yuv3 = fn(yuv3);
+ yuv4 = fn(yuv4);
+
+ Color new_uv = (yuv1 + yuv2 + yuv3 + yuv4) / 4.0f;
+
+ size_t pixel_y1_idx = x_chroma * 2 + y_chroma * 2 * image->luma_stride;
+ size_t pixel_y2_idx = (x_chroma * 2 + 1) + y_chroma * 2 * image->luma_stride;
+ size_t pixel_y3_idx = x_chroma * 2 + (y_chroma * 2 + 1) * image->luma_stride;
+ size_t pixel_y4_idx = (x_chroma * 2 + 1) + (y_chroma * 2 + 1) * image->luma_stride;
+
+ uint8_t& y1_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y1_idx];
+ uint8_t& y2_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y2_idx];
+ uint8_t& y3_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y3_idx];
+ uint8_t& y4_uint = reinterpret_cast<uint8_t*>(image->data)[pixel_y4_idx];
+
+ size_t pixel_count = image->chroma_stride * image->height / 2;
+ size_t pixel_uv_idx = x_chroma + y_chroma * (image->chroma_stride);
+
+ uint8_t& u_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_uv_idx];
+ uint8_t& v_uint = reinterpret_cast<uint8_t*>(image->chroma_data)[pixel_count + pixel_uv_idx];
+
+ y1_uint = static_cast<uint8_t>(CLIP3((yuv1.y * 255.0f + 0.5f), 0, 255));
+ y2_uint = static_cast<uint8_t>(CLIP3((yuv2.y * 255.0f + 0.5f), 0, 255));
+ y3_uint = static_cast<uint8_t>(CLIP3((yuv3.y * 255.0f + 0.5f), 0, 255));
+ y4_uint = static_cast<uint8_t>(CLIP3((yuv4.y * 255.0f + 0.5f), 0, 255));
+
+ u_uint = static_cast<uint8_t>(CLIP3((new_uv.u * 255.0f + 128.0f + 0.5f), 0, 255));
+ v_uint = static_cast<uint8_t>(CLIP3((new_uv.v * 255.0f + 128.0f + 0.5f), 0, 255));
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Gain map calculations
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata) {
+ return encodeGain(y_sdr, y_hdr, metadata,
+ log2(metadata->minContentBoost), log2(metadata->maxContentBoost));
+}
+
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
+ float log2MinContentBoost, float log2MaxContentBoost) {
+ float gain = 1.0f;
+ if (y_sdr > 0.0f) {
+ gain = y_hdr / y_sdr;
+ }
+
+ if (gain < metadata->minContentBoost) gain = metadata->minContentBoost;
+ if (gain > metadata->maxContentBoost) gain = metadata->maxContentBoost;
+
+ return static_cast<uint8_t>((log2(gain) - log2MinContentBoost)
+ / (log2MaxContentBoost - log2MinContentBoost)
+ * 255.0f);
+}
+
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata) {
+ float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
+ + log2(metadata->maxContentBoost) * gain;
+ float gainFactor = exp2(logBoost);
+ return e * gainFactor;
+}
+
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost) {
+ float logBoost = log2(metadata->minContentBoost) * (1.0f - gain)
+ + log2(metadata->maxContentBoost) * gain;
+ float gainFactor = exp2(logBoost * displayBoost / metadata->maxContentBoost);
+ return e * gainFactor;
+}
+
+Color applyGainLUT(Color e, float gain, GainLUT& gainLUT) {
+ float gainFactor = gainLUT.getGainFactor(gain);
+ return e * gainFactor;
+}
+
+Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
+ uint8_t* luma_data = reinterpret_cast<uint8_t*>(image->data);
+ size_t luma_stride = image->luma_stride;
+ uint8_t* chroma_data = reinterpret_cast<uint8_t*>(image->chroma_data);
+ size_t chroma_stride = image->chroma_stride;
+
+ size_t offset_cr = chroma_stride * (image->height / 2);
+ size_t pixel_y_idx = x + y * luma_stride;
+ size_t pixel_chroma_idx = x / 2 + (y / 2) * chroma_stride;
+
+ uint8_t y_uint = luma_data[pixel_y_idx];
+ uint8_t u_uint = chroma_data[pixel_chroma_idx];
+ uint8_t v_uint = chroma_data[offset_cr + pixel_chroma_idx];
+
+ // 128 bias for UV given we are using jpeglib; see:
+ // https://github.com/kornelski/libjpeg/blob/master/structure.doc
+ return {{{ static_cast<float>(y_uint) / 255.0f,
+ (static_cast<float>(u_uint) - 128.0f) / 255.0f,
+ (static_cast<float>(v_uint) - 128.0f) / 255.0f }}};
+}
+
+Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y) {
+ uint16_t* luma_data = reinterpret_cast<uint16_t*>(image->data);
+ size_t luma_stride = image->luma_stride == 0 ? image->width : image->luma_stride;
+ uint16_t* chroma_data = reinterpret_cast<uint16_t*>(image->chroma_data);
+ size_t chroma_stride = image->chroma_stride;
+
+ size_t pixel_y_idx = y * luma_stride + x;
+ size_t pixel_u_idx = (y >> 1) * chroma_stride + (x & ~0x1);
+ size_t pixel_v_idx = pixel_u_idx + 1;
+
+ uint16_t y_uint = luma_data[pixel_y_idx] >> 6;
+ uint16_t u_uint = chroma_data[pixel_u_idx] >> 6;
+ uint16_t v_uint = chroma_data[pixel_v_idx] >> 6;
+
+ // Conversions include taking narrow-range into account.
+ return {{{ (static_cast<float>(y_uint) - 64.0f) / 876.0f,
+ (static_cast<float>(u_uint) - 64.0f) / 896.0f - 0.5f,
+ (static_cast<float>(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<float>(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<float>(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<float>(x) / map_scale_factor;
+ float y_map = static_cast<float>(y) / map_scale_factor;
+
+ size_t x_lower = static_cast<size_t>(floor(x_map));
+ size_t x_upper = x_lower + 1;
+ size_t y_lower = static_cast<size_t>(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<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+ float e1_dist = pythDistance(x_map - static_cast<float>(x_lower),
+ y_map - static_cast<float>(y_lower));
+ if (e1_dist == 0.0f) return e1;
+
+ float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+ float e2_dist = pythDistance(x_map - static_cast<float>(x_lower),
+ y_map - static_cast<float>(y_upper));
+ if (e2_dist == 0.0f) return e2;
+
+ float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+ float e3_dist = pythDistance(x_map - static_cast<float>(x_upper),
+ y_map - static_cast<float>(y_lower));
+ if (e3_dist == 0.0f) return e3;
+
+ float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_upper * map->width]);
+ float e4_dist = pythDistance(x_map - static_cast<float>(x_upper),
+ y_map - static_cast<float>(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<uint8_t*>(map->data)[x_lower + y_lower * map->width]);
+ float e2 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_lower + y_upper * map->width]);
+ float e3 = mapUintToFloat(reinterpret_cast<uint8_t*>(map->data)[x_upper + y_lower * map->width]);
+ float e4 = mapUintToFloat(reinterpret_cast<uint8_t*>(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<uint32_t>(e_gamma.r * 1023.0f))
+ | ((0x3ff & static_cast<uint32_t>(e_gamma.g * 1023.0f)) << 10)
+ | ((0x3ff & static_cast<uint32_t>(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 0000000000..e41b645cce
--- /dev/null
+++ b/libs/ultrahdr/icc.cpp
@@ -0,0 +1,692 @@
+/*
+ * 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 <ultrahdr/icc.h>
+#include <vector>
+#include <utils/Log.h>
+
+#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<uint16_t*>(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<DataStruct> 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> dataStruct = sp<DataStruct>::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<DataStruct> IccHelper::write_xyz_tag(float x, float y, float z) {
+ uint32_t data[] = {
+ Endian_SwapBE32(kXYZ_PCSSpace),
+ 0,
+ static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(x))),
+ static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(y))),
+ static_cast<uint32_t>(Endian_SwapBE32(float_round_to_fixed(z))),
+ };
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(sizeof(data));
+ dataStruct->write(&data, sizeof(data));
+ return dataStruct;
+}
+
+sp<DataStruct> 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> dataStruct = sp<DataStruct>::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<const uint16_t*>(table_16)[i];
+ dataStruct->write16(value);
+ }
+ return dataStruct;
+}
+
+sp<DataStruct> IccHelper::write_trc_tag(const TransferFunction& fn) {
+ if (fn.a == 1.f && fn.b == 0.f && fn.c == 0.f
+ && fn.d == 0.f && fn.e == 0.f && fn.f == 0.f) {
+ int total_length = 16;
+ sp<DataStruct> dataStruct = new DataStruct(total_length);
+ dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type
+ dataStruct->write32(0); // Reserved
+ dataStruct->write32(Endian_SwapBE16(kExponential_ParaCurveType));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
+ return dataStruct;
+ }
+
+ int total_length = 40;
+ sp<DataStruct> dataStruct = new DataStruct(total_length);
+ dataStruct->write32(Endian_SwapBE32(kTAG_ParaCurveType)); // Type
+ dataStruct->write32(0); // Reserved
+ dataStruct->write32(Endian_SwapBE16(kGABCDEF_ParaCurveType));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.g)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.a)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.b)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.c)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.d)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.e)));
+ dataStruct->write32(Endian_SwapBE32(float_round_to_fixed(fn.f)));
+ return dataStruct;
+}
+
+float IccHelper::compute_tone_map_gain(const ultrahdr_transfer_function tf, float L) {
+ if (L <= 0.f) {
+ return 1.f;
+ }
+ if (tf == ULTRAHDR_TF_PQ) {
+ // The PQ transfer function will map to the range [0, 1]. Linearly scale
+ // it up to the range [0, 10,000/203]. We will then tone map that back
+ // down to [0, 1].
+ constexpr float kInputMaxLuminance = 10000 / 203.f;
+ constexpr float kOutputMaxLuminance = 1.0;
+ L *= kInputMaxLuminance;
+
+ // Compute the tone map gain which will tone map from 10,000/203 to 1.0.
+ constexpr float kToneMapA = kOutputMaxLuminance / (kInputMaxLuminance * kInputMaxLuminance);
+ constexpr float kToneMapB = 1.f / kOutputMaxLuminance;
+ return kInputMaxLuminance * (1.f + kToneMapA * L) / (1.f + kToneMapB * L);
+ }
+ if (tf == ULTRAHDR_TF_HLG) {
+ // Let Lw be the brightness of the display in nits.
+ constexpr float Lw = 203.f;
+ const float gamma = 1.2f + 0.42f * std::log(Lw / 1000.f) / std::log(10.f);
+ return std::pow(L, gamma - 1.f);
+ }
+ return 1.f;
+}
+
+sp<DataStruct> IccHelper::write_cicp_tag(uint32_t color_primaries,
+ uint32_t transfer_characteristics) {
+ int total_length = 12; // 4 + 4 + 1 + 1 + 1 + 1
+ sp<DataStruct> dataStruct = sp<DataStruct>::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<DataStruct> 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> dataStruct = sp<DataStruct>::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<const uint16_t*>(grid_16)[i];
+ dataStruct->write16(value);
+ }
+
+ return dataStruct;
+}
+
+sp<DataStruct> 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<DataStruct> b_curves_data[kNumChannels];
+ sp<DataStruct> a_curves_data[kNumChannels];
+ size_t clut_offset = 0;
+ sp<DataStruct> clut;
+ size_t a_curves_offset = 0;
+
+ // The "B" curve is required.
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ b_curves_data[i] = write_trc_tag(kLinear_TransFun);
+ }
+
+ // The "A" curve and CLUT are optional.
+ if (has_a_curves) {
+ clut_offset = b_curves_offset;
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ clut_offset += b_curves_data[i]->getLength();
+ }
+ clut = write_clut(grid_points, grid_16);
+
+ a_curves_offset = clut_offset + clut->getLength();
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ a_curves_data[i] = write_trc_tag(kLinear_TransFun);
+ }
+ }
+
+ int total_length = b_curves_offset;
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ total_length += b_curves_data[i]->getLength();
+ }
+ if (has_a_curves) {
+ total_length += clut->getLength();
+ for (size_t i = 0; i < kNumChannels; ++i) {
+ total_length += a_curves_data[i]->getLength();
+ }
+ }
+ sp<DataStruct> dataStruct = sp<DataStruct>::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<DataStruct> IccHelper::writeIccProfile(ultrahdr_transfer_function tf,
+ ultrahdr_color_gamut gamut) {
+ ICCHeader header;
+
+ std::vector<std::pair<uint32_t, sp<DataStruct>>> 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<uint8_t> 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<uint8_t*>(trc_table.data())));
+ tags.emplace_back(kTAG_gTRC,
+ write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
+ tags.emplace_back(kTAG_bTRC,
+ write_trc_tag(kTrcTableSize, reinterpret_cast<uint8_t*>(trc_table.data())));
+ } else {
+ tags.emplace_back(kTAG_rTRC, write_trc_tag(kSRGB_TransFun));
+ tags.emplace_back(kTAG_gTRC, write_trc_tag(kSRGB_TransFun));
+ tags.emplace_back(kTAG_bTRC, write_trc_tag(kSRGB_TransFun));
+ }
+ }
+
+ // Compute CICP.
+ if (tf == ULTRAHDR_TF_HLG || tf == ULTRAHDR_TF_PQ) {
+ // The CICP tag is present in ICC 4.4, so update the header's version.
+ header.version = Endian_SwapBE32(0x04400000);
+
+ uint32_t color_primaries = 0;
+ if (gamut == ULTRAHDR_COLORGAMUT_BT709) {
+ color_primaries = kCICPPrimariesSRGB;
+ } else if (gamut == ULTRAHDR_COLORGAMUT_P3) {
+ color_primaries = kCICPPrimariesP3;
+ }
+
+ uint32_t transfer_characteristics = 0;
+ if (tf == ULTRAHDR_TF_SRGB) {
+ transfer_characteristics = kCICPTrfnSRGB;
+ } else if (tf == ULTRAHDR_TF_LINEAR) {
+ transfer_characteristics = kCICPTrfnLinear;
+ } else if (tf == ULTRAHDR_TF_PQ) {
+ transfer_characteristics = kCICPTrfnPQ;
+ } else if (tf == ULTRAHDR_TF_HLG) {
+ transfer_characteristics = kCICPTrfnHLG;
+ }
+ tags.emplace_back(kTAG_cicp, write_cicp_tag(color_primaries, transfer_characteristics));
+ }
+
+ // Compute A2B0.
+ if (tf == ULTRAHDR_TF_PQ) {
+ std::vector<uint8_t> 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<const uint8_t*>(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> dataStruct = sp<DataStruct>::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<DataStruct> red_tag_test = write_xyz_tag(matrix.vals[0][0], matrix.vals[1][0],
+ matrix.vals[2][0]);
+ sp<DataStruct> green_tag_test = write_xyz_tag(matrix.vals[0][1], matrix.vals[1][1],
+ matrix.vals[2][1]);
+ sp<DataStruct> 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<uint8_t*>(icc_data) + kICCIdentifierSize;
+
+ ICCHeader* header = reinterpret_cast<ICCHeader*>(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<uint32_t*>(
+ 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 0000000000..9f1238f718
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/gainmapmath.h
@@ -0,0 +1,505 @@
+/*
+ * 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 <cmath>
+#include <stdint.h>
+
+#include <ultrahdr/jpegr.h>
+
+namespace android::ultrahdr {
+
+#define CLIP3(x, min, max) ((x) < (min)) ? (min) : ((x) > (max)) ? (max) : (x)
+
+////////////////////////////////////////////////////////////////////////////////
+// Framework
+
+const float kSdrWhiteNits = 100.0f;
+const float kHlgMaxNits = 1000.0f;
+const float kPqMaxNits = 10000.0f;
+
+struct Color {
+ union {
+ struct {
+ float r;
+ float g;
+ float b;
+ };
+ struct {
+ float y;
+ float u;
+ float v;
+ };
+ };
+};
+
+typedef Color (*ColorTransformFn)(Color);
+typedef float (*ColorCalculationFn)(Color);
+
+// A transfer function mapping encoded values to linear values,
+// represented by this 7-parameter piecewise function:
+//
+// linear = sign(encoded) * (c*|encoded| + f) , 0 <= |encoded| < d
+// = sign(encoded) * ((a*|encoded| + b)^g + e), d <= |encoded|
+//
+// (A simple gamma transfer function sets g to gamma and a to 1.)
+typedef struct TransferFunction {
+ float g, a,b,c,d,e,f;
+} TransferFunction;
+
+static constexpr TransferFunction kSRGB_TransFun =
+ { 2.4f, (float)(1/1.055), (float)(0.055/1.055), (float)(1/12.92), 0.04045f, 0.0f, 0.0f };
+
+static constexpr TransferFunction kLinear_TransFun =
+ { 1.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
+
+inline Color operator+=(Color& lhs, const Color& rhs) {
+ lhs.r += rhs.r;
+ lhs.g += rhs.g;
+ lhs.b += rhs.b;
+ return lhs;
+}
+inline Color operator-=(Color& lhs, const Color& rhs) {
+ lhs.r -= rhs.r;
+ lhs.g -= rhs.g;
+ lhs.b -= rhs.b;
+ return lhs;
+}
+
+inline Color operator+(const Color& lhs, const Color& rhs) {
+ Color temp = lhs;
+ return temp += rhs;
+}
+inline Color operator-(const Color& lhs, const Color& rhs) {
+ Color temp = lhs;
+ return temp -= rhs;
+}
+
+inline Color operator+=(Color& lhs, const float rhs) {
+ lhs.r += rhs;
+ lhs.g += rhs;
+ lhs.b += rhs;
+ return lhs;
+}
+inline Color operator-=(Color& lhs, const float rhs) {
+ lhs.r -= rhs;
+ lhs.g -= rhs;
+ lhs.b -= rhs;
+ return lhs;
+}
+inline Color operator*=(Color& lhs, const float rhs) {
+ lhs.r *= rhs;
+ lhs.g *= rhs;
+ lhs.b *= rhs;
+ return lhs;
+}
+inline Color operator/=(Color& lhs, const float rhs) {
+ lhs.r /= rhs;
+ lhs.g /= rhs;
+ lhs.b /= rhs;
+ return lhs;
+}
+
+inline Color operator+(const Color& lhs, const float rhs) {
+ Color temp = lhs;
+ return temp += rhs;
+}
+inline Color operator-(const Color& lhs, const float rhs) {
+ Color temp = lhs;
+ return temp -= rhs;
+}
+inline Color operator*(const Color& lhs, const float rhs) {
+ Color temp = lhs;
+ return temp *= rhs;
+}
+inline Color operator/(const Color& lhs, const float rhs) {
+ Color temp = lhs;
+ return temp /= rhs;
+}
+
+inline uint16_t floatToHalf(float f) {
+ // round-to-nearest-even: add last bit after truncated mantissa
+ const uint32_t b = *((uint32_t*)&f) + 0x00001000;
+
+ const uint32_t e = (b & 0x7F800000) >> 23; // exponent
+ const uint32_t m = b & 0x007FFFFF; // mantissa
+
+ // sign : normalized : denormalized : saturate
+ return (b & 0x80000000) >> 16
+ | (e > 112) * ((((e - 112) << 10) & 0x7C00) | m >> 13)
+ | ((e < 113) & (e > 101)) * ((((0x007FF000 + m) >> (125 - e)) + 1) >> 1)
+ | (e > 143) * 0x7FFF;
+}
+
+constexpr size_t kGainFactorPrecision = 10;
+constexpr size_t kGainFactorNumEntries = 1 << kGainFactorPrecision;
+struct GainLUT {
+ GainLUT(ultrahdr_metadata_ptr metadata) {
+ for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(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<float>(idx) / static_cast<float>(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<uint32_t>(gain * (kGainFactorNumEntries - 1) + 0.5);
+ //TODO() : Remove once conversion modules have appropriate clamping in place
+ idx = CLIP3(idx, 0, kGainFactorNumEntries - 1);
+ return mGainTable[idx];
+ }
+
+private:
+ float mGainTable[kGainFactorNumEntries];
+};
+
+struct ShepardsIDW {
+ ShepardsIDW(int mapScaleFactor) : mMapScaleFactor{mapScaleFactor} {
+ const int size = mMapScaleFactor * mMapScaleFactor * 4;
+ mWeights = new float[size];
+ mWeightsNR = new float[size];
+ mWeightsNB = new float[size];
+ mWeightsC = new float[size];
+ fillShepardsIDW(mWeights, 1, 1);
+ fillShepardsIDW(mWeightsNR, 0, 1);
+ fillShepardsIDW(mWeightsNB, 1, 0);
+ fillShepardsIDW(mWeightsC, 0, 0);
+ }
+ ~ShepardsIDW() {
+ delete[] mWeights;
+ delete[] mWeightsNR;
+ delete[] mWeightsNB;
+ delete[] mWeightsC;
+ }
+
+ int mMapScaleFactor;
+ // Image :-
+ // p00 p01 p02 p03 p04 p05 p06 p07
+ // p10 p11 p12 p13 p14 p15 p16 p17
+ // p20 p21 p22 p23 p24 p25 p26 p27
+ // p30 p31 p32 p33 p34 p35 p36 p37
+ // p40 p41 p42 p43 p44 p45 p46 p47
+ // p50 p51 p52 p53 p54 p55 p56 p57
+ // p60 p61 p62 p63 p64 p65 p66 p67
+ // p70 p71 p72 p73 p74 p75 p76 p77
+
+ // Gain Map (for 4 scale factor) :-
+ // m00 p01
+ // m10 m11
+
+ // Gain sample of curr 4x4, right 4x4, bottom 4x4, bottom right 4x4 are used during
+ // reconstruction. hence table weight size is 4.
+ float* mWeights;
+ // TODO: check if its ok to mWeights at places
+ float* mWeightsNR; // no right
+ float* mWeightsNB; // no bottom
+ float* mWeightsC; // no right & bottom
+
+ float euclideanDistance(float x1, float x2, float y1, float y2);
+ void fillShepardsIDW(float *weights, int incR, int incB);
+};
+
+////////////////////////////////////////////////////////////////////////////////
+// sRGB transformations
+// NOTE: sRGB has the same color primaries as BT.709, but different transfer
+// function. For this reason, all sRGB transformations here apply to BT.709,
+// except for those concerning transfer functions.
+
+/*
+ * Calculate the luminance of a linear RGB sRGB pixel, according to
+ * IEC 61966-2-1/Amd 1:2003.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float srgbLuminance(Color e);
+
+/*
+ * Convert from OETF'd srgb RGB to YUV, according to ITU-R BT.709-6.
+ *
+ * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color srgbRgbToYuv(Color e_gamma);
+
+
+/*
+ * Convert from OETF'd srgb YUV to RGB, according to ITU-R BT.709-6.
+ *
+ * BT.709 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color srgbYuvToRgb(Color e_gamma);
+
+/*
+ * Convert from srgb to linear, according to IEC 61966-2-1/Amd 1:2003.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float srgbInvOetf(float e_gamma);
+Color srgbInvOetf(Color e_gamma);
+float srgbInvOetfLUT(float e_gamma);
+Color srgbInvOetfLUT(Color e_gamma);
+
+constexpr size_t kSrgbInvOETFPrecision = 10;
+constexpr size_t kSrgbInvOETFNumEntries = 1 << kSrgbInvOETFPrecision;
+
+////////////////////////////////////////////////////////////////////////////////
+// Display-P3 transformations
+
+/*
+ * Calculated the luminance of a linear RGB P3 pixel, according to SMPTE EG 432-1.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float p3Luminance(Color e);
+
+/*
+ * Convert from OETF'd P3 RGB to YUV, according to ITU-R BT.601-7.
+ *
+ * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color p3RgbToYuv(Color e_gamma);
+
+/*
+ * Convert from OETF'd P3 YUV to RGB, according to ITU-R BT.601-7.
+ *
+ * BT.601 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color p3YuvToRgb(Color e_gamma);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// BT.2100 transformations - according to ITU-R BT.2100-2
+
+/*
+ * Calculate the luminance of a linear RGB BT.2100 pixel.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float bt2100Luminance(Color e);
+
+/*
+ * Convert from OETF'd BT.2100 RGB to YUV, according to ITU-R BT.2100-2.
+ *
+ * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color bt2100RgbToYuv(Color e_gamma);
+
+/*
+ * Convert from OETF'd BT.2100 YUV to RGB, according to ITU-R BT.2100-2.
+ *
+ * BT.2100 YUV<->RGB matrix is used to match expectations for DataSpace.
+ */
+Color bt2100YuvToRgb(Color e_gamma);
+
+/*
+ * Convert from scene luminance to HLG.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float hlgOetf(float e);
+Color hlgOetf(Color e);
+float hlgOetfLUT(float e);
+Color hlgOetfLUT(Color e);
+
+constexpr size_t kHlgOETFPrecision = 16;
+constexpr size_t kHlgOETFNumEntries = 1 << kHlgOETFPrecision;
+
+/*
+ * Convert from HLG to scene luminance.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float hlgInvOetf(float e_gamma);
+Color hlgInvOetf(Color e_gamma);
+float hlgInvOetfLUT(float e_gamma);
+Color hlgInvOetfLUT(Color e_gamma);
+
+constexpr size_t kHlgInvOETFPrecision = 12;
+constexpr size_t kHlgInvOETFNumEntries = 1 << kHlgInvOETFPrecision;
+
+/*
+ * Convert from scene luminance to PQ.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float pqOetf(float e);
+Color pqOetf(Color e);
+float pqOetfLUT(float e);
+Color pqOetfLUT(Color e);
+
+constexpr size_t kPqOETFPrecision = 16;
+constexpr size_t kPqOETFNumEntries = 1 << kPqOETFPrecision;
+
+/*
+ * Convert from PQ to scene luminance in nits.
+ *
+ * [0.0, 1.0] range in and out.
+ */
+float pqInvOetf(float e_gamma);
+Color pqInvOetf(Color e_gamma);
+float pqInvOetfLUT(float e_gamma);
+Color pqInvOetfLUT(Color e_gamma);
+
+constexpr size_t kPqInvOETFPrecision = 12;
+constexpr size_t kPqInvOETFNumEntries = 1 << kPqInvOETFPrecision;
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Color space conversions
+
+/*
+ * Convert between color spaces with linear RGB data, according to ITU-R BT.2407 and EG 432-1.
+ *
+ * All conversions are derived from multiplying the matrix for XYZ to output RGB color gamut by the
+ * matrix for input RGB color gamut to XYZ. The matrix for converting from XYZ to an RGB gamut is
+ * always the inverse of the RGB gamut to XYZ matrix.
+ */
+Color bt709ToP3(Color e);
+Color bt709ToBt2100(Color e);
+Color p3ToBt709(Color e);
+Color p3ToBt2100(Color e);
+Color bt2100ToBt709(Color e);
+Color bt2100ToP3(Color e);
+
+/*
+ * Identity conversion.
+ */
+inline Color identityConversion(Color e) { return e; }
+
+/*
+ * Get the conversion to apply to the HDR image for gain map generation
+ */
+ColorTransformFn getHdrConversionFn(ultrahdr_color_gamut sdr_gamut, ultrahdr_color_gamut hdr_gamut);
+
+/*
+ * Convert between YUV encodings, according to ITU-R BT.709-6, ITU-R BT.601-7, and ITU-R BT.2100-2.
+ *
+ * Bt.709 and Bt.2100 have well-defined YUV encodings; Display-P3's is less well defined, but is
+ * treated as Bt.601 by DataSpace, hence we do the same.
+ */
+Color yuv709To601(Color e_gamma);
+Color yuv709To2100(Color e_gamma);
+Color yuv601To709(Color e_gamma);
+Color yuv601To2100(Color e_gamma);
+Color yuv2100To709(Color e_gamma);
+Color yuv2100To601(Color e_gamma);
+
+/*
+ * Performs a transformation at the chroma x and y coordinates provided on a YUV420 image.
+ *
+ * Apply the transformation by determining transformed YUV for each of the 4 Y + 1 UV; each Y gets
+ * this result, and UV gets the averaged result.
+ *
+ * x_chroma and y_chroma should be less than or equal to half the image's width and height
+ * respecitively, since input is 4:2:0 subsampled.
+ */
+void transformYuv420(jr_uncompressed_ptr image, size_t x_chroma, size_t y_chroma,
+ ColorTransformFn fn);
+
+
+////////////////////////////////////////////////////////////////////////////////
+// Gain map calculations
+
+/*
+ * Calculate the 8-bit unsigned integer gain value for the given SDR and HDR
+ * luminances in linear space, and the hdr ratio to encode against.
+ *
+ * Note: since this library always uses gamma of 1.0, offsetSdr of 0.0, and
+ * offsetHdr of 0.0, this function doesn't handle different metadata values for
+ * these fields.
+ */
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata);
+uint8_t encodeGain(float y_sdr, float y_hdr, ultrahdr_metadata_ptr metadata,
+ float log2MinContentBoost, float log2MaxContentBoost);
+
+/*
+ * Calculates the linear luminance in nits after applying the given gain
+ * value, with the given hdr ratio, to the given sdr input in the range [0, 1].
+ *
+ * Note: similar to encodeGain(), this function only supports gamma 1.0,
+ * offsetSdr 0.0, offsetHdr 0.0, hdrCapacityMin 1.0, and hdrCapacityMax equal to
+ * gainMapMax, as this library encodes.
+ */
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata);
+Color applyGain(Color e, float gain, ultrahdr_metadata_ptr metadata, float displayBoost);
+Color applyGainLUT(Color e, float gain, GainLUT& gainLUT);
+
+/*
+ * Helper for sampling from YUV 420 images.
+ */
+Color getYuv420Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
+
+/*
+ * Helper for sampling from P010 images.
+ *
+ * Expect narrow-range image data for P010.
+ */
+Color getP010Pixel(jr_uncompressed_ptr image, size_t x, size_t y);
+
+/*
+ * Sample the image at the provided location, with a weighting based on nearby
+ * pixels and the map scale factor.
+ */
+Color sampleYuv420(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the image at the provided location, with a weighting based on nearby
+ * pixels and the map scale factor.
+ *
+ * Expect narrow-range image data for P010.
+ */
+Color sampleP010(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y);
+
+/*
+ * Sample the gain value for the map from a given x,y coordinate on a scale
+ * that is map scale factor larger than the map size.
+ */
+float sampleMap(jr_uncompressed_ptr map, float map_scale_factor, size_t x, size_t y);
+float sampleMap(jr_uncompressed_ptr map, size_t map_scale_factor, size_t x, size_t y,
+ ShepardsIDW& weightTables);
+
+/*
+ * Convert from Color to RGBA1010102.
+ *
+ * Alpha always set to 1.0.
+ */
+uint32_t colorToRgba1010102(Color e_gamma);
+
+/*
+ * Convert from Color to F16.
+ *
+ * Alpha always set to 1.0.
+ */
+uint64_t colorToRgbaF16(Color e_gamma);
+
+} // namespace android::ultrahdr
+
+#endif // ANDROID_ULTRAHDR_RECOVERYMAPMATH_H
diff --git a/libs/ultrahdr/include/ultrahdr/icc.h b/libs/ultrahdr/include/ultrahdr/icc.h
new file mode 100644
index 0000000000..971b267fe4
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/icc.h
@@ -0,0 +1,256 @@
+/*
+ * 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 <ultrahdr/gainmapmath.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegrutils.h>
+#include <utils/RefBase.h>
+#include <cmath>
+#include <string>
+
+#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<uint16_t>(x);
+}
+
+static void float_to_table16(const float f, uint8_t* table_16) {
+ *reinterpret_cast<uint16_t*>(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<DataStruct> write_text_tag(const char* text);
+ static std::string get_desc_string(const ultrahdr_transfer_function tf,
+ const ultrahdr_color_gamut gamut);
+ static sp<DataStruct> write_xyz_tag(float x, float y, float z);
+ static sp<DataStruct> write_trc_tag(const int table_entries, const void* table_16);
+ static sp<DataStruct> write_trc_tag(const TransferFunction& fn);
+ static float compute_tone_map_gain(const ultrahdr_transfer_function tf, float L);
+ static sp<DataStruct> write_cicp_tag(uint32_t color_primaries,
+ uint32_t transfer_characteristics);
+ static sp<DataStruct> 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<DataStruct> 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<DataStruct> 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 0000000000..b86ce5f450
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/jpegdecoderhelper.h
@@ -0,0 +1,143 @@
+/*
+ * 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 <cstdio>
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+#include <utils/Errors.h>
+#include <vector>
+
+// constraint on max width and max height is only due to device alloc constraints
+// Can tune these values basing on the target device
+static const int kMaxWidth = 8192;
+static const int kMaxHeight = 8192;
+
+namespace android::ultrahdr {
+/*
+ * Encapsulates a converter from JPEG to raw image (YUV420planer or grey-scale) format.
+ * This class is not thread-safe.
+ */
+class JpegDecoderHelper {
+public:
+ JpegDecoderHelper();
+ ~JpegDecoderHelper();
+ /*
+ * Decompresses JPEG image to raw image (YUV420planer, grey-scale or RGBA) format. After
+ * calling this method, call getDecompressedImage() to get the image.
+ * Returns false if decompressing the image fails.
+ */
+ bool decompressImage(const void* image, int length, bool decodeToRGBA = false);
+ /*
+ * Returns the decompressed raw image buffer pointer. This method must be called only after
+ * calling decompressImage().
+ */
+ void* getDecompressedImagePtr();
+ /*
+ * Returns the decompressed raw image buffer size. This method must be called only after
+ * calling decompressImage().
+ */
+ size_t getDecompressedImageSize();
+ /*
+ * Returns the image width in pixels. This method must be called only after calling
+ * decompressImage().
+ */
+ size_t getDecompressedImageWidth();
+ /*
+ * Returns the image width in pixels. This method must be called only after calling
+ * decompressImage().
+ */
+ size_t getDecompressedImageHeight();
+ /*
+ * Returns the XMP data from the image.
+ */
+ void* getXMPPtr();
+ /*
+ * Returns the decompressed XMP buffer size. This method must be called only after
+ * calling decompressImage() or getCompressedImageParameters().
+ */
+ size_t getXMPSize();
+ /*
+ * Extracts EXIF package and updates the EXIF position / length without decoding the image.
+ */
+ bool extractEXIF(const void* image, int length);
+ /*
+ * Returns the EXIF data from the image.
+ * This method must be called after extractEXIF() or decompressImage().
+ */
+ void* getEXIFPtr();
+ /*
+ * Returns the decompressed EXIF buffer size. This method must be called only after
+ * calling decompressImage(), extractEXIF() or getCompressedImageParameters().
+ */
+ size_t getEXIFSize();
+ /*
+ * Returns the position offset of EXIF package
+ * (4 bypes offset to FF sign, the byte after FF E1 XX XX <this byte>),
+ * or -1 if no EXIF exists.
+ * This method must be called after extractEXIF() or decompressImage().
+ */
+ int getEXIFPos() { return mExifPos; }
+ /*
+ * Returns the ICC data from the image.
+ */
+ void* getICCPtr();
+ /*
+ * Returns the decompressed ICC buffer size. This method must be called only after
+ * calling decompressImage() or getCompressedImageParameters().
+ */
+ size_t getICCSize();
+ /*
+ * Decompresses metadata of the image. All vectors are owned by the caller.
+ */
+ bool getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+ size_t* pHeight, std::vector<uint8_t>* iccData,
+ std::vector<uint8_t>* 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<JOCTET> mResultBuffer;
+ // The buffer that holds XMP Data.
+ std::vector<JOCTET> mXMPBuffer;
+ // The buffer that holds EXIF Data.
+ std::vector<JOCTET> mEXIFBuffer;
+ // The buffer that holds ICC Data.
+ std::vector<JOCTET> mICCBuffer;
+
+ // Resolution of the decompressed image.
+ size_t mWidth;
+ size_t mHeight;
+
+ // Position of EXIF package, default value is -1 which means no EXIF package appears.
+ ssize_t mExifPos = -1;
+};
+} /* namespace android::ultrahdr */
+
+#endif // ANDROID_ULTRAHDR_JPEGDECODERHELPER_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
new file mode 100644
index 0000000000..9d06415cb3
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/jpegencoderhelper.h
@@ -0,0 +1,102 @@
+/*
+ * 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 <cstdio>
+#include <vector>
+
+extern "C" {
+#include <jerror.h>
+#include <jpeglib.h>
+}
+
+#include <utils/Errors.h>
+
+namespace android::ultrahdr {
+
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
+
+/*
+ * Encapsulates a converter from raw image (YUV420planer or grey-scale) to JPEG format.
+ * This class is not thread-safe.
+ */
+class JpegEncoderHelper {
+public:
+ JpegEncoderHelper();
+ ~JpegEncoderHelper();
+
+ /*
+ * Compresses YUV420Planer image to JPEG format. After calling this method, call
+ * getCompressedImage() to get the image. |quality| is the jpeg image quality parameter to use.
+ * It ranges from 1 (poorest quality) to 100 (highest quality). |iccBuffer| is the buffer of
+ * ICC segment which will be added to the compressed image.
+ * Returns false if errors occur during compression.
+ */
+ bool compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
+ int lumaStride, int chromaStride, int quality, const void* iccBuffer,
+ unsigned int iccSize);
+
+ /*
+ * Returns the compressed JPEG buffer pointer. This method must be called only after calling
+ * compressImage().
+ */
+ void* getCompressedImagePtr();
+
+ /*
+ * Returns the compressed JPEG buffer size. This method must be called only after calling
+ * compressImage().
+ */
+ size_t getCompressedImageSize();
+
+ /*
+ * Process 16 lines of Y and 16 lines of U/V each time.
+ * We must pass at least 16 scanlines according to libjpeg documentation.
+ */
+ static const int kCompressBatchSize = 16;
+
+private:
+ // initDestination(), emptyOutputBuffer() and emptyOutputBuffer() are callback functions to be
+ // passed into jpeg library.
+ static void initDestination(j_compress_ptr cinfo);
+ static boolean emptyOutputBuffer(j_compress_ptr cinfo);
+ static void terminateDestination(j_compress_ptr cinfo);
+ static void outputErrorMessage(j_common_ptr cinfo);
+
+ // Returns false if errors occur.
+ bool encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width, int height,
+ int lumaStride, int chromaStride, int quality, const void* iccBuffer,
+ unsigned int iccSize);
+ void setJpegDestination(jpeg_compress_struct* cinfo);
+ void setJpegCompressStruct(int width, int height, int quality, jpeg_compress_struct* cinfo,
+ bool isSingleChannel);
+ // Returns false if errors occur.
+ bool compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, const uint8_t* uvBuffer,
+ int lumaStride, int chromaStride);
+ bool compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer, int lumaStride);
+
+ // The block size for encoded jpeg image buffer.
+ static const int kBlockSize = 16384;
+
+ // The buffer that holds the compressed result.
+ std::vector<JOCTET> 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 0000000000..114c81d818
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/jpegr.h
@@ -0,0 +1,452 @@
+/*
+ * 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 <cstdint>
+#include <vector>
+
+#include "ultrahdr/jpegdecoderhelper.h"
+#include "ultrahdr/jpegencoderhelper.h"
+#include "ultrahdr/jpegrerrorcode.h"
+#include "ultrahdr/ultrahdr.h"
+
+#ifndef FLT_MAX
+#define FLT_MAX 0x1.fffffep127f
+#endif
+
+namespace android::ultrahdr {
+
+// The current JPEGR version that we encode to
+static const char* const kJpegrVersion = "1.0";
+
+// Map is quarter res / sixteenth size
+static const size_t kMapDimensionScaleFactor = 4;
+
+// Gain Map width is (image_width / kMapDimensionScaleFactor). If we were to
+// compress 420 GainMap in jpeg, then we need at least 2 samples. For Grayscale
+// 1 sample is sufficient. We are using 2 here anyways
+static const int kMinWidth = 2 * kMapDimensionScaleFactor;
+static const int kMinHeight = 2 * kMapDimensionScaleFactor;
+
+// Minimum Codec Unit(MCU) for 420 sub-sampling is decided by JPEG encoder by parameter
+// JpegEncoderHelper::kCompressBatchSize.
+// The width and height of image under compression is expected to be a multiple of MCU size.
+// If this criteria is not satisfied, padding is done.
+static const size_t kJpegBlock = JpegEncoderHelper::kCompressBatchSize;
+
+/*
+ * Holds information of jpegr image
+ */
+struct jpegr_info_struct {
+ size_t width;
+ size_t height;
+ std::vector<uint8_t>* iccData;
+ std::vector<uint8_t>* exifData;
+};
+
+/*
+ * Holds information for uncompressed image or gain map.
+ */
+struct jpegr_uncompressed_struct {
+ // Pointer to the data location.
+ void* data;
+ // Width of the gain map or the luma plane of the image in pixels.
+ int width;
+ // Height of the gain map or the luma plane of the image in pixels.
+ int height;
+ // Color gamut.
+ ultrahdr_color_gamut colorGamut;
+
+ // Values below are optional
+ // Pointer to chroma data, if it's NULL, chroma plane is considered to be immediately
+ // after the luma plane.
+ void* chroma_data = nullptr;
+ // Stride of Y plane in number of pixels. 0 indicates the member is uninitialized. If
+ // non-zero this value must be larger than or equal to luma width. If stride is
+ // uninitialized then it is assumed to be equal to luma width.
+ int luma_stride = 0;
+ // Stride of UV plane in number of pixels.
+ // 1. If this handle points to P010 image then this value must be larger than
+ // or equal to luma width.
+ // 2. If this handle points to 420 image then this value must be larger than
+ // or equal to (luma width / 2).
+ // NOTE: if chroma_data is nullptr, chroma_stride is irrelevant. Just as the way,
+ // chroma_data is derived from luma ptr, chroma stride is derived from luma stride.
+ int chroma_stride = 0;
+};
+
+/*
+ * Holds information for compressed image or gain map.
+ */
+struct jpegr_compressed_struct {
+ // Pointer to the data location.
+ void* data;
+ // Used data length in bytes.
+ int length;
+ // Maximum available data length in bytes.
+ int maxLength;
+ // Color gamut.
+ ultrahdr_color_gamut colorGamut;
+};
+
+/*
+ * Holds information for EXIF metadata.
+ */
+struct jpegr_exif_struct {
+ // Pointer to the data location.
+ void* data;
+ // Data length;
+ int length;
+};
+
+typedef struct jpegr_uncompressed_struct* jr_uncompressed_ptr;
+typedef struct jpegr_compressed_struct* jr_compressed_ptr;
+typedef struct jpegr_exif_struct* jr_exif_ptr;
+typedef struct jpegr_info_struct* jr_info_ptr;
+
+class JpegR {
+public:
+ /*
+ * Experimental only
+ *
+ * Encode API-0
+ * Compress JPEGR image from 10-bit HDR YUV.
+ *
+ * Tonemap the HDR input to a SDR image, generate gain map from the HDR and SDR images,
+ * compress SDR YUV to 8-bit JPEG and append the gain map to the end of the compressed
+ * JPEG.
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the destination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+ * the highest quality
+ * @param exif pointer to the exif metadata.
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest, int quality, jr_exif_ptr exif);
+
+ /*
+ * Encode API-1
+ * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+ *
+ * Generate gain map from the HDR and SDR inputs, compress SDR YUV to 8-bit JPEG and append
+ * the gain map to the end of the compressed JPEG. HDR and SDR inputs must be the same
+ * resolution. SDR input is assumed to use the sRGB transfer function.
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+ * the highest quality
+ * @param exif pointer to the exif metadata.
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest, int quality,
+ jr_exif_ptr exif);
+
+ /*
+ * Encode API-2
+ * Compress JPEGR image from 10-bit HDR YUV, 8-bit SDR YUV and compressed 8-bit JPEG.
+ *
+ * This method requires HAL Hardware JPEG encoder.
+ *
+ * Generate gain map from the HDR and SDR inputs, append the gain map to the end of the
+ * compressed JPEG. Adds an ICC profile if one isn't present in the input JPEG image. HDR and
+ * SDR inputs must be the same resolution and color space. SDR image is assumed to use the sRGB
+ * transfer function.
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+ * Note: the compressed SDR image must be the compressed
+ * yuv420_image_ptr image in JPEG format.
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_uncompressed_ptr yuv420_image_ptr,
+ jr_compressed_ptr yuv420jpg_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest);
+
+ /*
+ * Encode API-3
+ * Compress JPEGR image from 10-bit HDR YUV and 8-bit SDR YUV.
+ *
+ * This method requires HAL Hardware JPEG encoder.
+ *
+ * Decode the compressed 8-bit JPEG image to YUV SDR, generate gain map from the HDR input
+ * and the decoded SDR result, append the gain map to the end of the compressed JPEG. Adds an
+ * ICC profile if one isn't present in the input JPEG image. HDR and SDR inputs must be the same
+ * resolution. JPEG image is assumed to use the sRGB transfer function.
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, jr_compressed_ptr yuv420jpg_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest);
+
+ /*
+ * Encode API-4
+ * Assemble JPEGR image from SDR JPEG and gainmap JPEG.
+ *
+ * Assemble the primary JPEG image, the gain map and the metadata to JPEG/R format. Adds an ICC
+ * profile if one isn't present in the input JPEG image.
+ * @param yuv420jpg_image_ptr SDR image compressed in jpeg format
+ * @param gainmapjpg_image_ptr gain map image compressed in jpeg format
+ * @param metadata metadata to be written in XMP of the primary jpeg
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
+ jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
+ jr_compressed_ptr dest);
+
+ /*
+ * Decode API
+ * Decompress JPEGR image.
+ *
+ * This method assumes that the JPEGR image contains an ICC profile with primaries that match
+ * those of a color gamut that this library is aware of; Bt.709, Display-P3, or Bt.2100. It also
+ * assumes the base image uses the sRGB transfer function.
+ *
+ * This method only supports single gain map metadata values for fields that allow multi-channel
+ * metadata values.
+ * @param jpegr_image_ptr compressed JPEGR image.
+ * @param dest destination of the uncompressed JPEGR image.
+ * @param max_display_boost (optional) the maximum available boost supported by a display,
+ * the value must be greater than or equal to 1.0.
+ * @param exif destination of the decoded EXIF metadata. The default value is NULL where the
+ decoder will do nothing about it. If configured not NULL the decoder will write
+ EXIF data into this structure. The format is defined in {@code jpegr_exif_struct}
+ * @param output_format flag for setting output color format. Its value configures the output
+ color format. The default value is {@code JPEGR_OUTPUT_HDR_LINEAR}.
+ ----------------------------------------------------------------------
+ | output_format | decoded color format to be written |
+ ----------------------------------------------------------------------
+ | JPEGR_OUTPUT_SDR | RGBA_8888 |
+ ----------------------------------------------------------------------
+ | JPEGR_OUTPUT_HDR_LINEAR | (default)RGBA_F16 linear |
+ ----------------------------------------------------------------------
+ | JPEGR_OUTPUT_HDR_PQ | RGBA_1010102 PQ |
+ ----------------------------------------------------------------------
+ | JPEGR_OUTPUT_HDR_HLG | RGBA_1010102 HLG |
+ ----------------------------------------------------------------------
+ * @param gainmap_image_ptr destination of the decoded gain map. The default value is NULL
+ where the decoder will do nothing about it. If configured not NULL
+ the decoder will write the decoded gain_map data into this
+ structure. The format is defined in
+ {@code jpegr_uncompressed_struct}.
+ * @param metadata destination of the decoded metadata. The default value is NULL where the
+ decoder will do nothing about it. If configured not NULL the decoder will
+ write metadata into this structure. the format of metadata is defined in
+ {@code ultrahdr_metadata_struct}.
+ * @return NO_ERROR if decoding succeeds, error code if error occurs.
+ */
+ status_t decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
+ float max_display_boost = FLT_MAX, jr_exif_ptr exif = nullptr,
+ ultrahdr_output_format output_format = ULTRAHDR_OUTPUT_HDR_LINEAR,
+ jr_uncompressed_ptr gainmap_image_ptr = nullptr,
+ ultrahdr_metadata_ptr metadata = nullptr);
+
+ /*
+ * Gets Info from JPEGR file without decoding it.
+ *
+ * This method only supports single gain map metadata values for fields that allow multi-channel
+ * metadata values.
+ *
+ * The output is filled jpegr_info structure
+ * @param jpegr_image_ptr compressed JPEGR image
+ * @param jpeg_image_info_ptr pointer to jpegr info struct. Members of jpegr_info
+ * are owned by the caller
+ * @return NO_ERROR if JPEGR parsing succeeds, error code otherwise
+ */
+ status_t getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr);
+
+protected:
+ /*
+ * This method is called in the encoding pipeline. It will take the uncompressed 8-bit and
+ * 10-bit yuv images as input, and calculate the uncompressed gain map. The input images
+ * must be the same resolution. The SDR input is assumed to use the sRGB transfer function.
+ *
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param hdr_tf transfer function of the HDR image
+ * @param metadata everything but "version" is filled in this struct
+ * @param dest location at which gain map image is stored (caller responsible for memory
+ of data).
+ * @param sdr_is_601 if true, then use BT.601 decoding of YUV regardless of SDR image gamut
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+ ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest,
+ bool sdr_is_601 = false);
+
+ /*
+ * This method is called in the decoding pipeline. It will take the uncompressed (decoded)
+ * 8-bit yuv image, the uncompressed (decoded) gain map, and extracted JPEG/R metadata as
+ * input, and calculate the 10-bit recovered image. The recovered output image is the same
+ * color gamut as the SDR image, with HLG transfer function, and is in RGBA1010102 data format.
+ * The SDR image is assumed to use the sRGB transfer function. The SDR image is also assumed to
+ * be a decoded JPEG for the purpose of YUV interpration.
+ *
+ * @param yuv420_image_ptr uncompressed SDR image in YUV_420 color format
+ * @param gainmap_image_ptr pointer to uncompressed gain map image struct.
+ * @param metadata JPEG/R metadata extracted from XMP.
+ * @param output_format flag for setting output color format. if set to
+ * {@code JPEGR_OUTPUT_SDR}, decoder will only decode the primary image
+ * which is SDR. Default value is JPEGR_OUTPUT_HDR_LINEAR.
+ * @param max_display_boost the maximum available boost supported by a display
+ * @param dest reconstructed HDR image
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
+ ultrahdr_output_format output_format, float max_display_boost,
+ jr_uncompressed_ptr dest);
+
+private:
+ /*
+ * This method is called in the encoding pipeline. It will encode the gain map.
+ *
+ * @param gainmap_image_ptr pointer to uncompressed gain map image struct
+ * @param jpeg_enc_obj_ptr helper resource to compress gain map
+ * @return NO_ERROR if encoding succeeds, error code if error occurs.
+ */
+ status_t compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
+ JpegEncoderHelper* jpeg_enc_obj_ptr);
+
+ /*
+ * This method is called to separate primary image and gain map image from JPEGR
+ *
+ * @param jpegr_image_ptr pointer to compressed JPEGR image.
+ * @param primary_jpg_image_ptr destination of primary image
+ * @param gainmap_jpg_image_ptr destination of compressed gain map image
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
+ jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr);
+
+ /*
+ * This method is called in the encoding pipeline. It will take the standard 8-bit JPEG image,
+ * the compressed gain map and optionally the exif package as inputs, and generate the XMP
+ * metadata, and finally append everything in the order of:
+ * SOI, APP2(EXIF) (if EXIF is from outside), APP2(XMP), primary image, gain map
+ *
+ * Note that in the final JPEG/R output, EXIF package will appear if ONLY ONE of the following
+ * conditions is fulfilled:
+ * (1) EXIF package is available from outside input. I.e. pExif != nullptr.
+ * (2) Input JPEG has EXIF.
+ * If both conditions are fulfilled, this method will return ERROR_JPEGR_INVALID_INPUT_TYPE
+ *
+ * @param primary_jpg_image_ptr destination of primary image
+ * @param gainmap_jpg_image_ptr destination of compressed gain map image
+ * @param (nullable) pExif EXIF package
+ * @param (nullable) pIcc ICC package
+ * @param icc_size length in bytes of ICC package
+ * @param metadata JPEG/R metadata to encode in XMP of the jpeg
+ * @param dest compressed JPEGR image
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif, void* pIcc,
+ size_t icc_size, ultrahdr_metadata_ptr metadata, jr_compressed_ptr dest);
+
+ /*
+ * This method will tone map a HDR image to an SDR image.
+ *
+ * @param src pointer to uncompressed HDR image struct. HDR image is expected to be
+ * in p010 color format
+ * @param dest pointer to store tonemapped SDR image
+ */
+ status_t toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest);
+
+ /*
+ * This method will convert a YUV420 image from one YUV encoding to another in-place (eg.
+ * Bt.709 to Bt.601 YUV encoding).
+ *
+ * src_encoding and dest_encoding indicate the encoding via the YUV conversion defined for that
+ * gamut. P3 indicates Rec.601, since this is how DataSpace encodes Display-P3 YUV data.
+ *
+ * @param image the YUV420 image to convert
+ * @param src_encoding input YUV encoding
+ * @param dest_encoding output YUV encoding
+ * @return NO_ERROR if calculation succeeds, error code if error occurs.
+ */
+ status_t convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
+ ultrahdr_color_gamut dest_encoding);
+
+ /*
+ * This method will check the validity of the input arguments.
+ *
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
+ * be in 420p color format
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the desitination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @return NO_ERROR if the input args are valid, error code is not valid.
+ */
+ status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest_ptr);
+
+ /*
+ * This method will check the validity of the input arguments.
+ *
+ * @param p010_image_ptr uncompressed HDR image in P010 color format
+ * @param yuv420_image_ptr pointer to uncompressed SDR image struct. HDR image is expected to
+ * be in 420p color format
+ * @param hdr_tf transfer function of the HDR image
+ * @param dest destination of the compressed JPEGR image. Please note that {@code maxLength}
+ * represents the maximum available size of the destination buffer, and it must be
+ * set before calling this method. If the encoded JPEGR size exceeds
+ * {@code maxLength}, this method will return {@code ERROR_JPEGR_BUFFER_TOO_SMALL}.
+ * @param quality target quality of the JPEG encoding, must be in range of 0-100 where 100 is
+ * the highest quality
+ * @return NO_ERROR if the input args are valid, error code is not valid.
+ */
+ status_t areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest,
+ int quality);
+};
+} // namespace android::ultrahdr
+
+#endif // ANDROID_ULTRAHDR_JPEGR_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
new file mode 100644
index 0000000000..5420e1c9cf
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/jpegrerrorcode.h
@@ -0,0 +1,61 @@
+/*
+ * 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 <utils/Errors.h>
+
+namespace android::ultrahdr {
+
+enum {
+ // status_t map for errors in the media framework
+ // OK or NO_ERROR or 0 represents no error.
+
+ // See system/core/include/utils/Errors.h
+ // System standard errors from -1 through (possibly) -133
+ //
+ // Errors with special meanings and side effects.
+ // INVALID_OPERATION: Operation attempted in an illegal state (will try to signal to app).
+ // DEAD_OBJECT: Signal from CodecBase to MediaCodec that MediaServer has died.
+ // NAME_NOT_FOUND: Signal from CodecBase to MediaCodec that the component was not found.
+
+ // JPEGR errors
+ JPEGR_IO_ERROR_BASE = -10000,
+ ERROR_JPEGR_INVALID_INPUT_TYPE = JPEGR_IO_ERROR_BASE,
+ ERROR_JPEGR_INVALID_OUTPUT_TYPE = JPEGR_IO_ERROR_BASE - 1,
+ ERROR_JPEGR_INVALID_NULL_PTR = JPEGR_IO_ERROR_BASE - 2,
+ ERROR_JPEGR_RESOLUTION_MISMATCH = JPEGR_IO_ERROR_BASE - 3,
+ ERROR_JPEGR_BUFFER_TOO_SMALL = JPEGR_IO_ERROR_BASE - 4,
+ ERROR_JPEGR_INVALID_COLORGAMUT = JPEGR_IO_ERROR_BASE - 5,
+ ERROR_JPEGR_INVALID_TRANS_FUNC = JPEGR_IO_ERROR_BASE - 6,
+ ERROR_JPEGR_INVALID_METADATA = JPEGR_IO_ERROR_BASE - 7,
+ ERROR_JPEGR_UNSUPPORTED_METADATA = JPEGR_IO_ERROR_BASE - 8,
+ ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND = JPEGR_IO_ERROR_BASE - 9,
+
+ JPEGR_RUNTIME_ERROR_BASE = -20000,
+ ERROR_JPEGR_ENCODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 1,
+ ERROR_JPEGR_DECODE_ERROR = JPEGR_RUNTIME_ERROR_BASE - 2,
+ ERROR_JPEGR_CALCULATION_ERROR = JPEGR_RUNTIME_ERROR_BASE - 3,
+ ERROR_JPEGR_METADATA_ERROR = JPEGR_RUNTIME_ERROR_BASE - 4,
+ ERROR_JPEGR_TONEMAP_ERROR = JPEGR_RUNTIME_ERROR_BASE - 5,
+
+ ERROR_JPEGR_UNSUPPORTED_FEATURE = -20000,
+};
+
+} // namespace android::ultrahdr
+
+#endif // ANDROID_ULTRAHDR_JPEGRERRORCODE_H
diff --git a/libs/ultrahdr/include/ultrahdr/jpegrutils.h b/libs/ultrahdr/include/ultrahdr/jpegrutils.h
new file mode 100644
index 0000000000..4ab664e798
--- /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 <ultrahdr/jpegr.h>
+#include <utils/RefBase.h>
+
+#include <sstream>
+#include <stdint.h>
+#include <string>
+#include <cstdio>
+
+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<uint16_t>((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
+ *
+ * <x:xmpmeta
+ * xmlns:x="adobe:ns:meta/"
+ * x:xmptk="Adobe XMP Core 5.1.2">
+ * <rdf:RDF
+ * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ * <rdf:Description
+ * xmlns:Container="http://ns.google.com/photos/1.0/container/"
+ * xmlns:Item="http://ns.google.com/photos/1.0/container/item/"
+ * xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
+ * hdrgm:Version="1">
+ * <Container:Directory>
+ * <rdf:Seq>
+ * <rdf:li
+ * rdf:parseType="Resource">
+ * <Container:Item
+ * Item:Semantic="Primary"
+ * Item:Mime="image/jpeg"/>
+ * </rdf:li>
+ * <rdf:li
+ * rdf:parseType="Resource">
+ * <Container:Item
+ * Item:Semantic="GainMap"
+ * Item:Mime="image/jpeg"
+ * Item:Length="1000"/>
+ * </rdf:li>
+ * </rdf:Seq>
+ * </Container:Directory>
+ * </rdf:Description>
+ * </rdf:RDF>
+ * </x:xmpmeta>
+ *
+ * @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
+ *
+ * <x:xmpmeta
+ * xmlns:x="adobe:ns:meta/"
+ * x:xmptk="Adobe XMP Core 5.1.2">
+ * <rdf:RDF
+ * xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ * <rdf:Description
+ * xmlns:hdrgm="http://ns.adobe.com/hdr-gain-map/1.0/"
+ * hdrgm:Version="1"
+ * hdrgm:GainMapMin="-1"
+ * hdrgm:GainMapMax="3"
+ * hdrgm:Gamma="1"
+ * hdrgm:OffsetSDR="0"
+ * hdrgm:OffsetHDR="0"
+ * hdrgm:HDRCapacityMin="0"
+ * hdrgm:HDRCapacityMax="3"
+ * hdrgm:BaseRenditionIsHDR="False"/>
+ * </rdf:RDF>
+ * </x:xmpmeta>
+ *
+ * @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 0000000000..c5bd09d6f2
--- /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 <ultrahdr/jpegrutils.h>
+
+#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<DataStruct> 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 0000000000..0252391a86
--- /dev/null
+++ b/libs/ultrahdr/include/ultrahdr/ultrahdr.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_ULTRAHDR_ULTRAHDR_H
+#define ANDROID_ULTRAHDR_ULTRAHDR_H
+
+#include <string>
+
+namespace android::ultrahdr {
+// Color gamuts for image data
+typedef enum {
+ ULTRAHDR_COLORGAMUT_UNSPECIFIED = -1,
+ ULTRAHDR_COLORGAMUT_BT709,
+ ULTRAHDR_COLORGAMUT_P3,
+ ULTRAHDR_COLORGAMUT_BT2100,
+ ULTRAHDR_COLORGAMUT_MAX = ULTRAHDR_COLORGAMUT_BT2100,
+} ultrahdr_color_gamut;
+
+// Transfer functions for image data
+// TODO: TF LINEAR is deprecated, remove this enum and the code surrounding it.
+typedef enum {
+ ULTRAHDR_TF_UNSPECIFIED = -1,
+ ULTRAHDR_TF_LINEAR = 0,
+ ULTRAHDR_TF_HLG = 1,
+ ULTRAHDR_TF_PQ = 2,
+ ULTRAHDR_TF_SRGB = 3,
+ ULTRAHDR_TF_MAX = ULTRAHDR_TF_SRGB,
+} ultrahdr_transfer_function;
+
+// Target output formats for decoder
+typedef enum {
+ ULTRAHDR_OUTPUT_UNSPECIFIED = -1,
+ ULTRAHDR_OUTPUT_SDR, // SDR in RGBA_8888 color format
+ ULTRAHDR_OUTPUT_HDR_LINEAR, // HDR in F16 color format (linear)
+ ULTRAHDR_OUTPUT_HDR_PQ, // HDR in RGBA_1010102 color format (PQ transfer function)
+ ULTRAHDR_OUTPUT_HDR_HLG, // HDR in RGBA_1010102 color format (HLG transfer function)
+ ULTRAHDR_OUTPUT_MAX = ULTRAHDR_OUTPUT_HDR_HLG,
+} ultrahdr_output_format;
+
+/*
+ * Holds information for gain map related metadata.
+ *
+ * Not: all values stored in linear. This differs from the metadata encoding in XMP, where
+ * maxContentBoost (aka gainMapMax), minContentBoost (aka gainMapMin), hdrCapacityMin, and
+ * hdrCapacityMax are stored in log2 space.
+ */
+struct ultrahdr_metadata_struct {
+ // Ultra HDR format version
+ std::string version;
+ // Max Content Boost for the map
+ float maxContentBoost;
+ // Min Content Boost for the map
+ float minContentBoost;
+ // Gamma of the map data
+ float gamma;
+ // Offset for SDR data in map calculations
+ float offsetSdr;
+ // Offset for HDR data in map calculations
+ float offsetHdr;
+ // HDR capacity to apply the map at all
+ float hdrCapacityMin;
+ // HDR capacity to apply the map completely
+ float hdrCapacityMax;
+};
+typedef struct ultrahdr_metadata_struct* ultrahdr_metadata_ptr;
+
+} // namespace android::ultrahdr
+
+#endif //ANDROID_ULTRAHDR_ULTRAHDR_H
diff --git a/libs/ultrahdr/jpegdecoderhelper.cpp b/libs/ultrahdr/jpegdecoderhelper.cpp
new file mode 100644
index 0000000000..2e7940cc2c
--- /dev/null
+++ b/libs/ultrahdr/jpegdecoderhelper.cpp
@@ -0,0 +1,543 @@
+/*
+ * 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 <ultrahdr/jpegdecoderhelper.h>
+
+#include <utils/Log.h>
+
+#include <errno.h>
+#include <setjmp.h>
+#include <string>
+
+using namespace std;
+
+namespace android::ultrahdr {
+
+#define ALIGNM(x, m) ((((x) + ((m)-1)) / (m)) * (m))
+
+const uint32_t kAPP0Marker = JPEG_APP0; // JFIF
+const uint32_t kAPP1Marker = JPEG_APP0 + 1; // EXIF, XMP
+const uint32_t kAPP2Marker = JPEG_APP0 + 2; // ICC
+
+constexpr uint32_t kICCMarkerHeaderSize = 14;
+constexpr uint8_t kICCSig[] = {
+ 'I', 'C', 'C', '_', 'P', 'R', 'O', 'F', 'I', 'L', 'E', '\0',
+};
+constexpr uint8_t kXmpNameSpace[] = {
+ 'h', 't', 't', 'p', ':', '/', '/', 'n', 's', '.', 'a', 'd', 'o', 'b', 'e',
+ '.', 'c', 'o', 'm', '/', 'x', 'a', 'p', '/', '1', '.', '0', '/', '\0',
+};
+constexpr uint8_t kExifIdCode[] = {
+ 'E', 'x', 'i', 'f', '\0', '\0',
+};
+
+struct jpegr_source_mgr : jpeg_source_mgr {
+ jpegr_source_mgr(const uint8_t* ptr, int len);
+ ~jpegr_source_mgr();
+
+ const uint8_t* mBufferPtr;
+ size_t mBufferLength;
+};
+
+struct jpegrerror_mgr {
+ struct jpeg_error_mgr pub;
+ jmp_buf setjmp_buffer;
+};
+
+static void jpegr_init_source(j_decompress_ptr cinfo) {
+ jpegr_source_mgr* src = static_cast<jpegr_source_mgr*>(cinfo->src);
+ src->next_input_byte = static_cast<const JOCTET*>(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<jpegr_source_mgr*>(cinfo->src);
+
+ if (num_bytes > static_cast<long>(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<jpegrerror_mgr*>(cinfo->err);
+ longjmp(err->setjmp_buffer, 1);
+}
+
+JpegDecoderHelper::JpegDecoderHelper() {}
+
+JpegDecoderHelper::~JpegDecoderHelper() {}
+
+bool JpegDecoderHelper::decompressImage(const void* image, int length, bool decodeToRGBA) {
+ if (image == nullptr || length <= 0) {
+ ALOGE("Image size can not be handled: %d", length);
+ return false;
+ }
+ mResultBuffer.clear();
+ mXMPBuffer.clear();
+ return decode(image, length, decodeToRGBA);
+}
+
+void* JpegDecoderHelper::getDecompressedImagePtr() {
+ return mResultBuffer.data();
+}
+
+size_t JpegDecoderHelper::getDecompressedImageSize() {
+ return mResultBuffer.size();
+}
+
+void* JpegDecoderHelper::getXMPPtr() {
+ return mXMPBuffer.data();
+}
+
+size_t JpegDecoderHelper::getXMPSize() {
+ return mXMPBuffer.size();
+}
+
+void* JpegDecoderHelper::getEXIFPtr() {
+ return mEXIFBuffer.data();
+}
+
+size_t JpegDecoderHelper::getEXIFSize() {
+ return mEXIFBuffer.size();
+}
+
+void* JpegDecoderHelper::getICCPtr() {
+ return mICCBuffer.data();
+}
+
+size_t JpegDecoderHelper::getICCSize() {
+ return mICCBuffer.size();
+}
+
+size_t JpegDecoderHelper::getDecompressedImageWidth() {
+ return mWidth;
+}
+
+size_t JpegDecoderHelper::getDecompressedImageHeight() {
+ return mHeight;
+}
+
+// Here we only handle the first EXIF package, and in theary EXIF (or JFIF) must be the first
+// in the image file.
+// We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+// two bytes of package length which is stored in marker->original_length, and the real data
+// which is stored in marker->data.
+bool JpegDecoderHelper::extractEXIF(const void* image, int length) {
+ jpeg_decompress_struct cinfo;
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ jpegrerror_mgr myerr;
+
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+
+ cinfo.src = &mgr;
+ jpeg_read_header(&cinfo, TRUE);
+
+ size_t pos = 2; // position after SOI
+ for (jpeg_marker_struct* marker = cinfo.marker_list;
+ marker;
+ marker = marker->next) {
+
+ pos += 4;
+ pos += marker->original_length;
+
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+
+ if (len > sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+ mEXIFBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+ mExifPos = pos - marker->original_length;
+ break;
+ }
+ }
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
+bool JpegDecoderHelper::decode(const void* image, int length, bool decodeToRGBA) {
+ bool status = true;
+ jpeg_decompress_struct cinfo;
+ jpegrerror_mgr myerr;
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kAPP0Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ cinfo.src = &mgr;
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
+ // Save XMP data, EXIF data, and ICC data.
+ // Here we only handle the first XMP / EXIF / ICC package.
+ // We assume that all packages are starting with two bytes marker (eg FF E1 for EXIF package),
+ // two bytes of package length which is stored in marker->original_length, and the real data
+ // which is stored in marker->data.
+ bool exifAppears = false;
+ bool xmpAppears = false;
+ bool iccAppears = false;
+ size_t pos = 2; // position after SOI
+ for (jpeg_marker_struct* marker = cinfo.marker_list;
+ marker && !(exifAppears && xmpAppears && iccAppears);
+ marker = marker->next) {
+ pos += 4;
+ pos += marker->original_length;
+ if (marker->marker != kAPP1Marker && marker->marker != kAPP2Marker) {
+ continue;
+ }
+ const unsigned int len = marker->data_length;
+ if (!xmpAppears &&
+ len > sizeof(kXmpNameSpace) &&
+ !memcmp(marker->data, kXmpNameSpace, sizeof(kXmpNameSpace))) {
+ mXMPBuffer.resize(len+1, 0);
+ memcpy(static_cast<void*>(mXMPBuffer.data()), marker->data, len);
+ xmpAppears = true;
+ } else if (!exifAppears &&
+ len > sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+ mEXIFBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mEXIFBuffer.data()), marker->data, len);
+ exifAppears = true;
+ mExifPos = pos - marker->original_length;
+ } else if (!iccAppears &&
+ len > sizeof(kICCSig) &&
+ !memcmp(marker->data, kICCSig, sizeof(kICCSig))) {
+ mICCBuffer.resize(len, 0);
+ memcpy(static_cast<void*>(mICCBuffer.data()), marker->data, len);
+ iccAppears = true;
+ }
+ }
+
+ mWidth = cinfo.image_width;
+ mHeight = cinfo.image_height;
+ if (mWidth > kMaxWidth || mHeight > kMaxHeight) {
+ status = false;
+ goto CleanUp;
+ }
+
+ if (decodeToRGBA) {
+ // The primary image is expected to be yuv420 sampling
+ if (cinfo.jpeg_color_space != JCS_YCbCr) {
+ status = false;
+ ALOGE("%s: decodeToRGBA unexpected jpeg color space ", __func__);
+ goto CleanUp;
+ }
+ if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+ cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+ cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
+ status = false;
+ ALOGE("%s: decodeToRGBA unexpected primary image sub-sampling", __func__);
+ goto CleanUp;
+ }
+ // 4 bytes per pixel
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 4);
+ cinfo.out_color_space = JCS_EXT_RGBA;
+ } else {
+ if (cinfo.jpeg_color_space == JCS_YCbCr) {
+ if (cinfo.comp_info[0].h_samp_factor != 2 || cinfo.comp_info[0].v_samp_factor != 2 ||
+ cinfo.comp_info[1].h_samp_factor != 1 || cinfo.comp_info[1].v_samp_factor != 1 ||
+ cinfo.comp_info[2].h_samp_factor != 1 || cinfo.comp_info[2].v_samp_factor != 1) {
+ status = false;
+ ALOGE("%s: decoding to YUV only supports 4:2:0 subsampling", __func__);
+ goto CleanUp;
+ }
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height * 3 / 2, 0);
+ } else if (cinfo.jpeg_color_space == JCS_GRAYSCALE) {
+ mResultBuffer.resize(cinfo.image_width * cinfo.image_height, 0);
+ } else {
+ status = false;
+ ALOGE("%s: decodeToYUV unexpected jpeg color space", __func__);
+ goto CleanUp;
+ }
+ cinfo.out_color_space = cinfo.jpeg_color_space;
+ cinfo.raw_data_out = TRUE;
+ }
+
+ cinfo.dct_method = JDCT_ISLOW;
+ jpeg_start_decompress(&cinfo);
+ if (!decompress(&cinfo, static_cast<const uint8_t*>(mResultBuffer.data()),
+ cinfo.jpeg_color_space == JCS_GRAYSCALE)) {
+ status = false;
+ goto CleanUp;
+ }
+
+CleanUp:
+ jpeg_finish_decompress(&cinfo);
+ jpeg_destroy_decompress(&cinfo);
+
+ return status;
+}
+
+bool JpegDecoderHelper::decompress(jpeg_decompress_struct* cinfo, const uint8_t* dest,
+ bool isSingleChannel) {
+ return isSingleChannel
+ ? decompressSingleChannel(cinfo, dest)
+ : ((cinfo->out_color_space == JCS_EXT_RGBA) ? decompressRGBA(cinfo, dest)
+ : decompressYUV(cinfo, dest));
+}
+
+bool JpegDecoderHelper::getCompressedImageParameters(const void* image, int length, size_t* pWidth,
+ size_t* pHeight, std::vector<uint8_t>* iccData,
+ std::vector<uint8_t>* exifData) {
+ jpeg_decompress_struct cinfo;
+ jpegrerror_mgr myerr;
+ cinfo.err = jpeg_std_error(&myerr.pub);
+ myerr.pub.error_exit = jpegrerror_exit;
+ if (setjmp(myerr.setjmp_buffer)) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+ jpeg_create_decompress(&cinfo);
+
+ jpeg_save_markers(&cinfo, kAPP1Marker, 0xFFFF);
+ jpeg_save_markers(&cinfo, kAPP2Marker, 0xFFFF);
+
+ jpegr_source_mgr mgr(static_cast<const uint8_t*>(image), length);
+ cinfo.src = &mgr;
+ if (jpeg_read_header(&cinfo, TRUE) != JPEG_HEADER_OK) {
+ jpeg_destroy_decompress(&cinfo);
+ return false;
+ }
+
+ if (pWidth != nullptr) {
+ *pWidth = cinfo.image_width;
+ }
+ if (pHeight != nullptr) {
+ *pHeight = cinfo.image_height;
+ }
+
+ if (iccData != nullptr) {
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker; marker = marker->next) {
+ if (marker->marker != kAPP2Marker) {
+ continue;
+ }
+ if (marker->data_length <= kICCMarkerHeaderSize ||
+ memcmp(marker->data, kICCSig, sizeof(kICCSig)) != 0) {
+ continue;
+ }
+
+ iccData->insert(iccData->end(), marker->data, marker->data + marker->data_length);
+ }
+ }
+
+ if (exifData != nullptr) {
+ bool exifAppears = false;
+ for (jpeg_marker_struct* marker = cinfo.marker_list; marker && !exifAppears;
+ marker = marker->next) {
+ if (marker->marker != kAPP1Marker) {
+ continue;
+ }
+
+ const unsigned int len = marker->data_length;
+ if (len >= sizeof(kExifIdCode) &&
+ !memcmp(marker->data, kExifIdCode, sizeof(kExifIdCode))) {
+ exifData->resize(len, 0);
+ memcpy(static_cast<void*>(exifData->data()), marker->data, len);
+ exifAppears = true;
+ }
+ }
+ }
+
+ jpeg_destroy_decompress(&cinfo);
+ return true;
+}
+
+bool JpegDecoderHelper::decompressRGBA(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+ JSAMPLE* out = (JSAMPLE*)dest;
+
+ while (cinfo->output_scanline < cinfo->image_height) {
+ if (1 != jpeg_read_scanlines(cinfo, &out, 1)) return false;
+ out += cinfo->image_width * 4;
+ }
+ return true;
+}
+
+bool JpegDecoderHelper::decompressYUV(jpeg_decompress_struct* cinfo, const uint8_t* dest) {
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPROW cb[kCompressBatchSize / 2];
+ JSAMPROW cr[kCompressBatchSize / 2];
+ JSAMPARRAY planes[3]{y, cb, cr};
+
+ size_t y_plane_size = cinfo->image_width * cinfo->image_height;
+ size_t uv_plane_size = y_plane_size / 4;
+ uint8_t* y_plane = const_cast<uint8_t*>(dest);
+ uint8_t* u_plane = const_cast<uint8_t*>(dest + y_plane_size);
+ uint8_t* v_plane = const_cast<uint8_t*>(dest + y_plane_size + uv_plane_size);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(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<uint8_t[]> 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<uint8_t[]>(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<uint8_t*>(dest);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(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<uint8_t[]> 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<uint8_t[]>(mcu_row_size);
+ y_plane_intrm = buffer_intrm.get();
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ y_intrm[i] = y_plane_intrm + i * aligned_width;
+ }
+ }
+
+ while (cinfo->output_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->output_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * cinfo->image_width;
+ } else {
+ y[i] = empty.get();
+ }
+ }
+
+ int processed = jpeg_read_raw_data(cinfo, is_width_aligned ? planes : planes_intrm,
+ kCompressBatchSize);
+ if (processed != kCompressBatchSize / 2) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ if (!is_width_aligned) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ memcpy(y[i], y_intrm[i], cinfo->image_width);
+ }
+ }
+ }
+ return true;
+}
+
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegencoderhelper.cpp b/libs/ultrahdr/jpegencoderhelper.cpp
new file mode 100644
index 0000000000..13ae7424d5
--- /dev/null
+++ b/libs/ultrahdr/jpegencoderhelper.cpp
@@ -0,0 +1,273 @@
+/*
+ * 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 <cstring>
+#include <memory>
+#include <vector>
+
+#include <ultrahdr/jpegencoderhelper.h>
+#include <utils/Log.h>
+
+namespace android::ultrahdr {
+
+// The destination manager that can access |mResultBuffer| in JpegEncoderHelper.
+struct destination_mgr {
+ struct jpeg_destination_mgr mgr;
+ JpegEncoderHelper* encoder;
+};
+
+JpegEncoderHelper::JpegEncoderHelper() {}
+
+JpegEncoderHelper::~JpegEncoderHelper() {}
+
+bool JpegEncoderHelper::compressImage(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
+ int height, int lumaStride, int chromaStride, int quality,
+ const void* iccBuffer, unsigned int iccSize) {
+ mResultBuffer.clear();
+ if (!encode(yBuffer, uvBuffer, width, height, lumaStride, chromaStride, quality, iccBuffer,
+ iccSize)) {
+ return false;
+ }
+ ALOGI("Compressed JPEG: %d[%dx%d] -> %zu bytes", (width * height * 12) / 8, width, height,
+ mResultBuffer.size());
+ return true;
+}
+
+void* JpegEncoderHelper::getCompressedImagePtr() {
+ return mResultBuffer.data();
+}
+
+size_t JpegEncoderHelper::getCompressedImageSize() {
+ return mResultBuffer.size();
+}
+
+void JpegEncoderHelper::initDestination(j_compress_ptr cinfo) {
+ destination_mgr* dest = reinterpret_cast<destination_mgr*>(cinfo->dest);
+ std::vector<JOCTET>& 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<destination_mgr*>(cinfo->dest);
+ std::vector<JOCTET>& 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<destination_mgr*>(cinfo->dest);
+ std::vector<JOCTET>& buffer = dest->encoder->mResultBuffer;
+ buffer.resize(buffer.size() - dest->mgr.free_in_buffer);
+}
+
+void JpegEncoderHelper::outputErrorMessage(j_common_ptr cinfo) {
+ char buffer[JMSG_LENGTH_MAX];
+
+ /* Create the message */
+ (*cinfo->err->format_message)(cinfo, buffer);
+ ALOGE("%s\n", buffer);
+}
+
+bool JpegEncoderHelper::encode(const uint8_t* yBuffer, const uint8_t* uvBuffer, int width,
+ int height, int lumaStride, int chromaStride, int quality,
+ const void* iccBuffer, unsigned int iccSize) {
+ jpeg_compress_struct cinfo;
+ jpeg_error_mgr jerr;
+
+ cinfo.err = jpeg_std_error(&jerr);
+ cinfo.err->output_message = &outputErrorMessage;
+ jpeg_create_compress(&cinfo);
+ setJpegDestination(&cinfo);
+ setJpegCompressStruct(width, height, quality, &cinfo, uvBuffer == nullptr);
+ jpeg_start_compress(&cinfo, TRUE);
+ if (iccBuffer != nullptr && iccSize > 0) {
+ jpeg_write_marker(&cinfo, JPEG_APP0 + 2, static_cast<const JOCTET*>(iccBuffer), iccSize);
+ }
+ bool status = cinfo.num_components == 1
+ ? compressY(&cinfo, yBuffer, lumaStride)
+ : compressYuv(&cinfo, yBuffer, uvBuffer, lumaStride, chromaStride);
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+
+ return status;
+}
+
+void JpegEncoderHelper::setJpegDestination(jpeg_compress_struct* cinfo) {
+ destination_mgr* dest = static_cast<struct destination_mgr*>(
+ (*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<struct jpeg_destination_mgr*>(dest);
+}
+
+void JpegEncoderHelper::setJpegCompressStruct(int width, int height, int quality,
+ jpeg_compress_struct* cinfo, bool isSingleChannel) {
+ cinfo->image_width = width;
+ cinfo->image_height = height;
+ cinfo->input_components = isSingleChannel ? 1 : 3;
+ cinfo->in_color_space = isSingleChannel ? JCS_GRAYSCALE : JCS_YCbCr;
+ jpeg_set_defaults(cinfo);
+ jpeg_set_quality(cinfo, quality, TRUE);
+ cinfo->raw_data_in = TRUE;
+ cinfo->dct_method = JDCT_ISLOW;
+ cinfo->comp_info[0].h_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
+ cinfo->comp_info[0].v_samp_factor = cinfo->in_color_space == JCS_GRAYSCALE ? 1 : 2;
+ for (int i = 1; i < cinfo->num_components; i++) {
+ cinfo->comp_info[i].h_samp_factor = 1;
+ cinfo->comp_info[i].v_samp_factor = 1;
+ }
+}
+
+bool JpegEncoderHelper::compressYuv(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
+ const uint8_t* uvBuffer, int lumaStride, int chromaStride) {
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPROW cb[kCompressBatchSize / 2];
+ JSAMPROW cr[kCompressBatchSize / 2];
+ JSAMPARRAY planes[3]{y, cb, cr};
+
+ size_t y_plane_size = lumaStride * cinfo->image_height;
+ size_t u_plane_size = chromaStride * cinfo->image_height / 2;
+ uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
+ uint8_t* u_plane = const_cast<uint8_t*>(uvBuffer);
+ uint8_t* v_plane = const_cast<uint8_t*>(u_plane + u_plane_size);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
+ memset(empty.get(), 0, cinfo->image_width);
+
+ const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
+ const bool need_padding = (lumaStride < aligned_width);
+ std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
+ uint8_t* y_plane_intrm = nullptr;
+ uint8_t* u_plane_intrm = nullptr;
+ uint8_t* v_plane_intrm = nullptr;
+ JSAMPROW y_intrm[kCompressBatchSize];
+ JSAMPROW cb_intrm[kCompressBatchSize / 2];
+ JSAMPROW cr_intrm[kCompressBatchSize / 2];
+ JSAMPARRAY planes_intrm[3]{y_intrm, cb_intrm, cr_intrm};
+ if (need_padding) {
+ size_t mcu_row_size = aligned_width * kCompressBatchSize * 3 / 2;
+ buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
+ y_plane_intrm = buffer_intrm.get();
+ u_plane_intrm = y_plane_intrm + (aligned_width * kCompressBatchSize);
+ v_plane_intrm = u_plane_intrm + (aligned_width * kCompressBatchSize) / 4;
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ y_intrm[i] = y_plane_intrm + i * aligned_width;
+ memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
+ }
+ for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+ int offset_intrm = i * (aligned_width / 2);
+ cb_intrm[i] = u_plane_intrm + offset_intrm;
+ cr_intrm[i] = v_plane_intrm + offset_intrm;
+ memset(cb_intrm[i] + cinfo->image_width / 2, 0,
+ (aligned_width - cinfo->image_width) / 2);
+ memset(cr_intrm[i] + cinfo->image_width / 2, 0,
+ (aligned_width - cinfo->image_width) / 2);
+ }
+ }
+
+ while (cinfo->next_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->next_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * lumaStride;
+ } else {
+ y[i] = empty.get();
+ }
+ if (need_padding) {
+ memcpy(y_intrm[i], y[i], cinfo->image_width);
+ }
+ }
+ // cb, cr only have half scanlines
+ for (int i = 0; i < kCompressBatchSize / 2; ++i) {
+ size_t scanline = cinfo->next_scanline / 2 + i;
+ if (scanline < cinfo->image_height / 2) {
+ int offset = scanline * chromaStride;
+ cb[i] = u_plane + offset;
+ cr[i] = v_plane + offset;
+ } else {
+ cb[i] = cr[i] = empty.get();
+ }
+ if (need_padding) {
+ memcpy(cb_intrm[i], cb[i], cinfo->image_width / 2);
+ memcpy(cr_intrm[i], cr[i], cinfo->image_width / 2);
+ }
+ }
+ int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
+ kCompressBatchSize);
+ if (processed != kCompressBatchSize) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ }
+ return true;
+}
+
+bool JpegEncoderHelper::compressY(jpeg_compress_struct* cinfo, const uint8_t* yBuffer,
+ int lumaStride) {
+ JSAMPROW y[kCompressBatchSize];
+ JSAMPARRAY planes[1]{y};
+
+ uint8_t* y_plane = const_cast<uint8_t*>(yBuffer);
+ std::unique_ptr<uint8_t[]> empty = std::make_unique<uint8_t[]>(cinfo->image_width);
+ memset(empty.get(), 0, cinfo->image_width);
+
+ const int aligned_width = ALIGNM(cinfo->image_width, kCompressBatchSize);
+ const bool need_padding = (lumaStride < aligned_width);
+ std::unique_ptr<uint8_t[]> buffer_intrm = nullptr;
+ uint8_t* y_plane_intrm = nullptr;
+ uint8_t* u_plane_intrm = nullptr;
+ JSAMPROW y_intrm[kCompressBatchSize];
+ JSAMPARRAY planes_intrm[]{y_intrm};
+ if (need_padding) {
+ size_t mcu_row_size = aligned_width * kCompressBatchSize;
+ buffer_intrm = std::make_unique<uint8_t[]>(mcu_row_size);
+ y_plane_intrm = buffer_intrm.get();
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ y_intrm[i] = y_plane_intrm + i * aligned_width;
+ memset(y_intrm[i] + cinfo->image_width, 0, aligned_width - cinfo->image_width);
+ }
+ }
+
+ while (cinfo->next_scanline < cinfo->image_height) {
+ for (int i = 0; i < kCompressBatchSize; ++i) {
+ size_t scanline = cinfo->next_scanline + i;
+ if (scanline < cinfo->image_height) {
+ y[i] = y_plane + scanline * lumaStride;
+ } else {
+ y[i] = empty.get();
+ }
+ if (need_padding) {
+ memcpy(y_intrm[i], y[i], cinfo->image_width);
+ }
+ }
+ int processed = jpeg_write_raw_data(cinfo, need_padding ? planes_intrm : planes,
+ kCompressBatchSize);
+ if (processed != kCompressBatchSize / 2) {
+ ALOGE("Number of processed lines does not equal input lines.");
+ return false;
+ }
+ }
+ return true;
+}
+
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegr.cpp b/libs/ultrahdr/jpegr.cpp
new file mode 100644
index 0000000000..3d70fcea71
--- /dev/null
+++ b/libs/ultrahdr/jpegr.cpp
@@ -0,0 +1,1503 @@
+/*
+ * 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 <cmath>
+#include <condition_variable>
+#include <deque>
+#include <memory>
+#include <mutex>
+#include <thread>
+
+#include <ultrahdr/gainmapmath.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegrutils.h>
+#include <ultrahdr/multipictureformat.h>
+
+#include <image_io/base/data_segment_data_source.h>
+#include <image_io/jpeg/jpeg_info.h>
+#include <image_io/jpeg/jpeg_info_builder.h>
+#include <image_io/jpeg/jpeg_marker.h>
+#include <image_io/jpeg/jpeg_scanner.h>
+
+#include <utils/Log.h>
+
+using namespace std;
+using namespace photos_editing_formats::image_io;
+
+namespace android::ultrahdr {
+
+#define USE_SRGB_INVOETF_LUT 1
+#define USE_HLG_OETF_LUT 1
+#define USE_PQ_OETF_LUT 1
+#define USE_HLG_INVOETF_LUT 1
+#define USE_PQ_INVOETF_LUT 1
+#define USE_APPLY_GAIN_LUT 1
+
+#define JPEGR_CHECK(x) \
+ { \
+ status_t status = (x); \
+ if ((status) != NO_ERROR) { \
+ return status; \
+ } \
+ }
+
+// JPEG compress quality (0 ~ 100) for gain map
+static const int kMapCompressQuality = 85;
+
+#define CONFIG_MULTITHREAD 1
+int GetCPUCoreCount() {
+ int cpuCoreCount = 1;
+#if CONFIG_MULTITHREAD
+#if defined(_SC_NPROCESSORS_ONLN)
+ cpuCoreCount = sysconf(_SC_NPROCESSORS_ONLN);
+#else
+ // _SC_NPROC_ONLN must be defined...
+ cpuCoreCount = sysconf(_SC_NPROC_ONLN);
+#endif
+#endif
+ return cpuCoreCount;
+}
+
+/*
+ * Helper function copies the JPEG image from without EXIF.
+ *
+ * @param pDest destination of the data to be written.
+ * @param pSource source of data being written.
+ * @param exif_pos position of the EXIF package, which is aligned with jpegdecoder.getEXIFPos().
+ * (4 bytes offset to FF sign, the byte after FF E1 XX XX <this byte>).
+ * @param exif_size exif size without the initial 4 bytes, aligned with jpegdecoder.getEXIFSize().
+ */
+static void copyJpegWithoutExif(jr_compressed_ptr pDest,
+ jr_compressed_ptr pSource,
+ size_t exif_pos,
+ size_t exif_size) {
+ const size_t exif_offset = 4; //exif_pos has 4 bytes offset to the FF sign
+ pDest->length = pSource->length - exif_size - exif_offset;
+ pDest->data = new uint8_t[pDest->length];
+ pDest->maxLength = pDest->length;
+ pDest->colorGamut = pSource->colorGamut;
+ memcpy(pDest->data, pSource->data, exif_pos - exif_offset);
+ memcpy((uint8_t*)pDest->data + exif_pos - exif_offset,
+ (uint8_t*)pSource->data + exif_pos + exif_size,
+ pSource->length - exif_pos - exif_size);
+}
+
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest_ptr) {
+ if (p010_image_ptr == nullptr || p010_image_ptr->data == nullptr) {
+ ALOGE("Received nullptr for input p010 image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (p010_image_ptr->width % 2 != 0 || p010_image_ptr->height % 2 != 0) {
+ ALOGE("Image dimensions cannot be odd, image dimensions %dx%d", p010_image_ptr->width,
+ p010_image_ptr->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (p010_image_ptr->width < kMinWidth || p010_image_ptr->height < kMinHeight) {
+ ALOGE("Image dimensions cannot be less than %dx%d, image dimensions %dx%d", kMinWidth,
+ kMinHeight, p010_image_ptr->width, p010_image_ptr->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (p010_image_ptr->width > kMaxWidth || p010_image_ptr->height > kMaxHeight) {
+ ALOGE("Image dimensions cannot be larger than %dx%d, image dimensions %dx%d", kMaxWidth,
+ kMaxHeight, p010_image_ptr->width, p010_image_ptr->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (p010_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ p010_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized p010 color gamut %d", p010_image_ptr->colorGamut);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (p010_image_ptr->luma_stride != 0 && p010_image_ptr->luma_stride < p010_image_ptr->width) {
+ ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
+ p010_image_ptr->luma_stride, p010_image_ptr->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (p010_image_ptr->chroma_data != nullptr &&
+ p010_image_ptr->chroma_stride < p010_image_ptr->width) {
+ ALOGE("Chroma stride must not be smaller than width, stride=%d, width=%d",
+ p010_image_ptr->chroma_stride, p010_image_ptr->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (dest_ptr == nullptr || dest_ptr->data == nullptr) {
+ ALOGE("Received nullptr for destination");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (hdr_tf <= ULTRAHDR_TF_UNSPECIFIED || hdr_tf > ULTRAHDR_TF_MAX || hdr_tf == ULTRAHDR_TF_SRGB) {
+ ALOGE("Invalid hdr transfer function %d", hdr_tf);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (yuv420_image_ptr == nullptr) {
+ return NO_ERROR;
+ }
+ if (yuv420_image_ptr->data == nullptr) {
+ ALOGE("Received nullptr for uncompressed 420 image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (yuv420_image_ptr->luma_stride != 0 &&
+ yuv420_image_ptr->luma_stride < yuv420_image_ptr->width) {
+ ALOGE("Luma stride must not be smaller than width, stride=%d, width=%d",
+ yuv420_image_ptr->luma_stride, yuv420_image_ptr->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (yuv420_image_ptr->chroma_data != nullptr &&
+ yuv420_image_ptr->chroma_stride < yuv420_image_ptr->width / 2) {
+ ALOGE("Chroma stride must not be smaller than (width / 2), stride=%d, width=%d",
+ yuv420_image_ptr->chroma_stride, yuv420_image_ptr->width);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (p010_image_ptr->width != yuv420_image_ptr->width ||
+ p010_image_ptr->height != yuv420_image_ptr->height) {
+ ALOGE("Image resolutions mismatch: P010: %dx%d, YUV420: %dx%d", p010_image_ptr->width,
+ p010_image_ptr->height, yuv420_image_ptr->width, yuv420_image_ptr->height);
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+ if (yuv420_image_ptr->colorGamut <= ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ yuv420_image_ptr->colorGamut > ULTRAHDR_COLORGAMUT_MAX) {
+ ALOGE("Unrecognized 420 color gamut %d", yuv420_image_ptr->colorGamut);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ return NO_ERROR;
+}
+
+status_t JpegR::areInputArgumentsValid(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest_ptr, int quality) {
+ if (quality < 0 || quality > 100) {
+ ALOGE("quality factor is out side range [0-100], quality factor : %d", quality);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ return areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest_ptr);
+}
+
+/* Encode API-0 */
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
+ // validate input arguments
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest, quality);
+ ret != NO_ERROR) {
+ return ret;
+ }
+ if (exif != nullptr && exif->data == nullptr) {
+ ALOGE("received nullptr for exif metadata");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+
+ const int yu420_luma_stride = ALIGNM(p010_image.width, kJpegBlock);
+ unique_ptr<uint8_t[]> yuv420_image_data =
+ make_unique<uint8_t[]>(yu420_luma_stride * p010_image.height * 3 / 2);
+ jpegr_uncompressed_struct yuv420_image = {.data = yuv420_image_data.get(),
+ .width = p010_image.width,
+ .height = p010_image.height,
+ .colorGamut = p010_image.colorGamut,
+ .luma_stride = yu420_luma_stride,
+ .chroma_data = nullptr,
+ .chroma_stride = yu420_luma_stride >> 1};
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+
+ // tone map
+ JPEGR_CHECK(toneMap(&p010_image, &yuv420_image));
+
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
+
+ // convert to Bt601 YUV encoding for JPEG encode
+ if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
+ JPEGR_CHECK(convertYuv(&yuv420_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
+ }
+
+ // compress 420 image
+ JpegEncoderHelper jpeg_enc_obj_yuv420;
+ if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_image.data),
+ reinterpret_cast<uint8_t*>(yuv420_image.chroma_data),
+ yuv420_image.width, yuv420_image.height,
+ yuv420_image.luma_stride, yuv420_image.chroma_stride,
+ quality, icc->getData(), icc->getLength())) {
+ return ERROR_JPEGR_ENCODE_ERROR;
+ }
+ jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .colorGamut = yuv420_image.colorGamut};
+
+ // append gain map, no ICC since JPEG encode already did it
+ JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
+ &metadata, dest));
+
+ return NO_ERROR;
+}
+
+/* Encode API-1 */
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr, ultrahdr_transfer_function hdr_tf,
+ jr_compressed_ptr dest, int quality, jr_exif_ptr exif) {
+ // validate input arguments
+ if (yuv420_image_ptr == nullptr) {
+ ALOGE("received nullptr for uncompressed 420 image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (exif != nullptr && exif->data == nullptr) {
+ ALOGE("received nullptr for exif metadata");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest, quality);
+ ret != NO_ERROR) {
+ return ret;
+ }
+
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+ jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
+ if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+ if (!yuv420_image.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+ yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+ }
+
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct compressed_map = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ sp<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420_image.colorGamut);
+
+ jpegr_uncompressed_struct yuv420_bt601_image = yuv420_image;
+ unique_ptr<uint8_t[]> yuv_420_bt601_data;
+ // Convert to bt601 YUV encoding for JPEG encode
+ if (yuv420_image.colorGamut != ULTRAHDR_COLORGAMUT_P3) {
+ const int yuv_420_bt601_luma_stride = ALIGNM(yuv420_image.width, kJpegBlock);
+ yuv_420_bt601_data =
+ make_unique<uint8_t[]>(yuv_420_bt601_luma_stride * yuv420_image.height * 3 / 2);
+ yuv420_bt601_image.data = yuv_420_bt601_data.get();
+ yuv420_bt601_image.colorGamut = yuv420_image.colorGamut;
+ yuv420_bt601_image.luma_stride = yuv_420_bt601_luma_stride;
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
+ yuv420_bt601_image.chroma_data = data + yuv_420_bt601_luma_stride * yuv420_image.height;
+ yuv420_bt601_image.chroma_stride = yuv_420_bt601_luma_stride >> 1;
+
+ {
+ // copy luma
+ uint8_t* y_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.data);
+ uint8_t* y_src = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ if (yuv420_bt601_image.luma_stride == yuv420_image.luma_stride) {
+ memcpy(y_dst, y_src, yuv420_bt601_image.luma_stride * yuv420_image.height);
+ } else {
+ for (size_t i = 0; i < yuv420_image.height; i++) {
+ memcpy(y_dst, y_src, yuv420_image.width);
+ if (yuv420_image.width != yuv420_bt601_image.luma_stride) {
+ memset(y_dst + yuv420_image.width, 0,
+ yuv420_bt601_image.luma_stride - yuv420_image.width);
+ }
+ y_dst += yuv420_bt601_image.luma_stride;
+ y_src += yuv420_image.luma_stride;
+ }
+ }
+ }
+
+ if (yuv420_bt601_image.chroma_stride == yuv420_image.chroma_stride) {
+ // copy luma
+ uint8_t* ch_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
+ uint8_t* ch_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
+ memcpy(ch_dst, ch_src, yuv420_bt601_image.chroma_stride * yuv420_image.height);
+ } else {
+ // copy cb & cr
+ uint8_t* cb_dst = reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data);
+ uint8_t* cb_src = reinterpret_cast<uint8_t*>(yuv420_image.chroma_data);
+ uint8_t* cr_dst = cb_dst + (yuv420_bt601_image.chroma_stride * yuv420_bt601_image.height / 2);
+ uint8_t* cr_src = cb_src + (yuv420_image.chroma_stride * yuv420_image.height / 2);
+ for (size_t i = 0; i < yuv420_image.height / 2; i++) {
+ memcpy(cb_dst, cb_src, yuv420_image.width / 2);
+ memcpy(cr_dst, cr_src, yuv420_image.width / 2);
+ if (yuv420_bt601_image.width / 2 != yuv420_bt601_image.chroma_stride) {
+ memset(cb_dst + yuv420_image.width / 2, 0,
+ yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
+ memset(cr_dst + yuv420_image.width / 2, 0,
+ yuv420_bt601_image.chroma_stride - yuv420_image.width / 2);
+ }
+ cb_dst += yuv420_bt601_image.chroma_stride;
+ cb_src += yuv420_image.chroma_stride;
+ cr_dst += yuv420_bt601_image.chroma_stride;
+ cr_src += yuv420_image.chroma_stride;
+ }
+ }
+ JPEGR_CHECK(convertYuv(&yuv420_bt601_image, yuv420_image.colorGamut, ULTRAHDR_COLORGAMUT_P3));
+ }
+
+ // compress 420 image
+ JpegEncoderHelper jpeg_enc_obj_yuv420;
+ if (!jpeg_enc_obj_yuv420.compressImage(reinterpret_cast<uint8_t*>(yuv420_bt601_image.data),
+ reinterpret_cast<uint8_t*>(yuv420_bt601_image.chroma_data),
+ yuv420_bt601_image.width, yuv420_bt601_image.height,
+ yuv420_bt601_image.luma_stride,
+ yuv420_bt601_image.chroma_stride, quality, icc->getData(),
+ icc->getLength())) {
+ return ERROR_JPEGR_ENCODE_ERROR;
+ }
+
+ jpegr_compressed_struct jpeg = {.data = jpeg_enc_obj_yuv420.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_yuv420.getCompressedImageSize()),
+ .colorGamut = yuv420_image.colorGamut};
+
+ // append gain map, no ICC since JPEG encode already did it
+ JPEGR_CHECK(appendGainMap(&jpeg, &compressed_map, exif, /* icc */ nullptr, /* icc size */ 0,
+ &metadata, dest));
+ return NO_ERROR;
+}
+
+/* Encode API-2 */
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+ jr_uncompressed_ptr yuv420_image_ptr,
+ jr_compressed_ptr yuv420jpg_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
+ // validate input arguments
+ if (yuv420_image_ptr == nullptr) {
+ ALOGE("received nullptr for uncompressed 420 image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
+ ALOGE("received nullptr for compressed jpeg image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, yuv420_image_ptr, hdr_tf, dest);
+ ret != NO_ERROR) {
+ return ret;
+ }
+
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+ jpegr_uncompressed_struct yuv420_image = *yuv420_image_ptr;
+ if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+ if (!yuv420_image.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
+ yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+ }
+
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
+}
+
+/* Encode API-3 */
+status_t JpegR::encodeJPEGR(jr_uncompressed_ptr p010_image_ptr,
+ jr_compressed_ptr yuv420jpg_image_ptr,
+ ultrahdr_transfer_function hdr_tf, jr_compressed_ptr dest) {
+ // validate input arguments
+ if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
+ ALOGE("received nullptr for compressed jpeg image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (auto ret = areInputArgumentsValid(p010_image_ptr, nullptr, hdr_tf, dest); ret != NO_ERROR) {
+ return ret;
+ }
+
+ // clean up input structure for later usage
+ jpegr_uncompressed_struct p010_image = *p010_image_ptr;
+ if (p010_image.luma_stride == 0) p010_image.luma_stride = p010_image.width;
+ if (!p010_image.chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(p010_image.data);
+ p010_image.chroma_data = data + p010_image.luma_stride * p010_image.height;
+ p010_image.chroma_stride = p010_image.luma_stride;
+ }
+
+ // decode input jpeg, gamut is going to be bt601.
+ JpegDecoderHelper jpeg_dec_obj_yuv420;
+ if (!jpeg_dec_obj_yuv420.decompressImage(yuv420jpg_image_ptr->data,
+ yuv420jpg_image_ptr->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ jpegr_uncompressed_struct yuv420_image{};
+ yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
+ yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+ yuv420_image.colorGamut = yuv420jpg_image_ptr->colorGamut;
+ if (yuv420_image.luma_stride == 0) yuv420_image.luma_stride = yuv420_image.width;
+ if (!yuv420_image.chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * p010_image.height;
+ yuv420_image.chroma_stride = yuv420_image.luma_stride >> 1;
+ }
+
+ if (p010_image_ptr->width != yuv420_image.width ||
+ p010_image_ptr->height != yuv420_image.height) {
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+
+ // gain map
+ ultrahdr_metadata_struct metadata = {.version = kJpegrVersion};
+ jpegr_uncompressed_struct gainmap_image;
+ JPEGR_CHECK(generateGainMap(&yuv420_image, &p010_image, hdr_tf, &metadata, &gainmap_image,
+ true /* sdr_is_601 */));
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(gainmap_image.data));
+
+ // compress gain map
+ JpegEncoderHelper jpeg_enc_obj_gm;
+ JPEGR_CHECK(compressGainMap(&gainmap_image, &jpeg_enc_obj_gm));
+ jpegr_compressed_struct gainmapjpg_image = {.data = jpeg_enc_obj_gm.getCompressedImagePtr(),
+ .length = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .maxLength = static_cast<int>(
+ jpeg_enc_obj_gm.getCompressedImageSize()),
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ return encodeJPEGR(yuv420jpg_image_ptr, &gainmapjpg_image, &metadata, dest);
+}
+
+/* Encode API-4 */
+status_t JpegR::encodeJPEGR(jr_compressed_ptr yuv420jpg_image_ptr,
+ jr_compressed_ptr gainmapjpg_image_ptr, ultrahdr_metadata_ptr metadata,
+ jr_compressed_ptr dest) {
+ if (yuv420jpg_image_ptr == nullptr || yuv420jpg_image_ptr->data == nullptr) {
+ ALOGE("received nullptr for compressed jpeg image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (gainmapjpg_image_ptr == nullptr || gainmapjpg_image_ptr->data == nullptr) {
+ ALOGE("received nullptr for compressed gain map");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (dest == nullptr || dest->data == nullptr) {
+ ALOGE("received nullptr for destination");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ // We just want to check if ICC is present, so don't do a full decode. Note,
+ // this doesn't verify that the ICC is valid.
+ JpegDecoderHelper decoder;
+ std::vector<uint8_t> icc;
+ decoder.getCompressedImageParameters(yuv420jpg_image_ptr->data, yuv420jpg_image_ptr->length,
+ /* pWidth */ nullptr, /* pHeight */ nullptr, &icc,
+ /* exifData */ nullptr);
+
+ // Add ICC if not already present.
+ if (icc.size() > 0) {
+ JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
+ /* icc */ nullptr, /* icc size */ 0, metadata, dest));
+ } else {
+ sp<DataStruct> newIcc =
+ IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, yuv420jpg_image_ptr->colorGamut);
+ JPEGR_CHECK(appendGainMap(yuv420jpg_image_ptr, gainmapjpg_image_ptr, /* exif */ nullptr,
+ newIcc->getData(), newIcc->getLength(), metadata, dest));
+ }
+
+ return NO_ERROR;
+}
+
+status_t JpegR::getJPEGRInfo(jr_compressed_ptr jpegr_image_ptr, jr_info_ptr jpeg_image_info_ptr) {
+ if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
+ ALOGE("received nullptr for compressed jpegr image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (jpeg_image_info_ptr == nullptr) {
+ ALOGE("received nullptr for compressed jpegr info struct");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ jpegr_compressed_struct primary_image, gainmap_image;
+ status_t status = extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_image, &gainmap_image);
+ if (status != NO_ERROR && status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
+ return status;
+ }
+
+ JpegDecoderHelper jpeg_dec_obj_hdr;
+ if (!jpeg_dec_obj_hdr.getCompressedImageParameters(primary_image.data, primary_image.length,
+ &jpeg_image_info_ptr->width,
+ &jpeg_image_info_ptr->height,
+ jpeg_image_info_ptr->iccData,
+ jpeg_image_info_ptr->exifData)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ return status;
+}
+
+/* Decode API */
+status_t JpegR::decodeJPEGR(jr_compressed_ptr jpegr_image_ptr, jr_uncompressed_ptr dest,
+ float max_display_boost, jr_exif_ptr exif,
+ ultrahdr_output_format output_format,
+ jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata) {
+ if (jpegr_image_ptr == nullptr || jpegr_image_ptr->data == nullptr) {
+ ALOGE("received nullptr for compressed jpegr image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (dest == nullptr || dest->data == nullptr) {
+ ALOGE("received nullptr for dest image");
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (max_display_boost < 1.0f) {
+ ALOGE("received bad value for max_display_boost %f", max_display_boost);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (exif != nullptr && exif->data == nullptr) {
+ ALOGE("received nullptr address for exif data");
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (output_format <= ULTRAHDR_OUTPUT_UNSPECIFIED || output_format > ULTRAHDR_OUTPUT_MAX) {
+ ALOGE("received bad value for output format %d", output_format);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ jpegr_compressed_struct primary_jpeg_image, gainmap_jpeg_image;
+ status_t status =
+ extractPrimaryImageAndGainMap(jpegr_image_ptr, &primary_jpeg_image, &gainmap_jpeg_image);
+ if (status != NO_ERROR) {
+ if (output_format != ULTRAHDR_OUTPUT_SDR || status != ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND) {
+ ALOGE("received invalid compressed jpegr image");
+ return status;
+ }
+ }
+
+ JpegDecoderHelper jpeg_dec_obj_yuv420;
+ if (!jpeg_dec_obj_yuv420.decompressImage(primary_jpeg_image.data, primary_jpeg_image.length,
+ (output_format == ULTRAHDR_OUTPUT_SDR))) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+
+ if (output_format == ULTRAHDR_OUTPUT_SDR) {
+ if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+ jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 4) >
+ jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
+ return ERROR_JPEGR_CALCULATION_ERROR;
+ }
+ } else {
+ if ((jpeg_dec_obj_yuv420.getDecompressedImageWidth() *
+ jpeg_dec_obj_yuv420.getDecompressedImageHeight() * 3 / 2) >
+ jpeg_dec_obj_yuv420.getDecompressedImageSize()) {
+ return ERROR_JPEGR_CALCULATION_ERROR;
+ }
+ }
+
+ if (exif != nullptr) {
+ if (exif->data == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (exif->length < jpeg_dec_obj_yuv420.getEXIFSize()) {
+ return ERROR_JPEGR_BUFFER_TOO_SMALL;
+ }
+ memcpy(exif->data, jpeg_dec_obj_yuv420.getEXIFPtr(), jpeg_dec_obj_yuv420.getEXIFSize());
+ exif->length = jpeg_dec_obj_yuv420.getEXIFSize();
+ }
+
+ if (output_format == ULTRAHDR_OUTPUT_SDR) {
+ dest->width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ dest->height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+ memcpy(dest->data, jpeg_dec_obj_yuv420.getDecompressedImagePtr(),
+ dest->width * dest->height * 4);
+ return NO_ERROR;
+ }
+
+ JpegDecoderHelper jpeg_dec_obj_gm;
+ if (!jpeg_dec_obj_gm.decompressImage(gainmap_jpeg_image.data, gainmap_jpeg_image.length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ if ((jpeg_dec_obj_gm.getDecompressedImageWidth() * jpeg_dec_obj_gm.getDecompressedImageHeight()) >
+ jpeg_dec_obj_gm.getDecompressedImageSize()) {
+ return ERROR_JPEGR_CALCULATION_ERROR;
+ }
+
+ jpegr_uncompressed_struct gainmap_image;
+ gainmap_image.data = jpeg_dec_obj_gm.getDecompressedImagePtr();
+ gainmap_image.width = jpeg_dec_obj_gm.getDecompressedImageWidth();
+ gainmap_image.height = jpeg_dec_obj_gm.getDecompressedImageHeight();
+
+ if (gainmap_image_ptr != nullptr) {
+ gainmap_image_ptr->width = gainmap_image.width;
+ gainmap_image_ptr->height = gainmap_image.height;
+ int size = gainmap_image_ptr->width * gainmap_image_ptr->height;
+ gainmap_image_ptr->data = malloc(size);
+ memcpy(gainmap_image_ptr->data, gainmap_image.data, size);
+ }
+
+ ultrahdr_metadata_struct uhdr_metadata;
+ if (!getMetadataFromXMP(static_cast<uint8_t*>(jpeg_dec_obj_gm.getXMPPtr()),
+ jpeg_dec_obj_gm.getXMPSize(), &uhdr_metadata)) {
+ return ERROR_JPEGR_INVALID_METADATA;
+ }
+
+ if (metadata != nullptr) {
+ metadata->version = uhdr_metadata.version;
+ metadata->minContentBoost = uhdr_metadata.minContentBoost;
+ metadata->maxContentBoost = uhdr_metadata.maxContentBoost;
+ metadata->gamma = uhdr_metadata.gamma;
+ metadata->offsetSdr = uhdr_metadata.offsetSdr;
+ metadata->offsetHdr = uhdr_metadata.offsetHdr;
+ metadata->hdrCapacityMin = uhdr_metadata.hdrCapacityMin;
+ metadata->hdrCapacityMax = uhdr_metadata.hdrCapacityMax;
+ }
+
+ jpegr_uncompressed_struct yuv420_image;
+ yuv420_image.data = jpeg_dec_obj_yuv420.getDecompressedImagePtr();
+ yuv420_image.width = jpeg_dec_obj_yuv420.getDecompressedImageWidth();
+ yuv420_image.height = jpeg_dec_obj_yuv420.getDecompressedImageHeight();
+ yuv420_image.colorGamut = IccHelper::readIccColorGamut(jpeg_dec_obj_yuv420.getICCPtr(),
+ jpeg_dec_obj_yuv420.getICCSize());
+ yuv420_image.luma_stride = yuv420_image.width;
+ uint8_t* data = reinterpret_cast<uint8_t*>(yuv420_image.data);
+ yuv420_image.chroma_data = data + yuv420_image.luma_stride * yuv420_image.height;
+ yuv420_image.chroma_stride = yuv420_image.width >> 1;
+
+ JPEGR_CHECK(applyGainMap(&yuv420_image, &gainmap_image, &uhdr_metadata, output_format,
+ max_display_boost, dest));
+ return NO_ERROR;
+}
+
+status_t JpegR::compressGainMap(jr_uncompressed_ptr gainmap_image_ptr,
+ JpegEncoderHelper* jpeg_enc_obj_ptr) {
+ if (gainmap_image_ptr == nullptr || jpeg_enc_obj_ptr == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ // Don't need to convert YUV to Bt601 since single channel
+ if (!jpeg_enc_obj_ptr->compressImage(reinterpret_cast<uint8_t*>(gainmap_image_ptr->data), nullptr,
+ gainmap_image_ptr->width, gainmap_image_ptr->height,
+ gainmap_image_ptr->luma_stride, 0, kMapCompressQuality,
+ nullptr, 0)) {
+ return ERROR_JPEGR_ENCODE_ERROR;
+ }
+
+ return NO_ERROR;
+}
+
+const int kJobSzInRows = 16;
+static_assert(kJobSzInRows > 0 && kJobSzInRows % kMapDimensionScaleFactor == 0,
+ "align job size to kMapDimensionScaleFactor");
+
+class JobQueue {
+public:
+ bool dequeueJob(size_t& rowStart, size_t& rowEnd);
+ void enqueueJob(size_t rowStart, size_t rowEnd);
+ void markQueueForEnd();
+ void reset();
+
+private:
+ bool mQueuedAllJobs = false;
+ std::deque<std::tuple<size_t, size_t>> mJobs;
+ std::mutex mMutex;
+ std::condition_variable mCv;
+};
+
+bool JobQueue::dequeueJob(size_t& rowStart, size_t& rowEnd) {
+ std::unique_lock<std::mutex> 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<std::mutex> lock{mMutex};
+ mJobs.push_back(std::make_tuple(rowStart, rowEnd));
+ lock.unlock();
+ mCv.notify_one();
+}
+
+void JobQueue::markQueueForEnd() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mQueuedAllJobs = true;
+ lock.unlock();
+ mCv.notify_all();
+}
+
+void JobQueue::reset() {
+ std::unique_lock<std::mutex> lock{mMutex};
+ mJobs.clear();
+ mQueuedAllJobs = false;
+}
+
+status_t JpegR::generateGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr p010_image_ptr,
+ ultrahdr_transfer_function hdr_tf, ultrahdr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest, bool sdr_is_601) {
+ if (yuv420_image_ptr == nullptr || p010_image_ptr == nullptr || metadata == nullptr ||
+ dest == nullptr || yuv420_image_ptr->data == nullptr ||
+ yuv420_image_ptr->chroma_data == nullptr || p010_image_ptr->data == nullptr ||
+ p010_image_ptr->chroma_data == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (yuv420_image_ptr->width != p010_image_ptr->width ||
+ yuv420_image_ptr->height != p010_image_ptr->height) {
+ return ERROR_JPEGR_RESOLUTION_MISMATCH;
+ }
+ if (yuv420_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ p010_image_ptr->colorGamut == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ size_t image_width = yuv420_image_ptr->width;
+ size_t image_height = yuv420_image_ptr->height;
+ size_t map_width = image_width / kMapDimensionScaleFactor;
+ size_t map_height = image_height / kMapDimensionScaleFactor;
+
+ dest->data = new uint8_t[map_width * map_height];
+ dest->width = map_width;
+ dest->height = map_height;
+ dest->colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ dest->luma_stride = map_width;
+ dest->chroma_data = nullptr;
+ dest->chroma_stride = 0;
+ std::unique_ptr<uint8_t[]> map_data;
+ map_data.reset(reinterpret_cast<uint8_t*>(dest->data));
+
+ ColorTransformFn hdrInvOetf = nullptr;
+ float hdr_white_nits;
+ switch (hdr_tf) {
+ case ULTRAHDR_TF_LINEAR:
+ hdrInvOetf = identityConversion;
+ // Note: this will produce clipping if the input exceeds kHlgMaxNits.
+ // TODO: TF LINEAR will be deprecated.
+ hdr_white_nits = kHlgMaxNits;
+ break;
+ case ULTRAHDR_TF_HLG:
+#if USE_HLG_INVOETF_LUT
+ hdrInvOetf = hlgInvOetfLUT;
+#else
+ hdrInvOetf = hlgInvOetf;
+#endif
+ hdr_white_nits = kHlgMaxNits;
+ break;
+ case ULTRAHDR_TF_PQ:
+#if USE_PQ_INVOETF_LUT
+ hdrInvOetf = pqInvOetfLUT;
+#else
+ hdrInvOetf = pqInvOetf;
+#endif
+ hdr_white_nits = kPqMaxNits;
+ break;
+ default:
+ // Should be impossible to hit after input validation.
+ return ERROR_JPEGR_INVALID_TRANS_FUNC;
+ }
+
+ metadata->maxContentBoost = hdr_white_nits / kSdrWhiteNits;
+ metadata->minContentBoost = 1.0f;
+ metadata->gamma = 1.0f;
+ metadata->offsetSdr = 0.0f;
+ metadata->offsetHdr = 0.0f;
+ metadata->hdrCapacityMin = 1.0f;
+ metadata->hdrCapacityMax = metadata->maxContentBoost;
+
+ float log2MinBoost = log2(metadata->minContentBoost);
+ float log2MaxBoost = log2(metadata->maxContentBoost);
+
+ ColorTransformFn hdrGamutConversionFn =
+ getHdrConversionFn(yuv420_image_ptr->colorGamut, p010_image_ptr->colorGamut);
+
+ ColorCalculationFn luminanceFn = nullptr;
+ ColorTransformFn sdrYuvToRgbFn = nullptr;
+ switch (yuv420_image_ptr->colorGamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ luminanceFn = srgbLuminance;
+ sdrYuvToRgbFn = srgbYuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ luminanceFn = p3Luminance;
+ sdrYuvToRgbFn = p3YuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ luminanceFn = bt2100Luminance;
+ sdrYuvToRgbFn = bt2100YuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ if (sdr_is_601) {
+ sdrYuvToRgbFn = p3YuvToRgb;
+ }
+
+ ColorTransformFn hdrYuvToRgbFn = nullptr;
+ switch (p010_image_ptr->colorGamut) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ hdrYuvToRgbFn = srgbYuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ hdrYuvToRgbFn = p3YuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ hdrYuvToRgbFn = bt2100YuvToRgb;
+ break;
+ case ULTRAHDR_COLORGAMUT_UNSPECIFIED:
+ // Should be impossible to hit after input validation.
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ std::mutex mutex;
+ const int threads = std::clamp(GetCPUCoreCount(), 1, 4);
+ size_t rowStep = threads == 1 ? image_height : kJobSzInRows;
+ JobQueue jobQueue;
+
+ std::function<void()> generateMap = [yuv420_image_ptr, p010_image_ptr, metadata, dest, hdrInvOetf,
+ hdrGamutConversionFn, luminanceFn, sdrYuvToRgbFn,
+ hdrYuvToRgbFn, hdr_white_nits, log2MinBoost, log2MaxBoost,
+ &jobQueue]() -> void {
+ size_t rowStart, rowEnd;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < dest->width; ++x) {
+ Color sdr_yuv_gamma = sampleYuv420(yuv420_image_ptr, kMapDimensionScaleFactor, x, y);
+ Color sdr_rgb_gamma = sdrYuvToRgbFn(sdr_yuv_gamma);
+ // We are assuming the SDR input is always sRGB transfer.
+#if USE_SRGB_INVOETF_LUT
+ Color sdr_rgb = srgbInvOetfLUT(sdr_rgb_gamma);
+#else
+ Color sdr_rgb = srgbInvOetf(sdr_rgb_gamma);
+#endif
+ float sdr_y_nits = luminanceFn(sdr_rgb) * kSdrWhiteNits;
+
+ Color hdr_yuv_gamma = sampleP010(p010_image_ptr, kMapDimensionScaleFactor, x, y);
+ Color hdr_rgb_gamma = hdrYuvToRgbFn(hdr_yuv_gamma);
+ Color hdr_rgb = hdrInvOetf(hdr_rgb_gamma);
+ hdr_rgb = hdrGamutConversionFn(hdr_rgb);
+ float hdr_y_nits = luminanceFn(hdr_rgb) * hdr_white_nits;
+
+ size_t pixel_idx = x + y * dest->width;
+ reinterpret_cast<uint8_t*>(dest->data)[pixel_idx] =
+ encodeGain(sdr_y_nits, hdr_y_nits, metadata, log2MinBoost, log2MaxBoost);
+ }
+ }
+ }
+ };
+
+ // generate map
+ std::vector<std::thread> workers;
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(generateMap));
+ }
+
+ rowStep = (threads == 1 ? image_height : kJobSzInRows) / kMapDimensionScaleFactor;
+ for (size_t rowStart = 0; rowStart < map_height;) {
+ size_t rowEnd = std::min(rowStart + rowStep, map_height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ generateMap();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+
+ map_data.release();
+ return NO_ERROR;
+}
+
+status_t JpegR::applyGainMap(jr_uncompressed_ptr yuv420_image_ptr,
+ jr_uncompressed_ptr gainmap_image_ptr, ultrahdr_metadata_ptr metadata,
+ ultrahdr_output_format output_format, float max_display_boost,
+ jr_uncompressed_ptr dest) {
+ if (yuv420_image_ptr == nullptr || gainmap_image_ptr == nullptr || metadata == nullptr ||
+ dest == nullptr || yuv420_image_ptr->data == nullptr ||
+ yuv420_image_ptr->chroma_data == nullptr || gainmap_image_ptr->data == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (metadata->version.compare(kJpegrVersion)) {
+ ALOGE("Unsupported metadata version: %s", metadata->version.c_str());
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+ if (metadata->gamma != 1.0f) {
+ ALOGE("Unsupported metadata gamma: %f", metadata->gamma);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+ if (metadata->offsetSdr != 0.0f || metadata->offsetHdr != 0.0f) {
+ ALOGE("Unsupported metadata offset sdr, hdr: %f, %f", metadata->offsetSdr, metadata->offsetHdr);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+ if (metadata->hdrCapacityMin != metadata->minContentBoost ||
+ metadata->hdrCapacityMax != metadata->maxContentBoost) {
+ ALOGE("Unsupported metadata hdr capacity min, max: %f, %f", metadata->hdrCapacityMin,
+ metadata->hdrCapacityMax);
+ return ERROR_JPEGR_UNSUPPORTED_METADATA;
+ }
+
+ // TODO: remove once map scaling factor is computed based on actual map dims
+ size_t image_width = yuv420_image_ptr->width;
+ size_t image_height = yuv420_image_ptr->height;
+ size_t map_width = image_width / kMapDimensionScaleFactor;
+ size_t map_height = image_height / kMapDimensionScaleFactor;
+ if (map_width != gainmap_image_ptr->width || map_height != gainmap_image_ptr->height) {
+ ALOGE("gain map dimensions and primary image dimensions are not to scale, computed gain map "
+ "resolution is %dx%d, received gain map resolution is %dx%d",
+ (int)map_width, (int)map_height, gainmap_image_ptr->width, gainmap_image_ptr->height);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ dest->width = yuv420_image_ptr->width;
+ dest->height = yuv420_image_ptr->height;
+ ShepardsIDW idwTable(kMapDimensionScaleFactor);
+ float display_boost = std::min(max_display_boost, metadata->maxContentBoost);
+ GainLUT gainLUT(metadata, display_boost);
+
+ JobQueue jobQueue;
+ std::function<void()> applyRecMap = [yuv420_image_ptr, gainmap_image_ptr, metadata, dest,
+ &jobQueue, &idwTable, output_format, &gainLUT,
+ display_boost]() -> void {
+ size_t width = yuv420_image_ptr->width;
+ size_t height = yuv420_image_ptr->height;
+
+ size_t rowStart, rowEnd;
+ while (jobQueue.dequeueJob(rowStart, rowEnd)) {
+ for (size_t y = rowStart; y < rowEnd; ++y) {
+ for (size_t x = 0; x < width; ++x) {
+ Color yuv_gamma_sdr = getYuv420Pixel(yuv420_image_ptr, x, y);
+ // Assuming the sdr image is a decoded JPEG, we should always use Rec.601 YUV coefficients
+ Color rgb_gamma_sdr = p3YuvToRgb(yuv_gamma_sdr);
+ // We are assuming the SDR base image is always sRGB transfer.
+#if USE_SRGB_INVOETF_LUT
+ Color rgb_sdr = srgbInvOetfLUT(rgb_gamma_sdr);
+#else
+ Color rgb_sdr = srgbInvOetf(rgb_gamma_sdr);
+#endif
+ float gain;
+ // TODO: determine map scaling factor based on actual map dims
+ size_t map_scale_factor = kMapDimensionScaleFactor;
+ // TODO: If map_scale_factor is guaranteed to be an integer, then remove the following.
+ // Currently map_scale_factor is of type size_t, but it could be changed to a float
+ // later.
+ if (map_scale_factor != floorf(map_scale_factor)) {
+ gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y);
+ } else {
+ gain = sampleMap(gainmap_image_ptr, map_scale_factor, x, y, idwTable);
+ }
+
+#if USE_APPLY_GAIN_LUT
+ Color rgb_hdr = applyGainLUT(rgb_sdr, gain, gainLUT);
+#else
+ Color rgb_hdr = applyGain(rgb_sdr, gain, metadata, display_boost);
+#endif
+ rgb_hdr = rgb_hdr / display_boost;
+ size_t pixel_idx = x + y * width;
+
+ switch (output_format) {
+ case ULTRAHDR_OUTPUT_HDR_LINEAR: {
+ uint64_t rgba_f16 = colorToRgbaF16(rgb_hdr);
+ reinterpret_cast<uint64_t*>(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<uint32_t*>(dest->data)[pixel_idx] = rgba_1010102;
+ break;
+ }
+ case ULTRAHDR_OUTPUT_HDR_PQ: {
+#if USE_PQ_OETF_LUT
+ ColorTransformFn hdrOetf = pqOetfLUT;
+#else
+ ColorTransformFn hdrOetf = pqOetf;
+#endif
+ Color rgb_gamma_hdr = hdrOetf(rgb_hdr);
+ uint32_t rgba_1010102 = colorToRgba1010102(rgb_gamma_hdr);
+ reinterpret_cast<uint32_t*>(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<std::thread> workers;
+ for (int th = 0; th < threads - 1; th++) {
+ workers.push_back(std::thread(applyRecMap));
+ }
+ const int rowStep = threads == 1 ? yuv420_image_ptr->height : kJobSzInRows;
+ for (int rowStart = 0; rowStart < yuv420_image_ptr->height;) {
+ int rowEnd = std::min(rowStart + rowStep, yuv420_image_ptr->height);
+ jobQueue.enqueueJob(rowStart, rowEnd);
+ rowStart = rowEnd;
+ }
+ jobQueue.markQueueForEnd();
+ applyRecMap();
+ std::for_each(workers.begin(), workers.end(), [](std::thread& t) { t.join(); });
+ return NO_ERROR;
+}
+
+status_t JpegR::extractPrimaryImageAndGainMap(jr_compressed_ptr jpegr_image_ptr,
+ jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr) {
+ if (jpegr_image_ptr == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+
+ MessageHandler msg_handler;
+ std::shared_ptr<DataSegment> seg =
+ DataSegment::Create(DataRange(0, jpegr_image_ptr->length),
+ static_cast<const uint8_t*>(jpegr_image_ptr->data),
+ DataSegment::BufferDispositionPolicy::kDontDelete);
+ DataSegmentDataSource data_source(seg);
+ JpegInfoBuilder jpeg_info_builder;
+ jpeg_info_builder.SetImageLimit(2);
+ JpegScanner jpeg_scanner(&msg_handler);
+ jpeg_scanner.Run(&data_source, &jpeg_info_builder);
+ data_source.Reset();
+
+ if (jpeg_scanner.HasError()) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ const auto& jpeg_info = jpeg_info_builder.GetInfo();
+ const auto& image_ranges = jpeg_info.GetImageRanges();
+
+ if (image_ranges.empty()) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ if (primary_jpg_image_ptr != nullptr) {
+ primary_jpg_image_ptr->data =
+ static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[0].GetBegin();
+ primary_jpg_image_ptr->length = image_ranges[0].GetLength();
+ }
+
+ if (image_ranges.size() == 1) {
+ return ERROR_JPEGR_GAIN_MAP_IMAGE_NOT_FOUND;
+ }
+
+ if (gainmap_jpg_image_ptr != nullptr) {
+ gainmap_jpg_image_ptr->data =
+ static_cast<uint8_t*>(jpegr_image_ptr->data) + image_ranges[1].GetBegin();
+ gainmap_jpg_image_ptr->length = image_ranges[1].GetLength();
+ }
+
+ // TODO: choose primary image and gain map image carefully
+ if (image_ranges.size() > 2) {
+ ALOGW("Number of jpeg images present %d, primary, gain map images may not be correctly chosen",
+ (int)image_ranges.size());
+ }
+
+ return NO_ERROR;
+}
+
+// JPEG/R structure:
+// SOI (ff d8)
+//
+// (Optional, if EXIF package is from outside (Encode API-0 API-1), or if EXIF package presents
+// in the JPEG input (Encode API-2, API-3, API-4))
+// APP1 (ff e1)
+// 2 bytes of length (2 + length of exif package)
+// EXIF package (this includes the first two bytes representing the package length)
+//
+// (Required, XMP package) APP1 (ff e1)
+// 2 bytes of length (2 + 29 + length of xmp package)
+// name space ("http://ns.adobe.com/xap/1.0/\0")
+// XMP
+//
+// (Required, MPF package) APP2 (ff e2)
+// 2 bytes of length
+// MPF
+//
+// (Required) primary image (without the first two bytes (SOI) and EXIF, may have other packages)
+//
+// SOI (ff d8)
+//
+// (Required, XMP package) APP1 (ff e1)
+// 2 bytes of length (2 + 29 + length of xmp package)
+// name space ("http://ns.adobe.com/xap/1.0/\0")
+// XMP
+//
+// (Required) secondary image (the gain map, without the first two bytes (SOI))
+//
+// Metadata versions we are using:
+// ECMA TR-98 for JFIF marker
+// Exif 2.2 spec for EXIF marker
+// Adobe XMP spec part 3 for XMP marker
+// ICC v4.3 spec for ICC
+status_t JpegR::appendGainMap(jr_compressed_ptr primary_jpg_image_ptr,
+ jr_compressed_ptr gainmap_jpg_image_ptr, jr_exif_ptr pExif,
+ void* pIcc, size_t icc_size, ultrahdr_metadata_ptr metadata,
+ jr_compressed_ptr dest) {
+ if (primary_jpg_image_ptr == nullptr || gainmap_jpg_image_ptr == nullptr || metadata == nullptr ||
+ dest == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (metadata->version.compare("1.0")) {
+ ALOGE("received bad value for version: %s", metadata->version.c_str());
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (metadata->maxContentBoost < metadata->minContentBoost) {
+ ALOGE("received bad value for content boost min %f, max %f", metadata->minContentBoost,
+ metadata->maxContentBoost);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (metadata->hdrCapacityMax < metadata->hdrCapacityMin || metadata->hdrCapacityMin < 1.0f) {
+ ALOGE("received bad value for hdr capacity min %f, max %f", metadata->hdrCapacityMin,
+ metadata->hdrCapacityMax);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (metadata->offsetSdr < 0.0f || metadata->offsetHdr < 0.0f) {
+ ALOGE("received bad value for offset sdr %f, hdr %f", metadata->offsetSdr, metadata->offsetHdr);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ if (metadata->gamma <= 0.0f) {
+ ALOGE("received bad value for gamma %f", metadata->gamma);
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+
+ const string nameSpace = "http://ns.adobe.com/xap/1.0/";
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+
+ // calculate secondary image length first, because the length will be written into the primary
+ // image xmp
+ const string xmp_secondary = generateXmpForSecondaryImage(*metadata);
+ const int xmp_secondary_length = 2 /* 2 bytes representing the length of the package */
+ + nameSpaceLength /* 29 bytes length of name space including \0 */
+ + xmp_secondary.size(); /* length of xmp packet */
+ const int secondary_image_size = 2 /* 2 bytes length of APP1 sign */
+ + xmp_secondary_length + gainmap_jpg_image_ptr->length;
+ // primary image
+ const string xmp_primary = generateXmpForPrimaryImage(secondary_image_size, *metadata);
+ // same as primary
+ const int xmp_primary_length = 2 + nameSpaceLength + xmp_primary.size();
+
+ // Check if EXIF package presents in the JPEG input.
+ // If so, extract and remove the EXIF package.
+ JpegDecoderHelper decoder;
+ if (!decoder.extractEXIF(primary_jpg_image_ptr->data, primary_jpg_image_ptr->length)) {
+ return ERROR_JPEGR_DECODE_ERROR;
+ }
+ jpegr_exif_struct exif_from_jpg = {.data = nullptr, .length = 0};
+ jpegr_compressed_struct new_jpg_image = {.data = nullptr,
+ .length = 0,
+ .maxLength = 0,
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+ std::unique_ptr<uint8_t[]> dest_data;
+ if (decoder.getEXIFPos() >= 0) {
+ if (pExif != nullptr) {
+ ALOGE("received EXIF from outside while the primary image already contains EXIF");
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ copyJpegWithoutExif(&new_jpg_image,
+ primary_jpg_image_ptr,
+ decoder.getEXIFPos(),
+ decoder.getEXIFSize());
+ dest_data.reset(reinterpret_cast<uint8_t*>(new_jpg_image.data));
+ exif_from_jpg.data = decoder.getEXIFPtr();
+ exif_from_jpg.length = decoder.getEXIFSize();
+ pExif = &exif_from_jpg;
+ }
+
+ jr_compressed_ptr final_primary_jpg_image_ptr =
+ new_jpg_image.length == 0 ? primary_jpg_image_ptr : &new_jpg_image;
+
+ int pos = 0;
+ // Begin primary image
+ // Write SOI
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
+
+ // Write EXIF
+ if (pExif != nullptr) {
+ const int length = 2 + pExif->length;
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, pExif->data, pExif->length, pos));
+ }
+
+ // Prepare and write XMP
+ {
+ const int length = xmp_primary_length;
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
+ JPEGR_CHECK(Write(dest, (void*)xmp_primary.c_str(), xmp_primary.size(), pos));
+ }
+
+ // Write ICC
+ if (pIcc != nullptr && icc_size > 0) {
+ const int length = icc_size + 2;
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, pIcc, icc_size, pos));
+ }
+
+ // Prepare and write MPF
+ {
+ const int length = 2 + calculateMpfSize();
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ int primary_image_size = pos + length + final_primary_jpg_image_ptr->length;
+ // between APP2 + package size + signature
+ // ff e2 00 58 4d 50 46 00
+ // 2 + 2 + 4 = 8 (bytes)
+ // and ff d8 sign of the secondary image
+ int secondary_image_offset = primary_image_size - pos - 8;
+ sp<DataStruct> mpf = generateMpf(primary_image_size, 0, /* primary_image_offset */
+ secondary_image_size, secondary_image_offset);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP2, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, (void*)mpf->getData(), mpf->getLength(), pos));
+ }
+
+ // Write primary image
+ JPEGR_CHECK(Write(dest, (uint8_t*)final_primary_jpg_image_ptr->data + 2,
+ final_primary_jpg_image_ptr->length - 2, pos));
+ // Finish primary image
+
+ // Begin secondary image (gain map)
+ // Write SOI
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kSOI, 1, pos));
+
+ // Prepare and write XMP
+ {
+ const int length = xmp_secondary_length;
+ const uint8_t lengthH = ((length >> 8) & 0xff);
+ const uint8_t lengthL = (length & 0xff);
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kStart, 1, pos));
+ JPEGR_CHECK(Write(dest, &photos_editing_formats::image_io::JpegMarker::kAPP1, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthH, 1, pos));
+ JPEGR_CHECK(Write(dest, &lengthL, 1, pos));
+ JPEGR_CHECK(Write(dest, (void*)nameSpace.c_str(), nameSpaceLength, pos));
+ JPEGR_CHECK(Write(dest, (void*)xmp_secondary.c_str(), xmp_secondary.size(), pos));
+ }
+
+ // Write secondary image
+ JPEGR_CHECK(Write(dest, (uint8_t*)gainmap_jpg_image_ptr->data + 2,
+ gainmap_jpg_image_ptr->length - 2, pos));
+
+ // Set back length
+ dest->length = pos;
+
+ // Done!
+ return NO_ERROR;
+}
+
+status_t JpegR::toneMap(jr_uncompressed_ptr src, jr_uncompressed_ptr dest) {
+ if (src == nullptr || dest == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (src->width != dest->width || src->height != dest->height) {
+ return ERROR_JPEGR_INVALID_INPUT_TYPE;
+ }
+ uint16_t* src_y_data = reinterpret_cast<uint16_t*>(src->data);
+ uint8_t* dst_y_data = reinterpret_cast<uint8_t*>(dest->data);
+ for (size_t y = 0; y < src->height; ++y) {
+ uint16_t* src_y_row = src_y_data + y * src->luma_stride;
+ uint8_t* dst_y_row = dst_y_data + y * dest->luma_stride;
+ for (size_t x = 0; x < src->width; ++x) {
+ uint16_t y_uint = src_y_row[x] >> 6;
+ dst_y_row[x] = static_cast<uint8_t>((y_uint >> 2) & 0xff);
+ }
+ if (dest->width != dest->luma_stride) {
+ memset(dst_y_row + dest->width, 0, dest->luma_stride - dest->width);
+ }
+ }
+ uint16_t* src_uv_data = reinterpret_cast<uint16_t*>(src->chroma_data);
+ uint8_t* dst_u_data = reinterpret_cast<uint8_t*>(dest->chroma_data);
+ size_t dst_v_offset = (dest->chroma_stride * dest->height / 2);
+ uint8_t* dst_v_data = dst_u_data + dst_v_offset;
+ for (size_t y = 0; y < src->height / 2; ++y) {
+ uint16_t* src_uv_row = src_uv_data + y * src->chroma_stride;
+ uint8_t* dst_u_row = dst_u_data + y * dest->chroma_stride;
+ uint8_t* dst_v_row = dst_v_data + y * dest->chroma_stride;
+ for (size_t x = 0; x < src->width / 2; ++x) {
+ uint16_t u_uint = src_uv_row[x << 1] >> 6;
+ uint16_t v_uint = src_uv_row[(x << 1) + 1] >> 6;
+ dst_u_row[x] = static_cast<uint8_t>((u_uint >> 2) & 0xff);
+ dst_v_row[x] = static_cast<uint8_t>((v_uint >> 2) & 0xff);
+ }
+ if (dest->width / 2 != dest->chroma_stride) {
+ memset(dst_u_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
+ memset(dst_v_row + dest->width / 2, 0, dest->chroma_stride - dest->width / 2);
+ }
+ }
+ dest->colorGamut = src->colorGamut;
+ return NO_ERROR;
+}
+
+status_t JpegR::convertYuv(jr_uncompressed_ptr image, ultrahdr_color_gamut src_encoding,
+ ultrahdr_color_gamut dest_encoding) {
+ if (image == nullptr) {
+ return ERROR_JPEGR_INVALID_NULL_PTR;
+ }
+ if (src_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED ||
+ dest_encoding == ULTRAHDR_COLORGAMUT_UNSPECIFIED) {
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ ColorTransformFn conversionFn = nullptr;
+ switch (src_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ switch (dest_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ return NO_ERROR;
+ case ULTRAHDR_COLORGAMUT_P3:
+ conversionFn = yuv709To601;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ conversionFn = yuv709To2100;
+ break;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ switch (dest_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ conversionFn = yuv601To709;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ return NO_ERROR;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ conversionFn = yuv601To2100;
+ break;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ switch (dest_encoding) {
+ case ULTRAHDR_COLORGAMUT_BT709:
+ conversionFn = yuv2100To709;
+ break;
+ case ULTRAHDR_COLORGAMUT_P3:
+ conversionFn = yuv2100To601;
+ break;
+ case ULTRAHDR_COLORGAMUT_BT2100:
+ return NO_ERROR;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+ break;
+ default:
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ if (conversionFn == nullptr) {
+ // Should be impossible to hit after input validation
+ return ERROR_JPEGR_INVALID_COLORGAMUT;
+ }
+
+ for (size_t y = 0; y < image->height / 2; ++y) {
+ for (size_t x = 0; x < image->width / 2; ++x) {
+ transformYuv420(image, x, y, conversionFn);
+ }
+ }
+
+ return NO_ERROR;
+}
+
+} // namespace android::ultrahdr
diff --git a/libs/ultrahdr/jpegrutils.cpp b/libs/ultrahdr/jpegrutils.cpp
new file mode 100644
index 0000000000..c434eb6459
--- /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 <ultrahdr/jpegrutils.h>
+
+#include <algorithm>
+#include <cmath>
+
+#include <image_io/xml/xml_reader.h>
+#include <image_io/xml/xml_writer.h>
+#include <image_io/base/message_handler.h>
+#include <image_io/xml/xml_element_rules.h>
+#include <image_io/xml/xml_handler.h>
+#include <image_io/xml/xml_rule.h>
+#include <utils/Log.h>
+
+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<char*>(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<const char*>(xmp_data), xmp_size);
+ MessageHandler msg_handler;
+ unique_ptr<XmlRule> 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<string> kConDirSeq({kConDirectory, string("rdf:Seq")});
+ const vector<string> 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<string> 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 0000000000..f1679ef1b3
--- /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 <ultrahdr/multipictureformat.h>
+#include <ultrahdr/jpegrutils.h>
+
+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<DataStruct> generateMpf(int primary_image_size, int primary_image_offset,
+ int secondary_image_size, int secondary_image_offset) {
+ size_t mpf_size = calculateMpfSize();
+ sp<DataStruct> dataStruct = sp<DataStruct>::make(mpf_size);
+
+ dataStruct->write(static_cast<const void*>(kMpfSig), sizeof(kMpfSig));
+#if USE_BIG_ENDIAN
+ dataStruct->write(static_cast<const void*>(kMpBigEndian), kMpEndianSize);
+#else
+ dataStruct->write(static_cast<const void*>(kMpLittleEndian), kMpEndianSize);
+#endif
+
+ // Set the Index IFD offset be the position after the endianness value and this offset.
+ constexpr uint32_t indexIfdOffset =
+ static_cast<uint16_t>(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<uint32_t>(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 0000000000..00cc797591
--- /dev/null
+++ b/libs/ultrahdr/tests/Android.bp
@@ -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.
+
+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: "ultrahdr_unit_test-deprecated",
+ enabled: false,
+ test_suites: ["device-tests"],
+ srcs: [
+ "gainmapmath_test.cpp",
+ "icchelper_test.cpp",
+ "jpegr_test.cpp",
+ "jpegencoderhelper_test.cpp",
+ "jpegdecoderhelper_test.cpp",
+ ],
+ shared_libs: [
+ "libimage_io",
+ "libjpeg",
+ "liblog",
+ ],
+ static_libs: [
+ "libgmock",
+ "libgtest",
+ "libjpegdecoder",
+ "libjpegencoder",
+ "libultrahdr",
+ "libutils",
+ ],
+ data: [
+ "./data/*.*",
+ ],
+}
diff --git a/libs/ultrahdr/tests/AndroidTest.xml b/libs/ultrahdr/tests/AndroidTest.xml
new file mode 100644
index 0000000000..1754a5ccb9
--- /dev/null
+++ b/libs/ultrahdr/tests/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Unit test configuration for ultrahdr_unit_test">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push-file" key="ultrahdr_unit_test" value="/data/local/tmp/ultrahdr_unit_test" />
+ <option name="push" value="data/*->/data/local/tmp/" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="ultrahdr_unit_test" />
+ </test>
+</configuration>
diff --git a/libs/ultrahdr/tests/data/jpeg_image.jpg b/libs/ultrahdr/tests/data/jpeg_image.jpg
new file mode 100644
index 0000000000..e2857425e7
--- /dev/null
+++ b/libs/ultrahdr/tests/data/jpeg_image.jpg
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-318x240.yu12 b/libs/ultrahdr/tests/data/minnie-318x240.yu12
new file mode 100644
index 0000000000..7b2fc71bc0
--- /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;<?DEKPROLQSSDE‘ÃÀ¿ÁµIwËÅÆÅÅÄÃÁ¾»¹¶´±­¬¬¯³¶º¿ÂÅÆÇÇÈÇÇÆÆÆÇÈÇÅÄÃÂÄÃÁÂÅÅÆÇÈÇÇÈÇÉÉÊÈÇÄÂÀ¾¹´¯¬§£¡Ÿ›š™™››ž¢¤¦«®­­­®®¯°¯®®­©¥ ™›–Š~ƒ…Œ—¢®³¶¶¶µ°¦¢ ›šŸ©²·¾À¾¾»¶°§¡¯¯®®®°±¶¶­©¨§¥¢žš˜˜–•––‘Œˆ€|}}y{ywspmjiigefb\`r{€ˆ˜¤§§«¯²¸½ÁÄÆÈÌÎÏÑÑÏÍÍÎÎÌÊÈÈÄÃÄÃÃÄÆÇÈÇÅÈÈÇÇÇÈËËÊÊÌÌËËÊÊÉÉÇÇÇÉÈÉÆÄÂÅÄÃÃÃÃÁÁÂÁÀ¾½»¼¼¼»¼½½ºº¹¸º¹º»¹·µ²³±¤™’Ž‚`=243.6IOPOSWY]^ehy¦ÆÁ¿Ç@¥ÉÃÅÄÄÄÿ¼¹¶³±¯¬­¯³µ»ÀÄÆÉÉÈÉÉÈÇÇÆÆÈÈÇÅÄÄÃÃÃÂÃÆÇÈÉÉÉÉÈÈÉÊÊÈÇÁ¿½ºµ±ª§¦¤ œšš››››ž¢¤§¬®­¬«¬¯°°¯­¬«©¥ž˜š›˜“ƒ~†œ©±µµ¶·²¨¢Ÿž›š£­¶¼¿À¾½¹³¬¨£µ±¯¯®®¯²·²¨¥£¡Ÿš˜——“‘““Žˆ~|{{zwsomjihggcZV\nw‡•£§¨©¬¯µ»ÀÅÇÌÎÏÐÐÎÍÍÌÌÍÊÉÆÄÂÂÂÀÂÃÄÆÆÆÇÈÈÇÆÇÉÊÊÊÊÊËÉËËËÊÊÉÉÈÉÈÇÆÄÅÆÅÅÅÅÅÃÂÁ¿¾¼¼»»¼½¾¾¾½»ºººº¹ºº¹¸¶¶´´µ·µ·º»´šuT?2>X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdŠ§¶¸¹¹º»½ÀÂÂƯEŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:<GZoŒ£®·œC}ÐÈÈÆÅÅ¿º·²±²³²°¯³¸½ÃÇÈÉÈÉÈÈÈÇÇÆÈÈÈÈÇÆÆÇÈÇÆÈÇÇÇÈÇÇÇÇÈÇÇÆÄ¿¼¹¸²°­«¨¤Ÿ›˜––—›› ¤¦©­¬¨ª¬¯¯±±­«««ª¨ –”’Œz~„Š”ª³³³µµ°©£™–š£©±¸¾¿¿½¼»¹³¬©¥¡¿º·¶³®«§¦¥¥¯°—”‘ŽŒ‹Š‹Š‘‰~yurqqnjeb`]WRLKYouz‹Ÿ¡¢£¢¢¡£¥©¯´·»¼¸·¸¹º»»½¼º¼¼¼¼¾¿¿ÀÁÁÁ¿ÀÂÂÆÈÇÆÈÆÇÈÈÈÉÉÈÉÌËËÌËÌÌÉÇÅÇÈÈÇÇÆÅÄÂÁÁÀ¿½½¼¹»»¹ºº¹···¸·µ²²²²µ·¸¸µµ¶·¶¶··¸·º¼¼¾Á¿³˜ƒoYIABGVLlÅÎÉÇÆÄ¿»¸µ´´µ³²®¯µ»ÁÅÉÊÊÉÉÈÈÈÈÇÅÆÇÇÇÈÈÉÈÇÇÇÇÇÇÈÇÇÈÉÈÇÆÅÂÀÀ¼¸¶³±«¨§¦£Ÿ™——™š›œž£¦ª­­¬«¬­®¯­¬«ªª¨¦Ÿ–“Ž}zˆ‘™¡¬²³·´°¨¡ ›˜˜Ÿ¦®µº¼½¾½º¹µ±¬¨¤¥¾»·®©¨§¦Ÿ ¤¯ªš•’‘‹Š‰‹Š‹‹ŒŒ†€{vqoomida^[SLIGWqtw‡ ¤¢¡    ¡£¨ª«±±°°°°²µ·¹¸¹»º»¼¾¾ÀÁÁÂÁÁÂÂÃÅÆÇÈÆÆÉÉÉÉÈÉÈÈËËËÌÍÌËÊÊÊÈÈÈÇÈÇÆÅÃÂÂÂÁ¾¼»ºººº¹º¸¶µµµ¶´´´µ·¹º»»¸¸¹¹¸·¸¸¸¹¼¼¼¼¼¾ÀÂÅÉǼ³ž„v|™ÆÎÊÊÈÆÄÀ»·±±µ¶¶µ²®±¸½ÄÈÊÊÌÉÇÅÅÅÃÄÃÂÄÅÄÄÄÆÆÆÇÈÇÈÈÈÇÈÉÈÈÅÿ¿¼¸¶²°­¨§¦¤¡ ™—™›œœž¡¦©¬­¬­®®°®¬¬ªª©¦¥¡“„wz€‹”ž§­±µ¶³«¢Ÿ›™šŸ¤«²¶¸¼¾½¼»¸³¯©£¡¨¸º¹¯¢  ž•—›œŸ­¯™•““ŒŒ‹ˆ‰ˆ‡…‚{xtonliea]ZOIDCOptwƒœ££¢ŸžŸ ¡¢¢¨«­­¬¬­¯²³µ¸¹¸¸»½½ÀÁÁÂÂÂÂÃÃÄÄÅÇÇÇÆÈÉÉÉÉÈÈÉÊÍÍÎÍÊÊÌÌËÉÈÈÈÈÈÈÆÅÆÅÃÂÁ¿¾¾½½»¹·¶µµµµ·¸¸»½¾À¿¾¼»¼»ººººº»½½¾½¾¾¿ÀÂÃÂÁÅÇÇÄÈÍËÊËÉÈÆÁºµ°°°±´µµ³³¶»ÀÆÈÉÉÇÄÃÁ¿¿½»º»½¾¿¿¿ÁÂÃÆÅÆÆÇÇÆÅÅÅÄÂÂÁ¿¿»·µ±­ª§¥¤¢ Ÿ›˜—™›› ¢¢§ª¬­®¯¯°°¯­¬«©¨§¤“ŒywzŽ˜¤¬¯²µ´¯§ œ››¤ª¯³·º¿¿¾¼»¹´¬¦¡£«³´³³©–’””’•­°ž“”‘Œ‹‰‡‡ˆ†„~{xtqolhec]UMHA@Iluxƒœ¤¢¡ž››ž ¢¡Ÿ ¥¨ª««¬­®°³µ´´¶·¸º»½¿¿ÀÁÂÃÃÃÄÃÄÅÅÇÆÇÈÉÈÊÊÊÊËÍÍÎÌËËËÌÌÊÉÉÈÈÈÇÇÇÇÆÆÆÇÃÀÁÀ¿»¸¹·¶µ·º½ÀÁÂÃÃÃÄÂÀ¾¾¿½½½½¾½½¾¿¿¿¿¿¿ÁÂÁÂÀ¿ÀÄÇÉÊËÉÆÄÀ»¶°®®­­¯±²³µ¹¿ÆÈÈÆÅÄÁ½»º¹·¶³³¶¸ºº»»¾ÀÂÃÄÅÆÅÄÃÃÃÃÃÂÀ¾º¸¶µ°«¨¥££¡ž›™™œ››› £¤©­®­­­°®®®«ªªª¨§£›‘…uw}‰’ž¦¬±´²°ª£Ÿœœž¥ª¯´¶»¾¾½½»º¶°«¦¤§°©©ª²¯£Œ‹Ž‹Ž’š¨µ¦˜’‘ŽŒŠˆ‰ˆ‡‡‚zvtplifcaZRJE=9@gvz…¤¢žŸ›žŸ ž £¦¨©©«¬¬¯²³³´µ·¸¸º»½½½¿ÀÁÂÃÄÃÄÅÆÇÇÇÈÈÇÈÉÊÌÌÎÎÎÌÌËÌÍÍÌËËËÊÊÈÇÇÇÇÇÈÈÆÄÄÄÿ»º¹º¹¹¼¿ÃÇÆÈÇÇÆÄÄÂÁÀÀ¿ÀÀ¿¾¾¿¾¿ÁÁ¿ÂÄÄÄÃÂÃÆÉËÌÊÈÄÁ½»·³°®ª«®¬­®³·½ÁÇÆÄÂÂÀ»¹·¶¶µ±¯°²²µ¶·¸»½¾¿ÀÁÃÄÂÂÃÃÃÁ¾¼º¸µ³°¬§¤¤¡ Ÿžš™™œœ›¡¡£¥ª­®­­¬®­««ª¨§§§¥¡švtz‚™¢ª¯²²¯¬§¤¡ ¢©®³¶¹»¾¾½¼»º¸´°ª§§®´¢¢¤§©§™‹…†‡‡‡Š“£°ªŸ•‘ŽŒ‹ŠŠ†„|uqmkida]WOD9407cz|…— ¢žœš›žžœ¡££¥§¦¨«¬­°²³´µ¶·¸¸¹»¼¼¾¾¾¿ÀÃÄÄÅÅÆÇÇÇÆÇÇÈÉËÌÏÐÏÎÎÌÍÍÎÎÍÍÎÍËÊÉÉÈÈÉÈÉÈÆÆÇÄÁÀ½»¼¼¼¾ÁÅÆÊÊÊÊÉÇÅÄÃÂÁ¿¿À¿¿À¾¿ÂÁÀÀÃÄÄÃÄÄÅÈÊÊÉÇÃÀ¾»·´²²°®­¯¯®±µºÀÄÅÄÃÀ¾º¶´³±²°­­­­¯±²³µ¶·¸¹»¼¾ÁÂÁÁÁÀ¾»¹¸¶³°¬¨¦¤¤¢ œ›™™›››Ÿ¢¤¦¨ª«¬¬¬­­­¬¬«¨§¥¤¢Ÿ—ow|ˆ’§­²±®­©¨¦¦¨¬³¶¹º»¼¾½½¼º¹µ²¬§¥¬³µ›œžŸ¤¤‚€€„„†˜¨­¨ž”’ŽŠ‹‡ƒ~ztplkeb\YQD:41.1Xx}†– ¢ ›šš›žž ¡¢£¤£¦ª¬­¯²³´µµ¶¶·¸º¼¾¿¿¾¿ÀÂÄÄÄÄÄÆÇÇÆÇÈÈÊËÌÏÑÐÏÎÍÍÏÐÏÏÎÍÎËËÊÊÉÊÊÊÊÉÉÈÇÅÁÁÀ¿ÀÁÀÂÃÆÈËÊÊÉÆÅÃÂÀÀ¿¿¿¿¿ÀÁÀÀÀÀ¿ÀÃÃÂÁÄÆÆÉÊÉÈÄÀ¾º¹·¶µµµ´²²°°´·»ÀÄÅÄÃÀ»¶±°¬ª«¬©§¥§©¬¬®°²²´µ¶¸¸»¾¿¿¾½¼¹¶µ²¯¬¨¦¤¤¤¢ššš›œœ›œ ¢¥¨ªª¬¬­®­¬­¬«©§¨¥¡Ÿ‘wty‚Œ–¤«¯¯­¬¬«ªª¬°µ¶º½½¼½»»½»¸¶³°©¥§­²´–”“‘•˜  €~{}~€„‡‘ ­±¦•Š‡|vsojfb\VME?;620/Jv}†”Ÿ¢ ››˜˜›ž   ¢£¤£¥¦¨ªª®²²´¶¶·¶¶·¹½ÀÁÁÂÃÄÄÄÅÅÄÅÆÆÆÇÈÊÌÌÍÎÐÑÑÎÍÏÑÑÐÐÎÍÌÌÌËÊÊËËÊÈÉÊÊÊÈÆÅÅÃÃÅÇÇÉÉËËÉÈÆÃÁ¿¾¿À¿¿¿¾¿¿ÀÀÁÀÁÁÃÄÃÃÃÆÈÉÊÊÉƾ½¹¶µ´´´µ¶¸¶³³´¶»¿ÂÁ¿½¸³°«¨¥¥¥¤£  ¤¦¨¨©««®±´´µµ·º»ºººº·µ´°¬©¦¥£¢¡žœ™˜˜œž ¡¥©«ª«¬­®­¬­¬©¨§§¤ žsu{…ž¨®¯­¬¬¬¬­®²¸»½¾¾½¼¼»¼¼º¹¹³«¦¦«°±²™Š‹Œ™•…~{{zz{~…˜¤°¯©“‰„}vrmgb^ZQIHDB?9736Dk}…šžŸœš——šœžŸ ¢¤¤¥¥¥¤¦ª¬¯¯±±²´´³³µ·»¿ÁÂÃÄÄÄÅÅÄÄÄÆÆÆÈÉËÍÍÎÐÑÑÏÎÐÐÐÑÏÍÌÌÌËÊÊËÍÎÌÊÊÌËÌËÊÊÉÇÈÉÊÉÉËÌÊÉÇÄÁ¿¾½¾¿¿¿¾½¾¾¿¿¾¿ÀÂÃÄÅÇÇÊÊÉÉÇÅ¿»¹·´²°®­°²´´³´µ¶¹¼»·µ±§£¤¤¡  ž›œžŸŸ¢¥¤¦¦¦©«®®°±´µ¶¸¹·¶µ´±®©§¦¤¡žœœ›˜——šœžžžŸ¢¥©«««¬­­¬¬­¬ª¨¦¥¤¢Ÿ›‡twŠ—£«®¬«ª¬«ª®³¸¼½½¼½»º»½½¼¼¹¶¬¦¦©­°±±–Ž‰ˆ‰‰Š–„|yxyz{|}ˆœ¦«­®¦“upe`\WOIGHGCA@=;:Bdz‚Š˜Ÿœ›™——šœžŸ Ÿ¡£¤§§§©¨©¬­¯­¯²²²³´¶¶º½½¿ÁÂÃÃÃÃÃÄÅÆÈÇÇÉËÎÐÏÐÐÏÏÐÑÑÐÏÐÊÂÊÎÍÎÏÎÏÎÌÍÍÍÌËÌËÈÉËËËÊÉÊÊÉÈÄÁ¿½½¼¾½¿¿½»»¼¼¼¼¾ÁÃÃÄÆÈÉËÊÉÇÿº··´±®­¬««ª«®¯³´¶·´­ª¥˜–˜™˜•”‘Ž’˜š›ž £££¥¦§ª«¬¯±³µµµµ³²±®ª¦¤£¡Ÿšš™™™˜™žž ¢¤§ªª««¬­¬«ª¬«ª¨¥¤¤£Ÿ›‡t}„ž¨¬¬¬ª©©©©®´¸º»ººº»ºº¼¼»»¶¯¦¥¥¦«®¯°“‹‡„„…ƒˆˆ}yxwvvxy{„‡—Ÿ¬®³®£‘}k_VOMIJIGEBA@=?Yr}ˆ–ŸŸš–˜˜˜˜–šžžœœ £¦¨ª«¨§©«¬®°±°±²´´µµ¸»½¿ÁÁÃÂÄÅÆÆÆÈÇÇÈÊÌÏÑÐÐÏÎÏÏÏÏÐÕ»]_lw‡²ÑÏÏÎÎÍÍÍÌÊÉÈÌÎÍÎÍËÊÊÉÆÁ¿½½¾ÀÀÁÀ¿¿¾¼¼¼»»¿ÁÃÃÄÆÇÈÈÈÆÄÁÀ»¸¸¶´±­©¨§¥¥¤¤©­¯±±¬¦¢–‘ŽŒ‹Š‹ˆˆ‹•˜š ¢£¦¨ª««­¯²´´´¶µ²°­«¨¤£¢Ÿœ™šš›š™™›ž  ¡¥¨©«¬­­«ª«ª«©ª¨¥¤£ ™„y€š¨««ªª¨¨§¨ª®²´µ¶···º¹¹¹»º¶®©¥¤¤¥¨¬«¬”Œ†‚‚‚…Š…}wusvxxxz~‚‡ˆ“œ£ª°³µ¯¥œ‹xoeZRPIFDBPlz†‘žœ•”••”’•—››šŸ£¦©ª¨©¨¨§ª­®¯®¯±²´µ¶¹¼¾¾ÀÂÂÃÅÆÆÆÆÇÈÈÊÍÎÐÒÑÐÏÎÎÏÎÎÎÒ«:8752†ÖÏÍÍÎÎÎÍÊÈÉÈ«¨°´µ·¸ÄÈÄ¿ÁÄÅÅÄÁÁÁ¾½½¼¼¼¼½¾¿ÀÂÅÄÆÉÌËÇý´²¯©©§¡››Ÿ¡£¡¡¤§¨¨ª¤žœ—‘ŠˆŒŠŠ„„Š“–™›¢¥¦ª­­®¯±µµ¶¶·¶²¯ª¨¦¤¡¡žœ›™™š›œ›œž  Ÿ ¥§¨©ª««¨¨¨¨§§©¨¥¤¡ž›–z†”¥¬«ªª©©©¨ª«¬±²±´´³³¶¸ºº»¹²¬¦£¢£§««©«•‹‡ƒ~€€‚‚~xurssutuxy{{~‚‡‹’š ¦­°³±±°¬¥Ž}jYOKPhuƒšœ›—‘’‘‘””–™™›Ÿ¤¨¨¨¨¨§¦¨©ªªª­®°³µ·¹»½½¿ÀÁÁÃÅÆÇÅÆÇÈÉÍÏÑÑÐÐÏÎÎÏÏÏÎÓ™:?>?:–ÖÌÍÍÍÌÍÊÊÒºl><ABDWÂÆÈ‹exŸ¯¹ÁÅÇÃÀÀ¾½¿ÁÀÂÂÁÂÞœ†qbUHD?866732489<>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"  
+ <t{}‚ƒƒƒƒƒ†‰‰ˆˆŠ„‚~~{yvussssssqqrpqqpnqpqpopqrtwz}„‰‘••’‹†„„††‡5"w€{{|t#b‹„†Š‹Ž|<”’”•š ¤£££¡Ÿš“Žˆ†jT‰†Š”–˜œ ¥¨ª¬¯±M)/164>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<BGIKMOQTWWWSOOSY[_bbdgkptxvpqqosx…ˆŒ’’”•™ 
+
+
+
+
+
+
+
+
+ Gƒ’Ÿ§««¬¬ª¦¢˜“‘ŽŽ”–•–——˜™š›œœš›œ››››Ÿ¦°¶¼ÂÄÂÀÀÂÃÅÆË‹5172692.5=?;963367583A¡ÍÂÃÄÃÂÀ½¼¾ÃÆËÌÒ¯:-343‚ƺº»¾ÁÂÁÁÂÅÆÆÆÅ˨ynad¼ÊÊÉÉÉÇÆÄÁ»º¶°© ››D6trsrqqpnpp'f~yyyz|}~ƒ†‹Œ’ŽŒ‰‡‡„~|{}~~~||{xuuusqnnlkkkheb`cQ248<BFIKMORTUSNMPTX]^_`deioswqppons|„„ˆ‘” 
+
+
+
+
+
+
+
+
+
+
+ 1t›¤©«¬¬¬«¦¢Ÿœš˜”“–—˜››™š››œ›š˜––—˜™ ¬¸¿ÄÆÅÆÈÊËÌÎ|)1:7723Mw­³­¦c=0:98=;>œÉÂÃÃÃÃÂÄÇËÌÌÌÆ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#
+ !(,7<?BGJLKJGJMRVYZ\]\acehijot}‡”•”“”•••–••  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rx…“ž£¦ª¬¯²²±¯­­«§¦§§¨§§¦£¡ ž˜‰„„ƒƒ„…†Œ’ž¤«°¶´D-6:5XÅËÇÈÈÈÈÇÆÇÇÇÅÆÆÅÃÁÇÊÇÆÆÈÉÊÌÍËÇÁ·¸”,+,./-/2286VÃÂÂÂÂÂÂÃÅÆÆÅÍz5:89?«Á»¹¸±¬¨Ÿ™ˆ…€|xycZtrrsz;#|Œ‘’’““”—™šž ¡¡žœ™—”“”••••“‹ˆ‡…‚‚‚‚‚‚……ƒƒ‚{~B #(.8>AGJJJIHJMPUXYZ[\_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"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4<AFFEDFILNPRTYZY]]\]amy‚„ˆ‹“””–”•—›š   
+
+
+
+
+
+
+
+
+
+
+
+ O„ˆ– ¨¬®°±²´²°¯¯®ª«©¨§¨©¨¨§¥¤ž•‹†ƒ€ƒ„Š“˜¥ "$)3¨¿ÀÃÅÇÈÈÈÈÇÇÇÅÄÄÅÆÆÉÈÈÊÊÊÉÈÆÃÁ½Ãš11114,wʪ96589C¶ÄÂÃÃÃÄÆÆÃĺF6763D®¯¨¤ž™’‡€}{xutxN#NWw|F8Y5Œ—›žŸžžžžœžžŸ¡¢£¢¢¢¢¢¡ Ÿ›™—”’‘‘ŽŽŽ’•—š›žš™“` #)-4:@EFDCEIKNPRUVXZ\\[[[`fnuz€…†‰ŽŽ“•–”•“
+
+
+
+   
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  ,w„“¥«­¯°±²³³³²°®­¬­¬¬¬¬«ªª©¥Ÿ–Œˆ…‚€€‚„Š›~ "* ½»¾ÀÄÅÆÅÅÅÅÅÅÅÅÇÇÉÊÊÊÊÊÉÇÆÅÅÂñ@.4271L¼»¿`066:2‹ËÁÁÁÄÅÅÄÃÄ°?7640D¨£žš”Š„€}{xutvt-:h-|j}UB—•˜œž ¡¢¢ žžžŸ ¡¢££¤¥¥£¢ žœ››š—””“”“”–˜›ž¡¤¦¦¥¥¡‹,
+ !'+29@DCCCEIJJMPTUUYZYYYY[\^hmrx{}ƒŠŽŒ‹‹Œ
+
+
+
+
+
+ 
+
+
+  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ \‡— ¨¬­®®±³¶·µ³±¯¯®®°±¯®¬«¬©¥ ˜ˆƒ€€ƒ‡$‡º´¸¼¾ÀÁÁÂÃÃÁÁÃÄÇÈÊÊÊÈÇÇÆÅÄÂÂÂÃ]*63352›Â·¿.6685VÃÀÀÁÃÃÃÃÂÆ£7710)D£›—“ŽŠƒ|yywust`Iw3U7\“OI—–™œŸŸ¡¢£¢  ¡¡¢¡¡££¡¡£¡¡ Ÿžœœš˜—˜™š›¡¢¦©«°¯®¬¨¨i
+ $)28?CBBEEFGGJNPSSTWWWXY[[^cfiortxx{€€€
+
+
+
+
+
+
+
+ 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  9u…‘¥«­®¯±²²³´³±¯­®®°±°¯®®®ª§£ —Ž‡„~}~„Š6U±®²µ·º¼¿ÀÂÃÂÁÃÄÅÈÉÇÆÅÄÃÃÂÿÈ~'2212,sǺ»¿·B47587 ÇÀÃÄÂÂÀ¾Æ”/2-+#AŸ”Ž‰„~zxvwvtrwEY}Y2–LR˜–šŸ¡¢£¥¥¤¤££¡¢¤£ ¡¡Ÿ  ž žœœš› ¡¤§ª­°²´¶¸¶¯¬9
+
+
+
+
+ $(28>BBCDEFFGGIMRRSVUVWX\`_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:<AEFJORVZ]]abfnruz‚…‡yj]E    
+
+
+
+
+
+
+
+
+
+
+
+ a{veY^`qˆ˜Ÿ¦ªª¬±µ¶¸¸¸¹¸¸¸··¶¶µ´³´´³²±¯®¬««©©§¤¢œ›—’Šˆ‡†‚ƒ‡‰ˆ‹‘“šž¢¤ª¯µ¸º¼¿Á¿¸¶²¬ª©­µ¾ÇÉÇƼ¶²©®Q/…ƒ…C #]²¯¤8*(,-g¸¶·¸····¸¹¸·´³³´´M,0.)ˆ´¯®®®®°¯¬©¦¥¥¦§§©¬±¹¿ÄÅÇÈÈÇÇÅÅÅÅÄÿº¶£lg$
+
+
+
+
+
+
+
+   /:AEILNVZ[]a`chmknkeYI?2(&&   
+
+
+
+
+
+
+
+
+
+
+ gywkZ]ae€–¤§ª«°´µ·¹»»º¸¸¸¸¶¶¶¶¶¶µ³²²±®®®®¬«©©©¥ ž™˜•‘ŽŠˆ…ƒ†‹ŒŽ”šš ¥©®®±³¸¹¹¶±­ª¨¯»ÃÇÆÅÁº²«¨£¤t*& !p…ƒ< !"#""$'+//447;BDy¸´²„}„Ž’¥·····¶¶¹ºº¸·´³³´¸¥˜˜’‘¨¯¯®®­­®¬©¦¦¦§©ª©®±¹¿ÄÆÈÇÈÇÅÅÆÆÅÅÂÀº±­df
+
+
+
+
+
+
+
+
+
+ 
+  $*3:<CFHNNOKFD<4)+&"%$&'   
+
+
+
+
+
+
+
+
+
+
+
+
+
+  kwwj\^adrŒ™Ÿ£¨«­²´´·º»º¸¸¸¸·¶¶µ¶µ´´³³³³³²²¯­¬­®­§¥¢Ÿœ™•‘Š†„€€‚ƒƒˆŽ“•š ¤¦¨«²·¸¸·³®«ª±¸¼»ºµ°«§¢–”†{utr}}tw{{~ƒ‡——™¡§­±¸¶¶·½º¼½½»¹¹¸¸º¹¸º¼¹¶´´¶µ¶´·¸¸µ¶°¯¯®®¯­­«§¦¦¨ª¬®°¶¹¿ÄÇÈÈÈÇÆÆÆÈÇÅÃÁ¼±©”cjb
+
+
+
+
+
+
+
+
+
+
+   $$!"!# """$%   
+
+
+
+
+
+
+
+
+
+
+  "rzyo^_aci|“™ £§¬±³µµ¶¸¹ºº»»º¸·¶µ³²²³³²²²±±°®¯¯°¯®­ª¦¢ž™’Žˆ‡…ƒ€€„‰Œ‘”™Ÿ¢§¬²µ´´·µ²±²±²±¯¯©¡—”‘ŽŒŠˆ†‰‹‰‚€ƒ‡‘Ž“”—™Ÿ¤¦¦§ª¯²µ¹ººººº»¼¼»»¼»¹¸¹¸¸º¸¶µ¶¶¶·¶²³²°°¯®®­­­­­«©¨ª«­°µº¾ÃÆÉÉÈÈÇÇÇÆÆÅÄÀ¾º¯¦šmbpa
+
+
+
+
+
+
+
+
+
+
+
+ 
+ !##rbTE1   
+
+
+
+
+
+
+
+
+
+
+
+
+ %tz{r\]^acm‡–›¡¤©¯³¶··¸¹ºº¼¼¼»¹·µ´²³´³±±±²²²±°±°¯­­ª¨¦£¢žš•ŽŒˆ†„‚„…†ˆŒ“˜Ÿ¦¬®®¯²µ´´³²³±­ª¦Ÿ™’ŒŠˆ‡ˆ‰‡„‚„†„„‡Œ‘‘’˜œ¡¤¥¦«®¯°²µº½¼¼¼¼½½½½½½½»º¹¹·´´µ´´¶µµµ³±´´²±¯­«¬«««­¬¬­®²µ¸¼ÀÅÉËËÊÈÈÇÇÇÇÇÄ»µ®¤›zagpa
+
+
+
+
+
+
+
+
+
+    #%œ›š–“‡|dF1 
+
+
+
+
+
+
+
+
+
+
+
+
+ .y}~s^[^adg~‘šŸ£©°²´¸ºº»»º¼¼¼¼¼º¸µ²´µ´²²±±²²²²²¯®®­¬ªª©§¤¢—“Š‡…†‡‡‰ŒŽ’”™¡¥§«°³¶´µµ··³®¥ —•‘Ž‹ˆ‡‡‡‡……†ˆ†„ˆŠ“•—œž¢¥§¨©¬¯±²³¶¹»¼¼¼¼½½½¼»»»º¹¸¸·µ´²²±±³²²²²²²³±°¯­¬«««ª­¯¯°´¹½ÂÆÈËËËÊÈÈÇÈÈÇÅÃÁ»´¯¦œ€adjs]
+
+
+
+
+
+
+
+
+
+
+
+  "#'•–”’“”˜™™’„j9 
+
+
+
+
+
+
+
+
+
+
+
+ 1}~~ud[]`ddoˆ˜Ÿ£¨®²´·¹º»½¼½½¼¼½»¹º¹¶³²²°¯¯±²°±±¯®¯¯¯­­­ª¨¥¢Ÿ›–’Š‡ˆˆ‰ŠŒŽŽ—¢¦«°²³·¹º¼¹´­£–•”‘ŽŒˆˆˆŠŒ‹ˆ‹‘”˜—›Ÿ¤¦¨¨ª­¯²´µ´¸º»¼¼»»¼½½»º¹·¸·µµµµµ³³±®®¯®®®®®­¬®®®­¬«¬®¯²´¶¸½ÀÅÉËÍËÉÊÈÈÉÊÈÇÄÁ¾¸³­§œŒi_flu\
+
+
+
+
+
+
+
+
+
+
+   #&(‘”““‘’“’’“—k  
+
+
+
+
+
+
+
+
+
+
+ 3€€zd[^_bdfuŽ˜¥«¯²´¸ºº¼¿¿¾½¾½»ºº»¸´²±°¯®¯°¯°±³²°¯®®®®¬«©§¤ œ™”ŽŠ‰ˆˆ‡ŠŒ‹“˜œ ¤©¯³¸¼½À¾¼¸®«¤¡Ÿ›š——“Ž’’’‘— ¡¢¥¨¬®­®¯°³´·¸º¾¿¾½¼¹»ººº¸··¶¶µ³³³³³°°¬««¬­¬«ª«««­®¯®¯±²³´¸»¾ÃÆÉÊËËÊÊÉÉÉÊÊÉÅ¿»¶²¬¨¡•r\cimq]
+
+
+
+
+
+
+
+ 
+
+
+ 
+ !$#%%)’’’’“““”’‘g 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <~€{h\^`cedm‚Œ˜¡¨®²´·¹º¼½¿¾½¼¼º¹º¸¸µ³³²±°°°±²³´²¯¯®®­­­«ªª©¥¢Ÿš–•ŒŠˆ†ˆˆ‹’–› ¥¬±·»½ÀÀ¼·³²¯©¦£ ›˜œ›››Ÿ¤¦¨­¯®¯±³¶¶µµ¶¶·¸»¾½»»º¹¹¸¹·¶¶¶´µ´´´³²°®­«ª«««¬¬­¬¬®®¯¯°³µ¹»¾ÁÃÆÊËËÌËËÉÊÊÉÊÊÈÅÁ¾»·±¯¨¦Ÿacgknq\
+
+
+
+
+
+
+
+
+ 
+
+    "#%'&&&*“’’’’’’’‘Ž’S  
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >ƒ‚{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:9<?O´¼¼º¸¸··³±±¯ª«“($!!%"§ ©lF§§§§¦¥¦§¦¤£¡ž›™——•˜iu›™Ÿ£¦©­´·¼ÆÉÊËËÊÉÈÇÅÀ»µ®¥˜…|tlosyzz~€€€€‚‡‘š ¦­³¹¹µ­Œ{Šž«°±²¯¦}
+
+
+
+
+
+
+
+
+ #'''''&&&%%&&%%##%+/65455300/-,+*)(!
+
+
+  6›  ¬³µµ´²±°¯¬ª¤™‹‡Œ’˜œœ˜•‘Œˆƒ~yutronopommlprrsstustvx{…—©³¹»¼¾¿¿¾½ÀÂÂÂÁ±?6324M¶»»¸¶µ´²¯¬ª¨¤®_ ! /˜£ ¥„ %ª§©¦¦§§¤£¢Ÿžš™˜–••@+’ž¢§ª¯³¹ÀÈÌÌÍÌËÊÊÉÆý¶¯¤—‹~tjlstwz{‚ƒ‚ƒƒ†Ž–ž£ª²·º¹´¥„|¢­±²²®¨€ 
+
+
+
+
+
+
+
+
+
+ $('''(('&&&&%&%$%#%,353310///,*'')&$
+
+
+
+
+
+  \­žžª°µµ´´³°¯®«§†Š—žŸœš–‘ˆ…€|ywsqqsrpopqqrrrrstutux}ˆœ¬²µ¸»¼½¾¾ÀÁÂÂÀ¨5.+++O»½º·´²±®ª¨¤Ÿ¢ˆ$M£ž ¡™/ b°©©§§§¤¢¡Ÿžžœ›™•žjO¤ž ¦¬°µ»ÀÆÉÊËËËËËÉÆÃÀ·­£—‰}ulkotwxz|„„„„†Š’š ¨°µ¹º¹±š|~”¤­²³³®¬{
+
+
+
+ &)))'&%&&%&&$%$%&%$&+.211/---,*('&&%"
+
+
+
+
+ #†´¢©¯³´´³³°°¯®«£“ˆˆ”›Ÿ  Ÿœš˜”†~|xutuvvutsssttstvvuxxz€¥¬¯°³·¹º¼¾¾¿À¿ÂŸ53,,,Y½»¸´°¬ª¦£•›4z šž£U! !)Š°¥¥¥¤¡Ÿžžžžœ˜‰.€§¢¥­²µ»ÀÅÈÊËÊËÊÊÉÇĽ¸­¤˜‰umkortx{~€‚„…„†‰Ž–¤«±¶ºº·¬{†˜¨°³³²®¨
+
+
+
+
+
+ &***'%&&%%%&%%%$&(&&',/!001--.,))'&&&" 
+
+
+  ?¦´¤œ©¯´´µ³³²°°°­¦–Š…‹”›¡¤£¢¡ž›™•“‰ƒ€|zyyz{zxvvtuuuuxz|~€‚‡¥©¬®±³µ¶·¸¹¼¼Á‘5:497j¾µ²°¬¦¢Ÿš””„9=š˜™¢ˆ ! "#"7˜¬¢¡ Ÿžžœ›˜˜”? A £¦«³µºÁÅÈËÌÌÊËÉÊÆÁ¾¶« œŽ~volosuvz}ƒ„……‡Œ•œ¢©¯²·º¹´§ˆ|‹ž¬²³³³¯¢„6 
+
+
+
+
+
+
+'++)(('&&&&%%%&%&&&&%&(1#/.,,++)%&&%$# 
+
+
+
+
+
+
+   b²´£©­²µ´´³³±±±¯©œ…‡—ž¦¥¤¢ œ›™–“‘Š†…ƒ||~~}zyxxx{}€ƒ‰‰•Ÿ¤¦¨©«­®°´´¶·À‚+4361w¿²¬¨¥œ››˜e)›•™ž¤J$""#7‡§Ÿšš™——˜˜›¡Œ@#  ¥£©°·ºÁÇËÌÌËËÊËÉÆ»¶®¡šƒwninsvwyy|€ƒ„„…ˆ‹‘š ¦®±´¸º¸±œ€ ­²´³³®š€Y  
+ !*-+)(((''&%%&&&'%$%%$$&+/****&%&$$$"!
+
+
+
+
+
+  "‡·¸£ ¨®²´´²´³²²²°©Ÿ‘ˆ…Œ•›¡¤¤¤£ žœ›—•”‘‹‰„ƒ‚ƒ„„ƒ€}|‚†Œ˜£¥›“™šœžŸ ¡¥¨ª®±¯¹q'0/0,n±ª¥¡ž˜b9X˜“”—šžž¢(!#!! *f‘œœ™™˜šœ’h1 !Tª¤ª°·»¿ÆÌÎÍËÊËÊÉÅÀ¼µ®¥—Žƒxoimqtvxy{~„„…ˆŠ‹• ¦«°´¶¸¸¶«“{“£®²µµ²«™€y!
+
+
+
+
+
+
+ %)++)(((''&&%%&&&&%%%$%%%()(%$##$##" 
+
+
+
+
+
+
+ 5¡¸¹¢ž§¯³´µ³´´²²²²­¢”‰ƒŠ–šž¤¦¦¤£¢ Ÿœš˜–•–•“ŽŒ‹Š‹ˆ‡‡†ˆŠ‘™¥­µ¶«˜’‘”——› ¢¥§§°]!&'&%,@DAC<90&G‘’•™›œ§t 2Sq}|{qV5 =ž¨ª²¸»ÀÄÉÍÍÌÊÊÊÇÄÁº´¯¤›‘†{qklqstvy{|~ƒ…ˆˆŠ’›¥ª®²´¶¸·´¦†zˆ—¦°µµ´±©–ƒˆI
+
+
+
+
+
+
+
+
+
+%*++)(((&&%%&%%&&'%$$$%$$%('#$&##$"
+
+
+
+
+
+
+
+ _¶»º¡©®²³´´´´´³±²¯¥˜‹„†˜ž¤§¨§¦¦££¡ž›ššš››˜–””“‘‘‘–¬¶µµ®©•ˆ„‰”’–™œžž§NNˆ‰ŒŽ•˜œ¦Z#%$! :—©¨®¶¼ÁÅÈËÍÌËÉÈÆÄÀ»µ«¥œ‘†~skkpsrtvy|~„‡‰‹‘™£©®±³µ¸º¸¯žœª°µ¶µ°§•‡o
+
+
+
+
+
+
+  )*,+)((('&'&&%%%%%%$$#%%%&('%%%##!
+
+
+
+
+    !‡º¿º¡œ©­°³³´´´´´´²¯§›‡„‰’›¡¥¨¨¦¦¦¤££¡Ÿ ¡¡£¢¢¡Ÿžœ›ššœ ®µ´¯©¢–…|{|~†‰Š’““–C'b‡†ŠŒŠ‹Œ–˜›¢WBœ®¨¯´º¿ÅÉËËËËÊÈÇÅ¿º´«¤’„}ukmprrsvyz|~ƒ‡‹Œ’˜ ¨­±³´·¹»¸­—}‚‘ ¬±µ·¶²§”†-
+
+
+
+
+
+ ")++**)))('&'&%%&&&%$%$%%%&&$$$"""
+
+
+
+
+
+ 9¡»Á¹¤œ§®°±³³³´´³³³±«Ÿ‘‰…†Œ”š ¤¦¦§¦£¤¤¤¤¥§¦©§©ªª¨§¦¥¤£¥¬²¯¦”Žƒzvrsuwy}ƒ……†…‰6'Rˆ„†‰‡‡‰Š’–˜˜™—h" W ¬ª°´ºÂÇËÍËÉÉÈÇÅÃÁº´© šŒztlmorrsuwz|…‰“•œ£ª°´µ¶¸»º¶¦‰{Š—¡«±´·´°¥’H
+
+
+
+
+
+
+
+ !*+*+*))(((&&&$%''&%%%%$%%%%$#"!
+
+
+
+
+
+
+
+ X³½Ã·ªš©­°±±²²³´³´´²­£•Ž‡ƒˆ—¡¥¦§¦¤¤¤¥¦¨©«®¯°°±±±±°­«ª¬¥™†€{upnmnopquxz{zyzy=-Ce{‡†…‡‡‰‰‹‹Ž”™˜•”‘•C<®©ª³·¾ÃÈËÌÌÉÈÇÆÅÃÀº´®¥›Ž€zvllnqrssuxy~„‰Œ’–›¢©°´µ¶·¸¹·¯šŽ›¤«°³¶´¯¤’–•“n 
+
+
+
+
+
+
+
+
+
+ !$*+****))((&''%%%%%&&%%%%%%$$# 
+
+
+
+  ~À¿À¹­•¦¬²²²²²³´´´µ²¯¦™‘‰‚ˆ“˜¢¦¦¦¥¦¦¦¦ª«¯±³´¶¶·¸·¶´¯¬¥’ƒ~zxqnnlkkkloprssssspmfca\[X[[[Y]bju|†Š‰†„‡‰‹Š‹’’’’“•’’“’”’zJ))As¢­ª­³¸¾ÄÈËÌÌÊÈÇÆÅÿ¼µ±¦œ‘‚ztnknprssuuxz~ƒˆŒ“—›¡©®³¶··¸¸º·«“~…‘ž¨­²µ·µ°¤š™–Ž8  
+
+
+
+
+  !"',+******)((('('&&&('&&&%%$#$#
+
+
+
+  .ŸÅÿ¸¯”¤¬²´´³´³³³³µ´²«œ’Š€ƒ‰‹’™ž¤¦§¨§§¦§ª­°±³µ·¹º»»¹·¯©›‹€zwuqomjjiikmooopqpppmmklnnmpsx„†‹‘‹ŠŠ‰‰‹’“‘“”“““’‘‘““‘’•‡saOINW`v ¨§«¯³¸¿ÄÈÊËËÉÈÇÅÃþ·³«£˜‹ysokmpqrrtwvx{€†‹“–š ¦­³¶·¸·¸¹º³¥Š{†“¢©¬³·¸µ¯¤  •˜Y 
+
+
+
+
+
+
+!(,+***+,+*)()(')(&'&&&%&&$#"# 
+
+
+
+
+ QºÄƽ·²—¢¬±´¶µµµ³²³µ´³­¡—‚‚…†Š•œ¡¥¦¨§§¦¦¨ª¯°±²·ºº¼½½·¬©Ÿ’‹wvsonmkijkmnnnopoooonllmllmotz}…‹ŒŽŒ“–•’““”•““•“‘‘‘Ž‘—˜—˜œ¡¢££¦«®´»ÁÆÉËÌÊÈÇÇÆÄ¿½·°¬ ”ˆ~yrjlmnoqprvwxz„‰Ž’–šŸ¦­²¶¹¹¸¸¸¹¹°˜~~‹š¦ª®´··´¯¡’£¦—˜v 
+
+
+
+
+  "(++++**+)))))('(('&%&%%%$&'
+
+
+
+
+ {ÂÃŽ»¶– «°´µ¶µ´´³³´¶´¯¤š‘†ƒ†…‡Ž•› £¥¦§¦¥¦§¬¯±µ´¾ÄÁ¿À·«±¬©¢—Šzwtrpmnmnoopqpppooppomnopptw{‚†‹‹‹ŽŽŽ‘”––•–––––˜™—“‘‘‘‘““”••˜šœ¡¦©­²¶½ÂÇÉÊÊÈÇÆÆÅľ¸±¨¢—Š€zrkonqromoqsy{~‚‰Ž”–›ž¢©±¶¹¹¸¹¹ºº¶§}‚‘Ÿ©¬°µ·¶³®œ¦©”Œ4
+
+
+
+
+
+
+ !(*+,,+*+*+)('''')(&&'%%%%&'
+
+
+
+   - ÂÄÅ»½¸“ž©­²µ¶´´µ´´µµ´°¨œ“ˆ‚„„‡‹‘—¢¢£¤¤¥£¥¨¬µšPmš»Å¶­¶··³¯¥œ’Šƒzvvsrrssusstrrstqqrtuuvuw|„‡‰ŠŽ’•––—˜™˜™˜™™™™–’Ž‘’”••—™›Ÿ¡¥«°µ¸¿ÆÇÉÊÉÈÆÆÅÄľ¹³ªž•Œz{kOB9ATjrqru{~ƒ‰Ž“–™Ÿ¢§­´·¹¹¸¹»»º² …}‡•£«®±µ·¶²¯™“¨¬ ––V 
+
+
+
+
+
+
+
+ ")++,,+***+)((('()('(''%&&&&
+
+
+
+
+  M·ÂÆƹ¿¹–¨®²³´µ¶¶µµµµ´²« —Š‚‚„…„‹“šŸ¢£¤¤¤¤¤¦©´Z")0P¬Æµ­¸º½»¸·´­¥š•‹†}||||zzxxxxvuuvzyyyvvy~ƒ…ˆ‹”—š››œ››››š››—–‘ŽŽ‘””•—šœŸ£¦¬°µº¿ÄÉÊÊÈÈÆÅÅÅÄÁ¼¸±« ˜Œ‚z{_*$Inwx~‚ˆ’–™ž¢¦«²¶¹º¹¹¹º»·­š‰š¤ª­±¶··³¬–•¨¬£˜˜t
+
+
+
+
+
+
+
+
+
+ !)++*+,***)((())(((&&&''''%%
+
+ rÅÄÉĹ»˜š¦®²´µµµµ´´´µ´²¬£šƒƒ„ƒ„‡Ž—›Ÿ¢¤£¥¤¢«”*(+)8¨È¶­·º»¼¾½¾½»¸¯ª£Ÿ˜“‘ŽŒŠŠ‡…„„„‚€|yxyy|}‚…‰Œ’—›ŸžŸ¡¢¡££žœ›™–•”“’’•–•˜›ž¡¦«®´¹¾ÂÇÉËËÉÇÆÅÄÃÿ»µ®¦›“Š~wzV,f€†’—™ž£¥ª°µ»¼º¹¹ºº»µ¦}†’ž¦«°´¶¸¹³¬——ª®©›˜: 
+
+ "(++++,*)***+*))())'&&'(''&'
+
+
+
+
+
+
+  #ÈÅ˺Á¹›š¦­±µµ´´³³³³´·¶¯¦›‘…‚‚„ˆ‘—˜™›ž¡¢£¡ª](*(=µÆ³¬¶¹»½À¿»Â³Ÿ§±¶·´©¨¥¢Ÿœ™—–•’‘ŽŠ…|zx{„ˆ“˜¡¥¦¥¥£¤¤£ žœš˜–••“”–™ššœž¡§«±µ¹¾ÃÆÊËÊÊÈÆÆÄÅÄÁ½¶°§œ‘Œ†{vwY!%k„Š‘•˜œ¢¦¨®´¸¼½»ºº»»º±ž‡‡— ©­°´¶··³ª“š«¯«Ÿœ–W
+ $)*********+*))''))((''(&%&'
+
+
+
+
+
+
+   :­ÇÈȽ¾Ãº˜¦­±³´¶µ³³³³´··³©”ˆ‚}|Œ”––˜šœŸ ¢¡8"&'L»Á³¬´¸½ÀÁÀŸW?GRap†¯¹µ±®«©¥£¢Ÿœ™–“‡}|}~‚‰–›ž¢¥¦§§¥££££ žœ›š™™˜™šžžžž¡¥ª®³¸½ÂÇÊËËÊÈÇÆÅÄÅÄÁ¿½µ­¦—‰ƒ{uwa'8ˆ•™œ ¤§¬±·½¿¾»º»¼»·«’€Š˜£«®²µ·¸¶±¦«±­¢ž›s
+
+
+
+
+
+
+$)+++++++))*)*,((((((('&''%'
+
+
+
+
+
+
+
+
+
+ S¿ÄÊɼÂú–£¬¯²´µ¶´²²´¶¸¸´¬¡–‹ƒ|zx{ƒ“”—šš›š ‰  %%S½º°ª°¶½ÁÁǽ]088485H²Â¼º¸·´¯­ª©¤¡Ÿœ˜“Š‰ˆƒ†Œ”›Ÿ£¥¥¦¦§¤¢¢¢¢ ŸŸžœœŸŸ¡ ¡¥¦§ª¯³¹¾ÀÅÈÉËÊÉÇÆÅÅÄÃÅ¿¸whed_x}ttm,E’“–›Ÿ¡¦«¯µº¿À½º¹»¼º´¢‡„œ¥¬±³¶¸·µ°£Ÿ­±¯£ œˆ3
+
+
+
+
+
+
+ %+*)**)*)'(())*'''('&&&&''&'
+
+
+
+
+
+
+
+ sÆÇÌ˽Âļž’¢¬¯²´µ¶¶´²³µ·¸·°¤™…€~zxx}ˆ“•——– m Z¼²©¦«±¹¼ÃÂ`0234367\ÃÄÂÂÁ¿¾»¸´°­ª¥£¡ ›’•œš˜Ÿ¤¦¨«ª©¨©©§¨¦¥£¤¦§¥§¥§©¨ª­®±·»¿ÄÉÌÐÓÒÐÐÍÈÅÄÃÃþ¼«:!kxrr8+Pae\?}—•™¡¥ª¯µ¹½¿¾½ºº¼¼¹®—…‡•Ÿ§®±µ···µ¯Ÿ¢®²°¦ŸŸO
+
+
+
+
+ '+)))***('()))((())'&&%'''('
+
+ 
+
+
+
+ (–ÊÊÌȾľ¥“¢­¯²´µ¶¶µµ´¶¶¸·²§š†€€{vuu{†‹‘’”œVR³¨¡ ¤«²·¿i35465572zÉÄÆÅÅÄÄÿº¸·³®ª±{9BGQs¬©­¯¯°±°°¯®®¯­­¬­®¯±²±³´´¶¸»ÁÆÇÁº´©œˆ…†™·ÊÇÂÄÂÁ·´Ÿ/2tpyS?mtuv|‚f(Vš–›Ÿ¡¥¨­³·»À¿¼¹»¼»³¦ƒ‚Œ—¡ª¯²µ·¹·´«š’¤­³±§Ÿ –l 
+
+
+
+
+
+
+
+
+ '+**)+*))***)))**)('(('&'&)( 
+ 
+
+
+ <²ÈËÌÅÂĽ¤“ ¬±²´¶µ¶µ´´µ¶¸·´«“„€|xttu}ƒ†ŽŒŽ‘A6žŸ™šž¦¬¸|,311566:;–ÅÂÅÆÅÅÅÅÅÅÄÃÀ¾¹¸ªE-,-+B­µµ··¸¸¸¸¸¸··¶¶¶µµ¶º»¼½¼¿Çȼ fYNG@:62118M¹Å¿½º¯ª#Nqqc"Grosw|…j<œœ £¥¨¬¯³·¼ÁÁ¾¼ºº»¹¬ŸŒ‚„›¥«±³¶·¸·²©˜“¥®²±©¡¡“y. 
+
+
+
+
+ !(*++*))+)*+))))))(()*)('(''&
+
+
+
+
+ 
+ \ÁÈÌËÃÂÅÁ»§“ª±´µµ¶¶µµ¶¶·¸¸´¬¡˜‹‚€~zvrruz‚‹Œ‹6{š’”™ž©Œ.)*,./37:F¯ÁÁÃÃÃÄÄÅÆÇÈÈÆÄ¿Ãk43.,,2šÁ¼½¾¿ÀÀÀÀÀÀÁ¿¾¿¿¿ÀÁÂÂÅËÄŸsU<6533332//.0/25R¤À¶²¨§t!gln1?uoty|ƒ‰Š’‘Ÿ ¤§¨©­°±¶º½ÀÀ½»º»º´¦“‡„ˆ’¡ª¯²´µ¶¸¶±¦”—§¯²²¬¡¤˜„Q
+
+
+ !*+***+**)**))*)))'(())('''&'
+
+
+ 
+
+
+
+  ƒÅÇÌÊÃÄÇý¬”™¦¯³¶···¶¶···¸º¸°¤™‡ƒ€{xuqpsy}‚‡‰‰/F‰”™Ÿ9'&(,,/56WÀÀÁÃÃÂÂÃÆÇÇÇÆÆÅÏŠ8630163xÇÃÅÆÄÄÆÆÆÆÆÅÇÈÇÇÇÉÈÉÏÉd=54642-,'')),020243A•µ§ œXBpmL.tuv|†‹Ž’˜¡¦¦ª«««¬¯³¸½ÀÀ¿½ºº»¸¯œŠ„†Žš¥¬°²´µ¶·µ°£’œ§®´´°¢¥ Žj
+
+
+
+ !$*.+,+,,,*)****)))**)**(()('('
+
+
+
+ 
+   , ÅÈÍËÃÇÈľ¬”˜£­±µ·¸·······º¹´¨œ“‹ˆ…|ytpnqvy~€‚+!!r‡†Ž˜e !$%).45nÁ½¾ÁÃÃÂÃÇÇÆÆÅÃ΢?5210148S¼ÈÈÉÈÉÉÈÇÈÉÇÈÉÈÊÊÊÌÑ­f=6651++>TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y-
+
+
+
+
+
+
+
+
+  33-,,,++*+*+*)*)))++**)(()&((
+
+
+ 
+
+
+  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*<d•°½ÃÁÁÁ½¥d,*,+&q¯¢—Šn"AnoM8~z‰”—œ ¤¦§©ª¨§¨ª«®´¹½ÁÂÁ½»»¼¹°¡††‹—¤¬¯¯±³³¸¹¶°Ÿ’ž©²´´±§¥¤Ÿ‰G
+
+
+
++81,,++**+))*++)()***)((('&(( !% 
+
+
+
+
+
+ RÀÉÉÍÇÃÉÈľ¯›–Ÿ§®³µ·¹¸··¶·¸¸¸¶¯Ÿ”Ž‹‰ˆ†€{uponmoor;%jlƒ]!%+3”¾»¼½¾ÀÂÃÅÅÄÃ˳K50/-++++,+rÍÉËËËËÊÊËÌÌÌËÉÈи_5824,/i¯ÇÈÄÁÀ¿½»»¿¿ƒ-(&k±¤™‰S]jn0]„„‹‘—š £¥¦§¨©©§¦§¨¬±µ¼¿ÁÁ¿¼»½¼¸«˜‹†ˆœ¨¯°°±´¶¸¸´®›’¡«²³µ²ª¤§¢”g
+ !"+38--,++++**+-+()()*++)((''(( !$'),/
+
+
+   rÇÉÌËÄÆÈǽ°œ–ž¦­²µ·¹¸··¶···¸µ¯¤™“ŠŒŠ„~zsqommmqU oB?ˆ01%1¡¾¼½¿¿ÀÂÄÆÄÁĸS/110*))))+*H¼ÍËËËËÊËËÌÌËÊÈѱM4231+K¡ÉÆÄÃÄÃÂÁ¿¼¸¸¸¾…1d®¤œŽ‚}4.nmg  x‹—› ¤¦¦¦¦§¦¦¤¥§©®±¸¼ÀÂÁ½»¼¾»´¤‘‰‡‹”¢­°°±³¶¸¹·³«—”£¬±³µ´¬¥ª¦žƒ<
+
+
+
+
+
+
+  #*.69-,,,+*)*)*+**)))*,+())))("&,-/005&
+  *—ÈËÎÌÅÈÉÇÁº°œ”›¦­±´·¹¹¸···¸¸¹¸±©œ“Ž‰~zvsmnnmj%XpolD[3¢¸¶¾ÀÀÀÀÂÃÃÅÀ_3522-9I'*+./ŽÐÈÉÉËËÊÊÌËÊÇͳO3311)`¾ÌÄÄÅÄÄÂÀ¾¼¸µ´±¯¯”¨¥Ÿ–‹~m%Dwu[0‰Œ‘™ ¢¤¥¦¦¦¥£¤¤¦¨­°³¸¾ÁÃÀ½»½¾¹¬™Š‰‰˜¥­±°²´¶·¹·°§–—¦­±´µ´­¦©¨£•^
+
+ !! #%+,-:7,-,**)))*,*)*))*))***))( $)+-0101*
+
+
+
+
+ ?²ÆÍÐÍÅÊÉÅ¿¹¯ž”—£«¯²¶¸¹¹·¸¸¹¹¸¸´« •‘“”‘Žˆ‚€|wuqomp::yO8=xc5¢¬®´¹»»»½¿ÀÊ|4:6479—σ(,+-.XÆÇÈÉÊÉÉÉËÊÈÈÆ]3400(eÄÆÁÂÂÂÁ¿¿¼¹·´²°¯®¬¬¥Ÿš’‡‚eS…ƒT;’˜Ÿ¡¢¤¤¤£¤¢¢¢£¥©¬±µ»ÁÃþ»¼½¼²¤‘‹‹“ž§°°±³µ¶¸¸µ¯¥•™§¯²µ¶´¯¨©¬¥•t,
+
+ ! ").-,181,+*)**+++)*+*(()(),)(*( ""$'*+..1200.
+
+ ^¿ÅÍÐËÆÌÉþ¸± •–¡ª°´µ·¹¹¸¸¸¹ºº¸µ¬£™‘’—˜”‰ƒ€}zwrnpO%kv,CŒi2š¤§­²´µ¸¸¶Àš866675|ÇÄ­7../17¢ËÇÉÈÈÈÇÉÈÅË‹4402/I»ÂÀÁ¿»¹¸¶¶´³°¯®««©§¦¢›–Œ‰eX“Y:˜˜›Ÿ ¢£¤£¡¡ ŸŸ¡¤¥©®²·½ÁÃÁ¼»¾¾¹¬šŒ‹‹Œ˜£«°°²³¶¸ºº´¯¡’›¦®³µµ´±ª¨°ªš…P
+
+
+
+  !!&..,+49/++**++*+)***)()((+*('(!%()))+.///13331  ÆÈÎÐÊÈËÇþº´¤—•Ÿª°µ··¸¸¸¸¸¹º¼»¸¯§•”š™“ŽŠ…ƒ€}xstaWyalˆm.”ž¡¦¬®±²³¹´I*0-/.Wº½¼¿Y-2013pÍÇÇÇÇÆÅÆÆÈÂ[21041ü¾½»¶µ´²²°®¬«¨§§¤¡žš”“‹Œj R™“g0”Ÿ ¢¢¢£¤¢ ŸŸž ¢¥«°µº¾ÁÁ¾¼¼¾¼²£”‹‹Š›¦®²°²³¶¸º¸µ­œ‘›§¯´µµ·³­©±­ŸŠt&
+
+
+ ##$#!#+,./-.65--+*+*)****)**)*)))))) '''&(0./20021/445558 *žÈÊÎÎÉÉËƽ»¶¥—“š¥®´¸¸¸¸¸··¹¼¼»¸³ª ™‘—š˜”Œ†‚zvn$Auu=6z|p/“¡£¦ª¬ª·u%)*)+2™º¶·¾€*1//1I¹ÈÅÆÅÃÃÂÂƬ>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.,,-,++*)))))()**<<=><?=BD@@>AB@CEEFEHHGO
+ 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“š¢¨®²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘Š†}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(;<<?BDABEGDEIJFFGIKIILMT4  e±ºÊÏÏÍÌÎÊÅÂÀÀÀ¿¹ªž‘—¢¨®°³¶·¸¸¸¹º»¼¿½¹²¨œ—“‘‘‘’’”•––˜’.)‘“‹–˜š›™!q‡„„„‰lU†„ƒ~}€FC}}‚„‹ŽŽŽŽ‘’“•sm‡„0$~–™œ§t"$#!"$%_Àµ¯´¼Á¼µ§“ŒŒŽ“›¥­²´³²³¶¹¼¾»µª—Œ” «°³´´·ºº¹¹±ª¯²¯†ŠG  ')))(*.--.,-/,-013.063,+*,-*)*+*****)==>DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnŠ‡+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k
+ *()((&).0/.-././12:82270,++++++++*))*+BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’š¥¬²¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0<N6-47-*+**+**++)(*,ILJIKLJJLOSRQOOMMNOLONLIH #–¿´ÅÌÍÊÌÏËÇÂÁÂÁÂÃÁ´¦˜“˜¡©°µ¹ºº»¼»»¼½¾½»·²«¢š••–˜———”‘’•–— ›˜–˜šœŸœ˜•y"W‡€~„D uƒuncYNJCA=81-*h{z|{xuy76rŽŽ’“–˜š˜š™s#$|‹‰r,ˆ—™œž Ÿ] !"$";‹Ã¾²¯³½ÁÁ¸ªœ“˜¡­³´¶´³´·º¾À½µª‘š¤ª°°±´µ¸º¼»¹®ª®²®‡‹O  !'''((**-../.-//.,/1KK-.78**+)**)*+*))*LOPPQRQONOPSSRSPLNONJKNMM1
+ YÀ¸¶ÆËÉÊÎÏËÆÂÁÁÁÃÅƺ«š“•œ¦¬²¶¸º»¼¼»»»½¾½º´¬¦Ÿ˜•–—™š™—”‘’•—˜™œŸŸ™–—˜›™–’|%Jƒ~€v#D‡ƒ‚…ˆŒŠ‰ˆˆ‡†ƒ~|c7{ywxvtvv/ R„˜ž  ¢£žŠW,ˆŒŒk*‡œœŸ  œœo8!*I€²Ä¼·¯²»ÁÁ¼²¢’ŽŽ‘•›§¯´´µ´´³·¼À¿½²¥–’¤ª¯±´µ¶¸º»»¹±«­±³©‡q &''(''(+.../..-..-/15B7,/63,+*)()**,*(*PSSPSXTPSMPPRRSPNNLKMOQROA  šÆ´ºÊÍÉËÏÎËÆÂÀÂÃÄÆǽ¬ž”•š¢©¯³¸º»»¼»»»»½¼»·°¨¢œ—––˜›››–“’“””’“˜žœ—–••”’'A}ƒWn‡ƒ…†„†‰ˆ‹‹‡…‚~{€AW}xxywy€x1%=[nxvfG*WŒ‰e)‡Ÿ £¢ œ™–š“v`Ycw—³¼¼»¹²²·À¿¶©—Ž‘”˜¡¬³³²´µ´µ¹¿¿¿»¯Ÿ’“ž¦«¯³´·¹º¼»»»µ©«¯²¯—‡‡= '(''&((*.../00-,-./1034-,05/**+*,,++*)*WWWY]^[YXTXSZ]ZURRSQPRWTMK VÅó½ÌÍÇËÑÍÇÂÁÁÃÄÅÇÉÀ° “”™ §¬±·º»»»»»¼¼¼¼»¸³¬£˜––™œžžœ—““’‘’’˜™™—”‘Žˆ†„?#7}|/?‡ƒ‡‰‰ˆ‡‰Ž‹Š‡ƒ}||t ,y}{}ƒƒ‰‚=E’‰ˆŒh&„žŸ Ÿœš˜——Ÿ¢¤¨ª­±µ»»ºµ±·¾ÂÁ¹­ž”‘’–œ¦¯´´´´´µ¶»ÀÀ¾¸©›•¡¦¬±³µ·º½½¼¼¼¹®®°±¯ŸŠˆd&))''()(,/.-010/.-/110/1/,-26,)(,..-,+**UUUVXZ[ZXYYTY`^ZWY[YR\[YVZ$
+ —Ì¿³¾ÍÊÅÎÑÌÄÂÁÃÄÇÈÊËĵ¤•“–¥¬³¹º»¼¼»»¼½¾¿Àºµ¯¥ ›—–™œžŸ¡—“‘“•——•ƒ€~{vpmfj€‡B-$g‡‰Š‰ŠŠŠŒŒ‡„~{zULƒ€†‹ŠŒ““T:’ŽŠ‹i zŸš˜˜–—šœž¡¥¨¬¶º½¼¹µ´¼Â¼±¢–“‘”š¡«³µµµ´³´¹¾¿¿¼³¤•‹˜¢©®±³¶¹»»»¼¾½º±®°±°ª’‡|)*+*)**))....-//...020/-.//,,43-++,,,,+**NOQQORRRSPQNMSPNQPQSSWVWVU5
+ N¿È¾²ÁËÇÆÐÐËÄÂÂÄÆÊÊÌÏƹ§—’”›¡¨°·¹¼½¼»»»¼¿ÁÀ¿¹²¨¢Ÿ›˜——›ž¡ œ—”“’’””’‘““Žˆƒ~~ƒ‹”™ž–‹€qjŠŒŽŽŽŽŠ‰†ƒ€~~4#r‡Š’“””—œy0?ŒŽ‰‹‰‹`({ š˜˜—–˜™œ¡¥¨®µ¼ÁÀ¼¸µ¹ÀÁ½³©›“’’•œ¦°¶¶´´´³µ»ÀÁ¿º­¡’Œ›£¬±²µµ·»»»¼¾½º²¯°±±¯Ÿ‹†I ++***))*./--...//.2510.../-+-64.,(*,,++*PRWVPOQTXTTRQQLLQOOOSVSTTSK ’ÊÆ»´ÅÉÅÇÒÐÉÃÃÅÇËÌÌÎÐ̾«™“–™Ÿ¦¯³·º»»¼»º¼¾ÀÀÀ¼¸®¦¡š——˜œžŸœ™–”•”•”‘Š‰ˆˆ‡††‰Ž”šœž£¥¥£¢ž—–•––”“”‘Œ‰‡„€‚x][\[Z]|‘“—˜šžž Ÿ¥š_&U’ˆ‰ˆ‡‰hH[vŸ žš–—–—˜šŸ£¦¬µ»ÂÃÀ¼¸¸ÀÂÀ¶¬ “‘‘’•™¡­´·¶´´²´·½ÁÁ¼³¨šŽŒ“ž§®²´µ·¹º½¾½½¼»³¯°²³²¨‘‡i,0-,,+++-//.-.///0/254//...-+-/71.+*++++*TXZXVQRYb^[[ZYXV\YVUZ][XW[_  TÁÆ÷·ÈÈÃÉÑÏÇÄÆËÏÐÎÎÎÑÐðš””—›¢«±µ¸»½½¼¼¼¾¿À¾¼º³ª¥ ˜—˜œž›š˜•”’“””’‹Š‰‰‹Ž’–˜›œŸ¡¤¦¦¦¥ œœ›™—˜–—–’ŒŒ‹‹‹ŠŠ“•“–šš—™œž¡£¦¦¨§¦§©’`2D{•Œ‡ˆˆ‰‹Œ‘Ÿ¤¤¦£ œ—”•–™ ¢§«²ºÁÅÅÀ»º¿ÂÁ»°¢“‘““—Ÿ©±¶¸¶´³³µ»À¿¸®£”– ©°´¶·¹º¼¾¾½½¾¾µ°±³µ³¯›…D62/-,--.00/..//...04511.-.--,+151-+*++*+TUTTTQPPVZUUVVVWWUWY\ZZZ[[[)  "“ÍÈÁ´»ËÆÂËÑÌÇÇÉÎÒÒÏÍÏÓÒƵ —”“˜ž¨®´¹»½»¼»¼¿¿À¾½»¸±«£™˜–™Ÿ ›˜——“‘’•”‘‘Ž‘““’“•™œœžš™›Ÿ£¦ª­­©¨¤¥¦¢Ÿžš”‘’’””•š›Ÿ££¥¨ª«­®­®­­¬««¯Ÿ‚hT>638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+SQRSRQPWWWRQQTVUSRTUWVSUTTW9
+ PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—š£¨¬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**++[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0<C4/00.-,,,,05.++,++UVUSTWUV[[X\VXZ[]_`acac_b^YT LÃÌËǶµÅÈÃÃËÔÐÏÕÙÚÖÏÊÊÍÒÕ;©˜””“–ž¨®´¹»¼¼¼¼½ÀÁÁÁÀ½º¶±«¤ ŸœœŸžš˜˜•”‘‘•£¥©ª««ª¨ª©ª¬¬«ªª©§¥¢ š—“‘ŽŽ”–˜š¡§¬³¸¼ÂÃÁÁÁÃÂÁÃÂÂÀ½»º¹¸¶²®­«§¢—Œ‰ˆ‡†„†ˆŠ‹ŒŒ‹‰’•›¡¦¬²¹¿ÅÉÆÁ¿¿Â¿·­ •“””“’“˜ ©³¸¸¶´³³¶¼ÁÂÀ¼´©˜‹‡Œ˜¢¨°µ¸¸¹¹»½¼¼»º»¼¶°³´¶·¸³¤‹c10-**,.0/////....-.2<512/./-,+*-23,*,+*MKPOPSRQNRONMQRTVTUUWWXZ[WTS% ÐËÌð¶ÆÆÃÄÏÔÔÕÖØÔÏËÇÈÍ×ØÐÁ«™”•““›£­´¸»¼½½½½ÀÀÁÁÁÀ½¹µ²ª¤¡ŸŸžŸ¡ ŸŸžœ˜—–•’‘’“—› ¤§ª­«ª¨§¨ª©©ª§¥¤ ›–’ŒŽ“˜œ¢¤«°µ»¿ÂÅÈÈÇÆÅÆÄÃÂÂÁ¿¾¾½½»»¹¶³¯ª£•‰‡„„„……†ˆ†…†‰‰Š‹Ž”›¢¥¬²·¼ÂÅÆÃÀÀÄÄÀº°¢—‘“•’“”š§¯¶·¶´³²µºÀÃÂÀº±¤’‡…š¥«±µ··¹º¼¼»º¹¹»¼´°³µ·¹º·¬–Žy80.+**///..0.---,,-.02351-/.,++-.53--+)MOPSOQRSQSSXQSSRSSTRPRSSTSSO>C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄƹ±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†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 0000000000..20b5a2c0df
--- /dev/null
+++ b/libs/ultrahdr/tests/data/minnie-320x240-y.jpg
Binary files 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 0000000000..c7f4538534
--- /dev/null
+++ b/libs/ultrahdr/tests/data/minnie-320x240-yuv-icc.jpg
Binary files 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 0000000000..41300f47f1
--- /dev/null
+++ b/libs/ultrahdr/tests/data/minnie-320x240-yuv.jpg
Binary files differ
diff --git a/libs/ultrahdr/tests/data/minnie-320x240.y b/libs/ultrahdr/tests/data/minnie-320x240.y
new file mode 100644
index 0000000000..f9d8371c18
--- /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;<?DEKPROLQSSDE‘ÃÀ¿ÁµIwËÅÆÅÅÄÃÁ¾»¹¶´±­¬¬¯³¶º¿ÂÅÆÇÇÈÇÇÆÆÆÇÈÇÅÄÃÂÄÃÁÂÅÅÆÇÈÇÇÈÇÉÉÊÈÇÄÂÀ¾¹´¯¬§£¡Ÿ›š™™››ž¢¤¦«®­­­®®¯°¯®®­©¥ ™›–Š~ƒ…Œ—¢®³¶¶¶µ°¦¢ ›šŸ©²·¾À¾¾»¶°§¡¢¨¯¯®®®°±¶¶­©¨§¥¢žš˜˜–•––‘Œˆ€|}}y{ywspmjiigefb\`r{€ˆ˜¤§§«¯²¸½ÁÄÆÈÌÎÏÑÑÏÍÍÎÎÌÊÈÈÄÃÄÃÃÄÆÇÈÇÅÈÈÇÇÇÈËËÊÊÌÌËËÊÊÉÉÇÇÇÉÈÉÆÄÂÅÄÃÃÃÃÁÁÂÁÀ¾½»¼¼¼»¼½½ºº¹¸º¹º»¹·µ²³±¤™’Ž‚`=243.6IOPOSWY]^ehy¦ÆÁ¿Ç@¥ÉÃÅÄÄÄÿ¼¹¶³±¯¬­¯³µ»ÀÄÆÉÉÈÉÉÈÇÇÆÆÈÈÇÅÄÄÃÃÃÂÃÆÇÈÉÉÉÉÈÈÉÊÊÈÇÁ¿½ºµ±ª§¦¤ œšš››››ž¢¤§¬®­¬«¬¯°°¯­¬«©¥ž˜š›˜“ƒ~†œ©±µµ¶·²¨¢Ÿž›š£­¶¼¿À¾½¹³¬¨£¢©µ±¯¯®®¯²·²¨¥£¡Ÿš˜——“‘““Žˆ~|{{zwsomjihggcZV\nw‡•£§¨©¬¯µ»ÀÅÇÌÎÏÐÐÎÍÍÌÌÍÊÉÆÄÂÂÂÀÂÃÄÆÆÆÇÈÈÇÆÇÉÊÊÊÊÊËÉËËËÊÊÉÉÈÉÈÇÆÄÅÆÅÅÅÅÅÃÂÁ¿¾¼¼»»¼½¾¾¾½»ºººº¹ºº¹¸¶¶´´µ·µ·º»´šuT?2>X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¥ª¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdŠ§¶¸¹¹º»½ÀÂÂƯEŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥§¬»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£¨¯»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:<GZoŒ£®·œC}ÐÈÈÆÅÅ¿º·²±²³²°¯³¸½ÃÇÈÉÈÉÈÈÈÇÇÆÈÈÈÈÇÆÆÇÈÇÆÈÇÇÇÈÇÇÇÇÈÇÇÆÄ¿¼¹¸²°­«¨¤Ÿ›˜––—›› ¤¦©­¬¨ª¬¯¯±±­«««ª¨ –”’Œz~„Š”ª³³³µµ°©£™–š£©±¸¾¿¿½¼»¹³¬©¥¡©²¿º·¶³®«§¦¥¥¯°—”‘ŽŒ‹Š‹Š‘‰~yurqqnjeb`]WRLKYouz‹Ÿ¡¢£¢¢¡£¥©¯´·»¼¸·¸¹º»»½¼º¼¼¼¼¾¿¿ÀÁÁÁ¿ÀÂÂÆÈÇÆÈÆÇÈÈÈÉÉÈÉÌËËÌËÌÌÉÇÅÇÈÈÇÇÆÅÄÂÁÁÀ¿½½¼¹»»¹ºº¹···¸·µ²²²²µ·¸¸µµ¶·¶¶··¸·º¼¼¾Á¿³˜ƒoYIABGVLlÅÎÉÇÆÄ¿»¸µ´´µ³²®¯µ»ÁÅÉÊÊÉÉÈÈÈÈÇÅÆÇÇÇÈÈÉÈÇÇÇÇÇÇÈÇÇÈÉÈÇÆÅÂÀÀ¼¸¶³±«¨§¦£Ÿ™——™š›œž£¦ª­­¬«¬­®¯­¬«ªª¨¦Ÿ–“Ž}zˆ‘™¡¬²³·´°¨¡ ›˜˜Ÿ¦®µº¼½¾½º¹µ±¬¨¤¥®¸¾»·®©¨§¦Ÿ ¤¯ªš•’‘‹Š‰‹Š‹‹ŒŒ†€{vqoomida^[SLIGWqtw‡ ¤¢¡    ¡£¨ª«±±°°°°²µ·¹¸¹»º»¼¾¾ÀÁÁÂÁÁÂÂÃÅÆÇÈÆÆÉÉÉÉÈÉÈÈËËËÌÍÌËÊÊÊÈÈÈÇÈÇÆÅÃÂÂÂÁ¾¼»ºººº¹º¸¶µµµ¶´´´µ·¹º»»¸¸¹¹¸·¸¸¸¹¼¼¼¼¼¾ÀÂÅÉǼ³ž„v|™ÆÎÊÊÈÆÄÀ»·±±µ¶¶µ²®±¸½ÄÈÊÊÌÉÇÅÅÅÃÄÃÂÄÅÄÄÄÆÆÆÇÈÇÈÈÈÇÈÉÈÈÅÿ¿¼¸¶²°­¨§¦¤¡ ™—™›œœž¡¦©¬­¬­®®°®¬¬ªª©¦¥¡“„wz€‹”ž§­±µ¶³«¢Ÿ›™šŸ¤«²¶¸¼¾½¼»¸³¯©£¡¨²¹¸º¹¯¢  ž•—›œŸ­¯™•““ŒŒ‹ˆ‰ˆ‡…‚{xtonliea]ZOIDCOptwƒœ££¢ŸžŸ ¡¢¢¨«­­¬¬­¯²³µ¸¹¸¸»½½ÀÁÁÂÂÂÂÃÃÄÄÅÇÇÇÆÈÉÉÉÉÈÈÉÊÍÍÎÍÊÊÌÌËÉÈÈÈÈÈÈÆÅÆÅÃÂÁ¿¾¾½½»¹·¶µµµµ·¸¸»½¾À¿¾¼»¼»ººººº»½½¾½¾¾¿ÀÂÃÂÁÅÇÇÄÈÍËÊËÉÈÆÁºµ°°°±´µµ³³¶»ÀÆÈÉÉÇÄÃÁ¿¿½»º»½¾¿¿¿ÁÂÃÆÅÆÆÇÇÆÅÅÅÄÂÂÁ¿¿»·µ±­ª§¥¤¢ Ÿ›˜—™›› ¢¢§ª¬­®¯¯°°¯­¬«©¨§¤“ŒywzŽ˜¤¬¯²µ´¯§ œ››¤ª¯³·º¿¿¾¼»¹´¬¦¡£«´¸³´³³©–’””’•­°ž“”‘Œ‹‰‡‡ˆ†„~{xtqolhec]UMHA@Iluxƒœ¤¢¡ž››ž ¢¡Ÿ ¥¨ª««¬­®°³µ´´¶·¸º»½¿¿ÀÁÂÃÃÃÄÃÄÅÅÇÆÇÈÉÈÊÊÊÊËÍÍÎÌËËËÌÌÊÉÉÈÈÈÇÇÇÇÆÆÆÇÃÀÁÀ¿»¸¹·¶µ·º½ÀÁÂÃÃÃÄÂÀ¾¾¿½½½½¾½½¾¿¿¿¿¿¿ÁÂÁÂÀ¿ÀÄÇÉÊËÉÆÄÀ»¶°®®­­¯±²³µ¹¿ÆÈÈÆÅÄÁ½»º¹·¶³³¶¸ºº»»¾ÀÂÃÄÅÆÅÄÃÃÃÃÃÂÀ¾º¸¶µ°«¨¥££¡ž›™™œ››› £¤©­®­­­°®®®«ªªª¨§£›‘…uw}‰’ž¦¬±´²°ª£Ÿœœž¥ª¯´¶»¾¾½½»º¶°«¦¤§°¶¸©©ª²¯£Œ‹Ž‹Ž’š¨µ¦˜’‘ŽŒŠˆ‰ˆ‡‡‚zvtplifcaZRJE=9@gvz…¤¢žŸ›žŸ ž £¦¨©©«¬¬¯²³³´µ·¸¸º»½½½¿ÀÁÂÃÄÃÄÅÆÇÇÇÈÈÇÈÉÊÌÌÎÎÎÌÌËÌÍÍÌËËËÊÊÈÇÇÇÇÇÈÈÆÄÄÄÿ»º¹º¹¹¼¿ÃÇÆÈÇÇÆÄÄÂÁÀÀ¿ÀÀ¿¾¾¿¾¿ÁÁ¿ÂÄÄÄÃÂÃÆÉËÌÊÈÄÁ½»·³°®ª«®¬­®³·½ÁÇÆÄÂÂÀ»¹·¶¶µ±¯°²²µ¶·¸»½¾¿ÀÁÃÄÂÂÃÃÃÁ¾¼º¸µ³°¬§¤¤¡ Ÿžš™™œœ›¡¡£¥ª­®­­¬®­««ª¨§§§¥¡švtz‚™¢ª¯²²¯¬§¤¡ ¢©®³¶¹»¾¾½¼»º¸´°ª§§®´·¶¢¢¤§©§™‹…†‡‡‡Š“£°ªŸ•‘ŽŒ‹ŠŠ†„|uqmkida]WOD9407cz|…— ¢žœš›žžœ¡££¥§¦¨«¬­°²³´µ¶·¸¸¹»¼¼¾¾¾¿ÀÃÄÄÅÅÆÇÇÇÆÇÇÈÉËÌÏÐÏÎÎÌÍÍÎÎÍÍÎÍËÊÉÉÈÈÉÈÉÈÆÆÇÄÁÀ½»¼¼¼¾ÁÅÆÊÊÊÊÉÇÅÄÃÂÁ¿¿À¿¿À¾¿ÂÁÀÀÃÄÄÃÄÄÅÈÊÊÉÇÃÀ¾»·´²²°®­¯¯®±µºÀÄÅÄÃÀ¾º¶´³±²°­­­­¯±²³µ¶·¸¹»¼¾ÁÂÁÁÁÀ¾»¹¸¶³°¬¨¦¤¤¢ œ›™™›››Ÿ¢¤¦¨ª«¬¬¬­­­¬¬«¨§¥¤¢Ÿ—ow|ˆ’§­²±®­©¨¦¦¨¬³¶¹º»¼¾½½¼º¹µ²¬§¥¬³µµ´›œžŸ¤¤‚€€„„†˜¨­¨ž”’ŽŠ‹‡ƒ~ztplkeb\YQD:41.1Xx}†– ¢ ›šš›žž ¡¢£¤£¦ª¬­¯²³´µµ¶¶·¸º¼¾¿¿¾¿ÀÂÄÄÄÄÄÆÇÇÆÇÈÈÊËÌÏÑÐÏÎÍÍÏÐÏÏÎÍÎËËÊÊÉÊÊÊÊÉÉÈÇÅÁÁÀ¿ÀÁÀÂÃÆÈËÊÊÉÆÅÃÂÀÀ¿¿¿¿¿ÀÁÀÀÀÀ¿ÀÃÃÂÁÄÆÆÉÊÉÈÄÀ¾º¹·¶µµµ´²²°°´·»ÀÄÅÄÃÀ»¶±°¬ª«¬©§¥§©¬¬®°²²´µ¶¸¸»¾¿¿¾½¼¹¶µ²¯¬¨¦¤¤¤¢ššš›œœ›œ ¢¥¨ªª¬¬­®­¬­¬«©§¨¥¡Ÿ‘wty‚Œ–¤«¯¯­¬¬«ªª¬°µ¶º½½¼½»»½»¸¶³°©¥§­²´³²–”“‘•˜  €~{}~€„‡‘ ­±¦•Š‡|vsojfb\VME?;620/Jv}†”Ÿ¢ ››˜˜›ž   ¢£¤£¥¦¨ªª®²²´¶¶·¶¶·¹½ÀÁÁÂÃÄÄÄÅÅÄÅÆÆÆÇÈÊÌÌÍÎÐÑÑÎÍÏÑÑÐÐÎÍÌÌÌËÊÊËËÊÈÉÊÊÊÈÆÅÅÃÃÅÇÇÉÉËËÉÈÆÃÁ¿¾¿À¿¿¿¾¿¿ÀÀÁÀÁÁÃÄÃÃÃÆÈÉÊÊÉƾ½¹¶µ´´´µ¶¸¶³³´¶»¿ÂÁ¿½¸³°«¨¥¥¥¤£  ¤¦¨¨©««®±´´µµ·º»ºººº·µ´°¬©¦¥£¢¡žœ™˜˜œž ¡¥©«ª«¬­®­¬­¬©¨§§¤ žsu{…ž¨®¯­¬¬¬¬­®²¸»½¾¾½¼¼»¼¼º¹¹³«¦¦«°±²±°™Š‹Œ™•…~{{zz{~…˜¤°¯©“‰„}vrmgb^ZQIHDB?9736Dk}…šžŸœš——šœžŸ ¢¤¤¥¥¥¤¦ª¬¯¯±±²´´³³µ·»¿ÁÂÃÄÄÄÅÅÄÄÄÆÆÆÈÉËÍÍÎÐÑÑÏÎÐÐÐÑÏÍÌÌÌËÊÊËÍÎÌÊÊÌËÌËÊÊÉÇÈÉÊÉÉËÌÊÉÇÄÁ¿¾½¾¿¿¿¾½¾¾¿¿¾¿ÀÂÃÄÅÇÇÊÊÉÉÇÅ¿»¹·´²°®­°²´´³´µ¶¹¼»·µ±§£¤¤¡  ž›œžŸŸ¢¥¤¦¦¦©«®®°±´µ¶¸¹·¶µ´±®©§¦¤¡žœœ›˜——šœžžžŸ¢¥©«««¬­­¬¬­¬ª¨¦¥¤¢Ÿ›‡twŠ—£«®¬«ª¬«ª®³¸¼½½¼½»º»½½¼¼¹¶¬¦¦©­°±±±°–Ž‰ˆ‰‰Š–„|yxyz{|}ˆœ¦«­®¦“upe`\WOIGHGCA@=;:Bdz‚Š˜Ÿœ›™——šœžŸ Ÿ¡£¤§§§©¨©¬­¯­¯²²²³´¶¶º½½¿ÁÂÃÃÃÃÃÄÅÆÈÇÇÉËÎÐÏÐÐÏÏÐÑÑÐÏÐÊÂÊÎÍÎÏÎÏÎÌÍÍÍÌËÌËÈÉËËËÊÉÊÊÉÈÄÁ¿½½¼¾½¿¿½»»¼¼¼¼¾ÁÃÃÄÆÈÉËÊÉÇÿº··´±®­¬««ª«®¯³´¶·´­ª¥˜–˜™˜•”‘Ž’˜š›ž £££¥¦§ª«¬¯±³µµµµ³²±®ª¦¤£¡Ÿšš™™™˜™žž ¢¤§ªª««¬­¬«ª¬«ª¨¥¤¤£Ÿ›‡t}„ž¨¬¬¬ª©©©©®´¸º»ººº»ºº¼¼»»¶¯¦¥¥¦«®¯°¯¯“‹‡„„…ƒˆˆ}yxwvvxy{„‡—Ÿ¬®³®£‘}k_VOMIJIGEBA@=?Yr}ˆ–ŸŸš–˜˜˜˜–šžžœœ £¦¨ª«¨§©«¬®°±°±²´´µµ¸»½¿ÁÁÃÂÄÅÆÆÆÈÇÇÈÊÌÏÑÐÐÏÎÏÏÏÏÐÕ»]_lw‡²ÑÏÏÎÎÍÍÍÌÊÉÈÌÎÍÎÍËÊÊÉÆÁ¿½½¾ÀÀÁÀ¿¿¾¼¼¼»»¿ÁÃÃÄÆÇÈÈÈÆÄÁÀ»¸¸¶´±­©¨§¥¥¤¤©­¯±±¬¦¢–‘ŽŒ‹Š‹ˆˆ‹•˜š ¢£¦¨ª««­¯²´´´¶µ²°­«¨¤£¢Ÿœ™šš›š™™›ž  ¡¥¨©«¬­­«ª«ª«©ª¨¥¤£ ™„y€š¨««ªª¨¨§¨ª®²´µ¶···º¹¹¹»º¶®©¥¤¤¥¨¬«¬®®”Œ†‚‚‚…Š…}wusvxxxz~‚‡ˆ“œ£ª°³µ¯¥œ‹xoeZRPIFDBPlz†‘žœ•”••”’•—››šŸ£¦©ª¨©¨¨§ª­®¯®¯±²´µ¶¹¼¾¾ÀÂÂÃÅÆÆÆÆÇÈÈÊÍÎÐÒÑÐÏÎÎÏÎÎÎÒ«:8752†ÖÏÍÍÎÎÎÍÊÈÉÈ«¨°´µ·¸ÄÈÄ¿ÁÄÅÅÄÁÁÁ¾½½¼¼¼¼½¾¿ÀÂÅÄÆÉÌËÇý´²¯©©§¡››Ÿ¡£¡¡¤§¨¨ª¤žœ—‘ŠˆŒŠŠ„„Š“–™›¢¥¦ª­­®¯±µµ¶¶·¶²¯ª¨¦¤¡¡žœ›™™š›œ›œž  Ÿ ¥§¨©ª««¨¨¨¨§§©¨¥¤¡ž›–z†”¥¬«ªª©©©¨ª«¬±²±´´³³¶¸ºº»¹²¬¦£¢£§««©«®®•‹‡ƒ~€€‚‚~xurssutuxy{{~‚‡‹’š ¦­°³±±°¬¥Ž}jYOKPhuƒšœ›—‘’‘‘””–™™›Ÿ¤¨¨¨¨¨§¦¨©ªªª­®°³µ·¹»½½¿ÀÁÁÃÅÆÇÅÆÇÈÉÍÏÑÑÐÐÏÎÎÏÏÏÎÓ™:?>?:–ÖÌÍÍÍÌÍÊÊÒºl><ABDWÂÆÈ‹exŸ¯¹ÁÅÇÃÀÀ¾½¿ÁÀÂÂÁÂÞœ†qbUHD?866732489<>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"  
+ <t{}‚ƒƒƒƒƒ†‰‰ˆˆŠ„‚~~{yvussssssqqrpqqpnqpqpopqrtwz}„‰‘••’‹†„„††‡5"w€{{|t#b‹„†Š‹Ž|<”’”•š ¤£££¡Ÿš“Žˆ†jT‰†Š”–˜œ ¥¨ª¬¯±M)/164>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<BGIKMOQTWWWSOOSY[_bbdgkptxvpqqosx…ˆŒ’’”•™  
+
+
+
+
+
+
+
+
+ Gƒ’Ÿ§««¬¬ª¦¢˜“‘ŽŽ”–•–——˜™š›œœš›œ››››Ÿ¦°¶¼ÂÄÂÀÀÂÃÅÆË‹5172692.5=?;963367583A¡ÍÂÃÄÃÂÀ½¼¾ÃÆËÌÒ¯:-343‚ƺº»¾ÁÂÁÁÂÅÆÆÆÅ˨ynad¼ÊÊÉÉÉÇÆÄÁ»º¶°© ››D6trsrqqpnpp'f~yyyz|}~ƒ†‹Œ’ŽŒ‰‡‡„~|{}~~~||{xuuusqnnlkkkheb`cQ248<BFIKMORTUSNMPTX]^_`deioswqppons|„„ˆ‘”˜ž 
+
+
+
+
+
+
+
+
+
+
+ 1t›¤©«¬¬¬«¦¢Ÿœš˜”“–—˜››™š››œ›š˜––—˜™ ¬¸¿ÄÆÅÆÈÊËÌÎ|)1:7723Mw­³­¦c=0:98=;>œÉÂÃÃÃÃÂÄÇËÌÌÌÆ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#
+ !(,7<?BGJLKJGJMRVYZ\]\acehijot}‡”•”“”•••–••“’  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rx…“ž£¦ª¬¯²²±¯­­«§¦§§¨§§¦£¡ ž˜‰„„ƒƒ„…†Œ’ž¤«°¶´D-6:5XÅËÇÈÈÈÈÇÆÇÇÇÅÆÆÅÃÁÇÊÇÆÆÈÉÊÌÍËÇÁ·¸”,+,./-/2286VÃÂÂÂÂÂÂÃÅÆÆÅÍz5:89?«Á»¹¸±¬¨Ÿ™ˆ…€|xycZtrrsz;#|Œ‘’’““”—™šž ¡¡žœ™—”“”••••“‹ˆ‡…‚‚‚‚‚‚……ƒƒ‚{~B #(.8>AGJJJIHJMPUXYZ[\_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"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4<AFFEDFILNPRTYZY]]\]amy‚„ˆ‹“””–”•—›š™—   
+
+
+
+
+
+
+
+
+
+
+
+ O„ˆ– ¨¬®°±²´²°¯¯®ª«©¨§¨©¨¨§¥¤ž•‹†ƒ€ƒ„Š“˜¥ "$)3¨¿ÀÃÅÇÈÈÈÈÇÇÇÅÄÄÅÆÆÉÈÈÊÊÊÉÈÆÃÁ½Ãš11114,wʪ96589C¶ÄÂÃÃÃÄÆÆÃĺF6763D®¯¨¤ž™’‡€}{xutxN#NWw|F8Y5Œ—›žŸžžžžœžžŸ¡¢£¢¢¢¢¢¡ Ÿ›™—”’‘‘ŽŽŽ’•—š›žš™“` #)-4:@EFDCEIKNPRUVXZ\\[[[`fnuz€…†‰ŽŽ“•–”•“‘
+
+
+
+   
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  ,w„“¥«­¯°±²³³³²°®­¬­¬¬¬¬«ªª©¥Ÿ–Œˆ…‚€€‚„Š›~ "* ½»¾ÀÄÅÆÅÅÅÅÅÅÅÅÇÇÉÊÊÊÊÊÉÇÆÅÅÂñ@.4271L¼»¿`066:2‹ËÁÁÁÄÅÅÄÃÄ°?7640D¨£žš”Š„€}{xutvt-:h-|j}UB—•˜œž ¡¢¢ žžžŸ ¡¢££¤¥¥£¢ žœ››š—””“”“”–˜›ž¡¤¦¦¥¥¡‹,
+ !'+29@DCCCEIJJMPTUUYZYYYY[\^hmrx{}ƒŠŽŒ‹‹Œˆ…
+
+
+
+
+
+ 
+
+
+  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ \‡— ¨¬­®®±³¶·µ³±¯¯®®°±¯®¬«¬©¥ ˜ˆƒ€€ƒ‡$‡º´¸¼¾ÀÁÁÂÃÃÁÁÃÄÇÈÊÊÊÈÇÇÆÅÄÂÂÂÃ]*63352›Â·¿.6685VÃÀÀÁÃÃÃÃÂÆ£7710)D£›—“ŽŠƒ|yywust`Iw3U7\“OI—–™œŸŸ¡¢£¢  ¡¡¢¡¡££¡¡£¡¡ Ÿžœœš˜—˜™š›¡¢¦©«°¯®¬¨¨i
+ $)28?CBBEEFGGJNPSSTWWWXY[[^cfiortxx{€€€€‚
+
+
+
+
+
+
+
+ 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  9u…‘¥«­®¯±²²³´³±¯­®®°±°¯®®®ª§£ —Ž‡„~}~„Š6U±®²µ·º¼¿ÀÂÃÂÁÃÄÅÈÉÇÆÅÄÃÃÂÿÈ~'2212,sǺ»¿·B47587 ÇÀÃÄÂÂÀ¾Æ”/2-+#AŸ”Ž‰„~zxvwvtrwEY}Y2–LR˜–šŸ¡¢£¥¥¤¤££¡¢¤£ ¡¡Ÿ  ž žœœš› ¡¤§ª­°²´¶¸¶¯¬9
+
+
+
+
+ $(28>BBCDEFFGGIMRRSVUVWX\`_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:<AEFJORVZ]]abfnruz‚…‡yj]E82    
+
+
+
+
+
+
+
+
+
+
+
+ a{veY^`qˆ˜Ÿ¦ªª¬±µ¶¸¸¸¹¸¸¸··¶¶µ´³´´³²±¯®¬««©©§¤¢œ›—’Šˆ‡†‚ƒ‡‰ˆ‹‘“šž¢¤ª¯µ¸º¼¿Á¿¸¶²¬ª©­µ¾ÇÉÇƼ¶²©®Q/…ƒ…C #]²¯¤8*(,-g¸¶·¸····¸¹¸·´³³´´M,0.)ˆ´¯®®®®°¯¬©¦¥¥¦§§©¬±¹¿ÄÅÇÈÈÇÇÅÅÅÅÄÿº¶£lg$
+
+
+
+
+
+
+
+   /:AEILNVZ[]a`chmknkeYI?2(&&'(   
+
+
+
+
+
+
+
+
+
+
+ gywkZ]ae€–¤§ª«°´µ·¹»»º¸¸¸¸¶¶¶¶¶¶µ³²²±®®®®¬«©©©¥ ž™˜•‘ŽŠˆ…ƒ†‹ŒŽ”šš ¥©®®±³¸¹¹¶±­ª¨¯»ÃÇÆÅÁº²«¨£¤t*& !p…ƒ< !"#""$'+//447;BDy¸´²„}„Ž’¥·····¶¶¹ºº¸·´³³´¸¥˜˜’‘¨¯¯®®­­®¬©¦¦¦§©ª©®±¹¿ÄÆÈÇÈÇÅÅÆÆÅÅÂÀº±­df
+
+
+
+
+
+
+
+
+
+ 
+  $*3:<CFHNNOKFD<4)+&"%$&'&&   
+
+
+
+
+
+
+
+
+
+
+
+
+
+  kwwj\^adrŒ™Ÿ£¨«­²´´·º»º¸¸¸¸·¶¶µ¶µ´´³³³³³²²¯­¬­®­§¥¢Ÿœ™•‘Š†„€€‚ƒƒˆŽ“•š ¤¦¨«²·¸¸·³®«ª±¸¼»ºµ°«§¢–”†{utr}}tw{{~ƒ‡——™¡§­±¸¶¶·½º¼½½»¹¹¸¸º¹¸º¼¹¶´´¶µ¶´·¸¸µ¶°¯¯®®¯­­«§¦¦¨ª¬®°¶¹¿ÄÇÈÈÈÇÆÆÆÈÇÅÃÁ¼±©”cjb
+
+
+
+
+
+
+
+
+
+
+   $$!"!# """$%$"   
+
+
+
+
+
+
+
+
+
+
+  "rzyo^_aci|“™ £§¬±³µµ¶¸¹ºº»»º¸·¶µ³²²³³²²²±±°®¯¯°¯®­ª¦¢ž™’Žˆ‡…ƒ€€„‰Œ‘”™Ÿ¢§¬²µ´´·µ²±²±²±¯¯©¡—”‘ŽŒŠˆ†‰‹‰‚€ƒ‡‘Ž“”—™Ÿ¤¦¦§ª¯²µ¹ººººº»¼¼»»¼»¹¸¹¸¸º¸¶µ¶¶¶·¶²³²°°¯®®­­­­­«©¨ª«­°µº¾ÃÆÉÉÈÈÇÇÇÆÆÅÄÀ¾º¯¦šmbpa
+
+
+
+
+
+
+
+
+
+
+
+ 
+ !###$rbTE1   
+
+
+
+
+
+
+
+
+
+
+
+
+ %tz{r\]^acm‡–›¡¤©¯³¶··¸¹ºº¼¼¼»¹·µ´²³´³±±±²²²±°±°¯­­ª¨¦£¢žš•ŽŒˆ†„‚„…†ˆŒ“˜Ÿ¦¬®®¯²µ´´³²³±­ª¦Ÿ™’ŒŠˆ‡ˆ‰‡„‚„†„„‡Œ‘‘’˜œ¡¤¥¦«®¯°²µº½¼¼¼¼½½½½½½½»º¹¹·´´µ´´¶µµµ³±´´²±¯­«¬«««­¬¬­®²µ¸¼ÀÅÉËËÊÈÈÇÇÇÇÇÄ»µ®¤›zagpa
+
+
+
+
+
+
+
+
+
+    #%%'œ›š–“‡|dF1 
+
+
+
+
+
+
+
+
+
+
+
+
+ .y}~s^[^adg~‘šŸ£©°²´¸ºº»»º¼¼¼¼¼º¸µ²´µ´²²±±²²²²²¯®®­¬ªª©§¤¢—“Š‡…†‡‡‰ŒŽ’”™¡¥§«°³¶´µµ··³®¥ —•‘Ž‹ˆ‡‡‡‡……†ˆ†„ˆŠ“•—œž¢¥§¨©¬¯±²³¶¹»¼¼¼¼½½½¼»»»º¹¸¸·µ´²²±±³²²²²²²³±°¯­¬«««ª­¯¯°´¹½ÂÆÈËËËÊÈÈÇÈÈÇÅÃÁ»´¯¦œ€adjs]
+
+
+
+
+
+
+
+
+
+
+
+  "#'()•–”’“”˜™™’„j9 
+
+
+
+
+
+
+
+
+
+
+
+ 1}~~ud[]`ddoˆ˜Ÿ£¨®²´·¹º»½¼½½¼¼½»¹º¹¶³²²°¯¯±²°±±¯®¯¯¯­­­ª¨¥¢Ÿ›–’Š‡ˆˆ‰ŠŒŽŽ—¢¦«°²³·¹º¼¹´­£–•”‘ŽŒˆˆˆŠŒ‹ˆ‹‘”˜—›Ÿ¤¦¨¨ª­¯²´µ´¸º»¼¼»»¼½½»º¹·¸·µµµµµ³³±®®¯®®®®®­¬®®®­¬«¬®¯²´¶¸½ÀÅÉËÍËÉÊÈÈÉÊÈÇÄÁ¾¸³­§œŒi_flu\
+
+
+
+
+
+
+
+
+
+
+   #&()*‘”““‘’“’’“—k  
+
+
+
+
+
+
+
+
+
+
+ 3€€zd[^_bdfuŽ˜¥«¯²´¸ºº¼¿¿¾½¾½»ºº»¸´²±°¯®¯°¯°±³²°¯®®®®¬«©§¤ œ™”ŽŠ‰ˆˆ‡ŠŒ‹“˜œ ¤©¯³¸¼½À¾¼¸®«¤¡Ÿ›š——“Ž’’’‘— ¡¢¥¨¬®­®¯°³´·¸º¾¿¾½¼¹»ººº¸··¶¶µ³³³³³°°¬««¬­¬«ª«««­®¯®¯±²³´¸»¾ÃÆÉÊËËÊÊÉÉÉÊÊÉÅ¿»¶²¬¨¡•r\cimq]
+
+
+
+
+
+
+
+ 
+
+
+ 
+ !$#%%)*)’’’’“““”’‘g 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <~€{h\^`cedm‚Œ˜¡¨®²´·¹º¼½¿¾½¼¼º¹º¸¸µ³³²±°°°±²³´²¯¯®®­­­«ªª©¥¢Ÿš–•ŒŠˆ†ˆˆ‹’–› ¥¬±·»½ÀÀ¼·³²¯©¦£ ›˜œ›››Ÿ¤¦¨­¯®¯±³¶¶µµ¶¶·¸»¾½»»º¹¹¸¹·¶¶¶´µ´´´³²°®­«ª«««¬¬­¬¬®®¯¯°³µ¹»¾ÁÃÆÊËËÌËËÉÊÊÉÊÊÈÅÁ¾»·±¯¨¦Ÿacgknq\
+
+
+
+
+
+
+
+
+ 
+
+    "#%'&&&*+)“’’’’’’’‘Ž’S  
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >ƒ‚{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::<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%' !$%*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:9<?O´¼¼º¸¸··³±±¯ª«“($!!%"§ ©lF§§§§¦¥¦§¦¤£¡ž›™——•˜iu›™Ÿ£¦©­´·¼ÆÉÊËËÊÉÈÇÅÀ»µ®¥˜…|tlosyzz~€€€€‚‡‘š ¦­³¹¹µ­Œ{Šž«°±²¯¦}
+
+
+
+
+
+
+
+
+ #'''''&&&%%&&%%##%+/65455300/-,+*)(!
+
+
+  6›  ¬³µµ´²±°¯¬ª¤™‹‡Œ’˜œœ˜•‘Œˆƒ~yutronopommlprrsstustvx{…—©³¹»¼¾¿¿¾½ÀÂÂÂÁ±?6324M¶»»¸¶µ´²¯¬ª¨¤®_ ! /˜£ ¥„ %ª§©¦¦§§¤£¢Ÿžš™˜–••@+’ž¢§ª¯³¹ÀÈÌÌÍÌËÊÊÉÆý¶¯¤—‹~tjlstwz{‚ƒ‚ƒƒ†Ž–ž£ª²·º¹´¥„|¢­±²²®¨€ 
+
+
+
+
+
+
+
+
+
+ $('''(('&&&&%&%$%#%,353310///,*'')&$
+
+
+
+
+
+  \­žžª°µµ´´³°¯®«§†Š—žŸœš–‘ˆ…€|ywsqqsrpopqqrrrrstutux}ˆœ¬²µ¸»¼½¾¾ÀÁÂÂÀ¨5.+++O»½º·´²±®ª¨¤Ÿ¢ˆ$M£ž ¡™/ b°©©§§§¤¢¡Ÿžžœ›™•žjO¤ž ¦¬°µ»ÀÆÉÊËËËËËÉÆÃÀ·­£—‰}ulkotwxz|„„„„†Š’š ¨°µ¹º¹±š|~”¤­²³³®¬{
+
+
+
+ &)))'&%&&%&&$%$%&%$&+.211/---,*('&&%"
+
+
+
+
+ #†´¢©¯³´´³³°°¯®«£“ˆˆ”›Ÿ  Ÿœš˜”†~|xutuvvutsssttstvvuxxz€¥¬¯°³·¹º¼¾¾¿À¿ÂŸ53,,,Y½»¸´°¬ª¦£•›4z šž£U! !)Š°¥¥¥¤¡Ÿžžžžœ˜‰.€§¢¥­²µ»ÀÅÈÊËÊËÊÊÉÇĽ¸­¤˜‰umkortx{~€‚„…„†‰Ž–¤«±¶ºº·¬{†˜¨°³³²®¨
+
+
+
+
+
+ &***'%&&%%%&%%%$&(&&',/!001--.,))'&&&" 
+
+
+  ?¦´¤œ©¯´´µ³³²°°°­¦–Š…‹”›¡¤£¢¡ž›™•“‰ƒ€|zyyz{zxvvtuuuuxz|~€‚‡¥©¬®±³µ¶·¸¹¼¼Á‘5:497j¾µ²°¬¦¢Ÿš””„9=š˜™¢ˆ ! "#"7˜¬¢¡ Ÿžžœ›˜˜”? A £¦«³µºÁÅÈËÌÌÊËÉÊÆÁ¾¶« œŽ~volosuvz}ƒ„……‡Œ•œ¢©¯²·º¹´§ˆ|‹ž¬²³³³¯¢„6 
+
+
+
+
+
+
+'++)(('&&&&%%%&%&&&&%&(1#/.,,++)%&&%$# 
+
+
+
+
+
+
+   b²´£©­²µ´´³³±±±¯©œ…‡—ž¦¥¤¢ œ›™–“‘Š†…ƒ||~~}zyxxx{}€ƒ‰‰•Ÿ¤¦¨©«­®°´´¶·À‚+4361w¿²¬¨¥œ››˜e)›•™ž¤J$""#7‡§Ÿšš™——˜˜›¡Œ@#  ¥£©°·ºÁÇËÌÌËËÊËÉÆ»¶®¡šƒwninsvwyy|€ƒ„„…ˆ‹‘š ¦®±´¸º¸±œ€ ­²´³³®š€Y  
+ !*-+)(((''&%%&&&'%$%%$$&+/$****&%&$$$"!
+
+
+
+
+
+  "‡·¸£ ¨®²´´²´³²²²°©Ÿ‘ˆ…Œ•›¡¤¤¤£ žœ›—•”‘‹‰„ƒ‚ƒ„„ƒ€}|‚†Œ˜£¥›“™šœžŸ ¡¥¨ª®±¯¹q'0/0,n±ª¥¡ž˜b9X˜“”—šžž¢(!#!! *f‘œœ™™˜šœ’h1 !Tª¤ª°·»¿ÆÌÎÍËÊËÊÉÅÀ¼µ®¥—Žƒxoimqtvxy{~„„…ˆŠ‹• ¦«°´¶¸¸¶«“{“£®²µµ²«™€y!
+
+
+
+
+
+
+ %)++)(((''&&%%&&&&%%%$%%%(3$)(%$##$##" 
+
+
+
+
+
+
+ 5¡¸¹¢ž§¯³´µ³´´²²²²­¢”‰ƒŠ–šž¤¦¦¤£¢ Ÿœš˜–•–•“ŽŒ‹Š‹ˆ‡‡†ˆŠ‘™¥­µ¶«˜’‘”——› ¢¥§§°]!&'&%,@DAC<90&G‘’•™›œ§t 2Sq}|{qV5 =ž¨ª²¸»ÀÄÉÍÍÌÊÊÊÇÄÁº´¯¤›‘†{qklqstvy{|~ƒ…ˆˆŠ’›¥ª®²´¶¸·´¦†zˆ—¦°µµ´±©–ƒˆI
+
+
+
+
+
+
+
+
+
+%*++)(((&&%%&%%&&'%$$$%$$%)/('#$&##$"
+
+
+
+
+
+
+
+ _¶»º¡©®²³´´´´´³±²¯¥˜‹„†˜ž¤§¨§¦¦££¡ž›ššš››˜–””“‘‘‘–¬¶µµ®©•ˆ„‰”’–™œžž§NNˆ‰ŒŽ•˜œ¦Z#%$! :—©¨®¶¼ÁÅÈËÍÌËÉÈÆÄÀ»µ«¥œ‘†~skkpsrtvy|~„‡‰‹‘™£©®±³µ¸º¸¯žœª°µ¶µ°§•‡o
+
+
+
+
+
+
+  )*,+)((('&'&&%%%%%%$$#%%%&'(('%%%##!
+
+
+
+
+    !‡º¿º¡œ©­°³³´´´´´´²¯§›‡„‰’›¡¥¨¨¦¦¦¤££¡Ÿ ¡¡£¢¢¡Ÿžœ›ššœ ®µ´¯©¢–…|{|~†‰Š’““–C'b‡†ŠŒŠ‹Œ–˜›¢WBœ®¨¯´º¿ÅÉËËËËÊÈÇÅ¿º´«¤’„}ukmprrsvyz|~ƒ‡‹Œ’˜ ¨­±³´·¹»¸­—}‚‘ ¬±µ·¶²§”†-
+
+
+
+
+
+ ")++**)))('&'&%%&&&%$%$%%%&&%&$$$"""
+
+
+
+
+
+ 9¡»Á¹¤œ§®°±³³³´´³³³±«Ÿ‘‰…†Œ”š ¤¦¦§¦£¤¤¤¤¥§¦©§©ªª¨§¦¥¤£¥¬²¯¦”Žƒzvrsuwy}ƒ……†…‰6'Rˆ„†‰‡‡‰Š’–˜˜™—h" W ¬ª°´ºÂÇËÍËÉÉÈÇÅÃÁº´© šŒztlmorrsuwz|…‰“•œ£ª°´µ¶¸»º¶¦‰{Š—¡«±´·´°¥’H
+
+
+
+
+
+
+
+ !*+*+*))(((&&&$%''&%%%%$%%%&$%$#"!
+
+
+
+
+
+
+
+ X³½Ã·ªš©­°±±²²³´³´´²­£•Ž‡ƒˆ—¡¥¦§¦¤¤¤¥¦¨©«®¯°°±±±±°­«ª¬¥™†€{upnmnopquxz{zyzy=-Ce{‡†…‡‡‰‰‹‹Ž”™˜•”‘•C<®©ª³·¾ÃÈËÌÌÉÈÇÆÅÃÀº´®¥›Ž€zvllnqrssuxy~„‰Œ’–›¢©°´µ¶·¸¹·¯šŽ›¤«°³¶´¯¤’–•“n 
+
+
+
+
+
+
+
+
+
+ !$*+****))((&''%%%%%&&%%%%%%$%$$# 
+
+
+
+  ~À¿À¹­•¦¬²²²²²³´´´µ²¯¦™‘‰‚ˆ“˜¢¦¦¦¥¦¦¦¦ª«¯±³´¶¶·¸·¶´¯¬¥’ƒ~zxqnnlkkkloprssssspmfca\[X[[[Y]bju|†Š‰†„‡‰‹Š‹’’’’“•’’“’”’zJ))As¢­ª­³¸¾ÄÈËÌÌÊÈÇÆÅÿ¼µ±¦œ‘‚ztnknprssuuxz~ƒˆŒ“—›¡©®³¶··¸¸º·«“~…‘ž¨­²µ·µ°¤š™–Ž8  
+
+
+
+
+  !"',+******)((('('&&&('&&&%%$%&#$#
+
+
+
+  .ŸÅÿ¸¯”¤¬²´´³´³³³³µ´²«œ’Š€ƒ‰‹’™ž¤¦§¨§§¦§ª­°±³µ·¹º»»¹·¯©›‹€zwuqomjjiikmooopqpppmmklnnmpsx„†‹‘‹ŠŠ‰‰‹’“‘“”“““’‘‘““‘’•‡saOINW`v ¨§«¯³¸¿ÄÈÊËËÉÈÇÅÃþ·³«£˜‹ysokmpqrrtwvx{€†‹“–š ¦­³¶·¸·¸¹º³¥Š{†“¢©¬³·¸µ¯¤  •˜Y 
+
+
+
+
+
+
+!(,+***+,+*)()(')(&'&&&%&&$#&&"# 
+
+
+
+
+ QºÄƽ·²—¢¬±´¶µµµ³²³µ´³­¡—‚‚…†Š•œ¡¥¦¨§§¦¦¨ª¯°±²·ºº¼½½·¬©Ÿ’‹wvsonmkijkmnnnopoooonllmllmotz}…‹ŒŽŒ“–•’““”•““•“‘‘‘Ž‘—˜—˜œ¡¢££¦«®´»ÁÆÉËÌÊÈÇÇÆÄ¿½·°¬ ”ˆ~yrjlmnoqprvwxz„‰Ž’–šŸ¦­²¶¹¹¸¸¸¹¹°˜~~‹š¦ª®´··´¯¡’£¦—˜v 
+
+
+
+
+  "(++++**+)))))('(('&%&%%%$&''&
+
+
+
+
+ {ÂÃŽ»¶– «°´µ¶µ´´³³´¶´¯¤š‘†ƒ†…‡Ž•› £¥¦§¦¥¦§¬¯±µ´¾ÄÁ¿À·«±¬©¢—Šzwtrpmnmnoopqpppooppomnopptw{‚†‹‹‹ŽŽŽ‘”––•–––––˜™—“‘‘‘‘““”••˜šœ¡¦©­²¶½ÂÇÉÊÊÈÇÆÆÅľ¸±¨¢—Š€zrkonqromoqsy{~‚‰Ž”–›ž¢©±¶¹¹¸¹¹ºº¶§}‚‘Ÿ©¬°µ·¶³®œ¦©”Œ4
+
+
+
+
+
+
+ !(*+,,+*+*+)('''')(&&'%%%%&'&%
+
+
+
+   - ÂÄÅ»½¸“ž©­²µ¶´´µ´´µµ´°¨œ“ˆ‚„„‡‹‘—¢¢£¤¤¥£¥¨¬µšPmš»Å¶­¶··³¯¥œ’Šƒzvvsrrssusstrrstqqrtuuvuw|„‡‰ŠŽ’•––—˜™˜™˜™™™™–’Ž‘’”••—™›Ÿ¡¥«°µ¸¿ÆÇÉÊÉÈÆÆÅÄľ¹³ªž•Œz{kOB9ATjrqru{~ƒ‰Ž“–™Ÿ¢§­´·¹¹¸¹»»º² …}‡•£«®±µ·¶²¯™“¨¬ ––V 
+
+
+
+
+
+
+
+ ")++,,+***+)((('()('(''%&&&&&%
+
+
+
+
+  M·ÂÆƹ¿¹–¨®²³´µ¶¶µµµµ´²« —Š‚‚„…„‹“šŸ¢£¤¤¤¤¤¦©´Z")0P¬Æµ­¸º½»¸·´­¥š•‹†}||||zzxxxxvuuvzyyyvvy~ƒ…ˆ‹”—š››œ››››š››—–‘ŽŽ‘””•—šœŸ£¦¬°µº¿ÄÉÊÊÈÈÆÅÅÅÄÁ¼¸±« ˜Œ‚z{_*$Inwx~‚ˆ’–™ž¢¦«²¶¹º¹¹¹º»·­š‰š¤ª­±¶··³¬–•¨¬£˜˜t
+
+
+
+
+
+
+
+
+
+ !)++*+,***)((())(((&&&''''%%'%
+
+ rÅÄÉĹ»˜š¦®²´µµµµ´´´µ´²¬£šƒƒ„ƒ„‡Ž—›Ÿ¢¤£¥¤¢«”*(+)8¨È¶­·º»¼¾½¾½»¸¯ª£Ÿ˜“‘ŽŒŠŠ‡…„„„‚€|yxyy|}‚…‰Œ’—›ŸžŸ¡¢¡££žœ›™–•”“’’•–•˜›ž¡¦«®´¹¾ÂÇÉËËÉÇÆÅÄÃÿ»µ®¦›“Š~wzV,f€†’—™ž£¥ª°µ»¼º¹¹ºº»µ¦}†’ž¦«°´¶¸¹³¬——ª®©›˜: 
+
+ "(++++,*)***+*))())'&&'(''&''&
+
+
+
+
+
+
+  #ÈÅ˺Á¹›š¦­±µµ´´³³³³´·¶¯¦›‘…‚‚„ˆ‘—˜™›ž¡¢£¡ª](*(=µÆ³¬¶¹»½À¿»Â³Ÿ§±¶·´©¨¥¢Ÿœ™—–•’‘ŽŠ…|zx{„ˆ“˜¡¥¦¥¥£¤¤£ žœš˜–••“”–™ššœž¡§«±µ¹¾ÃÆÊËÊÊÈÆÆÄÅÄÁ½¶°§œ‘Œ†{vwY!%k„Š‘•˜œ¢¦¨®´¸¼½»ºº»»º±ž‡‡— ©­°´¶··³ª“š«¯«Ÿœ–W
+ $)*********+*))''))((''(&%&''(
+
+
+
+
+
+
+   :­ÇÈȽ¾Ãº˜¦­±³´¶µ³³³³´··³©”ˆ‚}|Œ”––˜šœŸ ¢¡8"&'L»Á³¬´¸½ÀÁÀŸW?GRap†¯¹µ±®«©¥£¢Ÿœ™–“‡}|}~‚‰–›ž¢¥¦§§¥££££ žœ›š™™˜™šžžžž¡¥ª®³¸½ÂÇÊËËÊÈÇÆÅÄÅÄÁ¿½µ­¦—‰ƒ{uwa'8ˆ•™œ ¤§¬±·½¿¾»º»¼»·«’€Š˜£«®²µ·¸¶±¦«±­¢ž›s
+
+
+
+
+
+
+$)+++++++))*)*,((((((('&''%'('
+
+
+
+
+
+
+
+
+
+ S¿ÄÊɼÂú–£¬¯²´µ¶´²²´¶¸¸´¬¡–‹ƒ|zx{ƒ“”—šš›š ‰  %%S½º°ª°¶½ÁÁǽ]088485H²Â¼º¸·´¯­ª©¤¡Ÿœ˜“Š‰ˆƒ†Œ”›Ÿ£¥¥¦¦§¤¢¢¢¢ ŸŸžœœŸŸ¡ ¡¥¦§ª¯³¹¾ÀÅÈÉËÊÉÇÆÅÅÄÃÅ¿¸whed_x}ttm,E’“–›Ÿ¡¦«¯µº¿À½º¹»¼º´¢‡„œ¥¬±³¶¸·µ°£Ÿ­±¯£ œˆ3
+
+
+
+
+
+
+ %+*)**)*)'(())*'''('&&&&''&''%
+
+
+
+
+
+
+
+ sÆÇÌ˽Âļž’¢¬¯²´µ¶¶´²³µ·¸·°¤™…€~zxx}ˆ“•——– m Z¼²©¦«±¹¼ÃÂ`0234367\ÃÄÂÂÁ¿¾»¸´°­ª¥£¡ ›’•œš˜Ÿ¤¦¨«ª©¨©©§¨¦¥£¤¦§¥§¥§©¨ª­®±·»¿ÄÉÌÐÓÒÐÐÍÈÅÄÃÃþ¼«:!kxrr8+Pae\?}—•™¡¥ª¯µ¹½¿¾½ºº¼¼¹®—…‡•Ÿ§®±µ···µ¯Ÿ¢®²°¦ŸŸO
+
+
+
+
+ '+)))***('()))((())'&&%'''(''&
+
+ 
+
+
+
+ (–ÊÊÌȾľ¥“¢­¯²´µ¶¶µµ´¶¶¸·²§š†€€{vuu{†‹‘’”œVR³¨¡ ¤«²·¿i35465572zÉÄÆÅÅÄÄÿº¸·³®ª±{9BGQs¬©­¯¯°±°°¯®®¯­­¬­®¯±²±³´´¶¸»ÁÆÇÁº´©œˆ…†™·ÊÇÂÄÂÁ·´Ÿ/2tpyS?mtuv|‚f(Vš–›Ÿ¡¥¨­³·»À¿¼¹»¼»³¦ƒ‚Œ—¡ª¯²µ·¹·´«š’¤­³±§Ÿ –l 
+
+
+
+
+
+
+
+
+ '+**)+*))***)))**)('(('&'&)('% 
+ 
+
+
+ <²ÈËÌÅÂĽ¤“ ¬±²´¶µ¶µ´´µ¶¸·´«“„€|xttu}ƒ†ŽŒŽ‘A6žŸ™šž¦¬¸|,311566:;–ÅÂÅÆÅÅÅÅÅÅÄÃÀ¾¹¸ªE-,-+B­µµ··¸¸¸¸¸¸··¶¶¶µµ¶º»¼½¼¿Çȼ fYNG@:62118M¹Å¿½º¯ª#Nqqc"Grosw|…j<œœ £¥¨¬¯³·¼ÁÁ¾¼ºº»¹¬ŸŒ‚„›¥«±³¶·¸·²©˜“¥®²±©¡¡“y. 
+
+
+
+
+ !(*++*))+)*+))))))(()*)('(''&%'
+
+
+
+
+ 
+ \ÁÈÌËÃÂÅÁ»§“ª±´µµ¶¶µµ¶¶·¸¸´¬¡˜‹‚€~zvrruz‚‹Œ‹6{š’”™ž©Œ.)*,./37:F¯ÁÁÃÃÃÄÄÅÆÇÈÈÆÄ¿Ãk43.,,2šÁ¼½¾¿ÀÀÀÀÀÀÁ¿¾¿¿¿ÀÁÂÂÅËÄŸsU<6533332//.0/25R¤À¶²¨§t!gln1?uoty|ƒ‰Š’‘Ÿ ¤§¨©­°±¶º½ÀÀ½»º»º´¦“‡„ˆ’¡ª¯²´µ¶¸¶±¦”—§¯²²¬¡¤˜„Q
+
+
+ !*+***+**)**))*)))'(())('''&'&(
+
+
+ 
+
+
+
+  ƒÅÇÌÊÃÄÇý¬”™¦¯³¶···¶¶···¸º¸°¤™‡ƒ€{xuqpsy}‚‡‰‰/F‰”™Ÿ9'&(,,/56WÀÀÁÃÃÂÂÃÆÇÇÇÆÆÅÏŠ8630163xÇÃÅÆÄÄÆÆÆÆÆÅÇÈÇÇÇÉÈÉÏÉd=54642-,'')),020243A•µ§ œXBpmL.tuv|†‹Ž’˜¡¦¦ª«««¬¯³¸½ÀÀ¿½ºº»¸¯œŠ„†Žš¥¬°²´µ¶·µ°£’œ§®´´°¢¥ Žj
+
+
+
+ !$*.+,+,,,*)****)))**)**(()('('%'
+
+
+
+ 
+   , ÅÈÍËÃÇÈľ¬”˜£­±µ·¸·······º¹´¨œ“‹ˆ…|ytpnqvy~€‚+!!r‡†Ž˜e !$%).45nÁ½¾ÁÃÃÂÃÇÇÆÆÅÃ΢?5210148S¼ÈÈÉÈÉÉÈÇÈÉÇÈÉÈÊÊÊÌÑ­f=6651++>TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y-
+
+
+
+
+
+
+
+
+  33-,,,++*+*+*)*)))++**)(()&((&'
+
+
+ 
+
+
+  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*<d•°½ÃÁÁÁ½¥d,*,+&q¯¢—Šn"AnoM8~z‰”—œ ¤¦§©ª¨§¨ª«®´¹½ÁÂÁ½»»¼¹°¡††‹—¤¬¯¯±³³¸¹¶°Ÿ’ž©²´´±§¥¤Ÿ‰G
+
+
+
++81,,++**+))*++)()***)((('&(('( !% 
+
+
+
+
+
+ RÀÉÉÍÇÃÉÈľ¯›–Ÿ§®³µ·¹¸··¶·¸¸¸¶¯Ÿ”Ž‹‰ˆ†€{uponmoor;%jlƒ]!%+3”¾»¼½¾ÀÂÃÅÅÄÃ˳K50/-++++,+rÍÉËËËËÊÊËÌÌÌËÉÈи_5824,/i¯ÇÈÄÁÀ¿½»»¿¿ƒ-(&k±¤™‰S]jn0]„„‹‘—š £¥¦§¨©©§¦§¨¬±µ¼¿ÁÁ¿¼»½¼¸«˜‹†ˆœ¨¯°°±´¶¸¸´®›’¡«²³µ²ª¤§¢”g
+ !"+38--,++++**+-+()()*++)((''(((( !$'),/
+
+
+   rÇÉÌËÄÆÈǽ°œ–ž¦­²µ·¹¸··¶···¸µ¯¤™“ŠŒŠ„~zsqommmqU oB?ˆ01%1¡¾¼½¿¿ÀÂÄÆÄÁĸS/110*))))+*H¼ÍËËËËÊËËÌÌËÊÈѱM4231+K¡ÉÆÄÃÄÃÂÁ¿¼¸¸¸¾…1d®¤œŽ‚}4.nmg  x‹—› ¤¦¦¦¦§¦¦¤¥§©®±¸¼ÀÂÁ½»¼¾»´¤‘‰‡‹”¢­°°±³¶¸¹·³«—”£¬±³µ´¬¥ª¦žƒ<
+
+
+
+
+
+
+  #*.69-,,,+*)*)*+**)))*,+())))()'"&,-/005&
+  *—ÈËÎÌÅÈÉÇÁº°œ”›¦­±´·¹¹¸···¸¸¹¸±©œ“Ž‰~zvsmnnmj%XpolD[3¢¸¶¾ÀÀÀÀÂÃÃÅÀ_3522-9I'*+./ŽÐÈÉÉËËÊÊÌËÊÇͳO3311)`¾ÌÄÄÅÄÄÂÀ¾¼¸µ´±¯¯”¨¥Ÿ–‹~m%Dwu[0‰Œ‘™ ¢¤¥¦¦¦¥£¤¤¦¨­°³¸¾ÁÃÀ½»½¾¹¬™Š‰‰˜¥­±°²´¶·¹·°§–—¦­±´µ´­¦©¨£•^
+
+ !! #%+,-:7,-,**)))*,*)*))*))***))((' $)+-0101*
+
+
+
+
+ ?²ÆÍÐÍÅÊÉÅ¿¹¯ž”—£«¯²¶¸¹¹·¸¸¹¹¸¸´« •‘“”‘Žˆ‚€|wuqomp::yO8=xc5¢¬®´¹»»»½¿ÀÊ|4:6479—σ(,+-.XÆÇÈÉÊÉÉÉËÊÈÈÆ]3400(eÄÆÁÂÂÂÁ¿¿¼¹·´²°¯®¬¬¥Ÿš’‡‚eS…ƒT;’˜Ÿ¡¢¤¤¤£¤¢¢¢£¥©¬±µ»ÁÃþ»¼½¼²¤‘‹‹“ž§°°±³µ¶¸¸µ¯¥•™§¯²µ¶´¯¨©¬¥•t,
+
+ ! ").-,181,+*)**+++)*+*(()(),)(*('( ""$'*+..1200.
+
+ ^¿ÅÍÐËÆÌÉþ¸± •–¡ª°´µ·¹¹¸¸¸¹ºº¸µ¬£™‘’—˜”‰ƒ€}zwrnpO%kv,CŒi2š¤§­²´µ¸¸¶Àš866675|ÇÄ­7../17¢ËÇÉÈÈÈÇÉÈÅË‹4402/I»ÂÀÁ¿»¹¸¶¶´³°¯®««©§¦¢›–Œ‰eX“Y:˜˜›Ÿ ¢£¤£¡¡ ŸŸ¡¤¥©®²·½ÁÃÁ¼»¾¾¹¬šŒ‹‹Œ˜£«°°²³¶¸ºº´¯¡’›¦®³µµ´±ª¨°ªš…P
+
+
+
+  !!&..,+49/++**++*+)***)()((+*('(()!%()))+.///13331  ÆÈÎÐÊÈËÇþº´¤—•Ÿª°µ··¸¸¸¸¸¹º¼»¸¯§•”š™“ŽŠ…ƒ€}xstaWyalˆm.”ž¡¦¬®±²³¹´I*0-/.Wº½¼¿Y-2013pÍÇÇÇÇÆÅÆÆÈÂ[21041ü¾½»¶µ´²²°®¬«¨§§¤¡žš”“‹Œj R™“g0”Ÿ ¢¢¢£¤¢ ŸŸž ¢¥«°µº¾ÁÁ¾¼¼¾¼²£”‹‹Š›¦®²°²³¶¸º¸µ­œ‘›§¯´µµ·³­©±­ŸŠt&
+
+
+ ##$#!#+,./-.65--+*+*)****)**)*)))))))* '''&(0./20021/445558 *žÈÊÎÎÉÉËƽ»¶¥—“š¥®´¸¸¸¸¸··¹¼¼»¸³ª ™‘—š˜”Œ†‚zvn$Auu=6z|p/“¡£¦ª¬ª·u%)*)+2™º¶·¾€*1//1I¹ÈÅÆÅÃÃÂÂƬ>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.,,-,++*)))))()**)*<<=><?=BD@@>AB@CEEFEHHGO
+ 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“š¢¨®²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘Š†}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(();<<?BDABEGDEIJFFGIKIILMT4  e±ºÊÏÏÍÌÎÊÅÂÀÀÀ¿¹ªž‘—¢¨®°³¶·¸¸¸¹º»¼¿½¹²¨œ—“‘‘‘’’”•––˜’.)‘“‹–˜š›™!q‡„„„‰lU†„ƒ~}€FC}}‚„‹ŽŽŽŽ‘’“•sm‡„0$~–™œ§t"$#!"$%_Àµ¯´¼Á¼µ§“ŒŒŽ“›¥­²´³²³¶¹¼¾»µª—Œ” «°³´´·ºº¹¹±ª¯²¯†ŠG  ')))(*.--.,-/,-013.063,+*,-*)*+*****)**==>DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnŠ‡+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k
+ *()((&).0/.-././12:82270,++++++++*))*+**BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’š¥¬²¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0<N6-47-*+**+**++)(*,*(ILJIKLJJLOSRQOOMMNOLONLIH #–¿´ÅÌÍÊÌÏËÇÂÁÂÁÂÃÁ´¦˜“˜¡©°µ¹ºº»¼»»¼½¾½»·²«¢š••–˜———”‘’•–— ›˜–˜šœŸœ˜•y"W‡€~„D uƒuncYNJCA=81-*h{z|{xuy76rŽŽ’“–˜š˜š™s#$|‹‰r,ˆ—™œž Ÿ] !"$";‹Ã¾²¯³½ÁÁ¸ªœ“˜¡­³´¶´³´·º¾À½µª‘š¤ª°°±´µ¸º¼»¹®ª®²®‡‹O  !'''((**-../.-//.,/1KK-.78**+)**)*+*))***LOPPQRQONOPSSRSPLNONJKNMM1
+ YÀ¸¶ÆËÉÊÎÏËÆÂÁÁÁÃÅƺ«š“•œ¦¬²¶¸º»¼¼»»»½¾½º´¬¦Ÿ˜•–—™š™—”‘’•—˜™œŸŸ™–—˜›™–’|%Jƒ~€v#D‡ƒ‚…ˆŒŠ‰ˆˆ‡†ƒ~|c7{ywxvtvv/ R„˜ž  ¢£žŠW,ˆŒŒk*‡œœŸ  œœo8!*I€²Ä¼·¯²»ÁÁ¼²¢’ŽŽ‘•›§¯´´µ´´³·¼À¿½²¥–’¤ª¯±´µ¶¸º»»¹±«­±³©‡q &''(''(+.../..-..-/15B7,/63,+*)()**,*(*+*PSSPSXTPSMPPRRSPNNLKMOQROA  šÆ´ºÊÍÉËÏÎËÆÂÀÂÃÄÆǽ¬ž”•š¢©¯³¸º»»¼»»»»½¼»·°¨¢œ—––˜›››–“’“””’“˜žœ—–••”’'A}ƒWn‡ƒ…†„†‰ˆ‹‹‡…‚~{€AW}xxywy€x1%=[nxvfG*WŒ‰e)‡Ÿ £¢ œ™–š“v`Ycw—³¼¼»¹²²·À¿¶©—Ž‘”˜¡¬³³²´µ´µ¹¿¿¿»¯Ÿ’“ž¦«¯³´·¹º¼»»»µ©«¯²¯—‡‡= '(''&((*.../00-,-./1034-,05/**+*,,++*)***WWWY]^[YXTXSZ]ZURRSQPRWTMK VÅó½ÌÍÇËÑÍÇÂÁÁÃÄÅÇÉÀ° “”™ §¬±·º»»»»»¼¼¼¼»¸³¬£˜––™œžžœ—““’‘’’˜™™—”‘Žˆ†„?#7}|/?‡ƒ‡‰‰ˆ‡‰Ž‹Š‡ƒ}||t ,y}{}ƒƒ‰‚=E’‰ˆŒh&„žŸ Ÿœš˜——Ÿ¢¤¨ª­±µ»»ºµ±·¾ÂÁ¹­ž”‘’–œ¦¯´´´´´µ¶»ÀÀ¾¸©›•¡¦¬±³µ·º½½¼¼¼¹®®°±¯ŸŠˆd&))''()(,/.-010/.-/110/1/,-26,)(,..-,+***)UUUVXZ[ZXYYTY`^ZWY[YR\[YVZ$
+ —Ì¿³¾ÍÊÅÎÑÌÄÂÁÃÄÇÈÊËĵ¤•“–¥¬³¹º»¼¼»»¼½¾¿Àºµ¯¥ ›—–™œžŸ¡—“‘“•——•ƒ€~{vpmfj€‡B-$g‡‰Š‰ŠŠŠŒŒ‡„~{zULƒ€†‹ŠŒ““T:’ŽŠ‹i zŸš˜˜–—šœž¡¥¨¬¶º½¼¹µ´¼Â¼±¢–“‘”š¡«³µµµ´³´¹¾¿¿¼³¤•‹˜¢©®±³¶¹»»»¼¾½º±®°±°ª’‡|)*+*)**))....-//...020/-.//,,43-++,,,,+***)NOQQORRRSPQNMSPNQPQSSWVWVU5
+ N¿È¾²ÁËÇÆÐÐËÄÂÂÄÆÊÊÌÏƹ§—’”›¡¨°·¹¼½¼»»»¼¿ÁÀ¿¹²¨¢Ÿ›˜——›ž¡ œ—”“’’””’‘““Žˆƒ~~ƒ‹”™ž–‹€qjŠŒŽŽŽŽŠ‰†ƒ€~~4#r‡Š’“””—œy0?ŒŽ‰‹‰‹`({ š˜˜—–˜™œ¡¥¨®µ¼ÁÀ¼¸µ¹ÀÁ½³©›“’’•œ¦°¶¶´´´³µ»ÀÁ¿º­¡’Œ›£¬±²µµ·»»»¼¾½º²¯°±±¯Ÿ‹†I ++***))*./--...//.2510.../-+-64.,(*,,++*)(PRWVPOQTXTTRQQLLQOOOSVSTTSK ’ÊÆ»´ÅÉÅÇÒÐÉÃÃÅÇËÌÌÎÐ̾«™“–™Ÿ¦¯³·º»»¼»º¼¾ÀÀÀ¼¸®¦¡š——˜œžŸœ™–”•”•”‘Š‰ˆˆ‡††‰Ž”šœž£¥¥£¢ž—–•––”“”‘Œ‰‡„€‚x][\[Z]|‘“—˜šžž Ÿ¥š_&U’ˆ‰ˆ‡‰hH[vŸ žš–—–—˜šŸ£¦¬µ»ÂÃÀ¼¸¸ÀÂÀ¶¬ “‘‘’•™¡­´·¶´´²´·½ÁÁ¼³¨šŽŒ“ž§®²´µ·¹º½¾½½¼»³¯°²³²¨‘‡i,0-,,+++-//.-.///0/254//...-+-/71.+*++++*+*TXZXVQRYb^[[ZYXV\YVUZ][XW[_  TÁÆ÷·ÈÈÃÉÑÏÇÄÆËÏÐÎÎÎÑÐðš””—›¢«±µ¸»½½¼¼¼¾¿À¾¼º³ª¥ ˜—˜œž›š˜•”’“””’‹Š‰‰‹Ž’–˜›œŸ¡¤¦¦¦¥ œœ›™—˜–—–’ŒŒ‹‹‹ŠŠ“•“–šš—™œž¡£¦¦¨§¦§©’`2D{•Œ‡ˆˆ‰‹Œ‘Ÿ¤¤¦£ œ—”•–™ ¢§«²ºÁÅÅÀ»º¿ÂÁ»°¢“‘““—Ÿ©±¶¸¶´³³µ»À¿¸®£”– ©°´¶·¹º¼¾¾½½¾¾µ°±³µ³¯›…D62/-,--.00/..//...04511.-.--,+151-+*++*+--TUTTTQPPVZUUVVVWWUWY\ZZZ[[[)  "“ÍÈÁ´»ËÆÂËÑÌÇÇÉÎÒÒÏÍÏÓÒƵ —”“˜ž¨®´¹»½»¼»¼¿¿À¾½»¸±«£™˜–™Ÿ ›˜——“‘’•”‘‘Ž‘““’“•™œœžš™›Ÿ£¦ª­­©¨¤¥¦¢Ÿžš”‘’’””•š›Ÿ££¥¨ª«­®­®­­¬««¯Ÿ‚hT>638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+-,SQRSRQPWWWRQQTVUSRTUWVSUTTW9
+ PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—š£¨¬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**+++*[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0<C4/00.-,,,,05.++,++*)UVUSTWUV[[X\VXZ[]_`acac_b^YT LÃÌËǶµÅÈÃÃËÔÐÏÕÙÚÖÏÊÊÍÒÕ;©˜””“–ž¨®´¹»¼¼¼¼½ÀÁÁÁÀ½º¶±«¤ ŸœœŸžš˜˜•”‘‘•£¥©ª««ª¨ª©ª¬¬«ªª©§¥¢ š—“‘ŽŽ”–˜š¡§¬³¸¼ÂÃÁÁÁÃÂÁÃÂÂÀ½»º¹¸¶²®­«§¢—Œ‰ˆ‡†„†ˆŠ‹ŒŒ‹‰’•›¡¦¬²¹¿ÅÉÆÁ¿¿Â¿·­ •“””“’“˜ ©³¸¸¶´³³¶¼ÁÂÀ¼´©˜‹‡Œ˜¢¨°µ¸¸¹¹»½¼¼»º»¼¶°³´¶·¸³¤‹c10-**,.0/////....-.2<512/./-,+*-23,*,+*()MKPOPSRQNRONMQRTVTUUWWXZ[WTS% ÐËÌð¶ÆÆÃÄÏÔÔÕÖØÔÏËÇÈÍ×ØÐÁ«™”•““›£­´¸»¼½½½½ÀÀÁÁÁÀ½¹µ²ª¤¡ŸŸžŸ¡ ŸŸžœ˜—–•’‘’“—› ¤§ª­«ª¨§¨ª©©ª§¥¤ ›–’ŒŽ“˜œ¢¤«°µ»¿ÂÅÈÈÇÆÅÆÄÃÂÂÁ¿¾¾½½»»¹¶³¯ª£•‰‡„„„……†ˆ†…†‰‰Š‹Ž”›¢¥¬²·¼ÂÅÆÃÀÀÄÄÀº°¢—‘“•’“”š§¯¶·¶´³²µºÀÃÂÀº±¤’‡…š¥«±µ··¹º¼¼»º¹¹»¼´°³µ·¹º·¬–Žy80.+**///..0.---,,-.02351-/.,++-.53--+)*,MOPSOQRSQSSXQSSRSSTRPRSSTSSO>C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄƹ±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†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 0000000000..0d66f53029
--- /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;<?DEKPROLQSSDE‘ÃÀ¿ÁµIwËÅÆÅÅÄÃÁ¾»¹¶´±­¬¬¯³¶º¿ÂÅÆÇÇÈÇÇÆÆÆÇÈÇÅÄÃÂÄÃÁÂÅÅÆÇÈÇÇÈÇÉÉÊÈÇÄÂÀ¾¹´¯¬§£¡Ÿ›š™™››ž¢¤¦«®­­­®®¯°¯®®­©¥ ™›–Š~ƒ…Œ—¢®³¶¶¶µ°¦¢ ›šŸ©²·¾À¾¾»¶°§¡¢¨¯¯®®®°±¶¶­©¨§¥¢žš˜˜–•––‘Œˆ€|}}y{ywspmjiigefb\`r{€ˆ˜¤§§«¯²¸½ÁÄÆÈÌÎÏÑÑÏÍÍÎÎÌÊÈÈÄÃÄÃÃÄÆÇÈÇÅÈÈÇÇÇÈËËÊÊÌÌËËÊÊÉÉÇÇÇÉÈÉÆÄÂÅÄÃÃÃÃÁÁÂÁÀ¾½»¼¼¼»¼½½ºº¹¸º¹º»¹·µ²³±¤™’Ž‚`=243.6IOPOSWY]^ehy¦ÆÁ¿Ç@¥ÉÃÅÄÄÄÿ¼¹¶³±¯¬­¯³µ»ÀÄÆÉÉÈÉÉÈÇÇÆÆÈÈÇÅÄÄÃÃÃÂÃÆÇÈÉÉÉÉÈÈÉÊÊÈÇÁ¿½ºµ±ª§¦¤ œšš››››ž¢¤§¬®­¬«¬¯°°¯­¬«©¥ž˜š›˜“ƒ~†œ©±µµ¶·²¨¢Ÿž›š£­¶¼¿À¾½¹³¬¨£¢©µ±¯¯®®¯²·²¨¥£¡Ÿš˜——“‘““Žˆ~|{{zwsomjihggcZV\nw‡•£§¨©¬¯µ»ÀÅÇÌÎÏÐÐÎÍÍÌÌÍÊÉÆÄÂÂÂÀÂÃÄÆÆÆÇÈÈÇÆÇÉÊÊÊÊÊËÉËËËÊÊÉÉÈÉÈÇÆÄÅÆÅÅÅÅÅÃÂÁ¿¾¼¼»»¼½¾¾¾½»ºººº¹ºº¹¸¶¶´´µ·µ·º»´šuT?2>X‚Ÿ®¶¸··¸º¾ÃÇÂÂÃÂÅdZÇÇÆÆÅÿ¼º¸³¯¯¬ª«°´¸ÀÆÈÉÊÊÊÊÊÉÈÇÉÉÊÊÈÆÅÅÅÅÄÄÅÇÇÇÉÉÊÊÊÊÊÉÇÅÃÀ¾º·³¯«¦¤£¡œšššž ¡¢¤¨­®­¬«®±²±®­«©§¥Ÿ•˜˜—‡}€„Œ•£®´µµ·µ­¦£ œ™› «³¹¾¿¾½¼¹²ª§¢¥ª¹·³¯­¬®®±·®¢Ÿš˜––”’‘’“‘†|{{zurplkifde`XQVlw}„‘ ¦§§©¬²¸¿ÆÊÍÐÐÐÎÌËËÌÊÈÆÄÃÂÀÁÁÀÀÂÃÄÅÇÇÆÇÆÆÇÉÊÉÉÊÊÉÉÈÉÊÊÌÌËÊÊÉÈÅÃÄÅÅÆÅÆÅÄÂÁ¿¼½¼ºº»»¼¿¿½ººººº¹¹¹··¶·µ³´³²³³³´¶·®•nO::GdŠ§¶¸¹¹º»½ÀÂÂƯEŠÏÆÆÆÄÁ¿¿¼¹·²¯­««®±¶»ÂÆÈÉÉÉÉÉÊËÉÊËÊËÉÇÅÇÅÆÅÄÆÇÈÇÇÇÈÉÊÊÉÉÇÆÃÁ¿¼¹¶²®«¦¡Ÿ›™˜˜šœŸ£¤£¤«¯¯¬«¬¯°²±­¬ª©¨£›“˜—’}…ˆ‘œ¨³¶³·¶²¬¨£žš˜ž¦°¶»¿À¾¼»·±¬§¥§¬»º·²¯°®¬«°¶§ ›š—”•”‘Œ‘’“Žz{zxuromlgdaa]XRPgv{ŽŸ¤¥¤¤¦¬°¸ÀÇÍÏÍÍËÉÈÈÇÆÆÃÀÀÀÁÀÁ¿ÀÁÁÃÄÅÅÆÇÆÈÈÊÈÇÇÈÊÉÊÉÈÉÉËÌÊÊÌËÉÅÄÆÆÆÆÅÅÄÂÂÁ¾¼¼»¼»º»»½½¼»¼º¹»¹¹¸¶µ¶µµµµ´´´´´µ¶¶¸»¼¯—wP;2;X{˜¯¼ÃÂÂÁ¾É{H¶ËÈÇÆÄÃÀ½º¶²²±¯®®³µºÀÄÇÈÉÊÉÈÈÉÉÉÊÊÊÊÈÆÅÆÇÇÇÈÇÉÈÇÆÆÈÉÉÉÈÇÆÄÂÀ½¸¶µ±®ª¥¡˜–——šœž¡¥¤§«­¬©«®°±±¯«««ª¨¢˜“•”…|‚†˜¤­´³µ¸¶°¨¤š™›¤¬³¹¾ÀÀ½»ºµ¯«¥£¨¯»¹ººµ°¬©§§¯²¢›–““’’ŒŒ‹Ž‘“‘Œƒ|xwuroojfca`\XQK^sx}ŠŸ¡¤£¢¤¤¥¬²¹ÁÅÅÄÃÀÀÀÁÁÁÀ¿½½¿¿¿¾¾¿ÀÁÂÂÀÃÄÅÈÈÈÇÇÆÇÈÈÉÉÉÈÉËÊÊÊËÌËÈÆÆÈÈÆÄÅÄÃÂÁ¿¾¾½½½»º»½¾¼¼½»¹¹¹·µ´³µµµµµ¶µ´µ¶¶··¸··º¿À· bJ:<GZoŒ£®·œC}ÐÈÈÆÅÅ¿º·²±²³²°¯³¸½ÃÇÈÉÈÉÈÈÈÇÇÆÈÈÈÈÇÆÆÇÈÇÆÈÇÇÇÈÇÇÇÇÈÇÇÆÄ¿¼¹¸²°­«¨¤Ÿ›˜––—›› ¤¦©­¬¨ª¬¯¯±±­«««ª¨ –”’Œz~„Š”ª³³³µµ°©£™–š£©±¸¾¿¿½¼»¹³¬©¥¡©²¿º·¶³®«§¦¥¥¯°—”‘ŽŒ‹Š‹Š‘‰~yurqqnjeb`]WRLKYouz‹Ÿ¡¢£¢¢¡£¥©¯´·»¼¸·¸¹º»»½¼º¼¼¼¼¾¿¿ÀÁÁÁ¿ÀÂÂÆÈÇÆÈÆÇÈÈÈÉÉÈÉÌËËÌËÌÌÉÇÅÇÈÈÇÇÆÅÄÂÁÁÀ¿½½¼¹»»¹ºº¹···¸·µ²²²²µ·¸¸µµ¶·¶¶··¸·º¼¼¾Á¿³˜ƒoYIABGVLlÅÎÉÇÆÄ¿»¸µ´´µ³²®¯µ»ÁÅÉÊÊÉÉÈÈÈÈÇÅÆÇÇÇÈÈÉÈÇÇÇÇÇÇÈÇÇÈÉÈÇÆÅÂÀÀ¼¸¶³±«¨§¦£Ÿ™——™š›œž£¦ª­­¬«¬­®¯­¬«ªª¨¦Ÿ–“Ž}zˆ‘™¡¬²³·´°¨¡ ›˜˜Ÿ¦®µº¼½¾½º¹µ±¬¨¤¥®¸¾»·®©¨§¦Ÿ ¤¯ªš•’‘‹Š‰‹Š‹‹ŒŒ†€{vqoomida^[SLIGWqtw‡ ¤¢¡    ¡£¨ª«±±°°°°²µ·¹¸¹»º»¼¾¾ÀÁÁÂÁÁÂÂÃÅÆÇÈÆÆÉÉÉÉÈÉÈÈËËËÌÍÌËÊÊÊÈÈÈÇÈÇÆÅÃÂÂÂÁ¾¼»ºººº¹º¸¶µµµ¶´´´µ·¹º»»¸¸¹¹¸·¸¸¸¹¼¼¼¼¼¾ÀÂÅÉǼ³ž„v|™ÆÎÊÊÈÆÄÀ»·±±µ¶¶µ²®±¸½ÄÈÊÊÌÉÇÅÅÅÃÄÃÂÄÅÄÄÄÆÆÆÇÈÇÈÈÈÇÈÉÈÈÅÿ¿¼¸¶²°­¨§¦¤¡ ™—™›œœž¡¦©¬­¬­®®°®¬¬ªª©¦¥¡“„wz€‹”ž§­±µ¶³«¢Ÿ›™šŸ¤«²¶¸¼¾½¼»¸³¯©£¡¨²¹¸º¹¯¢  ž•—›œŸ­¯™•““ŒŒ‹ˆ‰ˆ‡…‚{xtonliea]ZOIDCOptwƒœ££¢ŸžŸ ¡¢¢¨«­­¬¬­¯²³µ¸¹¸¸»½½ÀÁÁÂÂÂÂÃÃÄÄÅÇÇÇÆÈÉÉÉÉÈÈÉÊÍÍÎÍÊÊÌÌËÉÈÈÈÈÈÈÆÅÆÅÃÂÁ¿¾¾½½»¹·¶µµµµ·¸¸»½¾À¿¾¼»¼»ººººº»½½¾½¾¾¿ÀÂÃÂÁÅÇÇÄÈÍËÊËÉÈÆÁºµ°°°±´µµ³³¶»ÀÆÈÉÉÇÄÃÁ¿¿½»º»½¾¿¿¿ÁÂÃÆÅÆÆÇÇÆÅÅÅÄÂÂÁ¿¿»·µ±­ª§¥¤¢ Ÿ›˜—™›› ¢¢§ª¬­®¯¯°°¯­¬«©¨§¤“ŒywzŽ˜¤¬¯²µ´¯§ œ››¤ª¯³·º¿¿¾¼»¹´¬¦¡£«´¸³´³³©–’””’•­°ž“”‘Œ‹‰‡‡ˆ†„~{xtqolhec]UMHA@Iluxƒœ¤¢¡ž››ž ¢¡Ÿ ¥¨ª««¬­®°³µ´´¶·¸º»½¿¿ÀÁÂÃÃÃÄÃÄÅÅÇÆÇÈÉÈÊÊÊÊËÍÍÎÌËËËÌÌÊÉÉÈÈÈÇÇÇÇÆÆÆÇÃÀÁÀ¿»¸¹·¶µ·º½ÀÁÂÃÃÃÄÂÀ¾¾¿½½½½¾½½¾¿¿¿¿¿¿ÁÂÁÂÀ¿ÀÄÇÉÊËÉÆÄÀ»¶°®®­­¯±²³µ¹¿ÆÈÈÆÅÄÁ½»º¹·¶³³¶¸ºº»»¾ÀÂÃÄÅÆÅÄÃÃÃÃÃÂÀ¾º¸¶µ°«¨¥££¡ž›™™œ››› £¤©­®­­­°®®®«ªªª¨§£›‘…uw}‰’ž¦¬±´²°ª£Ÿœœž¥ª¯´¶»¾¾½½»º¶°«¦¤§°¶¸©©ª²¯£Œ‹Ž‹Ž’š¨µ¦˜’‘ŽŒŠˆ‰ˆ‡‡‚zvtplifcaZRJE=9@gvz…¤¢žŸ›žŸ ž £¦¨©©«¬¬¯²³³´µ·¸¸º»½½½¿ÀÁÂÃÄÃÄÅÆÇÇÇÈÈÇÈÉÊÌÌÎÎÎÌÌËÌÍÍÌËËËÊÊÈÇÇÇÇÇÈÈÆÄÄÄÿ»º¹º¹¹¼¿ÃÇÆÈÇÇÆÄÄÂÁÀÀ¿ÀÀ¿¾¾¿¾¿ÁÁ¿ÂÄÄÄÃÂÃÆÉËÌÊÈÄÁ½»·³°®ª«®¬­®³·½ÁÇÆÄÂÂÀ»¹·¶¶µ±¯°²²µ¶·¸»½¾¿ÀÁÃÄÂÂÃÃÃÁ¾¼º¸µ³°¬§¤¤¡ Ÿžš™™œœ›¡¡£¥ª­®­­¬®­««ª¨§§§¥¡švtz‚™¢ª¯²²¯¬§¤¡ ¢©®³¶¹»¾¾½¼»º¸´°ª§§®´·¶¢¢¤§©§™‹…†‡‡‡Š“£°ªŸ•‘ŽŒ‹ŠŠ†„|uqmkida]WOD9407cz|…— ¢žœš›žžœ¡££¥§¦¨«¬­°²³´µ¶·¸¸¹»¼¼¾¾¾¿ÀÃÄÄÅÅÆÇÇÇÆÇÇÈÉËÌÏÐÏÎÎÌÍÍÎÎÍÍÎÍËÊÉÉÈÈÉÈÉÈÆÆÇÄÁÀ½»¼¼¼¾ÁÅÆÊÊÊÊÉÇÅÄÃÂÁ¿¿À¿¿À¾¿ÂÁÀÀÃÄÄÃÄÄÅÈÊÊÉÇÃÀ¾»·´²²°®­¯¯®±µºÀÄÅÄÃÀ¾º¶´³±²°­­­­¯±²³µ¶·¸¹»¼¾ÁÂÁÁÁÀ¾»¹¸¶³°¬¨¦¤¤¢ œ›™™›››Ÿ¢¤¦¨ª«¬¬¬­­­¬¬«¨§¥¤¢Ÿ—ow|ˆ’§­²±®­©¨¦¦¨¬³¶¹º»¼¾½½¼º¹µ²¬§¥¬³µµ´›œžŸ¤¤‚€€„„†˜¨­¨ž”’ŽŠ‹‡ƒ~ztplkeb\YQD:41.1Xx}†– ¢ ›šš›žž ¡¢£¤£¦ª¬­¯²³´µµ¶¶·¸º¼¾¿¿¾¿ÀÂÄÄÄÄÄÆÇÇÆÇÈÈÊËÌÏÑÐÏÎÍÍÏÐÏÏÎÍÎËËÊÊÉÊÊÊÊÉÉÈÇÅÁÁÀ¿ÀÁÀÂÃÆÈËÊÊÉÆÅÃÂÀÀ¿¿¿¿¿ÀÁÀÀÀÀ¿ÀÃÃÂÁÄÆÆÉÊÉÈÄÀ¾º¹·¶µµµ´²²°°´·»ÀÄÅÄÃÀ»¶±°¬ª«¬©§¥§©¬¬®°²²´µ¶¸¸»¾¿¿¾½¼¹¶µ²¯¬¨¦¤¤¤¢ššš›œœ›œ ¢¥¨ªª¬¬­®­¬­¬«©§¨¥¡Ÿ‘wty‚Œ–¤«¯¯­¬¬«ªª¬°µ¶º½½¼½»»½»¸¶³°©¥§­²´³²–”“‘•˜  €~{}~€„‡‘ ­±¦•Š‡|vsojfb\VME?;620/Jv}†”Ÿ¢ ››˜˜›ž   ¢£¤£¥¦¨ªª®²²´¶¶·¶¶·¹½ÀÁÁÂÃÄÄÄÅÅÄÅÆÆÆÇÈÊÌÌÍÎÐÑÑÎÍÏÑÑÐÐÎÍÌÌÌËÊÊËËÊÈÉÊÊÊÈÆÅÅÃÃÅÇÇÉÉËËÉÈÆÃÁ¿¾¿À¿¿¿¾¿¿ÀÀÁÀÁÁÃÄÃÃÃÆÈÉÊÊÉƾ½¹¶µ´´´µ¶¸¶³³´¶»¿ÂÁ¿½¸³°«¨¥¥¥¤£  ¤¦¨¨©««®±´´µµ·º»ºººº·µ´°¬©¦¥£¢¡žœ™˜˜œž ¡¥©«ª«¬­®­¬­¬©¨§§¤ žsu{…ž¨®¯­¬¬¬¬­®²¸»½¾¾½¼¼»¼¼º¹¹³«¦¦«°±²±°™Š‹Œ™•…~{{zz{~…˜¤°¯©“‰„}vrmgb^ZQIHDB?9736Dk}…šžŸœš——šœžŸ ¢¤¤¥¥¥¤¦ª¬¯¯±±²´´³³µ·»¿ÁÂÃÄÄÄÅÅÄÄÄÆÆÆÈÉËÍÍÎÐÑÑÏÎÐÐÐÑÏÍÌÌÌËÊÊËÍÎÌÊÊÌËÌËÊÊÉÇÈÉÊÉÉËÌÊÉÇÄÁ¿¾½¾¿¿¿¾½¾¾¿¿¾¿ÀÂÃÄÅÇÇÊÊÉÉÇÅ¿»¹·´²°®­°²´´³´µ¶¹¼»·µ±§£¤¤¡  ž›œžŸŸ¢¥¤¦¦¦©«®®°±´µ¶¸¹·¶µ´±®©§¦¤¡žœœ›˜——šœžžžŸ¢¥©«««¬­­¬¬­¬ª¨¦¥¤¢Ÿ›‡twŠ—£«®¬«ª¬«ª®³¸¼½½¼½»º»½½¼¼¹¶¬¦¦©­°±±±°–Ž‰ˆ‰‰Š–„|yxyz{|}ˆœ¦«­®¦“upe`\WOIGHGCA@=;:Bdz‚Š˜Ÿœ›™——šœžŸ Ÿ¡£¤§§§©¨©¬­¯­¯²²²³´¶¶º½½¿ÁÂÃÃÃÃÃÄÅÆÈÇÇÉËÎÐÏÐÐÏÏÐÑÑÐÏÐÊÂÊÎÍÎÏÎÏÎÌÍÍÍÌËÌËÈÉËËËÊÉÊÊÉÈÄÁ¿½½¼¾½¿¿½»»¼¼¼¼¾ÁÃÃÄÆÈÉËÊÉÇÿº··´±®­¬««ª«®¯³´¶·´­ª¥˜–˜™˜•”‘Ž’˜š›ž £££¥¦§ª«¬¯±³µµµµ³²±®ª¦¤£¡Ÿšš™™™˜™žž ¢¤§ªª««¬­¬«ª¬«ª¨¥¤¤£Ÿ›‡t}„ž¨¬¬¬ª©©©©®´¸º»ººº»ºº¼¼»»¶¯¦¥¥¦«®¯°¯¯“‹‡„„…ƒˆˆ}yxwvvxy{„‡—Ÿ¬®³®£‘}k_VOMIJIGEBA@=?Yr}ˆ–ŸŸš–˜˜˜˜–šžžœœ £¦¨ª«¨§©«¬®°±°±²´´µµ¸»½¿ÁÁÃÂÄÅÆÆÆÈÇÇÈÊÌÏÑÐÐÏÎÏÏÏÏÐÕ»]_lw‡²ÑÏÏÎÎÍÍÍÌÊÉÈÌÎÍÎÍËÊÊÉÆÁ¿½½¾ÀÀÁÀ¿¿¾¼¼¼»»¿ÁÃÃÄÆÇÈÈÈÆÄÁÀ»¸¸¶´±­©¨§¥¥¤¤©­¯±±¬¦¢–‘ŽŒ‹Š‹ˆˆ‹•˜š ¢£¦¨ª««­¯²´´´¶µ²°­«¨¤£¢Ÿœ™šš›š™™›ž  ¡¥¨©«¬­­«ª«ª«©ª¨¥¤£ ™„y€š¨««ªª¨¨§¨ª®²´µ¶···º¹¹¹»º¶®©¥¤¤¥¨¬«¬®®”Œ†‚‚‚…Š…}wusvxxxz~‚‡ˆ“œ£ª°³µ¯¥œ‹xoeZRPIFDBPlz†‘žœ•”••”’•—››šŸ£¦©ª¨©¨¨§ª­®¯®¯±²´µ¶¹¼¾¾ÀÂÂÃÅÆÆÆÆÇÈÈÊÍÎÐÒÑÐÏÎÎÏÎÎÎÒ«:8752†ÖÏÍÍÎÎÎÍÊÈÉÈ«¨°´µ·¸ÄÈÄ¿ÁÄÅÅÄÁÁÁ¾½½¼¼¼¼½¾¿ÀÂÅÄÆÉÌËÇý´²¯©©§¡››Ÿ¡£¡¡¤§¨¨ª¤žœ—‘ŠˆŒŠŠ„„Š“–™›¢¥¦ª­­®¯±µµ¶¶·¶²¯ª¨¦¤¡¡žœ›™™š›œ›œž  Ÿ ¥§¨©ª««¨¨¨¨§§©¨¥¤¡ž›–z†”¥¬«ªª©©©¨ª«¬±²±´´³³¶¸ºº»¹²¬¦£¢£§««©«®®•‹‡ƒ~€€‚‚~xurssutuxy{{~‚‡‹’š ¦­°³±±°¬¥Ž}jYOKPhuƒšœ›—‘’‘‘””–™™›Ÿ¤¨¨¨¨¨§¦¨©ªªª­®°³µ·¹»½½¿ÀÁÁÃÅÆÇÅÆÇÈÉÍÏÑÑÐÐÏÎÎÏÏÏÎÓ™:?>?:–ÖÌÍÍÍÌÍÊÊÒºl><ABDWÂÆÈ‹exŸ¯¹ÁÅÇÃÀÀ¾½¿ÁÀÂÂÁÂÞœ†qbUHD?866732489<>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"  
+ <t{}‚ƒƒƒƒƒ†‰‰ˆˆŠ„‚~~{yvussssssqqrpqqpnqpqpopqrtwz}„‰‘••’‹†„„††‡5"w€{{|t#b‹„†Š‹Ž|<”’”•š ¤£££¡Ÿš“Žˆ†jT‰†Š”–˜œ ¥¨ª¬¯±M)/164>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<BGIKMOQTWWWSOOSY[_bbdgkptxvpqqosx…ˆŒ’’”•™  
+
+
+
+
+
+
+
+
+ Gƒ’Ÿ§««¬¬ª¦¢˜“‘ŽŽ”–•–——˜™š›œœš›œ››››Ÿ¦°¶¼ÂÄÂÀÀÂÃÅÆË‹5172692.5=?;963367583A¡ÍÂÃÄÃÂÀ½¼¾ÃÆËÌÒ¯:-343‚ƺº»¾ÁÂÁÁÂÅÆÆÆÅ˨ynad¼ÊÊÉÉÉÇÆÄÁ»º¶°© ››D6trsrqqpnpp'f~yyyz|}~ƒ†‹Œ’ŽŒ‰‡‡„~|{}~~~||{xuuusqnnlkkkheb`cQ248<BFIKMORTUSNMPTX]^_`deioswqppons|„„ˆ‘”˜ž 
+
+
+
+
+
+
+
+
+
+
+ 1t›¤©«¬¬¬«¦¢Ÿœš˜”“–—˜››™š››œ›š˜––—˜™ ¬¸¿ÄÆÅÆÈÊËÌÎ|)1:7723Mw­³­¦c=0:98=;>œÉÂÃÃÃÃÂÄÇËÌÌÌÆ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#
+ !(,7<?BGJLKJGJMRVYZ\]\acehijot}‡”•”“”•••–••“’  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Rx…“ž£¦ª¬¯²²±¯­­«§¦§§¨§§¦£¡ ž˜‰„„ƒƒ„…†Œ’ž¤«°¶´D-6:5XÅËÇÈÈÈÈÇÆÇÇÇÅÆÆÅÃÁÇÊÇÆÆÈÉÊÌÍËÇÁ·¸”,+,./-/2286VÃÂÂÂÂÂÂÃÅÆÆÅÍz5:89?«Á»¹¸±¬¨Ÿ™ˆ…€|xycZtrrsz;#|Œ‘’’““”—™šž ¡¡žœ™—”“”••••“‹ˆ‡…‚‚‚‚‚‚……ƒƒ‚{~B #(.8>AGJJJIHJMPUXYZ[\_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"*.“—šœœ››œ›œ ¢£¢¡ŸŸŸž›™—”‘‹‰‡ˆˆ‰Š‹Ž‘’‘‘Š: &).4<AFFEDFILNPRTYZY]]\]amy‚„ˆ‹“””–”•—›š™—   
+
+
+
+
+
+
+
+
+
+
+
+ O„ˆ– ¨¬®°±²´²°¯¯®ª«©¨§¨©¨¨§¥¤ž•‹†ƒ€ƒ„Š“˜¥ "$)3¨¿ÀÃÅÇÈÈÈÈÇÇÇÅÄÄÅÆÆÉÈÈÊÊÊÉÈÆÃÁ½Ãš11114,wʪ96589C¶ÄÂÃÃÃÄÆÆÃĺF6763D®¯¨¤ž™’‡€}{xutxN#NWw|F8Y5Œ—›žŸžžžžœžžŸ¡¢£¢¢¢¢¢¡ Ÿ›™—”’‘‘ŽŽŽ’•—š›žš™“` #)-4:@EFDCEIKNPRUVXZ\\[[[`fnuz€…†‰ŽŽ“•–”•“‘
+
+
+
+   
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  ,w„“¥«­¯°±²³³³²°®­¬­¬¬¬¬«ªª©¥Ÿ–Œˆ…‚€€‚„Š›~ "* ½»¾ÀÄÅÆÅÅÅÅÅÅÅÅÇÇÉÊÊÊÊÊÉÇÆÅÅÂñ@.4271L¼»¿`066:2‹ËÁÁÁÄÅÅÄÃÄ°?7640D¨£žš”Š„€}{xutvt-:h-|j}UB—•˜œž ¡¢¢ žžžŸ ¡¢££¤¥¥£¢ žœ››š—””“”“”–˜›ž¡¤¦¦¥¥¡‹,
+ !'+29@DCCCEIJJMPTUUYZYYYY[\^hmrx{}ƒŠŽŒ‹‹Œˆ…
+
+
+
+
+
+ 
+
+
+  
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ \‡— ¨¬­®®±³¶·µ³±¯¯®®°±¯®¬«¬©¥ ˜ˆƒ€€ƒ‡$‡º´¸¼¾ÀÁÁÂÃÃÁÁÃÄÇÈÊÊÊÈÇÇÆÅÄÂÂÂÃ]*63352›Â·¿.6685VÃÀÀÁÃÃÃÃÂÆ£7710)D£›—“ŽŠƒ|yywust`Iw3U7\“OI—–™œŸŸ¡¢£¢  ¡¡¢¡¡££¡¡£¡¡ Ÿžœœš˜—˜™š›¡¢¦©«°¯®¬¨¨i
+ $)28?CBBEEFGGJNPSSTWWWXY[[^cfiortxx{€€€€‚
+
+
+
+
+
+
+
+ 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+  9u…‘¥«­®¯±²²³´³±¯­®®°±°¯®®®ª§£ —Ž‡„~}~„Š6U±®²µ·º¼¿ÀÂÃÂÁÃÄÅÈÉÇÆÅÄÃÃÂÿÈ~'2212,sǺ»¿·B47587 ÇÀÃÄÂÂÀ¾Æ”/2-+#AŸ”Ž‰„~zxvwvtrwEY}Y2–LR˜–šŸ¡¢£¥¥¤¤££¡¢¤£ ¡¡Ÿ  ž žœœš› ¡¤§ª­°²´¶¸¶¯¬9
+
+
+
+
+ $(28>BBCDEFFGGIMRRSVUVWX\`_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:<AEFJORVZ]]abfnruz‚…‡yj]E82    
+
+
+
+
+
+
+
+
+
+
+
+ a{veY^`qˆ˜Ÿ¦ªª¬±µ¶¸¸¸¹¸¸¸··¶¶µ´³´´³²±¯®¬««©©§¤¢œ›—’Šˆ‡†‚ƒ‡‰ˆ‹‘“šž¢¤ª¯µ¸º¼¿Á¿¸¶²¬ª©­µ¾ÇÉÇƼ¶²©®Q/…ƒ…C #]²¯¤8*(,-g¸¶·¸····¸¹¸·´³³´´M,0.)ˆ´¯®®®®°¯¬©¦¥¥¦§§©¬±¹¿ÄÅÇÈÈÇÇÅÅÅÅÄÿº¶£lg$
+
+
+
+
+
+
+
+   /:AEILNVZ[]a`chmknkeYI?2(&&'(   
+
+
+
+
+
+
+
+
+
+
+ gywkZ]ae€–¤§ª«°´µ·¹»»º¸¸¸¸¶¶¶¶¶¶µ³²²±®®®®¬«©©©¥ ž™˜•‘ŽŠˆ…ƒ†‹ŒŽ”šš ¥©®®±³¸¹¹¶±­ª¨¯»ÃÇÆÅÁº²«¨£¤t*& !p…ƒ< !"#""$'+//447;BDy¸´²„}„Ž’¥·····¶¶¹ºº¸·´³³´¸¥˜˜’‘¨¯¯®®­­®¬©¦¦¦§©ª©®±¹¿ÄÆÈÇÈÇÅÅÆÆÅÅÂÀº±­df
+
+
+
+
+
+
+
+
+
+ 
+  $*3:<CFHNNOKFD<4)+&"%$&'&&   
+
+
+
+
+
+
+
+
+
+
+
+
+
+  kwwj\^adrŒ™Ÿ£¨«­²´´·º»º¸¸¸¸·¶¶µ¶µ´´³³³³³²²¯­¬­®­§¥¢Ÿœ™•‘Š†„€€‚ƒƒˆŽ“•š ¤¦¨«²·¸¸·³®«ª±¸¼»ºµ°«§¢–”†{utr}}tw{{~ƒ‡——™¡§­±¸¶¶·½º¼½½»¹¹¸¸º¹¸º¼¹¶´´¶µ¶´·¸¸µ¶°¯¯®®¯­­«§¦¦¨ª¬®°¶¹¿ÄÇÈÈÈÇÆÆÆÈÇÅÃÁ¼±©”cjb
+
+
+
+
+
+
+
+
+
+
+   $$!"!# """$%$"   
+
+
+
+
+
+
+
+
+
+
+  "rzyo^_aci|“™ £§¬±³µµ¶¸¹ºº»»º¸·¶µ³²²³³²²²±±°®¯¯°¯®­ª¦¢ž™’Žˆ‡…ƒ€€„‰Œ‘”™Ÿ¢§¬²µ´´·µ²±²±²±¯¯©¡—”‘ŽŒŠˆ†‰‹‰‚€ƒ‡‘Ž“”—™Ÿ¤¦¦§ª¯²µ¹ººººº»¼¼»»¼»¹¸¹¸¸º¸¶µ¶¶¶·¶²³²°°¯®®­­­­­«©¨ª«­°µº¾ÃÆÉÉÈÈÇÇÇÆÆÅÄÀ¾º¯¦šmbpa
+
+
+
+
+
+
+
+
+
+
+
+ 
+ !###$rbTE1   
+
+
+
+
+
+
+
+
+
+
+
+
+ %tz{r\]^acm‡–›¡¤©¯³¶··¸¹ºº¼¼¼»¹·µ´²³´³±±±²²²±°±°¯­­ª¨¦£¢žš•ŽŒˆ†„‚„…†ˆŒ“˜Ÿ¦¬®®¯²µ´´³²³±­ª¦Ÿ™’ŒŠˆ‡ˆ‰‡„‚„†„„‡Œ‘‘’˜œ¡¤¥¦«®¯°²µº½¼¼¼¼½½½½½½½»º¹¹·´´µ´´¶µµµ³±´´²±¯­«¬«««­¬¬­®²µ¸¼ÀÅÉËËÊÈÈÇÇÇÇÇÄ»µ®¤›zagpa
+
+
+
+
+
+
+
+
+
+    #%%'œ›š–“‡|dF1 
+
+
+
+
+
+
+
+
+
+
+
+
+ .y}~s^[^adg~‘šŸ£©°²´¸ºº»»º¼¼¼¼¼º¸µ²´µ´²²±±²²²²²¯®®­¬ªª©§¤¢—“Š‡…†‡‡‰ŒŽ’”™¡¥§«°³¶´µµ··³®¥ —•‘Ž‹ˆ‡‡‡‡……†ˆ†„ˆŠ“•—œž¢¥§¨©¬¯±²³¶¹»¼¼¼¼½½½¼»»»º¹¸¸·µ´²²±±³²²²²²²³±°¯­¬«««ª­¯¯°´¹½ÂÆÈËËËÊÈÈÇÈÈÇÅÃÁ»´¯¦œ€adjs]
+
+
+
+
+
+
+
+
+
+
+
+  "#'()•–”’“”˜™™’„j9 
+
+
+
+
+
+
+
+
+
+
+
+ 1}~~ud[]`ddoˆ˜Ÿ£¨®²´·¹º»½¼½½¼¼½»¹º¹¶³²²°¯¯±²°±±¯®¯¯¯­­­ª¨¥¢Ÿ›–’Š‡ˆˆ‰ŠŒŽŽ—¢¦«°²³·¹º¼¹´­£–•”‘ŽŒˆˆˆŠŒ‹ˆ‹‘”˜—›Ÿ¤¦¨¨ª­¯²´µ´¸º»¼¼»»¼½½»º¹·¸·µµµµµ³³±®®¯®®®®®­¬®®®­¬«¬®¯²´¶¸½ÀÅÉËÍËÉÊÈÈÉÊÈÇÄÁ¾¸³­§œŒi_flu\
+
+
+
+
+
+
+
+
+
+
+   #&()*‘”““‘’“’’“—k  
+
+
+
+
+
+
+
+
+
+
+ 3€€zd[^_bdfuŽ˜¥«¯²´¸ºº¼¿¿¾½¾½»ºº»¸´²±°¯®¯°¯°±³²°¯®®®®¬«©§¤ œ™”ŽŠ‰ˆˆ‡ŠŒ‹“˜œ ¤©¯³¸¼½À¾¼¸®«¤¡Ÿ›š——“Ž’’’‘— ¡¢¥¨¬®­®¯°³´·¸º¾¿¾½¼¹»ººº¸··¶¶µ³³³³³°°¬««¬­¬«ª«««­®¯®¯±²³´¸»¾ÃÆÉÊËËÊÊÉÉÉÊÊÉÅ¿»¶²¬¨¡•r\cimq]
+
+
+
+
+
+
+
+ 
+
+
+ 
+ !$#%%)*)’’’’“““”’‘g 
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <~€{h\^`cedm‚Œ˜¡¨®²´·¹º¼½¿¾½¼¼º¹º¸¸µ³³²±°°°±²³´²¯¯®®­­­«ªª©¥¢Ÿš–•ŒŠˆ†ˆˆ‹’–› ¥¬±·»½ÀÀ¼·³²¯©¦£ ›˜œ›››Ÿ¤¦¨­¯®¯±³¶¶µµ¶¶·¸»¾½»»º¹¹¸¹·¶¶¶´µ´´´³²°®­«ª«««¬¬­¬¬®®¯¯°³µ¹»¾ÁÃÆÊËËÌËËÉÊÊÉÊÊÈÅÁ¾»·±¯¨¦Ÿacgknq\
+
+
+
+
+
+
+
+
+ 
+
+    "#%'&&&*+)“’’’’’’’‘Ž’S  
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >ƒ‚{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::<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%' !$%*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:9<?O´¼¼º¸¸··³±±¯ª«“($!!%"§ ©lF§§§§¦¥¦§¦¤£¡ž›™——•˜iu›™Ÿ£¦©­´·¼ÆÉÊËËÊÉÈÇÅÀ»µ®¥˜…|tlosyzz~€€€€‚‡‘š ¦­³¹¹µ­Œ{Šž«°±²¯¦}
+
+
+
+
+
+
+
+
+ #'''''&&&%%&&%%##%+/65455300/-,+*)(!
+
+
+  6›  ¬³µµ´²±°¯¬ª¤™‹‡Œ’˜œœ˜•‘Œˆƒ~yutronopommlprrsstustvx{…—©³¹»¼¾¿¿¾½ÀÂÂÂÁ±?6324M¶»»¸¶µ´²¯¬ª¨¤®_ ! /˜£ ¥„ %ª§©¦¦§§¤£¢Ÿžš™˜–••@+’ž¢§ª¯³¹ÀÈÌÌÍÌËÊÊÉÆý¶¯¤—‹~tjlstwz{‚ƒ‚ƒƒ†Ž–ž£ª²·º¹´¥„|¢­±²²®¨€ 
+
+
+
+
+
+
+
+
+
+ $('''(('&&&&%&%$%#%,353310///,*'')&$
+
+
+
+
+
+  \­žžª°µµ´´³°¯®«§†Š—žŸœš–‘ˆ…€|ywsqqsrpopqqrrrrstutux}ˆœ¬²µ¸»¼½¾¾ÀÁÂÂÀ¨5.+++O»½º·´²±®ª¨¤Ÿ¢ˆ$M£ž ¡™/ b°©©§§§¤¢¡Ÿžžœ›™•žjO¤ž ¦¬°µ»ÀÆÉÊËËËËËÉÆÃÀ·­£—‰}ulkotwxz|„„„„†Š’š ¨°µ¹º¹±š|~”¤­²³³®¬{
+
+
+
+ &)))'&%&&%&&$%$%&%$&+.211/---,*('&&%"
+
+
+
+
+ #†´¢©¯³´´³³°°¯®«£“ˆˆ”›Ÿ  Ÿœš˜”†~|xutuvvutsssttstvvuxxz€¥¬¯°³·¹º¼¾¾¿À¿ÂŸ53,,,Y½»¸´°¬ª¦£•›4z šž£U! !)Š°¥¥¥¤¡Ÿžžžžœ˜‰.€§¢¥­²µ»ÀÅÈÊËÊËÊÊÉÇĽ¸­¤˜‰umkortx{~€‚„…„†‰Ž–¤«±¶ºº·¬{†˜¨°³³²®¨
+
+
+
+
+
+ &***'%&&%%%&%%%$&(&&',/!001--.,))'&&&" 
+
+
+  ?¦´¤œ©¯´´µ³³²°°°­¦–Š…‹”›¡¤£¢¡ž›™•“‰ƒ€|zyyz{zxvvtuuuuxz|~€‚‡¥©¬®±³µ¶·¸¹¼¼Á‘5:497j¾µ²°¬¦¢Ÿš””„9=š˜™¢ˆ ! "#"7˜¬¢¡ Ÿžžœ›˜˜”? A £¦«³µºÁÅÈËÌÌÊËÉÊÆÁ¾¶« œŽ~volosuvz}ƒ„……‡Œ•œ¢©¯²·º¹´§ˆ|‹ž¬²³³³¯¢„6 
+
+
+
+
+
+
+'++)(('&&&&%%%&%&&&&%&(1#/.,,++)%&&%$# 
+
+
+
+
+
+
+   b²´£©­²µ´´³³±±±¯©œ…‡—ž¦¥¤¢ œ›™–“‘Š†…ƒ||~~}zyxxx{}€ƒ‰‰•Ÿ¤¦¨©«­®°´´¶·À‚+4361w¿²¬¨¥œ››˜e)›•™ž¤J$""#7‡§Ÿšš™——˜˜›¡Œ@#  ¥£©°·ºÁÇËÌÌËËÊËÉÆ»¶®¡šƒwninsvwyy|€ƒ„„…ˆ‹‘š ¦®±´¸º¸±œ€ ­²´³³®š€Y  
+ !*-+)(((''&%%&&&'%$%%$$&+/$****&%&$$$"!
+
+
+
+
+
+  "‡·¸£ ¨®²´´²´³²²²°©Ÿ‘ˆ…Œ•›¡¤¤¤£ žœ›—•”‘‹‰„ƒ‚ƒ„„ƒ€}|‚†Œ˜£¥›“™šœžŸ ¡¥¨ª®±¯¹q'0/0,n±ª¥¡ž˜b9X˜“”—šžž¢(!#!! *f‘œœ™™˜šœ’h1 !Tª¤ª°·»¿ÆÌÎÍËÊËÊÉÅÀ¼µ®¥—Žƒxoimqtvxy{~„„…ˆŠ‹• ¦«°´¶¸¸¶«“{“£®²µµ²«™€y!
+
+
+
+
+
+
+ %)++)(((''&&%%&&&&%%%$%%%(3$)(%$##$##" 
+
+
+
+
+
+
+ 5¡¸¹¢ž§¯³´µ³´´²²²²­¢”‰ƒŠ–šž¤¦¦¤£¢ Ÿœš˜–•–•“ŽŒ‹Š‹ˆ‡‡†ˆŠ‘™¥­µ¶«˜’‘”——› ¢¥§§°]!&'&%,@DAC<90&G‘’•™›œ§t 2Sq}|{qV5 =ž¨ª²¸»ÀÄÉÍÍÌÊÊÊÇÄÁº´¯¤›‘†{qklqstvy{|~ƒ…ˆˆŠ’›¥ª®²´¶¸·´¦†zˆ—¦°µµ´±©–ƒˆI
+
+
+
+
+
+
+
+
+
+%*++)(((&&%%&%%&&'%$$$%$$%)/('#$&##$"
+
+
+
+
+
+
+
+ _¶»º¡©®²³´´´´´³±²¯¥˜‹„†˜ž¤§¨§¦¦££¡ž›ššš››˜–””“‘‘‘–¬¶µµ®©•ˆ„‰”’–™œžž§NNˆ‰ŒŽ•˜œ¦Z#%$! :—©¨®¶¼ÁÅÈËÍÌËÉÈÆÄÀ»µ«¥œ‘†~skkpsrtvy|~„‡‰‹‘™£©®±³µ¸º¸¯žœª°µ¶µ°§•‡o
+
+
+
+
+
+
+  )*,+)((('&'&&%%%%%%$$#%%%&'(('%%%##!
+
+
+
+
+    !‡º¿º¡œ©­°³³´´´´´´²¯§›‡„‰’›¡¥¨¨¦¦¦¤££¡Ÿ ¡¡£¢¢¡Ÿžœ›ššœ ®µ´¯©¢–…|{|~†‰Š’““–C'b‡†ŠŒŠ‹Œ–˜›¢WBœ®¨¯´º¿ÅÉËËËËÊÈÇÅ¿º´«¤’„}ukmprrsvyz|~ƒ‡‹Œ’˜ ¨­±³´·¹»¸­—}‚‘ ¬±µ·¶²§”†-
+
+
+
+
+
+ ")++**)))('&'&%%&&&%$%$%%%&&%&$$$"""
+
+
+
+
+
+ 9¡»Á¹¤œ§®°±³³³´´³³³±«Ÿ‘‰…†Œ”š ¤¦¦§¦£¤¤¤¤¥§¦©§©ªª¨§¦¥¤£¥¬²¯¦”Žƒzvrsuwy}ƒ……†…‰6'Rˆ„†‰‡‡‰Š’–˜˜™—h" W ¬ª°´ºÂÇËÍËÉÉÈÇÅÃÁº´© šŒztlmorrsuwz|…‰“•œ£ª°´µ¶¸»º¶¦‰{Š—¡«±´·´°¥’H
+
+
+
+
+
+
+
+ !*+*+*))(((&&&$%''&%%%%$%%%&$%$#"!
+
+
+
+
+
+
+
+ X³½Ã·ªš©­°±±²²³´³´´²­£•Ž‡ƒˆ—¡¥¦§¦¤¤¤¥¦¨©«®¯°°±±±±°­«ª¬¥™†€{upnmnopquxz{zyzy=-Ce{‡†…‡‡‰‰‹‹Ž”™˜•”‘•C<®©ª³·¾ÃÈËÌÌÉÈÇÆÅÃÀº´®¥›Ž€zvllnqrssuxy~„‰Œ’–›¢©°´µ¶·¸¹·¯šŽ›¤«°³¶´¯¤’–•“n 
+
+
+
+
+
+
+
+
+
+ !$*+****))((&''%%%%%&&%%%%%%$%$$# 
+
+
+
+  ~À¿À¹­•¦¬²²²²²³´´´µ²¯¦™‘‰‚ˆ“˜¢¦¦¦¥¦¦¦¦ª«¯±³´¶¶·¸·¶´¯¬¥’ƒ~zxqnnlkkkloprssssspmfca\[X[[[Y]bju|†Š‰†„‡‰‹Š‹’’’’“•’’“’”’zJ))As¢­ª­³¸¾ÄÈËÌÌÊÈÇÆÅÿ¼µ±¦œ‘‚ztnknprssuuxz~ƒˆŒ“—›¡©®³¶··¸¸º·«“~…‘ž¨­²µ·µ°¤š™–Ž8  
+
+
+
+
+  !"',+******)((('('&&&('&&&%%$%&#$#
+
+
+
+  .ŸÅÿ¸¯”¤¬²´´³´³³³³µ´²«œ’Š€ƒ‰‹’™ž¤¦§¨§§¦§ª­°±³µ·¹º»»¹·¯©›‹€zwuqomjjiikmooopqpppmmklnnmpsx„†‹‘‹ŠŠ‰‰‹’“‘“”“““’‘‘““‘’•‡saOINW`v ¨§«¯³¸¿ÄÈÊËËÉÈÇÅÃþ·³«£˜‹ysokmpqrrtwvx{€†‹“–š ¦­³¶·¸·¸¹º³¥Š{†“¢©¬³·¸µ¯¤  •˜Y 
+
+
+
+
+
+
+!(,+***+,+*)()(')(&'&&&%&&$#&&"# 
+
+
+
+
+ QºÄƽ·²—¢¬±´¶µµµ³²³µ´³­¡—‚‚…†Š•œ¡¥¦¨§§¦¦¨ª¯°±²·ºº¼½½·¬©Ÿ’‹wvsonmkijkmnnnopoooonllmllmotz}…‹ŒŽŒ“–•’““”•““•“‘‘‘Ž‘—˜—˜œ¡¢££¦«®´»ÁÆÉËÌÊÈÇÇÆÄ¿½·°¬ ”ˆ~yrjlmnoqprvwxz„‰Ž’–šŸ¦­²¶¹¹¸¸¸¹¹°˜~~‹š¦ª®´··´¯¡’£¦—˜v 
+
+
+
+
+  "(++++**+)))))('(('&%&%%%$&''&
+
+
+
+
+ {ÂÃŽ»¶– «°´µ¶µ´´³³´¶´¯¤š‘†ƒ†…‡Ž•› £¥¦§¦¥¦§¬¯±µ´¾ÄÁ¿À·«±¬©¢—Šzwtrpmnmnoopqpppooppomnopptw{‚†‹‹‹ŽŽŽ‘”––•–––––˜™—“‘‘‘‘““”••˜šœ¡¦©­²¶½ÂÇÉÊÊÈÇÆÆÅľ¸±¨¢—Š€zrkonqromoqsy{~‚‰Ž”–›ž¢©±¶¹¹¸¹¹ºº¶§}‚‘Ÿ©¬°µ·¶³®œ¦©”Œ4
+
+
+
+
+
+
+ !(*+,,+*+*+)('''')(&&'%%%%&'&%
+
+
+
+   - ÂÄÅ»½¸“ž©­²µ¶´´µ´´µµ´°¨œ“ˆ‚„„‡‹‘—¢¢£¤¤¥£¥¨¬µšPmš»Å¶­¶··³¯¥œ’Šƒzvvsrrssusstrrstqqrtuuvuw|„‡‰ŠŽ’•––—˜™˜™˜™™™™–’Ž‘’”••—™›Ÿ¡¥«°µ¸¿ÆÇÉÊÉÈÆÆÅÄľ¹³ªž•Œz{kOB9ATjrqru{~ƒ‰Ž“–™Ÿ¢§­´·¹¹¸¹»»º² …}‡•£«®±µ·¶²¯™“¨¬ ––V 
+
+
+
+
+
+
+
+ ")++,,+***+)((('()('(''%&&&&&%
+
+
+
+
+  M·ÂÆƹ¿¹–¨®²³´µ¶¶µµµµ´²« —Š‚‚„…„‹“šŸ¢£¤¤¤¤¤¦©´Z")0P¬Æµ­¸º½»¸·´­¥š•‹†}||||zzxxxxvuuvzyyyvvy~ƒ…ˆ‹”—š››œ››››š››—–‘ŽŽ‘””•—šœŸ£¦¬°µº¿ÄÉÊÊÈÈÆÅÅÅÄÁ¼¸±« ˜Œ‚z{_*$Inwx~‚ˆ’–™ž¢¦«²¶¹º¹¹¹º»·­š‰š¤ª­±¶··³¬–•¨¬£˜˜t
+
+
+
+
+
+
+
+
+
+ !)++*+,***)((())(((&&&''''%%'%
+
+ rÅÄÉĹ»˜š¦®²´µµµµ´´´µ´²¬£šƒƒ„ƒ„‡Ž—›Ÿ¢¤£¥¤¢«”*(+)8¨È¶­·º»¼¾½¾½»¸¯ª£Ÿ˜“‘ŽŒŠŠ‡…„„„‚€|yxyy|}‚…‰Œ’—›ŸžŸ¡¢¡££žœ›™–•”“’’•–•˜›ž¡¦«®´¹¾ÂÇÉËËÉÇÆÅÄÃÿ»µ®¦›“Š~wzV,f€†’—™ž£¥ª°µ»¼º¹¹ºº»µ¦}†’ž¦«°´¶¸¹³¬——ª®©›˜: 
+
+ "(++++,*)***+*))())'&&'(''&''&
+
+
+
+
+
+
+  #ÈÅ˺Á¹›š¦­±µµ´´³³³³´·¶¯¦›‘…‚‚„ˆ‘—˜™›ž¡¢£¡ª](*(=µÆ³¬¶¹»½À¿»Â³Ÿ§±¶·´©¨¥¢Ÿœ™—–•’‘ŽŠ…|zx{„ˆ“˜¡¥¦¥¥£¤¤£ žœš˜–••“”–™ššœž¡§«±µ¹¾ÃÆÊËÊÊÈÆÆÄÅÄÁ½¶°§œ‘Œ†{vwY!%k„Š‘•˜œ¢¦¨®´¸¼½»ºº»»º±ž‡‡— ©­°´¶··³ª“š«¯«Ÿœ–W
+ $)*********+*))''))((''(&%&''(
+
+
+
+
+
+
+   :­ÇÈȽ¾Ãº˜¦­±³´¶µ³³³³´··³©”ˆ‚}|Œ”––˜šœŸ ¢¡8"&'L»Á³¬´¸½ÀÁÀŸW?GRap†¯¹µ±®«©¥£¢Ÿœ™–“‡}|}~‚‰–›ž¢¥¦§§¥££££ žœ›š™™˜™šžžžž¡¥ª®³¸½ÂÇÊËËÊÈÇÆÅÄÅÄÁ¿½µ­¦—‰ƒ{uwa'8ˆ•™œ ¤§¬±·½¿¾»º»¼»·«’€Š˜£«®²µ·¸¶±¦«±­¢ž›s
+
+
+
+
+
+
+$)+++++++))*)*,((((((('&''%'('
+
+
+
+
+
+
+
+
+
+ S¿ÄÊɼÂú–£¬¯²´µ¶´²²´¶¸¸´¬¡–‹ƒ|zx{ƒ“”—šš›š ‰  %%S½º°ª°¶½ÁÁǽ]088485H²Â¼º¸·´¯­ª©¤¡Ÿœ˜“Š‰ˆƒ†Œ”›Ÿ£¥¥¦¦§¤¢¢¢¢ ŸŸžœœŸŸ¡ ¡¥¦§ª¯³¹¾ÀÅÈÉËÊÉÇÆÅÅÄÃÅ¿¸whed_x}ttm,E’“–›Ÿ¡¦«¯µº¿À½º¹»¼º´¢‡„œ¥¬±³¶¸·µ°£Ÿ­±¯£ œˆ3
+
+
+
+
+
+
+ %+*)**)*)'(())*'''('&&&&''&''%
+
+
+
+
+
+
+
+ sÆÇÌ˽Âļž’¢¬¯²´µ¶¶´²³µ·¸·°¤™…€~zxx}ˆ“•——– m Z¼²©¦«±¹¼ÃÂ`0234367\ÃÄÂÂÁ¿¾»¸´°­ª¥£¡ ›’•œš˜Ÿ¤¦¨«ª©¨©©§¨¦¥£¤¦§¥§¥§©¨ª­®±·»¿ÄÉÌÐÓÒÐÐÍÈÅÄÃÃþ¼«:!kxrr8+Pae\?}—•™¡¥ª¯µ¹½¿¾½ºº¼¼¹®—…‡•Ÿ§®±µ···µ¯Ÿ¢®²°¦ŸŸO
+
+
+
+
+ '+)))***('()))((())'&&%'''(''&
+
+ 
+
+
+
+ (–ÊÊÌȾľ¥“¢­¯²´µ¶¶µµ´¶¶¸·²§š†€€{vuu{†‹‘’”œVR³¨¡ ¤«²·¿i35465572zÉÄÆÅÅÄÄÿº¸·³®ª±{9BGQs¬©­¯¯°±°°¯®®¯­­¬­®¯±²±³´´¶¸»ÁÆÇÁº´©œˆ…†™·ÊÇÂÄÂÁ·´Ÿ/2tpyS?mtuv|‚f(Vš–›Ÿ¡¥¨­³·»À¿¼¹»¼»³¦ƒ‚Œ—¡ª¯²µ·¹·´«š’¤­³±§Ÿ –l 
+
+
+
+
+
+
+
+
+ '+**)+*))***)))**)('(('&'&)('% 
+ 
+
+
+ <²ÈËÌÅÂĽ¤“ ¬±²´¶µ¶µ´´µ¶¸·´«“„€|xttu}ƒ†ŽŒŽ‘A6žŸ™šž¦¬¸|,311566:;–ÅÂÅÆÅÅÅÅÅÅÄÃÀ¾¹¸ªE-,-+B­µµ··¸¸¸¸¸¸··¶¶¶µµ¶º»¼½¼¿Çȼ fYNG@:62118M¹Å¿½º¯ª#Nqqc"Grosw|…j<œœ £¥¨¬¯³·¼ÁÁ¾¼ºº»¹¬ŸŒ‚„›¥«±³¶·¸·²©˜“¥®²±©¡¡“y. 
+
+
+
+
+ !(*++*))+)*+))))))(()*)('(''&%'
+
+
+
+
+ 
+ \ÁÈÌËÃÂÅÁ»§“ª±´µµ¶¶µµ¶¶·¸¸´¬¡˜‹‚€~zvrruz‚‹Œ‹6{š’”™ž©Œ.)*,./37:F¯ÁÁÃÃÃÄÄÅÆÇÈÈÆÄ¿Ãk43.,,2šÁ¼½¾¿ÀÀÀÀÀÀÁ¿¾¿¿¿ÀÁÂÂÅËÄŸsU<6533332//.0/25R¤À¶²¨§t!gln1?uoty|ƒ‰Š’‘Ÿ ¤§¨©­°±¶º½ÀÀ½»º»º´¦“‡„ˆ’¡ª¯²´µ¶¸¶±¦”—§¯²²¬¡¤˜„Q
+
+
+ !*+***+**)**))*)))'(())('''&'&(
+
+
+ 
+
+
+
+  ƒÅÇÌÊÃÄÇý¬”™¦¯³¶···¶¶···¸º¸°¤™‡ƒ€{xuqpsy}‚‡‰‰/F‰”™Ÿ9'&(,,/56WÀÀÁÃÃÂÂÃÆÇÇÇÆÆÅÏŠ8630163xÇÃÅÆÄÄÆÆÆÆÆÅÇÈÇÇÇÉÈÉÏÉd=54642-,'')),020243A•µ§ œXBpmL.tuv|†‹Ž’˜¡¦¦ª«««¬¯³¸½ÀÀ¿½ºº»¸¯œŠ„†Žš¥¬°²´µ¶·µ°£’œ§®´´°¢¥ Žj
+
+
+
+ !$*.+,+,,,*)****)))**)**(()('('%'
+
+
+
+ 
+   , ÅÈÍËÃÇÈľ¬”˜£­±µ·¸·······º¹´¨œ“‹ˆ…|ytpnqvy~€‚+!!r‡†Ž˜e !$%).45nÁ½¾ÁÃÃÂÃÇÇÆÆÅÃ΢?5210148S¼ÈÈÉÈÉÉÈÇÈÉÇÈÉÈÊÊÊÌÑ­f=6651++>TgtthR4-42.00.‡®‘‡:"fmi'axw~…‹•–œ ¤§©ª«ª««¬°µ¼ÀÂÁ¿¼»»¼¶©”ˆ†‰’Ÿ©®°²´´µ¸¶°£“¨±´´¯¥¦¥“y-
+
+
+
+
+
+
+
+
+  33-,,,++*+*+*)*)))++**)(()&((&'
+
+
+ 
+
+
+  =³ÈÊÍÉÅÉÈĽ®—™¡©°´·¸¸¸·¶··¸¹¹¶­ž”Œ‰‡†‚|wsonpruvx+,F=†ƒ‚) %*/3À»¼ÀÁÃÂÃÆÆÆÆĄ̈F4/..-.129ŸÌÈÊÊÊËÊÉÊÊÊÉÉÉÊÉÎÇ‚F523/*<d•°½ÃÁÁÁ½¥d,*,+&q¯¢—Šn"AnoM8~z‰”—œ ¤¦§©ª¨§¨ª«®´¹½ÁÂÁ½»»¼¹°¡††‹—¤¬¯¯±³³¸¹¶°Ÿ’ž©²´´±§¥¤Ÿ‰G
+
+
+
++81,,++**+))*++)()***)((('&(('( !% 
+
+
+
+
+
+ RÀÉÉÍÇÃÉÈľ¯›–Ÿ§®³µ·¹¸··¶·¸¸¸¶¯Ÿ”Ž‹‰ˆ†€{uponmoor;%jlƒ]!%+3”¾»¼½¾ÀÂÃÅÅÄÃ˳K50/-++++,+rÍÉËËËËÊÊËÌÌÌËÉÈи_5824,/i¯ÇÈÄÁÀ¿½»»¿¿ƒ-(&k±¤™‰S]jn0]„„‹‘—š £¥¦§¨©©§¦§¨¬±µ¼¿ÁÁ¿¼»½¼¸«˜‹†ˆœ¨¯°°±´¶¸¸´®›’¡«²³µ²ª¤§¢”g
+ !"+38--,++++**+-+()()*++)((''(((( !$'),/
+
+
+   rÇÉÌËÄÆÈǽ°œ–ž¦­²µ·¹¸··¶···¸µ¯¤™“ŠŒŠ„~zsqommmqU oB?ˆ01%1¡¾¼½¿¿ÀÂÄÆÄÁĸS/110*))))+*H¼ÍËËËËÊËËÌÌËÊÈѱM4231+K¡ÉÆÄÃÄÃÂÁ¿¼¸¸¸¾…1d®¤œŽ‚}4.nmg  x‹—› ¤¦¦¦¦§¦¦¤¥§©®±¸¼ÀÂÁ½»¼¾»´¤‘‰‡‹”¢­°°±³¶¸¹·³«—”£¬±³µ´¬¥ª¦žƒ<
+
+
+
+
+
+
+  #*.69-,,,+*)*)*+**)))*,+())))()'"&,-/005&
+  *—ÈËÎÌÅÈÉÇÁº°œ”›¦­±´·¹¹¸···¸¸¹¸±©œ“Ž‰~zvsmnnmj%XpolD[3¢¸¶¾ÀÀÀÀÂÃÃÅÀ_3522-9I'*+./ŽÐÈÉÉËËÊÊÌËÊÇͳO3311)`¾ÌÄÄÅÄÄÂÀ¾¼¸µ´±¯¯”¨¥Ÿ–‹~m%Dwu[0‰Œ‘™ ¢¤¥¦¦¦¥£¤¤¦¨­°³¸¾ÁÃÀ½»½¾¹¬™Š‰‰˜¥­±°²´¶·¹·°§–—¦­±´µ´­¦©¨£•^
+
+ !! #%+,-:7,-,**)))*,*)*))*))***))((' $)+-0101*
+
+
+
+
+ ?²ÆÍÐÍÅÊÉÅ¿¹¯ž”—£«¯²¶¸¹¹·¸¸¹¹¸¸´« •‘“”‘Žˆ‚€|wuqomp::yO8=xc5¢¬®´¹»»»½¿ÀÊ|4:6479—σ(,+-.XÆÇÈÉÊÉÉÉËÊÈÈÆ]3400(eÄÆÁÂÂÂÁ¿¿¼¹·´²°¯®¬¬¥Ÿš’‡‚eS…ƒT;’˜Ÿ¡¢¤¤¤£¤¢¢¢£¥©¬±µ»ÁÃþ»¼½¼²¤‘‹‹“ž§°°±³µ¶¸¸µ¯¥•™§¯²µ¶´¯¨©¬¥•t,
+
+ ! ").-,181,+*)**+++)*+*(()(),)(*('( ""$'*+..1200.
+
+ ^¿ÅÍÐËÆÌÉþ¸± •–¡ª°´µ·¹¹¸¸¸¹ºº¸µ¬£™‘’—˜”‰ƒ€}zwrnpO%kv,CŒi2š¤§­²´µ¸¸¶Àš866675|ÇÄ­7../17¢ËÇÉÈÈÈÇÉÈÅË‹4402/I»ÂÀÁ¿»¹¸¶¶´³°¯®««©§¦¢›–Œ‰eX“Y:˜˜›Ÿ ¢£¤£¡¡ ŸŸ¡¤¥©®²·½ÁÃÁ¼»¾¾¹¬šŒ‹‹Œ˜£«°°²³¶¸ºº´¯¡’›¦®³µµ´±ª¨°ªš…P
+
+
+
+  !!&..,+49/++**++*+)***)()((+*('(()!%()))+.///13331  ÆÈÎÐÊÈËÇþº´¤—•Ÿª°µ··¸¸¸¸¸¹º¼»¸¯§•”š™“ŽŠ…ƒ€}xstaWyalˆm.”ž¡¦¬®±²³¹´I*0-/.Wº½¼¿Y-2013pÍÇÇÇÇÆÅÆÆÈÂ[21041ü¾½»¶µ´²²°®¬«¨§§¤¡žš”“‹Œj R™“g0”Ÿ ¢¢¢£¤¢ ŸŸž ¢¥«°µº¾ÁÁ¾¼¼¾¼²£”‹‹Š›¦®²°²³¶¸º¸µ­œ‘›§¯´µµ·³­©±­ŸŠt&
+
+
+ ##$#!#+,./-.65--+*+*)****)**)*)))))))* '''&(0./20021/445558 *žÈÊÎÎÉÉËƽ»¶¥—“š¥®´¸¸¸¸¸··¹¼¼»¸³ª ™‘—š˜”Œ†‚zvn$Auu=6z|p/“¡£¦ª¬ª·u%)*)+2™º¶·¾€*1//1I¹ÈÅÆÅÃÃÂÂƬ>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.,,-,++*)))))()**)*<<=><?=BD@@>AB@CEEFEHHGO
+ 4™ºÄÎÏÍÌÍÊÆÂÀ¾¿¾·¦›“š¢¨®²³¶¹¹¸¸¸º¼¾¾¼¸®£™–““–˜˜™—–’4&}Š‹‡>‡™——˜‰&#yŒ‡†‡Š‹6-‹‘Š†}.,{‚ƒ‰”••H.--/-$fŒ‰†3"{“—Ÿ+ FrxZ&#"$$²²¸ÀÁ½°ŽŽ‘— ª°³³²²´¸»½»·¬ Ž’žª°³´´·¸º¹·®®²±©•ˆy  ()*+,......--,-..-.22,,,+-+****)*)()(();<<?BDABEGDEIJFFGIKIILMT4  e±ºÊÏÏÍÌÎÊÅÂÀÀÀ¿¹ªž‘—¢¨®°³¶·¸¸¸¹º»¼¿½¹²¨œ—“‘‘‘’’”•––˜’.)‘“‹–˜š›™!q‡„„„‰lU†„ƒ~}€FC}}‚„‹ŽŽŽŽ‘’“•sm‡„0$~–™œ§t"$#!"$%_Àµ¯´¼Á¼µ§“ŒŒŽ“›¥­²´³²³¶¹¼¾»µª—Œ” «°³´´·ºº¹¹±ª¯²¯†ŠG  ')))(*.--.,-/,-013.063,+*,-*)*+*****)**==>DFEEFKLLJMNNOKNLMILR]C  .œ³½ÎÐÎÌÍÌÈÄÂÀÀÀÀ»®¡’“Ÿ§­°µ¸ºº¹¸¹º»¼¾½ºµ­¢™•“‘‘’“““”•••tH'-†“˜›šš™™~k‚‚ƒ‡?#xƒ€{{zkJ‚‚‡ŠŠ‰Š’•™˜”–tnŠ‡+&ƒ—› Ÿ¡b ""!!"#T¸¸®±»Á¿¸«›Œ—Ÿ¨±µ´³³µ¸»½¾¸°¤•Ž—¡«¯±´´¶¹¹ºº³ªª°±¤‹‹k
+ *()((&).0/.-././12:82270,++++++++*))*+**BBFIJJKKNOQLNOOPNOMLOQPSD \¹°ÁÍÏÌËÎÌÆÃÁÁÁÁÁ½±¤•’š¥¬²¶¹»»¹ºº»¼¼¼»ºµ°§ž˜”‘“––—•’“••œˆ}—“‘”™œœ›˜–‘{"`„€€„r!'M‚}zx|OG‡ˆŠ‹Œ“•–•”\!u‰†t#+…–œŸšŸV!""$#f½¹¯®µ¿Â¼²¤”ŽŽ”œ¥°³¶´³³µ¹¼¾¾¸­¡”™£ª¯±±³µ¸º»¼·¬ª¯²¬”†ƒ. #*((('(*.0//..////0<N6-47-*+**+**++)(*,*(ILJIKLJJLOSRQOOMMNOLONLIH #–¿´ÅÌÍÊÌÏËÇÂÁÂÁÂÃÁ´¦˜“˜¡©°µ¹ºº»¼»»¼½¾½»·²«¢š••–˜———”‘’•–— ›˜–˜šœŸœ˜•y"W‡€~„D uƒuncYNJCA=81-*h{z|{xuy76rŽŽ’“–˜š˜š™s#$|‹‰r,ˆ—™œž Ÿ] !"$";‹Ã¾²¯³½ÁÁ¸ªœ“˜¡­³´¶´³´·º¾À½µª‘š¤ª°°±´µ¸º¼»¹®ª®²®‡‹O  !'''((**-../.-//.,/1KK-.78**+)**)*+*))***LOPPQRQONOPSSRSPLNONJKNMM1
+ YÀ¸¶ÆËÉÊÎÏËÆÂÁÁÁÃÅƺ«š“•œ¦¬²¶¸º»¼¼»»»½¾½º´¬¦Ÿ˜•–—™š™—”‘’•—˜™œŸŸ™–—˜›™–’|%Jƒ~€v#D‡ƒ‚…ˆŒŠ‰ˆˆ‡†ƒ~|c7{ywxvtvv/ R„˜ž  ¢£žŠW,ˆŒŒk*‡œœŸ  œœo8!*I€²Ä¼·¯²»ÁÁ¼²¢’ŽŽ‘•›§¯´´µ´´³·¼À¿½²¥–’¤ª¯±´µ¶¸º»»¹±«­±³©‡q &''(''(+.../..-..-/15B7,/63,+*)()**,*(*+*PSSPSXTPSMPPRRSPNNLKMOQROA  šÆ´ºÊÍÉËÏÎËÆÂÀÂÃÄÆǽ¬ž”•š¢©¯³¸º»»¼»»»»½¼»·°¨¢œ—––˜›››–“’“””’“˜žœ—–••”’'A}ƒWn‡ƒ…†„†‰ˆ‹‹‡…‚~{€AW}xxywy€x1%=[nxvfG*WŒ‰e)‡Ÿ £¢ œ™–š“v`Ycw—³¼¼»¹²²·À¿¶©—Ž‘”˜¡¬³³²´µ´µ¹¿¿¿»¯Ÿ’“ž¦«¯³´·¹º¼»»»µ©«¯²¯—‡‡= '(''&((*.../00-,-./1034-,05/**+*,,++*)***WWWY]^[YXTXSZ]ZURRSQPRWTMK VÅó½ÌÍÇËÑÍÇÂÁÁÃÄÅÇÉÀ° “”™ §¬±·º»»»»»¼¼¼¼»¸³¬£˜––™œžžœ—““’‘’’˜™™—”‘Žˆ†„?#7}|/?‡ƒ‡‰‰ˆ‡‰Ž‹Š‡ƒ}||t ,y}{}ƒƒ‰‚=E’‰ˆŒh&„žŸ Ÿœš˜——Ÿ¢¤¨ª­±µ»»ºµ±·¾ÂÁ¹­ž”‘’–œ¦¯´´´´´µ¶»ÀÀ¾¸©›•¡¦¬±³µ·º½½¼¼¼¹®®°±¯ŸŠˆd&))''()(,/.-010/.-/110/1/,-26,)(,..-,+***)UUUVXZ[ZXYYTY`^ZWY[YR\[YVZ$
+ —Ì¿³¾ÍÊÅÎÑÌÄÂÁÃÄÇÈÊËĵ¤•“–¥¬³¹º»¼¼»»¼½¾¿Àºµ¯¥ ›—–™œžŸ¡—“‘“•——•ƒ€~{vpmfj€‡B-$g‡‰Š‰ŠŠŠŒŒ‡„~{zULƒ€†‹ŠŒ““T:’ŽŠ‹i zŸš˜˜–—šœž¡¥¨¬¶º½¼¹µ´¼Â¼±¢–“‘”š¡«³µµµ´³´¹¾¿¿¼³¤•‹˜¢©®±³¶¹»»»¼¾½º±®°±°ª’‡|)*+*)**))....-//...020/-.//,,43-++,,,,+***)NOQQORRRSPQNMSPNQPQSSWVWVU5
+ N¿È¾²ÁËÇÆÐÐËÄÂÂÄÆÊÊÌÏƹ§—’”›¡¨°·¹¼½¼»»»¼¿ÁÀ¿¹²¨¢Ÿ›˜——›ž¡ œ—”“’’””’‘““Žˆƒ~~ƒ‹”™ž–‹€qjŠŒŽŽŽŽŠ‰†ƒ€~~4#r‡Š’“””—œy0?ŒŽ‰‹‰‹`({ š˜˜—–˜™œ¡¥¨®µ¼ÁÀ¼¸µ¹ÀÁ½³©›“’’•œ¦°¶¶´´´³µ»ÀÁ¿º­¡’Œ›£¬±²µµ·»»»¼¾½º²¯°±±¯Ÿ‹†I ++***))*./--...//.2510.../-+-64.,(*,,++*)(PRWVPOQTXTTRQQLLQOOOSVSTTSK ’ÊÆ»´ÅÉÅÇÒÐÉÃÃÅÇËÌÌÎÐ̾«™“–™Ÿ¦¯³·º»»¼»º¼¾ÀÀÀ¼¸®¦¡š——˜œžŸœ™–”•”•”‘Š‰ˆˆ‡††‰Ž”šœž£¥¥£¢ž—–•––”“”‘Œ‰‡„€‚x][\[Z]|‘“—˜šžž Ÿ¥š_&U’ˆ‰ˆ‡‰hH[vŸ žš–—–—˜šŸ£¦¬µ»ÂÃÀ¼¸¸ÀÂÀ¶¬ “‘‘’•™¡­´·¶´´²´·½ÁÁ¼³¨šŽŒ“ž§®²´µ·¹º½¾½½¼»³¯°²³²¨‘‡i,0-,,+++-//.-.///0/254//...-+-/71.+*++++*+*TXZXVQRYb^[[ZYXV\YVUZ][XW[_  TÁÆ÷·ÈÈÃÉÑÏÇÄÆËÏÐÎÎÎÑÐðš””—›¢«±µ¸»½½¼¼¼¾¿À¾¼º³ª¥ ˜—˜œž›š˜•”’“””’‹Š‰‰‹Ž’–˜›œŸ¡¤¦¦¦¥ œœ›™—˜–—–’ŒŒ‹‹‹ŠŠ“•“–šš—™œž¡£¦¦¨§¦§©’`2D{•Œ‡ˆˆ‰‹Œ‘Ÿ¤¤¦£ œ—”•–™ ¢§«²ºÁÅÅÀ»º¿ÂÁ»°¢“‘““—Ÿ©±¶¸¶´³³µ»À¿¸®£”– ©°´¶·¹º¼¾¾½½¾¾µ°±³µ³¯›…D62/-,--.00/..//...04511.-.--,+151-+*++*+--TUTTTQPPVZUUVVVWWUWY\ZZZ[[[)  "“ÍÈÁ´»ËÆÂËÑÌÇÇÉÎÒÒÏÍÏÓÒƵ —”“˜ž¨®´¹»½»¼»¼¿¿À¾½»¸±«£™˜–™Ÿ ›˜——“‘’•”‘‘Ž‘““’“•™œœžš™›Ÿ£¦ª­­©¨¤¥¦¢Ÿžš”‘’’””•š›Ÿ££¥¨ª«­®­®­­¬««¯Ÿ‚hT>638Kd…™“‹‹‰ŠŒ‘”šŸ  ›–••–˜› ¢¥¬µ»¿Åľ»¾Â½±£—‘’‘“”›¥¯µ¸·µ³³´·¼ÀÀ¾¶¬Ÿ‘Š™¡ª±´·¸¹»¼½¾¾¾¾¾µ°±³µµ³¤Š‰Y2/+-.++.00.//00/-./1420/...,+,/43.+****+-,SQRSRQPWWWRQQTVUSRTUWVSUTTW9
+ PÁÈÈ¿²¿ËÄÁËÒÌÉËÏÒ×ÒÏÍÎÑÔʸ¡—”“”˜¥­²·»¼¼»»¼¿¿ÀÀ¿¾º´®§¢›˜—šœžŸž›™™˜–’’–˜œœŸ    ŸŸž››ž £§ª°³¯¯­©¦¢—““••““””—šœŸ£¥©­¯°±²³´µµµ¶¶µ´³®®­«¨¢œœ ¡•ŽŒ‹Ž‘‘“•˜˜™šš˜’’“”—š£¨¬³»ÁÄÄ¿¼½Âý·§™“’‘’‘”™¡¬´¸·¶¶µ´¶º¿ÂÀ¼²¦–Ž‹‘œ¤­°µ·¹º¼½¾¾¾¾½¼·±±³µ·¶«‰q4.,--*).///0/.//../07600.-..,+-/44,)**+++*[Y]ZT[[^eaZ]YX[YW\XXXW[XYVSG”ÎÈȺ²ÃÈÂÂÌÒËËÏÓØØÒËÌÍÑÔË»¤—”•”•¡«±¶¹¼½½»½¾¿ÀÁÀ¾»¹³¬§¡ž›š™šœŸžžœš™™”Ž“—œ £¥§¦¥¦£¢¢££§§¤¤¦¨«­¬­¬§¢™‘‘““•—–™œž¢¦ª®²¶¸¸¸¹º¹º¼»¼¿¼¸·µ´°­©§¤£ œ˜“Œ‹Œ‹Š‹‹Ž‘“””’ŽŽ’•˜œ¢¨®³ºÀÆÇÄÁ¾¿Âľµ«”’”’‘“–œ¥°·¸¶µµ´µ¹¾ÁÁ¾¸¯ Š‹•Ÿ§®´·¸¹º¼¾½½¼»»»¶°±³µ·¶²™Š‚E20,)++.0/./../../.0<C4/00.-,,,,05.++,++*)UVUSTWUV[[X\VXZ[]_`acac_b^YT LÃÌËǶµÅÈÃÃËÔÐÏÕÙÚÖÏÊÊÍÒÕ;©˜””“–ž¨®´¹»¼¼¼¼½ÀÁÁÁÀ½º¶±«¤ ŸœœŸžš˜˜•”‘‘•£¥©ª««ª¨ª©ª¬¬«ªª©§¥¢ š—“‘ŽŽ”–˜š¡§¬³¸¼ÂÃÁÁÁÃÂÁÃÂÂÀ½»º¹¸¶²®­«§¢—Œ‰ˆ‡†„†ˆŠ‹ŒŒ‹‰’•›¡¦¬²¹¿ÅÉÆÁ¿¿Â¿·­ •“””“’“˜ ©³¸¸¶´³³¶¼ÁÂÀ¼´©˜‹‡Œ˜¢¨°µ¸¸¹¹»½¼¼»º»¼¶°³´¶·¸³¤‹c10-**,.0/////....-.2<512/./-,+*-23,*,+*()MKPOPSRQNRONMQRTVTUUWWXZ[WTS% ÐËÌð¶ÆÆÃÄÏÔÔÕÖØÔÏËÇÈÍ×ØÐÁ«™”•““›£­´¸»¼½½½½ÀÀÁÁÁÀ½¹µ²ª¤¡ŸŸžŸ¡ ŸŸžœ˜—–•’‘’“—› ¤§ª­«ª¨§¨ª©©ª§¥¤ ›–’ŒŽ“˜œ¢¤«°µ»¿ÂÅÈÈÇÆÅÆÄÃÂÂÁ¿¾¾½½»»¹¶³¯ª£•‰‡„„„……†ˆ†…†‰‰Š‹Ž”›¢¥¬²·¼ÂÅÆÃÀÀÄÄÀº°¢—‘“•’“”š§¯¶·¶´³²µºÀÃÂÀº±¤’‡…š¥«±µ··¹º¼¼»º¹¹»¼´°³µ·¹º·¬–Žy80.+**///..0.---,,-.02351-/.,++-.53--+)*,MOPSOQRSQSSXQSSRSSTRPRSSTSSO>C¾ÍÍÌÀ¬¸ÉÆÃÄÌÔÖ×ÕÔÐËÈÇÉÌÖÚÒ“‘‘”ž¨°µº¼½¾¾¾¾¿ÀÁÁ¿¼¸µ¯¨¤¡  Ÿ ¡¡¡¡ ›š™™—••—˜™™šœŸ£§¨¥££¢¥¥¥¥¢Ÿ›—”ŽŒŽ•—¢¦¬°¸½¿ÂÄÅÆÅÇÇÆÅÄÂÂÂÀ¾½¼º»¼½¾»·´¯¨£œ’ˆ…„‚‚‚„„„††…‡ˆˆ‹‘—¢§«°¶¼ÁÅÇÄÀÁÄƹ±¥™“’•“‘’˜£®´·¶µ³²´¸¾ÂÅÿ¸­Ÿ‘‡…‘§¯²´¶¸º¼½½º¹¹¹¼¼µ±³´·¸¹¸°š‹†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 0000000000..01673bf6d5
--- /dev/null
+++ b/libs/ultrahdr/tests/data/raw_p010_image.p010
Binary files 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 0000000000..c043da6423
--- /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=<HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠssssssssssssssssssssssssssssssssssssssssssssssssssssssssv€krst˜œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJFN@FHG„ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‰‰‡ŠNQOPHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttr~luww™œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENBIKJ‡‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰‰ŠŠ‡ˆJKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠssssssssssssssssssssssssssssssssssssssssssssssssssssssssv€krst˜œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHJFN@FHG„ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ‰‰‡ŠNQOPHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvwmuwxœ œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIENAGII†‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹‹Œ‡…DB=<HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxv€nvyzž¡œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEOCJLKˆƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ’‘Œ‹KJEDHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuq|ktwx›žœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDODLML‰ƒ„‚†KNMNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrmyhrtt–˜œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDPELMJ†ˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆˆ}ACAAHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠttttttttttttttttttttttttttttttttttttttttttttttttttttttttq}luwu•–œœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFDPEKJE€††††††††††††††††††††††††††††††††††††††††††††††††††††††††š™“NLFEHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ||||||||||||||||||||||||||||||||||||||||||||||||||||||||z‡u~~{ššœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFEPDIF@z‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡‡†‡†ŠNRQRHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„„ƒ~†…Ÿžœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHFEPDHD=vJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ>DL\-=EKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHGJllnweqoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHMGAAGKMKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKK<Qhpkhq|oooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHKGDEHJIGFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBWmuoinuoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHIIIGECMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMFYowrkknoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEIMMIEDDFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFCShsrnkloooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHCJONGDHNHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHEQ`kponmoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHCJOKDFR_GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGZ^cinpoooooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHDKNGAH^r””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””””€zsnnonmoooooooooooooooooooooooooooooooooooooooooooooooomoqnlnv}ƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒƒ‚„†‰ŠŠ‰‰ŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠŠ‰‹‹‰‡Œ–Ÿœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœœ›KIFKFKHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHDJLE>Ie~••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••••Ÿ’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 0000000000..7c2d076992
--- /dev/null
+++ b/libs/ultrahdr/tests/gainmapmath_test.cpp
@@ -0,0 +1,1359 @@
+/*
+ * 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 <cmath>
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <ultrahdr/gainmapmath.h>
+
+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<float>(y) / 255.0f,
+ (static_cast<float>(u) - 128.0f) / 255.0f,
+ (static_cast<float>(v) - 128.0f) / 255.0f }}};
+ }
+
+ Color P010(uint16_t y, uint16_t u, uint16_t v) {
+ return {{{ (static_cast<float>(y) - 64.0f) / 876.0f,
+ (static_cast<float>(u) - 64.0f) / 896.0f - 0.5f,
+ (static_cast<float>(v) - 64.0f) / 896.0f - 0.5f }}};
+ }
+
+ float Map(uint8_t e) {
+ return static_cast<float>(e) / 255.0f;
+ }
+
+ Color ColorMin(Color e1, Color e2) {
+ return {{{ fmin(e1.r, e2.r), fmin(e1.g, e2.g), fmin(e1.b, e2.b) }}};
+ }
+
+ Color ColorMax(Color e1, Color e2) {
+ return {{{ fmax(e1.r, e2.r), fmax(e1.g, e2.g), fmax(e1.b, e2.b) }}};
+ }
+
+ Color RgbBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
+ Color RgbWhite() { return {{{ 1.0f, 1.0f, 1.0f }}}; }
+
+ Color RgbRed() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
+ Color RgbGreen() { return {{{ 0.0f, 1.0f, 0.0f }}}; }
+ Color RgbBlue() { return {{{ 0.0f, 0.0f, 1.0f }}}; }
+
+ Color YuvBlack() { return {{{ 0.0f, 0.0f, 0.0f }}}; }
+ Color YuvWhite() { return {{{ 1.0f, 0.0f, 0.0f }}}; }
+
+ Color SrgbYuvRed() { return {{{ 0.2126f, -0.11457f, 0.5f }}}; }
+ Color SrgbYuvGreen() { return {{{ 0.7152f, -0.38543f, -0.45415f }}}; }
+ Color SrgbYuvBlue() { return {{{ 0.0722f, 0.5f, -0.04585f }}}; }
+
+ Color P3YuvRed() { return {{{ 0.299f, -0.16874f, 0.5f }}}; }
+ Color P3YuvGreen() { return {{{ 0.587f, -0.33126f, -0.41869f }}}; }
+ Color P3YuvBlue() { return {{{ 0.114f, 0.5f, -0.08131f }}}; }
+
+ Color Bt2100YuvRed() { return {{{ 0.2627f, -0.13963f, 0.5f }}}; }
+ Color Bt2100YuvGreen() { return {{{ 0.6780f, -0.36037f, -0.45979f }}}; }
+ Color Bt2100YuvBlue() { return {{{ 0.0593f, 0.5f, -0.04021f }}}; }
+
+ float SrgbYuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
+ Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
+ Color rgb = srgbInvOetf(rgb_gamma);
+ float luminance_scaled = luminanceFn(rgb);
+ return luminance_scaled * kSdrWhiteNits;
+ }
+
+ float P3YuvToLuminance(Color yuv_gamma, ColorCalculationFn luminanceFn) {
+ Color rgb_gamma = p3YuvToRgb(yuv_gamma);
+ Color rgb = srgbInvOetf(rgb_gamma);
+ float luminance_scaled = luminanceFn(rgb);
+ return luminance_scaled * kSdrWhiteNits;
+ }
+
+ float Bt2100YuvToLuminance(Color yuv_gamma, ColorTransformFn hdrInvOetf,
+ ColorTransformFn gamutConversionFn, ColorCalculationFn luminanceFn,
+ float scale_factor) {
+ Color rgb_gamma = bt2100YuvToRgb(yuv_gamma);
+ Color rgb = hdrInvOetf(rgb_gamma);
+ rgb = gamutConversionFn(rgb);
+ float luminance_scaled = luminanceFn(rgb);
+ return luminance_scaled * scale_factor;
+ }
+
+ Color Recover(Color yuv_gamma, float gain, ultrahdr_metadata_ptr metadata) {
+ Color rgb_gamma = srgbYuvToRgb(yuv_gamma);
+ Color rgb = srgbInvOetf(rgb_gamma);
+ return applyGain(rgb, gain, metadata);
+ }
+
+ jpegr_uncompressed_struct Yuv420Image() {
+ static uint8_t pixels[] = {
+ // Y
+ 0x00, 0x10, 0x20, 0x30,
+ 0x01, 0x11, 0x21, 0x31,
+ 0x02, 0x12, 0x22, 0x32,
+ 0x03, 0x13, 0x23, 0x33,
+ // U
+ 0xA0, 0xA1,
+ 0xA2, 0xA3,
+ // V
+ 0xB0, 0xB1,
+ 0xB2, 0xB3,
+ };
+ return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 2 };
+ }
+
+ Color (*Yuv420Colors())[4] {
+ static Color colors[4][4] = {
+ {
+ Yuv420(0x00, 0xA0, 0xB0), Yuv420(0x10, 0xA0, 0xB0),
+ Yuv420(0x20, 0xA1, 0xB1), Yuv420(0x30, 0xA1, 0xB1),
+ }, {
+ Yuv420(0x01, 0xA0, 0xB0), Yuv420(0x11, 0xA0, 0xB0),
+ Yuv420(0x21, 0xA1, 0xB1), Yuv420(0x31, 0xA1, 0xB1),
+ }, {
+ Yuv420(0x02, 0xA2, 0xB2), Yuv420(0x12, 0xA2, 0xB2),
+ Yuv420(0x22, 0xA3, 0xB3), Yuv420(0x32, 0xA3, 0xB3),
+ }, {
+ Yuv420(0x03, 0xA2, 0xB2), Yuv420(0x13, 0xA2, 0xB2),
+ Yuv420(0x23, 0xA3, 0xB3), Yuv420(0x33, 0xA3, 0xB3),
+ },
+ };
+ return colors;
+ }
+
+ jpegr_uncompressed_struct P010Image() {
+ static uint16_t pixels[] = {
+ // Y
+ 0x00 << 6, 0x10 << 6, 0x20 << 6, 0x30 << 6,
+ 0x01 << 6, 0x11 << 6, 0x21 << 6, 0x31 << 6,
+ 0x02 << 6, 0x12 << 6, 0x22 << 6, 0x32 << 6,
+ 0x03 << 6, 0x13 << 6, 0x23 << 6, 0x33 << 6,
+ // UV
+ 0xA0 << 6, 0xB0 << 6, 0xA1 << 6, 0xB1 << 6,
+ 0xA2 << 6, 0xB2 << 6, 0xA3 << 6, 0xB3 << 6,
+ };
+ return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_BT709, pixels + 16, 4, 4 };
+ }
+
+ Color (*P010Colors())[4] {
+ static Color colors[4][4] = {
+ {
+ P010(0x00, 0xA0, 0xB0), P010(0x10, 0xA0, 0xB0),
+ P010(0x20, 0xA1, 0xB1), P010(0x30, 0xA1, 0xB1),
+ }, {
+ P010(0x01, 0xA0, 0xB0), P010(0x11, 0xA0, 0xB0),
+ P010(0x21, 0xA1, 0xB1), P010(0x31, 0xA1, 0xB1),
+ }, {
+ P010(0x02, 0xA2, 0xB2), P010(0x12, 0xA2, 0xB2),
+ P010(0x22, 0xA3, 0xB3), P010(0x32, 0xA3, 0xB3),
+ }, {
+ P010(0x03, 0xA2, 0xB2), P010(0x13, 0xA2, 0xB2),
+ P010(0x23, 0xA3, 0xB3), P010(0x33, 0xA3, 0xB3),
+ },
+ };
+ return colors;
+ }
+
+ jpegr_uncompressed_struct MapImage() {
+ static uint8_t pixels[] = {
+ 0x00, 0x10, 0x20, 0x30,
+ 0x01, 0x11, 0x21, 0x31,
+ 0x02, 0x12, 0x22, 0x32,
+ 0x03, 0x13, 0x23, 0x33,
+ };
+ return { pixels, 4, 4, ULTRAHDR_COLORGAMUT_UNSPECIFIED };
+ }
+
+ float (*MapValues())[4] {
+ static float values[4][4] = {
+ {
+ Map(0x00), Map(0x10), Map(0x20), Map(0x30),
+ }, {
+ Map(0x01), Map(0x11), Map(0x21), Map(0x31),
+ }, {
+ Map(0x02), Map(0x12), Map(0x22), Map(0x32),
+ }, {
+ Map(0x03), Map(0x13), Map(0x23), Map(0x33),
+ },
+ };
+ return values;
+ }
+
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+};
+
+GainMapMathTest::GainMapMathTest() {}
+GainMapMathTest::~GainMapMathTest() {}
+
+void GainMapMathTest::SetUp() {}
+void GainMapMathTest::TearDown() {}
+
+#define EXPECT_RGB_EQ(e1, e2) \
+ EXPECT_FLOAT_EQ((e1).r, (e2).r); \
+ EXPECT_FLOAT_EQ((e1).g, (e2).g); \
+ EXPECT_FLOAT_EQ((e1).b, (e2).b)
+
+#define EXPECT_RGB_NEAR(e1, e2) \
+ EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon())
+
+#define EXPECT_RGB_CLOSE(e1, e2) \
+ EXPECT_NEAR((e1).r, (e2).r, ComparisonEpsilon() * 10.0f); \
+ EXPECT_NEAR((e1).g, (e2).g, ComparisonEpsilon() * 10.0f); \
+ EXPECT_NEAR((e1).b, (e2).b, ComparisonEpsilon() * 10.0f)
+
+#define EXPECT_YUV_EQ(e1, e2) \
+ EXPECT_FLOAT_EQ((e1).y, (e2).y); \
+ EXPECT_FLOAT_EQ((e1).u, (e2).u); \
+ EXPECT_FLOAT_EQ((e1).v, (e2).v)
+
+#define EXPECT_YUV_NEAR(e1, e2) \
+ EXPECT_NEAR((e1).y, (e2).y, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).u, (e2).u, ComparisonEpsilon()); \
+ EXPECT_NEAR((e1).v, (e2).v, ComparisonEpsilon())
+
+#define EXPECT_YUV_BETWEEN(e, min, max) \
+ EXPECT_THAT((e).y, testing::AllOf(testing::Ge((min).y), testing::Le((max).y))); \
+ EXPECT_THAT((e).u, testing::AllOf(testing::Ge((min).u), testing::Le((max).u))); \
+ EXPECT_THAT((e).v, testing::AllOf(testing::Ge((min).v), testing::Le((max).v)))
+
+// TODO: a bunch of these tests can be parameterized.
+
+TEST_F(GainMapMathTest, ColorConstruct) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ EXPECT_FLOAT_EQ(e1.r, 0.1f);
+ EXPECT_FLOAT_EQ(e1.g, 0.2f);
+ EXPECT_FLOAT_EQ(e1.b, 0.3f);
+
+ EXPECT_FLOAT_EQ(e1.y, 0.1f);
+ EXPECT_FLOAT_EQ(e1.u, 0.2f);
+ EXPECT_FLOAT_EQ(e1.v, 0.3f);
+}
+
+TEST_F(GainMapMathTest, ColorAddColor) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 + e1;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
+
+ e2 += e1;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 3.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 3.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 3.0f);
+}
+
+TEST_F(GainMapMathTest, ColorAddFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 + 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r + 0.1f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g + 0.1f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b + 0.1f);
+
+ e2 += 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r + 0.2f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g + 0.2f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b + 0.2f);
+}
+
+TEST_F(GainMapMathTest, ColorSubtractColor) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 - e1;
+ EXPECT_FLOAT_EQ(e2.r, 0.0f);
+ EXPECT_FLOAT_EQ(e2.g, 0.0f);
+ EXPECT_FLOAT_EQ(e2.b, 0.0f);
+
+ e2 -= e1;
+ EXPECT_FLOAT_EQ(e2.r, -e1.r);
+ EXPECT_FLOAT_EQ(e2.g, -e1.g);
+ EXPECT_FLOAT_EQ(e2.b, -e1.b);
+}
+
+TEST_F(GainMapMathTest, ColorSubtractFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 - 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r - 0.1f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g - 0.1f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b - 0.1f);
+
+ e2 -= 0.1f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r - 0.2f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g - 0.2f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b - 0.2f);
+}
+
+TEST_F(GainMapMathTest, ColorMultiplyFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 * 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 2.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 2.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 2.0f);
+
+ e2 *= 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r * 4.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g * 4.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b * 4.0f);
+}
+
+TEST_F(GainMapMathTest, ColorDivideFloat) {
+ Color e1 = {{{ 0.1f, 0.2f, 0.3f }}};
+
+ Color e2 = e1 / 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r / 2.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g / 2.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b / 2.0f);
+
+ e2 /= 2.0f;
+ EXPECT_FLOAT_EQ(e2.r, e1.r / 4.0f);
+ EXPECT_FLOAT_EQ(e2.g, e1.g / 4.0f);
+ EXPECT_FLOAT_EQ(e2.b, e1.b / 4.0f);
+}
+
+TEST_F(GainMapMathTest, SrgbLuminance) {
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbBlack()), 0.0f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbWhite()), 1.0f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbRed()), 0.2126f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbGreen()), 0.7152f);
+ EXPECT_FLOAT_EQ(srgbLuminance(RgbBlue()), 0.0722f);
+}
+
+TEST_F(GainMapMathTest, SrgbYuvToRgb) {
+ Color rgb_black = srgbYuvToRgb(YuvBlack());
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = srgbYuvToRgb(YuvWhite());
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = srgbYuvToRgb(SrgbYuvRed());
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = srgbYuvToRgb(SrgbYuvGreen());
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = srgbYuvToRgb(SrgbYuvBlue());
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(GainMapMathTest, SrgbRgbToYuv) {
+ Color yuv_black = srgbRgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+ Color yuv_white = srgbRgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+ Color yuv_r = srgbRgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv_r, SrgbYuvRed());
+
+ Color yuv_g = srgbRgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv_g, SrgbYuvGreen());
+
+ Color yuv_b = srgbRgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv_b, SrgbYuvBlue());
+}
+
+TEST_F(GainMapMathTest, SrgbRgbYuvRoundtrip) {
+ Color rgb_black = srgbYuvToRgb(srgbRgbToYuv(RgbBlack()));
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = srgbYuvToRgb(srgbRgbToYuv(RgbWhite()));
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = srgbYuvToRgb(srgbRgbToYuv(RgbRed()));
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = srgbYuvToRgb(srgbRgbToYuv(RgbGreen()));
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = srgbYuvToRgb(srgbRgbToYuv(RgbBlue()));
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(GainMapMathTest, SrgbTransferFunction) {
+ EXPECT_FLOAT_EQ(srgbInvOetf(0.0f), 0.0f);
+ EXPECT_NEAR(srgbInvOetf(0.02f), 0.00154f, ComparisonEpsilon());
+ EXPECT_NEAR(srgbInvOetf(0.04045f), 0.00313f, ComparisonEpsilon());
+ EXPECT_NEAR(srgbInvOetf(0.5f), 0.21404f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(srgbInvOetf(1.0f), 1.0f);
+}
+
+TEST_F(GainMapMathTest, P3Luminance) {
+ EXPECT_FLOAT_EQ(p3Luminance(RgbBlack()), 0.0f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbWhite()), 1.0f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbRed()), 0.20949f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbGreen()), 0.72160f);
+ EXPECT_FLOAT_EQ(p3Luminance(RgbBlue()), 0.06891f);
+}
+
+TEST_F(GainMapMathTest, P3YuvToRgb) {
+ Color rgb_black = p3YuvToRgb(YuvBlack());
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = p3YuvToRgb(YuvWhite());
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = p3YuvToRgb(P3YuvRed());
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = p3YuvToRgb(P3YuvGreen());
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = p3YuvToRgb(P3YuvBlue());
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(GainMapMathTest, P3RgbToYuv) {
+ Color yuv_black = p3RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+ Color yuv_white = p3RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+ Color yuv_r = p3RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv_r, P3YuvRed());
+
+ Color yuv_g = p3RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv_g, P3YuvGreen());
+
+ Color yuv_b = p3RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv_b, P3YuvBlue());
+}
+
+TEST_F(GainMapMathTest, P3RgbYuvRoundtrip) {
+ Color rgb_black = p3YuvToRgb(p3RgbToYuv(RgbBlack()));
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = p3YuvToRgb(p3RgbToYuv(RgbWhite()));
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = p3YuvToRgb(p3RgbToYuv(RgbRed()));
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = p3YuvToRgb(p3RgbToYuv(RgbGreen()));
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = p3YuvToRgb(p3RgbToYuv(RgbBlue()));
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+TEST_F(GainMapMathTest, Bt2100Luminance) {
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlack()), 0.0f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbWhite()), 1.0f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbRed()), 0.2627f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbGreen()), 0.6780f);
+ EXPECT_FLOAT_EQ(bt2100Luminance(RgbBlue()), 0.0593f);
+}
+
+TEST_F(GainMapMathTest, Bt2100YuvToRgb) {
+ Color rgb_black = bt2100YuvToRgb(YuvBlack());
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = bt2100YuvToRgb(YuvWhite());
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = bt2100YuvToRgb(Bt2100YuvRed());
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = bt2100YuvToRgb(Bt2100YuvGreen());
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = bt2100YuvToRgb(Bt2100YuvBlue());
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(GainMapMathTest, Bt2100RgbToYuv) {
+ Color yuv_black = bt2100RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv_black, YuvBlack());
+
+ Color yuv_white = bt2100RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv_white, YuvWhite());
+
+ Color yuv_r = bt2100RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv_r, Bt2100YuvRed());
+
+ Color yuv_g = bt2100RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv_g, Bt2100YuvGreen());
+
+ Color yuv_b = bt2100RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv_b, Bt2100YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt2100RgbYuvRoundtrip) {
+ Color rgb_black = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlack()));
+ EXPECT_RGB_NEAR(rgb_black, RgbBlack());
+
+ Color rgb_white = bt2100YuvToRgb(bt2100RgbToYuv(RgbWhite()));
+ EXPECT_RGB_NEAR(rgb_white, RgbWhite());
+
+ Color rgb_r = bt2100YuvToRgb(bt2100RgbToYuv(RgbRed()));
+ EXPECT_RGB_NEAR(rgb_r, RgbRed());
+
+ Color rgb_g = bt2100YuvToRgb(bt2100RgbToYuv(RgbGreen()));
+ EXPECT_RGB_NEAR(rgb_g, RgbGreen());
+
+ Color rgb_b = bt2100YuvToRgb(bt2100RgbToYuv(RgbBlue()));
+ EXPECT_RGB_NEAR(rgb_b, RgbBlue());
+}
+
+TEST_F(GainMapMathTest, Bt709ToBt601YuvConversion) {
+ Color yuv_black = srgbRgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_black), YuvBlack());
+
+ Color yuv_white = srgbRgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_white), YuvWhite());
+
+ Color yuv_r = srgbRgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_r), P3YuvRed());
+
+ Color yuv_g = srgbRgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_g), P3YuvGreen());
+
+ Color yuv_b = srgbRgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv709To601(yuv_b), P3YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt709ToBt2100YuvConversion) {
+ Color yuv_black = srgbRgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_black), YuvBlack());
+
+ Color yuv_white = srgbRgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_white), YuvWhite());
+
+ Color yuv_r = srgbRgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_r), Bt2100YuvRed());
+
+ Color yuv_g = srgbRgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_g), Bt2100YuvGreen());
+
+ Color yuv_b = srgbRgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv709To2100(yuv_b), Bt2100YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt601ToBt709YuvConversion) {
+ Color yuv_black = p3RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_black), YuvBlack());
+
+ Color yuv_white = p3RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_white), YuvWhite());
+
+ Color yuv_r = p3RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_r), SrgbYuvRed());
+
+ Color yuv_g = p3RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_g), SrgbYuvGreen());
+
+ Color yuv_b = p3RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv601To709(yuv_b), SrgbYuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt601ToBt2100YuvConversion) {
+ Color yuv_black = p3RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_black), YuvBlack());
+
+ Color yuv_white = p3RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_white), YuvWhite());
+
+ Color yuv_r = p3RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_r), Bt2100YuvRed());
+
+ Color yuv_g = p3RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_g), Bt2100YuvGreen());
+
+ Color yuv_b = p3RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv601To2100(yuv_b), Bt2100YuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt2100ToBt709YuvConversion) {
+ Color yuv_black = bt2100RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_black), YuvBlack());
+
+ Color yuv_white = bt2100RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_white), YuvWhite());
+
+ Color yuv_r = bt2100RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_r), SrgbYuvRed());
+
+ Color yuv_g = bt2100RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_g), SrgbYuvGreen());
+
+ Color yuv_b = bt2100RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv2100To709(yuv_b), SrgbYuvBlue());
+}
+
+TEST_F(GainMapMathTest, Bt2100ToBt601YuvConversion) {
+ Color yuv_black = bt2100RgbToYuv(RgbBlack());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_black), YuvBlack());
+
+ Color yuv_white = bt2100RgbToYuv(RgbWhite());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_white), YuvWhite());
+
+ Color yuv_r = bt2100RgbToYuv(RgbRed());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_r), P3YuvRed());
+
+ Color yuv_g = bt2100RgbToYuv(RgbGreen());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_g), P3YuvGreen());
+
+ Color yuv_b = bt2100RgbToYuv(RgbBlue());
+ EXPECT_YUV_NEAR(yuv2100To601(yuv_b), P3YuvBlue());
+}
+
+TEST_F(GainMapMathTest, TransformYuv420) {
+ ColorTransformFn transforms[] = { yuv709To601, yuv709To2100, yuv601To709, yuv601To2100,
+ yuv2100To709, yuv2100To601 };
+ for (const ColorTransformFn& transform : transforms) {
+ jpegr_uncompressed_struct input = Yuv420Image();
+
+ size_t out_buf_size = input.width * input.height * 3 / 2;
+ std::unique_ptr<uint8_t[]> out_buf = std::make_unique<uint8_t[]>(out_buf_size);
+ memcpy(out_buf.get(), input.data, out_buf_size);
+ jpegr_uncompressed_struct output = Yuv420Image();
+ output.data = out_buf.get();
+ output.chroma_data = out_buf.get() + input.width * input.height;
+ output.luma_stride = input.width;
+ output.chroma_stride = input.width / 2;
+
+ transformYuv420(&output, 1, 1, transform);
+
+ for (size_t y = 0; y < 4; ++y) {
+ for (size_t x = 0; x < 4; ++x) {
+ // Skip the last chroma sample, which we modified above
+ if (x >= 2 && y >= 2) {
+ continue;
+ }
+
+ // All other pixels should remain unchanged
+ EXPECT_YUV_EQ(getYuv420Pixel(&input, x, y), getYuv420Pixel(&output, x, y));
+ }
+ }
+
+ // modified pixels should be updated as intended by the transformYuv420 algorithm
+ Color in1 = getYuv420Pixel(&input, 2, 2);
+ Color in2 = getYuv420Pixel(&input, 3, 2);
+ Color in3 = getYuv420Pixel(&input, 2, 3);
+ Color in4 = getYuv420Pixel(&input, 3, 3);
+ Color out1 = getYuv420Pixel(&output, 2, 2);
+ Color out2 = getYuv420Pixel(&output, 3, 2);
+ Color out3 = getYuv420Pixel(&output, 2, 3);
+ Color out4 = getYuv420Pixel(&output, 3, 3);
+
+ EXPECT_NEAR(transform(in1).y, out1.y, YuvConversionEpsilon());
+ EXPECT_NEAR(transform(in2).y, out2.y, YuvConversionEpsilon());
+ EXPECT_NEAR(transform(in3).y, out3.y, YuvConversionEpsilon());
+ EXPECT_NEAR(transform(in4).y, out4.y, YuvConversionEpsilon());
+
+ Color expect_uv = (transform(in1) + transform(in2) + transform(in3) + transform(in4)) / 4.0f;
+
+ EXPECT_NEAR(expect_uv.u, out1.u, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.u, out2.u, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.u, out3.u, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.u, out4.u, YuvConversionEpsilon());
+
+ EXPECT_NEAR(expect_uv.v, out1.v, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.v, out2.v, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.v, out3.v, YuvConversionEpsilon());
+ EXPECT_NEAR(expect_uv.v, out4.v, YuvConversionEpsilon());
+ }
+}
+
+TEST_F(GainMapMathTest, HlgOetf) {
+ EXPECT_FLOAT_EQ(hlgOetf(0.0f), 0.0f);
+ EXPECT_NEAR(hlgOetf(0.04167f), 0.35357f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgOetf(0.08333f), 0.5f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgOetf(0.5f), 0.87164f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(hlgOetf(1.0f), 1.0f);
+
+ Color e = {{{ 0.04167f, 0.08333f, 0.5f }}};
+ Color e_gamma = {{{ 0.35357f, 0.5f, 0.87164f }}};
+ EXPECT_RGB_NEAR(hlgOetf(e), e_gamma);
+}
+
+TEST_F(GainMapMathTest, HlgInvOetf) {
+ EXPECT_FLOAT_EQ(hlgInvOetf(0.0f), 0.0f);
+ EXPECT_NEAR(hlgInvOetf(0.25f), 0.02083f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(0.5f), 0.08333f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(0.75f), 0.26496f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(hlgInvOetf(1.0f), 1.0f);
+
+ Color e_gamma = {{{ 0.25f, 0.5f, 0.75f }}};
+ Color e = {{{ 0.02083f, 0.08333f, 0.26496f }}};
+ EXPECT_RGB_NEAR(hlgInvOetf(e_gamma), e);
+}
+
+TEST_F(GainMapMathTest, HlgTransferFunctionRoundtrip) {
+ EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(0.0f)), 0.0f);
+ EXPECT_NEAR(hlgInvOetf(hlgOetf(0.04167f)), 0.04167f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(hlgOetf(0.08333f)), 0.08333f, ComparisonEpsilon());
+ EXPECT_NEAR(hlgInvOetf(hlgOetf(0.5f)), 0.5f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(hlgInvOetf(hlgOetf(1.0f)), 1.0f);
+}
+
+TEST_F(GainMapMathTest, PqOetf) {
+ EXPECT_FLOAT_EQ(pqOetf(0.0f), 0.0f);
+ EXPECT_NEAR(pqOetf(0.01f), 0.50808f, ComparisonEpsilon());
+ EXPECT_NEAR(pqOetf(0.5f), 0.92655f, ComparisonEpsilon());
+ EXPECT_NEAR(pqOetf(0.99f), 0.99895f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(pqOetf(1.0f), 1.0f);
+
+ Color e = {{{ 0.01f, 0.5f, 0.99f }}};
+ Color e_gamma = {{{ 0.50808f, 0.92655f, 0.99895f }}};
+ EXPECT_RGB_NEAR(pqOetf(e), e_gamma);
+}
+
+TEST_F(GainMapMathTest, PqInvOetf) {
+ EXPECT_FLOAT_EQ(pqInvOetf(0.0f), 0.0f);
+ EXPECT_NEAR(pqInvOetf(0.01f), 2.31017e-7f, ComparisonEpsilon());
+ EXPECT_NEAR(pqInvOetf(0.5f), 0.00922f, ComparisonEpsilon());
+ EXPECT_NEAR(pqInvOetf(0.99f), 0.90903f, ComparisonEpsilon());
+ EXPECT_FLOAT_EQ(pqInvOetf(1.0f), 1.0f);
+
+ Color e_gamma = {{{ 0.01f, 0.5f, 0.99f }}};
+ Color e = {{{ 2.31017e-7f, 0.00922f, 0.90903f }}};
+ EXPECT_RGB_NEAR(pqInvOetf(e_gamma), e);
+}
+
+TEST_F(GainMapMathTest, PqInvOetfLUT) {
+ for (int idx = 0; idx < kPqInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqInvOETFNumEntries - 1);
+ EXPECT_FLOAT_EQ(pqInvOetf(value), pqInvOetfLUT(value));
+ }
+}
+
+TEST_F(GainMapMathTest, HlgInvOetfLUT) {
+ for (int idx = 0; idx < kHlgInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgInvOETFNumEntries - 1);
+ EXPECT_FLOAT_EQ(hlgInvOetf(value), hlgInvOetfLUT(value));
+ }
+}
+
+TEST_F(GainMapMathTest, pqOetfLUT) {
+ for (int idx = 0; idx < kPqOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kPqOETFNumEntries - 1);
+ EXPECT_FLOAT_EQ(pqOetf(value), pqOetfLUT(value));
+ }
+}
+
+TEST_F(GainMapMathTest, hlgOetfLUT) {
+ for (int idx = 0; idx < kHlgOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(kHlgOETFNumEntries - 1);
+ EXPECT_FLOAT_EQ(hlgOetf(value), hlgOetfLUT(value));
+ }
+}
+
+TEST_F(GainMapMathTest, srgbInvOetfLUT) {
+ for (int idx = 0; idx < kSrgbInvOETFNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(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<float>(boost),
+ .minContentBoost = 1.0f / static_cast<float>(boost) };
+ GainLUT gainLUT(&metadata);
+ GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+ for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(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<float>(boost),
+ .minContentBoost = 1.0f };
+ GainLUT gainLUT(&metadata);
+ GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+ for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(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<float>(boost),
+ .minContentBoost = 1.0f / pow(static_cast<float>(boost),
+ 1.0f / 3.0f) };
+ GainLUT gainLUT(&metadata);
+ GainLUT gainLUTWithBoost(&metadata, metadata.maxContentBoost);
+ for (int idx = 0; idx < kGainFactorNumEntries; idx++) {
+ float value = static_cast<float>(idx) / static_cast<float>(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<uint32_t>(0.1f * static_cast<float>(0x3ff))
+ | static_cast<uint32_t>(0.2f * static_cast<float>(0x3ff)) << 10
+ | static_cast<uint32_t>(0.3f * static_cast<float>(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 0000000000..ff61c08574
--- /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 <gtest/gtest.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/ultrahdr.h>
+#include <utils/Log.h>
+
+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<DataStruct> 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<DataStruct> 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<DataStruct> 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<DataStruct> icc = IccHelper::writeIccProfile(ULTRAHDR_TF_SRGB, ULTRAHDR_COLORGAMUT_BT709);
+ size_t profile_size = icc->getLength() - kICCIdentifierSize;
+
+ uint8_t* icc_bytes = reinterpret_cast<uint8_t*>(icc->getData()) + kICCIdentifierSize;
+ uint32_t encoded_size = static_cast<uint32_t>(icc_bytes[0]) << 24 |
+ static_cast<uint32_t>(icc_bytes[1]) << 16 |
+ static_cast<uint32_t>(icc_bytes[2]) << 8 |
+ static_cast<uint32_t>(icc_bytes[3]);
+
+ EXPECT_EQ(static_cast<size_t>(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 0000000000..af0d59edc0
--- /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 <gtest/gtest.h>
+#include <ultrahdr/icc.h>
+#include <ultrahdr/jpegdecoderhelper.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::ultrahdr {
+
+// No ICC or EXIF
+#define YUV_IMAGE "/data/local/tmp/minnie-320x240-yuv.jpg"
+#define YUV_IMAGE_SIZE 20193
+// Has ICC and EXIF
+#define YUV_ICC_IMAGE "/data/local/tmp/minnie-320x240-yuv-icc.jpg"
+#define YUV_ICC_IMAGE_SIZE 34266
+// No ICC or EXIF
+#define GREY_IMAGE "/data/local/tmp/minnie-320x240-y.jpg"
+#define GREY_IMAGE_SIZE 20193
+
+#define IMAGE_WIDTH 320
+#define IMAGE_HEIGHT 240
+
+class JpegDecoderHelperTest : public testing::Test {
+public:
+ struct Image {
+ std::unique_ptr<uint8_t[]> 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<ssize_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(0));
+}
+
+TEST_F(JpegDecoderHelperTest, getCompressedImageParameters) {
+ size_t width = 0, height = 0;
+ std::vector<uint8_t> 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<uint8_t> 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 0000000000..af54eb2a8a
--- /dev/null
+++ b/libs/ultrahdr/tests/jpegencoderhelper_test.cpp
@@ -0,0 +1,135 @@
+/*
+ * 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 <ultrahdr/jpegencoderhelper.h>
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+#include <fcntl.h>
+
+namespace android::ultrahdr {
+
+#define ALIGNED_IMAGE "/data/local/tmp/minnie-320x240.yu12"
+#define ALIGNED_IMAGE_WIDTH 320
+#define ALIGNED_IMAGE_HEIGHT 240
+#define SINGLE_CHANNEL_IMAGE "/data/local/tmp/minnie-320x240.y"
+#define SINGLE_CHANNEL_IMAGE_WIDTH ALIGNED_IMAGE_WIDTH
+#define SINGLE_CHANNEL_IMAGE_HEIGHT ALIGNED_IMAGE_HEIGHT
+#define UNALIGNED_IMAGE "/data/local/tmp/minnie-318x240.yu12"
+#define UNALIGNED_IMAGE_WIDTH 318
+#define UNALIGNED_IMAGE_HEIGHT 240
+#define JPEG_QUALITY 90
+
+class JpegEncoderHelperTest : public testing::Test {
+public:
+ struct Image {
+ std::unique_ptr<uint8_t[]> 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<ssize_t>(length)) {
+ close(fd);
+ return false;
+ }
+ close(fd);
+ return true;
+}
+
+void JpegEncoderHelperTest::SetUp() {
+ if (!loadFile(ALIGNED_IMAGE, &mAlignedImage)) {
+ FAIL() << "Load file " << ALIGNED_IMAGE << " failed";
+ }
+ mAlignedImage.width = ALIGNED_IMAGE_WIDTH;
+ mAlignedImage.height = ALIGNED_IMAGE_HEIGHT;
+ if (!loadFile(UNALIGNED_IMAGE, &mUnalignedImage)) {
+ FAIL() << "Load file " << UNALIGNED_IMAGE << " failed";
+ }
+ mUnalignedImage.width = UNALIGNED_IMAGE_WIDTH;
+ mUnalignedImage.height = UNALIGNED_IMAGE_HEIGHT;
+ if (!loadFile(SINGLE_CHANNEL_IMAGE, &mSingleChannelImage)) {
+ FAIL() << "Load file " << SINGLE_CHANNEL_IMAGE << " failed";
+ }
+ mSingleChannelImage.width = SINGLE_CHANNEL_IMAGE_WIDTH;
+ mSingleChannelImage.height = SINGLE_CHANNEL_IMAGE_HEIGHT;
+}
+
+void JpegEncoderHelperTest::TearDown() {}
+
+TEST_F(JpegEncoderHelperTest, encodeAlignedImage) {
+ JpegEncoderHelper encoder;
+ EXPECT_TRUE(encoder.compressImage(mAlignedImage.buffer.get(),
+ mAlignedImage.buffer.get() +
+ mAlignedImage.width * mAlignedImage.height,
+ mAlignedImage.width, mAlignedImage.height,
+ mAlignedImage.width, mAlignedImage.width / 2, JPEG_QUALITY,
+ NULL, 0));
+ ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegEncoderHelperTest, encodeUnalignedImage) {
+ JpegEncoderHelper encoder;
+ EXPECT_TRUE(encoder.compressImage(mUnalignedImage.buffer.get(),
+ mUnalignedImage.buffer.get() +
+ mUnalignedImage.width * mUnalignedImage.height,
+ mUnalignedImage.width, mUnalignedImage.height,
+ mUnalignedImage.width, mUnalignedImage.width / 2,
+ JPEG_QUALITY, NULL, 0));
+ ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(0));
+}
+
+TEST_F(JpegEncoderHelperTest, encodeSingleChannelImage) {
+ JpegEncoderHelper encoder;
+ EXPECT_TRUE(encoder.compressImage(mSingleChannelImage.buffer.get(), nullptr,
+ mSingleChannelImage.width, mSingleChannelImage.height,
+ mSingleChannelImage.width, 0, JPEG_QUALITY, NULL, 0));
+ ASSERT_GT(encoder.getCompressedImageSize(), static_cast<uint32_t>(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 0000000000..5fa758e88d
--- /dev/null
+++ b/libs/ultrahdr/tests/jpegr_test.cpp
@@ -0,0 +1,2035 @@
+/*
+ * 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 <sys/time.h>
+#include <fstream>
+#include <iostream>
+
+#include <ultrahdr/gainmapmath.h>
+#include <ultrahdr/jpegr.h>
+#include <ultrahdr/jpegrutils.h>
+
+#include <gtest/gtest.h>
+#include <utils/Log.h>
+
+//#define DUMP_OUTPUT
+
+namespace android::ultrahdr {
+
+// resources used by unit tests
+const char* kYCbCrP010FileName = "/data/local/tmp/raw_p010_image.p010";
+const char* kYCbCr420FileName = "/data/local/tmp/raw_yuv420_image.yuv420";
+const char* kSdrJpgFileName = "/data/local/tmp/jpeg_image.jpg";
+const int kImageWidth = 1280;
+const int kImageHeight = 720;
+const int kQuality = 90;
+
+// Wrapper to describe the input type
+typedef enum {
+ YCbCr_p010 = 0,
+ YCbCr_420 = 1,
+} UhdrInputFormat;
+
+/**
+ * Wrapper class for raw resource
+ * Sample usage:
+ * UhdrUnCompressedStructWrapper rawImg(width, height, YCbCr_p010);
+ * rawImg.setImageColorGamut(colorGamut));
+ * rawImg.setImageStride(strideLuma, strideChroma); // optional
+ * rawImg.setChromaMode(false); // optional
+ * rawImg.allocateMemory();
+ * rawImg.loadRawResource(kYCbCrP010FileName);
+ */
+class UhdrUnCompressedStructWrapper {
+public:
+ UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height, UhdrInputFormat format);
+ ~UhdrUnCompressedStructWrapper() = default;
+
+ bool setChromaMode(bool isChromaContiguous);
+ bool setImageStride(int lumaStride, int chromaStride);
+ bool setImageColorGamut(ultrahdr_color_gamut colorGamut);
+ bool allocateMemory();
+ bool loadRawResource(const char* fileName);
+ jr_uncompressed_ptr getImageHandle();
+
+private:
+ std::unique_ptr<uint8_t[]> mLumaData;
+ std::unique_ptr<uint8_t[]> mChromaData;
+ jpegr_uncompressed_struct mImg;
+ UhdrInputFormat mFormat;
+ bool mIsChromaContiguous;
+};
+
+/**
+ * Wrapper class for compressed resource
+ * Sample usage:
+ * UhdrCompressedStructWrapper jpgImg(width, height);
+ * rawImg.allocateMemory();
+ */
+class UhdrCompressedStructWrapper {
+public:
+ UhdrCompressedStructWrapper(uint32_t width, uint32_t height);
+ ~UhdrCompressedStructWrapper() = default;
+
+ bool allocateMemory();
+ jr_compressed_ptr getImageHandle();
+
+private:
+ std::unique_ptr<uint8_t[]> mData;
+ jpegr_compressed_struct mImg{};
+ uint32_t mWidth;
+ uint32_t mHeight;
+};
+
+UhdrUnCompressedStructWrapper::UhdrUnCompressedStructWrapper(uint32_t width, uint32_t height,
+ UhdrInputFormat format) {
+ mImg.data = nullptr;
+ mImg.width = width;
+ mImg.height = height;
+ mImg.colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ mImg.chroma_data = nullptr;
+ mImg.luma_stride = 0;
+ mImg.chroma_stride = 0;
+ mFormat = format;
+ mIsChromaContiguous = true;
+}
+
+bool UhdrUnCompressedStructWrapper::setChromaMode(bool isChromaContiguous) {
+ if (mLumaData.get() != nullptr) {
+ std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
+ return false;
+ }
+ mIsChromaContiguous = isChromaContiguous;
+ return true;
+}
+
+bool UhdrUnCompressedStructWrapper::setImageStride(int lumaStride, int chromaStride) {
+ if (mLumaData.get() != nullptr) {
+ std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
+ return false;
+ }
+ if (lumaStride != 0) {
+ if (lumaStride < mImg.width) {
+ std::cerr << "Bad luma stride received" << std::endl;
+ return false;
+ }
+ mImg.luma_stride = lumaStride;
+ }
+ if (chromaStride != 0) {
+ if (mFormat == YCbCr_p010 && chromaStride < mImg.width) {
+ std::cerr << "Bad chroma stride received for format YCbCrP010" << std::endl;
+ return false;
+ }
+ if (mFormat == YCbCr_420 && chromaStride < (mImg.width >> 1)) {
+ std::cerr << "Bad chroma stride received for format YCbCr420" << std::endl;
+ return false;
+ }
+ mImg.chroma_stride = chromaStride;
+ }
+ return true;
+}
+
+bool UhdrUnCompressedStructWrapper::setImageColorGamut(ultrahdr_color_gamut colorGamut) {
+ if (mLumaData.get() != nullptr) {
+ std::cerr << "Object has sailed, no further modifications are allowed" << std::endl;
+ return false;
+ }
+ mImg.colorGamut = colorGamut;
+ return true;
+}
+
+bool UhdrUnCompressedStructWrapper::allocateMemory() {
+ if (mImg.width == 0 || (mImg.width % 2 != 0) || mImg.height == 0 || (mImg.height % 2 != 0) ||
+ (mFormat != YCbCr_p010 && mFormat != YCbCr_420)) {
+ std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+ return false;
+ }
+ int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
+ int lumaSize = lumaStride * mImg.height * (mFormat == YCbCr_p010 ? 2 : 1);
+ int chromaSize = (mImg.height >> 1) * (mFormat == YCbCr_p010 ? 2 : 1);
+ if (mIsChromaContiguous) {
+ chromaSize *= lumaStride;
+ } else {
+ if (mImg.chroma_stride == 0) {
+ std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+ return false;
+ }
+ if (mFormat == YCbCr_p010) {
+ chromaSize *= mImg.chroma_stride;
+ } else {
+ chromaSize *= (mImg.chroma_stride * 2);
+ }
+ }
+ if (mIsChromaContiguous) {
+ mLumaData = std::make_unique<uint8_t[]>(lumaSize + chromaSize);
+ mImg.data = mLumaData.get();
+ mImg.chroma_data = nullptr;
+ } else {
+ mLumaData = std::make_unique<uint8_t[]>(lumaSize);
+ mImg.data = mLumaData.get();
+ mChromaData = std::make_unique<uint8_t[]>(chromaSize);
+ mImg.chroma_data = mChromaData.get();
+ }
+ return true;
+}
+
+bool UhdrUnCompressedStructWrapper::loadRawResource(const char* fileName) {
+ if (!mImg.data) {
+ std::cerr << "memory is not allocated, read not possible" << std::endl;
+ return false;
+ }
+ std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
+ if (ifd.good()) {
+ int bpp = mFormat == YCbCr_p010 ? 2 : 1;
+ int size = ifd.tellg();
+ int length = mImg.width * mImg.height * bpp * 3 / 2; // 2x2 subsampling
+ if (size < length) {
+ std::cerr << "requested to read " << length << " bytes from file : " << fileName
+ << ", file contains only " << length << " bytes" << std::endl;
+ return false;
+ }
+ ifd.seekg(0, std::ios::beg);
+ int lumaStride = mImg.luma_stride == 0 ? mImg.width : mImg.luma_stride;
+ char* mem = static_cast<char*>(mImg.data);
+ for (int i = 0; i < mImg.height; i++) {
+ ifd.read(mem, mImg.width * bpp);
+ mem += lumaStride * bpp;
+ }
+ if (!mIsChromaContiguous) {
+ mem = static_cast<char*>(mImg.chroma_data);
+ }
+ int chromaStride;
+ if (mIsChromaContiguous) {
+ chromaStride = mFormat == YCbCr_p010 ? lumaStride : lumaStride / 2;
+ } else {
+ if (mFormat == YCbCr_p010) {
+ chromaStride = mImg.chroma_stride == 0 ? lumaStride : mImg.chroma_stride;
+ } else {
+ chromaStride = mImg.chroma_stride == 0 ? (lumaStride / 2) : mImg.chroma_stride;
+ }
+ }
+ if (mFormat == YCbCr_p010) {
+ for (int i = 0; i < mImg.height / 2; i++) {
+ ifd.read(mem, mImg.width * 2);
+ mem += chromaStride * 2;
+ }
+ } else {
+ for (int i = 0; i < mImg.height / 2; i++) {
+ ifd.read(mem, (mImg.width / 2));
+ mem += chromaStride;
+ }
+ for (int i = 0; i < mImg.height / 2; i++) {
+ ifd.read(mem, (mImg.width / 2));
+ mem += chromaStride;
+ }
+ }
+ return true;
+ }
+ std::cerr << "unable to open file : " << fileName << std::endl;
+ return false;
+}
+
+jr_uncompressed_ptr UhdrUnCompressedStructWrapper::getImageHandle() {
+ return &mImg;
+}
+
+UhdrCompressedStructWrapper::UhdrCompressedStructWrapper(uint32_t width, uint32_t height) {
+ mWidth = width;
+ mHeight = height;
+}
+
+bool UhdrCompressedStructWrapper::allocateMemory() {
+ if (mWidth == 0 || (mWidth % 2 != 0) || mHeight == 0 || (mHeight % 2 != 0)) {
+ std::cerr << "Object in bad state, mem alloc failed" << std::endl;
+ return false;
+ }
+ int maxLength = std::max(8 * 1024 /* min size 8kb */, (int)(mWidth * mHeight * 3 * 2));
+ mData = std::make_unique<uint8_t[]>(maxLength);
+ mImg.data = mData.get();
+ mImg.length = 0;
+ mImg.maxLength = maxLength;
+ return true;
+}
+
+jr_compressed_ptr UhdrCompressedStructWrapper::getImageHandle() {
+ return &mImg;
+}
+
+static bool writeFile(const char* filename, void*& result, int length) {
+ std::ofstream ofd(filename, std::ios::binary);
+ if (ofd.is_open()) {
+ ofd.write(static_cast<char*>(result), length);
+ return true;
+ }
+ std::cerr << "unable to write to file : " << filename << std::endl;
+ return false;
+}
+
+static bool readFile(const char* fileName, void*& result, int maxLength, int& length) {
+ std::ifstream ifd(fileName, std::ios::binary | std::ios::ate);
+ if (ifd.good()) {
+ length = ifd.tellg();
+ if (length > maxLength) {
+ std::cerr << "not enough space to read file" << std::endl;
+ return false;
+ }
+ ifd.seekg(0, std::ios::beg);
+ ifd.read(static_cast<char*>(result), length);
+ return true;
+ }
+ std::cerr << "unable to read file : " << fileName << std::endl;
+ return false;
+}
+
+void decodeJpegRImg(jr_compressed_ptr img, [[maybe_unused]] const char* outFileName) {
+ std::vector<uint8_t> iccData(0);
+ std::vector<uint8_t> exifData(0);
+ jpegr_info_struct info{0, 0, &iccData, &exifData};
+ JpegR jpegHdr;
+ ASSERT_EQ(OK, jpegHdr.getJPEGRInfo(img, &info));
+ ASSERT_EQ(kImageWidth, info.width);
+ ASSERT_EQ(kImageHeight, info.height);
+ size_t outSize = info.width * info.height * 8;
+ std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
+ jpegr_uncompressed_struct destImage{};
+ destImage.data = data.get();
+ ASSERT_EQ(OK, jpegHdr.decodeJPEGR(img, &destImage));
+ ASSERT_EQ(kImageWidth, destImage.width);
+ ASSERT_EQ(kImageHeight, destImage.height);
+#ifdef DUMP_OUTPUT
+ if (!writeFile(outFileName, destImage.data, outSize)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+}
+
+// ============================================================================
+// Unit Tests
+// ============================================================================
+
+// Test Encode API-0 invalid arguments
+TEST(JpegRTest, EncodeAPI0WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), -1, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), 101, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
+
+ // test dest
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
+ nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
+
+ // test p010 input
+ {
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1)));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
+}
+
+/* Test Encode API-1 invalid arguments */
+TEST(JpegRTest, EncodeAPI1WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), -1, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), 101, nullptr),
+ OK)
+ << "fail, API allows bad jpeg quality factor";
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
+
+ // test dest
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr, kQuality,
+ nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
+
+ // test p010 input
+ {
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
+
+ // test 420 input
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr 420 image";
+
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows nullptr 420 image";
+ }
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth - 1;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = 0;
+ rawImg420->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad luma stride for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = 0;
+ rawImg420->chroma_data = rawImgP010->data;
+ rawImg420->chroma_stride = kWidth / 2 - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK)
+ << "fail, API allows bad chroma stride for 420";
+ }
+}
+
+/* Test Encode API-2 invalid arguments */
+TEST(JpegRTest, EncodeAPI2WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
+
+ // test dest
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
+
+ // test compressed image
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(), nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ }
+
+ // test p010 input
+ {
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, rawImg2.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
+
+ // test 420 input
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr 420 image";
+
+ UhdrUnCompressedStructWrapper rawImg2(16, 16, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), rawImg2.getImageHandle(),
+ jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr 420 image";
+ }
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+ UhdrUnCompressedStructWrapper rawImg2(kWidth, kHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ auto rawImg420 = rawImg2.getImageHandle();
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad 420 color gamut";
+
+ rawImg420->width = kWidth - 1;
+ rawImg420->height = kHeight;
+ rawImg420->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = 0;
+ rawImg420->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad luma stride for 420";
+
+ rawImg420->width = kWidth;
+ rawImg420->height = kHeight;
+ rawImg420->luma_stride = 0;
+ rawImg420->chroma_data = rawImgP010->data;
+ rawImg420->chroma_stride = kWidth / 2 - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, rawImg420, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad chroma stride for 420";
+ }
+}
+
+/* Test Encode API-3 invalid arguments */
+TEST(JpegRTest, EncodeAPI3WithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test quality factor and transfer function
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_UNSPECIFIED,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(
+ ultrahdr_transfer_function::ULTRAHDR_TF_MAX + 1),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ static_cast<ultrahdr_transfer_function>(-10),
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad hdr transfer function";
+ }
+
+ // test dest
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr dest";
+ }
+
+ // test compressed image
+ {
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), nullptr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr for compressed image";
+ }
+
+ // test p010 input
+ {
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+
+ UhdrUnCompressedStructWrapper rawImg(16, 16, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImg.getImageHandle(), jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr p010 image";
+ }
+
+ {
+ const int kWidth = 32, kHeight = 32;
+ UhdrUnCompressedStructWrapper rawImg(kWidth, kHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ auto rawImgP010 = rawImg.getImageHandle();
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_UNSPECIFIED;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut =
+ static_cast<ultrahdr_color_gamut>(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_MAX + 1);
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad p010 color gamut";
+
+ rawImgP010->width = kWidth - 1;
+ rawImgP010->height = kHeight;
+ rawImgP010->colorGamut = ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight - 1;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = 0;
+ rawImgP010->height = kHeight;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image width";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = 0;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad image height";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad luma stride";
+
+ rawImgP010->width = kWidth;
+ rawImgP010->height = kHeight;
+ rawImgP010->luma_stride = kWidth + 64;
+ rawImgP010->chroma_data = rawImgP010->data;
+ rawImgP010->chroma_stride = kWidth - 2;
+ ASSERT_NE(uHdrLib.encodeJPEGR(rawImgP010, jpgImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad chroma stride";
+ }
+}
+
+/* Test Encode API-4 invalid arguments */
+TEST(JpegRTest, EncodeAPI4WithInvalidArgs) {
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ UhdrCompressedStructWrapper jpgImg2(16, 16);
+ JpegR uHdrLib;
+
+ // test dest
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr, nullptr),
+ OK)
+ << "fail, API allows nullptr dest";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), nullptr,
+ jpgImg2.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr dest";
+
+ // test primary image
+ ASSERT_NE(uHdrLib.encodeJPEGR(nullptr, jpgImg.getImageHandle(), nullptr, jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr primary image";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg2.getImageHandle(), jpgImg.getImageHandle(), nullptr,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr primary image";
+
+ // test gain map
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), nullptr, nullptr, jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr gain map image";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg2.getImageHandle(), nullptr,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows nullptr gain map image";
+
+ // test metadata
+ ultrahdr_metadata_struct good_metadata;
+ good_metadata.version = "1.0";
+ good_metadata.minContentBoost = 1.0f;
+ good_metadata.maxContentBoost = 2.0f;
+ good_metadata.gamma = 1.0f;
+ good_metadata.offsetSdr = 0.0f;
+ good_metadata.offsetHdr = 0.0f;
+ good_metadata.hdrCapacityMin = 1.0f;
+ good_metadata.hdrCapacityMax = 2.0f;
+
+ ultrahdr_metadata_struct metadata = good_metadata;
+ metadata.version = "1.1";
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata version";
+
+ metadata = good_metadata;
+ metadata.minContentBoost = 3.0f;
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata content boost";
+
+ metadata = good_metadata;
+ metadata.gamma = -0.1f;
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata gamma";
+
+ metadata = good_metadata;
+ metadata.offsetSdr = -0.1f;
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata offset sdr";
+
+ metadata = good_metadata;
+ metadata.offsetHdr = -0.1f;
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata offset hdr";
+
+ metadata = good_metadata;
+ metadata.hdrCapacityMax = 0.5f;
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata hdr capacity max";
+
+ metadata = good_metadata;
+ metadata.hdrCapacityMin = 0.5f;
+ ASSERT_NE(uHdrLib.encodeJPEGR(jpgImg.getImageHandle(), jpgImg.getImageHandle(), &metadata,
+ jpgImg.getImageHandle()),
+ OK)
+ << "fail, API allows bad metadata hdr capacity min";
+}
+
+/* Test Decode API invalid arguments */
+TEST(JpegRTest, DecodeAPIWithInvalidArgs) {
+ JpegR uHdrLib;
+
+ UhdrCompressedStructWrapper jpgImg(16, 16);
+ jpegr_uncompressed_struct destImage{};
+ size_t outSize = 16 * 16 * 8;
+ std::unique_ptr<uint8_t[]> data = std::make_unique<uint8_t[]>(outSize);
+ destImage.data = data.get();
+
+ // test jpegr image
+ ASSERT_NE(uHdrLib.decodeJPEGR(nullptr, &destImage), OK)
+ << "fail, API allows nullptr for jpegr img";
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
+ << "fail, API allows nullptr for jpegr img";
+ ASSERT_TRUE(jpgImg.allocateMemory());
+
+ // test dest image
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), nullptr), OK)
+ << "fail, API allows nullptr for dest";
+ destImage.data = nullptr;
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage), OK)
+ << "fail, API allows nullptr for dest";
+ destImage.data = data.get();
+
+ // test max display boost
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, 0.5), OK)
+ << "fail, API allows invalid max display boost";
+
+ // test output format
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
+ static_cast<ultrahdr_output_format>(-1)),
+ OK)
+ << "fail, API allows invalid output format";
+ ASSERT_NE(uHdrLib.decodeJPEGR(jpgImg.getImageHandle(), &destImage, FLT_MAX, nullptr,
+ static_cast<ultrahdr_output_format>(ULTRAHDR_OUTPUT_MAX + 1)),
+ OK)
+ << "fail, API allows invalid output format";
+}
+
+TEST(JpegRTest, writeXmpThenRead) {
+ ultrahdr_metadata_struct metadata_expected;
+ metadata_expected.version = "1.0";
+ metadata_expected.maxContentBoost = 1.25f;
+ metadata_expected.minContentBoost = 0.75f;
+ metadata_expected.gamma = 1.0f;
+ metadata_expected.offsetSdr = 0.0f;
+ metadata_expected.offsetHdr = 0.0f;
+ metadata_expected.hdrCapacityMin = 1.0f;
+ metadata_expected.hdrCapacityMax = metadata_expected.maxContentBoost;
+ const std::string nameSpace = "http://ns.adobe.com/xap/1.0/\0";
+ const int nameSpaceLength = nameSpace.size() + 1; // need to count the null terminator
+
+ std::string xmp = generateXmpForSecondaryImage(metadata_expected);
+
+ std::vector<uint8_t> xmpData;
+ xmpData.reserve(nameSpaceLength + xmp.size());
+ xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(nameSpace.c_str()),
+ reinterpret_cast<const uint8_t*>(nameSpace.c_str()) + nameSpaceLength);
+ xmpData.insert(xmpData.end(), reinterpret_cast<const uint8_t*>(xmp.c_str()),
+ reinterpret_cast<const uint8_t*>(xmp.c_str()) + xmp.size());
+
+ ultrahdr_metadata_struct metadata_read;
+ EXPECT_TRUE(getMetadataFromXMP(xmpData.data(), xmpData.size(), &metadata_read));
+ EXPECT_FLOAT_EQ(metadata_expected.maxContentBoost, metadata_read.maxContentBoost);
+ EXPECT_FLOAT_EQ(metadata_expected.minContentBoost, metadata_read.minContentBoost);
+ EXPECT_FLOAT_EQ(metadata_expected.gamma, metadata_read.gamma);
+ EXPECT_FLOAT_EQ(metadata_expected.offsetSdr, metadata_read.offsetSdr);
+ EXPECT_FLOAT_EQ(metadata_expected.offsetHdr, metadata_read.offsetHdr);
+ EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMin, metadata_read.hdrCapacityMin);
+ EXPECT_FLOAT_EQ(metadata_expected.hdrCapacityMax, metadata_read.hdrCapacityMax);
+}
+
+class JpegRAPIEncodeAndDecodeTest
+ : public ::testing::TestWithParam<std::tuple<ultrahdr_color_gamut, ultrahdr_color_gamut>> {
+public:
+ JpegRAPIEncodeAndDecodeTest()
+ : mP010ColorGamut(std::get<0>(GetParam())), mYuv420ColorGamut(std::get<1>(GetParam())){};
+
+ const ultrahdr_color_gamut mP010ColorGamut;
+ const ultrahdr_color_gamut mYuv420ColorGamut;
+};
+
+/* Test Encode API-0 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI0AndDecodeTest) {
+ // reference encode
+ UhdrUnCompressedStructWrapper rawImg(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg.allocateMemory());
+ ASSERT_TRUE(rawImg.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK);
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, 0));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(kImageWidth + 18, kImageWidth + 28));
+ ASSERT_TRUE(rawImg2.setChromaMode(false));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(0, kImageWidth + 34));
+ ASSERT_TRUE(rawImg2.setChromaMode(false));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set but no chroma ptr
+ {
+ UhdrUnCompressedStructWrapper rawImg2(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2.setImageStride(kImageWidth, kImageWidth + 38));
+ ASSERT_TRUE(rawImg2.allocateMemory());
+ ASSERT_TRUE(rawImg2.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+
+ auto jpg1 = jpgImg.getImageHandle();
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api0_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api0_output.rgb"));
+}
+
+/* Test Encode API-1 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI1AndDecodeTest) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg420.allocateMemory());
+ ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle(), kQuality, nullptr),
+ OK);
+ // encode with luma stride set p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set but no chroma ptr p010
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 64, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma stride set 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 14, 0));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 46, kImageWidth / 2 + 34));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth / 2 + 38));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set but no chroma ptr 420
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 26, kImageWidth / 2 + 44));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(),
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle(), kQuality, nullptr),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+
+ auto jpg1 = jpgImg.getImageHandle();
+
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api1_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api1_output.rgb"));
+}
+
+/* Test Encode API-2 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI2AndDecodeTest) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg420.allocateMemory());
+ ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgSdr.allocateMemory());
+ auto sdr = jpgSdr.getImageHandle();
+ ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK);
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), rawImg420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg2420.setImageColorGamut(mYuv420ColorGamut));
+ ASSERT_TRUE(rawImg2420.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2420.setChromaMode(false));
+ ASSERT_TRUE(rawImg2420.allocateMemory());
+ ASSERT_TRUE(rawImg2420.loadRawResource(kYCbCr420FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), rawImg2420.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+
+ auto jpg1 = jpgImg.getImageHandle();
+
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api2_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api2_output.rgb"));
+}
+
+/* Test Encode API-3 and Decode */
+TEST_P(JpegRAPIEncodeAndDecodeTest, EncodeAPI3AndDecodeTest) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg.allocateMemory());
+ UhdrCompressedStructWrapper jpgSdr(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgSdr.allocateMemory());
+ auto sdr = jpgSdr.getImageHandle();
+ ASSERT_TRUE(readFile(kSdrJpgFileName, sdr->data, sdr->maxLength, sdr->length));
+ JpegR uHdrLib;
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImgP010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg.getImageHandle()),
+ OK);
+ // encode with luma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, 0));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 128, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with chroma stride set
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(0, kImageWidth + 64));
+ ASSERT_TRUE(rawImg2P010.setChromaMode(false));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+ // encode with luma and chroma stride set and no chroma ptr
+ {
+ UhdrUnCompressedStructWrapper rawImg2P010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImg2P010.setImageColorGamut(mP010ColorGamut));
+ ASSERT_TRUE(rawImg2P010.setImageStride(kImageWidth + 32, kImageWidth + 256));
+ ASSERT_TRUE(rawImg2P010.allocateMemory());
+ ASSERT_TRUE(rawImg2P010.loadRawResource(kYCbCrP010FileName));
+ UhdrCompressedStructWrapper jpgImg2(kImageWidth, kImageHeight);
+ ASSERT_TRUE(jpgImg2.allocateMemory());
+ ASSERT_EQ(uHdrLib.encodeJPEGR(rawImg2P010.getImageHandle(), sdr,
+ ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ jpgImg2.getImageHandle()),
+ OK);
+ auto jpg1 = jpgImg.getImageHandle();
+ auto jpg2 = jpgImg2.getImageHandle();
+ ASSERT_EQ(jpg1->length, jpg2->length);
+ ASSERT_EQ(0, memcmp(jpg1->data, jpg2->data, jpg1->length));
+ }
+
+ auto jpg1 = jpgImg.getImageHandle();
+
+#ifdef DUMP_OUTPUT
+ if (!writeFile("encode_api3_output.jpeg", jpg1->data, jpg1->length)) {
+ std::cerr << "unable to write output file" << std::endl;
+ }
+#endif
+
+ ASSERT_NO_FATAL_FAILURE(decodeJpegRImg(jpg1, "decode_api3_output.rgb"));
+}
+
+INSTANTIATE_TEST_SUITE_P(
+ JpegRAPIParameterizedTests, JpegRAPIEncodeAndDecodeTest,
+ ::testing::Combine(::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
+ ULTRAHDR_COLORGAMUT_BT2100),
+ ::testing::Values(ULTRAHDR_COLORGAMUT_BT709, ULTRAHDR_COLORGAMUT_P3,
+ ULTRAHDR_COLORGAMUT_BT2100)));
+
+// ============================================================================
+// Profiling
+// ============================================================================
+
+class Profiler {
+public:
+ void timerStart() { gettimeofday(&mStartingTime, nullptr); }
+
+ void timerStop() { gettimeofday(&mEndingTime, nullptr); }
+
+ int64_t elapsedTime() {
+ struct timeval elapsedMicroseconds;
+ elapsedMicroseconds.tv_sec = mEndingTime.tv_sec - mStartingTime.tv_sec;
+ elapsedMicroseconds.tv_usec = mEndingTime.tv_usec - mStartingTime.tv_usec;
+ return elapsedMicroseconds.tv_sec * 1000000 + elapsedMicroseconds.tv_usec;
+ }
+
+private:
+ struct timeval mStartingTime;
+ struct timeval mEndingTime;
+};
+
+class JpegRBenchmark : public JpegR {
+public:
+ void BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr p010Image,
+ ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr map);
+ void BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+ ultrahdr_metadata_ptr metadata, jr_uncompressed_ptr dest);
+
+private:
+ const int kProfileCount = 10;
+};
+
+void JpegRBenchmark::BenchmarkGenerateGainMap(jr_uncompressed_ptr yuv420Image,
+ jr_uncompressed_ptr p010Image,
+ ultrahdr_metadata_ptr metadata,
+ jr_uncompressed_ptr map) {
+ ASSERT_EQ(yuv420Image->width, p010Image->width);
+ ASSERT_EQ(yuv420Image->height, p010Image->height);
+ Profiler profileGenerateMap;
+ profileGenerateMap.timerStart();
+ for (auto i = 0; i < kProfileCount; i++) {
+ ASSERT_EQ(OK,
+ generateGainMap(yuv420Image, p010Image, ultrahdr_transfer_function::ULTRAHDR_TF_HLG,
+ metadata, map));
+ if (i != kProfileCount - 1) delete[] static_cast<uint8_t*>(map->data);
+ }
+ profileGenerateMap.timerStop();
+ ALOGE("Generate Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
+ profileGenerateMap.elapsedTime() / (kProfileCount * 1000.f));
+}
+
+void JpegRBenchmark::BenchmarkApplyGainMap(jr_uncompressed_ptr yuv420Image, jr_uncompressed_ptr map,
+ ultrahdr_metadata_ptr metadata,
+ jr_uncompressed_ptr dest) {
+ Profiler profileRecMap;
+ profileRecMap.timerStart();
+ for (auto i = 0; i < kProfileCount; i++) {
+ ASSERT_EQ(OK,
+ applyGainMap(yuv420Image, map, metadata, ULTRAHDR_OUTPUT_HDR_HLG,
+ metadata->maxContentBoost /* displayBoost */, dest));
+ }
+ profileRecMap.timerStop();
+ ALOGE("Apply Gain Map:- Res = %i x %i, time = %f ms", yuv420Image->width, yuv420Image->height,
+ profileRecMap.elapsedTime() / (kProfileCount * 1000.f));
+}
+
+TEST(JpegRTest, ProfileGainMapFuncs) {
+ UhdrUnCompressedStructWrapper rawImgP010(kImageWidth, kImageHeight, YCbCr_p010);
+ ASSERT_TRUE(rawImgP010.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT2100));
+ ASSERT_TRUE(rawImgP010.allocateMemory());
+ ASSERT_TRUE(rawImgP010.loadRawResource(kYCbCrP010FileName));
+ UhdrUnCompressedStructWrapper rawImg420(kImageWidth, kImageHeight, YCbCr_420);
+ ASSERT_TRUE(rawImg420.setImageColorGamut(ultrahdr_color_gamut::ULTRAHDR_COLORGAMUT_BT709));
+ ASSERT_TRUE(rawImg420.allocateMemory());
+ ASSERT_TRUE(rawImg420.loadRawResource(kYCbCr420FileName));
+ ultrahdr_metadata_struct metadata = {.version = "1.0"};
+ jpegr_uncompressed_struct map = {.data = NULL,
+ .width = 0,
+ .height = 0,
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+ {
+ auto rawImg = rawImgP010.getImageHandle();
+ if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
+ if (!rawImg->chroma_data) {
+ uint16_t* data = reinterpret_cast<uint16_t*>(rawImg->data);
+ rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
+ rawImg->chroma_stride = rawImg->luma_stride;
+ }
+ }
+ {
+ auto rawImg = rawImg420.getImageHandle();
+ if (rawImg->luma_stride == 0) rawImg->luma_stride = rawImg->width;
+ if (!rawImg->chroma_data) {
+ uint8_t* data = reinterpret_cast<uint8_t*>(rawImg->data);
+ rawImg->chroma_data = data + rawImg->luma_stride * rawImg->height;
+ rawImg->chroma_stride = rawImg->luma_stride / 2;
+ }
+ }
+
+ JpegRBenchmark benchmark;
+ ASSERT_NO_FATAL_FAILURE(benchmark.BenchmarkGenerateGainMap(rawImg420.getImageHandle(),
+ rawImgP010.getImageHandle(), &metadata,
+ &map));
+
+ const int dstSize = kImageWidth * kImageWidth * 4;
+ auto bufferDst = std::make_unique<uint8_t[]>(dstSize);
+ jpegr_uncompressed_struct dest = {.data = bufferDst.get(),
+ .width = 0,
+ .height = 0,
+ .colorGamut = ULTRAHDR_COLORGAMUT_UNSPECIFIED};
+
+ ASSERT_NO_FATAL_FAILURE(
+ benchmark.BenchmarkApplyGainMap(rawImg420.getImageHandle(), &map, &metadata, &dest));
+}
+
+} // namespace android::ultrahdr
diff --git a/libs/vibrator/ExternalVibration.cpp b/libs/vibrator/ExternalVibration.cpp
index ec906458a3..80e911c65d 100644
--- a/libs/vibrator/ExternalVibration.cpp
+++ b/libs/vibrator/ExternalVibration.cpp
@@ -22,15 +22,6 @@
#include <log/log.h>
#include <utils/Errors.h>
-
-// To guarantee if HapticScale enum has the same value as IExternalVibratorService
-static_assert(static_cast<int>(android::os::HapticScale::MUTE) == static_cast<int>(android::os::IExternalVibratorService::SCALE_MUTE));
-static_assert(static_cast<int>(android::os::HapticScale::VERY_LOW) == static_cast<int>(android::os::IExternalVibratorService::SCALE_VERY_LOW));
-static_assert(static_cast<int>(android::os::HapticScale::LOW) == static_cast<int>(android::os::IExternalVibratorService::SCALE_LOW));
-static_assert(static_cast<int>(android::os::HapticScale::NONE) == static_cast<int>(android::os::IExternalVibratorService::SCALE_NONE));
-static_assert(static_cast<int>(android::os::HapticScale::HIGH) == static_cast<int>(android::os::IExternalVibratorService::SCALE_HIGH));
-static_assert(static_cast<int>(android::os::HapticScale::VERY_HIGH) == static_cast<int>(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/OWNERS b/libs/vibrator/OWNERS
index d073e2bd46..c4de58a517 100644
--- a/libs/vibrator/OWNERS
+++ b/libs/vibrator/OWNERS
@@ -1 +1,2 @@
+# Bug component: 345036
include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/libs/vibrator/fuzzer/Android.bp b/libs/vibrator/fuzzer/Android.bp
index f2a313cb8a..faa77ca9df 100644
--- a/libs/vibrator/fuzzer/Android.bp
+++ b/libs/vibrator/fuzzer/Android.bp
@@ -18,6 +18,7 @@
*/
package {
+ default_team: "trendy_team_haptics_framework",
// 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"
@@ -47,6 +48,17 @@ cc_fuzz {
],
fuzz_config: {
- componentid: 155276,
+ cc: [
+ "android-haptics@google.com",
+ ],
+ componentid: 345036,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libvibrator",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
},
}
diff --git a/libs/vibrator/include/vibrator/ExternalVibration.h b/libs/vibrator/include/vibrator/ExternalVibration.h
index 760dbce149..00cd3cd256 100644
--- a/libs/vibrator/include/vibrator/ExternalVibration.h
+++ b/libs/vibrator/include/vibrator/ExternalVibration.h
@@ -23,6 +23,7 @@
#include <binder/Parcelable.h>
#include <system/audio.h>
#include <utils/RefBase.h>
+#include <vibrator/ExternalVibrationUtils.h>
namespace android {
namespace os {
@@ -44,6 +45,10 @@ public :
audio_attributes_t getAudioAttributes() const { return mAttrs; }
sp<IExternalVibrationController> 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<IBinder> 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 c588bfdedd..ca219d3cbf 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/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h b/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h
index 2d0a4ea6ec..371ed89fc9 100644
--- a/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h
+++ b/libs/vr/libpdx/private/pdx/rpc/string_wrapper.h
@@ -17,12 +17,12 @@ namespace rpc {
// C strings more efficient by avoiding unnecessary copies when remote method
// signatures specify std::basic_string arguments or return values.
template <typename CharT = std::string::value_type,
- typename Traits = std::char_traits<CharT>>
+ typename Traits = std::char_traits<std::remove_cv_t<CharT>>>
class StringWrapper {
public:
// Define types in the style of STL strings to support STL operators.
typedef Traits traits_type;
- typedef typename Traits::char_type value_type;
+ typedef CharT value_type;
typedef std::size_t size_type;
typedef value_type& reference;
typedef const value_type& const_reference;
diff --git a/opengl/Android.bp b/opengl/Android.bp
index b15694bf50..4454f36b67 100644
--- a/opengl/Android.bp
+++ b/opengl/Android.bp
@@ -72,6 +72,10 @@ cc_library_headers {
llndk: {
llndk_headers: true,
},
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
}
subdirs = [
diff --git a/opengl/TEST_MAPPING b/opengl/TEST_MAPPING
index d391dce2de..7c50a945fa 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 501bf58531..c787fc9717 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 62cf2555ca..16de3908f8 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 aecfc6b077..b3a4bc19fd 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 <errno.h>
#include <inttypes.h>
#include <log/log.h>
+#include <utils/Trace.h>
#include <chrono>
@@ -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 751f3be81a..4a0fac4ce5 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 <errno.h>
@@ -24,6 +26,7 @@
#include <unistd.h>
#include <log/log.h>
+#include <utils/Trace.h>
// 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 393703f234..f083b0d6ca 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 dd14bcfb55..af0bcffc1f 100644
--- a/opengl/libs/EGL/Loader.cpp
+++ b/opengl/libs/EGL/Loader.cpp
@@ -21,6 +21,7 @@
#include <android-base/properties.h>
#include <android/dlext.h>
+#include <cutils/properties.h>
#include <dirent.h>
#include <dlfcn.h>
#include <graphicsenv/GraphicsEnv.h>
@@ -40,24 +41,52 @@ namespace android {
/*
* EGL userspace drivers must be provided either:
* - as a single library:
- * /vendor/lib/egl/libGLES.so
+ * /vendor/${LIB}/egl/libGLES.so
*
* - as separate libraries:
- * /vendor/lib/egl/libEGL.so
- * /vendor/lib/egl/libGLESv1_CM.so
- * /vendor/lib/egl/libGLESv2.so
+ * /vendor/${LIB}/egl/libEGL.so
+ * /vendor/${LIB}/egl/libGLESv1_CM.so
+ * /vendor/${LIB}/egl/libGLESv2.so
*
* For backward compatibility and to facilitate the transition to
* this new naming scheme, the loader will additionally look for:
*
- * /{vendor|system}/lib/egl/lib{GLES | [EGL|GLESv1_CM|GLESv2]}_*.so
+ * /vendor/${LIB}/egl/lib{GLES | [EGL|GLESv1_CM|GLESv2]}_${SUFFIX}.so
*
*/
-Loader& Loader::getInstance() {
- static Loader loader;
- return loader;
-}
+#ifndef SYSTEM_LIB_PATH
+#if defined(__LP64__)
+#define SYSTEM_LIB_PATH "/system/lib64"
+#else
+#define SYSTEM_LIB_PATH "/system/lib"
+#endif
+#endif
+
+static const char* PERSIST_DRIVER_SUFFIX_PROPERTY = "persist.graphics.egl";
+static const char* RO_DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
+static const char* RO_BOARD_PLATFORM_PROPERTY = "ro.board.platform";
+static const char* ANGLE_SUFFIX_VALUE = "angle";
+
+static const char* HAL_SUBNAME_KEY_PROPERTIES[3] = {
+ PERSIST_DRIVER_SUFFIX_PROPERTY,
+ RO_DRIVER_SUFFIX_PROPERTY,
+ RO_BOARD_PLATFORM_PROPERTY,
+};
+
+static const char* const VENDOR_LIB_EGL_DIR =
+#if defined(__LP64__)
+ "/vendor/lib64/egl";
+#else
+ "/vendor/lib/egl";
+#endif
+
+static const char* const SYSTEM_LIB_DIR =
+#if defined(__LP64__)
+ "/system/lib64";
+#else
+ "/system/lib";
+#endif
static void* do_dlopen(const char* path, int mode) {
ATRACE_CALL();
@@ -79,6 +108,17 @@ static int do_android_unload_sphal_library(void* dso) {
return android_unload_sphal_library(dso);
}
+static void* load_wrapper(const char* path) {
+ void* so = do_dlopen(path, RTLD_NOW | RTLD_LOCAL);
+ ALOGE_IF(!so, "dlopen(\"%s\") failed: %s", path, dlerror());
+ return so;
+}
+
+Loader& Loader::getInstance() {
+ static Loader loader;
+ return loader;
+}
+
Loader::driver_t::driver_t(void* gles)
{
dso[0] = gles;
@@ -122,36 +162,30 @@ Loader::Loader()
Loader::~Loader() {
}
-static void* load_wrapper(const char* path) {
- void* so = do_dlopen(path, RTLD_NOW | RTLD_LOCAL);
- ALOGE_IF(!so, "dlopen(\"%s\") failed: %s", path, dlerror());
- return so;
-}
-
-#ifndef EGL_WRAPPER_DIR
-#if defined(__LP64__)
-#define EGL_WRAPPER_DIR "/system/lib64"
-#else
-#define EGL_WRAPPER_DIR "/system/lib"
-#endif
-#endif
-
-static const char* DRIVER_SUFFIX_PROPERTY = "ro.hardware.egl";
-
-static const char* HAL_SUBNAME_KEY_PROPERTIES[2] = {
- DRIVER_SUFFIX_PROPERTY,
- "ro.board.platform",
-};
-
+// Check whether the loaded system drivers should be unloaded in order to
+// load ANGLE or the updatable graphics drivers.
+// If ANGLE namespace is set, it means the application is identified to run on top of ANGLE.
+// If updatable graphics driver namespace is set, it means the application is identified to
+// run on top of updatable graphics drivers.
static bool should_unload_system_driver(egl_connection_t* cnx) {
// Return false if the system driver has been unloaded once.
if (cnx->systemDriverUnloaded) {
return false;
}
- // Return true if Angle namespace is set.
+ // Return true if ANGLE namespace is set.
android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
if (ns) {
+ // Unless the default GLES driver is ANGLE and the process should use system ANGLE, since
+ // the intended GLES driver is already loaded.
+ // This should be updated in a later patch that cleans up namespaces
+ if (!(cnx->angleLoaded && android::GraphicsEnv::getInstance().shouldUseSystemAngle())) {
+ return true;
+ }
+ }
+
+ // Return true if native GLES drivers should be used and ANGLE is already loaded.
+ if (android::GraphicsEnv::getInstance().shouldUseNativeDriver() && cnx->angleLoaded) {
return true;
}
@@ -197,18 +231,17 @@ void Loader::unload_system_driver(egl_connection_t* cnx) {
do_android_unload_sphal_library(hnd->dso[0]);
}
cnx->dso = nullptr;
+ cnx->angleLoaded = false;
}
cnx->systemDriverUnloaded = true;
}
-void* Loader::open(egl_connection_t* cnx)
-{
+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 (cnx->dso && should_unload_system_driver(cnx)) {
unload_system_driver(cnx);
}
@@ -217,62 +250,73 @@ 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) {
+ // Firstly, try to load ANGLE driver, if ANGLE should be loaded and fail, abort.
+ if (android::GraphicsEnv::getInstance().shouldUseAngle()) {
hnd = attempt_to_load_angle(cnx);
+ LOG_ALWAYS_FATAL_IF(!hnd, "Failed to load ANGLE.");
}
if (!hnd) {
// Secondly, try to load from driver apk.
hnd = attempt_to_load_updated_driver(cnx);
+
+ // If updated driver apk is set but fail to load, abort here.
+ LOG_ALWAYS_FATAL_IF(android::GraphicsEnv::getInstance().getDriverNamespace(),
+ "couldn't find an OpenGL ES implementation from %s",
+ android::GraphicsEnv::getInstance().getDriverPath().c_str());
}
+ // Attempt to load native GLES drivers specified by ro.hardware.egl if native is selected.
+ // If native is selected but fail to load, abort.
+ if (!hnd && android::GraphicsEnv::getInstance().shouldUseNativeDriver()) {
+ auto driverSuffix = base::GetProperty(RO_DRIVER_SUFFIX_PROPERTY, "");
+ LOG_ALWAYS_FATAL_IF(driverSuffix.empty(),
+ "Native GLES driver is selected but not specified in %s",
+ RO_DRIVER_SUFFIX_PROPERTY);
+ hnd = attempt_to_load_system_driver(cnx, driverSuffix.c_str(), true);
+ LOG_ALWAYS_FATAL_IF(!hnd, "Native GLES driver is selected but failed to load. %s=%s",
+ RO_DRIVER_SUFFIX_PROPERTY, driverSuffix.c_str());
+ }
+
+ // Finally, try to load default driver.
bool failToLoadFromDriverSuffixProperty = false;
if (!hnd) {
- // If updated driver apk is set but fail to load, abort here.
- if (android::GraphicsEnv::getInstance().getDriverNamespace()) {
- LOG_ALWAYS_FATAL("couldn't find an OpenGL ES implementation from %s",
- android::GraphicsEnv::getInstance().getDriverPath().c_str());
- }
- // Finally, try to load system driver. If ANGLE is the system driver
- // (i.e. we are forcing the legacy system driver instead of ANGLE), use
- // the driver suffix that was passed down from above.
- if (shouldForceLegacyDriver) {
- std::string suffix = android::GraphicsEnv::getInstance().getLegacySuffix();
- hnd = attempt_to_load_system_driver(cnx, suffix.c_str(), true);
- } else {
- // Start by searching for the library name appended by the system
- // properties of the GLES userspace driver in both locations.
- // i.e.:
- // libGLES_${prop}.so, or:
- // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so
- for (auto key : HAL_SUBNAME_KEY_PROPERTIES) {
- auto prop = base::GetProperty(key, "");
- if (prop.empty()) {
- continue;
- }
- hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true);
- if (hnd) {
- break;
- } else if (strcmp(key, DRIVER_SUFFIX_PROPERTY) == 0) {
- failToLoadFromDriverSuffixProperty = true;
- }
+ // Start by searching for the library name appended by the system
+ // properties of the GLES userspace driver in both locations.
+ // i.e.:
+ // libGLES_${prop}.so, or:
+ // libEGL_${prop}.so, libGLESv1_CM_${prop}.so, libGLESv2_${prop}.so
+ for (auto key : HAL_SUBNAME_KEY_PROPERTIES) {
+ auto prop = base::GetProperty(key, "");
+ if (prop.empty()) {
+ continue;
+ }
+ hnd = attempt_to_load_system_driver(cnx, prop.c_str(), true);
+ if (!hnd) {
+ ALOGD("Failed to load drivers from property %s with value %s", key, prop.c_str());
+ failToLoadFromDriverSuffixProperty = true;
}
+
+ // Abort regardless of whether subsequent properties are set, the value must be set
+ // correctly with the first property that has a value.
+ break;
}
}
if (!hnd) {
- // Can't find graphics driver by appending system properties, now search for the exact name
- // without any suffix of the GLES userspace driver in both locations.
+ // Can't find graphics driver by appending the value from system properties, now search for
+ // the exact name without any suffix of the GLES userspace driver in both locations.
// i.e.:
// libGLES.so, or:
// libEGL.so, libGLESv1_CM.so, libGLESv2.so
hnd = attempt_to_load_system_driver(cnx, nullptr, true);
}
- if (!hnd && !failToLoadFromDriverSuffixProperty) {
+ if (!hnd && !failToLoadFromDriverSuffixProperty &&
+ property_get_int32("ro.vendor.api_level", 0) < __ANDROID_API_U__) {
+ // Still can't find the graphics drivers with the exact name. This time try to use wildcard
+ // matching if the device is launched before Android 14.
hnd = attempt_to_load_system_driver(cnx, nullptr, false);
}
@@ -281,24 +325,26 @@ void* Loader::open(egl_connection_t* cnx)
false, systemTime() - openTime);
} else {
// init_angle_backend will check if loaded driver is ANGLE or not,
- // will set cnx->useAngle appropriately.
+ // will set cnx->angleLoaded appropriately.
// Do this here so that we use ANGLE path when driver is ANGLE (e.g. loaded as native),
// not just loading ANGLE as option.
- init_angle_backend(hnd->dso[2], cnx);
+ attempt_to_init_angle_backend(hnd->dso[2], 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");
+ cnx->libEgl = load_wrapper(SYSTEM_LIB_PATH "/libEGL.so");
}
if (!cnx->libGles1) {
- cnx->libGles1 = load_wrapper(EGL_WRAPPER_DIR "/libGLESv1_CM.so");
+ cnx->libGles1 = load_wrapper(SYSTEM_LIB_PATH "/libGLESv1_CM.so");
}
if (!cnx->libGles2) {
- cnx->libGles2 = load_wrapper(EGL_WRAPPER_DIR "/libGLESv2.so");
+ cnx->libGles2 = load_wrapper(SYSTEM_LIB_PATH "/libGLESv2.so");
}
if (!cnx->libEgl || !cnx->libGles2 || !cnx->libGles1) {
@@ -324,7 +370,7 @@ void Loader::close(egl_connection_t* cnx)
delete hnd;
cnx->dso = nullptr;
- cnx->useAngle = false;
+ cnx->angleLoaded = false;
}
void Loader::init_api(void* dso,
@@ -396,113 +442,108 @@ void Loader::init_api(void* dso,
}
}
-static void* load_system_driver(const char* kind, const char* suffix, const bool exact) {
- ATRACE_CALL();
- class MatchFile {
- public:
- static std::string find(const char* libraryName, const bool exact) {
- const char* const searchPaths[] = {
-#if defined(__LP64__)
- "/vendor/lib64/egl",
- "/system/lib64/egl"
-#else
- "/vendor/lib/egl",
- "/system/lib/egl"
-#endif
- };
+static std::string findLibrary(const std::string libraryName, const std::string searchPath,
+ const bool exact) {
+ if (exact) {
+ std::string absolutePath = searchPath + "/" + libraryName + ".so";
+ if (!access(absolutePath.c_str(), R_OK)) {
+ return absolutePath;
+ }
+ return std::string();
+ }
- for (auto dir : searchPaths) {
- std::string absolutePath;
- if (find(absolutePath, libraryName, dir, exact)) {
- return absolutePath;
- }
+ DIR* d = opendir(searchPath.c_str());
+ if (d != nullptr) {
+ struct dirent* e;
+ while ((e = readdir(d)) != nullptr) {
+ if (e->d_type == DT_DIR) {
+ continue;
}
-
- // Driver not found. gah.
- return std::string();
- }
- private:
- static bool find(std::string& result,
- const std::string& pattern, const char* const search, bool exact) {
- if (exact) {
- std::string absolutePath = std::string(search) + "/" + pattern + ".so";
- if (!access(absolutePath.c_str(), R_OK)) {
- result = absolutePath;
- return true;
- }
- return false;
+ if (!strcmp(e->d_name, "libGLES_android.so")) {
+ // always skip the software renderer
+ continue;
}
-
- DIR* d = opendir(search);
- if (d != nullptr) {
- struct dirent* e;
- while ((e = readdir(d)) != nullptr) {
- if (e->d_type == DT_DIR) {
- continue;
- }
- if (!strcmp(e->d_name, "libGLES_android.so")) {
- // always skip the software renderer
- continue;
- }
- if (strstr(e->d_name, pattern.c_str()) == e->d_name) {
- if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
- result = std::string(search) + "/" + e->d_name;
- closedir(d);
- return true;
- }
- }
+ if (strstr(e->d_name, libraryName.c_str()) == e->d_name) {
+ if (!strcmp(e->d_name + strlen(e->d_name) - 3, ".so")) {
+ std::string result = searchPath + "/" + e->d_name;
+ closedir(d);
+ return result;
}
- closedir(d);
}
- return false;
}
- };
+ closedir(d);
+ }
+ // Driver not found. gah.
+ return std::string();
+}
+
+static void* load_system_driver(const char* kind, const char* suffix, const bool exact) {
+ ATRACE_CALL();
std::string libraryName = std::string("lib") + kind;
if (suffix) {
libraryName += std::string("_") + suffix;
} else if (!exact) {
- // Deprecated: we look for files that match
- // libGLES_*.so, or:
+ // Deprecated for devices launching in Android 14
+ // Look for files that match
+ // libGLES_*.so, or,
// libEGL_*.so, libGLESv1_CM_*.so, libGLESv2_*.so
libraryName += std::string("_");
}
- std::string absolutePath = MatchFile::find(libraryName.c_str(), exact);
+
+ void* dso = nullptr;
+
+ const bool isSuffixAngle = suffix != nullptr && strcmp(suffix, ANGLE_SUFFIX_VALUE) == 0;
+ const std::string absolutePath =
+ findLibrary(libraryName, isSuffixAngle ? SYSTEM_LIB_PATH : VENDOR_LIB_EGL_DIR, exact);
if (absolutePath.empty()) {
// this happens often, we don't want to log an error
return nullptr;
}
- const char* const driver_absolute_path = absolutePath.c_str();
+ const char* const driverAbsolutePath = absolutePath.c_str();
+
+ // Currently the default driver is unlikely to be ANGLE on most devices,
+ // hence put this first. Only use sphal namespace when system ANGLE binaries
+ // are not the default drivers.
+ if (!isSuffixAngle) {
+ // Try to load drivers from the 'sphal' namespace, if it exist. Fall back to
+ // the original routine when the namespace does not exist.
+ // See /system/linkerconfig/contents/namespace for the configuration of the
+ // sphal namespace.
+ dso = do_android_load_sphal_library(driverAbsolutePath, RTLD_NOW | RTLD_LOCAL);
+ } else {
+ // Try to load drivers from the default namespace.
+ // See /system/linkerconfig/contents/namespace for the configuration of the
+ // default namespace.
+ dso = do_dlopen(driverAbsolutePath, RTLD_NOW | RTLD_LOCAL);
+ }
- // Try to load drivers from the 'sphal' namespace, if it exist. Fall back to
- // the original routine when the namespace does not exist.
- // See /system/core/rootdir/etc/ld.config.txt for the configuration of the
- // sphal namespace.
- void* dso = do_android_load_sphal_library(driver_absolute_path,
- RTLD_NOW | RTLD_LOCAL);
if (dso == nullptr) {
const char* err = dlerror();
- ALOGE("load_driver(%s): %s", driver_absolute_path, err ? err : "unknown");
+ ALOGE("load_driver(%s): %s", driverAbsolutePath, err ? err : "unknown");
return nullptr;
}
- ALOGD("loaded %s", driver_absolute_path);
+ ALOGV("loaded %s", driverAbsolutePath);
return dso;
}
static void* load_angle(const char* kind, android_namespace_t* ns) {
- const android_dlextinfo dlextinfo = {
- .flags = ANDROID_DLEXT_USE_NAMESPACE,
- .library_namespace = ns,
- };
-
std::string name = std::string("lib") + kind + "_angle.so";
+ void* so = nullptr;
- void* so = do_android_dlopen_ext(name.c_str(), RTLD_LOCAL | RTLD_NOW, &dlextinfo);
+ if (android::GraphicsEnv::getInstance().shouldUseSystemAngle()) {
+ so = do_dlopen(name.c_str(), RTLD_NOW | RTLD_LOCAL);
+ } else {
+ const android_dlextinfo dlextinfo = {
+ .flags = ANDROID_DLEXT_USE_NAMESPACE,
+ .library_namespace = ns,
+ };
+ 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());
@@ -537,15 +578,15 @@ static void* load_updated_driver(const char* kind, android_namespace_t* ns) {
Loader::driver_t* Loader::attempt_to_load_angle(egl_connection_t* cnx) {
ATRACE_CALL();
- if (!android::GraphicsEnv::getInstance().shouldUseAngle()) {
- return nullptr;
- }
-
android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
- if (!ns) {
+ // ANGLE namespace is used for loading ANGLE from apk, and hence if namespace is not
+ // constructed, it means ANGLE apk is not set to be the OpenGL ES driver.
+ // Hence skip if ANGLE apk and system ANGLE are not set to be the OpenGL ES driver.
+ if (!ns && !android::GraphicsEnv::getInstance().shouldUseSystemAngle()) {
return nullptr;
}
+ // use ANGLE APK driver
android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::ANGLE);
driver_t* hnd = nullptr;
@@ -566,14 +607,17 @@ Loader::driver_t* Loader::attempt_to_load_angle(egl_connection_t* cnx) {
return hnd;
}
-void Loader::init_angle_backend(void* dso, egl_connection_t* cnx) {
- void* pANGLEGetDisplayPlatform = dlsym(dso, "ANGLEGetDisplayPlatform");
- if (pANGLEGetDisplayPlatform) {
- ALOGV("ANGLE GLES library in use");
- cnx->useAngle = true;
+void Loader::attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx) {
+ cnx->angleGetDisplayPlatformFunc = dlsym(dso, "ANGLEGetDisplayPlatform");
+ cnx->angleResetDisplayPlatformFunc = dlsym(dso, "ANGLEResetDisplayPlatform");
+
+ if (cnx->angleGetDisplayPlatformFunc) {
+ ALOGV("ANGLE GLES library loaded");
+ cnx->angleLoaded = true;
+ android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::ANGLE);
} else {
- ALOGV("Native GLES library in use");
- cnx->useAngle = false;
+ ALOGV("Native GLES library loaded");
+ cnx->angleLoaded = false;
}
}
@@ -614,7 +658,13 @@ Loader::driver_t* Loader::attempt_to_load_updated_driver(egl_connection_t* cnx)
Loader::driver_t* Loader::attempt_to_load_system_driver(egl_connection_t* cnx, const char* suffix,
const bool exact) {
ATRACE_CALL();
- android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::GL);
+ if (suffix && strcmp(suffix, "angle") == 0) {
+ // use system ANGLE driver
+ android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::ANGLE);
+ } else {
+ android::GraphicsEnv::getInstance().setDriverToLoad(android::GpuStatsInfo::Driver::GL);
+ }
+
driver_t* hnd = nullptr;
void* dso = load_system_driver("GLES", suffix, exact);
if (dso) {
diff --git a/opengl/libs/EGL/Loader.h b/opengl/libs/EGL/Loader.h
index 81742ab9ae..cadbd4639b 100644
--- a/opengl/libs/EGL/Loader.h
+++ b/opengl/libs/EGL/Loader.h
@@ -57,7 +57,7 @@ private:
driver_t* attempt_to_load_system_driver(egl_connection_t* cnx, const char* suffix, const bool exact);
void unload_system_driver(egl_connection_t* cnx);
void initialize_api(void* dso, egl_connection_t* cnx, uint32_t mask);
- void init_angle_backend(void* dso, egl_connection_t* cnx);
+ void attempt_to_init_angle_backend(void* dso, egl_connection_t* cnx);
static __attribute__((noinline)) void init_api(void* dso, const char* const* api,
const char* const* ref_api,
diff --git a/opengl/libs/EGL/MultifileBlobCache.cpp b/opengl/libs/EGL/MultifileBlobCache.cpp
new file mode 100644
index 0000000000..9905210014
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.cpp
@@ -0,0 +1,937 @@
+/*
+ ** 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 <android-base/properties.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <log/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <time.h>
+#include <unistd.h>
+#include <utime.h>
+
+#include <algorithm>
+#include <chrono>
+#include <limits>
+#include <locale>
+
+#include <utils/JenkinsHash.h>
+
+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 the entry
+ munmap(entry.entryBuffer, entry.entrySize);
+ } 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,
+ size_t maxTotalEntries, const std::string& baseDir)
+ : mInitialized(false),
+ mCacheVersion(0),
+ mMaxKeySize(maxKeySize),
+ mMaxValueSize(maxValueSize),
+ mMaxTotalSize(maxTotalSize),
+ mMaxTotalEntries(maxTotalEntries),
+ mTotalCacheSize(0),
+ mTotalCacheEntries(0),
+ mHotCacheLimit(0),
+ mHotCacheSize(0),
+ mWorkerThreadIdle(true) {
+ if (baseDir.empty()) {
+ ALOGV("INIT: no baseDir provided in MultifileBlobCache constructor, returning early.");
+ return;
+ }
+
+ // Set the cache version, override if debug value set
+ mCacheVersion = kMultifileBlobCacheVersion;
+ int debugCacheVersion = base::GetIntProperty("debug.egl.blobcache.cache_version", -1);
+ if (debugCacheVersion >= 0) {
+ ALOGV("INIT: Using %u as cacheVersion instead of %u", debugCacheVersion, mCacheVersion);
+ mCacheVersion = debugCacheVersion;
+ }
+
+ // Set the platform build ID, override if debug value set
+ mBuildId = base::GetProperty("ro.build.id", "");
+ std::string debugBuildId = base::GetProperty("debug.egl.blobcache.build_id", "");
+ if (!debugBuildId.empty()) {
+ ALOGV("INIT: Using %s as buildId instead of %s", debugBuildId.c_str(), mBuildId.c_str());
+ if (debugBuildId.length() > PROP_VALUE_MAX) {
+ ALOGV("INIT: debugBuildId is too long (%zu), reduce it to %u", debugBuildId.length(),
+ PROP_VALUE_MAX);
+ }
+ mBuildId = debugBuildId;
+ }
+
+ // 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
+ bool statusGood = false;
+
+ // Check that our cacheVersion and buildId match
+ struct stat st;
+ if (stat(mMultifileDirName.c_str(), &st) == 0) {
+ if (checkStatus(mMultifileDirName.c_str())) {
+ statusGood = true;
+ } else {
+ ALOGV("INIT: Cache status has changed, clearing the cache");
+ if (!clearCache()) {
+ ALOGE("INIT: Unable to clear cache");
+ return;
+ }
+ }
+ }
+
+ if (statusGood) {
+ // 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 ||
+ strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
+ 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<uint32_t>(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("INIT: 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<void*>(&header), sizeof(MultifileHeader));
+ if (result != sizeof(MultifileHeader)) {
+ ALOGE("INIT: Error reading MultifileHeader from cache entry (%s): %s",
+ fullPath.c_str(), std::strerror(errno));
+ close(fd);
+ 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("INIT: Error removing %s: %s", fullPath.c_str(),
+ std::strerror(errno));
+ }
+ close(fd);
+ continue;
+ }
+
+ // Note: Converting from off_t (signed) to size_t (unsigned)
+ size_t fileSize = static_cast<size_t>(st.st_size);
+
+ // Memory map the file
+ uint8_t* mappedEntry = reinterpret_cast<uint8_t*>(
+ mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+ // We can close the file now and the mmap will remain
+ close(fd);
+
+ 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))) {
+ ALOGV("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) {
+ ALOGV("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("INIT: 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);
+ return;
+ }
+ } else {
+ // If we're not keeping it in hot cache, unmap it now
+ munmap(mappedEntry, fileSize);
+ }
+ }
+ 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);
+ return;
+ }
+
+ // Create new status file
+ if (!createStatus(mMultifileDirName.c_str())) {
+ ALOGE("INIT: Failed to create status file!");
+ return;
+ }
+ }
+
+ ALOGV("INIT: Multifile BlobCache initialization succeeded");
+ 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<const uint8_t*>(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 || getTotalEntries() + 1 > mMaxTotalEntries) {
+ 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<void*>(buffer), static_cast<const void*>(&header),
+ sizeof(android::MultifileHeader));
+ // Write the key and value after the header
+ memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader)), static_cast<const void*>(key),
+ keySize);
+ memcpy(static_cast<void*>(buffer + sizeof(MultifileHeader) + keySize),
+ static_cast<const void*>(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<std::mutex> 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<const uint8_t*>(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<std::mutex> 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<uint8_t*>(mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fd, 0));
+
+ // We can close the file now and the mmap will remain
+ close(fd);
+
+ if (cacheEntry == MAP_FAILED) {
+ ALOGE("Failed to mmap cacheEntry, error: %s", std::strerror(errno));
+ 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<MultifileHeader*>(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++);
+ }
+}
+
+bool MultifileBlobCache::createStatus(const std::string& baseDir) {
+ // Populate the status struct
+ struct MultifileStatus status;
+ memset(&status, 0, sizeof(status));
+ status.magic = kMultifileMagic;
+ status.cacheVersion = mCacheVersion;
+
+ // Copy the buildId string in, up to our allocated space
+ strncpy(status.buildId, mBuildId.c_str(),
+ mBuildId.length() > PROP_VALUE_MAX ? PROP_VALUE_MAX : mBuildId.length());
+
+ // Finally update the crc, using cacheVersion and everything the follows
+ status.crc =
+ crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+ sizeof(status) - offsetof(MultifileStatus, cacheVersion));
+
+ // Create the status file
+ std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+ int fd = open(cacheStatus.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
+ if (fd == -1) {
+ ALOGE("STATUS(CREATE): Unable to create status file: %s, error: %s", cacheStatus.c_str(),
+ std::strerror(errno));
+ return false;
+ }
+
+ // Write the buffer contents to disk
+ ssize_t result = write(fd, &status, sizeof(status));
+ close(fd);
+ if (result != sizeof(status)) {
+ ALOGE("STATUS(CREATE): Error writing cache status file: %s, error %s", cacheStatus.c_str(),
+ std::strerror(errno));
+ return false;
+ }
+
+ ALOGV("STATUS(CREATE): Created status file: %s", cacheStatus.c_str());
+ return true;
+}
+
+bool MultifileBlobCache::checkStatus(const std::string& baseDir) {
+ std::string cacheStatus = baseDir + "/" + kMultifileBlobCacheStatusFile;
+
+ // Does status exist
+ struct stat st;
+ if (stat(cacheStatus.c_str(), &st) != 0) {
+ ALOGV("STATUS(CHECK): Status file (%s) missing", cacheStatus.c_str());
+ return false;
+ }
+
+ // If the status entry is damaged or no good, remove it
+ if (st.st_size <= 0 || st.st_atime <= 0) {
+ ALOGE("STATUS(CHECK): Cache status has invalid stats!");
+ return false;
+ }
+
+ // Open the file so we can read its header
+ int fd = open(cacheStatus.c_str(), O_RDONLY);
+ if (fd == -1) {
+ ALOGE("STATUS(CHECK): Cache error - failed to open cacheStatus: %s, error: %s",
+ cacheStatus.c_str(), std::strerror(errno));
+ return false;
+ }
+
+ // Read in the status header
+ MultifileStatus status;
+ size_t result = read(fd, static_cast<void*>(&status), sizeof(MultifileStatus));
+ close(fd);
+ if (result != sizeof(MultifileStatus)) {
+ ALOGE("STATUS(CHECK): Error reading cache status (%s): %s", cacheStatus.c_str(),
+ std::strerror(errno));
+ return false;
+ }
+
+ // Verify header magic
+ if (status.magic != kMultifileMagic) {
+ ALOGE("STATUS(CHECK): Cache status has bad magic (%u)!", status.magic);
+ return false;
+ }
+
+ // Ensure we have a good CRC
+ if (status.crc !=
+ crc32c(reinterpret_cast<uint8_t*>(&status) + offsetof(MultifileStatus, cacheVersion),
+ sizeof(status) - offsetof(MultifileStatus, cacheVersion))) {
+ ALOGE("STATUS(CHECK): Cache status failed CRC check!");
+ return false;
+ }
+
+ // Check cacheVersion
+ if (status.cacheVersion != mCacheVersion) {
+ ALOGV("STATUS(CHECK): Cache version has changed! old(%u) new(%u)", status.cacheVersion,
+ mCacheVersion);
+ return false;
+ }
+
+ // Check buildId
+ if (strcmp(status.buildId, mBuildId.c_str()) != 0) {
+ ALOGV("STATUS(CHECK): BuildId has changed! old(%s) new(%s)", status.buildId,
+ mBuildId.c_str());
+ return false;
+ }
+
+ // All checks passed!
+ ALOGV("STATUS(CHECK): Status file is good! cacheVersion(%u), buildId(%s) file(%s)",
+ status.cacheVersion, status.buildId, cacheStatus.c_str());
+ return true;
+}
+
+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;
+ mTotalCacheEntries++;
+}
+
+void MultifileBlobCache::decreaseTotalCacheSize(size_t fileSize) {
+ mTotalCacheSize -= fileSize;
+ mTotalCacheEntries--;
+}
+
+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 cacheSizeLimit, size_t cacheEntryLimit) {
+ // 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();
+ size_t totalCacheEntries = getTotalEntries();
+ if (totalCacheSize <= cacheSizeLimit && totalCacheEntries <= cacheEntryLimit) {
+ // Success
+ ALOGV("LRU: Reduced cache to size %zu entries %zu", totalCacheSize, totalCacheEntries);
+ return true;
+ }
+ }
+
+ ALOGV("LRU: Cache is empty");
+ return false;
+}
+
+// Clear the cache by removing all entries and deleting the directory
+bool MultifileBlobCache::clearCache() {
+ DIR* dir;
+ struct dirent* entry;
+ dir = opendir(mMultifileDirName.c_str());
+ if (dir == nullptr) {
+ ALOGE("CLEAR: Unable to open multifile dir: %s", mMultifileDirName.c_str());
+ return false;
+ }
+
+ // Delete all entries and the status file
+ 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;
+ if (remove(fullPath.c_str()) != 0) {
+ ALOGE("CLEAR: Error removing %s: %s", fullPath.c_str(), std::strerror(errno));
+ return false;
+ }
+ }
+
+ // Delete the directory
+ if (remove(mMultifileDirName.c_str()) != 0) {
+ ALOGE("CLEAR: Error removing %s: %s", mMultifileDirName.c_str(), std::strerror(errno));
+ return false;
+ }
+
+ ALOGV("CLEAR: Cleared the multifile blobcache");
+ return true;
+}
+
+// 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
+// We use the same limit to manage size and entry count
+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, entries %zu",
+ mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / kCacheLimitDivisor);
+ if (!applyLRU(mMaxTotalSize / kCacheLimitDivisor, mMaxTotalEntries / 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<MultifileHeader*>(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<std::mutex> lock(mDeferredWriteStatusMutex);
+ typedef std::multimap<uint32_t, uint8_t*>::iterator entryIter;
+ std::pair<entryIter, entryIter> 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<std::mutex> 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<std::mutex> queueLock(mWorkerMutex);
+ mTasks.emplace(std::move(task));
+ mWorkAvailableCondition.notify_one();
+}
+
+// Wait until all tasks have been completed
+void MultifileBlobCache::waitForWorkComplete() {
+ std::unique_lock<std::mutex> 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 0000000000..18566c2892
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache.h
@@ -0,0 +1,201 @@
+/*
+ ** 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 <EGL/egl.h>
+#include <EGL/eglext.h>
+
+#include <android-base/thread_annotations.h>
+#include <cutils/properties.h>
+#include <future>
+#include <map>
+#include <queue>
+#include <string>
+#include <thread>
+#include <unordered_map>
+#include <unordered_set>
+
+#include "FileBlobCache.h"
+
+namespace android {
+
+constexpr uint32_t kMultifileBlobCacheVersion = 1;
+constexpr char kMultifileBlobCacheStatusFile[] = "cache.status";
+
+struct MultifileHeader {
+ uint32_t magic;
+ uint32_t crc;
+ EGLsizeiANDROID keySize;
+ EGLsizeiANDROID valueSize;
+};
+
+struct MultifileEntryStats {
+ EGLsizeiANDROID valueSize;
+ size_t fileSize;
+ time_t accessTime;
+};
+
+struct MultifileStatus {
+ uint32_t magic;
+ uint32_t crc;
+ uint32_t cacheVersion;
+ char buildId[PROP_VALUE_MAX];
+};
+
+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,
+ size_t maxTotalEntries, 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; }
+ size_t getTotalEntries() const { return mTotalCacheEntries; }
+
+ const std::string& getCurrentBuildId() const { return mBuildId; }
+ void setCurrentBuildId(const std::string& buildId) { mBuildId = buildId; }
+
+ uint32_t getCurrentCacheVersion() const { return mCacheVersion; }
+ void setCurrentCacheVersion(uint32_t cacheVersion) { mCacheVersion = cacheVersion; }
+
+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);
+
+ bool createStatus(const std::string& baseDir);
+ bool checkStatus(const std::string& baseDir);
+
+ 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);
+
+ bool clearCache();
+ void trimCache();
+ bool applyLRU(size_t cacheSizeLimit, size_t cacheEntryLimit);
+
+ bool mInitialized;
+ std::string mMultifileDirName;
+
+ std::string mBuildId;
+ uint32_t mCacheVersion;
+
+ std::unordered_set<uint32_t> mEntries;
+ std::unordered_map<uint32_t, MultifileEntryStats> mEntryStats;
+ std::unordered_map<uint32_t, MultifileHotCache> mHotCache;
+
+ size_t mMaxKeySize;
+ size_t mMaxValueSize;
+ size_t mMaxTotalSize;
+ size_t mMaxTotalEntries;
+ size_t mTotalCacheSize;
+ size_t mTotalCacheEntries;
+ 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<uint32_t, uint8_t*> 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<DeferredTask> 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 0000000000..90a0f1ee06
--- /dev/null
+++ b/opengl/libs/EGL/MultifileBlobCache_test.cpp
@@ -0,0 +1,506 @@
+/*
+ ** 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 <android-base/properties.h>
+#include <android-base/test_utils.h>
+#include <fcntl.h>
+#include <gtest/gtest.h>
+#include <stdio.h>
+
+#include <fstream>
+#include <memory>
+
+using namespace std::literals;
+
+namespace android {
+
+template <typename T>
+using sp = std::shared_ptr<T>;
+
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
+constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
+
+class MultifileBlobCacheTest : public ::testing::Test {
+protected:
+ virtual void SetUp() {
+ clearProperties();
+ mTempFile.reset(new TemporaryFile());
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize,
+ kMaxTotalEntries, &mTempFile->path[0]));
+ }
+
+ virtual void TearDown() {
+ clearProperties();
+ mMBC.reset();
+ }
+
+ int getFileDescriptorCount();
+ std::vector<std::string> getCacheEntries();
+
+ void clearProperties();
+
+ std::unique_ptr<TemporaryFile> mTempFile;
+ std::unique_ptr<MultifileBlobCache> mMBC;
+};
+
+void MultifileBlobCacheTest::clearProperties() {
+ // Clear any debug properties used in the tests
+ base::SetProperty("debug.egl.blobcache.cache_version", "");
+ base::WaitForProperty("debug.egl.blobcache.cache_version", "");
+
+ base::SetProperty("debug.egl.blobcache.build_id", "");
+ base::WaitForProperty("debug.egl.blobcache.build_id", "");
+}
+
+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, CacheMaxEntrySucceeds) {
+ // Fill the cache with max entries
+ int i = 0;
+ for (i = 0; i < kMaxTotalEntries; i++) {
+ mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+ }
+
+ // Ensure it is full
+ ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries);
+
+ // Add another entry
+ mMBC->set(std::to_string(i).c_str(), sizeof(i), std::to_string(i).c_str(), sizeof(i));
+
+ // Ensure total entries is cut in half + 1
+ ASSERT_EQ(mMBC->getTotalEntries(), kMaxTotalEntries / 2 + 1);
+}
+
+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]);
+}
+
+int MultifileBlobCacheTest::getFileDescriptorCount() {
+ DIR* directory = opendir("/proc/self/fd");
+
+ int fileCount = 0;
+ struct dirent* entry;
+ while ((entry = readdir(directory)) != NULL) {
+ fileCount++;
+ // printf("File: %s\n", entry->d_name);
+ }
+
+ closedir(directory);
+ return fileCount;
+}
+
+TEST_F(MultifileBlobCacheTest, EnsureFileDescriptorsClosed) {
+ // Populate the cache with a bunch of entries
+ for (int i = 0; i < kMaxTotalEntries; i++) {
+ // printf("Caching: %i", i);
+
+ // Use the index as the key and value
+ mMBC->set(&i, sizeof(i), &i, sizeof(i));
+
+ int result = 0;
+ ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+ ASSERT_EQ(i, result);
+ }
+
+ // Ensure we don't have a bunch of open fds
+ ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Now open it again and ensure we still don't have a bunch of open fds
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Check after initialization
+ ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
+
+ for (int i = 0; i < kMaxTotalEntries; i++) {
+ int result = 0;
+ ASSERT_EQ(sizeof(i), mMBC->get(&i, sizeof(i), &result, sizeof(result)));
+ ASSERT_EQ(i, result);
+ }
+
+ // And again after we've actually used it
+ ASSERT_LT(getFileDescriptorCount(), kMaxTotalEntries / 2);
+}
+
+std::vector<std::string> MultifileBlobCacheTest::getCacheEntries() {
+ std::string cachePath = &mTempFile->path[0];
+ std::string multifileDirName = cachePath + ".multifile";
+ std::vector<std::string> cacheEntries;
+
+ struct stat info;
+ if (stat(multifileDirName.c_str(), &info) == 0) {
+ // We have a multifile dir. Skip the status file and return the only entry.
+ 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;
+ }
+ if (strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
+ continue;
+ }
+ cacheEntries.push_back(multifileDirName + "/" + entry->d_name);
+ }
+ } else {
+ printf("Unable to open %s, error: %s\n", multifileDirName.c_str(),
+ std::strerror(errno));
+ }
+ } else {
+ printf("Unable to stat %s, error: %s\n", multifileDirName.c_str(), std::strerror(errno));
+ }
+
+ return cacheEntries;
+}
+
+TEST_F(MultifileBlobCacheTest, CacheContainsStatus) {
+ struct stat info;
+ std::stringstream statusFile;
+ statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+ // After INIT, cache should have a status
+ ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+ // Set one entry
+ mMBC->set("abcd", 4, "efgh", 4);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Ensure status lives after closing the cache
+ ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+
+ // Open the cache again
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Ensure we still have a status
+ ASSERT_TRUE(stat(statusFile.str().c_str(), &info) == 0);
+}
+
+// Verify missing cache status file causes cache the be cleared
+TEST_F(MultifileBlobCacheTest, MissingCacheStatusClears) {
+ // Set one entry
+ mMBC->set("abcd", 4, "efgh", 4);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Ensure there is one cache entry
+ ASSERT_EQ(getCacheEntries().size(), 1);
+
+ // Delete the status file
+ std::stringstream statusFile;
+ statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+ remove(statusFile.str().c_str());
+
+ // Open the cache again and ensure no cache hits
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Ensure we have no entries
+ ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file BEGIN causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusBeginClears) {
+ // Set one entry
+ mMBC->set("abcd", 4, "efgh", 4);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Ensure there is one cache entry
+ ASSERT_EQ(getCacheEntries().size(), 1);
+
+ // Modify the status file
+ std::stringstream statusFile;
+ statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+ // Stomp on the beginning of the cache file
+ const char* stomp = "BADF00D";
+ std::fstream fs(statusFile.str());
+ fs.seekp(0, std::ios_base::beg);
+ fs.write(stomp, strlen(stomp));
+ fs.flush();
+ fs.close();
+
+ // Open the cache again and ensure no cache hits
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Ensure we have no entries
+ ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify modified cache status file END causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, ModifiedCacheStatusEndClears) {
+ // Set one entry
+ mMBC->set("abcd", 4, "efgh", 4);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Ensure there is one cache entry
+ ASSERT_EQ(getCacheEntries().size(), 1);
+
+ // Modify the status file
+ std::stringstream statusFile;
+ statusFile << &mTempFile->path[0] << ".multifile/" << kMultifileBlobCacheStatusFile;
+
+ // Stomp on the END of the cache status file, modifying its contents
+ const char* stomp = "BADF00D";
+ std::fstream fs(statusFile.str());
+ fs.seekp(-strlen(stomp), std::ios_base::end);
+ fs.write(stomp, strlen(stomp));
+ fs.flush();
+ fs.close();
+
+ // Open the cache again and ensure no cache hits
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Ensure we have no entries
+ ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched cacheVersion causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedCacheVersionClears) {
+ // Set one entry
+ mMBC->set("abcd", 4, "efgh", 4);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Ensure there is one cache entry
+ ASSERT_EQ(getCacheEntries().size(), 1);
+
+ // Set a debug cacheVersion
+ std::string newCacheVersion = std::to_string(kMultifileBlobCacheVersion + 1);
+ ASSERT_TRUE(base::SetProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+ ASSERT_TRUE(
+ base::WaitForProperty("debug.egl.blobcache.cache_version", newCacheVersion.c_str()));
+
+ // Open the cache again and ensure no cache hits
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Ensure we have no entries
+ ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+// Verify mismatched buildId causes cache to be cleared
+TEST_F(MultifileBlobCacheTest, MismatchedBuildIdClears) {
+ // Set one entry
+ mMBC->set("abcd", 4, "efgh", 4);
+
+ // Close the cache so everything writes out
+ mMBC->finish();
+ mMBC.reset();
+
+ // Ensure there is one cache entry
+ ASSERT_EQ(getCacheEntries().size(), 1);
+
+ // Set a debug buildId
+ base::SetProperty("debug.egl.blobcache.build_id", "foo");
+ base::WaitForProperty("debug.egl.blobcache.build_id", "foo");
+
+ // Open the cache again and ensure no cache hits
+ mMBC.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &mTempFile->path[0]));
+
+ // Ensure we have no entries
+ ASSERT_EQ(getCacheEntries().size(), 0);
+}
+
+} // namespace android
diff --git a/opengl/libs/EGL/egl_angle_platform.cpp b/opengl/libs/EGL/egl_angle_platform.cpp
index d38f2eff01..f0054a7cf7 100644
--- a/opengl/libs/EGL/egl_angle_platform.cpp
+++ b/opengl/libs/EGL/egl_angle_platform.cpp
@@ -35,11 +35,6 @@
namespace angle {
-constexpr int kAngleDlFlags = RTLD_LOCAL | RTLD_NOW;
-
-static GetDisplayPlatformFunc angleGetDisplayPlatform = nullptr;
-static ResetDisplayPlatformFunc angleResetDisplayPlatform = nullptr;
-
static time_t startTime = time(nullptr);
static const unsigned char* getTraceCategoryEnabledFlag(PlatformMethods* /*platform*/,
@@ -68,9 +63,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 +79,13 @@ static TraceEventHandle addTraceEvent(
ATRACE_NAME(name);
break;
}
+ case 'C': {
+ for(int i=0; i<num_args ; i++)
+ {
+ ATRACE_INT(arg_names[i],arg_values[i]);
+ }
+ break;
+ }
default:
// Could handle other event types here
break;
@@ -103,53 +105,14 @@ static void assignAnglePlatformMethods(PlatformMethods* platformMethods) {
}
// Initialize function ptrs for ANGLE PlatformMethods struct, used for systrace
-bool initializeAnglePlatform(EGLDisplay dpy) {
- // Since we're inside libEGL, use dlsym to lookup fptr for ANGLEGetDisplayPlatform
- android_namespace_t* ns = android::GraphicsEnv::getInstance().getAngleNamespace();
- void* so = nullptr;
- if (ns) {
- // Loading from an APK, so hard-code the suffix to "_angle".
- constexpr char kAngleEs2Lib[] = "libGLESv2_angle.so";
- const android_dlextinfo dlextinfo = {
- .flags = ANDROID_DLEXT_USE_NAMESPACE,
- .library_namespace = ns,
- };
- so = android_dlopen_ext(kAngleEs2Lib, kAngleDlFlags, &dlextinfo);
- if (so) {
- ALOGD("dlopen_ext from APK (%s) success at %p", kAngleEs2Lib, so);
- } else {
- ALOGE("dlopen_ext(\"%s\") failed: %s", kAngleEs2Lib, dlerror());
- return false;
- }
- } else {
- // If we are here, ANGLE is loaded as built-in gl driver in the sphal.
- // Get the specified ANGLE library filename suffix.
- std::string angleEs2LibSuffix = android::base::GetProperty("ro.hardware.egl", "");
- if (angleEs2LibSuffix.empty()) {
- ALOGE("%s failed to get valid ANGLE library filename suffix!", __FUNCTION__);
- return false;
- }
-
- std::string angleEs2LibName = "libGLESv2_" + angleEs2LibSuffix + ".so";
- so = android_load_sphal_library(angleEs2LibName.c_str(), kAngleDlFlags);
- if (so) {
- ALOGD("dlopen (%s) success at %p", angleEs2LibName.c_str(), so);
- } else {
- ALOGE("%s failed to dlopen %s!", __FUNCTION__, angleEs2LibName.c_str());
- return false;
- }
- }
-
- angleGetDisplayPlatform =
- reinterpret_cast<GetDisplayPlatformFunc>(dlsym(so, "ANGLEGetDisplayPlatform"));
-
- if (!angleGetDisplayPlatform) {
- ALOGE("dlsym lookup of ANGLEGetDisplayPlatform in libEGL_angle failed!");
+bool initializeAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx) {
+ if (cnx->angleGetDisplayPlatformFunc == nullptr) {
+ ALOGE("ANGLEGetDisplayPlatform is not initialized!");
return false;
}
- angleResetDisplayPlatform =
- reinterpret_cast<ResetDisplayPlatformFunc>(dlsym(so, "ANGLEResetDisplayPlatform"));
+ GetDisplayPlatformFunc angleGetDisplayPlatform =
+ reinterpret_cast<GetDisplayPlatformFunc>(cnx->angleGetDisplayPlatformFunc);
PlatformMethods* platformMethods = nullptr;
if (!((angleGetDisplayPlatform)(dpy, g_PlatformMethodNames, g_NumPlatformMethods, nullptr,
@@ -166,8 +129,10 @@ bool initializeAnglePlatform(EGLDisplay dpy) {
return true;
}
-void resetAnglePlatform(EGLDisplay dpy) {
- if (angleResetDisplayPlatform) {
+void resetAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx) {
+ if (cnx->angleResetDisplayPlatformFunc) {
+ ResetDisplayPlatformFunc angleResetDisplayPlatform =
+ reinterpret_cast<ResetDisplayPlatformFunc>(cnx->angleResetDisplayPlatformFunc);
angleResetDisplayPlatform(dpy);
}
}
diff --git a/opengl/libs/EGL/egl_angle_platform.h b/opengl/libs/EGL/egl_angle_platform.h
index 6c24aa5418..63806c2a4c 100644
--- a/opengl/libs/EGL/egl_angle_platform.h
+++ b/opengl/libs/EGL/egl_angle_platform.h
@@ -25,8 +25,8 @@
namespace angle {
-bool initializeAnglePlatform(EGLDisplay dpy);
-void resetAnglePlatform(EGLDisplay dpy);
+bool initializeAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx);
+void resetAnglePlatform(EGLDisplay dpy, android::egl_connection_t* const cnx);
}; // namespace angle
diff --git a/opengl/libs/EGL/egl_cache.cpp b/opengl/libs/EGL/egl_cache.cpp
index efa67dbc1c..98ff1d12cc 100644
--- a/opengl/libs/EGL/egl_cache.cpp
+++ b/opengl/libs/EGL/egl_cache.cpp
@@ -14,8 +14,12 @@
** limitations under the License.
*/
+// #define LOG_NDEBUG 0
+
#include "egl_cache.h"
+#include <android-base/properties.h>
+#include <inttypes.h>
#include <log/log.h>
#include <private/EGL/cache.h>
#include <unistd.h>
@@ -25,13 +29,19 @@
#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;
+constexpr uint32_t kMaxMultifileTotalEntries = 4 * 1024;
namespace android {
@@ -58,7 +68,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 +83,7 @@ void egl_cache_t::initialize(egl_display_t* display) {
std::lock_guard<std::mutex> 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 +121,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 +137,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<std::mutex> 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<std::mutex> lock(mMutex);
+ if (mInitialized && mBlobCache) {
+ mBlobCache->writeToFile();
+ }
+ mSavePending = false;
+ });
+ deferredSaveThread.detach();
+ }
}
}
}
@@ -145,27 +168,119 @@ EGLsizeiANDROID egl_cache_t::getBlob(const void* key, EGLsizeiANDROID keySize, v
std::lock_guard<std::mutex> 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<std::mutex> lock(mMutex);
mFilename = filename;
}
+void egl_cache_t::setCacheLimit(int64_t cacheByteLimit) {
+ std::lock_guard<std::mutex> 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<std::mutex> 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<size_t>(
+ base::GetUintProperty<uint32_t>("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,
+ kMaxMultifileTotalEntries, mFilename));
+ }
+ return mMultifileBlobCache.get();
+}
+
}; // namespace android
diff --git a/opengl/libs/EGL/egl_cache.h b/opengl/libs/EGL/egl_cache.h
index d10a6154b7..ae6d38146e 100644
--- a/opengl/libs/EGL/egl_cache.h
+++ b/opengl/libs/EGL/egl_cache.h
@@ -25,6 +25,7 @@
#include <string>
#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<FileBlobCache> mBlobCache;
+ // The multifile version of blobcache allowing larger contents to be stored
+ std::unique_ptr<MultifileBlobCache> 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 0b755aadca..1ada33ed6e 100644
--- a/opengl/libs/EGL/egl_display.cpp
+++ b/opengl/libs/EGL/egl_display.cpp
@@ -64,11 +64,6 @@ bool findExtension(const char* exts, const char* name, size_t nameLen) {
return false;
}
-bool needsAndroidPEglMitigation() {
- static const int32_t vndk_version = base::GetIntProperty("ro.vndk.version", -1);
- return vndk_version <= 28;
-}
-
int egl_get_init_count(EGLDisplay dpy) {
egl_display_t* eglDisplay = egl_display_t::get(dpy);
return eglDisplay ? eglDisplay->getRefsCount() : 0;
@@ -168,7 +163,7 @@ static EGLDisplay getPlatformDisplayAngle(EGLNativeDisplayType display, egl_conn
if (dpy == EGL_NO_DISPLAY) {
ALOGE("eglGetPlatformDisplay failed!");
} else {
- if (!angle::initializeAnglePlatform(dpy)) {
+ if (!angle::initializeAnglePlatform(dpy, cnx)) {
ALOGE("initializeAnglePlatform failed!");
}
}
@@ -191,7 +186,7 @@ EGLDisplay egl_display_t::getPlatformDisplay(EGLNativeDisplayType display,
if (cnx->dso) {
EGLDisplay dpy = EGL_NO_DISPLAY;
- if (cnx->useAngle) {
+ if (cnx->angleLoaded) {
EGLint error;
dpy = getPlatformDisplayAngle(display, cnx, attrib_list, &error);
if (error != EGL_NONE) {
@@ -322,6 +317,16 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) {
mExtensionString = gBuiltinExtensionString;
+ // b/269060366 Conditionally enabled EGL_ANDROID_get_frame_timestamps extension if the
+ // device's present timestamps are reliable (which may not be the case on emulators).
+ if (cnx->angleLoaded) {
+ if (android::base::GetBoolProperty("service.sf.present_timestamp", false)) {
+ mExtensionString.append("EGL_ANDROID_get_frame_timestamps ");
+ }
+ } else {
+ mExtensionString.append("EGL_ANDROID_get_frame_timestamps ");
+ }
+
hasColorSpaceSupport = findExtension(disp.queryString.extensions, "EGL_KHR_gl_colorspace");
// Note: CDD requires that devices supporting wide color and/or HDR color also support
@@ -343,8 +348,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;
@@ -354,13 +360,6 @@ EGLBoolean egl_display_t::initialize(EGLint* major, EGLint* minor) {
if (len) {
// NOTE: we could avoid the copy if we had strnstr.
const std::string ext(start, len);
- // Mitigation for Android P vendor partitions: Adreno 530 driver shipped on
- // some Android P vendor partitions this extension under the draft KHR name,
- // but during Khronos review it was decided to demote it to EXT.
- if (needsAndroidPEglMitigation() && ext == "EGL_EXT_image_gl_colorspace" &&
- findExtension(disp.queryString.extensions, "EGL_KHR_image_gl_colorspace")) {
- mExtensionString.append("EGL_EXT_image_gl_colorspace ");
- }
if (findExtension(disp.queryString.extensions, ext.c_str(), len)) {
mExtensionString.append(ext + " ");
}
@@ -420,8 +419,8 @@ EGLBoolean egl_display_t::terminate() {
egl_connection_t* const cnx = &gEGLImpl;
if (cnx->dso && disp.state == egl_display_t::INITIALIZED) {
// If we're using ANGLE reset any custom DisplayPlatform
- if (cnx->useAngle) {
- angle::resetAnglePlatform(disp.dpy);
+ if (cnx->angleLoaded) {
+ angle::resetAnglePlatform(disp.dpy, cnx);
}
if (cnx->egl.eglTerminate(disp.dpy) == EGL_FALSE) {
ALOGW("eglTerminate(%p) failed (%s)", disp.dpy,
diff --git a/opengl/libs/EGL/egl_display.h b/opengl/libs/EGL/egl_display.h
index 87c2176045..867a1173b9 100644
--- a/opengl/libs/EGL/egl_display.h
+++ b/opengl/libs/EGL/egl_display.h
@@ -39,7 +39,6 @@ class egl_context_t;
struct egl_connection_t;
bool findExtension(const char* exts, const char* name, size_t nameLen = 0);
-bool needsAndroidPEglMitigation();
class EGLAPI egl_display_t { // marked as EGLAPI for testing purposes
static std::map<EGLDisplay, std::unique_ptr<egl_display_t>> displayMap;
diff --git a/opengl/libs/EGL/egl_object.cpp b/opengl/libs/EGL/egl_object.cpp
index efbe613542..33a77c4470 100644
--- a/opengl/libs/EGL/egl_object.cpp
+++ b/opengl/libs/EGL/egl_object.cpp
@@ -84,7 +84,7 @@ void egl_surface_t::disconnect() {
if (win != nullptr && connected) {
// NOTE: When using Vulkan backend, the Vulkan runtime makes all the
// native_window_* calls, so don't do them here.
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
native_window_set_buffers_format(win, 0);
if (native_window_api_disconnect(win, NATIVE_WINDOW_API_EGL)) {
ALOGW("EGLNativeWindowType %p disconnect failed", win);
diff --git a/opengl/libs/EGL/egl_platform_entries.cpp b/opengl/libs/EGL/egl_platform_entries.cpp
index 7619a5071c..a6af713830 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 <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/hardware_buffer.h>
@@ -29,7 +30,6 @@
#include <private/android/AHardwareBufferHelpers.h>
#include <stdlib.h>
#include <string.h>
-#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <condition_variable>
#include <deque>
@@ -49,6 +49,7 @@
#include "egl_trace.h"
using namespace android;
+using PixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
// ----------------------------------------------------------------------------
@@ -84,7 +85,8 @@ extern const char* const gExtensionString;
// Extensions implemented by the EGL wrapper.
const char* const gBuiltinExtensionString =
"EGL_ANDROID_front_buffer_auto_refresh "
- "EGL_ANDROID_get_frame_timestamps "
+ // b/269060366 Conditionally enabled during display initialization:
+ //"EGL_ANDROID_get_frame_timestamps "
"EGL_ANDROID_get_native_client_buffer "
"EGL_ANDROID_presentation_time "
"EGL_EXT_surface_CTA861_3_metadata "
@@ -405,7 +407,7 @@ EGLBoolean eglGetConfigAttribImpl(EGLDisplay dpy, EGLConfig config, EGLint attri
// ----------------------------------------------------------------------------
// Translates EGL color spaces to Android data spaces.
-static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace) {
+static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace, PixelFormat pixelFormat) {
if (colorspace == EGL_GL_COLORSPACE_LINEAR_KHR) {
return HAL_DATASPACE_UNKNOWN;
} else if (colorspace == EGL_GL_COLORSPACE_SRGB_KHR) {
@@ -420,11 +422,20 @@ static android_dataspace dataSpaceFromEGLColorSpace(EGLint colorspace) {
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<android_dataspace>(HAL_DATASPACE_BT2020_HLG);
} else if (colorspace == EGL_GL_COLORSPACE_BT2020_LINEAR_EXT) {
- return HAL_DATASPACE_BT2020_LINEAR;
+ if (pixelFormat == PixelFormat::RGBA_FP16) {
+ return static_cast<android_dataspace>(HAL_DATASPACE_STANDARD_BT2020 |
+ HAL_DATASPACE_TRANSFER_LINEAR |
+ HAL_DATASPACE_RANGE_EXTENDED);
+ } else {
+ return HAL_DATASPACE_BT2020_LINEAR;
+ }
} else if (colorspace == EGL_GL_COLORSPACE_BT2020_PQ_EXT) {
return HAL_DATASPACE_BT2020_PQ;
}
+
return HAL_DATASPACE_UNKNOWN;
}
@@ -451,6 +462,9 @@ static std::vector<EGLint> 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);
}
@@ -484,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:
@@ -565,8 +580,6 @@ void convertAttribs(const EGLAttrib* attribList, std::vector<EGLint>& newList) {
newList.push_back(EGL_NONE);
}
-using PixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
-
// Gets the native pixel format corrsponding to the passed EGLConfig.
void getNativePixelFormat(EGLDisplay dpy, egl_connection_t* cnx, EGLConfig config,
PixelFormat* format) {
@@ -672,7 +685,7 @@ EGLSurface eglCreateWindowSurfaceTmpl(egl_display_t* dp, egl_connection_t* cnx,
// NOTE: When using Vulkan backend, the Vulkan runtime makes all the
// native_window_* calls, so don't do them here.
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
int result = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
if (result < 0) {
ALOGE("eglCreateWindowSurface: native_window_api_connect (win=%p) "
@@ -691,14 +704,14 @@ EGLSurface eglCreateWindowSurfaceTmpl(egl_display_t* dp, egl_connection_t* cnx,
std::vector<AttrType> strippedAttribList;
if (!processAttributes<AttrType>(dp, window, attrib_list, &colorSpace, &strippedAttribList)) {
ALOGE("error invalid colorspace: %d", colorSpace);
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
}
return EGL_NO_SURFACE;
}
attrib_list = strippedAttribList.data();
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
int err = native_window_set_buffers_format(window, static_cast<int>(format));
if (err != 0) {
ALOGE("error setting native window pixel format: %s (%d)", strerror(-err), err);
@@ -706,7 +719,7 @@ EGLSurface eglCreateWindowSurfaceTmpl(egl_display_t* dp, egl_connection_t* cnx,
return setError(EGL_BAD_NATIVE_WINDOW, EGL_NO_SURFACE);
}
- android_dataspace dataSpace = dataSpaceFromEGLColorSpace(colorSpace);
+ android_dataspace dataSpace = dataSpaceFromEGLColorSpace(colorSpace, format);
// Set dataSpace even if it could be HAL_DATASPACE_UNKNOWN.
// HAL_DATASPACE_UNKNOWN is the default value, but it may have changed
// at this point.
@@ -730,7 +743,7 @@ EGLSurface eglCreateWindowSurfaceTmpl(egl_display_t* dp, egl_connection_t* cnx,
}
// EGLSurface creation failed
- if (!cnx->useAngle) {
+ if (!cnx->angleLoaded) {
native_window_set_buffers_format(window, 0);
native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
}
@@ -937,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;
}
@@ -1339,7 +1354,7 @@ EGLBoolean eglSwapBuffersWithDamageKHRImpl(EGLDisplay dpy, EGLSurface draw, EGLi
}
}
- if (!s->cnx->useAngle) {
+ if (!s->cnx->angleLoaded) {
if (!sendSurfaceMetadata(s)) {
native_window_api_disconnect(s->getNativeWindow(), NATIVE_WINDOW_API_EGL);
return setError(EGL_BAD_NATIVE_WINDOW, (EGLBoolean)EGL_FALSE);
@@ -1364,7 +1379,7 @@ EGLBoolean eglSwapBuffersWithDamageKHRImpl(EGLDisplay dpy, EGLSurface draw, EGLi
androidRect.bottom = y;
androidRects.push_back(androidRect);
}
- if (!s->cnx->useAngle) {
+ if (!s->cnx->angleLoaded) {
native_window_set_surface_damage(s->getNativeWindow(), androidRects.data(),
androidRects.size());
}
@@ -1455,7 +1470,7 @@ EGLBoolean eglSurfaceAttribImpl(EGLDisplay dpy, EGLSurface surface, EGLint attri
int err = native_window_set_auto_refresh(s->getNativeWindow(), value != 0);
if (err != 0) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
- } else if (!s->cnx->useAngle) {
+ } else if (!s->cnx->angleLoaded) {
return EGL_TRUE;
} // else if ANGLE, fall through to the call to the driver (i.e. ANGLE) below
}
@@ -1469,7 +1484,7 @@ EGLBoolean eglSurfaceAttribImpl(EGLDisplay dpy, EGLSurface surface, EGLint attri
int err = native_window_enable_frame_timestamps(s->getNativeWindow(), value != 0);
if (err != 0) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
- } else if (!s->cnx->useAngle) {
+ } else if (!s->cnx->angleLoaded) {
return EGL_TRUE;
} // else if ANGLE, fall through to the call to the driver (i.e. ANGLE) below
}
@@ -1629,26 +1644,6 @@ EGLImageKHR eglCreateImageTmpl(EGLDisplay dpy, EGLContext ctx, EGLenum target,
const egl_display_t* dp = validate_display(dpy);
if (!dp) return EGL_NO_IMAGE_KHR;
- std::vector<AttrType> strippedAttribs;
- if (needsAndroidPEglMitigation()) {
- // Mitigation for Android P vendor partitions: eglImageCreateKHR should accept
- // EGL_GL_COLORSPACE_LINEAR_KHR, EGL_GL_COLORSPACE_SRGB_KHR and
- // EGL_GL_COLORSPACE_DEFAULT_EXT if EGL_EXT_image_gl_colorspace is supported,
- // but some drivers don't like the DEFAULT value and generate an error.
- for (const AttrType* attr = attrib_list; attr && attr[0] != EGL_NONE; attr += 2) {
- if (attr[0] == EGL_GL_COLORSPACE_KHR &&
- dp->haveExtension("EGL_EXT_image_gl_colorspace")) {
- if (attr[1] != EGL_GL_COLORSPACE_LINEAR_KHR &&
- attr[1] != EGL_GL_COLORSPACE_SRGB_KHR) {
- continue;
- }
- }
- strippedAttribs.push_back(attr[0]);
- strippedAttribs.push_back(attr[1]);
- }
- strippedAttribs.push_back(EGL_NONE);
- }
-
ContextRef _c(dp, ctx);
egl_context_t* const c = _c.get();
@@ -1656,8 +1651,7 @@ EGLImageKHR eglCreateImageTmpl(EGLDisplay dpy, EGLContext ctx, EGLenum target,
egl_connection_t* const cnx = &gEGLImpl;
if (cnx->dso && eglCreateImageFunc) {
result = eglCreateImageFunc(dp->disp.dpy, c ? c->context : EGL_NO_CONTEXT, target, buffer,
- needsAndroidPEglMitigation() ? strippedAttribs.data()
- : attrib_list);
+ attrib_list);
}
return result;
}
@@ -2183,6 +2177,10 @@ EGLBoolean eglGetNextFrameIdANDROIDImpl(EGLDisplay dpy, EGLSurface surface, EGLu
return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
}
+ if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+ return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+ }
+
SurfaceRef _s(dp, surface);
if (!_s.get()) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2216,6 +2214,10 @@ EGLBoolean eglGetCompositorTimingANDROIDImpl(EGLDisplay dpy, EGLSurface surface,
return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
}
+ if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+ return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+ }
+
SurfaceRef _s(dp, surface);
if (!_s.get()) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2270,6 +2272,10 @@ EGLBoolean eglGetCompositorTimingSupportedANDROIDImpl(EGLDisplay dpy, EGLSurface
return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
}
+ if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+ return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+ }
+
SurfaceRef _s(dp, surface);
if (!_s.get()) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2300,6 +2306,10 @@ EGLBoolean eglGetFrameTimestampsANDROIDImpl(EGLDisplay dpy, EGLSurface surface,
return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
}
+ if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+ return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+ }
+
SurfaceRef _s(dp, surface);
if (!_s.get()) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
@@ -2385,6 +2395,10 @@ EGLBoolean eglGetFrameTimestampSupportedANDROIDImpl(EGLDisplay dpy, EGLSurface s
return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
}
+ if (!dp->haveExtension("EGL_ANDROID_get_frame_timestamps")) {
+ return setError(EGL_BAD_DISPLAY, (EGLBoolean)EGL_FALSE);
+ }
+
SurfaceRef _s(dp, surface);
if (!_s.get()) {
return setError(EGL_BAD_SURFACE, (EGLBoolean)EGL_FALSE);
diff --git a/opengl/libs/EGL/egldefs.h b/opengl/libs/EGL/egldefs.h
index fcc11f1b55..90a3c199a9 100644
--- a/opengl/libs/EGL/egldefs.h
+++ b/opengl/libs/EGL/egldefs.h
@@ -41,7 +41,10 @@ struct egl_connection_t {
libEgl(nullptr),
libGles1(nullptr),
libGles2(nullptr),
- systemDriverUnloaded(false) {
+ systemDriverUnloaded(false),
+ angleLoaded(false),
+ angleGetDisplayPlatformFunc(nullptr),
+ angleResetDisplayPlatformFunc(nullptr) {
const char* const* entries = platform_names;
EGLFuncPointer* curr = reinterpret_cast<EGLFuncPointer*>(&platform);
while (*entries) {
@@ -73,7 +76,10 @@ struct egl_connection_t {
void* libGles2;
bool systemDriverUnloaded;
- bool useAngle; // Was ANGLE successfully loaded
+ bool angleLoaded; // Was ANGLE successfully loaded
+
+ void* angleGetDisplayPlatformFunc;
+ void* angleResetDisplayPlatformFunc;
};
extern gl_hooks_t gHooks[2];
diff --git a/opengl/libs/EGL/fuzzer/Android.bp b/opengl/libs/EGL/fuzzer/Android.bp
new file mode 100644
index 0000000000..022a2a3f06
--- /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 0000000000..2d9ee3a49e
--- /dev/null
+++ b/opengl/libs/EGL/fuzzer/MultifileBlobCache_fuzzer.cpp
@@ -0,0 +1,159 @@
+/*
+ ** 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 <android-base/test_utils.h>
+#include <fcntl.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+namespace android {
+
+constexpr size_t kMaxKeySize = 2 * 1024;
+constexpr size_t kMaxValueSize = 6 * 1024;
+constexpr size_t kMaxTotalSize = 32 * 1024;
+constexpr size_t kMaxTotalEntries = 64;
+
+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<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+ EGLsizeiANDROID valueSize = static_cast<EGLsizeiANDROID>(fdp.ConsumeIntegral<uint8_t>());
+ 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<uint8_t> key;
+ std::vector<uint8_t> value;
+ key = fdp.ConsumeBytes<uint8_t>(keySize);
+ value = fdp.ConsumeBytes<uint8_t>(valueSize);
+
+ // Create a tempfile and a cache
+ std::unique_ptr<TemporaryFile> tempFile;
+ std::unique_ptr<MultifileBlobCache> mbc;
+
+ tempFile.reset(new TemporaryFile());
+ mbc.reset(new MultifileBlobCache(kMaxKeySize, kMaxValueSize, kMaxTotalSize, kMaxTotalEntries,
+ &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<uint8_t> 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, kMaxTotalEntries,
+ &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),
+ kMaxTotalEntries, &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 51c937614f..d96a89564d 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 bbd786d155..cbe4ef9c40 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 c974f63c13..ce5818229a 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 <gtest/gtest.h>
@@ -24,27 +24,42 @@
#include <android-base/test_utils.h>
#include "egl_cache.h"
+#include "MultifileBlobCache.h"
#include "egl_display.h"
+#include <fstream>
#include <memory>
+using namespace std::literals;
+
namespace android {
-class EGLCacheTest : public ::testing::Test {
+class EGLCacheTest : public ::testing::TestWithParam<egl_cache_t::EGLCacheMode> {
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<TemporaryFile> 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,244 @@ 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 entryFileCount = 0;
- virtual void SetUp() {
- EGLCacheTest::SetUp();
- mTempFile.reset(new TemporaryFile());
+ // We have a multifile dir. Return the only entry 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 ||
+ strcmp(entry->d_name, kMultifileBlobCacheStatusFile) == 0) {
+ continue;
+ }
+ cachefileName = multifileDirName + "/" + entry->d_name;
+ entryFileCount++;
+ }
+ } else {
+ printf("Unable to open %s, error: %s\n",
+ multifileDirName.c_str(), std::strerror(errno));
+ }
+
+ if (entryFileCount != 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<TemporaryFile> 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/gldual/AndroidManifest.xml b/opengl/tests/gldual/AndroidManifest.xml
index a36f4f715e..d6335b0698 100644
--- a/opengl/tests/gldual/AndroidManifest.xml
+++ b/opengl/tests/gldual/AndroidManifest.xml
@@ -20,8 +20,9 @@
android:label="@string/gldual_activity">
<activity android:name="GLDualActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
- android:launchMode="singleTask"
- android:configChanges="orientation|keyboardHidden">
+ android:launchMode="singleTask"
+ android:configChanges="orientation|keyboardHidden"
+ android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
diff --git a/opengl/tests/lib/WindowSurface.cpp b/opengl/tests/lib/WindowSurface.cpp
index fd4522e757..e94b565f11 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/opengl/tools/glgen/gen b/opengl/tools/glgen/gen
index 7fd9c3a735..f73fa5d63c 100755
--- a/opengl/tools/glgen/gen
+++ b/opengl/tools/glgen/gen
@@ -22,8 +22,10 @@ mkdir out
mkdir -p out/javax/microedition/khronos/opengles
mkdir -p out/com/google/android/gles_jni
+mkdir -p out/android/annotation
mkdir -p out/android/app
mkdir -p out/android/graphics
+mkdir -p out/android/hardware
mkdir -p out/android/view
mkdir -p out/android/opengl
mkdir -p out/android/content
@@ -34,18 +36,20 @@ mkdir -p out/android/util
echo "package android.graphics;" > out/android/graphics/Canvas.java
echo "public interface Canvas {}" >> out/android/graphics/Canvas.java
+echo "package android.annotation; public @interface NonNull {}" > out/android/annotation/NonNull.java
echo "package android.app; import android.content.pm.IPackageManager; public class AppGlobals { public static IPackageManager getPackageManager() { return null;} }" > out/android/app/AppGlobals.java
# echo "package android.content; import android.content.pm.PackageManager; public interface Context { public PackageManager getPackageManager(); }" > out/android/content/Context.java
echo "package android.content.pm; public class ApplicationInfo {public int targetSdkVersion;}" > out/android/content/pm/ApplicationInfo.java
echo "package android.content.pm; public interface IPackageManager {ApplicationInfo getApplicationInfo(java.lang.String packageName, int flags, java.lang.String userId) throws android.os.RemoteException;}" > out/android/content/pm/IPackageManager.java
-echo "package android.os; public class Build {public static class VERSION_CODES { public static final int CUPCAKE = 3;}; }" > out/android/os/Build.java
+echo "package android.hardware; import android.os.ParcelFileDescriptor; public class SyncFence { public static SyncFence create(ParcelFileDescriptor w) { return null; } public static SyncFence createEmpty() { return null; } }" > out/android/hardware/SyncFence.java
+echo "package android.os; public class Build {public static class VERSION_CODES { public static final int CUPCAKE = 0; public static final int R = 0; }; }" > out/android/os/Build.java
+echo "package android.os; public class ParcelFileDescriptor { public static ParcelFileDescriptor adoptFd(int fd) { return null; } }" > out/android/os/ParcelFileDescriptor.java
echo "package android.os; public class UserHandle {public static String myUserId() { return \"\"; } }" > out/android/os/UserHandle.java
echo "package android.os; public class RemoteException extends Exception {}" > out/android/os/RemoteException.java
-echo "package android.util; public class Log {public static void w(String a, String b) {} public static void e(String a, String b) {}}" > out/android/util/Log.java
+echo "package android.util; public class Log {public static void d(String a, String b) {} public static void w(String a, String b) {} public static void e(String a, String b) {}}" > out/android/util/Log.java
echo "package android.opengl; public abstract class EGLObjectHandle { public int getHandle() { return 0; } }" > out/android/opengl/EGLObjectHandle.java
-
echo "package android.graphics;" > out/android/graphics/SurfaceTexture.java
echo "public interface SurfaceTexture {}" >> out/android/graphics/SurfaceTexture.java
echo "package android.view;" > out/android/view/SurfaceView.java
diff --git a/opengl/tools/glgen/stubs/egl/EGL14Header.java-if b/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
index 951ecffc32..695b571a92 100644
--- a/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
+++ b/opengl/tools/glgen/stubs/egl/EGL14Header.java-if
@@ -20,6 +20,7 @@ package android.opengl;
import android.compat.annotation.UnsupportedAppUsage;
import android.graphics.SurfaceTexture;
+import android.os.Build;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
diff --git a/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp b/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp
index b2ea041cd5..ea55179bbd 100644
--- a/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp
+++ b/opengl/tools/glgen/stubs/egl/EGL14cHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp b/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp
index 6dffac5945..8e452fb9cd 100644
--- a/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp
+++ b/opengl/tools/glgen/stubs/egl/EGL15cHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if b/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if
index 523bc574be..75e17044c6 100644
--- a/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if
+++ b/opengl/tools/glgen/stubs/egl/EGLExtHeader.java-if
@@ -18,6 +18,11 @@
package android.opengl;
+import android.annotation.NonNull;
+import android.hardware.SyncFence;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
/**
* EGL Extensions
*/
@@ -30,8 +35,44 @@ public class EGLExt {
public static final int EGL_OPENGL_ES3_BIT_KHR = 0x0040;
public static final int EGL_RECORDABLE_ANDROID = 0x3142;
+ // EGL_ANDROID_native_fence_sync
+ public static final int EGL_SYNC_NATIVE_FENCE_ANDROID = 0x3144;
+ public static final int EGL_SYNC_NATIVE_FENCE_FD_ANDROID = 0x3145;
+ public static final int EGL_SYNC_NATIVE_FENCE_SIGNALED_ANDROID = 0x3146;
+ public static final int EGL_NO_NATIVE_FENCE_FD_ANDROID = -1;
+
native private static void _nativeClassInit();
static {
_nativeClassInit();
}
+ /**
+ * Retrieves the SyncFence for an EGLSync created with EGL_SYNC_NATIVE_FENCE_ANDROID
+ *
+ * See <a href="https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_native_fence_sync.txt">
+ * EGL_ANDROID_native_fence_sync</a> extension for more details
+ * @param display The EGLDisplay connection
+ * @param sync The EGLSync to fetch the SyncFence from
+ * @return A SyncFence representing the native fence.
+ * * If <sync> is not a valid sync object for <display>,
+ * an {@link SyncFence#isValid() invalid} SyncFence is returned and an EGL_BAD_PARAMETER
+ * error is generated.
+ * * If the EGL_SYNC_NATIVE_FENCE_FD_ANDROID attribute of <sync> is
+ * EGL_NO_NATIVE_FENCE_FD_ANDROID, an {@link SyncFence#isValid() invalid} SyncFence is
+ * returned and an EGL_BAD_PARAMETER error is generated.
+ * * If <display> does not match the display passed to eglCreateSync
+ * when <sync> was created, the behaviour is undefined.
+ */
+ public static @NonNull SyncFence eglDupNativeFenceFDANDROID(@NonNull EGLDisplay display,
+ @NonNull EGLSync sync) {
+ int fd = eglDupNativeFenceFDANDROIDImpl(display, sync);
+ Log.d("EGL", "eglDupNativeFence returned " + fd);
+ if (fd >= 0) {
+ return SyncFence.create(ParcelFileDescriptor.adoptFd(fd));
+ } else {
+ return SyncFence.createEmpty();
+ }
+ }
+
+ private static native int eglDupNativeFenceFDANDROIDImpl(EGLDisplay display, EGLSync sync);
+
diff --git a/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp b/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp
index be8b3e3977..f1f0ac574b 100644
--- a/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/egl/EGLExtcHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
@@ -37,25 +38,12 @@
#include <ui/ANativeObjectBase.h>
static jclass egldisplayClass;
-static jclass eglcontextClass;
static jclass eglsurfaceClass;
-static jclass eglconfigClass;
+static jclass eglsyncClass;
static jmethodID egldisplayGetHandleID;
-static jmethodID eglcontextGetHandleID;
static jmethodID eglsurfaceGetHandleID;
-static jmethodID eglconfigGetHandleID;
-
-static jmethodID egldisplayConstructor;
-static jmethodID eglcontextConstructor;
-static jmethodID eglsurfaceConstructor;
-static jmethodID eglconfigConstructor;
-
-static jobject eglNoContextObject;
-static jobject eglNoDisplayObject;
-static jobject eglNoSurfaceObject;
-
-
+static jmethodID eglsyncGetHandleID;
/* Cache method IDs each time the class is loaded. */
@@ -64,37 +52,14 @@ nativeClassInit(JNIEnv *_env, jclass glImplClass)
{
jclass egldisplayClassLocal = _env->FindClass("android/opengl/EGLDisplay");
egldisplayClass = (jclass) _env->NewGlobalRef(egldisplayClassLocal);
- jclass eglcontextClassLocal = _env->FindClass("android/opengl/EGLContext");
- eglcontextClass = (jclass) _env->NewGlobalRef(eglcontextClassLocal);
jclass eglsurfaceClassLocal = _env->FindClass("android/opengl/EGLSurface");
eglsurfaceClass = (jclass) _env->NewGlobalRef(eglsurfaceClassLocal);
- jclass eglconfigClassLocal = _env->FindClass("android/opengl/EGLConfig");
- eglconfigClass = (jclass) _env->NewGlobalRef(eglconfigClassLocal);
+ jclass eglsyncClassLocal = _env->FindClass("android/opengl/EGLSync");
+ eglsyncClass = (jclass) _env->NewGlobalRef(eglsyncClassLocal);
egldisplayGetHandleID = _env->GetMethodID(egldisplayClass, "getNativeHandle", "()J");
- eglcontextGetHandleID = _env->GetMethodID(eglcontextClass, "getNativeHandle", "()J");
eglsurfaceGetHandleID = _env->GetMethodID(eglsurfaceClass, "getNativeHandle", "()J");
- eglconfigGetHandleID = _env->GetMethodID(eglconfigClass, "getNativeHandle", "()J");
-
-
- egldisplayConstructor = _env->GetMethodID(egldisplayClass, "<init>", "(J)V");
- eglcontextConstructor = _env->GetMethodID(eglcontextClass, "<init>", "(J)V");
- eglsurfaceConstructor = _env->GetMethodID(eglsurfaceClass, "<init>", "(J)V");
- eglconfigConstructor = _env->GetMethodID(eglconfigClass, "<init>", "(J)V");
-
-
- jclass eglClass = _env->FindClass("android/opengl/EGL14");
- jfieldID noContextFieldID = _env->GetStaticFieldID(eglClass, "EGL_NO_CONTEXT", "Landroid/opengl/EGLContext;");
- jobject localeglNoContextObject = _env->GetStaticObjectField(eglClass, noContextFieldID);
- eglNoContextObject = _env->NewGlobalRef(localeglNoContextObject);
-
- jfieldID noDisplayFieldID = _env->GetStaticFieldID(eglClass, "EGL_NO_DISPLAY", "Landroid/opengl/EGLDisplay;");
- jobject localeglNoDisplayObject = _env->GetStaticObjectField(eglClass, noDisplayFieldID);
- eglNoDisplayObject = _env->NewGlobalRef(localeglNoDisplayObject);
-
- jfieldID noSurfaceFieldID = _env->GetStaticFieldID(eglClass, "EGL_NO_SURFACE", "Landroid/opengl/EGLSurface;");
- jobject localeglNoSurfaceObject = _env->GetStaticObjectField(eglClass, noSurfaceFieldID);
- eglNoSurfaceObject = _env->NewGlobalRef(localeglNoSurfaceObject);
+ eglsyncGetHandleID = _env->GetMethodID(eglsyncClass, "getNativeHandle", "()J");
}
static void *
@@ -108,24 +73,12 @@ fromEGLHandle(JNIEnv *_env, jmethodID mid, jobject obj) {
return reinterpret_cast<void*>(_env->CallLongMethod(obj, mid));
}
-static jobject
-toEGLHandle(JNIEnv *_env, jclass cls, jmethodID con, void * handle) {
- if (cls == eglcontextClass &&
- (EGLContext)handle == EGL_NO_CONTEXT) {
- return eglNoContextObject;
- }
-
- if (cls == egldisplayClass &&
- (EGLDisplay)handle == EGL_NO_DISPLAY) {
- return eglNoDisplayObject;
- }
-
- if (cls == eglsurfaceClass &&
- (EGLSurface)handle == EGL_NO_SURFACE) {
- return eglNoSurfaceObject;
- }
+// TODO: this should be generated from the .spec file, but needs to be renamed and made private
+static jint android_eglDupNativeFenceFDANDROID(JNIEnv *env, jobject, jobject dpy, jobject sync) {
+ EGLDisplay dpy_native = (EGLDisplay)fromEGLHandle(env, egldisplayGetHandleID, dpy);
+ EGLSync sync_native = (EGLSync)fromEGLHandle(env, eglsyncGetHandleID, sync);
- return _env->NewObject(cls, con, reinterpret_cast<jlong>(handle));
+ return eglDupNativeFenceFDANDROID(dpy_native, sync_native);
}
// --------------------------------------------------------------------------
diff --git a/opengl/tools/glgen/stubs/egl/eglGetDisplay.java b/opengl/tools/glgen/stubs/egl/eglGetDisplay.java
index 85f743d815..78b0819374 100755
--- a/opengl/tools/glgen/stubs/egl/eglGetDisplay.java
+++ b/opengl/tools/glgen/stubs/egl/eglGetDisplay.java
@@ -7,7 +7,7 @@
/**
* {@hide}
*/
- @UnsupportedAppUsage
+ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public static native EGLDisplay eglGetDisplay(
long display_id
);
diff --git a/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp
index dd17ca458a..1fa92758ad 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES10ExtcHeader.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp
index dd17ca458a..1fa92758ad 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES10cHeader.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp
index dd17ca458a..1fa92758ad 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES11ExtcHeader.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp
index dd17ca458a..1fa92758ad 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES11cHeader.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES/gl.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp
index b2bbdf6fe9..4004a7dfcf 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES20cHeader.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES2/gl2.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp
index b039bc9a83..c5bdf323fc 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES30cHeader.cpp
@@ -18,6 +18,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES3/gl3.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp
index dd00e9205c..2260a80bb4 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES31ExtcHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <GLES3/gl31.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp
index 88e00bef6e..130612d7cc 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES31cHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <stdint.h>
diff --git a/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp b/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp
index 3e7ec8b2de..5446fc2fc3 100644
--- a/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp
+++ b/opengl/tools/glgen/stubs/gles11/GLES32cHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include <stdint.h>
diff --git a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
index 9cab1d6a59..c3534bff31 100644
--- a/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
+++ b/opengl/tools/glgen/stubs/jsr239/GLCHeader.cpp
@@ -17,6 +17,7 @@
// This source file is automatically generated
#pragma GCC diagnostic ignored "-Wunused-variable"
+#pragma GCC diagnostic ignored "-Wunused-but-set-variable"
#pragma GCC diagnostic ignored "-Wunused-function"
#include "jni.h"
diff --git a/services/audiomanager/IAudioManager.cpp b/services/audiomanager/IAudioManager.cpp
index ae1bb1a0d0..3ef5049230 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<os::PersistableBundle>& 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/automotive/display/Android.bp b/services/automotive/display/Android.bp
index 614a78ecc7..72bd29254d 100644
--- a/services/automotive/display/Android.bp
+++ b/services/automotive/display/Android.bp
@@ -53,6 +53,4 @@ cc_binary {
vintf_fragments: [
"manifest_android.frameworks.automotive.display@1.0.xml",
],
-
- system_ext_specific: true,
}
diff --git a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
index ea1077a555..5c7f344486 100644
--- a/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
+++ b/services/automotive/display/android.frameworks.automotive.display@1.0-service.rc
@@ -1,4 +1,4 @@
-service automotive_display /system_ext/bin/android.frameworks.automotive.display@1.0-service
+service automotive_display /system/bin/android.frameworks.automotive.display@1.0-service
class hal
user graphics
group automotive_evs
diff --git a/services/batteryservice/Android.bp b/services/batteryservice/Android.bp
index 1e3799185e..9b783916d3 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/batteryservice/include/batteryservice/BatteryService.h b/services/batteryservice/include/batteryservice/BatteryService.h
index bf6189d7af..f697f94a7e 100644
--- a/services/batteryservice/include/batteryservice/BatteryService.h
+++ b/services/batteryservice/include/batteryservice/BatteryService.h
@@ -34,10 +34,11 @@ enum {
BATTERY_PROP_CAPACITY = 4, // equals BATTERY_PROPERTY_CAPACITY
BATTERY_PROP_ENERGY_COUNTER = 5, // equals BATTERY_PROPERTY_ENERGY_COUNTER
BATTERY_PROP_BATTERY_STATUS = 6, // equals BATTERY_PROPERTY_BATTERY_STATUS
- BATTERY_PROP_CHARGING_POLICY = 7, // equals BATTERY_PROPERTY_CHARGING_POLICY
- BATTERY_PROP_MANUFACTURING_DATE = 8, // equals BATTERY_PROPERTY_MANUFACTURING_DATE
- BATTERY_PROP_FIRST_USAGE_DATE = 9, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE
+ BATTERY_PROP_MANUFACTURING_DATE = 7, // equals BATTERY_PROPERTY_MANUFACTURING_DATE
+ BATTERY_PROP_FIRST_USAGE_DATE = 8, // equals BATTERY_PROPERTY_FIRST_USAGE_DATE
+ BATTERY_PROP_CHARGING_POLICY = 9, // equals BATTERY_PROPERTY_CHARGING_POLICY
BATTERY_PROP_STATE_OF_HEALTH = 10, // equals BATTERY_PROPERTY_STATE_OF_HEALTH
+ BATTERY_PROP_PART_STATUS = 12, // equals BATTERY_PROPERTY_PART_STATUS
};
struct BatteryProperties {
diff --git a/services/displayservice/Android.bp b/services/displayservice/Android.bp
index 8681784405..c88f2fca83 100644
--- a/services/displayservice/Android.bp
+++ b/services/displayservice/Android.bp
@@ -23,7 +23,7 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
+cc_library_static {
name: "libdisplayservicehidl",
srcs: [
@@ -37,18 +37,24 @@ cc_library_shared {
"libgui",
"libhidlbase",
"libutils",
+ ],
+
+ static_libs: [
"android.frameworks.displayservice@1.0",
],
export_include_dirs: ["include"],
export_shared_lib_headers: [
- "android.frameworks.displayservice@1.0",
"libgui",
"libutils",
],
+ export_static_lib_headers: [
+ "android.frameworks.displayservice@1.0",
+ ],
+
cflags: [
"-Werror",
"-Wall",
- ]
+ ],
}
diff --git a/services/gpuservice/Android.bp b/services/gpuservice/Android.bp
index fba64c7569..e5b1e44b44 100644
--- a/services/gpuservice/Android.bp
+++ b/services/gpuservice/Android.bp
@@ -21,7 +21,13 @@ cc_defaults {
cc_defaults {
name: "libgpuservice_defaults",
- defaults: ["gpuservice_defaults"],
+ defaults: [
+ "gpuservice_defaults",
+ "libvkjson_deps",
+ "libgfxstats_deps",
+ "libgpumem_deps",
+ "libgpumemtracer_deps",
+ ],
cflags: [
"-DLOG_TAG=\"GpuService\"",
],
@@ -29,17 +35,17 @@ cc_defaults {
"libbase",
"libbinder",
"libcutils",
- "libgfxstats",
- "libgpumem",
"libgpuwork",
- "libgpumemtracer",
"libgraphicsenv",
"liblog",
"libutils",
- "libvkjson",
],
static_libs: [
+ "libgfxstats",
+ "libgpumem",
+ "libgpumemtracer",
"libserviceutils",
+ "libvkjson",
],
export_static_lib_headers: [
"libserviceutils",
@@ -68,9 +74,10 @@ filegroup {
],
}
-cc_library_shared {
+cc_library_static {
name: "libgpuservice",
defaults: ["libgpuservice_production_defaults"],
+ export_include_dirs: ["include"],
srcs: [
":libgpuservice_sources",
],
@@ -95,14 +102,17 @@ filegroup {
cc_binary {
name: "gpuservice",
- defaults: ["libgpuservice_binary"],
+ defaults: [
+ "libgpuservice_binary",
+ "libgpuservice_production_defaults",
+ ],
init_rc: ["gpuservice.rc"],
required: [
"bpfloader",
"gpuMem.o",
],
srcs: [":gpuservice_binary_sources"],
- shared_libs: [
+ static_libs: [
"libgpuservice",
],
}
diff --git a/services/gpuservice/GpuService.cpp b/services/gpuservice/GpuService.cpp
index 7b9782f4e8..080e62b164 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 <android-base/stringprintf.h>
+#include <android-base/properties.h>
#include <binder/IPCThreadState.h>
#include <binder/IResultReceiver.h>
#include <binder/Parcel.h>
@@ -34,6 +35,7 @@
#include <vkjson.h>
#include <thread>
+#include <memory>
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,24 @@ GpuService::GpuService()
mGpuStats(std::make_unique<GpuStats>()),
mGpuMemTracer(std::make_unique<GpuMemTracer>()) {
- std::thread gpuMemAsyncInitThread([this]() {
+ mGpuMemAsyncInitThread = std::make_unique<std::thread>([this] (){
mGpuMem->initialize();
mGpuMemTracer->initialize(mGpuMem);
});
- gpuMemAsyncInitThread.detach();
- std::thread gpuWorkAsyncInitThread([this]() {
+ mGpuWorkAsyncInitThread = std::make_unique<std::thread>([this]() {
mGpuWork->initialize();
});
- gpuWorkAsyncInitThread.detach();
};
+GpuService::~GpuService() {
+ mGpuMem->stop();
+ mGpuWork->stop();
+
+ 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 +92,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<std::mutex> 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();
@@ -107,7 +146,7 @@ status_t GpuService::shellCommand(int /*in*/, int out, int err, std::vector<Stri
ALOGV("shellCommand");
for (size_t i = 0, n = args.size(); i < n; i++)
- ALOGV(" arg[%zu]: '%s'", i, String8(args[i]).string());
+ ALOGV(" arg[%zu]: '%s'", i, String8(args[i]).c_str());
if (args.size() >= 1) {
if (args[0] == String16("vkjson")) return cmdVkjson(out, err);
diff --git a/services/gpuservice/OWNERS b/services/gpuservice/OWNERS
index 0ff65bf4ae..07c681f1f4 100644
--- a/services/gpuservice/OWNERS
+++ b/services/gpuservice/OWNERS
@@ -4,3 +4,4 @@ alecmouri@google.com
lfy@google.com
paulthomson@google.com
pbaiget@google.com
+kocdemir@google.com
diff --git a/services/gpuservice/gpumem/Android.bp b/services/gpuservice/gpumem/Android.bp
index d0ea856fb5..66a30597a7 100644
--- a/services/gpuservice/gpumem/Android.bp
+++ b/services/gpuservice/gpumem/Android.bp
@@ -21,12 +21,8 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
- name: "libgpumem",
- srcs: [
- "GpuMem.cpp",
- ],
- header_libs: ["bpf_headers"],
+cc_defaults {
+ name: "libgpumem_deps",
shared_libs: [
"libbase",
"libbpf_bcc",
@@ -34,6 +30,17 @@ cc_library_shared {
"liblog",
"libutils",
],
+}
+
+cc_library_static {
+ name: "libgpumem",
+ defaults: [
+ "libgpumem_deps",
+ ],
+ srcs: [
+ "GpuMem.cpp",
+ ],
+ header_libs: ["bpf_headers"],
export_include_dirs: ["include"],
export_header_lib_headers: ["bpf_headers"],
export_shared_lib_headers: ["libbase"],
diff --git a/services/gpuservice/gpumem/GpuMem.cpp b/services/gpuservice/gpumem/GpuMem.cpp
index dd3cc3bd86..d0783df109 100644
--- a/services/gpuservice/gpumem/GpuMem.cpp
+++ b/services/gpuservice/gpumem/GpuMem.cpp
@@ -61,6 +61,7 @@ void GpuMem::initialize() {
return;
}
// Retry until GPU driver loaded or timeout.
+ if (mStop.load()) return;
sleep(1);
}
@@ -77,7 +78,7 @@ void GpuMem::initialize() {
mInitialized.store(true);
}
-void GpuMem::setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+void GpuMem::setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map) {
mGpuMemTotalMap = std::move(map);
}
diff --git a/services/gpuservice/gpumem/include/gpumem/GpuMem.h b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
index 7588b54818..16b201f516 100644
--- a/services/gpuservice/gpumem/include/gpumem/GpuMem.h
+++ b/services/gpuservice/gpumem/include/gpumem/GpuMem.h
@@ -34,6 +34,7 @@ public:
// dumpsys interface
void dump(const Vector<String16>& args, std::string* result);
bool isInitialized() { return mInitialized.load(); }
+ void stop() { mStop.store(true); }
// Traverse the gpu memory total map to feed the callback function.
void traverseGpuMemTotals(const std::function<void(int64_t ts, uint32_t gpuId, uint32_t pid,
@@ -44,12 +45,16 @@ private:
friend class TestableGpuMem;
// set gpu memory total map
- void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map);
+ void setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map);
// indicate whether ebpf has been initialized
std::atomic<bool> mInitialized = false;
+
+ // whether initialization should be stopped
+ std::atomic<bool> mStop = false;
+
// bpf map for GPU memory total data
- android::bpf::BpfMap<uint64_t, uint64_t> mGpuMemTotalMap;
+ android::bpf::BpfMapRO<uint64_t, uint64_t> mGpuMemTotalMap;
// gpu memory tracepoint event category
static constexpr char kGpuMemTraceGroup[] = "gpu_mem";
diff --git a/services/gpuservice/gpustats/Android.bp b/services/gpuservice/gpustats/Android.bp
index 54291ad6c6..0e64716e6d 100644
--- a/services/gpuservice/gpustats/Android.bp
+++ b/services/gpuservice/gpustats/Android.bp
@@ -7,11 +7,8 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
- name: "libgfxstats",
- srcs: [
- "GpuStats.cpp",
- ],
+cc_defaults {
+ name: "libgfxstats_deps",
shared_libs: [
"libcutils",
"libgraphicsenv",
@@ -22,6 +19,16 @@ cc_library_shared {
"libstatssocket",
"libutils",
],
+}
+
+cc_library_static {
+ name: "libgfxstats",
+ defaults: [
+ "libgfxstats_deps",
+ ],
+ srcs: [
+ "GpuStats.cpp",
+ ],
export_include_dirs: ["include"],
export_shared_lib_headers: [
"libstatspull",
diff --git a/services/gpuservice/gpustats/GpuStats.cpp b/services/gpuservice/gpustats/GpuStats.cpp
index d033453dd7..11b636d564 100644
--- a/services/gpuservice/gpustats/GpuStats.cpp
+++ b/services/gpuservice/gpustats/GpuStats.cpp
@@ -163,11 +163,13 @@ void GpuStats::insertDriverStats(const std::string& driverPackageName,
addLoadingTime(driver, driverLoadingTime, &appInfo);
appInfo.appPackageName = appPackageName;
appInfo.driverVersionCode = driverVersionCode;
- appInfo.angleInUse = driverPackageName == "angle";
+ appInfo.angleInUse =
+ driver == GpuStatsInfo::Driver::ANGLE || driverPackageName == "angle";
appInfo.lastAccessTime = std::chrono::system_clock::now();
mAppStats.insert({appStatsKey, appInfo});
} else {
- mAppStats[appStatsKey].angleInUse = driverPackageName == "angle";
+ mAppStats[appStatsKey].angleInUse =
+ driver == GpuStatsInfo::Driver::ANGLE || driverPackageName == "angle";
addLoadingTime(driver, driverLoadingTime, &mAppStats[appStatsKey]);
mAppStats[appStatsKey].lastAccessTime = std::chrono::system_clock::now();
}
@@ -175,29 +177,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<std::mutex> 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<int32_t>& 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 +403,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 2aba651af9..22c64dbc02 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<String16>& 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/gpuwork/GpuWork.cpp b/services/gpuservice/gpuwork/GpuWork.cpp
index fd703239e9..1a744abc6d 100644
--- a/services/gpuservice/gpuwork/GpuWork.cpp
+++ b/services/gpuservice/gpuwork/GpuWork.cpp
@@ -243,6 +243,7 @@ bool GpuWork::attachTracepoint(const char* programPath, const char* tracepointGr
return false;
}
// Retry until GPU driver loaded or timeout.
+ if (mStop.load()) return false;
sleep(1);
errno = 0;
}
diff --git a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
index cece9999c6..e70da540b9 100644
--- a/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
+++ b/services/gpuservice/gpuwork/include/gpuwork/GpuWork.h
@@ -40,6 +40,7 @@ public:
~GpuWork();
void initialize();
+ void stop() { mStop.store(true); }
// Dumps the GPU work information.
void dump(const Vector<String16>& args, std::string* result);
@@ -47,7 +48,7 @@ public:
private:
// Attaches tracepoint |tracepoint_group|/|tracepoint_name| to BPF program at path
// |program_path|. The tracepoint is also enabled.
- static bool attachTracepoint(const char* program_path, const char* tracepoint_group,
+ bool attachTracepoint(const char* program_path, const char* tracepoint_group,
const char* tracepoint_name);
// Native atom puller callback registered in statsd.
@@ -80,6 +81,9 @@ private:
// Indicates whether our eBPF components have been initialized.
std::atomic<bool> mInitialized = false;
+ // Indicates whether eBPF initialization should be stopped.
+ std::atomic<bool> mStop = false;
+
// A thread that periodically checks whether |mGpuWorkMap| is nearly full
// and, if so, clears it.
std::thread mMapClearerThread;
diff --git a/services/gpuservice/GpuService.h b/services/gpuservice/include/gpuservice/GpuService.h
index d7313d165e..54f8f666bc 100644
--- a/services/gpuservice/GpuService.h
+++ b/services/gpuservice/include/gpuservice/GpuService.h
@@ -24,6 +24,7 @@
#include <serviceutils/PriorityDumper.h>
#include <mutex>
+#include <thread>
#include <vector>
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<String16>& 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<GpuMemTracer> mGpuMemTracer;
std::mutex mLock;
std::string mDeveloperDriverPath;
+ std::unique_ptr<std::thread> mGpuMemAsyncInitThread;
+ std::unique_ptr<std::thread> mGpuWorkAsyncInitThread;
};
} // namespace android
diff --git a/services/gpuservice/main_gpuservice.cpp b/services/gpuservice/main_gpuservice.cpp
index 64aafcab6a..200237219e 100644
--- a/services/gpuservice/main_gpuservice.cpp
+++ b/services/gpuservice/main_gpuservice.cpp
@@ -18,7 +18,7 @@
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
#include <sys/resource.h>
-#include "GpuService.h"
+#include "gpuservice/GpuService.h"
using namespace android;
diff --git a/services/gpuservice/tests/fuzzers/Android.bp b/services/gpuservice/tests/fuzzers/Android.bp
new file mode 100644
index 0000000000..d4d48c48ea
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/Android.bp
@@ -0,0 +1,25 @@
+package {
+ default_applicable_licenses: ["frameworks_native_license"],
+}
+
+cc_fuzz {
+ name: "gpu_service_fuzzer",
+ defaults: [
+ "libgpuservice_defaults",
+ "service_fuzzer_defaults",
+ "fuzzer_disable_leaks",
+ ],
+ static_libs: [
+ "libgpuservice",
+ "liblog",
+ ],
+ fuzz_config: {
+ cc: [
+ "paulthomson@google.com",
+ "pbaiget@google.com",
+ ],
+ triage_assignee: "waghpawan@google.com",
+ },
+ include_dirs: ["frameworks/native/services/gpuservice/"],
+ srcs: ["GpuServiceFuzzer.cpp"],
+}
diff --git a/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp b/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp
new file mode 100644
index 0000000000..241b8646e1
--- /dev/null
+++ b/services/gpuservice/tests/fuzzers/GpuServiceFuzzer.cpp
@@ -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 <fuzzbinder/libbinder_driver.h>
+
+#include "gpuservice/GpuService.h"
+
+using ::android::fuzzService;
+using ::android::GpuService;
+using ::android::sp;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ sp<GpuService> gpuService = new GpuService();
+ fuzzService(gpuService, FuzzedDataProvider(data, size));
+ return 0;
+}
diff --git a/services/gpuservice/tests/unittests/Android.bp b/services/gpuservice/tests/unittests/Android.bp
index 86f6c7febb..8056a2c601 100644
--- a/services/gpuservice/tests/unittests/Android.bp
+++ b/services/gpuservice/tests/unittests/Android.bp
@@ -24,13 +24,14 @@ package {
cc_test {
name: "gpuservice_unittest",
test_suites: ["device-tests"],
- sanitize: {
- address: true,
- },
+ defaults: [
+ "libgpuservice_defaults",
+ ],
srcs: [
"GpuMemTest.cpp",
"GpuMemTracerTest.cpp",
"GpuStatsTest.cpp",
+ "GpuServiceTest.cpp",
],
header_libs: ["bpf_headers"],
shared_libs: [
@@ -38,9 +39,6 @@ cc_test {
"libbinder",
"libbpf_bcc",
"libcutils",
- "libgfxstats",
- "libgpumem",
- "libgpumemtracer",
"libgraphicsenv",
"liblog",
"libprotobuf-cpp-lite",
@@ -51,6 +49,7 @@ cc_test {
],
static_libs: [
"libgmock",
+ "libgpuservice",
"libperfetto_client_experimental",
"perfetto_trace_protos",
],
diff --git a/services/gpuservice/tests/unittests/GpuMemTest.cpp b/services/gpuservice/tests/unittests/GpuMemTest.cpp
index 8dabe4fbdb..1f5b2889d1 100644
--- a/services/gpuservice/tests/unittests/GpuMemTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTest.cpp
@@ -66,9 +66,7 @@ public:
mTestableGpuMem = TestableGpuMem(mGpuMem.get());
mTestableGpuMem.setInitialized();
errno = 0;
- mTestMap = std::move(bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH,
- TEST_MAP_SIZE,
- BPF_F_NO_PREALLOC));
+ mTestMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
EXPECT_EQ(0, errno);
EXPECT_TRUE(mTestMap.isValid());
diff --git a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
index 5c042102b2..6550df9e58 100644
--- a/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
+++ b/services/gpuservice/tests/unittests/GpuMemTracerTest.cpp
@@ -65,9 +65,7 @@ public:
mTestableGpuMem = TestableGpuMem(mGpuMem.get());
errno = 0;
- mTestMap = std::move(bpf::BpfMap<uint64_t, uint64_t>(BPF_MAP_TYPE_HASH,
- TEST_MAP_SIZE,
- BPF_F_NO_PREALLOC));
+ mTestMap.resetMap(BPF_MAP_TYPE_HASH, TEST_MAP_SIZE, BPF_F_NO_PREALLOC);
EXPECT_EQ(0, errno);
EXPECT_TRUE(mTestMap.isValid());
diff --git a/services/gpuservice/tests/unittests/GpuServiceTest.cpp b/services/gpuservice/tests/unittests/GpuServiceTest.cpp
new file mode 100644
index 0000000000..62b3e53f53
--- /dev/null
+++ b/services/gpuservice/tests/unittests/GpuServiceTest.cpp
@@ -0,0 +1,52 @@
+#undef LOG_TAG
+#define LOG_TAG "gpuservice_unittest"
+
+#include "gpuservice/GpuService.h"
+
+#include <gtest/gtest.h>
+#include <log/log_main.h>
+
+#include <chrono>
+#include <thread>
+
+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<GpuService> 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 7ea22888f8..4ce533ff7c 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/tests/unittests/TestableGpuMem.h b/services/gpuservice/tests/unittests/TestableGpuMem.h
index 6c8becb075..f21843fe1a 100644
--- a/services/gpuservice/tests/unittests/TestableGpuMem.h
+++ b/services/gpuservice/tests/unittests/TestableGpuMem.h
@@ -28,7 +28,7 @@ public:
void setInitialized() { mGpuMem->mInitialized.store(true); }
- void setGpuMemTotalMap(bpf::BpfMap<uint64_t, uint64_t>& map) {
+ void setGpuMemTotalMap(bpf::BpfMapRO<uint64_t, uint64_t>& map) {
mGpuMem->setGpuMemTotalMap(map);
}
diff --git a/services/gpuservice/tracing/Android.bp b/services/gpuservice/tracing/Android.bp
index a1bc1edad8..d636b7d195 100644
--- a/services/gpuservice/tracing/Android.bp
+++ b/services/gpuservice/tracing/Android.bp
@@ -21,20 +21,28 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
- name: "libgpumemtracer",
- srcs: [
- "GpuMemTracer.cpp",
- ],
+cc_defaults {
+ name: "libgpumemtracer_deps",
shared_libs: [
- "libgpumem",
"libbase",
"liblog",
"libutils",
],
static_libs: [
+ "libgpumem",
"libperfetto_client_experimental",
],
+}
+
+cc_library_static {
+ name: "libgpumemtracer",
+ defaults: [
+ "libgpumemtracer_deps",
+ "libgpumem_deps",
+ ],
+ srcs: [
+ "GpuMemTracer.cpp",
+ ],
export_include_dirs: ["include"],
export_static_lib_headers: [
"libperfetto_client_experimental",
diff --git a/services/gpuservice/vts/Android.bp b/services/gpuservice/vts/Android.bp
index 83a40e7bbf..a24822a7d1 100644
--- a/services/gpuservice/vts/Android.bp
+++ b/services/gpuservice/vts/Android.bp
@@ -21,7 +21,7 @@ java_test_host {
srcs: ["src/**/*.java"],
libs: [
"tradefed",
- "vts-core-tradefed-harness",
+ "compatibility-host-util",
],
test_suites: [
"general-tests",
diff --git a/services/gpuservice/vts/OWNERS b/services/gpuservice/vts/OWNERS
index e789052fa3..a63de1c306 100644
--- a/services/gpuservice/vts/OWNERS
+++ b/services/gpuservice/vts/OWNERS
@@ -1,4 +1,5 @@
# Bug component: 653544
+kocdemir@google.com
paulthomson@google.com
pbaiget@google.com
lfy@google.com
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 9fa901675d..6c16335359 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 18d670ae38..7f2d03d310 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -28,6 +28,7 @@ inputflinger_tidy_checks = [
cc_defaults {
name: "inputflinger_defaults",
+ host_supported: true,
cpp_std: "c++20",
cflags: [
"-Wall",
@@ -38,15 +39,32 @@ 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: [
"-*", // Disable all checks not explicitly enabled for now
] + inputflinger_tidy_checks,
tidy_checks_as_errors: inputflinger_tidy_checks,
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ include_dirs: [
+ "bionic/libc/kernel/android/uapi/",
+ "bionic/libc/kernel/uapi",
+ ],
+ cflags: [
+ "-D__ANDROID_HOST__",
+ ],
+ },
+ },
}
/////////////////////////////////////////////////
@@ -56,11 +74,13 @@ cc_defaults {
filegroup {
name: "libinputflinger_sources",
srcs: [
- "InputClassifier.cpp",
"InputCommonConverter.cpp",
+ "InputDeviceMetricsCollector.cpp",
+ "InputFilter.cpp",
+ "InputProcessor.cpp",
+ "PointerChoreographer.cpp",
"PreferStylusOverTouchBlocker.cpp",
"UnwantedInteractionBlocker.cpp",
- "InputManager.cpp",
],
}
@@ -69,6 +89,7 @@ cc_defaults {
srcs: [":libinputflinger_sources"],
shared_libs: [
"android.hardware.input.processor-V1-ndk",
+ "com.android.server.inputflinger-ndk",
"libbase",
"libbinder",
"libbinder_ndk",
@@ -81,24 +102,44 @@ cc_defaults {
"liblog",
"libprotobuf-cpp-lite",
"libstatslog",
+ "libutils",
"libstatspull",
"libstatssocket",
- "libutils",
- "libui",
"server_configurable_flags",
],
static_libs: [
"libattestation",
"libpalmrejection",
+ "libui-types",
+ ],
+ generated_headers: [
+ "cxx-bridge-header",
+ "inputflinger_rs_bootstrap_bridge_header",
],
+ header_libs: ["inputflinger_rs_bootstrap_cxx_headers"],
+ generated_sources: ["inputflinger_rs_bootstrap_bridge_code"],
+ whole_static_libs: ["libinputflinger_rs"],
+ export_shared_lib_headers: ["com.android.server.inputflinger-ndk"],
+ target: {
+ android: {
+ shared_libs: [
+ "libgui",
+ ],
+ },
+ },
}
cc_library_shared {
name: "libinputflinger",
+ host_supported: true,
defaults: [
"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 +148,9 @@ 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",
+ "libPlatformProperties",
],
static_libs: [
"libinputdispatcher",
@@ -129,15 +170,18 @@ cc_library_shared {
cc_library_headers {
name: "libinputflinger_headers",
+ host_supported: true,
export_include_dirs: ["include"],
}
filegroup {
name: "libinputflinger_base_sources",
srcs: [
+ "InputDeviceMetricsSource.cpp",
"InputListener.cpp",
"InputReaderBase.cpp",
"InputThread.cpp",
+ "NotifyArgs.cpp",
"VibrationElement.cpp",
],
}
@@ -151,7 +195,7 @@ cc_defaults {
"libcutils",
"libinput",
"liblog",
- "libui",
+ "libstatslog",
"libutils",
],
header_libs: [
@@ -161,6 +205,7 @@ cc_defaults {
cc_library_shared {
name: "libinputflinger_base",
+ host_supported: true,
defaults: [
"inputflinger_defaults",
"libinputflinger_base_defaults",
@@ -169,3 +214,58 @@ 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",
+ "libinputreader_static",
+ "libinputflinger",
+ "inputflinger_tests",
+ "inputflinger_benchmarks",
+ "libinput_tests",
+ "libpalmrejection_test",
+ "libandroid_runtime",
+ "libinputservice_test",
+ "Bug-115739809",
+ "StructLayout_test",
+
+ // jni
+ "libservices.core",
+
+ // rust targets
+ "libinput_rust_test",
+
+ // native fuzzers
+ "inputflinger_latencytracker_fuzzer",
+ "inputflinger_cursor_input_fuzzer",
+ "inputflinger_keyboard_input_fuzzer",
+ "inputflinger_multitouch_input_fuzzer",
+ "inputflinger_switch_input_fuzzer",
+ "inputflinger_touchpad_input_fuzzer",
+ "inputflinger_input_reader_fuzzer",
+ "inputflinger_blocking_queue_fuzzer",
+ "inputflinger_input_classifier_fuzzer",
+ "inputflinger_input_dispatcher_fuzzer",
+
+ // Java/Kotlin targets
+ "CtsWindowManagerDeviceWindow",
+ "InputTests",
+ "CtsHardwareTestCases",
+ "CtsInputTestCases",
+ "CtsViewTestCases",
+ "CtsWidgetTestCases",
+ "FrameworksCoreTests",
+ "FrameworksServicesTests",
+ "CtsSecurityTestCases",
+ "CtsSecurityBulletinHostTestCases",
+ ],
+}
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 8aee39fd0b..6ccd9e7697 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<common::Button>(buttonState);
}
-static common::ToolType getToolType(int32_t toolType) {
- static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) ==
- common::ToolType::UNKNOWN);
- static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_FINGER) ==
- common::ToolType::FINGER);
- static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_STYLUS) ==
- common::ToolType::STYLUS);
- static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_MOUSE) ==
- common::ToolType::MOUSE);
- static_assert(static_cast<common::ToolType>(AMOTION_EVENT_TOOL_TYPE_ERASER) ==
- common::ToolType::ERASER);
+static common::ToolType getToolType(ToolType toolType) {
+ static_assert(static_cast<common::ToolType>(ToolType::UNKNOWN) == common::ToolType::UNKNOWN);
+ static_assert(static_cast<common::ToolType>(ToolType::FINGER) == common::ToolType::FINGER);
+ static_assert(static_cast<common::ToolType>(ToolType::STYLUS) == common::ToolType::STYLUS);
+ static_assert(static_cast<common::ToolType>(ToolType::MOUSE) == common::ToolType::MOUSE);
+ static_assert(static_cast<common::ToolType>(ToolType::ERASER) == common::ToolType::ERASER);
return static_cast<common::ToolType>(toolType);
}
@@ -263,6 +258,12 @@ static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_13) == common
static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == common::Axis::GENERIC_14);
static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == common::Axis::GENERIC_15);
static_assert(static_cast<common::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == common::Axis::GENERIC_16);
+// TODO(b/251196347): add GESTURE_{X,Y}_OFFSET, GESTURE_SCROLL_{X,Y}_DISTANCE,
+// GESTURE_PINCH_SCALE_FACTOR, and GESTURE_SWIPE_FINGER_COUNT.
+// 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<common::Axis>(AMOTION_EVENT_MAXIMUM_VALID_AXIS_VALUE) ==
+ static_cast<common::Axis>(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT));
static common::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) {
common::VideoFrame out;
@@ -288,9 +289,9 @@ static std::vector<common::VideoFrame> convertVideoFrames(
static void getHalPropertiesAndCoords(const NotifyMotionArgs& args,
std::vector<common::PointerProperties>& outPointerProperties,
std::vector<common::PointerCoords>& outPointerCoords) {
- outPointerProperties.reserve(args.pointerCount);
- outPointerCoords.reserve(args.pointerCount);
- for (size_t i = 0; i < args.pointerCount; i++) {
+ outPointerProperties.reserve(args.getPointerCount());
+ outPointerCoords.reserve(args.getPointerCount());
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
common::PointerProperties properties;
properties.id = args.pointerProperties[i].id;
properties.toolType = getToolType(args.pointerProperties[i].toolType);
@@ -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<float>(args.pointerCoords[i].values,
- args.pointerCoords[i].values +
+ coords.values = std::vector<float>(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/InputDeviceMetricsCollector.cpp b/services/inputflinger/InputDeviceMetricsCollector.cpp
new file mode 100644
index 0000000000..b5cb3cbb57
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsCollector.cpp
@@ -0,0 +1,414 @@
+/*
+ * 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 LOG_TAG "InputDeviceMetricsCollector"
+#include "InputDeviceMetricsCollector.h"
+
+#include "InputDeviceMetricsSource.h"
+
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
+
+namespace android {
+
+using android::base::StringPrintf;
+using std::chrono::nanoseconds;
+using std::chrono_literals::operator""ns;
+
+namespace {
+
+constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::minutes(2);
+
+/**
+ * Log debug messages about metrics events logged to statsd.
+ * Enable this via "adb shell setprop log.tag.InputDeviceMetricsCollector DEBUG" (requires restart)
+ */
+const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
+
+constexpr size_t INTERACTIONS_QUEUE_CAPACITY = 500;
+
+int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus, bool isUsiStylus) {
+ if (isUsiStylus) {
+ // This is a stylus connected over the Universal Stylus Initiative (USI) protocol.
+ // For metrics purposes, we treat this protocol as a separate bus.
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USI;
+ }
+
+ // When adding cases to this switch, also add them to the copy of this method in
+ // TouchpadInputMapper.cpp.
+ // TODO(b/286394420): deduplicate this method with the one in TouchpadInputMapper.cpp.
+ switch (linuxBus) {
+ case BUS_USB:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
+ case BUS_BLUETOOTH:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
+ default:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
+ }
+}
+
+class : public InputDeviceMetricsLogger {
+ nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
+
+ void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
+ const DeviceUsageReport& report) override {
+ const int32_t durationMillis =
+ std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
+ const static std::vector<int32_t> empty;
+
+ ALOGD_IF(DEBUG, "Usage session reported for device id: %d", info.deviceId);
+ ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis);
+ ALOGD_IF(DEBUG, " Source breakdown:");
+
+ std::vector<int32_t> sources;
+ std::vector<int32_t> durationsPerSource;
+ for (auto& [src, dur] : report.sourceBreakdown) {
+ sources.push_back(ftl::to_underlying(src));
+ int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
+ durationsPerSource.emplace_back(durMillis);
+ ALOGD_IF(DEBUG, " - usageSource: %s\t duration: %dms",
+ ftl::enum_string(src).c_str(), durMillis);
+ }
+
+ ALOGD_IF(DEBUG, " Uid breakdown:");
+
+ std::vector<int32_t> uids;
+ std::vector<int32_t> durationsPerUid;
+ for (auto& [uid, dur] : report.uidBreakdown) {
+ uids.push_back(uid.val());
+ int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
+ durationsPerUid.push_back(durMillis);
+ ALOGD_IF(DEBUG, " - uid: %s\t duration: %dms", uid.toString().c_str(),
+ durMillis);
+ }
+ util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, info.vendor, info.product, info.version,
+ linuxBusToInputDeviceBusEnum(info.bus, info.isUsiStylus), durationMillis,
+ sources, durationsPerSource, uids, durationsPerUid);
+ }
+} sStatsdLogger;
+
+bool isIgnoredInputDeviceId(int32_t deviceId) {
+ switch (deviceId) {
+ case INVALID_INPUT_DEVICE_ID:
+ case VIRTUAL_KEYBOARD_ID:
+ return true;
+ default:
+ return false;
+ }
+}
+
+} // namespace
+
+InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
+ : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}
+
+InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener,
+ InputDeviceMetricsLogger& logger,
+ nanoseconds usageSessionTimeout)
+ : mNextListener(listener),
+ mLogger(logger),
+ mUsageSessionTimeout(usageSessionTimeout),
+ mInteractionsQueue(INTERACTIONS_QUEUE_CAPACITY) {}
+
+void InputDeviceMetricsCollector::notifyInputDevicesChanged(
+ const NotifyInputDevicesChangedArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ onInputDevicesChanged(args.inputDeviceInfos);
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyConfigurationChanged(
+ const NotifyConfigurationChangedArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
+ return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
+ };
+ onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime),
+ [&args](const auto&) { return getUsageSourcesForMotionArgs(args); });
+ }
+
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyPointerCaptureChanged(
+ const NotifyPointerCaptureChangedArgs& args) {
+ {
+ std::scoped_lock lock(mLock);
+ reportCompletedSessions();
+ }
+ mNextListener.notify(args);
+}
+
+void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<Uid>& uids) {
+ if (isIgnoredInputDeviceId(deviceId)) {
+ return;
+ }
+ std::scoped_lock lock(mLock);
+ mInteractionsQueue.push(DeviceId{deviceId}, timestamp, uids);
+}
+
+void InputDeviceMetricsCollector::dump(std::string& dump) {
+ std::scoped_lock lock(mLock);
+ dump += "InputDeviceMetricsCollector:\n";
+
+ dump += " Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n";
+ dump += " Devices with active usage sessions: " +
+ dumpMapKeys(mActiveUsageSessions, &toString) + "\n";
+}
+
+void InputDeviceMetricsCollector::monitor() {
+ std::scoped_lock lock(mLock);
+}
+
+void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
+ std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
+
+ for (const InputDeviceInfo& info : infos) {
+ if (isIgnoredInputDeviceId(info.getId())) {
+ continue;
+ }
+ const auto& i = info.getIdentifier();
+ newDeviceInfos.emplace(info.getId(),
+ MetricsDeviceInfo{
+ .deviceId = info.getId(),
+ .vendor = i.vendor,
+ .product = i.product,
+ .version = i.version,
+ .bus = i.bus,
+ .isUsiStylus = info.getUsiVersion().has_value(),
+ .keyboardType = info.getKeyboardType(),
+ });
+ }
+
+ for (auto [deviceId, info] : mLoggedDeviceInfos) {
+ if (newDeviceInfos.count(deviceId) != 0) {
+ continue;
+ }
+ onInputDeviceRemoved(deviceId, info);
+ }
+
+ std::swap(newDeviceInfos, mLoggedDeviceInfos);
+}
+
+void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
+ const MetricsDeviceInfo& info) {
+ auto it = mActiveUsageSessions.find(deviceId);
+ if (it == mActiveUsageSessions.end()) {
+ return;
+ }
+ // Report usage for that device if there is an active session.
+ auto& [_, activeSession] = *it;
+ mLogger.logInputDeviceUsageReported(info, activeSession.finishSession());
+ mActiveUsageSessions.erase(it);
+
+ // We don't remove this from mLoggedDeviceInfos because it will be updated in
+ // onInputDevicesChanged().
+}
+
+void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime,
+ const SourceProvider& getSources) {
+ auto infoIt = mLoggedDeviceInfos.find(deviceId);
+ if (infoIt == mLoggedDeviceInfos.end()) {
+ // Do not track usage for devices that are not logged.
+ return;
+ }
+
+ auto [sessionIt, _] =
+ mActiveUsageSessions.try_emplace(deviceId, mUsageSessionTimeout, eventTime);
+ for (InputDeviceUsageSource source : getSources(infoIt->second)) {
+ sessionIt->second.recordUsage(eventTime, source);
+ }
+}
+
+void InputDeviceMetricsCollector::onInputDeviceInteraction(const Interaction& interaction) {
+ auto activeSessionIt = mActiveUsageSessions.find(std::get<DeviceId>(interaction));
+ if (activeSessionIt == mActiveUsageSessions.end()) {
+ return;
+ }
+
+ activeSessionIt->second.recordInteraction(interaction);
+}
+
+void InputDeviceMetricsCollector::reportCompletedSessions() {
+ // Process all pending interactions.
+ for (auto interaction = mInteractionsQueue.pop(); interaction;
+ interaction = mInteractionsQueue.pop()) {
+ onInputDeviceInteraction(*interaction);
+ }
+
+ const auto currentTime = mLogger.getCurrentTime();
+ std::vector<DeviceId> completedUsageSessions;
+
+ // Process usages for all active session to determine if any sessions have expired.
+ for (auto& [deviceId, activeSession] : mActiveUsageSessions) {
+ if (activeSession.checkIfCompletedAt(currentTime)) {
+ completedUsageSessions.emplace_back(deviceId);
+ }
+ }
+
+ // Close out and log all expired usage sessions.
+ for (DeviceId deviceId : completedUsageSessions) {
+ const auto infoIt = mLoggedDeviceInfos.find(deviceId);
+ LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end());
+
+ auto activeSessionIt = mActiveUsageSessions.find(deviceId);
+ LOG_ALWAYS_FATAL_IF(activeSessionIt == mActiveUsageSessions.end());
+ auto& [_, activeSession] = *activeSessionIt;
+ mLogger.logInputDeviceUsageReported(infoIt->second, activeSession.finishSession());
+ mActiveUsageSessions.erase(activeSessionIt);
+ }
+}
+
+// --- InputDeviceMetricsCollector::ActiveSession ---
+
+InputDeviceMetricsCollector::ActiveSession::ActiveSession(nanoseconds usageSessionTimeout,
+ nanoseconds startTime)
+ : mUsageSessionTimeout(usageSessionTimeout), mDeviceSession({startTime, startTime}) {}
+
+void InputDeviceMetricsCollector::ActiveSession::recordUsage(nanoseconds eventTime,
+ InputDeviceUsageSource source) {
+ // We assume that event times for subsequent events are always monotonically increasing for each
+ // input device.
+ auto [activeSourceIt, inserted] =
+ mActiveSessionsBySource.try_emplace(source, eventTime, eventTime);
+ if (!inserted) {
+ activeSourceIt->second.end = eventTime;
+ }
+ mDeviceSession.end = eventTime;
+}
+
+void InputDeviceMetricsCollector::ActiveSession::recordInteraction(const Interaction& interaction) {
+ const auto sessionExpiryTime = mDeviceSession.end + mUsageSessionTimeout;
+ const auto timestamp = std::get<nanoseconds>(interaction);
+ if (timestamp >= sessionExpiryTime) {
+ // This interaction occurred after the device's current active session is set to expire.
+ // Ignore it.
+ return;
+ }
+
+ for (Uid uid : std::get<std::set<Uid>>(interaction)) {
+ auto [activeUidIt, inserted] = mActiveSessionsByUid.try_emplace(uid, timestamp, timestamp);
+ if (!inserted) {
+ activeUidIt->second.end = timestamp;
+ }
+ }
+}
+
+bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds timestamp) {
+ const auto sessionExpiryTime = timestamp - mUsageSessionTimeout;
+ std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice;
+ for (auto& [source, session] : mActiveSessionsBySource) {
+ if (session.end <= sessionExpiryTime) {
+ completedSourceSessionsForDevice.emplace_back(source);
+ }
+ }
+ for (InputDeviceUsageSource source : completedSourceSessionsForDevice) {
+ auto it = mActiveSessionsBySource.find(source);
+ const auto& [_, session] = *it;
+ mSourceUsageBreakdown.emplace_back(source, session.end - session.start);
+ mActiveSessionsBySource.erase(it);
+ }
+
+ std::vector<Uid> completedUidSessionsForDevice;
+ for (auto& [uid, session] : mActiveSessionsByUid) {
+ if (session.end <= sessionExpiryTime) {
+ completedUidSessionsForDevice.emplace_back(uid);
+ }
+ }
+ for (Uid uid : completedUidSessionsForDevice) {
+ auto it = mActiveSessionsByUid.find(uid);
+ const auto& [_, session] = *it;
+ mUidUsageBreakdown.emplace_back(uid, session.end - session.start);
+ mActiveSessionsByUid.erase(it);
+ }
+
+ // This active session has expired if there are no more active source sessions tracked.
+ return mActiveSessionsBySource.empty();
+}
+
+InputDeviceMetricsLogger::DeviceUsageReport
+InputDeviceMetricsCollector::ActiveSession::finishSession() {
+ const auto deviceUsageDuration = mDeviceSession.end - mDeviceSession.start;
+
+ for (const auto& [source, sourceSession] : mActiveSessionsBySource) {
+ mSourceUsageBreakdown.emplace_back(source, sourceSession.end - sourceSession.start);
+ }
+ mActiveSessionsBySource.clear();
+
+ for (const auto& [uid, uidSession] : mActiveSessionsByUid) {
+ mUidUsageBreakdown.emplace_back(uid, uidSession.end - uidSession.start);
+ }
+ mActiveSessionsByUid.clear();
+
+ return {deviceUsageDuration, mSourceUsageBreakdown, mUidUsageBreakdown};
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsCollector.h b/services/inputflinger/InputDeviceMetricsCollector.h
new file mode 100644
index 0000000000..1bcd527626
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsCollector.h
@@ -0,0 +1,186 @@
+/*
+ * 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 "InputDeviceMetricsSource.h"
+#include "InputListener.h"
+#include "NotifyArgs.h"
+#include "SyncQueue.h"
+
+#include <android-base/thread_annotations.h>
+#include <ftl/mixins.h>
+#include <gui/WindowInfo.h>
+#include <input/InputDevice.h>
+#include <chrono>
+#include <functional>
+#include <map>
+#include <mutex>
+#include <set>
+#include <vector>
+
+namespace android {
+
+/**
+ * Logs metrics about registered input devices and their usages.
+ */
+class InputDeviceMetricsCollectorInterface : public InputListenerInterface {
+public:
+ /**
+ * Notify the metrics collector that there was an input device interaction with apps.
+ * Called from the InputDispatcher thread.
+ */
+ virtual void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<gui::Uid>& uids) = 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 heartbeat to ensure that this component has not deadlocked. */
+ virtual void monitor() = 0;
+};
+
+/** The logging interface for the metrics collector, injected for testing. */
+class InputDeviceMetricsLogger {
+public:
+ virtual std::chrono::nanoseconds getCurrentTime() = 0;
+
+ // Describes the breakdown of an input device usage session by its usage sources.
+ // An input device can have more than one usage source. For example, some game controllers have
+ // buttons, joysticks, and touchpads. We track usage by these sources to get a better picture of
+ // the device usage. The source breakdown of a 10 minute usage session could look like this:
+ // { {GAMEPAD, <9 mins>}, {TOUCHPAD, <2 mins>}, {TOUCHPAD, <3 mins>} }
+ // This would indicate that the GAMEPAD source was used first, and that source usage session
+ // lasted for 9 mins. During that time, the TOUCHPAD was used for 2 mins, until its source
+ // usage session expired. The TOUCHPAD was then used again later for another 3 mins.
+ using SourceUsageBreakdown =
+ std::vector<std::pair<InputDeviceUsageSource, std::chrono::nanoseconds /*duration*/>>;
+
+ // Describes the breakdown of an input device usage session by the UIDs that it interacted with.
+ using UidUsageBreakdown =
+ std::vector<std::pair<gui::Uid, std::chrono::nanoseconds /*duration*/>>;
+
+ struct DeviceUsageReport {
+ std::chrono::nanoseconds usageDuration;
+ SourceUsageBreakdown sourceBreakdown;
+ UidUsageBreakdown uidBreakdown;
+ };
+
+ // A subset of information from the InputDeviceInfo class that is used for metrics collection,
+ // used to avoid copying and storing all of the fields and strings in InputDeviceInfo.
+ struct MetricsDeviceInfo {
+ int32_t deviceId;
+ int32_t vendor;
+ int32_t product;
+ int32_t version;
+ int32_t bus;
+ bool isUsiStylus;
+ int32_t keyboardType;
+ };
+ virtual void logInputDeviceUsageReported(const MetricsDeviceInfo&,
+ const DeviceUsageReport&) = 0;
+ virtual ~InputDeviceMetricsLogger() = default;
+};
+
+class InputDeviceMetricsCollector : public InputDeviceMetricsCollectorInterface {
+public:
+ explicit InputDeviceMetricsCollector(InputListenerInterface& listener);
+ ~InputDeviceMetricsCollector() override = default;
+
+ // Test constructor
+ InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger,
+ std::chrono::nanoseconds usageSessionTimeout);
+
+ 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;
+
+ void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<gui::Uid>& uids) override;
+ void dump(std::string& dump) override;
+ void monitor() override;
+
+private:
+ std::mutex mLock;
+ InputListenerInterface& mNextListener;
+ InputDeviceMetricsLogger& mLogger GUARDED_BY(mLock);
+ const std::chrono::nanoseconds mUsageSessionTimeout;
+
+ // Type-safe wrapper for input device id.
+ struct DeviceId : ftl::Constructible<DeviceId, std::int32_t>,
+ ftl::Equatable<DeviceId>,
+ ftl::Orderable<DeviceId> {
+ using Constructible::Constructible;
+ };
+ static inline std::string toString(const DeviceId& id) {
+ return std::to_string(ftl::to_underlying(id));
+ }
+
+ using Uid = gui::Uid;
+ using MetricsDeviceInfo = InputDeviceMetricsLogger::MetricsDeviceInfo;
+
+ std::map<DeviceId, MetricsDeviceInfo> mLoggedDeviceInfos GUARDED_BY(mLock);
+
+ using Interaction = std::tuple<DeviceId, std::chrono::nanoseconds, std::set<Uid>>;
+ SyncQueue<Interaction> mInteractionsQueue GUARDED_BY(mLock);
+
+ class ActiveSession {
+ public:
+ explicit ActiveSession(std::chrono::nanoseconds usageSessionTimeout,
+ std::chrono::nanoseconds startTime);
+ void recordUsage(std::chrono::nanoseconds eventTime, InputDeviceUsageSource source);
+ void recordInteraction(const Interaction&);
+ bool checkIfCompletedAt(std::chrono::nanoseconds timestamp);
+ InputDeviceMetricsLogger::DeviceUsageReport finishSession();
+
+ private:
+ struct UsageSession {
+ std::chrono::nanoseconds start{};
+ std::chrono::nanoseconds end{};
+ };
+
+ const std::chrono::nanoseconds mUsageSessionTimeout;
+ UsageSession mDeviceSession{};
+
+ std::map<InputDeviceUsageSource, UsageSession> mActiveSessionsBySource{};
+ InputDeviceMetricsLogger::SourceUsageBreakdown mSourceUsageBreakdown{};
+
+ std::map<Uid, UsageSession> mActiveSessionsByUid{};
+ InputDeviceMetricsLogger::UidUsageBreakdown mUidUsageBreakdown{};
+ };
+
+ // The input devices that currently have active usage sessions.
+ std::map<DeviceId, ActiveSession> mActiveUsageSessions GUARDED_BY(mLock);
+
+ void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) REQUIRES(mLock);
+ void onInputDeviceRemoved(DeviceId deviceId, const MetricsDeviceInfo& info) REQUIRES(mLock);
+ using SourceProvider =
+ std::function<std::set<InputDeviceUsageSource>(const MetricsDeviceInfo&)>;
+ void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime,
+ const SourceProvider& getSources) REQUIRES(mLock);
+ void onInputDeviceInteraction(const Interaction&) REQUIRES(mLock);
+ void reportCompletedSessions() REQUIRES(mLock);
+};
+
+} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsSource.cpp b/services/inputflinger/InputDeviceMetricsSource.cpp
new file mode 100644
index 0000000000..dee4cb836e
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsSource.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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 "InputDeviceMetricsSource.h"
+
+#include "KeyCodeClassifications.h"
+
+#include <android/input.h>
+#include <input/Input.h>
+#include <linux/input.h>
+#include <log/log_main.h>
+
+#include <set>
+
+namespace android {
+
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType,
+ const NotifyKeyArgs& keyArgs) {
+ if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
+ return InputDeviceUsageSource::UNKNOWN;
+ }
+
+ if (isFromSource(keyArgs.source, AINPUT_SOURCE_DPAD) &&
+ DPAD_ALL_KEYCODES.count(keyArgs.keyCode) != 0) {
+ return InputDeviceUsageSource::DPAD;
+ }
+
+ if (isFromSource(keyArgs.source, AINPUT_SOURCE_GAMEPAD) &&
+ GAMEPAD_KEYCODES.count(keyArgs.keyCode) != 0) {
+ return InputDeviceUsageSource::GAMEPAD;
+ }
+
+ if (keyboardType == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
+ return InputDeviceUsageSource::KEYBOARD;
+ }
+
+ return InputDeviceUsageSource::BUTTONS;
+}
+
+std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs& motionArgs) {
+ LOG_ALWAYS_FATAL_IF(motionArgs.getPointerCount() < 1, "Received motion args without pointers");
+ std::set<InputDeviceUsageSource> sources;
+
+ for (uint32_t i = 0; i < motionArgs.getPointerCount(); i++) {
+ const auto toolType = motionArgs.pointerProperties[i].toolType;
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE)) {
+ if (toolType == ToolType::MOUSE) {
+ sources.emplace(InputDeviceUsageSource::MOUSE);
+ continue;
+ }
+ if (toolType == ToolType::FINGER) {
+ sources.emplace(InputDeviceUsageSource::TOUCHPAD);
+ continue;
+ }
+ if (isStylusToolType(toolType)) {
+ sources.emplace(InputDeviceUsageSource::STYLUS_INDIRECT);
+ continue;
+ }
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE_RELATIVE) &&
+ toolType == ToolType::MOUSE) {
+ sources.emplace(InputDeviceUsageSource::MOUSE_CAPTURED);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHPAD) &&
+ toolType == ToolType::FINGER) {
+ sources.emplace(InputDeviceUsageSource::TOUCHPAD_CAPTURED);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_BLUETOOTH_STYLUS) &&
+ isStylusToolType(toolType)) {
+ sources.emplace(InputDeviceUsageSource::STYLUS_FUSED);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(toolType)) {
+ sources.emplace(InputDeviceUsageSource::STYLUS_DIRECT);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCH_NAVIGATION)) {
+ sources.emplace(InputDeviceUsageSource::TOUCH_NAVIGATION);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_JOYSTICK)) {
+ sources.emplace(InputDeviceUsageSource::JOYSTICK);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_ROTARY_ENCODER)) {
+ sources.emplace(InputDeviceUsageSource::ROTARY_ENCODER);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_TRACKBALL)) {
+ sources.emplace(InputDeviceUsageSource::TRACKBALL);
+ continue;
+ }
+ if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+ sources.emplace(InputDeviceUsageSource::TOUCHSCREEN);
+ continue;
+ }
+ sources.emplace(InputDeviceUsageSource::UNKNOWN);
+ }
+
+ return sources;
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputDeviceMetricsSource.h b/services/inputflinger/InputDeviceMetricsSource.h
new file mode 100644
index 0000000000..a6be8f42cc
--- /dev/null
+++ b/services/inputflinger/InputDeviceMetricsSource.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 "NotifyArgs.h"
+
+#include <linux/input.h>
+#include <statslog.h>
+
+namespace android {
+
+/**
+ * Enum representation of the InputDeviceUsageSource.
+ */
+enum class InputDeviceUsageSource : int32_t {
+ UNKNOWN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__UNKNOWN,
+ BUTTONS = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__BUTTONS,
+ KEYBOARD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__KEYBOARD,
+ DPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__DPAD,
+ GAMEPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__GAMEPAD,
+ JOYSTICK = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__JOYSTICK,
+ MOUSE = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE,
+ MOUSE_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__MOUSE_CAPTURED,
+ TOUCHPAD = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD,
+ TOUCHPAD_CAPTURED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHPAD_CAPTURED,
+ ROTARY_ENCODER = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__ROTARY_ENCODER,
+ STYLUS_DIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_DIRECT,
+ STYLUS_INDIRECT = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_INDIRECT,
+ STYLUS_FUSED = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__STYLUS_FUSED,
+ TOUCH_NAVIGATION = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCH_NAVIGATION,
+ TOUCHSCREEN = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TOUCHSCREEN,
+ TRACKBALL = util::INPUT_DEVICE_USAGE_REPORTED__USAGE_SOURCES__TRACKBALL,
+
+ ftl_first = UNKNOWN,
+ ftl_last = TRACKBALL,
+ // Used by latency fuzzer
+ kMaxValue = ftl_last
+};
+
+/** Returns the InputDeviceUsageSource that corresponds to the key event. */
+InputDeviceUsageSource getUsageSourceForKeyArgs(int32_t keyboardType, const NotifyKeyArgs&);
+
+/** Returns the InputDeviceUsageSources that correspond to the motion event. */
+std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs&);
+
+} // namespace android
diff --git a/services/inputflinger/InputFilter.cpp b/services/inputflinger/InputFilter.cpp
new file mode 100644
index 0000000000..9c4a3eb274
--- /dev/null
+++ b/services/inputflinger/InputFilter.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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 LOG_TAG "InputFilter"
+
+#include "InputFilter.h"
+
+namespace android {
+
+using aidl::com::android::server::inputflinger::IInputFilter;
+using AidlKeyEvent = aidl::com::android::server::inputflinger::KeyEvent;
+using aidl::com::android::server::inputflinger::KeyEventAction;
+using AidlDeviceInfo = aidl::com::android::server::inputflinger::DeviceInfo;
+using aidl::android::hardware::input::common::Source;
+
+AidlKeyEvent notifyKeyArgsToKeyEvent(const NotifyKeyArgs& args) {
+ AidlKeyEvent event;
+ event.id = args.id;
+ event.eventTime = args.eventTime;
+ event.deviceId = args.deviceId;
+ event.source = static_cast<Source>(args.source);
+ event.displayId = args.displayId;
+ event.policyFlags = args.policyFlags;
+ event.action = static_cast<KeyEventAction>(args.action);
+ event.flags = args.flags;
+ event.keyCode = args.keyCode;
+ event.scanCode = args.scanCode;
+ event.metaState = args.metaState;
+ event.downTime = args.downTime;
+ event.readTime = args.readTime;
+ return event;
+}
+
+NotifyKeyArgs keyEventToNotifyKeyArgs(const AidlKeyEvent& event) {
+ return NotifyKeyArgs(event.id, event.eventTime, event.readTime, event.deviceId,
+ static_cast<uint32_t>(event.source), event.displayId, event.policyFlags,
+ static_cast<int32_t>(event.action), event.flags, event.keyCode,
+ event.scanCode, event.metaState, event.downTime);
+}
+
+namespace {
+
+class RustCallbacks : public IInputFilter::BnInputFilterCallbacks {
+public:
+ RustCallbacks(InputListenerInterface& nextListener) : mNextListener(nextListener) {}
+ ndk::ScopedAStatus sendKeyEvent(const AidlKeyEvent& event) override {
+ mNextListener.notifyKey(keyEventToNotifyKeyArgs(event));
+ return ndk::ScopedAStatus::ok();
+ }
+
+private:
+ InputListenerInterface& mNextListener;
+};
+
+} // namespace
+
+InputFilter::InputFilter(InputListenerInterface& listener, IInputFlingerRust& rust)
+ : mNextListener(listener), mCallbacks(ndk::SharedRefBase::make<RustCallbacks>(listener)) {
+ LOG_ALWAYS_FATAL_IF(!rust.createInputFilter(mCallbacks, &mInputFilterRust).isOk());
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust);
+}
+
+void InputFilter::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ if (isFilterEnabled()) {
+ std::vector<AidlDeviceInfo> deviceInfos;
+ for (auto info : args.inputDeviceInfos) {
+ AidlDeviceInfo aidlInfo;
+ aidlInfo.deviceId = info.getId();
+ aidlInfo.external = info.isExternal();
+ deviceInfos.push_back(aidlInfo);
+ }
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyInputDevicesChanged(deviceInfos).isOk());
+ }
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifyKey(const NotifyKeyArgs& args) {
+ if (!isFilterEnabled()) {
+ mNextListener.notifyKey(args);
+ return;
+ }
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyKey(notifyKeyArgsToKeyEvent(args)).isOk());
+}
+
+void InputFilter::notifyMotion(const NotifyMotionArgs& args) {
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifySwitch(const NotifySwitchArgs& args) {
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifySensor(const NotifySensorArgs& args) {
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ mNextListener.notify(args);
+}
+
+void InputFilter::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+ mNextListener.notify(args);
+}
+
+bool InputFilter::isFilterEnabled() {
+ bool result;
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust->isEnabled(&result).isOk());
+ return result;
+}
+
+void InputFilter::setAccessibilityBounceKeysThreshold(nsecs_t threshold) {
+ std::scoped_lock _l(mLock);
+
+ if (mConfig.bounceKeysThresholdNs != threshold) {
+ mConfig.bounceKeysThresholdNs = threshold;
+ LOG_ALWAYS_FATAL_IF(!mInputFilterRust->notifyConfigurationChanged(mConfig).isOk());
+ }
+}
+
+void InputFilter::dump(std::string& dump) {
+ dump += "InputFilter:\n";
+}
+
+} // namespace android
diff --git a/services/inputflinger/InputFilter.h b/services/inputflinger/InputFilter.h
new file mode 100644
index 0000000000..06f7d0e601
--- /dev/null
+++ b/services/inputflinger/InputFilter.h
@@ -0,0 +1,71 @@
+/*
+ * 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 <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <utils/Mutex.h>
+#include "InputListener.h"
+#include "NotifyArgs.h"
+
+namespace android {
+
+/**
+ * The C++ component of InputFilter designed as a wrapper around the rust implementation.
+ */
+class InputFilterInterface : public InputListenerInterface {
+public:
+ /**
+ * This method may be called on any thread (usually by the input manager on a binder thread).
+ */
+ virtual void dump(std::string& dump) = 0;
+ virtual void setAccessibilityBounceKeysThreshold(nsecs_t threshold) = 0;
+};
+
+class InputFilter : public InputFilterInterface {
+public:
+ using IInputFlingerRust = aidl::com::android::server::inputflinger::IInputFlingerRust;
+ using IInputFilter = aidl::com::android::server::inputflinger::IInputFilter;
+ using IInputFilterCallbacks =
+ aidl::com::android::server::inputflinger::IInputFilter::IInputFilterCallbacks;
+ using InputFilterConfiguration =
+ aidl::com::android::server::inputflinger::InputFilterConfiguration;
+
+ explicit InputFilter(InputListenerInterface& listener, IInputFlingerRust&);
+ ~InputFilter() override = default;
+ 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;
+ void setAccessibilityBounceKeysThreshold(nsecs_t threshold) override;
+ void dump(std::string& dump) override;
+
+private:
+ InputListenerInterface& mNextListener;
+ std::shared_ptr<IInputFilterCallbacks> mCallbacks;
+ std::shared_ptr<IInputFilter> mInputFilterRust;
+ mutable std::mutex mLock;
+ InputFilterConfiguration mConfig GUARDED_BY(mLock);
+
+ bool isFilterEnabled();
+};
+
+} // namespace android
diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp
index dce327ed18..016ae045a9 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -24,375 +24,155 @@
#include <android-base/stringprintf.h>
#include <android/log.h>
-#include <math.h>
-#include <utils/Trace.h>
+#include <input/TraceTools.h>
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<TouchVideoFrame>& 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]);
- }
+std::list<NotifyArgs>& operator+=(std::list<NotifyArgs>& keep, std::list<NotifyArgs>&& consume) {
+ keep.splice(keep.end(), consume);
+ return keep;
}
-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;
- }
+// --- InputListenerInterface ---
- 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);
-}
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... { using V::operator()...; };
+// explicit deduction guide (not needed as of C++20)
+template <typename... V>
+Visitor(V...) -> Visitor<V...>;
-void NotifyMotionArgs::notify(InputListenerInterface& listener) const {
- listener.notifyMotion(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);
}
-// --- 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) {}
+// --- QueuedInputListener ---
-bool NotifySwitchArgs::operator==(const NotifySwitchArgs rhs) const {
- return id == rhs.id && eventTime == rhs.eventTime && policyFlags == rhs.policyFlags &&
- switchValues == rhs.switchValues && switchMask == rhs.switchMask;
-}
+QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener)
+ : mInnerListener(innerListener) {}
-void NotifySwitchArgs::notify(InputListenerInterface& listener) const {
- listener.notifySwitch(this);
+void QueuedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-// --- 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<float> 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 QueuedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-void NotifySensorArgs::notify(InputListenerInterface& listener) const {
- listener.notifySensor(this);
+void QueuedInputListener::notifyKey(const NotifyKeyArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-// --- 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 QueuedInputListener::notifyMotion(const NotifyMotionArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-void NotifyVibratorStateArgs::notify(InputListenerInterface& listener) const {
- listener.notifyVibratorState(this);
+void QueuedInputListener::notifySwitch(const NotifySwitchArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-// --- 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 QueuedInputListener::notifySensor(const NotifySensorArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-void NotifyDeviceResetArgs::notify(InputListenerInterface& listener) const {
- listener.notifyDeviceReset(this);
+void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-// --- NotifyPointerCaptureChangedArgs ---
-
-NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs(
- int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request)
- : NotifyArgs(id, eventTime), request(request) {}
-
-NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs(
- const NotifyPointerCaptureChangedArgs& other)
- : NotifyArgs(other.id, other.eventTime), request(other.request) {}
-
-bool NotifyPointerCaptureChangedArgs::operator==(const NotifyPointerCaptureChangedArgs& rhs) const {
- return id == rhs.id && eventTime == rhs.eventTime && request == rhs.request;
+void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-void NotifyPointerCaptureChangedArgs::notify(InputListenerInterface& listener) const {
- listener.notifyPointerCaptureChanged(this);
+void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+ mArgsQueue.emplace_back(args);
}
-// --- QueuedInputListener ---
-
-static inline void traceEvent(const char* functionName, int32_t id) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("%s(id=0x%" PRIx32 ")", functionName, id);
- ATRACE_NAME(message.c_str());
+void QueuedInputListener::flush() {
+ for (const NotifyArgs& args : mArgsQueue) {
+ mInnerListener.notify(args);
}
+ mArgsQueue.clear();
}
-QueuedInputListener::QueuedInputListener(InputListenerInterface& innerListener)
- : mInnerListener(innerListener) {}
+// --- TracedInputListener ---
+
+TracedInputListener::TracedInputListener(const char* name, InputListenerInterface& innerListener)
+ : mInnerListener(innerListener), mName(name) {}
-void QueuedInputListener::notifyConfigurationChanged(
- const NotifyConfigurationChangedArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifyConfigurationChangedArgs>(*args));
+void TracedInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifyKeyArgs>(*args));
+void TracedInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifyMotionArgs>(*args));
+void TracedInputListener::notifyKey(const NotifyKeyArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifySwitch(const NotifySwitchArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifySwitchArgs>(*args));
+void TracedInputListener::notifyMotion(const NotifyMotionArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifySensor(const NotifySensorArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifySensorArgs>(*args));
+void TracedInputListener::notifySwitch(const NotifySwitchArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifyVibratorStateArgs>(*args));
+void TracedInputListener::notifySensor(const NotifySensorArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifyDeviceResetArgs>(*args));
+void TracedInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
- traceEvent(__func__, args->id);
- mArgsQueue.emplace_back(std::make_unique<NotifyPointerCaptureChangedArgs>(*args));
+void TracedInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
-void QueuedInputListener::flush() {
- for (const std::unique_ptr<NotifyArgs>& args : mArgsQueue) {
- args->notify(mInnerListener);
- }
- mArgsQueue.clear();
+void TracedInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+ constexpr static auto& fnName = __func__;
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("%s::%s(id=0x%" PRIx32 ")", mName, fnName, args.id));
+ mInnerListener.notify(args);
}
} // namespace android
diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp
index 9767cd9b71..296f2449ea 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -23,18 +23,28 @@
#include "InputReaderFactory.h"
#include "UnwantedInteractionBlocker.h"
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
+#include <android/binder_interface_utils.h>
+#include <android/sysprop/InputProperties.sysprop.h>
#include <binder/IPCThreadState.h>
-
+#include <com_android_input_flags.h>
+#include <inputflinger_bootstrap.rs.h>
#include <log/log.h>
-#include <unordered_map>
-
#include <private/android_filesystem_config.h>
+namespace input_flags = com::android::input::flags;
+
namespace android {
-using gui::FocusRequest;
+namespace {
+
+const bool ENABLE_INPUT_DEVICE_USAGE_METRICS =
+ sysprop::InputProperties::enable_input_device_usage_metrics().value_or(true);
-static int32_t exceptionCodeFromStatusT(status_t status) {
+const bool ENABLE_POINTER_CHOREOGRAPHER = input_flags::enable_pointer_choreographer();
+const bool ENABLE_INPUT_FILTER_RUST = input_flags::enable_input_filter_rust_impl();
+
+int32_t exceptionCodeFromStatusT(status_t status) {
switch (status) {
case OK:
return binder::Status::EX_NONE;
@@ -53,17 +63,105 @@ static int32_t exceptionCodeFromStatusT(status_t status) {
}
}
+// Convert a binder interface into a raw pointer to an AIBinder.
+using IInputFlingerRustBootstrapCallback = aidl::com::android::server::inputflinger::
+ IInputFlingerRust::IInputFlingerRustBootstrapCallback;
+IInputFlingerRustBootstrapCallbackAIBinder* binderToPointer(
+ IInputFlingerRustBootstrapCallback& interface) {
+ ndk::SpAIBinder spAIBinder = interface.asBinder();
+ auto* ptr = spAIBinder.get();
+ AIBinder_incStrong(ptr);
+ return ptr;
+}
+
+// Create the Rust component of InputFlinger that uses AIDL interfaces as a the foreign function
+// interface (FFI). The bootstraping process for IInputFlingerRust is as follows:
+// - Create BnInputFlingerRustBootstrapCallback in C++.
+// - Use the cxxbridge ffi interface to call the Rust function `create_inputflinger_rust()`, and
+// pass the callback binder object as a raw pointer.
+// - The Rust implementation will create the implementation of IInputFlingerRust, and pass it
+// to C++ through the callback.
+// - After the Rust function returns, the binder interface provided to the callback will be the
+// only strong reference to the IInputFlingerRust.
+std::shared_ptr<IInputFlingerRust> createInputFlingerRust() {
+ using namespace aidl::com::android::server::inputflinger;
+
+ class Callback : public IInputFlingerRust::BnInputFlingerRustBootstrapCallback {
+ ndk::ScopedAStatus onProvideInputFlingerRust(
+ const std::shared_ptr<IInputFlingerRust>& inputFlingerRust) override {
+ mService = inputFlingerRust;
+ return ndk::ScopedAStatus::ok();
+ }
+
+ public:
+ std::shared_ptr<IInputFlingerRust> consumeInputFlingerRust() {
+ auto service = mService;
+ mService.reset();
+ return service;
+ }
+
+ private:
+ std::shared_ptr<IInputFlingerRust> mService;
+ };
+
+ auto callback = ndk::SharedRefBase::make<Callback>();
+ create_inputflinger_rust(binderToPointer(*callback));
+ auto service = callback->consumeInputFlingerRust();
+ LOG_ALWAYS_FATAL_IF(!service,
+ "create_inputflinger_rust did not provide the IInputFlingerRust "
+ "implementation through the callback.");
+ return service;
+}
+
+} // namespace
+
/**
* The event flow is via the "InputListener" interface, as follows:
- * InputReader -> UnwantedInteractionBlocker -> InputClassifier -> InputDispatcher
+ * InputReader
+ * -> UnwantedInteractionBlocker
+ * -> InputFilter
+ * -> PointerChoreographer
+ * -> InputProcessor
+ * -> InputDeviceMetricsCollector
+ * -> InputDispatcher
*/
-InputManager::InputManager(
- const sp<InputReaderPolicyInterface>& readerPolicy,
- const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
+InputManager::InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
+ InputDispatcherPolicyInterface& dispatcherPolicy,
+ PointerChoreographerPolicyInterface& choreographerPolicy) {
+ mInputFlingerRust = createInputFlingerRust();
+
mDispatcher = createInputDispatcher(dispatcherPolicy);
- mClassifier = std::make_unique<InputClassifier>(*mDispatcher);
- mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mClassifier);
- mReader = createInputReader(readerPolicy, *mBlocker);
+ mTracingStages.emplace_back(
+ std::make_unique<TracedInputListener>("InputDispatcher", *mDispatcher));
+
+ if (ENABLE_INPUT_FILTER_RUST) {
+ mInputFilter = std::make_unique<InputFilter>(*mTracingStages.back(), *mInputFlingerRust);
+ mTracingStages.emplace_back(
+ std::make_unique<TracedInputListener>("InputFilter", *mInputFilter));
+ }
+
+ if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
+ mCollector = std::make_unique<InputDeviceMetricsCollector>(*mTracingStages.back());
+ mTracingStages.emplace_back(
+ std::make_unique<TracedInputListener>("MetricsCollector", *mCollector));
+ }
+
+ mProcessor = std::make_unique<InputProcessor>(*mTracingStages.back());
+ mTracingStages.emplace_back(
+ std::make_unique<TracedInputListener>("InputProcessor", *mProcessor));
+
+ if (ENABLE_POINTER_CHOREOGRAPHER) {
+ mChoreographer =
+ std::make_unique<PointerChoreographer>(*mTracingStages.back(), choreographerPolicy);
+ mTracingStages.emplace_back(
+ std::make_unique<TracedInputListener>("PointerChoreographer", *mChoreographer));
+ }
+
+ mBlocker = std::make_unique<UnwantedInteractionBlocker>(*mTracingStages.back());
+ mTracingStages.emplace_back(
+ std::make_unique<TracedInputListener>("UnwantedInteractionBlocker", *mBlocker));
+
+ mReader = createInputReader(readerPolicy, *mTracingStages.back());
}
InputManager::~InputManager() {
@@ -110,25 +208,55 @@ InputReaderInterface& InputManager::getReader() {
return *mReader;
}
-UnwantedInteractionBlockerInterface& InputManager::getUnwantedInteractionBlocker() {
- return *mBlocker;
+PointerChoreographerInterface& InputManager::getChoreographer() {
+ return *mChoreographer;
+}
+
+InputProcessorInterface& InputManager::getProcessor() {
+ return *mProcessor;
}
-InputClassifierInterface& InputManager::getClassifier() {
- return *mClassifier;
+InputDeviceMetricsCollectorInterface& InputManager::getMetricsCollector() {
+ return *mCollector;
}
InputDispatcherInterface& InputManager::getDispatcher() {
return *mDispatcher;
}
+InputFilterInterface& InputManager::getInputFilter() {
+ return *mInputFilter;
+}
+
void InputManager::monitor() {
mReader->monitor();
mBlocker->monitor();
- mClassifier->monitor();
+ mProcessor->monitor();
+ if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
+ mCollector->monitor();
+ }
mDispatcher->monitor();
}
+void InputManager::dump(std::string& dump) {
+ mReader->dump(dump);
+ dump += '\n';
+ mBlocker->dump(dump);
+ dump += '\n';
+ if (ENABLE_POINTER_CHOREOGRAPHER) {
+ mChoreographer->dump(dump);
+ dump += '\n';
+ }
+ mProcessor->dump(dump);
+ dump += '\n';
+ if (ENABLE_INPUT_DEVICE_USAGE_METRICS) {
+ mCollector->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();
@@ -158,11 +286,11 @@ status_t InputManager::dump(int fd, const Vector<String16>& args) {
dump += " InputFlinger dump\n";
- ::write(fd, dump.c_str(), dump.size());
+ TEMP_FAILURE_RETRY(::write(fd, dump.c_str(), dump.size()));
return NO_ERROR;
}
-binder::Status InputManager::setFocusedWindow(const FocusRequest& request) {
+binder::Status InputManager::setFocusedWindow(const gui::FocusRequest& request) {
mDispatcher->setFocusedWindow(request);
return binder::Status::ok();
}
diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h
index 8aad35bf1e..fa7db379e0 100644
--- a/services/inputflinger/InputManager.h
+++ b/services/inputflinger/InputManager.h
@@ -14,22 +14,26 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_MANAGER_H
-#define _UI_INPUT_MANAGER_H
+#pragma once
/**
* Native input manager.
*/
-#include "InputClassifier.h"
+#include "InputDeviceMetricsCollector.h"
+#include "InputFilter.h"
+#include "InputProcessor.h"
#include "InputReaderBase.h"
+#include "PointerChoreographer.h"
#include "include/UnwantedInteractionBlockerInterface.h"
#include <InputDispatcherInterface.h>
#include <InputDispatcherPolicyInterface.h>
+#include <PointerChoreographerPolicyInterface.h>
#include <input/Input.h>
#include <input/InputTransport.h>
+#include <aidl/com/android/server/inputflinger/IInputFlingerRust.h>
#include <android/os/BnInputFlinger.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
@@ -37,6 +41,9 @@
using android::os::BnInputFlinger;
+using aidl::com::android::server::inputflinger::IInputFilter;
+using aidl::com::android::server::inputflinger::IInputFlingerRust;
+
namespace android {
class InputChannel;
class InputDispatcherThread;
@@ -52,10 +59,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 +90,26 @@ public:
/* Gets the input reader. */
virtual InputReaderInterface& getReader() = 0;
- /* Gets the unwanted interaction blocker. */
- virtual UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() = 0;
+ /* Gets the PointerChoreographer. */
+ virtual PointerChoreographerInterface& getChoreographer() = 0;
+
+ /* Gets the input processor. */
+ virtual InputProcessorInterface& getProcessor() = 0;
- /* Gets the input classifier */
- virtual InputClassifierInterface& getClassifier() = 0;
+ /* Gets the metrics collector. */
+ virtual InputDeviceMetricsCollectorInterface& getMetricsCollector() = 0;
/* Gets the input dispatcher. */
virtual InputDispatcherInterface& getDispatcher() = 0;
+ /* Gets the input filter */
+ virtual InputFilterInterface& getInputFilter() = 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 +117,21 @@ protected:
~InputManager() override;
public:
- InputManager(
- const sp<InputReaderPolicyInterface>& readerPolicy,
- const sp<InputDispatcherPolicyInterface>& dispatcherPolicy);
+ InputManager(const sp<InputReaderPolicyInterface>& readerPolicy,
+ InputDispatcherPolicyInterface& dispatcherPolicy,
+ PointerChoreographerPolicyInterface& choreographerPolicy);
status_t start() override;
status_t stop() override;
InputReaderInterface& getReader() override;
- UnwantedInteractionBlockerInterface& getUnwantedInteractionBlocker() override;
- InputClassifierInterface& getClassifier() override;
+ PointerChoreographerInterface& getChoreographer() override;
+ InputProcessorInterface& getProcessor() override;
+ InputDeviceMetricsCollectorInterface& getMetricsCollector() override;
InputDispatcherInterface& getDispatcher() override;
+ InputFilterInterface& getInputFilter() override;
void monitor() override;
+ void dump(std::string& dump) override;
status_t dump(int fd, const Vector<String16>& args) override;
binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override;
@@ -123,11 +143,19 @@ private:
std::unique_ptr<UnwantedInteractionBlockerInterface> mBlocker;
- std::unique_ptr<InputClassifierInterface> mClassifier;
+ std::unique_ptr<InputFilterInterface> mInputFilter;
+
+ std::unique_ptr<PointerChoreographerInterface> mChoreographer;
+
+ std::unique_ptr<InputProcessorInterface> mProcessor;
+
+ std::unique_ptr<InputDeviceMetricsCollectorInterface> mCollector;
std::unique_ptr<InputDispatcherInterface> mDispatcher;
+
+ std::shared_ptr<IInputFlingerRust> mInputFlingerRust;
+
+ std::vector<std::unique_ptr<TracedInputListener>> mTracingStages;
};
} // namespace android
-
-#endif // _UI_INPUT_MANAGER_H
diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputProcessor.cpp
index 8ce2f35d7b..6dd267ce8f 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 <android-base/stringprintf.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
+#include <input/Input.h>
#include <inttypes.h>
#include <log/log.h>
#include <algorithm>
#include <cmath>
#if defined(__linux__)
- #include <pthread.h>
+#include <pthread.h>
#endif
#include <unordered_set>
@@ -44,10 +45,10 @@ using aidl::android::hardware::input::processor::IInputProcessor;
namespace android {
-//Max number of elements to store in mEvents.
+// Max number of elements to store in mEvents.
static constexpr size_t MAX_EVENTS = 5;
-template<class K, class V>
+template <class K, class V>
static V getValueForKey(const std::unordered_map<K, V>& map, K key, V defaultValue) {
auto it = map.find(key);
if (it == map.end()) {
@@ -123,40 +124,38 @@ ScopedDeathRecipient::~ScopedDeathRecipient() {
// --- ClassifierEvent ---
-ClassifierEvent::ClassifierEvent(std::unique_ptr<NotifyMotionArgs> args) :
- type(ClassifierEventType::MOTION), args(std::move(args)) { };
-ClassifierEvent::ClassifierEvent(std::unique_ptr<NotifyDeviceResetArgs> args) :
- type(ClassifierEventType::DEVICE_RESET), args(std::move(args)) { };
-ClassifierEvent::ClassifierEvent(ClassifierEventType type, std::unique_ptr<NotifyArgs> args) :
- type(type), args(std::move(args)) { };
+ClassifierEvent::ClassifierEvent(const NotifyMotionArgs& args)
+ : type(ClassifierEventType::MOTION), args(args){};
-ClassifierEvent::ClassifierEvent(ClassifierEvent&& other) :
- type(other.type), args(std::move(other.args)) { };
+ClassifierEvent::ClassifierEvent(const NotifyDeviceResetArgs& args)
+ : type(ClassifierEventType::DEVICE_RESET), args(args){};
+
+ClassifierEvent::ClassifierEvent(ClassifierEventType type, std::optional<NotifyArgs> args)
+ : type(type), args(args){};
ClassifierEvent& ClassifierEvent::operator=(ClassifierEvent&& other) {
type = other.type;
- args = std::move(other.args);
+ args = other.args;
return *this;
}
ClassifierEvent ClassifierEvent::createHalResetEvent() {
- return ClassifierEvent(ClassifierEventType::HAL_RESET, nullptr);
+ return ClassifierEvent(ClassifierEventType::HAL_RESET, std::nullopt);
}
ClassifierEvent ClassifierEvent::createExitEvent() {
- return ClassifierEvent(ClassifierEventType::EXIT, nullptr);
+ return ClassifierEvent(ClassifierEventType::EXIT, std::nullopt);
}
std::optional<int32_t> ClassifierEvent::getDeviceId() const {
switch (type) {
case ClassifierEventType::MOTION: {
- NotifyMotionArgs* motionArgs = static_cast<NotifyMotionArgs*>(args.get());
- return motionArgs->deviceId;
+ const NotifyMotionArgs& motionArgs = std::get<NotifyMotionArgs>(*args);
+ return motionArgs.deviceId;
}
case ClassifierEventType::DEVICE_RESET: {
- NotifyDeviceResetArgs* deviceResetArgs =
- static_cast<NotifyDeviceResetArgs*>(args.get());
- return deviceResetArgs->deviceId;
+ const NotifyDeviceResetArgs& deviceResetArgs = std::get<NotifyDeviceResetArgs>(*args);
+ return deviceResetArgs.deviceId;
}
case ClassifierEventType::HAL_RESET: {
return std::nullopt;
@@ -180,7 +179,7 @@ MotionClassifier::MotionClassifier(std::shared_ptr<IInputProcessor> service)
mHalThread = std::thread(&MotionClassifier::processEvents, this);
#if defined(__linux__)
// Set the thread name for debugging
- pthread_setname_np(mHalThread.native_handle(), "InputClassifier");
+ pthread_setname_np(mHalThread.native_handle(), "InputProcessor");
#endif
}
@@ -198,7 +197,7 @@ MotionClassifier::~MotionClassifier() {
/**
* Obtain the classification from the HAL for a given MotionEvent.
- * Should only be called from the InputClassifier thread (mHalThread).
+ * Should only be called from the InputProcessor thread (mHalThread).
* Should not be called from the thread that notifyMotion runs on.
*
* There is no way to provide a timeout for a HAL call. So if the HAL takes too long
@@ -212,12 +211,12 @@ void MotionClassifier::processEvents() {
bool halResponseOk = true;
switch (event.type) {
case ClassifierEventType::MOTION: {
- NotifyMotionArgs* motionArgs = static_cast<NotifyMotionArgs*>(event.args.get());
- common::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(*motionArgs);
+ NotifyMotionArgs& motionArgs = std::get<NotifyMotionArgs>(*event.args);
+ common::MotionEvent motionEvent = notifyMotionArgsToHalMotionEvent(motionArgs);
common::Classification classification;
ndk::ScopedAStatus response = mService->classify(motionEvent, &classification);
if (response.isOk()) {
- updateClassification(motionArgs->deviceId, motionArgs->eventTime,
+ updateClassification(motionArgs.deviceId, motionArgs.eventTime,
getMotionClassification(classification));
}
break;
@@ -239,8 +238,8 @@ void MotionClassifier::processEvents() {
}
}
if (!halResponseOk) {
- ALOGE("Error communicating with InputClassifier HAL. "
- "Exiting MotionClassifier HAL thread");
+ ALOGE("Error communicating with InputProcessor HAL. "
+ "Exiting MotionClassifier HAL thread");
clearClassifications();
return;
}
@@ -262,14 +261,14 @@ void MotionClassifier::requestExit() {
}
void MotionClassifier::updateClassification(int32_t deviceId, nsecs_t eventTime,
- MotionClassification classification) {
+ MotionClassification classification) {
std::scoped_lock lock(mLock);
const nsecs_t lastDownTime = getValueForKey(mLastDownTimes, deviceId, static_cast<nsecs_t>(0));
if (eventTime < lastDownTime) {
// HAL just finished processing an event that belonged to an earlier gesture,
// but new gesture is already in progress. Drop this classification.
ALOGW("Received late classification. Late by at least %" PRId64 " ms.",
- nanoseconds_to_milliseconds(lastDownTime - eventTime));
+ nanoseconds_to_milliseconds(lastDownTime - eventTime));
return;
}
mClassifications[deviceId] = classification;
@@ -307,8 +306,7 @@ MotionClassification MotionClassifier::classify(const NotifyMotionArgs& args) {
updateLastDownTime(args.deviceId, args.downTime);
}
- ClassifierEvent event(std::make_unique<NotifyMotionArgs>(args));
- enqueueEvent(std::move(event));
+ enqueueEvent(args);
return getClassification(args.deviceId);
}
@@ -319,66 +317,66 @@ void MotionClassifier::reset() {
/**
* Per-device reset. Clear the outstanding events that are going to be sent to HAL.
- * Request InputClassifier thread to call resetDevice for this particular device.
+ * Request InputProcessor thread to call resetDevice for this particular device.
*/
void MotionClassifier::reset(const NotifyDeviceResetArgs& args) {
int32_t deviceId = args.deviceId;
// Clear the pending events right away, to avoid unnecessary work done by the HAL.
- mEvents.erase([deviceId](const ClassifierEvent& event) {
- std::optional<int32_t> eventDeviceId = event.getDeviceId();
- return eventDeviceId && (*eventDeviceId == deviceId);
+ mEvents.erase_if([deviceId](const ClassifierEvent& event) {
+ std::optional<int32_t> eventDeviceId = event.getDeviceId();
+ return eventDeviceId && (*eventDeviceId == deviceId);
});
- enqueueEvent(std::make_unique<NotifyDeviceResetArgs>(args));
-}
-
-const char* MotionClassifier::getServiceStatus() REQUIRES(mLock) {
- if (!mService) {
- return "null";
- }
-
- if (AIBinder_ping(mService->asBinder().get()) == STATUS_OK) {
- return "running";
- }
- return "not responding";
+ enqueueEvent(args);
}
void MotionClassifier::dump(std::string& dump) {
std::scoped_lock lock(mLock);
- dump += StringPrintf(INDENT2 "mService status: %s\n", getServiceStatus());
- dump += StringPrintf(INDENT2 "mEvents: %zu element(s) (max=%zu)\n",
- mEvents.size(), MAX_EVENTS);
+ dump += StringPrintf(INDENT2 "mService connected: %s\n", mService ? "true" : "false");
+ dump += StringPrintf(INDENT2 "mEvents: %zu element(s) (max=%zu)\n", mEvents.size(), MAX_EVENTS);
dump += INDENT2 "mClassifications, mLastDownTimes:\n";
dump += INDENT3 "Device Id\tClassification\tLast down time";
// Combine mClassifications and mLastDownTimes into a single table.
// Create a superset of device ids.
std::unordered_set<int32_t> deviceIds;
std::for_each(mClassifications.begin(), mClassifications.end(),
- [&deviceIds](auto pair){ deviceIds.insert(pair.first); });
+ [&deviceIds](auto pair) { deviceIds.insert(pair.first); });
std::for_each(mLastDownTimes.begin(), mLastDownTimes.end(),
- [&deviceIds](auto pair){ deviceIds.insert(pair.first); });
- for(int32_t deviceId : deviceIds) {
+ [&deviceIds](auto pair) { deviceIds.insert(pair.first); });
+ for (int32_t deviceId : deviceIds) {
const MotionClassification classification =
getValueForKey(mClassifications, deviceId, MotionClassification::NONE);
const nsecs_t downTime = getValueForKey(mLastDownTimes, deviceId, static_cast<nsecs_t>(0));
- dump += StringPrintf("\n" INDENT4 "%" PRId32 "\t%s\t%" PRId64,
- deviceId, motionClassificationToString(classification), downTime);
+ dump += StringPrintf("\n" INDENT4 "%" PRId32 "\t%s\t%" PRId64, deviceId,
+ motionClassificationToString(classification), downTime);
+ }
+}
+
+void MotionClassifier::monitor() {
+ std::scoped_lock lock(mLock);
+ if (mService) {
+ // Ping the HAL service to ensure it is alive and not blocked.
+ const binder_status_t status = AIBinder_ping(mService->asBinder().get());
+ if (status != STATUS_OK) {
+ ALOGW("IInputProcessor HAL is not responding; binder ping result: %s",
+ AStatus_getDescription(AStatus_fromStatus(status)));
+ }
}
}
-// --- InputClassifier ---
+// --- InputProcessor ---
-InputClassifier::InputClassifier(InputListenerInterface& listener) : mQueuedListener(listener) {}
+InputProcessor::InputProcessor(InputListenerInterface& listener) : mQueuedListener(listener) {}
-void InputClassifier::onBinderDied(void* cookie) {
- InputClassifier* classifier = static_cast<InputClassifier*>(cookie);
- if (classifier == nullptr) {
+void InputProcessor::onBinderDied(void* cookie) {
+ InputProcessor* processor = static_cast<InputProcessor*>(cookie);
+ if (processor == nullptr) {
LOG_ALWAYS_FATAL("Cookie is not valid");
return;
}
- classifier->setMotionClassifierEnabled(false);
+ processor->setMotionClassifierEnabled(false);
}
-void InputClassifier::setMotionClassifierEnabled(bool enabled) {
+void InputProcessor::setMotionClassifierEnabled(bool enabled) {
std::scoped_lock lock(mLock);
if (enabled) {
ALOGI("Enabling motion classifier");
@@ -391,7 +389,7 @@ void InputClassifier::setMotionClassifierEnabled(bool enabled) {
* and we can't continue because 'mInitializeMotionClassifier' will block in its
* destructor.
*/
- LOG_ALWAYS_FATAL("The thread to load IInputClassifier is stuck!");
+ LOG_ALWAYS_FATAL("The thread to load IInputProcessor is stuck!");
}
}
mInitializeMotionClassifier = std::async(std::launch::async, [this] {
@@ -404,7 +402,7 @@ void InputClassifier::setMotionClassifierEnabled(bool enabled) {
{ // acquire lock
std::scoped_lock threadLock(mLock);
mHalDeathRecipient =
- std::make_unique<ScopedDeathRecipient>(onBinderDied, this /*cookie*/);
+ std::make_unique<ScopedDeathRecipient>(onBinderDied, /*cookie=*/this);
mHalDeathRecipient->linkToDeath(service->asBinder().get());
setMotionClassifierLocked(MotionClassifier::create(std::move(service)));
} // release lock
@@ -415,57 +413,69 @@ void InputClassifier::setMotionClassifierEnabled(bool enabled) {
}
}
-void InputClassifier::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
+void InputProcessor::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ // pass through
+ mQueuedListener.notify(args);
+ mQueuedListener.flush();
+}
+
+void InputProcessor::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
// pass through
mQueuedListener.notifyConfigurationChanged(args);
mQueuedListener.flush();
}
-void InputClassifier::notifyKey(const NotifyKeyArgs* args) {
+void InputProcessor::notifyKey(const NotifyKeyArgs& args) {
// pass through
mQueuedListener.notifyKey(args);
mQueuedListener.flush();
}
-void InputClassifier::notifyMotion(const NotifyMotionArgs* args) {
+void InputProcessor::notifyMotion(const NotifyMotionArgs& args) {
{ // acquire lock
std::scoped_lock lock(mLock);
// MotionClassifier is only used for touch events, for now
- const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(*args);
+ const bool sendToMotionClassifier = mMotionClassifier && isTouchEvent(args);
if (!sendToMotionClassifier) {
mQueuedListener.notifyMotion(args);
} else {
- NotifyMotionArgs newArgs(*args);
- newArgs.classification = mMotionClassifier->classify(newArgs);
- mQueuedListener.notifyMotion(&newArgs);
+ NotifyMotionArgs newArgs(args);
+ const MotionClassification newClassification = mMotionClassifier->classify(newArgs);
+ LOG_ALWAYS_FATAL_IF(args.classification != MotionClassification::NONE &&
+ newClassification != MotionClassification::NONE,
+ "Conflicting classifications %s (new) and %s (old)!",
+ motionClassificationToString(newClassification),
+ motionClassificationToString(args.classification));
+ newArgs.classification = newClassification;
+ mQueuedListener.notifyMotion(newArgs);
}
} // release lock
mQueuedListener.flush();
}
-void InputClassifier::notifySensor(const NotifySensorArgs* args) {
+void InputProcessor::notifySensor(const NotifySensorArgs& args) {
// pass through
mQueuedListener.notifySensor(args);
mQueuedListener.flush();
}
-void InputClassifier::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+void InputProcessor::notifyVibratorState(const NotifyVibratorStateArgs& args) {
// pass through
mQueuedListener.notifyVibratorState(args);
mQueuedListener.flush();
}
-void InputClassifier::notifySwitch(const NotifySwitchArgs* args) {
+void InputProcessor::notifySwitch(const NotifySwitchArgs& args) {
// pass through
mQueuedListener.notifySwitch(args);
mQueuedListener.flush();
}
-void InputClassifier::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+void InputProcessor::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
{ // acquire lock
std::scoped_lock lock(mLock);
if (mMotionClassifier) {
- mMotionClassifier->reset(*args);
+ mMotionClassifier->reset(args);
}
} // release lock
@@ -474,13 +484,13 @@ void InputClassifier::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
mQueuedListener.flush();
}
-void InputClassifier::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
+void InputProcessor::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
// pass through
mQueuedListener.notifyPointerCaptureChanged(args);
mQueuedListener.flush();
}
-void InputClassifier::setMotionClassifierLocked(
+void InputProcessor::setMotionClassifierLocked(
std::unique_ptr<MotionClassifierInterface> motionClassifier) REQUIRES(mLock) {
if (motionClassifier == nullptr) {
// Destroy the ScopedDeathRecipient object, which will cause it to unlinkToDeath.
@@ -490,9 +500,9 @@ void InputClassifier::setMotionClassifierLocked(
mMotionClassifier = std::move(motionClassifier);
}
-void InputClassifier::dump(std::string& dump) {
+void InputProcessor::dump(std::string& dump) {
std::scoped_lock lock(mLock);
- dump += "Input Classifier State:\n";
+ dump += "Input Processor State:\n";
dump += INDENT1 "Motion Classifier:\n";
if (mMotionClassifier) {
mMotionClassifier->dump(dump);
@@ -502,11 +512,11 @@ void InputClassifier::dump(std::string& dump) {
dump += "\n";
}
-void InputClassifier::monitor() {
+void InputProcessor::monitor() {
std::scoped_lock lock(mLock);
+ if (mMotionClassifier) mMotionClassifier->monitor();
}
-InputClassifier::~InputClassifier() {
-}
+InputProcessor::~InputProcessor() {}
} // namespace android
diff --git a/services/inputflinger/InputClassifier.h b/services/inputflinger/InputProcessor.h
index 56cf760256..7a00a2dae8 100644
--- a/services/inputflinger/InputClassifier.h
+++ b/services/inputflinger/InputProcessor.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_CLASSIFIER_H
-#define _UI_INPUT_CLASSIFIER_H
+#pragma once
#include <android-base/thread_annotations.h>
#include <future>
@@ -23,7 +22,7 @@
#include <unordered_map>
#include <aidl/android/hardware/input/processor/IInputProcessor.h>
-#include "BlockingQueue.h"
+#include <input/BlockingQueue.h>
#include "InputListener.h"
namespace android {
@@ -36,12 +35,12 @@ enum class ClassifierEventType : uint8_t {
struct ClassifierEvent {
ClassifierEventType type;
- std::unique_ptr<NotifyArgs> args;
+ std::optional<NotifyArgs> args;
- ClassifierEvent(ClassifierEventType type, std::unique_ptr<NotifyArgs> args);
- ClassifierEvent(std::unique_ptr<NotifyMotionArgs> args);
- ClassifierEvent(std::unique_ptr<NotifyDeviceResetArgs> args);
- ClassifierEvent(ClassifierEvent&& other);
+ ClassifierEvent(ClassifierEventType type, std::optional<NotifyArgs> args);
+ ClassifierEvent(const NotifyMotionArgs& args);
+ ClassifierEvent(const NotifyDeviceResetArgs& args);
+ ClassifierEvent(ClassifierEvent&& other) = default;
ClassifierEvent& operator=(ClassifierEvent&& other);
// Convenience function to create a HAL_RESET event
@@ -61,8 +60,8 @@ struct ClassifierEvent {
*/
class MotionClassifierInterface {
public:
- MotionClassifierInterface() { }
- virtual ~MotionClassifierInterface() { }
+ MotionClassifierInterface() {}
+ virtual ~MotionClassifierInterface() {}
/**
* Based on the motion event described by NotifyMotionArgs,
* provide a MotionClassification for the current gesture.
@@ -78,16 +77,21 @@ public:
virtual void reset(const NotifyDeviceResetArgs& args) = 0;
/**
- * Dump the state of the motion classifier
+ * Dump the state of the motion classifier.
*/
virtual void dump(std::string& dump) = 0;
+
+ /**
+ * Called by the heartbeat to ensure the HAL is still processing normally.
+ */
+ virtual void monitor() = 0;
};
/**
* Base interface for an InputListener stage.
* Provides classification to events.
*/
-class InputClassifierInterface : public InputListenerInterface {
+class InputProcessorInterface : public InputListenerInterface {
public:
virtual void setMotionClassifierEnabled(bool enabled) = 0;
/**
@@ -96,11 +100,11 @@ public:
*/
virtual void dump(std::string& dump) = 0;
- /* Called by the heatbeat to ensures that the classifier has not deadlocked. */
+ /** Called by the heartbeat to ensure that the classifier has not deadlocked. */
virtual void monitor() = 0;
- InputClassifierInterface() { }
- virtual ~InputClassifierInterface() { }
+ InputProcessorInterface() {}
+ virtual ~InputProcessorInterface() {}
};
// --- Implementations ---
@@ -119,10 +123,10 @@ private:
};
/**
- * Implementation of MotionClassifierInterface that calls the InputClassifier HAL
+ * Implementation of MotionClassifierInterface that calls the InputProcessor HAL
* in order to determine the classification for the current gesture.
*
- * The InputClassifier HAL may keep track of the entire gesture in order to determine
+ * The InputProcessor HAL may keep track of the entire gesture in order to determine
* the classification, and may be hardware-specific. It may use the data in
* NotifyMotionArgs::videoFrames field to drive the classification decisions.
* The HAL is called from a separate thread.
@@ -155,33 +159,34 @@ public:
virtual void reset(const NotifyDeviceResetArgs& args) override;
virtual void dump(std::string& dump) override;
+ virtual void monitor() override;
private:
friend class MotionClassifierTest; // to create MotionClassifier with a test HAL implementation
explicit MotionClassifier(
std::shared_ptr<aidl::android::hardware::input::processor::IInputProcessor> service);
- // The events that need to be sent to the HAL.
+ /** The events that need to be sent to the HAL. */
BlockingQueue<ClassifierEvent> mEvents;
/**
* Add an event to the queue mEvents.
*/
void enqueueEvent(ClassifierEvent&& event);
/**
- * Thread that will communicate with InputClassifier HAL.
- * This should be the only thread that communicates with InputClassifier HAL,
+ * Thread that will communicate with InputProcessor HAL.
+ * This should be the only thread that communicates with InputProcessor HAL,
* because this thread is allowed to block on the HAL calls.
*/
std::thread mHalThread;
/**
- * Process events and call the InputClassifier HAL
+ * Process events and call the InputProcessor HAL
*/
void processEvents();
/**
* Access to the InputProcessor HAL. May be null if init() hasn't completed yet.
* When init() successfully completes, mService is guaranteed to remain non-null and to not
* change its value until MotionClassifier is destroyed.
- * This variable is *not* guarded by mLock in the InputClassifier thread, because
+ * This variable is *not* guarded by mLock in the InputProcessor thread, because
* that thread knows exactly when this variable is initialized.
* When accessed in any other thread, mService is checked for nullness with a lock.
*/
@@ -191,8 +196,8 @@ private:
* Per-device input classifications. Should only be accessed using the
* getClassification / setClassification methods.
*/
- std::unordered_map<int32_t /*deviceId*/, MotionClassification>
- mClassifications GUARDED_BY(mLock);
+ std::unordered_map<int32_t /*deviceId*/, MotionClassification> mClassifications
+ GUARDED_BY(mLock);
/**
* Set the current classification for a given device.
*/
@@ -202,7 +207,7 @@ private:
*/
MotionClassification getClassification(int32_t deviceId);
void updateClassification(int32_t deviceId, nsecs_t eventTime,
- MotionClassification classification);
+ MotionClassification classification);
/**
* Clear all current classifications
*/
@@ -211,7 +216,7 @@ private:
* Per-device times when the last ACTION_DOWN was received.
* Used to reject late classifications that do not belong to the current gesture.
*
- * Accessed indirectly by both InputClassifier thread and the thread that receives notifyMotion.
+ * Accessed indirectly by both InputProcessor thread and the thread that receives notifyMotion.
*/
std::unordered_map<int32_t /*deviceId*/, nsecs_t /*downTime*/> mLastDownTimes GUARDED_BY(mLock);
@@ -220,7 +225,7 @@ private:
void clearDeviceState(int32_t deviceId);
/**
- * Exit the InputClassifier HAL thread.
+ * Exit the InputProcessor HAL thread.
* Useful for tests to ensure proper cleanup.
*/
void requestExit();
@@ -231,28 +236,29 @@ private:
};
/**
- * Implementation of the InputClassifierInterface.
+ * Implementation of the InputProcessorInterface.
* Represents a separate stage of input processing. All of the input events go through this stage.
* Acts as a passthrough for all input events except for motion events.
* The events of motion type are sent to MotionClassifier.
*/
-class InputClassifier : public InputClassifierInterface {
+class InputProcessor : public InputProcessorInterface {
public:
- explicit InputClassifier(InputListenerInterface& listener);
-
- 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;
+ explicit InputProcessor(InputListenerInterface& listener);
+
+ 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;
void dump(std::string& dump) override;
void monitor() override;
- ~InputClassifier();
+ ~InputProcessor();
// Called from InputManager
void setMotionClassifierEnabled(bool enabled) override;
@@ -282,4 +288,3 @@ private:
};
} // namespace android
-#endif
diff --git a/services/inputflinger/InputReaderBase.cpp b/services/inputflinger/InputReaderBase.cpp
index a864cf8202..4ec5b898b1 100644
--- a/services/inputflinger/InputReaderBase.cpp
+++ b/services/inputflinger/InputReaderBase.cpp
@@ -38,47 +38,6 @@ namespace android {
// --- InputReaderConfiguration ---
-std::string InputReaderConfiguration::changesToString(uint32_t changes) {
- if (changes == 0) {
- return "<none>";
- }
- std::string result;
- if (changes & CHANGE_POINTER_SPEED) {
- result += "POINTER_SPEED | ";
- }
- if (changes & CHANGE_POINTER_GESTURE_ENABLEMENT) {
- result += "POINTER_GESTURE_ENABLEMENT | ";
- }
- if (changes & CHANGE_DISPLAY_INFO) {
- result += "DISPLAY_INFO | ";
- }
- if (changes & CHANGE_SHOW_TOUCHES) {
- result += "SHOW_TOUCHES | ";
- }
- if (changes & CHANGE_KEYBOARD_LAYOUTS) {
- result += "KEYBOARD_LAYOUTS | ";
- }
- if (changes & CHANGE_DEVICE_ALIAS) {
- result += "DEVICE_ALIAS | ";
- }
- if (changes & CHANGE_TOUCH_AFFINE_TRANSFORMATION) {
- result += "TOUCH_AFFINE_TRANSFORMATION | ";
- }
- if (changes & CHANGE_EXTERNAL_STYLUS_PRESENCE) {
- result += "EXTERNAL_STYLUS_PRESENCE | ";
- }
- if (changes & CHANGE_POINTER_CAPTURE) {
- result += "POINTER_CAPTURE | ";
- }
- if (changes & CHANGE_ENABLED_STATE) {
- result += "ENABLED_STATE | ";
- }
- if (changes & CHANGE_MUST_REOPEN) {
- result += "MUST_REOPEN | ";
- }
- return result;
-}
-
std::optional<DisplayViewport> InputReaderConfiguration::getDisplayViewportByUniqueId(
const std::string& uniqueDisplayId) const {
if (uniqueDisplayId.empty()) {
diff --git a/services/inputflinger/InputThread.cpp b/services/inputflinger/InputThread.cpp
index b87f7a1243..e74f258168 100644
--- a/services/inputflinger/InputThread.cpp
+++ b/services/inputflinger/InputThread.cpp
@@ -41,7 +41,7 @@ private:
InputThread::InputThread(std::string name, std::function<void()> loop, std::function<void()> wake)
: mName(name), mThreadWake(wake) {
- mThread = new InputThreadImpl(loop);
+ mThread = sp<InputThreadImpl>::make(loop);
mThread->run(mName.c_str(), ANDROID_PRIORITY_URGENT_DISPLAY);
}
@@ -54,7 +54,13 @@ InputThread::~InputThread() {
}
bool InputThread::isCallingThread() {
+#if defined(__ANDROID__)
return gettid() == mThread->getTid();
+#else
+ // Assume that the caller is doing everything correctly,
+ // since thread information is not available on host
+ return false;
+#endif
}
} // namespace android \ No newline at end of file
diff --git a/services/inputflinger/NotifyArgs.cpp b/services/inputflinger/NotifyArgs.cpp
new file mode 100644
index 0000000000..de836e93a4
--- /dev/null
+++ b/services/inputflinger/NotifyArgs.cpp
@@ -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.
+ */
+
+#define LOG_TAG "NotifyArgs"
+
+#define ATRACE_TAG ATRACE_TAG_INPUT
+
+#include "NotifyArgs.h"
+
+#include <android-base/stringprintf.h>
+#include <android/log.h>
+#include <math.h>
+#include <utils/Trace.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+
+// --- NotifyInputDevicesChangedArgs ---
+
+NotifyInputDevicesChangedArgs::NotifyInputDevicesChangedArgs(int32_t id,
+ std::vector<InputDeviceInfo> infos)
+ : id(id), inputDeviceInfos(std::move(infos)) {}
+
+// --- NotifyConfigurationChangedArgs ---
+
+NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs(int32_t id, nsecs_t eventTime)
+ : id(id), eventTime(eventTime) {}
+
+// --- 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)
+ : id(id),
+ eventTime(eventTime),
+ deviceId(deviceId),
+ source(source),
+ displayId(displayId),
+ policyFlags(policyFlags),
+ action(action),
+ flags(flags),
+ keyCode(keyCode),
+ scanCode(scanCode),
+ metaState(metaState),
+ downTime(downTime),
+ readTime(readTime) {}
+
+// --- 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<TouchVideoFrame>& videoFrames)
+ : id(id),
+ eventTime(eventTime),
+ deviceId(deviceId),
+ source(source),
+ displayId(displayId),
+ policyFlags(policyFlags),
+ action(action),
+ actionButton(actionButton),
+ flags(flags),
+ metaState(metaState),
+ buttonState(buttonState),
+ classification(classification),
+ edgeFlags(edgeFlags),
+ xPrecision(xPrecision),
+ yPrecision(yPrecision),
+ xCursorPosition(xCursorPosition),
+ yCursorPosition(yCursorPosition),
+ downTime(downTime),
+ readTime(readTime),
+ videoFrames(videoFrames) {
+ for (uint32_t i = 0; i < pointerCount; i++) {
+ this->pointerProperties.emplace_back(pointerProperties[i]);
+ this->pointerCoords.emplace_back(pointerCoords[i]);
+ }
+}
+
+static inline bool isCursorPositionEqual(float lhs, float rhs) {
+ return (isnan(lhs) && isnan(rhs)) || lhs == rhs;
+}
+
+bool NotifyMotionArgs::operator==(const NotifyMotionArgs& 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 &&
+ actionButton == rhs.actionButton && flags == rhs.flags && metaState == rhs.metaState &&
+ buttonState == rhs.buttonState && classification == rhs.classification &&
+ edgeFlags == rhs.edgeFlags && pointerProperties == rhs.pointerProperties &&
+ pointerCoords == rhs.pointerCoords && xPrecision == rhs.xPrecision &&
+ yPrecision == rhs.yPrecision &&
+ isCursorPositionEqual(xCursorPosition, rhs.xCursorPosition) &&
+ isCursorPositionEqual(yCursorPosition, rhs.yCursorPosition) &&
+ downTime == rhs.downTime && videoFrames == rhs.videoFrames;
+}
+
+std::string NotifyMotionArgs::dump() const {
+ std::string coords;
+ for (uint32_t i = 0; i < getPointerCount(); 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 ToolType toolType = pointerProperties[i].toolType;
+ if (toolType != ToolType::FINGER) {
+ coords += StringPrintf(" toolType=%s", ftl::enum_string(toolType).c_str());
+ }
+ 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=%zu pointers=%s, flags=0x%08x)",
+ id, eventTime, deviceId, inputEventSourceToString(source).c_str(),
+ MotionEvent::actionToString(action).c_str(), getPointerCount(),
+ coords.c_str(), flags);
+}
+
+// --- NotifySwitchArgs ---
+
+NotifySwitchArgs::NotifySwitchArgs(int32_t id, nsecs_t eventTime, uint32_t policyFlags,
+ uint32_t switchValues, uint32_t switchMask)
+ : id(id),
+ eventTime(eventTime),
+ policyFlags(policyFlags),
+ switchValues(switchValues),
+ switchMask(switchMask) {}
+
+// --- 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<float> values)
+ : id(id),
+ eventTime(eventTime),
+ deviceId(deviceId),
+ source(source),
+ sensorType(sensorType),
+ accuracy(accuracy),
+ accuracyChanged(accuracyChanged),
+ hwTimestamp(hwTimestamp),
+ values(std::move(values)) {}
+
+// --- NotifyVibratorStateArgs ---
+
+NotifyVibratorStateArgs::NotifyVibratorStateArgs(int32_t id, nsecs_t eventTime, int32_t deviceId,
+ bool isOn)
+ : id(id), eventTime(eventTime), deviceId(deviceId), isOn(isOn) {}
+
+// --- NotifyDeviceResetArgs ---
+
+NotifyDeviceResetArgs::NotifyDeviceResetArgs(int32_t id, nsecs_t eventTime, int32_t deviceId)
+ : id(id), eventTime(eventTime), deviceId(deviceId) {}
+
+// --- NotifyPointerCaptureChangedArgs ---
+
+NotifyPointerCaptureChangedArgs::NotifyPointerCaptureChangedArgs(
+ int32_t id, nsecs_t eventTime, const PointerCaptureRequest& request)
+ : id(id), eventTime(eventTime), request(request) {}
+
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... { using V::operator()...; };
+// explicit deduction guide (not needed as of C++20)
+template <typename... V>
+Visitor(V...) -> Visitor<V...>;
+
+const char* toString(const NotifyArgs& args) {
+ Visitor toStringVisitor{
+ [&](const NotifyInputDevicesChangedArgs&) { return "NotifyInputDevicesChangedArgs"; },
+ [&](const NotifyConfigurationChangedArgs&) { return "NotifyConfigurationChangedArgs"; },
+ [&](const NotifyKeyArgs&) { return "NotifyKeyArgs"; },
+ [&](const NotifyMotionArgs&) { return "NotifyMotionArgs"; },
+ [&](const NotifySensorArgs&) { return "NotifySensorArgs"; },
+ [&](const NotifySwitchArgs&) { return "NotifySwitchArgs"; },
+ [&](const NotifyDeviceResetArgs&) { return "NotifyDeviceResetArgs"; },
+ [&](const NotifyPointerCaptureChangedArgs&) {
+ return "NotifyPointerCaptureChangedArgs";
+ },
+ [&](const NotifyVibratorStateArgs&) { return "NotifyVibratorStateArgs"; },
+ };
+ return std::visit(toStringVisitor, args);
+}
+
+} // namespace android
diff --git a/services/inputflinger/OWNERS b/services/inputflinger/OWNERS
index c88bfe97ca..21d208f577 100644
--- a/services/inputflinger/OWNERS
+++ b/services/inputflinger/OWNERS
@@ -1 +1,2 @@
+# Bug component: 136048
include platform/frameworks/base:/INPUT_OWNERS
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
new file mode 100644
index 0000000000..0be4c327b7
--- /dev/null
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -0,0 +1,543 @@
+/*
+ * 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 LOG_TAG "PointerChoreographer"
+
+#include <android-base/logging.h>
+#include <input/PrintTools.h>
+
+#include "PointerChoreographer.h"
+
+#define INDENT " "
+
+namespace android {
+
+namespace {
+bool isFromMouse(const NotifyMotionArgs& args) {
+ return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
+ args.pointerProperties[0].toolType == ToolType::MOUSE;
+}
+
+bool isFromTouchpad(const NotifyMotionArgs& args) {
+ return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
+ args.pointerProperties[0].toolType == ToolType::FINGER;
+}
+
+bool isHoverAction(int32_t action) {
+ return action == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+ action == AMOTION_EVENT_ACTION_HOVER_MOVE || action == AMOTION_EVENT_ACTION_HOVER_EXIT;
+}
+
+bool isStylusHoverEvent(const NotifyMotionArgs& args) {
+ return isStylusEvent(args.source, args.pointerProperties) && isHoverAction(args.action);
+}
+} // namespace
+
+// --- PointerChoreographer ---
+
+PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
+ PointerChoreographerPolicyInterface& policy)
+ : mTouchControllerConstructor([this]() REQUIRES(mLock) {
+ return mPolicy.createPointerController(
+ PointerControllerInterface::ControllerType::TOUCH);
+ }),
+ mNextListener(listener),
+ mPolicy(policy),
+ mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
+ mNotifiedPointerDisplayId(ADISPLAY_ID_NONE),
+ mShowTouchesEnabled(false),
+ mStylusPointerIconEnabled(false) {}
+
+void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ std::scoped_lock _l(mLock);
+
+ mInputDeviceInfos = args.inputDeviceInfos;
+ updatePointerControllersLocked();
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) {
+ NotifyMotionArgs newArgs = processMotion(args);
+
+ mNextListener.notify(newArgs);
+}
+
+NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
+ std::scoped_lock _l(mLock);
+
+ if (isFromMouse(args)) {
+ return processMouseEventLocked(args);
+ } else if (isFromTouchpad(args)) {
+ return processTouchpadEventLocked(args);
+ } else if (mStylusPointerIconEnabled && isStylusHoverEvent(args)) {
+ processStylusHoverEventLocked(args);
+ } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+ processTouchscreenAndStylusEventLocked(args);
+ }
+ return args;
+}
+
+NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
+ if (args.getPointerCount() != 1) {
+ LOG(FATAL) << "Only mouse events with a single pointer are currently supported: "
+ << args.dump();
+ }
+
+ auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+
+ const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ pc.move(deltaX, deltaY);
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ const auto [x, y] = pc.getPosition();
+ NotifyMotionArgs newArgs(args);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ newArgs.xCursorPosition = x;
+ newArgs.yCursorPosition = y;
+ newArgs.displayId = displayId;
+ return newArgs;
+}
+
+NotifyMotionArgs PointerChoreographer::processTouchpadEventLocked(const NotifyMotionArgs& args) {
+ auto [displayId, pc] = getDisplayIdAndMouseControllerLocked(args.displayId);
+
+ NotifyMotionArgs newArgs(args);
+ newArgs.displayId = displayId;
+ if (args.getPointerCount() == 1 && args.classification == MotionClassification::NONE) {
+ // This is a movement of the mouse pointer.
+ const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ pc.move(deltaX, deltaY);
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ const auto [x, y] = pc.getPosition();
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ newArgs.xCursorPosition = x;
+ newArgs.yCursorPosition = y;
+ } else {
+ // This is a trackpad gesture with fake finger(s) that should not move the mouse pointer.
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ const auto [x, y] = pc.getPosition();
+ for (uint32_t i = 0; i < newArgs.getPointerCount(); i++) {
+ newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_X,
+ args.pointerCoords[i].getX() + x);
+ newArgs.pointerCoords[i].setAxisValue(AMOTION_EVENT_AXIS_Y,
+ args.pointerCoords[i].getY() + y);
+ }
+ newArgs.xCursorPosition = x;
+ newArgs.yCursorPosition = y;
+ }
+ return newArgs;
+}
+
+/**
+ * When screen is touched, fade the mouse pointer on that display. We only call fade for
+ * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
+ * mouse device keeps moving and unfades the cursor.
+ * For touch events, we do not need to populate the cursor position.
+ */
+void PointerChoreographer::processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) {
+ if (args.displayId == ADISPLAY_ID_NONE) {
+ return;
+ }
+
+ if (const auto it = mMousePointersByDisplay.find(args.displayId);
+ it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) {
+ it->second->fade(PointerControllerInterface::Transition::GRADUAL);
+ }
+
+ if (!mShowTouchesEnabled) {
+ return;
+ }
+
+ // Get the touch pointer controller for the device, or create one if it doesn't exist.
+ auto [it, _] = mTouchPointersByDevice.try_emplace(args.deviceId, mTouchControllerConstructor);
+
+ PointerControllerInterface& pc = *it->second;
+
+ const PointerCoords* coords = args.pointerCoords.data();
+ const int32_t maskedAction = MotionEvent::getActionMasked(args.action);
+ const uint8_t actionIndex = MotionEvent::getActionIndex(args.action);
+ std::array<uint32_t, MAX_POINTER_ID + 1> idToIndex;
+ BitSet32 idBits;
+ if (maskedAction != AMOTION_EVENT_ACTION_UP && maskedAction != AMOTION_EVENT_ACTION_CANCEL) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
+ if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP && actionIndex == i) {
+ continue;
+ }
+ uint32_t id = args.pointerProperties[i].id;
+ idToIndex[id] = i;
+ idBits.markBit(id);
+ }
+ }
+ // The PointerController already handles setting spots per-display, so
+ // we do not need to manually manage display changes for touch spots for now.
+ pc.setSpots(coords, idToIndex.cbegin(), idBits, args.displayId);
+}
+
+void PointerChoreographer::processStylusHoverEventLocked(const NotifyMotionArgs& args) {
+ if (args.displayId == ADISPLAY_ID_NONE) {
+ return;
+ }
+
+ if (args.getPointerCount() != 1) {
+ LOG(WARNING) << "Only stylus hover events with a single pointer are currently supported: "
+ << args.dump();
+ }
+
+ // Get the stylus pointer controller for the device, or create one if it doesn't exist.
+ auto [it, _] =
+ mStylusPointersByDevice.try_emplace(args.deviceId,
+ getStylusControllerConstructor(args.displayId));
+
+ PointerControllerInterface& pc = *it->second;
+
+ const float x = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X);
+ const float y = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y);
+ pc.setPosition(x, y);
+ if (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ pc.fade(PointerControllerInterface::Transition::IMMEDIATE);
+ } else {
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+}
+
+void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifySensor(const NotifySensorArgs& args) {
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ processDeviceReset(args);
+
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::processDeviceReset(const NotifyDeviceResetArgs& args) {
+ std::scoped_lock _l(mLock);
+ mTouchPointersByDevice.erase(args.deviceId);
+ mStylusPointersByDevice.erase(args.deviceId);
+}
+
+void PointerChoreographer::notifyPointerCaptureChanged(
+ const NotifyPointerCaptureChangedArgs& args) {
+ if (args.request.enable) {
+ std::scoped_lock _l(mLock);
+ for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
+ mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+ }
+ mNextListener.notify(args);
+}
+
+void PointerChoreographer::dump(std::string& dump) {
+ std::scoped_lock _l(mLock);
+
+ dump += "PointerChoreographer:\n";
+ dump += StringPrintf("show touches: %s\n", mShowTouchesEnabled ? "true" : "false");
+ dump += StringPrintf("stylus pointer icon enabled: %s\n",
+ mStylusPointerIconEnabled ? "true" : "false");
+
+ dump += INDENT "MousePointerControllers:\n";
+ for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
+ std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
+ dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
+ }
+ dump += INDENT "TouchPointerControllers:\n";
+ for (const auto& [deviceId, touchPointerController] : mTouchPointersByDevice) {
+ std::string pointerControllerDump = addLinePrefix(touchPointerController->dump(), INDENT);
+ dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+ }
+ dump += INDENT "StylusPointerControllers:\n";
+ for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+ std::string pointerControllerDump = addLinePrefix(stylusPointerController->dump(), INDENT);
+ dump += INDENT + std::to_string(deviceId) + " : " + pointerControllerDump;
+ }
+ dump += "\n";
+}
+
+const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const {
+ for (auto& viewport : mViewports) {
+ if (viewport.displayId == displayId) {
+ return &viewport;
+ }
+ }
+ return nullptr;
+}
+
+int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const {
+ return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
+}
+
+std::pair<int32_t, PointerControllerInterface&>
+PointerChoreographer::getDisplayIdAndMouseControllerLocked(int32_t associatedDisplayId) {
+ const int32_t displayId = getTargetMouseDisplayLocked(associatedDisplayId);
+
+ // Get the mouse pointer controller for the display, or create one if it doesn't exist.
+ auto [it, emplaced] =
+ mMousePointersByDisplay.try_emplace(displayId,
+ getMouseControllerConstructor(displayId));
+ if (emplaced) {
+ notifyPointerDisplayIdChangedLocked();
+ }
+
+ return {displayId, *it->second};
+}
+
+InputDeviceInfo* PointerChoreographer::findInputDeviceLocked(DeviceId deviceId) {
+ auto it = std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
+ [deviceId](const auto& info) { return info.getId() == deviceId; });
+ return it != mInputDeviceInfos.end() ? &(*it) : nullptr;
+}
+
+void PointerChoreographer::updatePointerControllersLocked() {
+ std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
+ std::set<DeviceId> touchDevicesToKeep;
+ std::set<DeviceId> stylusDevicesToKeep;
+
+ // Mark the displayIds or deviceIds of PointerControllers currently needed, and create
+ // new PointerControllers if necessary.
+ for (const auto& info : mInputDeviceInfos) {
+ const uint32_t sources = info.getSources();
+ if (isFromSource(sources, AINPUT_SOURCE_MOUSE) ||
+ isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) {
+ const int32_t displayId = getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
+ mouseDisplaysToKeep.insert(displayId);
+ // For mice, show the cursor immediately when the device is first connected or
+ // when it moves to a new display.
+ auto [mousePointerIt, isNewMousePointer] =
+ mMousePointersByDisplay.try_emplace(displayId,
+ getMouseControllerConstructor(displayId));
+ auto [_, isNewMouseDevice] = mMouseDevices.emplace(info.getId());
+ if (isNewMouseDevice || isNewMousePointer) {
+ mousePointerIt->second->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+ }
+ if (isFromSource(sources, AINPUT_SOURCE_TOUCHSCREEN) && mShowTouchesEnabled &&
+ info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+ touchDevicesToKeep.insert(info.getId());
+ }
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS) && mStylusPointerIconEnabled &&
+ info.getAssociatedDisplayId() != ADISPLAY_ID_NONE) {
+ stylusDevicesToKeep.insert(info.getId());
+ }
+ }
+
+ // Remove PointerControllers no longer needed.
+ std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& pair) {
+ return mouseDisplaysToKeep.find(pair.first) == mouseDisplaysToKeep.end();
+ });
+ std::erase_if(mTouchPointersByDevice, [&touchDevicesToKeep](const auto& pair) {
+ return touchDevicesToKeep.find(pair.first) == touchDevicesToKeep.end();
+ });
+ std::erase_if(mStylusPointersByDevice, [&stylusDevicesToKeep](const auto& pair) {
+ return stylusDevicesToKeep.find(pair.first) == stylusDevicesToKeep.end();
+ });
+ std::erase_if(mMouseDevices, [&](DeviceId id) REQUIRES(mLock) {
+ return std::find_if(mInputDeviceInfos.begin(), mInputDeviceInfos.end(),
+ [id](const auto& info) { return info.getId() == id; }) ==
+ mInputDeviceInfos.end();
+ });
+
+ // Notify the policy if there's a change on the pointer display ID.
+ notifyPointerDisplayIdChangedLocked();
+}
+
+void PointerChoreographer::notifyPointerDisplayIdChangedLocked() {
+ int32_t displayIdToNotify = ADISPLAY_ID_NONE;
+ FloatPoint cursorPosition = {0, 0};
+ if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
+ it != mMousePointersByDisplay.end()) {
+ const auto& pointerController = it->second;
+ // Use the displayId from the pointerController, because it accurately reflects whether
+ // the viewport has been added for that display. Otherwise, we would have to check if
+ // the viewport exists separately.
+ displayIdToNotify = pointerController->getDisplayId();
+ cursorPosition = pointerController->getPosition();
+ }
+
+ if (mNotifiedPointerDisplayId == displayIdToNotify) {
+ return;
+ }
+ mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition);
+ mNotifiedPointerDisplayId = displayIdToNotify;
+}
+
+void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) {
+ std::scoped_lock _l(mLock);
+
+ mDefaultMouseDisplayId = displayId;
+ updatePointerControllersLocked();
+}
+
+void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
+ std::scoped_lock _l(mLock);
+ for (const auto& viewport : viewports) {
+ const int32_t displayId = viewport.displayId;
+ if (const auto it = mMousePointersByDisplay.find(displayId);
+ it != mMousePointersByDisplay.end()) {
+ it->second->setDisplayViewport(viewport);
+ }
+ for (const auto& [deviceId, stylusPointerController] : mStylusPointersByDevice) {
+ const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+ if (info && info->getAssociatedDisplayId() == displayId) {
+ stylusPointerController->setDisplayViewport(viewport);
+ }
+ }
+ }
+ mViewports = viewports;
+ notifyPointerDisplayIdChangedLocked();
+}
+
+std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
+ int32_t associatedDisplayId) {
+ std::scoped_lock _l(mLock);
+ const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
+ if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
+ return *viewport;
+ }
+ return std::nullopt;
+}
+
+FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) {
+ std::scoped_lock _l(mLock);
+ const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
+ if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
+ it != mMousePointersByDisplay.end()) {
+ return it->second->getPosition();
+ }
+ return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
+}
+
+void PointerChoreographer::setShowTouchesEnabled(bool enabled) {
+ std::scoped_lock _l(mLock);
+ if (mShowTouchesEnabled == enabled) {
+ return;
+ }
+ mShowTouchesEnabled = enabled;
+ updatePointerControllersLocked();
+}
+
+void PointerChoreographer::setStylusPointerIconEnabled(bool enabled) {
+ std::scoped_lock _l(mLock);
+ if (mStylusPointerIconEnabled == enabled) {
+ return;
+ }
+ mStylusPointerIconEnabled = enabled;
+ updatePointerControllersLocked();
+}
+
+bool PointerChoreographer::setPointerIcon(
+ std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon, int32_t displayId,
+ DeviceId deviceId) {
+ std::scoped_lock _l(mLock);
+ if (deviceId < 0) {
+ LOG(WARNING) << "Invalid device id " << deviceId << ". Cannot set pointer icon.";
+ return false;
+ }
+ const InputDeviceInfo* info = findInputDeviceLocked(deviceId);
+ if (!info) {
+ LOG(WARNING) << "No input device info found for id " << deviceId
+ << ". Cannot set pointer icon.";
+ return false;
+ }
+ const uint32_t sources = info->getSources();
+ const auto stylusPointerIt = mStylusPointersByDevice.find(deviceId);
+
+ if (isFromSource(sources, AINPUT_SOURCE_STYLUS) &&
+ stylusPointerIt != mStylusPointersByDevice.end()) {
+ if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+ if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+ LOG(FATAL) << "SpriteIcon should not be null";
+ }
+ stylusPointerIt->second->setCustomPointerIcon(
+ *std::get<std::unique_ptr<SpriteIcon>>(icon));
+ } else {
+ stylusPointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+ }
+ } else if (isFromSource(sources, AINPUT_SOURCE_MOUSE)) {
+ if (const auto mousePointerIt = mMousePointersByDisplay.find(displayId);
+ mousePointerIt != mMousePointersByDisplay.end()) {
+ if (std::holds_alternative<std::unique_ptr<SpriteIcon>>(icon)) {
+ if (std::get<std::unique_ptr<SpriteIcon>>(icon) == nullptr) {
+ LOG(FATAL) << "SpriteIcon should not be null";
+ }
+ mousePointerIt->second->setCustomPointerIcon(
+ *std::get<std::unique_ptr<SpriteIcon>>(icon));
+ } else {
+ mousePointerIt->second->updatePointerIcon(std::get<PointerIconStyle>(icon));
+ }
+ } else {
+ LOG(WARNING) << "No mouse pointer controller found for display " << displayId
+ << ", device " << deviceId << ".";
+ return false;
+ }
+ } else {
+ LOG(WARNING) << "Cannot set pointer icon for display " << displayId << ", device "
+ << deviceId << ".";
+ return false;
+ }
+ return true;
+}
+
+PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
+ int32_t displayId) {
+ std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
+ [this, displayId]() REQUIRES(mLock) {
+ auto pc = mPolicy.createPointerController(
+ PointerControllerInterface::ControllerType::MOUSE);
+ if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+ pc->setDisplayViewport(*viewport);
+ }
+ return pc;
+ };
+ return ConstructorDelegate(std::move(ctor));
+}
+
+PointerChoreographer::ControllerConstructor PointerChoreographer::getStylusControllerConstructor(
+ int32_t displayId) {
+ std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
+ [this, displayId]() REQUIRES(mLock) {
+ auto pc = mPolicy.createPointerController(
+ PointerControllerInterface::ControllerType::STYLUS);
+ if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+ pc->setDisplayViewport(*viewport);
+ }
+ return pc;
+ };
+ return ConstructorDelegate(std::move(ctor));
+}
+
+} // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
new file mode 100644
index 0000000000..f46419ec2e
--- /dev/null
+++ b/services/inputflinger/PointerChoreographer.h
@@ -0,0 +1,148 @@
+/*
+ * 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 "InputListener.h"
+#include "NotifyArgs.h"
+#include "PointerChoreographerPolicyInterface.h"
+
+#include <android-base/thread_annotations.h>
+#include <type_traits>
+
+namespace android {
+
+struct SpriteIcon;
+
+/**
+ * A helper class that wraps a factory method that acts as a constructor for the type returned
+ * by the factory method.
+ */
+template <typename Factory>
+struct ConstructorDelegate {
+ constexpr ConstructorDelegate(Factory&& factory) : mFactory(std::move(factory)) {}
+
+ using ConstructedType = std::invoke_result_t<const Factory&>;
+ constexpr operator ConstructedType() const { return mFactory(); }
+
+ Factory mFactory;
+};
+
+/**
+ * PointerChoreographer manages the icons shown by the system for input interactions.
+ * This includes showing the mouse cursor, stylus hover icons, and touch spots.
+ * It is responsible for accumulating the location of the mouse cursor, and populating
+ * the cursor position for incoming events, if necessary.
+ */
+class PointerChoreographerInterface : public InputListenerInterface {
+public:
+ /**
+ * Set the display that pointers, like the mouse cursor and drawing tablets,
+ * should be drawn on.
+ */
+ virtual void setDefaultMouseDisplayId(int32_t displayId) = 0;
+ virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
+ virtual std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
+ virtual FloatPoint getMouseCursorPosition(int32_t displayId) = 0;
+ virtual void setShowTouchesEnabled(bool enabled) = 0;
+ virtual void setStylusPointerIconEnabled(bool enabled) = 0;
+ /**
+ * Set the icon that is shown for the given pointer. The request may fail in some cases, such
+ * as if the device or display was removed, or if the cursor was moved to a different display.
+ * Returns true if the icon was changed successfully, false otherwise.
+ */
+ virtual bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+ int32_t displayId, DeviceId deviceId) = 0;
+
+ /**
+ * This method may be called on any thread (usually by the input manager on a binder thread).
+ */
+ virtual void dump(std::string& dump) = 0;
+};
+
+class PointerChoreographer : public PointerChoreographerInterface {
+public:
+ explicit PointerChoreographer(InputListenerInterface& listener,
+ PointerChoreographerPolicyInterface&);
+ ~PointerChoreographer() override = default;
+
+ void setDefaultMouseDisplayId(int32_t displayId) override;
+ void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
+ std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId) override;
+ FloatPoint getMouseCursorPosition(int32_t displayId) override;
+ void setShowTouchesEnabled(bool enabled) override;
+ void setStylusPointerIconEnabled(bool enabled) override;
+ bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
+ int32_t displayId, DeviceId deviceId) override;
+
+ 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;
+
+ void dump(std::string& dump) override;
+
+private:
+ void updatePointerControllersLocked() REQUIRES(mLock);
+ void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
+ const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
+ int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
+ std::pair<int32_t, PointerControllerInterface&> getDisplayIdAndMouseControllerLocked(
+ int32_t associatedDisplayId) REQUIRES(mLock);
+ InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
+
+ NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
+ NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ void processTouchscreenAndStylusEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ void processStylusHoverEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ void processDeviceReset(const NotifyDeviceResetArgs& args);
+
+ using ControllerConstructor =
+ ConstructorDelegate<std::function<std::shared_ptr<PointerControllerInterface>()>>;
+ ControllerConstructor mTouchControllerConstructor GUARDED_BY(mLock);
+ ControllerConstructor getMouseControllerConstructor(int32_t displayId) REQUIRES(mLock);
+ ControllerConstructor getStylusControllerConstructor(int32_t displayId) REQUIRES(mLock);
+
+ std::mutex mLock;
+
+ InputListenerInterface& mNextListener;
+ PointerChoreographerPolicyInterface& mPolicy;
+
+ std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
+ GUARDED_BY(mLock);
+ std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mTouchPointersByDevice
+ GUARDED_BY(mLock);
+ std::map<DeviceId, std::shared_ptr<PointerControllerInterface>> mStylusPointersByDevice
+ GUARDED_BY(mLock);
+
+ int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
+ int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
+ std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
+ std::set<DeviceId> mMouseDevices GUARDED_BY(mLock);
+ std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
+ bool mShowTouchesEnabled GUARDED_BY(mLock);
+ bool mStylusPointerIconEnabled GUARDED_BY(mLock);
+};
+
+} // namespace android
diff --git a/services/inputflinger/PreferStylusOverTouchBlocker.cpp b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
index beec2e162e..d9d0450148 100644
--- a/services/inputflinger/PreferStylusOverTouchBlocker.cpp
+++ b/services/inputflinger/PreferStylusOverTouchBlocker.cpp
@@ -15,21 +15,25 @@
*/
#include "PreferStylusOverTouchBlocker.h"
+#include <com_android_input_flags.h>
#include <input/PrintTools.h>
+namespace input_flags = com::android::input::flags;
+
namespace android {
+const bool BLOCK_TOUCH_WHEN_STYLUS_HOVER = !input_flags::disable_reject_touch_on_stylus_hover();
+
static std::pair<bool, bool> checkToolType(const NotifyMotionArgs& args) {
bool hasStylus = false;
bool hasTouch = false;
- for (size_t i = 0; i < args.pointerCount; i++) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
// Make sure we are canceling stylus pointers
- const int32_t toolType = args.pointerProperties[i].toolType;
- if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
- toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
+ const ToolType toolType = args.pointerProperties[i].toolType;
+ if (isStylusToolType(toolType)) {
hasStylus = true;
}
- if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
+ if (toolType == ToolType::FINGER) {
hasTouch = true;
}
}
@@ -97,8 +101,11 @@ static void intersectInPlace(std::map<K, V>& map, const std::set<K>& set2) {
std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
const NotifyMotionArgs& args) {
const auto [hasTouch, hasStylus] = checkToolType(args);
- const bool isUpOrCancel =
- args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL;
+ const bool isDisengageOrCancel = BLOCK_TOUCH_WHEN_STYLUS_HOVER
+ ? (args.action == AMOTION_EVENT_ACTION_HOVER_EXIT ||
+ args.action == AMOTION_EVENT_ACTION_UP || args.action == AMOTION_EVENT_ACTION_CANCEL)
+ : (args.action == AMOTION_EVENT_ACTION_UP ||
+ args.action == AMOTION_EVENT_ACTION_CANCEL);
if (hasTouch && hasStylus) {
mDevicesWithMixedToolType.insert(args.deviceId);
@@ -110,7 +117,7 @@ std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
if (mCanceledDevices.find(args.deviceId) != mCanceledDevices.end()) {
// If we started to cancel events from this device, continue to do so to keep
// the stream consistent. It should happen at most once per "mixed" device.
- if (isUpOrCancel) {
+ if (isDisengageOrCancel) {
mCanceledDevices.erase(args.deviceId);
mLastTouchEvents.erase(args.deviceId);
}
@@ -120,10 +127,13 @@ std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
}
const bool isStylusEvent = hasStylus;
- const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
+ const bool isEngage = BLOCK_TOUCH_WHEN_STYLUS_HOVER
+ ? (args.action == AMOTION_EVENT_ACTION_DOWN ||
+ args.action == AMOTION_EVENT_ACTION_HOVER_ENTER)
+ : (args.action == AMOTION_EVENT_ACTION_DOWN);
if (isStylusEvent) {
- if (isDown) {
+ if (isEngage) {
// Reject all touch while stylus is down
mActiveStyli.insert(args.deviceId);
@@ -144,7 +154,7 @@ std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
result.push_back(args);
return result;
}
- if (isUpOrCancel) {
+ if (isDisengageOrCancel) {
mActiveStyli.erase(args.deviceId);
}
// Never drop stylus events
@@ -159,7 +169,7 @@ std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
}
const bool shouldDrop = mCanceledDevices.find(args.deviceId) != mCanceledDevices.end();
- if (isUpOrCancel) {
+ if (isDisengageOrCancel) {
mCanceledDevices.erase(args.deviceId);
mLastTouchEvents.erase(args.deviceId);
}
@@ -170,7 +180,7 @@ std::vector<NotifyMotionArgs> PreferStylusOverTouchBlocker::processMotion(
return {};
}
- if (!isUpOrCancel) {
+ if (!isDisengageOrCancel) {
mLastTouchEvents[args.deviceId] = args;
}
return {args};
diff --git a/services/inputflinger/SyncQueue.h b/services/inputflinger/SyncQueue.h
new file mode 100644
index 0000000000..84ccace96e
--- /dev/null
+++ b/services/inputflinger/SyncQueue.h
@@ -0,0 +1,66 @@
+/*
+ * 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 <utils/threads.h>
+#include <list>
+#include <mutex>
+#include <optional>
+
+namespace android {
+
+/** A thread-safe FIFO queue. */
+template <class T>
+class SyncQueue {
+public:
+ SyncQueue() = default;
+
+ SyncQueue(size_t capacity) : mCapacity(capacity) {}
+
+ /** Retrieve and remove the oldest object. Returns std::nullopt if the queue is empty. */
+ std::optional<T> pop() {
+ std::scoped_lock lock(mLock);
+ if (mQueue.empty()) {
+ return {};
+ }
+ T t = std::move(mQueue.front());
+ mQueue.erase(mQueue.begin());
+ return t;
+ };
+
+ /**
+ * Add a new object to the queue.
+ * Return true if an element was successfully added.
+ * Return false if the queue is full.
+ */
+ template <class... Args>
+ bool push(Args&&... args) {
+ std::scoped_lock lock(mLock);
+ if (mCapacity && mQueue.size() == mCapacity) {
+ return false;
+ }
+ mQueue.emplace_back(args...);
+ return true;
+ };
+
+private:
+ const std::optional<size_t> mCapacity;
+ std::mutex mLock;
+ std::list<T> mQueue GUARDED_BY(mLock);
+};
+
+} // namespace android
diff --git a/services/inputflinger/TEST_MAPPING b/services/inputflinger/TEST_MAPPING
index 8d5d8839e8..513cbfa7cc 100644
--- a/services/inputflinger/TEST_MAPPING
+++ b/services/inputflinger/TEST_MAPPING
@@ -1,10 +1,10 @@
{
"presubmit": [
{
- "name": "CtsWindowManagerDeviceTestCases",
+ "name": "CtsWindowManagerDeviceWindow",
"options": [
{
- "include-filter": "android.server.wm.WindowInputTests"
+ "include-filter": "android.server.wm.window.WindowInputTests"
}
]
},
@@ -27,6 +27,160 @@
"name": "libinputservice_test"
},
{
+ "name": "libgui_test",
+ "options": [
+ {
+ "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+ }
+ ]
+ },
+ {
+ "name": "CtsHardwareTestCases",
+ "options": [
+ {
+ "include-filter": "android.hardware.input.cts.tests"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsInputTestCases"
+ },
+ {
+ "name": "CtsViewTestCases",
+ "options": [
+ {
+ "include-filter": "android.view.cts.input"
+ }
+ ]
+ },
+ {
+ "name": "CtsViewTestCases",
+ "options": [
+ {
+ "include-filter": "android.view.cts.HoverTest"
+ },
+ {
+ "include-filter": "android.view.cts.MotionEventTest"
+ },
+ {
+ "include-filter": "android.view.cts.PointerCaptureTest"
+ },
+ {
+ "include-filter": "android.view.cts.TooltipTest"
+ },
+ {
+ "include-filter": "android.view.cts.TouchDelegateTest"
+ },
+ {
+ "include-filter": "android.view.cts.VerifyInputEventTest"
+ },
+ {
+ "include-filter": "android.view.cts.ViewTest"
+ },
+ {
+ "include-filter": "android.view.cts.ViewUnbufferedTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsWidgetTestCases",
+ "options": [
+ {
+ "include-filter": "android.widget.cts.NumberPickerTest"
+ },
+ {
+ "include-filter": "android.widget.cts.SeekBarTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.hardware.input"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksCoreTests",
+ "options": [
+ {
+ "include-filter": "android.view.VerifiedKeyEventTest"
+ },
+ {
+ "include-filter": "android.view.VerifiedMotionEventTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsAppTestCases",
+ "options": [
+ {
+ "include-filter": "android.app.cts.ToolbarActionBarTest"
+ }
+ ]
+ },
+ {
+ "name": "FrameworksServicesTests",
+ "options": [
+ {
+ "include-filter": "com.android.server.input"
+ }
+ ]
+ },
+ {
+ "name": "CtsSecurityTestCases",
+ "options": [
+ {
+ "include-filter": "android.security.cts.MotionEventTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsSecurityBulletinHostTestCases",
+ "options": [
+ {
+ "include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "CtsWindowManagerDeviceWindow",
+ "options": [
+ {
+ "include-filter": "android.server.wm.window.WindowInputTests"
+ }
+ ]
+ },
+ {
+ "name": "libinput_tests"
+ },
+ {
+ "name": "inputflinger_tests"
+ },
+ {
+ "name": "libpalmrejection_test"
+ },
+ {
+ "name": "InputTests"
+ },
+ {
+ "name": "libinputservice_test"
+ },
+ {
+ "name": "libgui_test",
+ "options": [
+ {
+ "native-test-flag": "--gtest_filter=\"InputSurfacesTest*:MultiDisplayTests*\""
+ }
+ ]
+ },
+ {
"name": "CtsHardwareTestCases",
"options": [
{
@@ -41,9 +195,47 @@
"name": "CtsViewTestCases",
"options": [
{
- "include-filter": "android.view.cts.MotionEventTest",
- "include-filter": "android.view.cts.PointerCaptureTest",
+ "include-filter": "android.view.cts.input"
+ }
+ ]
+ },
+ {
+ "name": "CtsViewTestCases",
+ "options": [
+ {
+ "include-filter": "android.view.cts.HoverTest"
+ },
+ {
+ "include-filter": "android.view.cts.MotionEventTest"
+ },
+ {
+ "include-filter": "android.view.cts.PointerCaptureTest"
+ },
+ {
+ "include-filter": "android.view.cts.TooltipTest"
+ },
+ {
+ "include-filter": "android.view.cts.TouchDelegateTest"
+ },
+ {
"include-filter": "android.view.cts.VerifyInputEventTest"
+ },
+ {
+ "include-filter": "android.view.cts.ViewTest"
+ },
+ {
+ "include-filter": "android.view.cts.ViewUnbufferedTest"
+ }
+ ]
+ },
+ {
+ "name": "CtsWidgetTestCases",
+ "options": [
+ {
+ "include-filter": "android.widget.cts.NumberPickerTest"
+ },
+ {
+ "include-filter": "android.widget.cts.SeekBarTest"
}
]
},
@@ -51,12 +243,22 @@
"name": "FrameworksCoreTests",
"options": [
{
- "include-filter": "android.view.VerifiedKeyEventTest",
+ "include-filter": "android.view.VerifiedKeyEventTest"
+ },
+ {
"include-filter": "android.view.VerifiedMotionEventTest"
}
]
},
{
+ "name": "CtsAppTestCases",
+ "options": [
+ {
+ "include-filter": "android.app.cts.ToolbarActionBarTest"
+ }
+ ]
+ },
+ {
"name": "FrameworksServicesTests",
"options": [
{
@@ -79,6 +281,9 @@
"include-filter": "android.security.cts.Poc19_03#testPocBug_115739809"
}
]
+ },
+ {
+ "name": "CtsInputHostTestCases"
}
]
}
diff --git a/services/inputflinger/UnwantedInteractionBlocker.cpp b/services/inputflinger/UnwantedInteractionBlocker.cpp
index ec41025e9d..1e2b9b3ad3 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.cpp
+++ b/services/inputflinger/UnwantedInteractionBlocker.cpp
@@ -18,6 +18,8 @@
#include "UnwantedInteractionBlocker.h"
#include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
+#include <ftl/enum.h>
#include <input/PrintTools.h>
#include <inttypes.h>
#include <linux/input-event-codes.h>
@@ -27,6 +29,8 @@
#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
#include "ui/events/ozone/evdev/touch_filter/palm_model/onedevice_train_palm_detection_filter_model.h"
+namespace input_flags = com::android::input::flags;
+
using android::base::StringPrintf;
/**
@@ -63,6 +67,16 @@ const bool DEBUG_OUTBOUND_MOTION =
const bool DEBUG_MODEL =
__android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Model", ANDROID_LOG_INFO);
+/**
+ * When multi-device input is enabled, we shouldn't use PreferStylusOverTouchBlocker at all.
+ * However, multi-device input has the following default behaviour: hovering stylus rejects touch.
+ * Therefore, if we want to disable that behaviour (and go back to a place where stylus down
+ * blocks touch, but hovering stylus doesn't interact with touch), we should just disable the entire
+ * multi-device input feature.
+ */
+const bool ENABLE_MULTI_DEVICE_INPUT = input_flags::enable_multi_device_input() &&
+ !input_flags::disable_reject_touch_on_stylus_hover();
+
// Category (=namespace) name for the input settings that are applied at boot time
static const char* INPUT_NATIVE_BOOT = "input_native_boot";
/**
@@ -98,19 +112,25 @@ static bool isPalmRejectionEnabled() {
return false;
}
-static int getLinuxToolCode(int toolType) {
- if (toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
- return BTN_TOOL_PEN;
- }
- if (toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) {
- return BTN_TOOL_FINGER;
+static int getLinuxToolCode(ToolType toolType) {
+ switch (toolType) {
+ case ToolType::STYLUS:
+ return BTN_TOOL_PEN;
+ case ToolType::ERASER:
+ return BTN_TOOL_RUBBER;
+ case ToolType::FINGER:
+ return BTN_TOOL_FINGER;
+ case ToolType::UNKNOWN:
+ case ToolType::MOUSE:
+ case ToolType::PALM:
+ break;
}
- ALOGW("Got tool type %" PRId32 ", converting to BTN_TOOL_FINGER", toolType);
+ ALOGW("Got tool type %s, converting to BTN_TOOL_FINGER", ftl::enum_string(toolType).c_str());
return BTN_TOOL_FINGER;
}
static int32_t getActionUpForPointerId(const NotifyMotionArgs& args, int32_t pointerId) {
- for (size_t i = 0; i < args.pointerCount; i++) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
if (pointerId == args.pointerProperties[i].id) {
return AMOTION_EVENT_ACTION_POINTER_UP |
(i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
@@ -149,9 +169,10 @@ NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
actionMasked == AMOTION_EVENT_ACTION_POINTER_UP;
NotifyMotionArgs newArgs{args};
- newArgs.pointerCount = 0;
+ newArgs.pointerProperties.clear();
+ newArgs.pointerCoords.clear();
int32_t newActionIndex = 0;
- for (uint32_t i = 0; i < args.pointerCount; i++) {
+ for (uint32_t i = 0; i < args.getPointerCount(); i++) {
const int32_t pointerId = args.pointerProperties[i].id;
if (pointerIds.find(pointerId) != pointerIds.end()) {
// skip this pointer
@@ -163,19 +184,18 @@ NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
}
continue;
}
- newArgs.pointerProperties[newArgs.pointerCount].copyFrom(args.pointerProperties[i]);
- newArgs.pointerCoords[newArgs.pointerCount].copyFrom(args.pointerCoords[i]);
+ newArgs.pointerProperties.push_back(args.pointerProperties[i]);
+ newArgs.pointerCoords.push_back(args.pointerCoords[i]);
if (i == actionIndex) {
- newActionIndex = newArgs.pointerCount;
+ newActionIndex = newArgs.getPointerCount() - 1;
}
- newArgs.pointerCount++;
}
// Update POINTER_DOWN or POINTER_UP actions
if (isPointerUpOrDownAction && newArgs.action != ACTION_UNKNOWN) {
newArgs.action =
actionMasked | (newActionIndex << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
// Convert POINTER_DOWN and POINTER_UP to DOWN and UP if there's only 1 pointer remaining
- if (newArgs.pointerCount == 1) {
+ if (newArgs.getPointerCount() == 1) {
if (actionMasked == AMOTION_EVENT_ACTION_POINTER_DOWN) {
newArgs.action = AMOTION_EVENT_ACTION_DOWN;
} else if (actionMasked == AMOTION_EVENT_ACTION_POINTER_UP) {
@@ -194,13 +214,14 @@ NotifyMotionArgs removePointerIds(const NotifyMotionArgs& args,
*/
static std::optional<NotifyMotionArgs> removeStylusPointerIds(const NotifyMotionArgs& args) {
std::set<int32_t> stylusPointerIds;
- for (uint32_t i = 0; i < args.pointerCount; i++) {
- if (args.pointerProperties[i].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS) {
+ for (uint32_t i = 0; i < args.getPointerCount(); i++) {
+ if (isStylusToolType(args.pointerProperties[i].toolType)) {
stylusPointerIds.insert(args.pointerProperties[i].id);
}
}
NotifyMotionArgs withoutStylusPointers = removePointerIds(args, stylusPointerIds);
- if (withoutStylusPointers.pointerCount == 0 || withoutStylusPointers.action == ACTION_UNKNOWN) {
+ if (withoutStylusPointers.getPointerCount() == 0 ||
+ withoutStylusPointers.action == ACTION_UNKNOWN) {
return std::nullopt;
}
return withoutStylusPointers;
@@ -265,7 +286,7 @@ std::optional<AndroidPalmFilterDeviceInfo> createPalmFilterDeviceInfo(
std::vector<NotifyMotionArgs> cancelSuppressedPointers(
const NotifyMotionArgs& args, const std::set<int32_t>& oldSuppressedPointerIds,
const std::set<int32_t>& newSuppressedPointerIds) {
- LOG_ALWAYS_FATAL_IF(args.pointerCount == 0, "0 pointers in %s", args.dump().c_str());
+ LOG_ALWAYS_FATAL_IF(args.getPointerCount() == 0, "0 pointers in %s", args.dump().c_str());
// First, let's remove the old suppressed pointers. They've already been canceled previously.
NotifyMotionArgs oldArgs = removePointerIds(args, oldSuppressedPointerIds);
@@ -277,7 +298,7 @@ std::vector<NotifyMotionArgs> cancelSuppressedPointers(
const int32_t actionMasked = MotionEvent::getActionMasked(args.action);
// We will iteratively remove pointers from 'removedArgs'.
NotifyMotionArgs removedArgs{oldArgs};
- for (uint32_t i = 0; i < oldArgs.pointerCount; i++) {
+ for (uint32_t i = 0; i < oldArgs.getPointerCount(); i++) {
const int32_t pointerId = oldArgs.pointerProperties[i].id;
if (newSuppressedPointerIds.find(pointerId) == newSuppressedPointerIds.end()) {
// This is a pointer that should not be canceled. Move on.
@@ -289,7 +310,7 @@ std::vector<NotifyMotionArgs> cancelSuppressedPointers(
continue;
}
- if (removedArgs.pointerCount == 1) {
+ if (removedArgs.getPointerCount() == 1) {
// We are about to remove the last pointer, which means there will be no more gesture
// remaining. This is identical to canceling all pointers, so just send a single CANCEL
// event, without any of the preceding POINTER_UP with FLAG_CANCELED events.
@@ -307,7 +328,7 @@ std::vector<NotifyMotionArgs> cancelSuppressedPointers(
}
// Now 'removedArgs' contains only pointers that are valid.
- if (removedArgs.pointerCount <= 0 || removedArgs.action == ACTION_UNKNOWN) {
+ if (removedArgs.getPointerCount() <= 0 || removedArgs.action == ACTION_UNKNOWN) {
return out;
}
out.push_back(removedArgs);
@@ -322,24 +343,28 @@ UnwantedInteractionBlocker::UnwantedInteractionBlocker(InputListenerInterface& l
: mQueuedListener(listener), mEnablePalmRejection(enablePalmRejection) {}
void UnwantedInteractionBlocker::notifyConfigurationChanged(
- const NotifyConfigurationChangedArgs* args) {
+ const NotifyConfigurationChangedArgs& args) {
mQueuedListener.notifyConfigurationChanged(args);
mQueuedListener.flush();
}
-void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs* args) {
+void UnwantedInteractionBlocker::notifyKey(const NotifyKeyArgs& args) {
mQueuedListener.notifyKey(args);
mQueuedListener.flush();
}
-void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
- ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args->dump().c_str());
+void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs& args) {
+ ALOGD_IF(DEBUG_INBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
{ // acquire lock
std::scoped_lock lock(mLock);
- const std::vector<NotifyMotionArgs> processedArgs =
- mPreferStylusOverTouchBlocker.processMotion(*args);
- for (const NotifyMotionArgs& loopArgs : processedArgs) {
- notifyMotionLocked(&loopArgs);
+ if (ENABLE_MULTI_DEVICE_INPUT) {
+ notifyMotionLocked(args);
+ } else {
+ const std::vector<NotifyMotionArgs> processedArgs =
+ mPreferStylusOverTouchBlocker.processMotion(args);
+ for (const NotifyMotionArgs& loopArgs : processedArgs) {
+ notifyMotionLocked(loopArgs);
+ }
}
} // release lock
@@ -349,61 +374,68 @@ void UnwantedInteractionBlocker::notifyMotion(const NotifyMotionArgs* args) {
void UnwantedInteractionBlocker::enqueueOutboundMotionLocked(const NotifyMotionArgs& args) {
ALOGD_IF(DEBUG_OUTBOUND_MOTION, "%s: %s", __func__, args.dump().c_str());
- mQueuedListener.notifyMotion(&args);
+ mQueuedListener.notifyMotion(args);
}
-void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs* args) {
- auto it = mPalmRejectors.find(args->deviceId);
- const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args->source);
+void UnwantedInteractionBlocker::notifyMotionLocked(const NotifyMotionArgs& args) {
+ auto it = mPalmRejectors.find(args.deviceId);
+ const bool sendToPalmRejector = it != mPalmRejectors.end() && isFromTouchscreen(args.source);
if (!sendToPalmRejector) {
- enqueueOutboundMotionLocked(*args);
+ enqueueOutboundMotionLocked(args);
return;
}
- std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(*args);
+ std::vector<NotifyMotionArgs> processedArgs = it->second.processMotion(args);
for (const NotifyMotionArgs& loopArgs : processedArgs) {
enqueueOutboundMotionLocked(loopArgs);
}
}
-void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs* args) {
+void UnwantedInteractionBlocker::notifySwitch(const NotifySwitchArgs& args) {
mQueuedListener.notifySwitch(args);
mQueuedListener.flush();
}
-void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs* args) {
+void UnwantedInteractionBlocker::notifySensor(const NotifySensorArgs& args) {
mQueuedListener.notifySensor(args);
mQueuedListener.flush();
}
-void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs* args) {
+void UnwantedInteractionBlocker::notifyVibratorState(const NotifyVibratorStateArgs& args) {
mQueuedListener.notifyVibratorState(args);
mQueuedListener.flush();
}
-void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
+void UnwantedInteractionBlocker::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
{ // acquire lock
std::scoped_lock lock(mLock);
- auto it = mPalmRejectors.find(args->deviceId);
+ auto it = mPalmRejectors.find(args.deviceId);
if (it != mPalmRejectors.end()) {
AndroidPalmFilterDeviceInfo info = it->second.getPalmFilterDeviceInfo();
// Re-create the object instead of resetting it
mPalmRejectors.erase(it);
- mPalmRejectors.emplace(args->deviceId, info);
+ mPalmRejectors.emplace(args.deviceId, info);
}
mQueuedListener.notifyDeviceReset(args);
- mPreferStylusOverTouchBlocker.notifyDeviceReset(*args);
+ mPreferStylusOverTouchBlocker.notifyDeviceReset(args);
} // release lock
// Send events to the next stage without holding the lock
mQueuedListener.flush();
}
void UnwantedInteractionBlocker::notifyPointerCaptureChanged(
- const NotifyPointerCaptureChangedArgs* args) {
+ const NotifyPointerCaptureChangedArgs& args) {
mQueuedListener.notifyPointerCaptureChanged(args);
mQueuedListener.flush();
}
void UnwantedInteractionBlocker::notifyInputDevicesChanged(
+ const NotifyInputDevicesChangedArgs& args) {
+ onInputDevicesChanged(args.inputDeviceInfos);
+ mQueuedListener.notify(args);
+ mQueuedListener.flush();
+}
+
+void UnwantedInteractionBlocker::onInputDevicesChanged(
const std::vector<InputDeviceInfo>& inputDevices) {
std::scoped_lock lock(mLock);
if (!mEnablePalmRejection) {
@@ -459,7 +491,7 @@ void UnwantedInteractionBlocker::monitor() {
UnwantedInteractionBlocker::~UnwantedInteractionBlocker() {}
void SlotState::update(const NotifyMotionArgs& args) {
- for (size_t i = 0; i < args.pointerCount; i++) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
const int32_t pointerId = args.pointerProperties[i].id;
const int32_t resolvedAction = resolveActionForPointer(i, args.action);
processPointerId(pointerId, resolvedAction);
@@ -557,7 +589,7 @@ std::vector<::ui::InProgressTouchEvdev> getTouches(const NotifyMotionArgs& args,
const SlotState& newSlotState) {
std::vector<::ui::InProgressTouchEvdev> touches;
- for (size_t i = 0; i < args.pointerCount; i++) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
const int32_t pointerId = args.pointerProperties[i].id;
touches.emplace_back(::ui::InProgressTouchEvdev());
touches.back().major = args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
@@ -646,7 +678,7 @@ std::set<int32_t> PalmRejector::detectPalmPointers(const NotifyMotionArgs& args)
// Now that we know which slots should be suppressed, let's convert those to pointer id's.
std::set<int32_t> newSuppressedIds;
- for (size_t i = 0; i < args.pointerCount; i++) {
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
const int32_t pointerId = args.pointerProperties[i].id;
std::optional<size_t> slot = oldSlotState.getSlotForPointerId(pointerId);
if (!slot) {
diff --git a/services/inputflinger/UnwantedInteractionBlocker.h b/services/inputflinger/UnwantedInteractionBlocker.h
index 5d0dde8e64..419da8366e 100644
--- a/services/inputflinger/UnwantedInteractionBlocker.h
+++ b/services/inputflinger/UnwantedInteractionBlocker.h
@@ -90,16 +90,16 @@ public:
explicit UnwantedInteractionBlocker(InputListenerInterface& listener);
explicit UnwantedInteractionBlocker(InputListenerInterface& listener, bool enablePalmRejection);
- 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;
-
- void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+ 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;
+
void dump(std::string& dump) override;
void monitor() override;
@@ -119,10 +119,12 @@ private:
// Use a separate palm rejector for every touch device.
std::map<int32_t /*deviceId*/, PalmRejector> mPalmRejectors GUARDED_BY(mLock);
// TODO(b/210159205): delete this when simultaneous stylus and touch is supported
- void notifyMotionLocked(const NotifyMotionArgs* args) REQUIRES(mLock);
+ void notifyMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
// Call this function for outbound events so that they can be logged when logging is enabled.
void enqueueOutboundMotionLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+
+ void onInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices);
};
class SlotState {
diff --git a/services/inputflinger/aidl/Android.bp b/services/inputflinger/aidl/Android.bp
new file mode 100644
index 0000000000..d06812995c
--- /dev/null
+++ b/services/inputflinger/aidl/Android.bp
@@ -0,0 +1,34 @@
+// 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.
+
+aidl_interface {
+ name: "com.android.server.inputflinger",
+ srcs: ["**/*.aidl"],
+ unstable: true,
+ host_supported: true,
+ imports: [
+ "android.hardware.input.common-V1",
+ ],
+ backend: {
+ cpp: {
+ enabled: false,
+ },
+ java: {
+ enabled: false,
+ },
+ rust: {
+ enabled: true,
+ },
+ },
+}
diff --git a/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
index 6929a6cb49..b9e6a03560 100644
--- a/libs/binder/aidl/android/content/pm/IPackageChangeObserver.aidl
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/DeviceInfo.aidl
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 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.
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.content.pm;
-
-import android.content.pm.PackageChangeEvent;
+package com.android.server.inputflinger;
/**
- * This is a non-blocking notification when a package has changed.
- *
- * @hide
+ * Analogous to Android's InputDeviceInfo
+ * Stores the basic information connected input devices.
*/
-oneway interface IPackageChangeObserver {
- void onPackageChanged(in PackageChangeEvent event);
-}
+parcelable DeviceInfo {
+ int deviceId;
+ boolean external;
+} \ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
new file mode 100644
index 0000000000..14b41cd00c
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFilter.aidl
@@ -0,0 +1,50 @@
+/*
+ * 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 com.android.server.inputflinger;
+
+import com.android.server.inputflinger.DeviceInfo;
+import com.android.server.inputflinger.InputFilterConfiguration;
+import com.android.server.inputflinger.KeyEvent;
+
+/**
+ * A local AIDL interface used as a foreign function interface (ffi) to
+ * filter input events.
+ *
+ * NOTE: Since we use this as a local interface, all processing happens on the
+ * calling thread.
+ */
+interface IInputFilter {
+
+ /** Callbacks for the rust InputFilter to call into C++. */
+ interface IInputFilterCallbacks {
+ /** Sends back a filtered key event */
+ void sendKeyEvent(in KeyEvent event);
+ }
+
+ /** Returns if InputFilter is enabled */
+ boolean isEnabled();
+
+ /** Notifies if a key event occurred */
+ void notifyKey(in KeyEvent event);
+
+ /** Notifies if any InputDevice list changed and provides the list of connected peripherals */
+ void notifyInputDevicesChanged(in DeviceInfo[] deviceInfos);
+
+ /** Notifies when configuration changes */
+ void notifyConfigurationChanged(in InputFilterConfiguration config);
+}
+
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
new file mode 100644
index 0000000000..de852c0c24
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/IInputFlingerRust.aidl
@@ -0,0 +1,40 @@
+/*
+ * 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 com.android.server.inputflinger;
+
+import com.android.server.inputflinger.IInputFilter;
+import com.android.server.inputflinger.IInputFilter.IInputFilterCallbacks;
+
+/**
+ * A local AIDL interface used as a foreign function interface (ffi) to
+ * communicate with the Rust component of inputflinger.
+ *
+ * NOTE: Since we use this as a local interface, all processing happens on the
+ * calling thread.
+ */
+interface IInputFlingerRust {
+
+ /**
+ * An interface used to get a strong reference to IInputFlingerRust on boot.
+ */
+ interface IInputFlingerRustBootstrapCallback {
+ void onProvideInputFlingerRust(in IInputFlingerRust inputFlingerRust);
+ }
+
+ /** Create the rust implementation of InputFilter. */
+ IInputFilter createInputFilter(IInputFilterCallbacks callbacks);
+}
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
new file mode 100644
index 0000000000..3b2e88ba24
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/InputFilterConfiguration.aidl
@@ -0,0 +1,25 @@
+/*
+ * 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 com.android.server.inputflinger;
+
+/**
+ * Contains data for the current Input filter configuration
+ */
+parcelable InputFilterConfiguration {
+ // Threshold value for Bounce keys filter (check bounce_keys_filter.rs)
+ long bounceKeysThresholdNs;
+} \ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
new file mode 100644
index 0000000000..2cae6e1673
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEvent.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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 com.android.server.inputflinger;
+
+import android.hardware.input.common.Source;
+import com.android.server.inputflinger.KeyEventAction;
+
+/**
+ * Analogous to Android's native KeyEvent / NotifyKeyArgs.
+ * Stores the basic information about Key events.
+ */
+@RustDerive(Copy=true, Clone=true, Eq=true, PartialEq=true)
+parcelable KeyEvent {
+ int id;
+ int deviceId;
+ long downTime;
+ long readTime;
+ long eventTime;
+ Source source;
+ int displayId;
+ int policyFlags;
+ KeyEventAction action;
+ int flags;
+ int keyCode;
+ int scanCode;
+ int metaState;
+} \ No newline at end of file
diff --git a/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl
new file mode 100644
index 0000000000..43ee5fe069
--- /dev/null
+++ b/services/inputflinger/aidl/com/android/server/inputflinger/KeyEventAction.aidl
@@ -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.
+ */
+
+package com.android.server.inputflinger;
+
+/** Different Key event actions */
+enum KeyEventAction {
+ /** The key has been pressed down. */
+ DOWN = 0,
+
+ /** The key has been released. */
+ UP = 1,
+
+ /**
+ * Multiple duplicate key events have occurred in a row, or a
+ * complex string is being delivered. The repeat_count property
+ * of the key event contains the number of times the given key
+ * code should be executed.
+ *
+ * NOTE: This is deprecated and should never be used. This just
+ * for consistency with KeyEvent actions defined in NotifyKeyArgs.
+ */
+ MULTIPLE = 2
+} \ No newline at end of file
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index 75071d55ac..2d1257478d 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -1,4 +1,5 @@
package {
+ default_team: "trendy_team_input_framework",
// 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"
@@ -19,14 +20,13 @@ cc_benchmark {
shared_libs: [
"libbase",
"libbinder",
+ "libbinder_ndk",
"libcrypto",
"libcutils",
- "libinput",
"libinputflinger_base",
"libinputreporter",
"liblog",
"libstatslog",
- "libui",
"libutils",
],
static_libs: [
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index a2e60c4e6f..5ae3715f2f 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -20,196 +20,39 @@
#include <binder/Binder.h>
#include <gui/constants.h>
#include "../dispatcher/InputDispatcher.h"
+#include "../tests/FakeApplicationHandle.h"
+#include "../tests/FakeInputDispatcherPolicy.h"
+#include "../tests/FakeWindowHandle.h"
using android::base::Result;
using android::gui::WindowInfo;
-using android::gui::WindowInfoHandle;
using android::os::IInputConstants;
using android::os::InputEventInjectionResult;
using android::os::InputEventInjectionSync;
namespace android::inputdispatcher {
+namespace {
+
// An arbitrary device id.
-constexpr int32_t DEVICE_ID = 1;
+constexpr DeviceId DEVICE_ID = 1;
-// The default pid and uid for windows created by the test.
-constexpr int32_t WINDOW_PID = 999;
-constexpr int32_t WINDOW_UID = 1001;
+// An arbitrary display id
+constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 5s;
-static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
static nsecs_t now() {
return systemTime(SYSTEM_TIME_MONOTONIC);
}
-// --- FakeInputDispatcherPolicy ---
-
-class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
-public:
- FakeInputDispatcherPolicy() {}
-
-protected:
- virtual ~FakeInputDispatcherPolicy() {}
-
-private:
- void notifyConfigurationChanged(nsecs_t) override {}
-
- void notifyNoFocusedWindowAnr(
- const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
- ALOGE("There is no focused window for %s", applicationHandle->getName().c_str());
- }
-
- void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<int32_t> pid,
- const std::string& reason) override {
- ALOGE("Window is not responding: %s", reason.c_str());
- }
-
- void notifyWindowResponsive(const sp<IBinder>& connectionToken,
- std::optional<int32_t> pid) override {}
-
- void notifyInputChannelBroken(const sp<IBinder>&) override {}
-
- void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
-
- void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
- InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
- const std::vector<float>& values) override {}
-
- void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
- InputDeviceSensorAccuracy accuracy) override {}
-
- void notifyVibratorState(int32_t deviceId, bool isOn) override {}
-
- void notifyUntrustedTouch(const std::string& obscuringPackage) override {}
-
- void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
- *outConfig = mConfig;
- }
-
- bool filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) override {
- return true;
- }
-
- void interceptKeyBeforeQueueing(const KeyEvent*, uint32_t&) override {}
-
- void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
-
- nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*, uint32_t) override {
- return 0;
- }
-
- bool dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent*, uint32_t, KeyEvent*) override {
- return false;
- }
-
- void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
-
- void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
-
- void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
-
- void setPointerCapture(const PointerCaptureRequest&) override {}
-
- void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
-
- InputDispatcherConfiguration mConfig;
-};
-
-class FakeApplicationHandle : public InputApplicationHandle {
-public:
- FakeApplicationHandle() {}
- virtual ~FakeApplicationHandle() {}
-
- virtual bool updateInfo() {
- mInfo.dispatchingTimeoutMillis =
- std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
- return true;
- }
-};
-
-class FakeInputReceiver {
-public:
- void consumeEvent() {
- uint32_t consumeSeq = 0;
- InputEvent* event;
-
- std::chrono::time_point start = std::chrono::steady_clock::now();
- status_t result = WOULD_BLOCK;
- while (result == WOULD_BLOCK) {
- std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
- if (elapsed > 10ms) {
- ALOGE("Waited too long for consumer to produce an event, giving up");
- break;
- }
- result = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq,
- &event);
- }
- if (result != OK) {
- ALOGE("Received result = %d from consume()", result);
- }
- result = mConsumer->sendFinishedSignal(consumeSeq, true);
- if (result != OK) {
- ALOGE("Received result = %d from sendFinishedSignal", result);
- }
- }
-
-protected:
- explicit FakeInputReceiver(InputDispatcher& dispatcher, const std::string name) {
- Result<std::unique_ptr<InputChannel>> channelResult = dispatcher.createInputChannel(name);
- LOG_ALWAYS_FATAL_IF(!channelResult.ok());
- mClientChannel = std::move(*channelResult);
- mConsumer = std::make_unique<InputConsumer>(mClientChannel);
- }
-
- virtual ~FakeInputReceiver() {}
-
- std::shared_ptr<InputChannel> mClientChannel;
- std::unique_ptr<InputConsumer> mConsumer;
- PreallocatedInputEventFactory mEventFactory;
-};
-
-class FakeWindowHandle : public WindowInfoHandle, public FakeInputReceiver {
-public:
- static const int32_t WIDTH = 200;
- static const int32_t HEIGHT = 200;
-
- FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
- InputDispatcher& dispatcher, const std::string name)
- : FakeInputReceiver(dispatcher, name), mFrame(Rect(0, 0, WIDTH, HEIGHT)) {
- inputApplicationHandle->updateInfo();
- updateInfo();
- mInfo.applicationInfo = *inputApplicationHandle->getInfo();
- }
-
- void updateInfo() {
- mInfo.token = mClientChannel->getConnectionToken();
- mInfo.name = "FakeWindowHandle";
- mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
- mInfo.frameLeft = mFrame.left;
- mInfo.frameTop = mFrame.top;
- mInfo.frameRight = mFrame.right;
- mInfo.frameBottom = mFrame.bottom;
- mInfo.globalScaleFactor = 1.0;
- mInfo.touchableRegion.clear();
- mInfo.addTouchableRegion(mFrame);
- mInfo.ownerPid = WINDOW_PID;
- mInfo.ownerUid = WINDOW_UID;
- mInfo.displayId = ADISPLAY_ID_DEFAULT;
- }
-
-protected:
- Rect mFrame;
-};
-
static MotionEvent generateMotionEvent() {
PointerProperties pointerProperties[1];
PointerCoords pointerCoords[1];
pointerProperties[0].clear();
pointerProperties[0].id = 0;
- pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ pointerProperties[0].toolType = ToolType::FINGER;
pointerCoords[0].clear();
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 100);
@@ -237,7 +80,7 @@ static NotifyMotionArgs generateMotionArgs() {
pointerProperties[0].clear();
pointerProperties[0].id = 0;
- pointerProperties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER;
+ pointerProperties[0].toolType = ToolType::FINGER;
pointerCoords[0].clear();
pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 100);
@@ -260,16 +103,17 @@ static NotifyMotionArgs generateMotionArgs() {
static void benchmarkNotifyMotion(benchmark::State& state) {
// Create dispatcher
- sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
+ FakeInputDispatcherPolicy fakePolicy;
InputDispatcher dispatcher(fakePolicy);
dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
dispatcher.start();
// Create a window that will receive motion events
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window = new FakeWindowHandle(application, dispatcher, "Fake Window");
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
- dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
NotifyMotionArgs motionArgs = generateMotionArgs();
@@ -278,15 +122,15 @@ static void benchmarkNotifyMotion(benchmark::State& state) {
motionArgs.action = AMOTION_EVENT_ACTION_DOWN;
motionArgs.downTime = now();
motionArgs.eventTime = motionArgs.downTime;
- dispatcher.notifyMotion(&motionArgs);
+ dispatcher.notifyMotion(motionArgs);
// Send ACTION_UP
motionArgs.action = AMOTION_EVENT_ACTION_UP;
motionArgs.eventTime = now();
- dispatcher.notifyMotion(&motionArgs);
+ dispatcher.notifyMotion(motionArgs);
- window->consumeEvent();
- window->consumeEvent();
+ window->consumeMotion();
+ window->consumeMotion();
}
dispatcher.stop();
@@ -294,32 +138,33 @@ static void benchmarkNotifyMotion(benchmark::State& state) {
static void benchmarkInjectMotion(benchmark::State& state) {
// Create dispatcher
- sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
+ FakeInputDispatcherPolicy fakePolicy;
InputDispatcher dispatcher(fakePolicy);
dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
dispatcher.start();
// Create a window that will receive motion events
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window = new FakeWindowHandle(application, dispatcher, "Fake Window");
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
- dispatcher.setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ dispatcher.onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
for (auto _ : state) {
MotionEvent event = generateMotionEvent();
// Send ACTION_DOWN
- dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
INJECT_EVENT_TIMEOUT,
POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
// Send ACTION_UP
event.setAction(AMOTION_EVENT_ACTION_UP);
- dispatcher.injectInputEvent(&event, {} /*targetUid*/, InputEventInjectionSync::NONE,
+ dispatcher.injectInputEvent(&event, /*targetUid=*/{}, InputEventInjectionSync::NONE,
INJECT_EVENT_TIMEOUT,
POLICY_FLAG_FILTERED | POLICY_FLAG_PASS_TO_USER);
- window->consumeEvent();
- window->consumeEvent();
+ window->consumeMotion();
+ window->consumeMotion();
}
dispatcher.stop();
@@ -327,14 +172,15 @@ static void benchmarkInjectMotion(benchmark::State& state) {
static void benchmarkOnWindowInfosChanged(benchmark::State& state) {
// Create dispatcher
- sp<FakeInputDispatcherPolicy> fakePolicy = new FakeInputDispatcherPolicy();
+ FakeInputDispatcherPolicy fakePolicy;
InputDispatcher dispatcher(fakePolicy);
dispatcher.setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
dispatcher.start();
// Create a window
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window = new FakeWindowHandle(application, dispatcher, "Fake Window");
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, dispatcher, "Fake Window", DISPLAY_ID);
std::vector<gui::WindowInfo> windowInfos{*window->getInfo()};
gui::DisplayInfo info;
@@ -342,12 +188,16 @@ static void benchmarkOnWindowInfosChanged(benchmark::State& state) {
std::vector<gui::DisplayInfo> displayInfos{info};
for (auto _ : state) {
- dispatcher.onWindowInfosChanged(windowInfos, displayInfos);
- dispatcher.onWindowInfosChanged({} /*windowInfos*/, {} /*displayInfos*/);
+ dispatcher.onWindowInfosChanged(
+ {windowInfos, displayInfos, /*vsyncId=*/0, /*timestamp=*/0});
+ dispatcher.onWindowInfosChanged(
+ {/*windowInfos=*/{}, /*displayInfos=*/{}, /*vsyncId=*/{}, /*timestamp=*/0});
}
dispatcher.stop();
}
+} // namespace
+
BENCHMARK(benchmarkNotifyMotion);
BENCHMARK(benchmarkInjectMotion);
BENCHMARK(benchmarkOnWindowInfosChanged);
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index cdad9c93fd..c7baceea70 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -23,6 +23,7 @@ package {
cc_library_headers {
name: "libinputdispatcher_headers",
+ host_supported: true,
export_include_dirs: [
"include",
],
@@ -33,6 +34,8 @@ filegroup {
srcs: [
"AnrTracker.cpp",
"Connection.cpp",
+ "DebugConfig.cpp",
+ "DragState.cpp",
"Entry.cpp",
"FocusResolver.cpp",
"InjectionState.cpp",
@@ -44,8 +47,8 @@ filegroup {
"LatencyAggregator.cpp",
"LatencyTracker.cpp",
"Monitor.cpp",
+ "TouchedWindow.cpp",
"TouchState.cpp",
- "DragState.cpp",
],
}
@@ -61,16 +64,22 @@ cc_defaults {
"liblog",
"libprotobuf-cpp-lite",
"libstatslog",
+ "libutils",
"libstatspull",
"libstatssocket",
- "libui",
- "libgui",
- "libutils",
"server_configurable_flags",
],
static_libs: [
"libattestation",
+ "libgui_window_info_static",
],
+ target: {
+ android: {
+ shared_libs: [
+ "libgui",
+ ],
+ },
+ },
header_libs: [
"libinputdispatcher_headers",
],
@@ -78,6 +87,7 @@ cc_defaults {
cc_library_static {
name: "libinputdispatcher",
+ host_supported: true,
defaults: [
"inputflinger_defaults",
"libinputdispatcher_defaults",
@@ -85,8 +95,8 @@ cc_library_static {
shared_libs: [
// This should consist only of dependencies from inputflinger. Other dependencies should be
// in cc_defaults so that they are included in the tests.
- "libinputreporter",
"libinputflinger_base",
+ "libinputreporter",
],
export_header_lib_headers: [
"libinputdispatcher_headers",
diff --git a/services/inputflinger/dispatcher/AnrTracker.cpp b/services/inputflinger/dispatcher/AnrTracker.cpp
index c3f611e7db..a18063f299 100644
--- a/services/inputflinger/dispatcher/AnrTracker.cpp
+++ b/services/inputflinger/dispatcher/AnrTracker.cpp
@@ -54,7 +54,7 @@ bool AnrTracker::empty() const {
}
// If empty() is false, return the time at which the next connection should cause an ANR
-// If empty() is true, return LONG_LONG_MAX
+// If empty() is true, return LLONG_MAX
nsecs_t AnrTracker::firstTimeout() const {
if (mAnrTimeouts.empty()) {
return std::numeric_limits<nsecs_t>::max();
diff --git a/services/inputflinger/dispatcher/AnrTracker.h b/services/inputflinger/dispatcher/AnrTracker.h
index 097dba5bea..cff5d00e4b 100644
--- a/services/inputflinger/dispatcher/AnrTracker.h
+++ b/services/inputflinger/dispatcher/AnrTracker.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H
-#define _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H
+#pragma once
#include <binder/IBinder.h>
#include <utils/Timers.h>
@@ -36,7 +35,7 @@ public:
bool empty() const;
// If empty() is false, return the time at which the next connection should cause an ANR
- // If empty() is true, return LONG_LONG_MAX
+ // If empty() is true, return LLONG_MAX
nsecs_t firstTimeout() const;
// Return the token of the next connection that should cause an ANR.
// Do not call this unless empty() is false, you will encounter undefined behaviour.
@@ -56,5 +55,3 @@ private:
};
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_ANRTRACKER_H
diff --git a/services/inputflinger/dispatcher/CancelationOptions.h b/services/inputflinger/dispatcher/CancelationOptions.h
index 99e2108dbf..83e6a60602 100644
--- a/services/inputflinger/dispatcher/CancelationOptions.h
+++ b/services/inputflinger/dispatcher/CancelationOptions.h
@@ -14,20 +14,23 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H
-#define _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H
+#pragma once
+#include <input/Input.h>
+#include <bitset>
#include <optional>
-namespace android::inputdispatcher {
+namespace android {
+namespace inputdispatcher {
/* Specifies which events are to be canceled and why. */
struct CancelationOptions {
- enum Mode {
+ enum class Mode {
CANCEL_ALL_EVENTS = 0,
CANCEL_POINTER_EVENTS = 1,
CANCEL_NON_POINTER_EVENTS = 2,
CANCEL_FALLBACK_EVENTS = 3,
+ ftl_last = CANCEL_FALLBACK_EVENTS,
};
// The criterion to use to determine which events should be canceled.
@@ -45,9 +48,11 @@ struct CancelationOptions {
// The specific display id of events to cancel, or nullopt to cancel events on any display.
std::optional<int32_t> displayId = std::nullopt;
+ // The specific pointers to cancel, or nullopt to cancel all pointer events
+ std::optional<std::bitset<MAX_POINTER_ID + 1>> pointerIds = std::nullopt;
+
CancelationOptions(Mode mode, const char* reason) : mode(mode), reason(reason) {}
};
-} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_CANCELLATIONOPTIONS_H
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/dispatcher/Connection.cpp b/services/inputflinger/dispatcher/Connection.cpp
index b4497fd653..f304712e8c 100644
--- a/services/inputflinger/dispatcher/Connection.cpp
+++ b/services/inputflinger/dispatcher/Connection.cpp
@@ -28,8 +28,6 @@ Connection::Connection(const std::shared_ptr<InputChannel>& inputChannel, bool m
inputPublisher(inputChannel),
inputState(idGenerator) {}
-Connection::~Connection() {}
-
const std::string Connection::getWindowName() const {
if (inputChannel != nullptr) {
return inputChannel->getName();
@@ -40,13 +38,4 @@ const std::string Connection::getWindowName() const {
return "?";
}
-std::deque<DispatchEntry*>::iterator Connection::findWaitQueueEntry(uint32_t seq) {
- for (std::deque<DispatchEntry*>::iterator it = waitQueue.begin(); it != waitQueue.end(); it++) {
- if ((*it)->seq == seq) {
- return it;
- }
- }
- return waitQueue.end();
-}
-
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Connection.h b/services/inputflinger/dispatcher/Connection.h
index dc6a081ff6..c17baea0e5 100644
--- a/services/inputflinger/dispatcher/Connection.h
+++ b/services/inputflinger/dispatcher/Connection.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_CONNECTION_H
-#define _UI_INPUT_INPUTDISPATCHER_CONNECTION_H
+#pragma once
#include "InputState.h"
@@ -28,10 +27,7 @@ namespace android::inputdispatcher {
struct DispatchEntry;
/* Manages the dispatch state associated with a single input channel. */
-class Connection : public RefBase {
-protected:
- virtual ~Connection();
-
+class Connection {
public:
enum class Status {
// Everything is peachy.
@@ -57,11 +53,11 @@ public:
bool responsive = true;
// Queue of events that need to be published to the connection.
- std::deque<DispatchEntry*> outboundQueue;
+ std::deque<std::unique_ptr<DispatchEntry>> outboundQueue;
// Queue of events that have been published to the connection but that have not
// yet received a "finished" response from the application.
- std::deque<DispatchEntry*> waitQueue;
+ std::deque<std::unique_ptr<DispatchEntry>> waitQueue;
Connection(const std::shared_ptr<InputChannel>& inputChannel, bool monitor,
const IdGenerator& idGenerator);
@@ -69,10 +65,6 @@ public:
inline const std::string getInputChannelName() const { return inputChannel->getName(); }
const std::string getWindowName() const;
-
- std::deque<DispatchEntry*>::iterator findWaitQueueEntry(uint32_t seq);
};
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_CONNECTION_H
diff --git a/services/inputflinger/dispatcher/DebugConfig.cpp b/services/inputflinger/dispatcher/DebugConfig.cpp
new file mode 100644
index 0000000000..12122fd354
--- /dev/null
+++ b/services/inputflinger/dispatcher/DebugConfig.cpp
@@ -0,0 +1,39 @@
+/*
+ * 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 "DebugConfig.h"
+
+#include <android-base/properties.h>
+
+namespace android::inputdispatcher {
+
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+ android::base::GetBoolProperty("ro.debuggable", false);
+#else
+ true;
+#endif
+
+bool debugInboundEventDetails() {
+ if (!IS_DEBUGGABLE_BUILD) {
+ static const bool DEBUG_INBOUND_EVENT_DETAILS =
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "InboundEvent");
+ return DEBUG_INBOUND_EVENT_DETAILS;
+ }
+ return android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "InboundEvent");
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DebugConfig.h b/services/inputflinger/dispatcher/DebugConfig.h
index 9a2aea69ec..c889b9b136 100644
--- a/services/inputflinger/dispatcher/DebugConfig.h
+++ b/services/inputflinger/dispatcher/DebugConfig.h
@@ -14,35 +14,44 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_DISPATCHER_DEBUG_CONFIG_H
-#define _UI_INPUT_DISPATCHER_DEBUG_CONFIG_H
+#pragma once
#define LOG_TAG "InputDispatcher"
-#include <log/log.h>
-#include <log/log_event_list.h>
+#include <android-base/logging.h>
+#include <com_android_input_flags.h>
+
+namespace input_flags = com::android::input::flags;
namespace android::inputdispatcher {
+
+/**
+ * Signals whether this is a debuggable Android build.
+ * This is populated by reading the value of the "ro.debuggable" property.
+ */
+extern const bool IS_DEBUGGABLE_BUILD;
+
/**
* Log detailed debug messages about each inbound event notification to the dispatcher.
- * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG" (requires restart)
+ * Enable this via "adb shell setprop log.tag.InputDispatcherInboundEvent DEBUG".
+ * This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
+ * on debuggable builds (e.g. userdebug).
*/
-const bool DEBUG_INBOUND_EVENT_DETAILS =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "InboundEvent", ANDROID_LOG_INFO);
+bool debugInboundEventDetails();
/**
* Log detailed debug messages about each outbound event processed by the dispatcher.
* Enable this via "adb shell setprop log.tag.InputDispatcherOutboundEvent DEBUG" (requires restart)
*/
const bool DEBUG_OUTBOUND_EVENT_DETAILS =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "OutboundEvent", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "OutboundEvent");
/**
* Log debug messages about the dispatch cycle.
* Enable this via "adb shell setprop log.tag.InputDispatcherDispatchCycle DEBUG" (requires restart)
*/
const bool DEBUG_DISPATCH_CYCLE =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "DispatchCycle", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "DispatchCycle");
/**
* Log debug messages about channel creation
@@ -50,28 +59,38 @@ const bool DEBUG_DISPATCH_CYCLE =
* restart)
*/
const bool DEBUG_CHANNEL_CREATION =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "ChannelCreation", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "ChannelCreation");
/**
* Log debug messages about input event injection.
* Enable this via "adb shell setprop log.tag.InputDispatcherInjection DEBUG" (requires restart)
*/
const bool DEBUG_INJECTION =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Injection", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Injection");
+
+/**
+ * Generally, we always log whenever events are dropped. However, to reduce logspam, some messages
+ * are suppressed.
+ * Log additional debug messages about dropped input events with this flag.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherDroppedEventsVerbose DEBUG".
+ * Requires system_server restart via `adb shell stop && adb shell start`.
+ */
+const bool DEBUG_DROPPED_EVENTS_VERBOSE =
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "DroppedEventsVerbose");
/**
* Log debug messages about input focus tracking.
* Enable this via "adb shell setprop log.tag.InputDispatcherFocus DEBUG" (requires restart)
*/
const bool DEBUG_FOCUS =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Focus", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Focus");
/**
* Log debug messages about touch mode event
* Enable this via "adb shell setprop log.tag.InputDispatcherTouchMode DEBUG" (requires restart)
*/
const bool DEBUG_TOUCH_MODE =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "TouchMode", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "TouchMode");
/**
* Log debug messages about touch occlusion
@@ -83,14 +102,20 @@ constexpr bool DEBUG_TOUCH_OCCLUSION = true;
* Enable this via "adb shell setprop log.tag.InputDispatcherAppSwitch DEBUG" (requires restart)
*/
const bool DEBUG_APP_SWITCH =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "AppSwitch", ANDROID_LOG_INFO);
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "AppSwitch");
/**
* Log debug messages about hover events.
* Enable this via "adb shell setprop log.tag.InputDispatcherHover DEBUG" (requires restart)
*/
const bool DEBUG_HOVER =
- __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Hover", ANDROID_LOG_INFO);
-} // namespace android::inputdispatcher
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "Hover");
+
+/**
+ * Crash if a bad stream from InputListener is detected.
+ * Enable this via "adb shell setprop log.tag.InputDispatcherVerifyEvents DEBUG" (requires restart)
+ */
+const bool DEBUG_VERIFY_EVENTS = input_flags::enable_inbound_event_verification() ||
+ android::base::ShouldLog(android::base::LogSeverity::DEBUG, LOG_TAG "VerifyEvents");
-#endif // _UI_INPUT_DISPATCHER_DEBUG_CONFIG_H \ No newline at end of file
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/DragState.h b/services/inputflinger/dispatcher/DragState.h
index d1c8b8a10e..9809148853 100644
--- a/services/inputflinger/dispatcher/DragState.h
+++ b/services/inputflinger/dispatcher/DragState.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H
-#define _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H
+#pragma once
#include <gui/WindowInfo.h>
#include <utils/StrongPointer.h>
@@ -44,5 +43,3 @@ struct DragState {
} // namespace inputdispatcher
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H \ No newline at end of file
diff --git a/services/inputflinger/dispatcher/Entry.cpp b/services/inputflinger/dispatcher/Entry.cpp
index 8046bbe25c..cc0d49c15e 100644
--- a/services/inputflinger/dispatcher/Entry.cpp
+++ b/services/inputflinger/dispatcher/Entry.cpp
@@ -14,16 +14,17 @@
* limitations under the License.
*/
+#define LOG_TAG "InputDispatcher"
+
#include "Entry.h"
#include "Connection.h"
+#include "DebugConfig.h"
-#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <cutils/atomic.h>
#include <inttypes.h>
-using android::base::GetBoolProperty;
using android::base::StringPrintf;
namespace android::inputdispatcher {
@@ -66,24 +67,11 @@ EventEntry::EventEntry(int32_t id, Type type, nsecs_t eventTime, uint32_t policy
injectionState(nullptr),
dispatchInProgress(false) {}
-EventEntry::~EventEntry() {
- releaseInjectionState();
-}
-
-void EventEntry::releaseInjectionState() {
- if (injectionState) {
- injectionState->release();
- injectionState = nullptr;
- }
-}
-
// --- ConfigurationChangedEntry ---
ConfigurationChangedEntry::ConfigurationChangedEntry(int32_t id, nsecs_t eventTime)
: EventEntry(id, Type::CONFIGURATION_CHANGED, eventTime, 0) {}
-ConfigurationChangedEntry::~ConfigurationChangedEntry() {}
-
std::string ConfigurationChangedEntry::getDescription() const {
return StringPrintf("ConfigurationChangedEvent(), policyFlags=0x%08x", policyFlags);
}
@@ -93,8 +81,6 @@ std::string ConfigurationChangedEntry::getDescription() const {
DeviceResetEntry::DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId)
: EventEntry(id, Type::DEVICE_RESET, eventTime, 0), deviceId(deviceId) {}
-DeviceResetEntry::~DeviceResetEntry() {}
-
std::string DeviceResetEntry::getDescription() const {
return StringPrintf("DeviceResetEvent(deviceId=%d), policyFlags=0x%08x", deviceId, policyFlags);
}
@@ -109,8 +95,6 @@ FocusEntry::FocusEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToke
hasFocus(hasFocus),
reason(reason) {}
-FocusEntry::~FocusEntry() {}
-
std::string FocusEntry::getDescription() const {
return StringPrintf("FocusEvent(hasFocus=%s)", hasFocus ? "true" : "false");
}
@@ -124,8 +108,6 @@ PointerCaptureChangedEntry::PointerCaptureChangedEntry(int32_t id, nsecs_t event
: EventEntry(id, Type::POINTER_CAPTURE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
pointerCaptureRequest(request) {}
-PointerCaptureChangedEntry::~PointerCaptureChangedEntry() {}
-
std::string PointerCaptureChangedEntry::getDescription() const {
return StringPrintf("PointerCaptureChangedEvent(pointerCaptureEnabled=%s)",
pointerCaptureRequest.enable ? "true" : "false");
@@ -142,37 +124,35 @@ DragEntry::DragEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken,
x(x),
y(y) {}
-DragEntry::~DragEntry() {}
-
std::string DragEntry::getDescription() const {
return StringPrintf("DragEntry(isExiting=%s, x=%f, y=%f)", isExiting ? "true" : "false", x, y);
}
// --- KeyEntry ---
-KeyEntry::KeyEntry(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, int32_t repeatCount,
- nsecs_t downTime)
+KeyEntry::KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,
+ int32_t metaState, int32_t repeatCount, nsecs_t downTime)
: EventEntry(id, Type::KEY, eventTime, policyFlags),
deviceId(deviceId),
source(source),
displayId(displayId),
action(action),
- flags(flags),
keyCode(keyCode),
scanCode(scanCode),
metaState(metaState),
- repeatCount(repeatCount),
downTime(downTime),
syntheticRepeat(false),
- interceptKeyResult(KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN),
- interceptKeyWakeupTime(0) {}
-
-KeyEntry::~KeyEntry() {}
+ interceptKeyResult(KeyEntry::InterceptKeyResult::UNKNOWN),
+ interceptKeyWakeupTime(0),
+ flags(flags),
+ repeatCount(repeatCount) {
+ EventEntry::injectionState = std::move(injectionState);
+}
std::string KeyEntry::getDescription() const {
- if (!GetBoolProperty("ro.debuggable", false)) {
+ if (!IS_DEBUGGABLE_BUILD) {
return "KeyEvent";
}
return StringPrintf("KeyEvent(deviceId=%d, eventTime=%" PRIu64 ", source=%s, displayId=%" PRId32
@@ -184,22 +164,12 @@ std::string KeyEntry::getDescription() const {
keyCode, scanCode, metaState, repeatCount, policyFlags);
}
-void KeyEntry::recycle() {
- releaseInjectionState();
-
- dispatchInProgress = false;
- syntheticRepeat = false;
- interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
- interceptKeyWakeupTime = 0;
-}
-
// --- TouchModeEntry ---
-TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode)
+TouchModeEntry::TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int displayId)
: EventEntry(id, Type::TOUCH_MODE_CHANGED, eventTime, POLICY_FLAG_PASS_TO_USER),
- inTouchMode(inTouchMode) {}
-
-TouchModeEntry::~TouchModeEntry() {}
+ inTouchMode(inTouchMode),
+ displayId(displayId) {}
std::string TouchModeEntry::getDescription() const {
return StringPrintf("TouchModeEvent(inTouchMode=%s)", inTouchMode ? "true" : "false");
@@ -207,14 +177,14 @@ std::string TouchModeEntry::getDescription() const {
// --- MotionEntry ---
-MotionEntry::MotionEntry(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, MotionClassification classification,
- int32_t edgeFlags, float xPrecision, float yPrecision,
- float xCursorPosition, float yCursorPosition, nsecs_t downTime,
- uint32_t pointerCount, const PointerProperties* pointerProperties,
- const PointerCoords* pointerCoords)
+MotionEntry::MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState,
+ nsecs_t eventTime, int32_t deviceId, uint32_t source, int32_t displayId,
+ uint32_t policyFlags, int32_t action, int32_t actionButton, int32_t flags,
+ int32_t metaState, int32_t buttonState,
+ MotionClassification classification, int32_t edgeFlags, float xPrecision,
+ float yPrecision, float xCursorPosition, float yCursorPosition,
+ nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords)
: EventEntry(id, Type::MOTION, eventTime, policyFlags),
deviceId(deviceId),
source(source),
@@ -231,17 +201,13 @@ MotionEntry::MotionEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32
xCursorPosition(xCursorPosition),
yCursorPosition(yCursorPosition),
downTime(downTime),
- pointerCount(pointerCount) {
- for (uint32_t i = 0; i < pointerCount; i++) {
- this->pointerProperties[i].copyFrom(pointerProperties[i]);
- this->pointerCoords[i].copyFrom(pointerCoords[i]);
- }
+ pointerProperties(pointerProperties),
+ pointerCoords(pointerCoords) {
+ EventEntry::injectionState = std::move(injectionState);
}
-MotionEntry::~MotionEntry() {}
-
std::string MotionEntry::getDescription() const {
- if (!GetBoolProperty("ro.debuggable", false)) {
+ if (!IS_DEBUGGABLE_BUILD) {
return "MotionEvent";
}
std::string msg;
@@ -256,7 +222,7 @@ std::string MotionEntry::getDescription() const {
buttonState, motionClassificationToString(classification), edgeFlags,
xPrecision, yPrecision, xCursorPosition, yCursorPosition);
- for (uint32_t i = 0; i < pointerCount; i++) {
+ for (uint32_t i = 0; i < getPointerCount(); i++) {
if (i) {
msg += ", ";
}
@@ -267,6 +233,11 @@ std::string MotionEntry::getDescription() const {
return msg;
}
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry) {
+ out << motionEntry.getDescription();
+ return out;
+}
+
// --- SensorEntry ---
SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32_t source,
@@ -282,8 +253,6 @@ SensorEntry::SensorEntry(int32_t id, nsecs_t eventTime, int32_t deviceId, uint32
hwTimestamp(hwTimestamp),
values(std::move(values)) {}
-SensorEntry::~SensorEntry() {}
-
std::string SensorEntry::getDescription() const {
std::string msg;
msg += StringPrintf("SensorEntry(deviceId=%d, source=%s, sensorType=%s, "
@@ -291,7 +260,7 @@ std::string SensorEntry::getDescription() const {
deviceId, inputEventSourceToString(source).c_str(),
ftl::enum_string(sensorType).c_str(), accuracy, hwTimestamp);
- if (!GetBoolProperty("ro.debuggable", false)) {
+ if (IS_DEBUGGABLE_BUILD) {
for (size_t i = 0; i < values.size(); i++) {
if (i > 0) {
msg += ", ";
@@ -307,7 +276,8 @@ std::string SensorEntry::getDescription() const {
volatile int32_t DispatchEntry::sNextSeqAtomic;
-DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
+DispatchEntry::DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
+ ftl::Flags<InputTarget::Flags> targetFlags,
const ui::Transform& transform, const ui::Transform& rawTransform,
float globalScaleFactor)
: seq(nextSeq()),
@@ -317,8 +287,23 @@ DispatchEntry::DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t tar
rawTransform(rawTransform),
globalScaleFactor(globalScaleFactor),
deliveryTime(0),
- resolvedAction(0),
- resolvedFlags(0) {}
+ resolvedFlags(0) {
+ switch (this->eventEntry->type) {
+ case EventEntry::Type::KEY: {
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*this->eventEntry);
+ resolvedFlags = keyEntry.flags;
+ break;
+ }
+ case EventEntry::Type::MOTION: {
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*this->eventEntry);
+ resolvedFlags = motionEntry.flags;
+ break;
+ }
+ default: {
+ break;
+ }
+ }
+}
uint32_t DispatchEntry::nextSeq() {
// Sequence number 0 is reserved and will never be returned.
@@ -329,4 +314,13 @@ uint32_t DispatchEntry::nextSeq() {
return seq;
}
+std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry) {
+ std::string transform;
+ entry.transform.dump(transform, "transform");
+ out << "DispatchEntry{resolvedFlags=" << entry.resolvedFlags
+ << ", targetFlags=" << entry.targetFlags.string() << ", transform=" << transform
+ << "} original: " << entry.eventEntry->getDescription();
+ return out;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index 0f792967eb..e2e13c3fde 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_ENTRY_H
-#define _UI_INPUT_INPUTDISPATCHER_ENTRY_H
+#pragma once
#include "InjectionState.h"
#include "InputTarget.h"
@@ -25,6 +24,7 @@
#include <stdint.h>
#include <utils/Timers.h>
#include <functional>
+#include <ostream>
#include <string>
namespace android::inputdispatcher {
@@ -48,9 +48,9 @@ struct EventEntry {
Type type;
nsecs_t eventTime;
uint32_t policyFlags;
- InjectionState* injectionState;
+ std::shared_ptr<InjectionState> injectionState;
- bool dispatchInProgress; // initially false, set to true while dispatching
+ mutable bool dispatchInProgress; // initially false, set to true while dispatching
/**
* Injected keys are events from an external (probably untrusted) application
@@ -72,17 +72,14 @@ struct EventEntry {
virtual std::string getDescription() const = 0;
EventEntry(int32_t id, Type type, nsecs_t eventTime, uint32_t policyFlags);
- virtual ~EventEntry();
-
-protected:
- void releaseInjectionState();
+ EventEntry(const EventEntry&) = delete;
+ EventEntry& operator=(const EventEntry&) = delete;
+ virtual ~EventEntry() = default;
};
struct ConfigurationChangedEntry : EventEntry {
explicit ConfigurationChangedEntry(int32_t id, nsecs_t eventTime);
std::string getDescription() const override;
-
- ~ConfigurationChangedEntry() override;
};
struct DeviceResetEntry : EventEntry {
@@ -90,8 +87,6 @@ struct DeviceResetEntry : EventEntry {
DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId);
std::string getDescription() const override;
-
- ~DeviceResetEntry() override;
};
struct FocusEntry : EventEntry {
@@ -102,8 +97,6 @@ struct FocusEntry : EventEntry {
FocusEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken, bool hasFocus,
const std::string& reason);
std::string getDescription() const override;
-
- ~FocusEntry() override;
};
struct PointerCaptureChangedEntry : EventEntry {
@@ -111,8 +104,6 @@ struct PointerCaptureChangedEntry : EventEntry {
PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, const PointerCaptureRequest&);
std::string getDescription() const override;
-
- ~PointerCaptureChangedEntry() override;
};
struct DragEntry : EventEntry {
@@ -123,8 +114,6 @@ struct DragEntry : EventEntry {
DragEntry(int32_t id, nsecs_t eventTime, sp<IBinder> connectionToken, bool isExiting, float x,
float y);
std::string getDescription() const override;
-
- ~DragEntry() override;
};
struct KeyEntry : EventEntry {
@@ -132,31 +121,30 @@ struct KeyEntry : EventEntry {
uint32_t source;
int32_t displayId;
int32_t action;
- int32_t flags;
int32_t keyCode;
int32_t scanCode;
int32_t metaState;
- int32_t repeatCount;
nsecs_t downTime;
bool syntheticRepeat; // set to true for synthetic key repeats
- enum InterceptKeyResult {
- INTERCEPT_KEY_RESULT_UNKNOWN,
- INTERCEPT_KEY_RESULT_SKIP,
- INTERCEPT_KEY_RESULT_CONTINUE,
- INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER,
+ enum class InterceptKeyResult {
+ UNKNOWN,
+ SKIP,
+ CONTINUE,
+ TRY_AGAIN_LATER,
};
- InterceptKeyResult interceptKeyResult; // set based on the interception result
- nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
-
- KeyEntry(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, int32_t repeatCount, nsecs_t downTime);
+ // These are special fields that may need to be modified while the event is being dispatched.
+ mutable InterceptKeyResult interceptKeyResult; // set based on the interception result
+ mutable nsecs_t interceptKeyWakeupTime; // used with INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER
+ mutable int32_t flags;
+ mutable int32_t repeatCount;
+
+ KeyEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+ int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState,
+ int32_t repeatCount, nsecs_t downTime);
std::string getDescription() const override;
- void recycle();
-
- ~KeyEntry() override;
};
struct MotionEntry : EventEntry {
@@ -175,21 +163,23 @@ struct MotionEntry : EventEntry {
float xCursorPosition;
float yCursorPosition;
nsecs_t downTime;
- uint32_t pointerCount;
- PointerProperties pointerProperties[MAX_POINTERS];
- PointerCoords pointerCoords[MAX_POINTERS];
-
- MotionEntry(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, MotionClassification classification,
- int32_t edgeFlags, float xPrecision, float yPrecision, float xCursorPosition,
- float yCursorPosition, nsecs_t downTime, uint32_t pointerCount,
- const PointerProperties* pointerProperties, const PointerCoords* pointerCoords);
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
+
+ size_t getPointerCount() const { return pointerProperties.size(); }
+
+ MotionEntry(int32_t id, std::shared_ptr<InjectionState> injectionState, nsecs_t eventTime,
+ int32_t deviceId, uint32_t source, int32_t displayId, uint32_t policyFlags,
+ int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
+ int32_t buttonState, MotionClassification classification, int32_t edgeFlags,
+ float xPrecision, float yPrecision, float xCursorPosition, float yCursorPosition,
+ nsecs_t downTime, const std::vector<PointerProperties>& pointerProperties,
+ const std::vector<PointerCoords>& pointerCoords);
std::string getDescription() const override;
-
- ~MotionEntry() override;
};
+std::ostream& operator<<(std::ostream& out, const MotionEntry& motionEntry);
+
struct SensorEntry : EventEntry {
int32_t deviceId;
uint32_t source;
@@ -205,25 +195,22 @@ struct SensorEntry : EventEntry {
InputDeviceSensorAccuracy accuracy, bool accuracyChanged,
std::vector<float> values);
std::string getDescription() const override;
-
- ~SensorEntry() override;
};
struct TouchModeEntry : EventEntry {
bool inTouchMode;
+ int32_t displayId;
- TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode);
+ TouchModeEntry(int32_t id, nsecs_t eventTime, bool inTouchMode, int32_t displayId);
std::string getDescription() const override;
-
- ~TouchModeEntry() override;
};
// Tracks the progress of dispatching a particular event to a particular connection.
struct DispatchEntry {
const uint32_t seq; // unique sequence number, never 0
- std::shared_ptr<EventEntry> eventEntry; // the event to dispatch
- int32_t targetFlags;
+ std::shared_ptr<const EventEntry> eventEntry; // the event to dispatch
+ const ftl::Flags<InputTarget::Flags> targetFlags;
ui::Transform transform;
ui::Transform rawTransform;
float globalScaleFactor;
@@ -233,18 +220,19 @@ struct DispatchEntry {
// An ANR will be triggered if a response for this entry is not received by timeoutTime
nsecs_t timeoutTime;
- // Set to the resolved ID, action and flags when the event is enqueued.
- int32_t resolvedEventId;
- int32_t resolvedAction;
int32_t resolvedFlags;
- DispatchEntry(std::shared_ptr<EventEntry> eventEntry, int32_t targetFlags,
- const ui::Transform& transform, const ui::Transform& rawTransform,
- float globalScaleFactor);
+ DispatchEntry(std::shared_ptr<const EventEntry> eventEntry,
+ ftl::Flags<InputTarget::Flags> targetFlags, const ui::Transform& transform,
+ const ui::Transform& rawTransform, float globalScaleFactor);
+ DispatchEntry(const DispatchEntry&) = delete;
+ DispatchEntry& operator=(const DispatchEntry&) = delete;
- inline bool hasForegroundTarget() const { return targetFlags & InputTarget::FLAG_FOREGROUND; }
+ inline bool hasForegroundTarget() const {
+ return targetFlags.test(InputTarget::Flags::FOREGROUND);
+ }
- inline bool isSplit() const { return targetFlags & InputTarget::FLAG_SPLIT; }
+ inline bool isSplit() const { return targetFlags.test(InputTarget::Flags::SPLIT); }
private:
static volatile int32_t sNextSeqAtomic;
@@ -252,10 +240,10 @@ private:
static uint32_t nextSeq();
};
+std::ostream& operator<<(std::ostream& out, const DispatchEntry& entry);
+
VerifiedKeyEvent verifiedKeyEventFromKeyEntry(const KeyEntry& entry);
VerifiedMotionEvent verifiedMotionEventFromMotionEntry(const MotionEntry& entry,
const ui::Transform& rawTransform);
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_ENTRY_H
diff --git a/services/inputflinger/dispatcher/FocusResolver.cpp b/services/inputflinger/dispatcher/FocusResolver.cpp
index a02b3e8779..0e4e79e88e 100644
--- a/services/inputflinger/dispatcher/FocusResolver.cpp
+++ b/services/inputflinger/dispatcher/FocusResolver.cpp
@@ -13,24 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
-#define LOG_TAG "FocusResolver"
+#include <optional>
+#define LOG_TAG "InputDispatcher"
#define ATRACE_TAG ATRACE_TAG_INPUT
#define INDENT " "
#define INDENT2 " "
-// Log debug messages about input focus tracking.
-static constexpr bool DEBUG_FOCUS = false;
-
#include <inttypes.h>
#include <android-base/stringprintf.h>
#include <binder/Binder.h>
#include <ftl/enum.h>
#include <gui/WindowInfo.h>
-#include <log/log.h>
+#include <unordered_set>
+#include "DebugConfig.h"
#include "FocusResolver.h"
using android::gui::FocusRequest;
@@ -38,6 +36,11 @@ using android::gui::WindowInfoHandle;
namespace android::inputdispatcher {
+template <typename T>
+struct SpHash {
+ size_t operator()(const sp<T>& k) const { return std::hash<T*>()(k.get()); }
+};
+
sp<IBinder> FocusResolver::getFocusedWindowToken(int32_t displayId) const {
auto it = mFocusedWindowTokenByDisplay.find(displayId);
return it != mFocusedWindowTokenByDisplay.end() ? it->second.second : nullptr;
@@ -58,30 +61,30 @@ std::optional<FocusResolver::FocusChanges> FocusResolver::setInputWindows(
int32_t displayId, const std::vector<sp<WindowInfoHandle>>& windows) {
std::string removeFocusReason;
- // Check if the currently focused window is still focusable.
+ const std::optional<FocusRequest> request = getFocusRequest(displayId);
const sp<IBinder> currentFocus = getFocusedWindowToken(displayId);
- if (currentFocus) {
- Focusability result = isTokenFocusable(currentFocus, windows);
- if (result == Focusability::OK) {
- return std::nullopt;
- }
- removeFocusReason = ftl::enum_string(result);
- }
- // We don't have a focused window or the currently focused window is no longer focusable. Check
- // to see if we can grant focus to the window that previously requested focus.
- const std::optional<FocusRequest> request = getFocusRequest(displayId);
+ // Find the next focused token based on the latest FocusRequest. If the requested focus window
+ // cannot be focused, focus will be removed.
if (request) {
sp<IBinder> requestedFocus = request->token;
- const Focusability result = isTokenFocusable(requestedFocus, windows);
+ sp<WindowInfoHandle> resolvedFocusWindow;
+ Focusability result = getResolvedFocusWindow(requestedFocus, windows, resolvedFocusWindow);
+ if (result == Focusability::OK && resolvedFocusWindow->getToken() == currentFocus) {
+ return std::nullopt;
+ }
const Focusability previousResult = mLastFocusResultByDisplay[displayId];
mLastFocusResultByDisplay[displayId] = result;
if (result == Focusability::OK) {
+ LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow,
+ "Focused window should be non-null when result is OK!");
return updateFocusedWindow(displayId,
"Window became focusable. Previous reason: " +
ftl::enum_string(previousResult),
- requestedFocus, request->windowName);
+ resolvedFocusWindow->getToken(),
+ resolvedFocusWindow->getName());
}
+ removeFocusReason = ftl::enum_string(result);
}
// Focused window is no longer focusable and we don't have a suitable focus request to grant.
@@ -100,35 +103,18 @@ std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
return std::nullopt;
}
- // Handle conditional focus requests, i.e. requests that have a focused token. These requests
- // are not persistent. If the window is no longer focusable, we expect focus to go back to the
- // previously focused window.
- if (request.focusedToken) {
- if (currentFocus != request.focusedToken) {
- ALOGW("setFocusedWindow %s on display %" PRId32
- " ignored, reason: focusedToken %s is not focused",
- request.windowName.c_str(), displayId, request.focusedWindowName.c_str());
- return std::nullopt;
- }
- Focusability result = isTokenFocusable(request.token, windows);
- if (result == Focusability::OK) {
- return updateFocusedWindow(displayId, "setFocusedWindow with focus check",
- request.token, request.windowName);
- }
- ALOGW("setFocusedWindow %s on display %" PRId32 " ignored, reason: %s",
- request.windowName.c_str(), displayId, ftl::enum_string(result).c_str());
- return std::nullopt;
- }
-
- Focusability result = isTokenFocusable(request.token, windows);
+ sp<WindowInfoHandle> resolvedFocusWindow;
+ Focusability result = getResolvedFocusWindow(request.token, windows, resolvedFocusWindow);
// Update focus request. The focus resolver will always try to handle this request if there is
// no focused window on the display.
mFocusRequestByDisplay[displayId] = request;
mLastFocusResultByDisplay[displayId] = result;
if (result == Focusability::OK) {
- return updateFocusedWindow(displayId, "setFocusedWindow", request.token,
- request.windowName);
+ LOG_ALWAYS_FATAL_IF(!resolvedFocusWindow,
+ "Focused window should be non-null when result is OK!");
+ return updateFocusedWindow(displayId, "setFocusedWindow", resolvedFocusWindow->getToken(),
+ resolvedFocusWindow->getName());
}
// The requested window is not currently focusable. Wait for the window to become focusable
@@ -138,11 +124,43 @@ std::optional<FocusResolver::FocusChanges> FocusResolver::setFocusedWindow(
nullptr);
}
+FocusResolver::Focusability FocusResolver::getResolvedFocusWindow(
+ const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows,
+ sp<WindowInfoHandle>& outFocusableWindow) {
+ sp<IBinder> curFocusCandidate = token;
+ bool focusedWindowFound = false;
+
+ // Keep track of all windows reached to prevent a cyclical transferFocus request.
+ std::unordered_set<sp<IBinder>, SpHash<IBinder>> tokensReached;
+
+ while (curFocusCandidate != nullptr && tokensReached.count(curFocusCandidate) == 0) {
+ tokensReached.emplace(curFocusCandidate);
+ Focusability result = isTokenFocusable(curFocusCandidate, windows, outFocusableWindow);
+ if (result == Focusability::OK) {
+ LOG_ALWAYS_FATAL_IF(!outFocusableWindow,
+ "Focused window should be non-null when result is OK!");
+ focusedWindowFound = true;
+ // outFocusableWindow has been updated by isTokenFocusable to contain
+ // the window info for curFocusCandidate. See if we can grant focus
+ // to the token that it wants to transfer its focus to.
+ curFocusCandidate = outFocusableWindow->getInfo()->focusTransferTarget;
+ }
+
+ // If the initial token is not focusable, return early with the failed result.
+ if (!focusedWindowFound) {
+ return result;
+ }
+ }
+
+ return focusedWindowFound ? Focusability::OK : Focusability::NO_WINDOW;
+}
+
FocusResolver::Focusability FocusResolver::isTokenFocusable(
- const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows) {
+ const sp<IBinder>& token, const std::vector<sp<WindowInfoHandle>>& windows,
+ sp<WindowInfoHandle>& outFocusableWindow) {
bool allWindowsAreFocusable = true;
- bool visibleWindowFound = false;
bool windowFound = false;
+ sp<WindowInfoHandle> visibleWindowHandle = nullptr;
for (const sp<WindowInfoHandle>& window : windows) {
if (window->getToken() != token) {
continue;
@@ -150,7 +168,7 @@ FocusResolver::Focusability FocusResolver::isTokenFocusable(
windowFound = true;
if (!window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_VISIBLE)) {
// Check if at least a single window is visible.
- visibleWindowFound = true;
+ visibleWindowHandle = window;
}
if (window->getInfo()->inputConfig.test(gui::WindowInfo::InputConfig::NOT_FOCUSABLE)) {
// Check if all windows with the window token are focusable.
@@ -165,10 +183,12 @@ FocusResolver::Focusability FocusResolver::isTokenFocusable(
if (!allWindowsAreFocusable) {
return Focusability::NOT_FOCUSABLE;
}
- if (!visibleWindowFound) {
+ if (!visibleWindowHandle) {
return Focusability::NOT_VISIBLE;
}
+ // Only set the outFoundWindow if the window can be focused
+ outFocusableWindow = visibleWindowHandle;
return Focusability::OK;
}
diff --git a/services/inputflinger/dispatcher/FocusResolver.h b/services/inputflinger/dispatcher/FocusResolver.h
index 6d11a77aad..5bb157b7c6 100644
--- a/services/inputflinger/dispatcher/FocusResolver.h
+++ b/services/inputflinger/dispatcher/FocusResolver.h
@@ -92,7 +92,13 @@ private:
//
static Focusability isTokenFocusable(
const sp<IBinder>& token,
- const std::vector<sp<android::gui::WindowInfoHandle>>& windows);
+ const std::vector<sp<android::gui::WindowInfoHandle>>& windows,
+ sp<android::gui::WindowInfoHandle>& outFocusableWindow);
+
+ static FocusResolver::Focusability getResolvedFocusWindow(
+ const sp<IBinder>& token,
+ const std::vector<sp<android::gui::WindowInfoHandle>>& windows,
+ sp<android::gui::WindowInfoHandle>& outFocusableWindow);
// Focus tracking for keys, trackball, etc. A window token can be associated with one or
// more InputWindowHandles. If a window is mirrored, the window and its mirror will share
diff --git a/services/inputflinger/dispatcher/InjectionState.cpp b/services/inputflinger/dispatcher/InjectionState.cpp
index c2d3ad6b9d..f693dcf8e4 100644
--- a/services/inputflinger/dispatcher/InjectionState.cpp
+++ b/services/inputflinger/dispatcher/InjectionState.cpp
@@ -20,22 +20,10 @@
namespace android::inputdispatcher {
-InjectionState::InjectionState(const std::optional<int32_t>& targetUid)
- : refCount(1),
- targetUid(targetUid),
+InjectionState::InjectionState(const std::optional<gui::Uid>& targetUid, bool isAsync)
+ : targetUid(targetUid),
+ injectionIsAsync(isAsync),
injectionResult(android::os::InputEventInjectionResult::PENDING),
- injectionIsAsync(false),
pendingForegroundDispatches(0) {}
-InjectionState::~InjectionState() {}
-
-void InjectionState::release() {
- refCount -= 1;
- if (refCount == 0) {
- delete this;
- } else {
- ALOG_ASSERT(refCount > 0);
- }
-}
-
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InjectionState.h b/services/inputflinger/dispatcher/InjectionState.h
index 90cf150ac3..8225dec87a 100644
--- a/services/inputflinger/dispatcher/InjectionState.h
+++ b/services/inputflinger/dispatcher/InjectionState.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INJECTIONSTATE_H
-#define _UI_INPUT_INPUTDISPATCHER_INJECTIONSTATE_H
+#pragma once
#include <stdint.h>
#include "InputDispatcherInterface.h"
@@ -25,21 +24,13 @@ namespace android {
namespace inputdispatcher {
struct InjectionState {
- mutable int32_t refCount;
-
- std::optional<int32_t> targetUid;
+ const std::optional<gui::Uid> targetUid;
+ const bool injectionIsAsync; // set to true if injection is not waiting for the result
android::os::InputEventInjectionResult injectionResult; // initially PENDING
- bool injectionIsAsync; // set to true if injection is not waiting for the result
int32_t pendingForegroundDispatches; // the number of foreground dispatches in progress
- explicit InjectionState(const std::optional<int32_t>& targetUid);
- void release();
-
-private:
- ~InjectionState();
+ explicit InjectionState(const std::optional<gui::Uid>& targetUid, bool isAsync);
};
} // namespace inputdispatcher
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_INJECTIONSTATE_H
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index eb97a68a9a..67e48a266d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -20,13 +20,21 @@
#define LOG_NDEBUG 1
#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android/os/IInputConstants.h>
#include <binder/Binder.h>
+#include <com_android_input_flags.h>
#include <ftl/enum.h>
+#include <log/log_event_list.h>
+#if defined(__ANDROID__)
#include <gui/SurfaceComposerClient.h>
+#endif
#include <input/InputDevice.h>
+#include <input/PrintTools.h>
+#include <input/TraceTools.h>
+#include <openssl/mem.h>
#include <powermanager/PowerManager.h>
#include <unistd.h>
#include <utils/Trace.h>
@@ -39,6 +47,8 @@
#include <queue>
#include <sstream>
+#include "../InputDeviceMetricsSource.h"
+
#include "Connection.h"
#include "DebugConfig.h"
#include "InputDispatcher.h"
@@ -48,6 +58,8 @@
#define INDENT3 " "
#define INDENT4 " "
+using namespace android::ftl::flag_operators;
+using android::base::Error;
using android::base::HwTimeoutMultiplier;
using android::base::Result;
using android::base::StringPrintf;
@@ -56,10 +68,11 @@ using android::gui::FocusRequest;
using android::gui::TouchOcclusionMode;
using android::gui::WindowInfo;
using android::gui::WindowInfoHandle;
-using android::os::BlockUntrustedTouchesMode;
-using android::os::IInputConstants;
using android::os::InputEventInjectionResult;
using android::os::InputEventInjectionSync;
+namespace input_flags = com::android::input::flags;
+
+static const bool REMOVE_APP_SWITCH_DROPS = input_flags::remove_app_switch_drops();
namespace android::inputdispatcher {
@@ -107,66 +120,85 @@ constexpr int LOGTAG_INPUT_INTERACTION = 62000;
constexpr int LOGTAG_INPUT_FOCUS = 62001;
constexpr int LOGTAG_INPUT_CANCEL = 62003;
+const ui::Transform kIdentityTransform;
+
inline nsecs_t now() {
return systemTime(SYSTEM_TIME_MONOTONIC);
}
-inline const char* toString(bool value) {
- return value ? "true" : "false";
-}
-
-inline const std::string toString(const sp<IBinder>& binder) {
+inline const std::string binderToString(const sp<IBinder>& binder) {
if (binder == nullptr) {
return "<null>";
}
return StringPrintf("%p", binder.get());
}
-inline int32_t getMotionEventActionPointerIndex(int32_t action) {
- return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >>
- AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
+static std::string uidString(const gui::Uid& uid) {
+ return uid.toString();
}
-bool isValidKeyAction(int32_t action) {
+Result<void> checkKeyAction(int32_t action) {
switch (action) {
case AKEY_EVENT_ACTION_DOWN:
case AKEY_EVENT_ACTION_UP:
- return true;
+ return {};
default:
- return false;
+ return Error() << "Key event has invalid action code " << action;
}
}
-bool validateKeyEvent(int32_t action) {
- if (!isValidKeyAction(action)) {
- ALOGE("Key event has invalid action code 0x%x", action);
- return false;
- }
- return true;
+Result<void> validateKeyEvent(int32_t action) {
+ return checkKeyAction(action);
}
-bool isValidMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
- switch (action & AMOTION_EVENT_ACTION_MASK) {
+Result<void> checkMotionAction(int32_t action, int32_t actionButton, int32_t pointerCount) {
+ switch (MotionEvent::getActionMasked(action)) {
case AMOTION_EVENT_ACTION_DOWN:
- case AMOTION_EVENT_ACTION_UP:
- case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_UP: {
+ if (pointerCount != 1) {
+ return Error() << "invalid pointer count " << pointerCount;
+ }
+ return {};
+ }
case AMOTION_EVENT_ACTION_MOVE:
- case AMOTION_EVENT_ACTION_OUTSIDE:
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE:
- case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ case AMOTION_EVENT_ACTION_HOVER_EXIT: {
+ if (pointerCount < 1) {
+ return Error() << "invalid pointer count " << pointerCount;
+ }
+ return {};
+ }
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_OUTSIDE:
case AMOTION_EVENT_ACTION_SCROLL:
- return true;
+ return {};
case AMOTION_EVENT_ACTION_POINTER_DOWN:
case AMOTION_EVENT_ACTION_POINTER_UP: {
- int32_t index = getMotionEventActionPointerIndex(action);
- return index >= 0 && index < pointerCount;
+ const int32_t index = MotionEvent::getActionIndex(action);
+ if (index < 0) {
+ return Error() << "invalid index " << index << " for "
+ << MotionEvent::actionToString(action);
+ }
+ if (index >= pointerCount) {
+ return Error() << "invalid index " << index << " for pointerCount " << pointerCount;
+ }
+ if (pointerCount <= 1) {
+ return Error() << "invalid pointer count " << pointerCount << " for "
+ << MotionEvent::actionToString(action);
+ }
+ return {};
}
case AMOTION_EVENT_ACTION_BUTTON_PRESS:
- case AMOTION_EVENT_ACTION_BUTTON_RELEASE:
- return actionButton != 0;
+ case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
+ if (actionButton == 0) {
+ return Error() << "action button should be nonzero for "
+ << MotionEvent::actionToString(action);
+ }
+ return {};
+ }
default:
- return false;
+ return Error() << "invalid action " << action;
}
}
@@ -174,32 +206,50 @@ int64_t millis(std::chrono::nanoseconds t) {
return std::chrono::duration_cast<std::chrono::milliseconds>(t).count();
}
-bool validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount,
- const PointerProperties* pointerProperties) {
- if (!isValidMotionAction(action, actionButton, pointerCount)) {
- ALOGE("Motion event has invalid action code 0x%x", action);
- return false;
+Result<void> validateMotionEvent(int32_t action, int32_t actionButton, size_t pointerCount,
+ const PointerProperties* pointerProperties) {
+ Result<void> actionCheck = checkMotionAction(action, actionButton, pointerCount);
+ if (!actionCheck.ok()) {
+ return actionCheck;
}
if (pointerCount < 1 || pointerCount > MAX_POINTERS) {
- ALOGE("Motion event has invalid pointer count %zu; value must be between 1 and %zu.",
- pointerCount, MAX_POINTERS);
- return false;
+ return Error() << "Motion event has invalid pointer count " << pointerCount
+ << "; value must be between 1 and " << MAX_POINTERS << ".";
}
- BitSet32 pointerIdBits;
+ std::bitset<MAX_POINTER_ID + 1> pointerIdBits;
for (size_t i = 0; i < pointerCount; i++) {
int32_t id = pointerProperties[i].id;
if (id < 0 || id > MAX_POINTER_ID) {
- ALOGE("Motion event has invalid pointer id %d; value must be between 0 and %d", id,
- MAX_POINTER_ID);
- return false;
+ return Error() << "Motion event has invalid pointer id " << id
+ << "; value must be between 0 and " << MAX_POINTER_ID;
}
- if (pointerIdBits.hasBit(id)) {
- ALOGE("Motion event has duplicate pointer id %d", id);
- return false;
+ if (pointerIdBits.test(id)) {
+ return Error() << "Motion event has duplicate pointer id " << id;
+ }
+ pointerIdBits.set(id);
+ }
+ return {};
+}
+
+Result<void> validateInputEvent(const InputEvent& event) {
+ switch (event.getType()) {
+ case InputEventType::KEY: {
+ const KeyEvent& key = static_cast<const KeyEvent&>(event);
+ const int32_t action = key.getAction();
+ return validateKeyEvent(action);
+ }
+ case InputEventType::MOTION: {
+ const MotionEvent& motion = static_cast<const MotionEvent&>(event);
+ const int32_t action = motion.getAction();
+ const size_t pointerCount = motion.getPointerCount();
+ const PointerProperties* pointerProperties = motion.getPointerProperties();
+ const int32_t actionButton = motion.getActionButton();
+ return validateMotionEvent(action, actionButton, pointerCount, pointerProperties);
+ }
+ default: {
+ return {};
}
- pointerIdBits.markBit(id);
}
- return true;
}
std::string dumpRegion(const Region& region) {
@@ -223,7 +273,8 @@ std::string dumpRegion(const Region& region) {
return dump;
}
-std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTime) {
+std::string dumpQueue(const std::deque<std::unique_ptr<DispatchEntry>>& queue,
+ nsecs_t currentTime) {
constexpr size_t maxEntries = 50; // max events to print
constexpr size_t skipBegin = maxEntries / 2;
const size_t skipEnd = queue.size() - maxEntries / 2;
@@ -240,9 +291,8 @@ std::string dumpQueue(const std::deque<DispatchEntry*>& queue, nsecs_t currentTi
}
dump.append(INDENT4);
dump += entry.eventEntry->getDescription();
- dump += StringPrintf(", seq=%" PRIu32
- ", targetFlags=0x%08x, resolvedAction=%d, age=%" PRId64 "ms",
- entry.seq, entry.targetFlags, entry.resolvedAction,
+ dump += StringPrintf(", seq=%" PRIu32 ", targetFlags=%s, age=%" PRId64 "ms", entry.seq,
+ entry.targetFlags.string().c_str(),
ns2ms(currentTime - entry.eventEntry->eventTime));
if (entry.deliveryTime != 0) {
// This entry was delivered, so add information on how long we've been waiting
@@ -287,9 +337,20 @@ bool haveSameApplicationToken(const WindowInfo* first, const WindowInfo* second)
first->applicationInfo.token == second->applicationInfo.token;
}
-std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarget,
- std::shared_ptr<EventEntry> eventEntry,
- int32_t inputTargetFlags) {
+template <typename T>
+size_t firstMarkedBit(T set) {
+ // TODO: replace with std::countr_zero from <bit> when that's available
+ LOG_ALWAYS_FATAL_IF(set.none());
+ size_t i = 0;
+ while (!set.test(i)) {
+ i++;
+ }
+ return i;
+}
+
+std::unique_ptr<DispatchEntry> createDispatchEntry(
+ const InputTarget& inputTarget, std::shared_ptr<const EventEntry> eventEntry,
+ ftl::Flags<InputTarget::Flags> inputTargetFlags) {
if (inputTarget.useDefaultPointerTransform()) {
const ui::Transform& transform = inputTarget.getDefaultPointerTransform();
return std::make_unique<DispatchEntry>(eventEntry, inputTargetFlags, transform,
@@ -301,17 +362,17 @@ std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarge
const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
std::vector<PointerCoords> pointerCoords;
- pointerCoords.resize(motionEntry.pointerCount);
+ pointerCoords.resize(motionEntry.getPointerCount());
// Use the first pointer information to normalize all other pointers. This could be any pointer
// as long as all other pointers are normalized to the same value and the final DispatchEntry
// uses the transform for the normalized pointer.
const ui::Transform& firstPointerTransform =
- inputTarget.pointerTransforms[inputTarget.pointerIds.firstMarkedBit()];
+ inputTarget.pointerTransforms[firstMarkedBit(inputTarget.pointerIds)];
ui::Transform inverseFirstTransform = firstPointerTransform.inverse();
// Iterate through all pointers in the event to normalize against the first.
- for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.pointerCount; pointerIndex++) {
+ for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount(); pointerIndex++) {
const PointerProperties& pointerProperties = motionEntry.pointerProperties[pointerIndex];
uint32_t pointerId = uint32_t(pointerProperties.id);
const ui::Transform& currTransform = inputTarget.pointerTransforms[pointerId];
@@ -326,22 +387,17 @@ std::unique_ptr<DispatchEntry> createDispatchEntry(const InputTarget& inputTarge
}
std::unique_ptr<MotionEntry> combinedMotionEntry =
- std::make_unique<MotionEntry>(motionEntry.id, motionEntry.eventTime,
- motionEntry.deviceId, motionEntry.source,
- motionEntry.displayId, motionEntry.policyFlags,
- motionEntry.action, motionEntry.actionButton,
- motionEntry.flags, motionEntry.metaState,
- motionEntry.buttonState, motionEntry.classification,
- motionEntry.edgeFlags, motionEntry.xPrecision,
- motionEntry.yPrecision, motionEntry.xCursorPosition,
- motionEntry.yCursorPosition, motionEntry.downTime,
- motionEntry.pointerCount, motionEntry.pointerProperties,
- pointerCoords.data());
-
- if (motionEntry.injectionState) {
- combinedMotionEntry->injectionState = motionEntry.injectionState;
- combinedMotionEntry->injectionState->refCount += 1;
- }
+ std::make_unique<MotionEntry>(motionEntry.id, motionEntry.injectionState,
+ motionEntry.eventTime, motionEntry.deviceId,
+ motionEntry.source, motionEntry.displayId,
+ motionEntry.policyFlags, motionEntry.action,
+ motionEntry.actionButton, motionEntry.flags,
+ motionEntry.metaState, motionEntry.buttonState,
+ motionEntry.classification, motionEntry.edgeFlags,
+ motionEntry.xPrecision, motionEntry.yPrecision,
+ motionEntry.xCursorPosition, motionEntry.yCursorPosition,
+ motionEntry.downTime, motionEntry.pointerProperties,
+ pointerCoords);
std::unique_ptr<DispatchEntry> dispatchEntry =
std::make_unique<DispatchEntry>(std::move(combinedMotionEntry), inputTargetFlags,
@@ -394,10 +450,6 @@ bool shouldReportMetricsForConnection(const Connection& connection) {
bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry, const Connection& connection) {
const EventEntry& eventEntry = *dispatchEntry.eventEntry;
const int32_t& inputEventId = eventEntry.id;
- if (inputEventId != dispatchEntry.resolvedEventId) {
- // Event was transmuted
- return false;
- }
if (inputEventId == android::os::IInputConstants::INVALID_INPUT_EVENT_ID) {
return false;
}
@@ -433,8 +485,8 @@ bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry, const Connect
*/
bool isConnectionResponsive(const Connection& connection) {
const nsecs_t currentTime = now();
- for (const DispatchEntry* entry : connection.waitQueue) {
- if (entry->timeoutTime < currentTime) {
+ for (const auto& dispatchEntry : connection.waitQueue) {
+ if (dispatchEntry->timeoutTime < currentTime) {
return false;
}
}
@@ -444,14 +496,14 @@ bool isConnectionResponsive(const Connection& connection) {
// Returns true if the event type passed as argument represents a user activity.
bool isUserActivityEvent(const EventEntry& eventEntry) {
switch (eventEntry.type) {
+ case EventEntry::Type::CONFIGURATION_CHANGED:
+ case EventEntry::Type::DEVICE_RESET:
+ case EventEntry::Type::DRAG:
case EventEntry::Type::FOCUS:
case EventEntry::Type::POINTER_CAPTURE_CHANGED:
- case EventEntry::Type::DRAG:
- case EventEntry::Type::TOUCH_MODE_CHANGED:
case EventEntry::Type::SENSOR:
- case EventEntry::Type::CONFIGURATION_CHANGED:
+ case EventEntry::Type::TOUCH_MODE_CHANGED:
return false;
- case EventEntry::Type::DEVICE_RESET:
case EventEntry::Type::KEY:
case EventEntry::Type::MOTION:
return true;
@@ -459,8 +511,8 @@ bool isUserActivityEvent(const EventEntry& eventEntry) {
}
// Returns true if the given window can accept pointer events at the given display location.
-bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, int32_t x, int32_t y,
- bool isStylus) {
+bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, float x, float y,
+ bool isStylus, const ui::Transform& displayTransform) {
const auto inputConfig = windowInfo.inputConfig;
if (windowInfo.displayId != displayId ||
inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
@@ -470,7 +522,17 @@ bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, int32
if (inputConfig.test(WindowInfo::InputConfig::NOT_TOUCHABLE) && !windowCanInterceptTouch) {
return false;
}
- if (!windowInfo.touchableRegionContainsPoint(x, y)) {
+
+ // Window Manager works in the logical display coordinate space. When it specifies bounds for a
+ // window as (l, t, r, b), the range of x in [l, r) and y in [t, b) are considered to be inside
+ // the window. Points on the right and bottom edges should not be inside the window, so we need
+ // to be careful about performing a hit test when the display is rotated, since the "right" and
+ // "bottom" of the window will be different in the display (un-rotated) space compared to in the
+ // logical display in which WM determined the bounds. Perform the hit test in the logical
+ // display space to ensure these edges are considered correctly in all orientations.
+ const auto touchableRegion = displayTransform.transform(windowInfo.touchableRegion);
+ const auto p = displayTransform.transform(x, y);
+ if (!touchableRegion.contains(std::floor(p.x), std::floor(p.y))) {
return false;
}
return true;
@@ -478,22 +540,21 @@ bool windowAcceptsTouchAt(const WindowInfo& windowInfo, int32_t displayId, int32
bool isPointerFromStylus(const MotionEntry& entry, int32_t pointerIndex) {
return isFromSource(entry.source, AINPUT_SOURCE_STYLUS) &&
- (entry.pointerProperties[pointerIndex].toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS ||
- entry.pointerProperties[pointerIndex].toolType == AMOTION_EVENT_TOOL_TYPE_ERASER);
+ isStylusToolType(entry.pointerProperties[pointerIndex].toolType);
}
-// Determines if the given window can be targeted as InputTarget::FLAG_FOREGROUND.
+// Determines if the given window can be targeted as InputTarget::Flags::FOREGROUND.
// Foreground events are only sent to "foreground targetable" windows, but not all gestures sent to
// such window are necessarily targeted with the flag. For example, an event with ACTION_OUTSIDE can
// be sent to such a window, but it is not a foreground event and doesn't use
-// InputTarget::FLAG_FOREGROUND.
+// InputTarget::Flags::FOREGROUND.
bool canReceiveForegroundTouches(const WindowInfo& info) {
// A non-touchable window can still receive touch events (e.g. in the case of
// STYLUS_INTERCEPTOR), so prevent such windows from receiving foreground events for touches.
return !info.inputConfig.test(gui::WindowInfo::InputConfig::NOT_TOUCHABLE) && !info.isSpy();
}
-bool isWindowOwnedBy(const sp<WindowInfoHandle>& windowHandle, int32_t pid, int32_t uid) {
+bool isWindowOwnedBy(const sp<WindowInfoHandle>& windowHandle, gui::Pid pid, gui::Uid uid) {
if (windowHandle == nullptr) {
return false;
}
@@ -513,57 +574,201 @@ std::optional<std::string> verifyTargetedInjection(const sp<WindowInfoHandle>& w
// The event was not injected, or the injected event does not target a window.
return {};
}
- const int32_t uid = *entry.injectionState->targetUid;
+ const auto uid = *entry.injectionState->targetUid;
if (window == nullptr) {
- return StringPrintf("No valid window target for injection into uid %d.", uid);
+ return StringPrintf("No valid window target for injection into uid %s.",
+ uid.toString().c_str());
}
if (entry.injectionState->targetUid != window->getInfo()->ownerUid) {
- return StringPrintf("Injected event targeted at uid %d would be dispatched to window '%s' "
- "owned by uid %d.",
- uid, window->getName().c_str(), window->getInfo()->ownerUid);
+ return StringPrintf("Injected event targeted at uid %s would be dispatched to window '%s' "
+ "owned by uid %s.",
+ uid.toString().c_str(), window->getName().c_str(),
+ window->getInfo()->ownerUid.toString().c_str());
}
return {};
}
+std::pair<float, float> resolveTouchedPosition(const MotionEntry& entry) {
+ const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
+ // Always dispatch mouse events to cursor position.
+ if (isFromMouse) {
+ return {entry.xCursorPosition, entry.yCursorPosition};
+ }
+
+ const int32_t pointerIndex = MotionEvent::getActionIndex(entry.action);
+ return {entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X),
+ entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y)};
+}
+
+std::optional<nsecs_t> getDownTime(const EventEntry& eventEntry) {
+ if (eventEntry.type == EventEntry::Type::KEY) {
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
+ return keyEntry.downTime;
+ } else if (eventEntry.type == EventEntry::Type::MOTION) {
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+ return motionEntry.downTime;
+ }
+ return std::nullopt;
+}
+
+/**
+ * Compare the old touch state to the new touch state, and generate the corresponding touched
+ * windows (== input targets).
+ * If a window had the hovering pointer, but now it doesn't, produce HOVER_EXIT for that window.
+ * If the pointer just entered the new window, produce HOVER_ENTER.
+ * For pointers remaining in the window, produce HOVER_MOVE.
+ */
+std::vector<TouchedWindow> getHoveringWindowsLocked(const TouchState* oldState,
+ const TouchState& newTouchState,
+ const MotionEntry& entry) {
+ std::vector<TouchedWindow> out;
+ const int32_t maskedAction = MotionEvent::getActionMasked(entry.action);
+
+ if (maskedAction == AMOTION_EVENT_ACTION_SCROLL) {
+ // ACTION_SCROLL events should not affect the hovering pointer dispatch
+ return {};
+ }
+
+ // We should consider all hovering pointers here. But for now, just use the first one
+ const int32_t pointerId = entry.pointerProperties[0].id;
+
+ std::set<sp<WindowInfoHandle>> oldWindows;
+ if (oldState != nullptr) {
+ oldWindows = oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+ }
+
+ std::set<sp<WindowInfoHandle>> newWindows =
+ newTouchState.getWindowsWithHoveringPointer(entry.deviceId, pointerId);
+
+ // If the pointer is no longer in the new window set, send HOVER_EXIT.
+ for (const sp<WindowInfoHandle>& oldWindow : oldWindows) {
+ if (newWindows.find(oldWindow) == newWindows.end()) {
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = oldWindow;
+ touchedWindow.dispatchMode = InputTarget::DispatchMode::HOVER_EXIT;
+ out.push_back(touchedWindow);
+ }
+ }
+
+ for (const sp<WindowInfoHandle>& newWindow : newWindows) {
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = newWindow;
+ if (oldWindows.find(newWindow) == oldWindows.end()) {
+ // Any windows that have this pointer now, and didn't have it before, should get
+ // HOVER_ENTER
+ touchedWindow.dispatchMode = InputTarget::DispatchMode::HOVER_ENTER;
+ } else {
+ // This pointer was already sent to the window. Use ACTION_HOVER_MOVE.
+ if (CC_UNLIKELY(maskedAction != AMOTION_EVENT_ACTION_HOVER_MOVE)) {
+ android::base::LogSeverity severity = android::base::LogSeverity::FATAL;
+ if (!input_flags::a11y_crash_on_inconsistent_event_stream() &&
+ entry.flags & AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT) {
+ // The Accessibility injected touch exploration event stream
+ // has known inconsistencies, so log ERROR instead of
+ // crashing the device with FATAL.
+ severity = android::base::LogSeverity::ERROR;
+ }
+ LOG(severity) << "Expected ACTION_HOVER_MOVE instead of " << entry.getDescription();
+ }
+ touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
+ }
+ touchedWindow.addHoveringPointer(entry.deviceId, pointerId);
+ if (canReceiveForegroundTouches(*newWindow->getInfo())) {
+ touchedWindow.targetFlags |= InputTarget::Flags::FOREGROUND;
+ }
+ out.push_back(touchedWindow);
+ }
+ return out;
+}
+
+template <typename T>
+std::vector<T>& operator+=(std::vector<T>& left, const std::vector<T>& right) {
+ left.insert(left.end(), right.begin(), right.end());
+ return left;
+}
+
+// Filter windows in a TouchState and targets in a vector to remove untrusted windows/targets from
+// both.
+void filterUntrustedTargets(TouchState& touchState, std::vector<InputTarget>& targets) {
+ std::erase_if(touchState.windows, [&](const TouchedWindow& window) {
+ if (!window.windowHandle->getInfo()->inputConfig.test(
+ WindowInfo::InputConfig::TRUSTED_OVERLAY)) {
+ // In addition to TouchState, erase this window from the input targets! We don't have a
+ // good way to do this today except by adding a nested loop.
+ // TODO(b/282025641): simplify this code once InputTargets are being identified
+ // separately from TouchedWindows.
+ std::erase_if(targets, [&](const InputTarget& target) {
+ return target.inputChannel->getConnectionToken() == window.windowHandle->getToken();
+ });
+ return true;
+ }
+ return false;
+ });
+}
+
+/**
+ * In general, touch should be always split between windows. Some exceptions:
+ * 1. Don't split touch if all of the below is true:
+ * (a) we have an active pointer down *and*
+ * (b) a new pointer is going down that's from the same device *and*
+ * (c) the window that's receiving the current pointer does not support split touch.
+ * 2. Don't split mouse events
+ */
+bool shouldSplitTouch(const TouchState& touchState, const MotionEntry& entry) {
+ if (isFromSource(entry.source, AINPUT_SOURCE_MOUSE)) {
+ // We should never split mouse events
+ return false;
+ }
+ for (const TouchedWindow& touchedWindow : touchState.windows) {
+ if (touchedWindow.windowHandle->getInfo()->isSpy()) {
+ // Spy windows should not affect whether or not touch is split.
+ continue;
+ }
+ if (touchedWindow.windowHandle->getInfo()->supportsSplitTouch()) {
+ continue;
+ }
+ if (touchedWindow.windowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::IS_WALLPAPER)) {
+ // Wallpaper window should not affect whether or not touch is split
+ continue;
+ }
+
+ if (touchedWindow.hasTouchingPointers(entry.deviceId)) {
+ return false;
+ }
+ }
+ return true;
+}
+
} // namespace
// --- InputDispatcher ---
-InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
- : InputDispatcher(policy, STALE_EVENT_TIMEOUT) {}
-
-InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy,
- std::chrono::nanoseconds staleEventTimeout)
+InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
: mPolicy(policy),
mPendingEvent(nullptr),
mLastDropReason(DropReason::NOT_DROPPED),
mIdGenerator(IdGenerator::Source::INPUT_DISPATCHER),
mAppSwitchSawKeyDown(false),
- mAppSwitchDueTime(LONG_LONG_MAX),
+ mAppSwitchDueTime(LLONG_MAX),
mNextUnblockedEvent(nullptr),
mMonitorDispatchingTimeout(DEFAULT_INPUT_DISPATCHING_TIMEOUT),
mDispatchEnabled(false),
mDispatchFrozen(false),
mInputFilterEnabled(false),
- // mInTouchMode will be initialized by the WindowManager to the default device config.
- // To avoid leaking stack in case that call never comes, and for tests,
- // initialize it here anyways.
- mInTouchMode(kDefaultInTouchMode),
mMaximumObscuringOpacityForTouch(1.0f),
mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
mWindowTokenWithPointerCapture(nullptr),
- mStaleEventTimeout(staleEventTimeout),
mLatencyAggregator(),
mLatencyTracker(&mLatencyAggregator) {
- mLooper = new Looper(false);
+ mLooper = sp<Looper>::make(false);
mReporter = createInputReporter();
- mWindowInfoListener = new DispatcherWindowListener(*this);
+ mWindowInfoListener = sp<DispatcherWindowListener>::make(*this);
+#if defined(__ANDROID__)
SurfaceComposerClient::getDefault()->addWindowInfosListener(mWindowInfoListener);
-
+#endif
mKeyRepeatState.lastKeyEntry = nullptr;
-
- policy->getDispatcherConfiguration(&mConfig);
}
InputDispatcher::~InputDispatcher() {
@@ -575,9 +780,8 @@ InputDispatcher::~InputDispatcher() {
mCommandQueue.clear();
while (!mConnectionsByToken.empty()) {
- sp<Connection> connection = mConnectionsByToken.begin()->second;
- removeInputChannelLocked(connection->inputChannel->getConnectionToken(),
- false /* notify */);
+ std::shared_ptr<Connection> connection = mConnectionsByToken.begin()->second;
+ removeInputChannelLocked(connection->inputChannel->getConnectionToken(), /*notify=*/false);
}
}
@@ -600,7 +804,7 @@ status_t InputDispatcher::stop() {
}
void InputDispatcher::dispatchOnce() {
- nsecs_t nextWakeupTime = LONG_LONG_MAX;
+ nsecs_t nextWakeupTime = LLONG_MAX;
{ // acquire lock
std::scoped_lock _l(mLock);
mDispatcherIsAlive.notify_all();
@@ -614,7 +818,7 @@ void InputDispatcher::dispatchOnce() {
// Run all pending commands if there are any.
// If any commands were run then force the next poll to wake up immediately.
if (runCommandsLockedInterruptable()) {
- nextWakeupTime = LONG_LONG_MIN;
+ nextWakeupTime = LLONG_MIN;
}
// If we are still waiting for ack on some events,
@@ -624,7 +828,7 @@ void InputDispatcher::dispatchOnce() {
// We are about to enter an infinitely long sleep, because we have no commands or
// pending or queued events
- if (nextWakeupTime == LONG_LONG_MAX) {
+ if (nextWakeupTime == LLONG_MAX) {
mDispatcherEnteredIdle.notify_all();
}
} // release lock
@@ -669,14 +873,14 @@ void InputDispatcher::processNoFocusedWindowAnrLocked() {
*/
nsecs_t InputDispatcher::processAnrsLocked() {
const nsecs_t currentTime = now();
- nsecs_t nextAnrCheck = LONG_LONG_MAX;
+ nsecs_t nextAnrCheck = LLONG_MAX;
// Check if we are waiting for a focused window to appear. Raise ANR if waited too long
if (mNoFocusedWindowTimeoutTime.has_value() && mAwaitedFocusedApplication != nullptr) {
if (currentTime >= *mNoFocusedWindowTimeoutTime) {
processNoFocusedWindowAnrLocked();
mAwaitedFocusedApplication.reset();
mNoFocusedWindowTimeoutTime = std::nullopt;
- return LONG_LONG_MIN;
+ return LLONG_MIN;
} else {
// Keep waiting. We will drop the event when mNoFocusedWindowTimeoutTime comes.
nextAnrCheck = *mNoFocusedWindowTimeoutTime;
@@ -690,7 +894,7 @@ nsecs_t InputDispatcher::processAnrsLocked() {
}
// If we reached here, we have an unresponsive connection.
- sp<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
+ std::shared_ptr<Connection> connection = getConnectionLocked(mAnrTracker.firstToken());
if (connection == nullptr) {
ALOGE("Could not find connection for entry %" PRId64, mAnrTracker.firstTimeout());
return nextAnrCheck;
@@ -699,11 +903,11 @@ nsecs_t InputDispatcher::processAnrsLocked() {
// Stop waking up for this unresponsive connection
mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
onAnrLocked(connection);
- return LONG_LONG_MIN;
+ return LLONG_MIN;
}
std::chrono::nanoseconds InputDispatcher::getDispatchingTimeoutLocked(
- const sp<Connection>& connection) {
+ const std::shared_ptr<Connection>& connection) {
if (connection->monitor) {
return mMonitorDispatchingTimeout;
}
@@ -736,20 +940,25 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
// Optimize latency of app switches.
// Essentially we start a short timeout when an app switch key (HOME / ENDCALL) has
// been pressed. When it expires, we preempt dispatch and drop all other pending events.
- bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;
- if (mAppSwitchDueTime < *nextWakeupTime) {
- *nextWakeupTime = mAppSwitchDueTime;
+ bool isAppSwitchDue;
+ if (!REMOVE_APP_SWITCH_DROPS) {
+ isAppSwitchDue = mAppSwitchDueTime <= currentTime;
+ if (mAppSwitchDueTime < *nextWakeupTime) {
+ *nextWakeupTime = mAppSwitchDueTime;
+ }
}
// Ready to start a new event.
// If we don't already have a pending event, go grab one.
if (!mPendingEvent) {
if (mInboundQueue.empty()) {
- if (isAppSwitchDue) {
- // The inbound queue is empty so the app switch key we were waiting
- // for will never arrive. Stop waiting for it.
- resetPendingAppSwitchLocked(false);
- isAppSwitchDue = false;
+ if (!REMOVE_APP_SWITCH_DROPS) {
+ if (isAppSwitchDue) {
+ // The inbound queue is empty so the app switch key we were waiting
+ // for will never arrive. Stop waiting for it.
+ resetPendingAppSwitchLocked(false);
+ isAppSwitchDue = false;
+ }
}
// Synthesize a key repeat if appropriate.
@@ -813,8 +1022,8 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
}
case EventEntry::Type::FOCUS: {
- std::shared_ptr<FocusEntry> typedEntry =
- std::static_pointer_cast<FocusEntry>(mPendingEvent);
+ std::shared_ptr<const FocusEntry> typedEntry =
+ std::static_pointer_cast<const FocusEntry>(mPendingEvent);
dispatchFocusLocked(currentTime, typedEntry);
done = true;
dropReason = DropReason::NOT_DROPPED; // focus events are never dropped
@@ -822,7 +1031,7 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
}
case EventEntry::Type::TOUCH_MODE_CHANGED: {
- const auto typedEntry = std::static_pointer_cast<TouchModeEntry>(mPendingEvent);
+ const auto typedEntry = std::static_pointer_cast<const TouchModeEntry>(mPendingEvent);
dispatchTouchModeChangeLocked(currentTime, typedEntry);
done = true;
dropReason = DropReason::NOT_DROPPED; // touch mode events are never dropped
@@ -831,28 +1040,31 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
case EventEntry::Type::POINTER_CAPTURE_CHANGED: {
const auto typedEntry =
- std::static_pointer_cast<PointerCaptureChangedEntry>(mPendingEvent);
+ std::static_pointer_cast<const PointerCaptureChangedEntry>(mPendingEvent);
dispatchPointerCaptureChangedLocked(currentTime, typedEntry, dropReason);
done = true;
break;
}
case EventEntry::Type::DRAG: {
- std::shared_ptr<DragEntry> typedEntry =
- std::static_pointer_cast<DragEntry>(mPendingEvent);
+ std::shared_ptr<const DragEntry> typedEntry =
+ std::static_pointer_cast<const DragEntry>(mPendingEvent);
dispatchDragLocked(currentTime, typedEntry);
done = true;
break;
}
case EventEntry::Type::KEY: {
- std::shared_ptr<KeyEntry> keyEntry = std::static_pointer_cast<KeyEntry>(mPendingEvent);
- if (isAppSwitchDue) {
- if (isAppSwitchKeyEvent(*keyEntry)) {
- resetPendingAppSwitchLocked(true);
- isAppSwitchDue = false;
- } else if (dropReason == DropReason::NOT_DROPPED) {
- dropReason = DropReason::APP_SWITCH;
+ std::shared_ptr<const KeyEntry> keyEntry =
+ std::static_pointer_cast<const KeyEntry>(mPendingEvent);
+ if (!REMOVE_APP_SWITCH_DROPS) {
+ if (isAppSwitchDue) {
+ if (isAppSwitchKeyEvent(*keyEntry)) {
+ resetPendingAppSwitchLocked(true);
+ isAppSwitchDue = false;
+ } else if (dropReason == DropReason::NOT_DROPPED) {
+ dropReason = DropReason::APP_SWITCH;
+ }
}
}
if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *keyEntry)) {
@@ -866,26 +1078,42 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
}
case EventEntry::Type::MOTION: {
- std::shared_ptr<MotionEntry> motionEntry =
- std::static_pointer_cast<MotionEntry>(mPendingEvent);
- if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
- dropReason = DropReason::APP_SWITCH;
+ std::shared_ptr<const MotionEntry> motionEntry =
+ std::static_pointer_cast<const MotionEntry>(mPendingEvent);
+ if (!REMOVE_APP_SWITCH_DROPS) {
+ if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DropReason::APP_SWITCH;
+ }
}
if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
- dropReason = DropReason::STALE;
+ // The event is stale. However, only drop stale events if there isn't an ongoing
+ // gesture. That would allow us to complete the processing of the current stroke.
+ const auto touchStateIt = mTouchStatesByDisplay.find(motionEntry->displayId);
+ if (touchStateIt != mTouchStatesByDisplay.end()) {
+ const TouchState& touchState = touchStateIt->second;
+ if (!touchState.hasTouchingPointers(motionEntry->deviceId) &&
+ !touchState.hasHoveringPointers(motionEntry->deviceId)) {
+ dropReason = DropReason::STALE;
+ }
+ }
}
if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
- dropReason = DropReason::BLOCKED;
+ if (!isFromSource(motionEntry->source, AINPUT_SOURCE_CLASS_POINTER)) {
+ // Only drop events that are focus-dispatched.
+ dropReason = DropReason::BLOCKED;
+ }
}
done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
break;
}
case EventEntry::Type::SENSOR: {
- std::shared_ptr<SensorEntry> sensorEntry =
- std::static_pointer_cast<SensorEntry>(mPendingEvent);
- if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
- dropReason = DropReason::APP_SWITCH;
+ std::shared_ptr<const SensorEntry> sensorEntry =
+ std::static_pointer_cast<const SensorEntry>(mPendingEvent);
+ if (!REMOVE_APP_SWITCH_DROPS) {
+ if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
+ dropReason = DropReason::APP_SWITCH;
+ }
}
// Sensor timestamps use SYSTEM_TIME_BOOTTIME time base, so we can't use
// 'currentTime' here, get SYSTEM_TIME_BOOTTIME instead.
@@ -906,12 +1134,12 @@ void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
mLastDropReason = dropReason;
releasePendingEventLocked();
- *nextWakeupTime = LONG_LONG_MIN; // force next poll to wake up immediately
+ *nextWakeupTime = LLONG_MIN; // force next poll to wake up immediately
}
}
bool InputDispatcher::isStaleEvent(nsecs_t currentTime, const EventEntry& entry) {
- return std::chrono::nanoseconds(currentTime - entry.eventTime) >= mStaleEventTimeout;
+ return mPolicy.isStaleEvent(currentTime, entry.eventTime);
}
/**
@@ -927,15 +1155,12 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt
// If the application takes too long to catch up then we drop all events preceding
// the touch into the other window.
if (isPointerDownEvent && mAwaitedFocusedApplication != nullptr) {
- int32_t displayId = motionEntry.displayId;
- int32_t x = static_cast<int32_t>(
- motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
- int32_t y = static_cast<int32_t>(
- motionEntry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
+ const int32_t displayId = motionEntry.displayId;
+ const auto [x, y] = resolveTouchedPosition(motionEntry);
+ const bool isStylus = isPointerFromStylus(motionEntry, /*pointerIndex=*/0);
- const bool isStylus = isPointerFromStylus(motionEntry, 0 /*pointerIndex*/);
sp<WindowInfoHandle> touchedWindowHandle =
- findTouchedWindowAtLocked(displayId, x, y, nullptr, isStylus);
+ findTouchedWindowAtLocked(displayId, x, y, isStylus);
if (touchedWindowHandle != nullptr &&
touchedWindowHandle->getApplicationToken() !=
mAwaitedFocusedApplication->getApplicationToken()) {
@@ -950,7 +1175,8 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt
const std::vector<sp<WindowInfoHandle>> touchedSpies =
findTouchedSpyWindowsAtLocked(displayId, x, y, isStylus);
for (const auto& windowHandle : touchedSpies) {
- const sp<Connection> connection = getConnectionLocked(windowHandle->getToken());
+ const std::shared_ptr<Connection> connection =
+ getConnectionLocked(windowHandle->getToken());
if (connection != nullptr && connection->responsive) {
// This spy window could take more input. Drop all events preceding this
// event, so that the spy window can get a chance to receive the stream.
@@ -978,7 +1204,7 @@ bool InputDispatcher::shouldPruneInboundQueueLocked(const MotionEntry& motionEnt
bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
bool needWake = mInboundQueue.empty();
mInboundQueue.push_back(std::move(newEntry));
- EventEntry& entry = *(mInboundQueue.back());
+ const EventEntry& entry = *(mInboundQueue.back());
traceInboundQueueLengthLocked();
switch (entry.type) {
@@ -989,31 +1215,33 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newE
// If the application takes too long to catch up then we drop all events preceding
// the app switch key.
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
- if (isAppSwitchKeyEvent(keyEntry)) {
- if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
- mAppSwitchSawKeyDown = true;
- } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
- if (mAppSwitchSawKeyDown) {
- if (DEBUG_APP_SWITCH) {
- ALOGD("App switch is pending!");
+
+ if (!REMOVE_APP_SWITCH_DROPS) {
+ if (isAppSwitchKeyEvent(keyEntry)) {
+ if (keyEntry.action == AKEY_EVENT_ACTION_DOWN) {
+ mAppSwitchSawKeyDown = true;
+ } else if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
+ if (mAppSwitchSawKeyDown) {
+ if (DEBUG_APP_SWITCH) {
+ ALOGD("App switch is pending!");
+ }
+ mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
+ mAppSwitchSawKeyDown = false;
+ needWake = true;
}
- mAppSwitchDueTime = keyEntry.eventTime + APP_SWITCH_TIMEOUT;
- mAppSwitchSawKeyDown = false;
- needWake = true;
}
}
}
-
// If a new up event comes in, and the pending event with same key code has been asked
// to try again later because of the policy. We have to reset the intercept key wake up
// time for it may have been handled in the policy and could be dropped.
if (keyEntry.action == AKEY_EVENT_ACTION_UP && mPendingEvent &&
mPendingEvent->type == EventEntry::Type::KEY) {
- KeyEntry& pendingKey = static_cast<KeyEntry&>(*mPendingEvent);
+ const KeyEntry& pendingKey = static_cast<const KeyEntry&>(*mPendingEvent);
if (pendingKey.keyCode == keyEntry.keyCode &&
pendingKey.interceptKeyResult ==
- KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
- pendingKey.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
+ pendingKey.interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
pendingKey.interceptKeyWakeupTime = 0;
needWake = true;
}
@@ -1024,7 +1252,7 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newE
case EventEntry::Type::MOTION: {
LOG_ALWAYS_FATAL_IF((entry.policyFlags & POLICY_FLAG_TRUSTED) == 0,
"Unexpected untrusted event.");
- if (shouldPruneInboundQueueLocked(static_cast<MotionEntry&>(entry))) {
+ if (shouldPruneInboundQueueLocked(static_cast<const MotionEntry&>(entry))) {
mNextUnblockedEvent = mInboundQueue.back();
needWake = true;
}
@@ -1048,7 +1276,7 @@ bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newE
return needWake;
}
-void InputDispatcher::addRecentEventLocked(std::shared_ptr<EventEntry> entry) {
+void InputDispatcher::addRecentEventLocked(std::shared_ptr<const EventEntry> entry) {
// Do not store sensor event in recent queue to avoid flooding the queue.
if (entry->type != EventEntry::Type::SENSOR) {
mRecentQueue.push_back(entry);
@@ -1058,14 +1286,9 @@ void InputDispatcher::addRecentEventLocked(std::shared_ptr<EventEntry> entry) {
}
}
-sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, int32_t x,
- int32_t y, TouchState* touchState,
+sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayId, float x, float y,
bool isStylus,
- bool addOutsideTargets,
- bool ignoreDragWindow) {
- if (addOutsideTargets && touchState == nullptr) {
- LOG_ALWAYS_FATAL("Must provide a valid touch state if adding outside targets");
- }
+ bool ignoreDragWindow) const {
// Traverse windows from front to back to find touched window.
const auto& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
@@ -1074,28 +1297,50 @@ sp<WindowInfoHandle> InputDispatcher::findTouchedWindowAtLocked(int32_t displayI
}
const WindowInfo& info = *windowHandle->getInfo();
- if (!info.isSpy() && windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
+ if (!info.isSpy() &&
+ windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
return windowHandle;
}
+ }
+ return nullptr;
+}
+
+std::vector<InputTarget> InputDispatcher::findOutsideTargetsLocked(
+ int32_t displayId, const sp<WindowInfoHandle>& touchedWindow, int32_t pointerId) const {
+ if (touchedWindow == nullptr) {
+ return {};
+ }
+ // Traverse windows from front to back until we encounter the touched window.
+ std::vector<InputTarget> outsideTargets;
+ const auto& windowHandles = getWindowHandlesLocked(displayId);
+ for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+ if (windowHandle == touchedWindow) {
+ // Stop iterating once we found a touched window. Any WATCH_OUTSIDE_TOUCH window
+ // below the touched window will not get ACTION_OUTSIDE event.
+ return outsideTargets;
+ }
- if (addOutsideTargets &&
- info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
- touchState->addOrUpdateWindow(windowHandle, InputTarget::FLAG_DISPATCH_AS_OUTSIDE,
- BitSet32(0));
+ const WindowInfo& info = *windowHandle->getInfo();
+ if (info.inputConfig.test(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH)) {
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(pointerId);
+ addPointerWindowTargetLocked(windowHandle, InputTarget::DispatchMode::OUTSIDE,
+ ftl::Flags<InputTarget::Flags>(), pointerIds,
+ /*firstDownTimeInTarget=*/std::nullopt, outsideTargets);
}
}
- return nullptr;
+ return outsideTargets;
}
std::vector<sp<WindowInfoHandle>> InputDispatcher::findTouchedSpyWindowsAtLocked(
- int32_t displayId, int32_t x, int32_t y, bool isStylus) const {
+ int32_t displayId, float x, float y, bool isStylus) const {
// Traverse windows from front to back and gather the touched spy windows.
std::vector<sp<WindowInfoHandle>> spyWindows;
const auto& windowHandles = getWindowHandlesLocked(displayId);
for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
const WindowInfo& info = *windowHandle->getInfo();
- if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus)) {
+ if (!windowAcceptsTouchAt(info, displayId, x, y, isStylus, getTransformLocked(displayId))) {
continue;
}
if (!info.isSpy()) {
@@ -1111,7 +1356,7 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason
const char* reason;
switch (dropReason) {
case DropReason::POLICY:
- if (DEBUG_INBOUND_EVENT_DETAILS) {
+ if (debugInboundEventDetails()) {
ALOGD("Dropped event because policy consumed it.");
}
reason = "inbound event was dropped because the policy consumed it";
@@ -1148,17 +1393,25 @@ void InputDispatcher::dropInboundEventLocked(const EventEntry& entry, DropReason
switch (entry.type) {
case EventEntry::Type::KEY: {
- CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS, reason);
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
+ options.displayId = keyEntry.displayId;
+ options.deviceId = keyEntry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
break;
}
case EventEntry::Type::MOTION: {
const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
if (motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) {
- CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS, reason);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS, reason);
+ options.displayId = motionEntry.displayId;
+ options.deviceId = motionEntry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
} else {
- CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS, reason);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
+ reason);
+ options.displayId = motionEntry.displayId;
+ options.deviceId = motionEntry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
}
break;
@@ -1191,12 +1444,12 @@ bool InputDispatcher::isAppSwitchKeyEvent(const KeyEntry& keyEntry) {
(keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER);
}
-bool InputDispatcher::isAppSwitchPendingLocked() {
- return mAppSwitchDueTime != LONG_LONG_MAX;
+bool InputDispatcher::isAppSwitchPendingLocked() const {
+ return mAppSwitchDueTime != LLONG_MAX;
}
void InputDispatcher::resetPendingAppSwitchLocked(bool handled) {
- mAppSwitchDueTime = LONG_LONG_MAX;
+ mAppSwitchDueTime = LLONG_MAX;
if (DEBUG_APP_SWITCH) {
if (handled) {
@@ -1231,7 +1484,7 @@ void InputDispatcher::postCommandLocked(Command&& command) {
void InputDispatcher::drainInboundQueueLocked() {
while (!mInboundQueue.empty()) {
- std::shared_ptr<EventEntry> entry = mInboundQueue.front();
+ std::shared_ptr<const EventEntry> entry = mInboundQueue.front();
mInboundQueue.pop_front();
releaseInboundEventLocked(entry);
}
@@ -1245,8 +1498,8 @@ void InputDispatcher::releasePendingEventLocked() {
}
}
-void InputDispatcher::releaseInboundEventLocked(std::shared_ptr<EventEntry> entry) {
- InjectionState* injectionState = entry->injectionState;
+void InputDispatcher::releaseInboundEventLocked(std::shared_ptr<const EventEntry> entry) {
+ const std::shared_ptr<InjectionState>& injectionState = entry->injectionState;
if (injectionState && injectionState->injectionResult == InputEventInjectionResult::PENDING) {
if (DEBUG_DISPATCH_CYCLE) {
ALOGD("Injected inbound event was dropped.");
@@ -1266,16 +1519,17 @@ void InputDispatcher::resetKeyRepeatLocked() {
}
std::shared_ptr<KeyEntry> InputDispatcher::synthesizeKeyRepeatLocked(nsecs_t currentTime) {
- std::shared_ptr<KeyEntry> entry = mKeyRepeatState.lastKeyEntry;
+ std::shared_ptr<const KeyEntry> entry = mKeyRepeatState.lastKeyEntry;
uint32_t policyFlags = entry->policyFlags &
(POLICY_FLAG_RAW_MASK | POLICY_FLAG_PASS_TO_USER | POLICY_FLAG_TRUSTED);
std::shared_ptr<KeyEntry> newEntry =
- std::make_unique<KeyEntry>(mIdGenerator.nextId(), currentTime, entry->deviceId,
- entry->source, entry->displayId, policyFlags, entry->action,
- entry->flags, entry->keyCode, entry->scanCode,
- entry->metaState, entry->repeatCount + 1, entry->downTime);
+ std::make_unique<KeyEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+ currentTime, entry->deviceId, entry->source,
+ entry->displayId, policyFlags, entry->action, entry->flags,
+ entry->keyCode, entry->scanCode, entry->metaState,
+ entry->repeatCount + 1, entry->downTime);
newEntry->syntheticRepeat = true;
mKeyRepeatState.lastKeyEntry = newEntry;
@@ -1295,7 +1549,7 @@ bool InputDispatcher::dispatchConfigurationChangedLocked(nsecs_t currentTime,
// Enqueue a command to run outside the lock to tell the policy that the configuration changed.
auto command = [this, eventTime = entry.eventTime]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyConfigurationChanged(eventTime);
+ mPolicy.notifyConfigurationChanged(eventTime);
};
postCommandLocked(std::move(command));
return true;
@@ -1313,9 +1567,14 @@ bool InputDispatcher::dispatchDeviceResetLocked(nsecs_t currentTime,
resetKeyRepeatLocked();
}
- CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, "device was reset");
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, "device was reset");
options.deviceId = entry.deviceId;
synthesizeCancelationEventsForAllConnectionsLocked(options);
+
+ // Remove all active pointers from this device
+ for (auto& [_, touchState] : mTouchStatesByDisplay) {
+ touchState.removeAllPointersForDevice(entry.deviceId);
+ }
return true;
}
@@ -1334,24 +1593,23 @@ void InputDispatcher::enqueueFocusEventLocked(const sp<IBinder>& windowToken, bo
// This event should go to the front of the queue, but behind all other focus events
// Find the last focus event, and insert right after it
- std::deque<std::shared_ptr<EventEntry>>::reverse_iterator it =
- std::find_if(mInboundQueue.rbegin(), mInboundQueue.rend(),
- [](const std::shared_ptr<EventEntry>& event) {
- return event->type == EventEntry::Type::FOCUS;
- });
+ auto it = std::find_if(mInboundQueue.rbegin(), mInboundQueue.rend(),
+ [](const std::shared_ptr<const EventEntry>& event) {
+ return event->type == EventEntry::Type::FOCUS;
+ });
// Maintain the order of focus events. Insert the entry after all other focus events.
mInboundQueue.insert(it.base(), std::move(focusEntry));
}
-void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry) {
+void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime,
+ std::shared_ptr<const FocusEntry> entry) {
std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
if (channel == nullptr) {
return; // Window has gone away
}
InputTarget target;
target.inputChannel = channel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
entry->dispatchInProgress = true;
std::string message = std::string("Focus ") + (entry->hasFocus ? "entering " : "leaving ") +
channel->getName();
@@ -1361,7 +1619,7 @@ void InputDispatcher::dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<F
}
void InputDispatcher::dispatchPointerCaptureChangedLocked(
- nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ nsecs_t currentTime, const std::shared_ptr<const PointerCaptureChangedEntry>& entry,
DropReason& dropReason) {
dropReason = DropReason::NOT_DROPPED;
@@ -1425,17 +1683,16 @@ void InputDispatcher::dispatchPointerCaptureChangedLocked(
}
InputTarget target;
target.inputChannel = channel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
entry->dispatchInProgress = true;
dispatchEventLocked(currentTime, entry, {target});
dropReason = DropReason::NOT_DROPPED;
}
-void InputDispatcher::dispatchTouchModeChangeLocked(nsecs_t currentTime,
- const std::shared_ptr<TouchModeEntry>& entry) {
+void InputDispatcher::dispatchTouchModeChangeLocked(
+ nsecs_t currentTime, const std::shared_ptr<const TouchModeEntry>& entry) {
const std::vector<sp<WindowInfoHandle>>& windowHandles =
- getWindowHandlesLocked(mFocusedDisplayId);
+ getWindowHandlesLocked(entry->displayId);
if (windowHandles.empty()) {
return;
}
@@ -1452,7 +1709,6 @@ std::vector<InputTarget> InputDispatcher::getInputTargetsFromWindowHandlesLocked
const std::vector<sp<WindowInfoHandle>>& windowHandles) const {
std::vector<InputTarget> inputTargets;
for (const sp<WindowInfoHandle>& handle : windowHandles) {
- // TODO(b/193718270): Due to performance concerns, consider notifying visible windows only.
const sp<IBinder>& token = handle->getToken();
if (token == nullptr) {
continue;
@@ -1463,13 +1719,12 @@ std::vector<InputTarget> InputDispatcher::getInputTargetsFromWindowHandlesLocked
}
InputTarget target;
target.inputChannel = channel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
inputTargets.push_back(target);
}
return inputTargets;
}
-bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
+bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
// Preprocessing.
if (!entry->dispatchInProgress) {
@@ -1490,7 +1745,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key
// stop the key repeat on current device.
entry->repeatCount = mKeyRepeatState.lastKeyEntry->repeatCount + 1;
resetKeyRepeatLocked();
- mKeyRepeatState.nextRepeatTime = LONG_LONG_MAX; // don't generate repeats ourselves
+ mKeyRepeatState.nextRepeatTime = LLONG_MAX; // don't generate repeats ourselves
} else {
// Not a repeat. Save key down state in case we do see a repeat later.
resetKeyRepeatLocked();
@@ -1500,7 +1755,7 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key
} else if (entry->action == AKEY_EVENT_ACTION_UP && mKeyRepeatState.lastKeyEntry &&
mKeyRepeatState.lastKeyEntry->deviceId != entry->deviceId) {
// The key on device 'deviceId' is still down, do not stop key repeat
- if (DEBUG_INBOUND_EVENT_DETAILS) {
+ if (debugInboundEventDetails()) {
ALOGD("deviceId=%d got KEY_UP as stale", entry->deviceId);
}
} else if (!entry->syntheticRepeat) {
@@ -1519,19 +1774,19 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key
}
// Handle case where the policy asked us to try again later last time.
- if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER) {
+ if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER) {
if (currentTime < entry->interceptKeyWakeupTime) {
if (entry->interceptKeyWakeupTime < *nextWakeupTime) {
*nextWakeupTime = entry->interceptKeyWakeupTime;
}
return false; // wait until next wakeup
}
- entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN;
+ entry->interceptKeyResult = KeyEntry::InterceptKeyResult::UNKNOWN;
entry->interceptKeyWakeupTime = 0;
}
// Give the policy a chance to intercept the key.
- if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_UNKNOWN) {
+ if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::UNKNOWN) {
if (entry->policyFlags & POLICY_FLAG_PASS_TO_USER) {
sp<IBinder> focusedWindowToken =
mFocusResolver.getFocusedWindowToken(getTargetDisplayId(*entry));
@@ -1540,11 +1795,13 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key
doInterceptKeyBeforeDispatchingCommand(focusedWindowToken, *entry);
};
postCommandLocked(std::move(command));
+ // Poke user activity for keys not passed to user
+ pokeUserActivityLocked(*entry);
return false; // wait for the command to run
} else {
- entry->interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ entry->interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
}
- } else if (entry->interceptKeyResult == KeyEntry::INTERCEPT_KEY_RESULT_SKIP) {
+ } else if (entry->interceptKeyResult == KeyEntry::InterceptKeyResult::SKIP) {
if (*dropReason == DropReason::NOT_DROPPED) {
*dropReason = DropReason::POLICY;
}
@@ -1556,13 +1813,16 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key
*dropReason == DropReason::POLICY ? InputEventInjectionResult::SUCCEEDED
: InputEventInjectionResult::FAILED);
mReporter->reportDroppedKey(entry->id);
+ // Poke user activity for undispatched keys
+ pokeUserActivityLocked(*entry);
return true;
}
// Identify targets.
- std::vector<InputTarget> inputTargets;
- InputEventInjectionResult injectionResult =
- findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
+ InputEventInjectionResult injectionResult;
+ sp<WindowInfoHandle> focusedWindow =
+ findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime,
+ /*byref*/ injectionResult);
if (injectionResult == InputEventInjectionResult::PENDING) {
return false;
}
@@ -1571,6 +1831,11 @@ bool InputDispatcher::dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<Key
if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
return true;
}
+ LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
+
+ std::vector<InputTarget> inputTargets;
+ addWindowTargetLocked(focusedWindow, InputTarget::DispatchMode::AS_IS,
+ InputTarget::Flags::FOREGROUND, getDownTime(*entry), inputTargets);
// Add monitor channels from event's or focused display.
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
@@ -1592,7 +1857,7 @@ void InputDispatcher::logOutboundKeyDetails(const char* prefix, const KeyEntry&
}
void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
- const std::shared_ptr<SensorEntry>& entry,
+ const std::shared_ptr<const SensorEntry>& entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("notifySensorEvent eventTime=%" PRId64 ", hwTimestamp=%" PRId64 ", deviceId=%d, "
@@ -1604,10 +1869,10 @@ void InputDispatcher::dispatchSensorLocked(nsecs_t currentTime,
scoped_unlock unlock(mLock);
if (entry->accuracyChanged) {
- mPolicy->notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
+ mPolicy.notifySensorAccuracy(entry->deviceId, entry->sensorType, entry->accuracy);
}
- mPolicy->notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
- entry->hwTimestamp, entry->values);
+ mPolicy.notifySensorEvent(entry->deviceId, entry->sensorType, entry->accuracy,
+ entry->hwTimestamp, entry->values);
};
postCommandLocked(std::move(command));
}
@@ -1621,7 +1886,7 @@ bool InputDispatcher::flushSensor(int deviceId, InputDeviceSensorType sensorType
std::scoped_lock _l(mLock);
for (auto it = mInboundQueue.begin(); it != mInboundQueue.end(); it++) {
- std::shared_ptr<EventEntry> entry = *it;
+ std::shared_ptr<const EventEntry> entry = *it;
if (entry->type == EventEntry::Type::SENSOR) {
it = mInboundQueue.erase(it);
releaseInboundEventLocked(entry);
@@ -1631,7 +1896,8 @@ bool InputDispatcher::flushSensor(int deviceId, InputDeviceSensorType sensorType
return true;
}
-bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
+bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime,
+ std::shared_ptr<const MotionEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) {
ATRACE_CALL();
// Preprocessing.
@@ -1654,17 +1920,30 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<
// Identify targets.
std::vector<InputTarget> inputTargets;
- bool conflictingPointerActions = false;
InputEventInjectionResult injectionResult;
if (isPointerEvent) {
// Pointer event. (eg. touchscreen)
- injectionResult =
- findTouchedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime,
- &conflictingPointerActions);
+
+ if (mDragState &&
+ (entry->action & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_POINTER_DOWN) {
+ // If drag and drop ongoing and pointer down occur: pilfer drag window pointers
+ pilferPointersLocked(mDragState->dragWindow->getToken());
+ }
+
+ inputTargets =
+ findTouchedWindowTargetsLocked(currentTime, *entry, /*byref*/ injectionResult);
+ LOG_ALWAYS_FATAL_IF(injectionResult != InputEventInjectionResult::SUCCEEDED &&
+ !inputTargets.empty());
} else {
// Non touch event. (eg. trackball)
- injectionResult =
- findFocusedWindowTargetsLocked(currentTime, *entry, inputTargets, nextWakeupTime);
+ sp<WindowInfoHandle> focusedWindow =
+ findFocusedWindowTargetLocked(currentTime, *entry, nextWakeupTime, injectionResult);
+ if (injectionResult == InputEventInjectionResult::SUCCEEDED) {
+ LOG_ALWAYS_FATAL_IF(focusedWindow == nullptr);
+ addWindowTargetLocked(focusedWindow, InputTarget::DispatchMode::AS_IS,
+ InputTarget::Flags::FOREGROUND, getDownTime(*entry),
+ inputTargets);
+ }
}
if (injectionResult == InputEventInjectionResult::PENDING) {
return false;
@@ -1675,9 +1954,9 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<
return true;
}
if (injectionResult != InputEventInjectionResult::SUCCEEDED) {
- CancelationOptions::Mode mode(isPointerEvent
- ? CancelationOptions::CANCEL_POINTER_EVENTS
- : CancelationOptions::CANCEL_NON_POINTER_EVENTS);
+ CancelationOptions::Mode mode(
+ isPointerEvent ? CancelationOptions::Mode::CANCEL_POINTER_EVENTS
+ : CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS);
CancelationOptions options(mode, "input event injection failed");
synthesizeCancelationEventsForMonitorsLocked(options);
return true;
@@ -1687,11 +1966,6 @@ bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<
addGlobalMonitoringTargetsLocked(inputTargets, getTargetDisplayId(*entry));
// Dispatch the motion.
- if (conflictingPointerActions) {
- CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
- "conflicting pointer actions");
- synthesizeCancelationEventsForAllConnectionsLocked(options);
- }
dispatchEventLocked(currentTime, entry, inputTargets);
return true;
}
@@ -1707,36 +1981,38 @@ void InputDispatcher::enqueueDragEventLocked(const sp<WindowInfoHandle>& windowH
enqueueInboundEventLocked(std::move(dragEntry));
}
-void InputDispatcher::dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<DragEntry> entry) {
+void InputDispatcher::dispatchDragLocked(nsecs_t currentTime,
+ std::shared_ptr<const DragEntry> entry) {
std::shared_ptr<InputChannel> channel = getInputChannelLocked(entry->connectionToken);
if (channel == nullptr) {
return; // Window has gone away
}
InputTarget target;
target.inputChannel = channel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
entry->dispatchInProgress = true;
dispatchEventLocked(currentTime, entry, {target});
}
void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionEntry& entry) {
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
+ ALOGD("%seventTime=%" PRId64 ", deviceId=%d, source=%s, displayId=%" PRId32
", policyFlags=0x%x, "
"action=%s, actionButton=0x%x, flags=0x%x, "
"metaState=0x%x, buttonState=0x%x,"
"edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, downTime=%" PRId64,
- prefix, entry.eventTime, entry.deviceId, entry.source, entry.displayId,
- entry.policyFlags, MotionEvent::actionToString(entry.action).c_str(),
- entry.actionButton, entry.flags, entry.metaState, entry.buttonState, entry.edgeFlags,
- entry.xPrecision, entry.yPrecision, entry.downTime);
-
- for (uint32_t i = 0; i < entry.pointerCount; i++) {
- ALOGD(" Pointer %d: id=%d, toolType=%d, "
+ prefix, entry.eventTime, entry.deviceId,
+ inputEventSourceToString(entry.source).c_str(), entry.displayId, entry.policyFlags,
+ MotionEvent::actionToString(entry.action).c_str(), entry.actionButton, entry.flags,
+ entry.metaState, entry.buttonState, entry.edgeFlags, entry.xPrecision,
+ entry.yPrecision, entry.downTime);
+
+ for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%s, "
"x=%f, y=%f, pressure=%f, size=%f, "
"touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
"orientation=%f",
- i, entry.pointerProperties[i].id, entry.pointerProperties[i].toolType,
+ i, entry.pointerProperties[i].id,
+ ftl::enum_string(entry.pointerProperties[i].toolType).c_str(),
entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
entry.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
@@ -1751,35 +2027,35 @@ void InputDispatcher::logOutboundMotionDetails(const char* prefix, const MotionE
}
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
- std::shared_ptr<EventEntry> eventEntry,
+ std::shared_ptr<const EventEntry> eventEntry,
const std::vector<InputTarget>& inputTargets) {
ATRACE_CALL();
if (DEBUG_DISPATCH_CYCLE) {
ALOGD("dispatchEventToCurrentInputTargets");
}
- updateInteractionTokensLocked(*eventEntry, inputTargets);
+ processInteractionsLocked(*eventEntry, inputTargets);
ALOG_ASSERT(eventEntry->dispatchInProgress); // should already have been set to true
pokeUserActivityLocked(*eventEntry);
for (const InputTarget& inputTarget : inputTargets) {
- sp<Connection> connection =
+ std::shared_ptr<Connection> connection =
getConnectionLocked(inputTarget.inputChannel->getConnectionToken());
if (connection != nullptr) {
prepareDispatchCycleLocked(currentTime, connection, eventEntry, inputTarget);
} else {
- if (DEBUG_FOCUS) {
- ALOGD("Dropping event delivery to target with channel '%s' because it "
- "is no longer registered with the input dispatcher.",
- inputTarget.inputChannel->getName().c_str());
+ if (DEBUG_DROPPED_EVENTS_VERBOSE) {
+ LOG(INFO) << "Dropping event delivery to target with channel "
+ << inputTarget.inputChannel->getName()
+ << " because it is no longer registered with the input dispatcher.";
}
}
}
}
-void InputDispatcher::cancelEventsForAnrLocked(const sp<Connection>& connection) {
+void InputDispatcher::cancelEventsForAnrLocked(const std::shared_ptr<Connection>& connection) {
// We will not be breaking any connections here, even if the policy wants us to abort dispatch.
// If the policy decides to close the app, we will get a channel removal event via
// unregisterInputChannel, and will clean up the connection that way. We are already not
@@ -1788,7 +2064,7 @@ void InputDispatcher::cancelEventsForAnrLocked(const sp<Connection>& connection)
ALOGW("Canceling events for %s because it is unresponsive",
connection->inputChannel->getName().c_str());
if (connection->status == Connection::Status::NORMAL) {
- CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS,
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS,
"application not responding");
synthesizeCancelationEventsForConnectionLocked(connection, options);
}
@@ -1866,10 +2142,10 @@ bool InputDispatcher::shouldWaitToSendKeyLocked(nsecs_t currentTime,
return false;
}
-InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
- nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime) {
- std::string reason;
+sp<WindowInfoHandle> InputDispatcher::findFocusedWindowTargetLocked(
+ nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+ InputEventInjectionResult& outInjectionResult) {
+ outInjectionResult = InputEventInjectionResult::FAILED; // Default result
int32_t displayId = getTargetDisplayId(entry);
sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
@@ -1882,12 +2158,12 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
ALOGI("Dropping %s event because there is no focused window or focused application in "
"display %" PRId32 ".",
ftl::enum_string(entry.type).c_str(), displayId);
- return InputEventInjectionResult::FAILED;
+ return nullptr;
}
// Drop key events if requested by input feature
if (focusedWindowHandle != nullptr && shouldDropInput(entry, focusedWindowHandle)) {
- return InputEventInjectionResult::FAILED;
+ return nullptr;
}
// Compatibility behavior: raise ANR if there is a focused application, but no focused window.
@@ -1907,15 +2183,17 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
"window when it finishes starting up. Will wait for %" PRId64 "ms",
mAwaitedFocusedApplication->getName().c_str(), millis(timeout));
*nextWakeupTime = *mNoFocusedWindowTimeoutTime;
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
} else if (currentTime > *mNoFocusedWindowTimeoutTime) {
// Already raised ANR. Drop the event
ALOGE("Dropping %s event because there is no focused window",
ftl::enum_string(entry.type).c_str());
- return InputEventInjectionResult::FAILED;
+ return nullptr;
} else {
// Still waiting for the focused window
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
}
}
@@ -1925,13 +2203,15 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(focusedWindowHandle, entry); err) {
ALOGW("Dropping injected event: %s", (*err).c_str());
- return InputEventInjectionResult::TARGET_MISMATCH;
+ outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
+ return nullptr;
}
if (focusedWindowHandle->getInfo()->inputConfig.test(
WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
ALOGI("Waiting because %s is paused", focusedWindowHandle->getName().c_str());
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
}
// If the event is a key event, then we must wait for all previous events to
@@ -1948,17 +2228,13 @@ InputEventInjectionResult InputDispatcher::findFocusedWindowTargetsLocked(
if (entry.type == EventEntry::Type::KEY) {
if (shouldWaitToSendKeyLocked(currentTime, focusedWindowHandle->getName().c_str())) {
*nextWakeupTime = *mKeyIsWaitingForEventsTimeout;
- return InputEventInjectionResult::PENDING;
+ outInjectionResult = InputEventInjectionResult::PENDING;
+ return nullptr;
}
}
- // Success! Output targets.
- addWindowTargetLocked(focusedWindowHandle,
- InputTarget::FLAG_FOREGROUND | InputTarget::FLAG_DISPATCH_AS_IS,
- BitSet32(0), inputTargets);
-
- // Done.
- return InputEventInjectionResult::SUCCEEDED;
+ outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+ return focusedWindowHandle;
}
/**
@@ -1970,7 +2246,7 @@ std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked(
std::vector<Monitor> responsiveMonitors;
std::copy_if(monitors.begin(), monitors.end(), std::back_inserter(responsiveMonitors),
[this](const Monitor& monitor) REQUIRES(mLock) {
- sp<Connection> connection =
+ std::shared_ptr<Connection> connection =
getConnectionLocked(monitor.inputChannel->getConnectionToken());
if (connection == nullptr) {
ALOGE("Could not find connection for monitor %s",
@@ -1987,21 +2263,20 @@ std::vector<Monitor> InputDispatcher::selectResponsiveMonitorsLocked(
return responsiveMonitors;
}
-InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
- nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
+std::vector<InputTarget> InputDispatcher::findTouchedWindowTargetsLocked(
+ nsecs_t currentTime, const MotionEntry& entry,
+ InputEventInjectionResult& outInjectionResult) {
ATRACE_CALL();
+ std::vector<InputTarget> targets;
// For security reasons, we defer updating the touch state until we are sure that
// event injection will be allowed.
const int32_t displayId = entry.displayId;
const int32_t action = entry.action;
- const int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
+ const int32_t maskedAction = MotionEvent::getActionMasked(action);
// Update the touch state as needed based on the properties of the touch event.
- InputEventInjectionResult injectionResult = InputEventInjectionResult::PENDING;
- sp<WindowInfoHandle> newHoverWindowHandle(mLastHoverWindowHandle);
- sp<WindowInfoHandle> newTouchedWindowHandle;
+ outInjectionResult = InputEventInjectionResult::PENDING;
// Copy current touch state into tempTouchState.
// This state will be used to update mTouchStatesByDisplay at the end of this function.
@@ -2013,70 +2288,60 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
tempTouchState = *oldState;
}
- bool isSplit = tempTouchState.split;
- bool switchedDevice = tempTouchState.deviceId >= 0 && tempTouchState.displayId >= 0 &&
- (tempTouchState.deviceId != entry.deviceId || tempTouchState.source != entry.source ||
- tempTouchState.displayId != displayId);
+ bool isSplit = shouldSplitTouch(tempTouchState, entry);
const bool isHoverAction = (maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT);
- const bool newGesture = (maskedAction == AMOTION_EVENT_ACTION_DOWN ||
- maskedAction == AMOTION_EVENT_ACTION_SCROLL || isHoverAction);
+ // A DOWN could be generated from POINTER_DOWN if the initial pointers did not land into any
+ // touchable windows.
+ const bool wasDown = oldState != nullptr && oldState->isDown(entry.deviceId);
+ const bool isDown = (maskedAction == AMOTION_EVENT_ACTION_DOWN) ||
+ (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN && !wasDown);
+ const bool newGesture = isDown || maskedAction == AMOTION_EVENT_ACTION_SCROLL ||
+ maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
+ maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE;
const bool isFromMouse = isFromSource(entry.source, AINPUT_SOURCE_MOUSE);
- bool wrongDevice = false;
+
if (newGesture) {
- bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
- if (switchedDevice && tempTouchState.down && !down && !isHoverAction) {
- ALOGI("Dropping event because a pointer for a different device is already down "
- "in display %" PRId32,
- displayId);
- // TODO: test multiple simultaneous input streams.
- injectionResult = InputEventInjectionResult::FAILED;
- switchedDevice = false;
- wrongDevice = true;
- goto Failed;
- }
- tempTouchState.reset();
- tempTouchState.down = down;
- tempTouchState.deviceId = entry.deviceId;
- tempTouchState.source = entry.source;
- tempTouchState.displayId = displayId;
isSplit = false;
- } else if (switchedDevice && maskedAction == AMOTION_EVENT_ACTION_MOVE) {
- ALOGI("Dropping move event because a pointer for a different device is already active "
- "in display %" PRId32,
- displayId);
- // TODO: test multiple simultaneous input streams.
- injectionResult = InputEventInjectionResult::FAILED;
- switchedDevice = false;
- wrongDevice = true;
- goto Failed;
}
- if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
- /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
+ if (isDown && tempTouchState.hasHoveringPointers(entry.deviceId)) {
+ // Compatibility behaviour: ACTION_DOWN causes HOVER_EXIT to get generated.
+ tempTouchState.clearHoveringPointers(entry.deviceId);
+ }
- int32_t x;
- int32_t y;
- const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
- // Always dispatch mouse events to cursor position.
- if (isFromMouse) {
- x = int32_t(entry.xCursorPosition);
- y = int32_t(entry.yCursorPosition);
- } else {
- x = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_X));
- y = int32_t(entry.pointerCoords[pointerIndex].getAxisValue(AMOTION_EVENT_AXIS_Y));
+ if (isHoverAction) {
+ if (wasDown) {
+ // Started hovering, but the device is already down: reject the hover event
+ LOG(ERROR) << "Got hover event " << entry.getDescription()
+ << " but the device is already down " << oldState->dump();
+ outInjectionResult = InputEventInjectionResult::FAILED;
+ return {};
}
- const bool isDown = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+ // For hover actions, we will treat 'tempTouchState' as a new state, so let's erase
+ // all of the existing hovering pointers and recompute.
+ tempTouchState.clearHoveringPointers(entry.deviceId);
+ }
+
+ if (newGesture || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+ /* Case 1: New splittable pointer going down, or need target for hover or scroll. */
+ const auto [x, y] = resolveTouchedPosition(entry);
+ const int32_t pointerIndex = MotionEvent::getActionIndex(action);
+ const int32_t pointerId = entry.pointerProperties[pointerIndex].id;
+ // Outside targets should be added upon first dispatched DOWN event. That means, this should
+ // be a pointer that would generate ACTION_DOWN, *and* touch should not already be down.
const bool isStylus = isPointerFromStylus(entry, pointerIndex);
- newTouchedWindowHandle = findTouchedWindowAtLocked(displayId, x, y, &tempTouchState,
- isStylus, isDown /*addOutsideTargets*/);
+ sp<WindowInfoHandle> newTouchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y, isStylus);
+ if (isDown) {
+ targets += findOutsideTargetsLocked(displayId, newTouchedWindowHandle, pointerId);
+ }
// Handle the case where we did not find a window.
if (newTouchedWindowHandle == nullptr) {
- ALOGD("No new touched window at (%" PRId32 ", %" PRId32 ") in display %" PRId32, x, y,
- displayId);
+ ALOGD("No new touched window at (%.1f, %.1f) in display %" PRId32, x, y, displayId);
// Try to assign the pointer to the first foreground window we find, if there is one.
newTouchedWindowHandle = tempTouchState.getFirstForegroundWindowHandle();
}
@@ -2084,9 +2349,9 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
ALOGW("Dropping injected touch event: %s", (*err).c_str());
- injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
+ outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
newTouchedWindowHandle = nullptr;
- goto Failed;
+ return {};
}
// Figure out whether splitting will be allowed for this window.
@@ -2097,22 +2362,15 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
} else if (isSplit) {
// New window does not support splitting but we have already split events.
// Ignore the new window.
+ LOG(INFO) << "Skipping " << newTouchedWindowHandle->getName()
+ << " because it doesn't support split touch";
newTouchedWindowHandle = nullptr;
}
} else {
// No window is touched, so set split to true. This will allow the next pointer down to
// be delivered to a new window which supports split touch. Pointers from a mouse device
// should never be split.
- tempTouchState.split = isSplit = !isFromMouse;
- }
-
- // Update hover state.
- if (newTouchedWindowHandle != nullptr) {
- if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
- newHoverWindowHandle = nullptr;
- } else if (isHoverAction) {
- newHoverWindowHandle = newTouchedWindowHandle;
- }
+ isSplit = !isFromMouse;
}
std::vector<sp<WindowInfoHandle>> newTouchedWindows =
@@ -2123,116 +2381,140 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
}
if (newTouchedWindows.empty()) {
- ALOGI("Dropping event because there is no touchable window at (%d, %d) on display %d.",
+ ALOGI("Dropping event because there is no touchable window at (%.1f, %.1f) on display "
+ "%d.",
x, y, displayId);
- injectionResult = InputEventInjectionResult::FAILED;
- goto Failed;
+ outInjectionResult = InputEventInjectionResult::FAILED;
+ return {};
}
for (const sp<WindowInfoHandle>& windowHandle : newTouchedWindows) {
- const WindowInfo& info = *windowHandle->getInfo();
-
- // Skip spy window targets that are not valid for targeted injection.
- if (const auto err = verifyTargetedInjection(windowHandle, entry); err) {
- continue;
- }
-
- if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
- ALOGI("Not sending touch event to %s because it is paused",
- windowHandle->getName().c_str());
- continue;
- }
-
- // Ensure the window has a connection and the connection is responsive
- const bool isResponsive = hasResponsiveConnectionLocked(*windowHandle);
- if (!isResponsive) {
- ALOGW("Not sending touch gesture to %s because it is not responsive",
- windowHandle->getName().c_str());
+ if (!canWindowReceiveMotionLocked(windowHandle, entry)) {
continue;
}
- // Drop events that can't be trusted due to occlusion
- if (mBlockUntrustedTouchesMode != BlockUntrustedTouchesMode::DISABLED) {
- TouchOcclusionInfo occlusionInfo =
- computeTouchOcclusionInfoLocked(windowHandle, x, y);
- if (!isTouchTrustedLocked(occlusionInfo)) {
- if (DEBUG_TOUCH_OCCLUSION) {
- ALOGD("Stack of obscuring windows during untrusted touch (%d, %d):", x, y);
- for (const auto& log : occlusionInfo.debugInfo) {
- ALOGD("%s", log.c_str());
- }
- }
- sendUntrustedTouchCommandLocked(occlusionInfo.obscuringPackage);
- if (mBlockUntrustedTouchesMode == BlockUntrustedTouchesMode::BLOCK) {
- ALOGW("Dropping untrusted touch event due to %s/%d",
- occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid);
- continue;
- }
- }
- }
-
- // Drop touch events if requested by input feature
- if (shouldDropInput(entry, windowHandle)) {
- continue;
+ if (isHoverAction) {
+ // The "windowHandle" is the target of this hovering pointer.
+ tempTouchState.addHoveringPointerToWindow(windowHandle, entry.deviceId, pointerId);
}
// Set target flags.
- int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_IS;
+ ftl::Flags<InputTarget::Flags> targetFlags;
if (canReceiveForegroundTouches(*windowHandle->getInfo())) {
// There should only be one touched window that can be "foreground" for the pointer.
- targetFlags |= InputTarget::FLAG_FOREGROUND;
+ targetFlags |= InputTarget::Flags::FOREGROUND;
}
if (isSplit) {
- targetFlags |= InputTarget::FLAG_SPLIT;
+ targetFlags |= InputTarget::Flags::SPLIT;
}
if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
- targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
} else if (isWindowObscuredLocked(windowHandle)) {
- targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+ targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
}
// Update the temporary touch state.
- BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
- tempTouchState.addOrUpdateWindow(windowHandle, targetFlags, pointerIds);
+ if (!isHoverAction) {
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(pointerId);
+ const bool isDownOrPointerDown = maskedAction == AMOTION_EVENT_ACTION_DOWN ||
+ maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN;
+ tempTouchState.addOrUpdateWindow(windowHandle, InputTarget::DispatchMode::AS_IS,
+ targetFlags, entry.deviceId, pointerIds,
+ isDownOrPointerDown
+ ? std::make_optional(entry.eventTime)
+ : std::nullopt);
+ // If this is the pointer going down and the touched window has a wallpaper
+ // then also add the touched wallpaper windows so they are locked in for the
+ // duration of the touch gesture. We do not collect wallpapers during HOVER_MOVE or
+ // SCROLL because the wallpaper engine only supports touch events. We would need to
+ // add a mechanism similar to View.onGenericMotionEvent to enable wallpapers to
+ // handle these events.
+ if (isDownOrPointerDown && targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ windowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
+ sp<WindowInfoHandle> wallpaper = findWallpaperWindowBelow(windowHandle);
+ if (wallpaper != nullptr) {
+ ftl::Flags<InputTarget::Flags> wallpaperFlags =
+ InputTarget::Flags::WINDOW_IS_OBSCURED |
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
+ if (isSplit) {
+ wallpaperFlags |= InputTarget::Flags::SPLIT;
+ }
+ tempTouchState.addOrUpdateWindow(wallpaper,
+ InputTarget::DispatchMode::AS_IS,
+ wallpaperFlags, entry.deviceId, pointerIds,
+ entry.eventTime);
+ }
+ }
+ }
}
+
+ // If a window is already pilfering some pointers, give it this new pointer as well and
+ // make it pilfering. This will prevent other non-spy windows from getting this pointer,
+ // which is a specific behaviour that we want.
+ for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+ if (touchedWindow.hasTouchingPointer(entry.deviceId, pointerId) &&
+ touchedWindow.hasPilferingPointers(entry.deviceId)) {
+ // This window is already pilfering some pointers, and this new pointer is also
+ // going to it. Therefore, take over this pointer and don't give it to anyone
+ // else.
+ touchedWindow.addPilferingPointer(entry.deviceId, pointerId);
+ }
+ }
+
+ // Restrict all pilfered pointers to the pilfering windows.
+ tempTouchState.cancelPointersForNonPilferingWindows();
} else {
/* Case 2: Pointer move, up, cancel or non-splittable pointer down. */
// If the pointer is not currently down, then ignore the event.
- if (!tempTouchState.down) {
- if (DEBUG_FOCUS) {
- ALOGD("Dropping event because the pointer is not down or we previously "
- "dropped the pointer down event in display %" PRId32,
- displayId);
+ if (!tempTouchState.isDown(entry.deviceId) &&
+ maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ if (DEBUG_DROPPED_EVENTS_VERBOSE) {
+ LOG(INFO) << "Dropping event because the pointer for device " << entry.deviceId
+ << " is not down or we previously dropped the pointer down event in "
+ << "display " << displayId << ": " << entry.getDescription();
}
- injectionResult = InputEventInjectionResult::FAILED;
- goto Failed;
+ outInjectionResult = InputEventInjectionResult::FAILED;
+ return {};
+ }
+
+ // If the pointer is not currently hovering, then ignore the event.
+ if (maskedAction == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ const int32_t pointerId = entry.pointerProperties[0].id;
+ if (oldState == nullptr ||
+ oldState->getWindowsWithHoveringPointer(entry.deviceId, pointerId).empty()) {
+ LOG(INFO) << "Dropping event because the hovering pointer is not in any windows in "
+ "display "
+ << displayId << ": " << entry.getDescription();
+ outInjectionResult = InputEventInjectionResult::FAILED;
+ return {};
+ }
+ tempTouchState.removeHoveringPointer(entry.deviceId, pointerId);
}
addDragEventLocked(entry);
// Check whether touches should slip outside of the current foreground window.
- if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
+ if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.getPointerCount() == 1 &&
tempTouchState.isSlippery()) {
- const int32_t x = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
- const int32_t y = int32_t(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
-
- const bool isStylus = isPointerFromStylus(entry, 0 /*pointerIndex*/);
+ const auto [x, y] = resolveTouchedPosition(entry);
+ const bool isStylus = isPointerFromStylus(entry, /*pointerIndex=*/0);
sp<WindowInfoHandle> oldTouchedWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
- newTouchedWindowHandle =
- findTouchedWindowAtLocked(displayId, x, y, &tempTouchState, isStylus);
+ LOG_ALWAYS_FATAL_IF(oldTouchedWindowHandle == nullptr);
+ sp<WindowInfoHandle> newTouchedWindowHandle =
+ findTouchedWindowAtLocked(displayId, x, y, isStylus);
// Verify targeted injection.
if (const auto err = verifyTargetedInjection(newTouchedWindowHandle, entry); err) {
ALOGW("Dropping injected event: %s", (*err).c_str());
- injectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
- newTouchedWindowHandle = nullptr;
- goto Failed;
+ outInjectionResult = os::InputEventInjectionResult::TARGET_MISMATCH;
+ return {};
}
// Drop touch events if requested by input feature
@@ -2241,258 +2523,215 @@ InputEventInjectionResult InputDispatcher::findTouchedWindowTargetsLocked(
newTouchedWindowHandle = nullptr;
}
- if (oldTouchedWindowHandle != newTouchedWindowHandle &&
- oldTouchedWindowHandle != nullptr && newTouchedWindowHandle != nullptr) {
- if (DEBUG_FOCUS) {
- ALOGD("Touch is slipping out of window %s into window %s in display %" PRId32,
- oldTouchedWindowHandle->getName().c_str(),
- newTouchedWindowHandle->getName().c_str(), displayId);
- }
+ if (newTouchedWindowHandle != nullptr &&
+ !haveSameToken(oldTouchedWindowHandle, newTouchedWindowHandle)) {
+ ALOGI("Touch is slipping out of window %s into window %s in display %" PRId32,
+ oldTouchedWindowHandle->getName().c_str(),
+ newTouchedWindowHandle->getName().c_str(), displayId);
+
// Make a slippery exit from the old window.
- tempTouchState.addOrUpdateWindow(oldTouchedWindowHandle,
- InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT,
- BitSet32(0));
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ const int32_t pointerId = entry.pointerProperties[0].id;
+ pointerIds.set(pointerId);
+
+ const TouchedWindow& touchedWindow =
+ tempTouchState.getTouchedWindow(oldTouchedWindowHandle);
+ addPointerWindowTargetLocked(oldTouchedWindowHandle,
+ InputTarget::DispatchMode::SLIPPERY_EXIT,
+ ftl::Flags<InputTarget::Flags>(), pointerIds,
+ touchedWindow.getDownTimeInTarget(entry.deviceId),
+ targets);
// Make a slippery entrance into the new window.
if (newTouchedWindowHandle->getInfo()->supportsSplitTouch()) {
isSplit = !isFromMouse;
}
- int32_t targetFlags = InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER;
+ ftl::Flags<InputTarget::Flags> targetFlags;
if (canReceiveForegroundTouches(*newTouchedWindowHandle->getInfo())) {
- targetFlags |= InputTarget::FLAG_FOREGROUND;
+ targetFlags |= InputTarget::Flags::FOREGROUND;
}
if (isSplit) {
- targetFlags |= InputTarget::FLAG_SPLIT;
+ targetFlags |= InputTarget::Flags::SPLIT;
}
if (isWindowObscuredAtPointLocked(newTouchedWindowHandle, x, y)) {
- targetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
+ targetFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED;
} else if (isWindowObscuredLocked(newTouchedWindowHandle)) {
- targetFlags |= InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+ targetFlags |= InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
}
- BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[0].id);
- tempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);
+ tempTouchState.addOrUpdateWindow(newTouchedWindowHandle,
+ InputTarget::DispatchMode::SLIPPERY_ENTER,
+ targetFlags, entry.deviceId, pointerIds,
+ entry.eventTime);
+
+ // Check if the wallpaper window should deliver the corresponding event.
+ slipWallpaperTouch(targetFlags, oldTouchedWindowHandle, newTouchedWindowHandle,
+ tempTouchState, entry.deviceId, pointerId, targets);
+ tempTouchState.removeTouchingPointerFromWindow(entry.deviceId, pointerId,
+ oldTouchedWindowHandle);
}
}
// Update the pointerIds for non-splittable when it received pointer down.
if (!isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN) {
// If no split, we suppose all touched windows should receive pointer down.
- const int32_t pointerIndex = getMotionEventActionPointerIndex(action);
+ const int32_t pointerIndex = MotionEvent::getActionIndex(action);
for (size_t i = 0; i < tempTouchState.windows.size(); i++) {
TouchedWindow& touchedWindow = tempTouchState.windows[i];
// Ignore drag window for it should just track one pointer.
if (mDragState && mDragState->dragWindow == touchedWindow.windowHandle) {
continue;
}
- touchedWindow.pointerIds.markBit(entry.pointerProperties[pointerIndex].id);
+ std::bitset<MAX_POINTER_ID + 1> touchingPointers;
+ touchingPointers.set(entry.pointerProperties[pointerIndex].id);
+ touchedWindow.addTouchingPointers(entry.deviceId, touchingPointers);
}
}
}
// Update dispatching for hover enter and exit.
- if (newHoverWindowHandle != mLastHoverWindowHandle) {
- // Let the previous window know that the hover sequence is over, unless we already did
- // it when dispatching it as is to newTouchedWindowHandle.
- if (mLastHoverWindowHandle != nullptr &&
- (maskedAction != AMOTION_EVENT_ACTION_HOVER_EXIT ||
- mLastHoverWindowHandle != newTouchedWindowHandle)) {
- if (DEBUG_HOVER) {
- ALOGD("Sending hover exit event to window %s.",
- mLastHoverWindowHandle->getName().c_str());
- }
- tempTouchState.addOrUpdateWindow(mLastHoverWindowHandle,
- InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT, BitSet32(0));
- }
-
- // Let the new window know that the hover sequence is starting, unless we already did it
- // when dispatching it as is to newTouchedWindowHandle.
- if (newHoverWindowHandle != nullptr &&
- (maskedAction != AMOTION_EVENT_ACTION_HOVER_ENTER ||
- newHoverWindowHandle != newTouchedWindowHandle)) {
- if (DEBUG_HOVER) {
- ALOGD("Sending hover enter event to window %s.",
- newHoverWindowHandle->getName().c_str());
- }
- tempTouchState.addOrUpdateWindow(newHoverWindowHandle,
- InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER,
- BitSet32(0));
- }
- }
-
- // Ensure that we have at least one foreground window or at least one window that cannot be a
- // foreground target. If we only have windows that are not receiving foreground touches (e.g. we
- // only have windows getting ACTION_OUTSIDE), then drop the event, because there is no window
- // that is actually receiving the entire gesture.
- if (std::none_of(tempTouchState.windows.begin(), tempTouchState.windows.end(),
- [](const TouchedWindow& touchedWindow) {
- return !canReceiveForegroundTouches(
- *touchedWindow.windowHandle->getInfo()) ||
- (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND) != 0;
- })) {
- ALOGI("Dropping event because there is no touched window on display %d to receive it: %s",
- displayId, entry.getDescription().c_str());
- injectionResult = InputEventInjectionResult::FAILED;
- goto Failed;
+ {
+ std::vector<TouchedWindow> hoveringWindows =
+ getHoveringWindowsLocked(oldState, tempTouchState, entry);
+ for (const TouchedWindow& touchedWindow : hoveringWindows) {
+ std::optional<InputTarget> target =
+ createInputTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+ touchedWindow.targetFlags,
+ touchedWindow.getDownTimeInTarget(entry.deviceId));
+ if (!target) {
+ continue;
+ }
+ // Hardcode to single hovering pointer for now.
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(entry.pointerProperties[0].id);
+ target->addPointers(pointerIds, touchedWindow.windowHandle->getInfo()->transform);
+ targets.push_back(*target);
+ }
}
// Ensure that all touched windows are valid for injection.
if (entry.injectionState != nullptr) {
std::string errs;
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
- // Allow ACTION_OUTSIDE events generated by targeted injection to be
- // dispatched to any uid, since the coords will be zeroed out later.
- continue;
- }
const auto err = verifyTargetedInjection(touchedWindow.windowHandle, entry);
if (err) errs += "\n - " + *err;
}
if (!errs.empty()) {
ALOGW("Dropping targeted injection: At least one touched window is not owned by uid "
- "%d:%s",
- *entry.injectionState->targetUid, errs.c_str());
- injectionResult = InputEventInjectionResult::TARGET_MISMATCH;
- goto Failed;
+ "%s:%s",
+ entry.injectionState->targetUid->toString().c_str(), errs.c_str());
+ outInjectionResult = InputEventInjectionResult::TARGET_MISMATCH;
+ return {};
}
}
- // Check whether windows listening for outside touches are owned by the same UID. If it is
- // set the policy flag that we will not reveal coordinate information to this window.
+ // Check whether windows listening for outside touches are owned by the same UID. If the owner
+ // has a different UID, then we will not reveal coordinate information to this window.
if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
sp<WindowInfoHandle> foregroundWindowHandle =
tempTouchState.getFirstForegroundWindowHandle();
if (foregroundWindowHandle) {
- const int32_t foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
- for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- if (touchedWindow.targetFlags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
- sp<WindowInfoHandle> windowInfoHandle = touchedWindow.windowHandle;
- if (windowInfoHandle->getInfo()->ownerUid != foregroundWindowUid) {
- tempTouchState.addOrUpdateWindow(windowInfoHandle,
- InputTarget::FLAG_ZERO_COORDS,
- BitSet32(0));
+ const auto foregroundWindowUid = foregroundWindowHandle->getInfo()->ownerUid;
+ for (InputTarget& target : targets) {
+ if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
+ sp<WindowInfoHandle> targetWindow =
+ getWindowHandleLocked(target.inputChannel->getConnectionToken());
+ if (targetWindow->getInfo()->ownerUid != foregroundWindowUid) {
+ target.flags |= InputTarget::Flags::ZERO_COORDS;
}
}
}
}
}
- // If this is the first pointer going down and the touched window has a wallpaper
- // then also add the touched wallpaper windows so they are locked in for the duration
- // of the touch gesture.
- // We do not collect wallpapers during HOVER_MOVE or SCROLL because the wallpaper
- // engine only supports touch events. We would need to add a mechanism similar
- // to View.onGenericMotionEvent to enable wallpapers to handle these events.
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
- sp<WindowInfoHandle> foregroundWindowHandle =
- tempTouchState.getFirstForegroundWindowHandle();
- if (foregroundWindowHandle &&
- foregroundWindowHandle->getInfo()->inputConfig.test(
- WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
- const std::vector<sp<WindowInfoHandle>>& windowHandles =
- getWindowHandlesLocked(displayId);
- for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
- const WindowInfo* info = windowHandle->getInfo();
- if (info->displayId == displayId &&
- windowHandle->getInfo()->inputConfig.test(
- WindowInfo::InputConfig::IS_WALLPAPER)) {
- BitSet32 pointerIds;
- pointerIds.markBit(entry.pointerProperties[0].id);
- tempTouchState
- .addOrUpdateWindow(windowHandle,
- InputTarget::FLAG_WINDOW_IS_OBSCURED |
- InputTarget::
- FLAG_WINDOW_IS_PARTIALLY_OBSCURED |
- InputTarget::FLAG_DISPATCH_AS_IS,
- pointerIds);
- }
- }
- }
+ // If this is a touchpad navigation gesture, it needs to only be sent to trusted targets, as we
+ // only want the system UI to handle these gestures.
+ const bool isTouchpadNavGesture = isFromSource(entry.source, AINPUT_SOURCE_MOUSE) &&
+ entry.classification == MotionClassification::MULTI_FINGER_SWIPE;
+ if (isTouchpadNavGesture) {
+ filterUntrustedTargets(/* byref */ tempTouchState, /* byref */ targets);
}
- // Success! Output targets.
- injectionResult = InputEventInjectionResult::SUCCEEDED;
-
+ // Output targets from the touch state.
for (const TouchedWindow& touchedWindow : tempTouchState.windows) {
- addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
- touchedWindow.pointerIds, inputTargets);
+ std::bitset<MAX_POINTER_ID + 1> touchingPointers =
+ touchedWindow.getTouchingPointers(entry.deviceId);
+ if (touchingPointers.none()) {
+ continue;
+ }
+ addPointerWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.dispatchMode,
+ touchedWindow.targetFlags, touchingPointers,
+ touchedWindow.getDownTimeInTarget(entry.deviceId), targets);
}
- // Drop the outside or hover touch windows since we will not care about them
- // in the next iteration.
- tempTouchState.filterNonAsIsTouchWindows();
-
-Failed:
- // Update final pieces of touch state if the injector had permission.
- if (!wrongDevice) {
- if (switchedDevice) {
- if (DEBUG_FOCUS) {
- ALOGD("Conflicting pointer actions: Switched to a different device.");
- }
- *outConflictingPointerActions = true;
+ // During targeted injection, only allow owned targets to receive events
+ std::erase_if(targets, [&](const InputTarget& target) {
+ LOG_ALWAYS_FATAL_IF(target.windowHandle == nullptr);
+ const auto err = verifyTargetedInjection(target.windowHandle, entry);
+ if (err) {
+ LOG(WARNING) << "Dropping injected event from " << target.windowHandle->getName()
+ << ": " << (*err);
+ return true;
}
+ return false;
+ });
- if (isHoverAction) {
- // Started hovering, therefore no longer down.
- if (oldState && oldState->down) {
- if (DEBUG_FOCUS) {
- ALOGD("Conflicting pointer actions: Hover received while pointer was "
- "down.");
- }
- *outConflictingPointerActions = true;
- }
- tempTouchState.reset();
- if (maskedAction == AMOTION_EVENT_ACTION_HOVER_ENTER ||
- maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
- tempTouchState.deviceId = entry.deviceId;
- tempTouchState.source = entry.source;
- tempTouchState.displayId = displayId;
- }
- } else if (maskedAction == AMOTION_EVENT_ACTION_UP ||
- maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
- // All pointers up or canceled.
- tempTouchState.reset();
- } else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
- // First pointer went down.
- if (oldState && oldState->down) {
- if (DEBUG_FOCUS) {
- ALOGD("Conflicting pointer actions: Down received while already down.");
- }
- *outConflictingPointerActions = true;
- }
- } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
- // One pointer went up.
- int32_t pointerIndex = getMotionEventActionPointerIndex(action);
- uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
+ if (targets.empty()) {
+ LOG(INFO) << "Dropping event because no targets were found: " << entry.getDescription();
+ outInjectionResult = InputEventInjectionResult::FAILED;
+ return {};
+ }
- for (size_t i = 0; i < tempTouchState.windows.size();) {
- TouchedWindow& touchedWindow = tempTouchState.windows[i];
- touchedWindow.pointerIds.clearBit(pointerId);
- if (touchedWindow.pointerIds.isEmpty()) {
- tempTouchState.windows.erase(tempTouchState.windows.begin() + i);
- continue;
- }
- i += 1;
- }
- }
+ // If we only have windows getting ACTION_OUTSIDE, then drop the event, because there is no
+ // window that is actually receiving the entire gesture.
+ if (std::all_of(targets.begin(), targets.end(), [](const InputTarget& target) {
+ return target.dispatchMode == InputTarget::DispatchMode::OUTSIDE;
+ })) {
+ LOG(INFO) << "Dropping event because all windows would just receive ACTION_OUTSIDE: "
+ << entry.getDescription();
+ outInjectionResult = InputEventInjectionResult::FAILED;
+ return {};
+ }
- // Save changes unless the action was scroll in which case the temporary touch
- // state was only valid for this one action.
- if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
- if (tempTouchState.displayId >= 0) {
- mTouchStatesByDisplay[displayId] = tempTouchState;
- } else {
- mTouchStatesByDisplay.erase(displayId);
- }
+ outInjectionResult = InputEventInjectionResult::SUCCEEDED;
+
+ // Now that we have generated all of the input targets for this event, reset the dispatch
+ // mode for all touched window to AS_IS.
+ for (TouchedWindow& touchedWindow : tempTouchState.windows) {
+ touchedWindow.dispatchMode = InputTarget::DispatchMode::AS_IS;
+ }
+
+ // Update final pieces of touch state if the injector had permission.
+ if (maskedAction == AMOTION_EVENT_ACTION_UP) {
+ // Pointer went up.
+ tempTouchState.removeTouchingPointer(entry.deviceId, entry.pointerProperties[0].id);
+ } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ // All pointers up or canceled.
+ tempTouchState.removeAllPointersForDevice(entry.deviceId);
+ } else if (maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
+ // One pointer went up.
+ const int32_t pointerIndex = MotionEvent::getActionIndex(action);
+ const uint32_t pointerId = entry.pointerProperties[pointerIndex].id;
+ tempTouchState.removeTouchingPointer(entry.deviceId, pointerId);
+ }
+
+ // Save changes unless the action was scroll in which case the temporary touch
+ // state was only valid for this one action.
+ if (maskedAction != AMOTION_EVENT_ACTION_SCROLL) {
+ if (displayId >= 0) {
+ tempTouchState.clearWindowsWithoutPointers();
+ mTouchStatesByDisplay[displayId] = tempTouchState;
+ } else {
+ mTouchStatesByDisplay.erase(displayId);
}
+ }
- // Update hover state.
- mLastHoverWindowHandle = newHoverWindowHandle;
+ if (tempTouchState.windows.empty()) {
+ mTouchStatesByDisplay.erase(displayId);
}
- return injectionResult;
+ return targets;
}
void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
@@ -2500,9 +2739,8 @@ void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
// have an explicit reason to support it.
constexpr bool isStylus = false;
- const sp<WindowInfoHandle> dropWindow =
- findTouchedWindowAtLocked(displayId, x, y, nullptr /*touchState*/, isStylus,
- false /*addOutsideTargets*/, true /*ignoreDragWindow*/);
+ sp<WindowInfoHandle> dropWindow =
+ findTouchedWindowAtLocked(displayId, x, y, isStylus, /*ignoreDragWindow=*/true);
if (dropWindow) {
vec2 local = dropWindow->getInfo()->transform.transform(x, y);
sendDropWindowCommandLocked(dropWindow->getToken(), local.x, local.y);
@@ -2526,18 +2764,15 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
// Find the pointer index by id.
int32_t pointerIndex = 0;
- for (; static_cast<uint32_t>(pointerIndex) < entry.pointerCount; pointerIndex++) {
+ for (; static_cast<uint32_t>(pointerIndex) < entry.getPointerCount(); pointerIndex++) {
const PointerProperties& pointerProperties = entry.pointerProperties[pointerIndex];
if (pointerProperties.id == mDragState->pointerId) {
break;
}
}
- if (uint32_t(pointerIndex) == entry.pointerCount) {
+ if (uint32_t(pointerIndex) == entry.getPointerCount()) {
LOG_ALWAYS_FATAL("Should find a valid pointer index by id %d", mDragState->pointerId);
- sendDropWindowCommandLocked(nullptr, 0, 0);
- mDragState.reset();
- return;
}
const int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK;
@@ -2558,28 +2793,27 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
// until we have an explicit reason to support it.
constexpr bool isStylus = false;
- const sp<WindowInfoHandle> hoverWindowHandle =
- findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/,
- isStylus, false /*addOutsideTargets*/,
- true /*ignoreDragWindow*/);
+ sp<WindowInfoHandle> hoverWindowHandle =
+ findTouchedWindowAtLocked(entry.displayId, x, y, isStylus,
+ /*ignoreDragWindow=*/true);
// enqueue drag exit if needed.
if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
!haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
if (mDragState->dragHoverWindowHandle != nullptr) {
- enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/, x,
+ enqueueDragEventLocked(mDragState->dragHoverWindowHandle, /*isExiting=*/true, x,
y);
}
mDragState->dragHoverWindowHandle = hoverWindowHandle;
}
// enqueue drag location if needed.
if (hoverWindowHandle != nullptr) {
- enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, x, y);
+ enqueueDragEventLocked(hoverWindowHandle, /*isExiting=*/false, x, y);
}
break;
}
case AMOTION_EVENT_ACTION_POINTER_UP:
- if (getMotionEventActionPointerIndex(entry.action) != pointerIndex) {
+ if (MotionEvent::getActionIndex(entry.action) != pointerIndex) {
break;
}
// The drag pointer is up.
@@ -2596,9 +2830,37 @@ void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
}
}
+std::optional<InputTarget> InputDispatcher::createInputTargetLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+ std::optional<nsecs_t> firstDownTimeInTarget) const {
+ std::shared_ptr<InputChannel> inputChannel = getInputChannelLocked(windowHandle->getToken());
+ if (inputChannel == nullptr) {
+ ALOGW("Not creating InputTarget for %s, no input channel", windowHandle->getName().c_str());
+ return {};
+ }
+ InputTarget inputTarget;
+ inputTarget.inputChannel = inputChannel;
+ inputTarget.windowHandle = windowHandle;
+ inputTarget.dispatchMode = dispatchMode;
+ inputTarget.flags = targetFlags;
+ inputTarget.globalScaleFactor = windowHandle->getInfo()->globalScaleFactor;
+ inputTarget.firstDownTimeInTarget = firstDownTimeInTarget;
+ const auto& displayInfoIt = mDisplayInfos.find(windowHandle->getInfo()->displayId);
+ if (displayInfoIt != mDisplayInfos.end()) {
+ inputTarget.displayTransform = displayInfoIt->second.transform;
+ } else {
+ // DisplayInfo not found for this window on display windowHandle->getInfo()->displayId.
+ // TODO(b/198444055): Make this an error message after 'setInputWindows' API is removed.
+ }
+ return inputTarget;
+}
+
void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHandle,
- int32_t targetFlags, BitSet32 pointerIds,
- std::vector<InputTarget>& inputTargets) {
+ InputTarget::DispatchMode dispatchMode,
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::optional<nsecs_t> firstDownTimeInTarget,
+ std::vector<InputTarget>& inputTargets) const {
std::vector<InputTarget>::iterator it =
std::find_if(inputTargets.begin(), inputTargets.end(),
[&windowHandle](const InputTarget& inputTarget) {
@@ -2609,28 +2871,80 @@ void InputDispatcher::addWindowTargetLocked(const sp<WindowInfoHandle>& windowHa
const WindowInfo* windowInfo = windowHandle->getInfo();
if (it == inputTargets.end()) {
- InputTarget inputTarget;
- std::shared_ptr<InputChannel> inputChannel =
- getInputChannelLocked(windowHandle->getToken());
- if (inputChannel == nullptr) {
- ALOGW("Window %s already unregistered input channel", windowHandle->getName().c_str());
+ std::optional<InputTarget> target =
+ createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
+ firstDownTimeInTarget);
+ if (!target) {
return;
}
- inputTarget.inputChannel = inputChannel;
- inputTarget.flags = targetFlags;
- inputTarget.globalScaleFactor = windowInfo->globalScaleFactor;
- const auto& displayInfoIt = mDisplayInfos.find(windowInfo->displayId);
- if (displayInfoIt != mDisplayInfos.end()) {
- inputTarget.displayTransform = displayInfoIt->second.transform;
- } else {
- ALOGE("DisplayInfo not found for window on display: %d", windowInfo->displayId);
+ inputTargets.push_back(*target);
+ it = inputTargets.end() - 1;
+ }
+
+ if (it->flags != targetFlags) {
+ LOG(ERROR) << "Flags don't match! targetFlags=" << targetFlags.string() << ", it=" << *it;
+ }
+ if (it->globalScaleFactor != windowInfo->globalScaleFactor) {
+ LOG(ERROR) << "Mismatch! it->globalScaleFactor=" << it->globalScaleFactor
+ << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
+ }
+}
+
+void InputDispatcher::addPointerWindowTargetLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds, std::optional<nsecs_t> firstDownTimeInTarget,
+ std::vector<InputTarget>& inputTargets) const REQUIRES(mLock) {
+ if (pointerIds.none()) {
+ for (const auto& target : inputTargets) {
+ LOG(INFO) << "Target: " << target;
}
- inputTargets.push_back(inputTarget);
+ LOG(FATAL) << "No pointers specified for " << windowHandle->getName();
+ return;
+ }
+ std::vector<InputTarget>::iterator it =
+ std::find_if(inputTargets.begin(), inputTargets.end(),
+ [&windowHandle](const InputTarget& inputTarget) {
+ return inputTarget.inputChannel->getConnectionToken() ==
+ windowHandle->getToken();
+ });
+
+ // This is a hack, because the actual entry could potentially be an ACTION_DOWN event that
+ // causes a HOVER_EXIT to be generated. That means that the same entry of ACTION_DOWN would
+ // have DISPATCH_AS_HOVER_EXIT and DISPATCH_AS_IS. And therefore, we have to create separate
+ // input targets for hovering pointers and for touching pointers.
+ // If we picked an existing input target above, but it's for HOVER_EXIT - let's use a new
+ // target instead.
+ if (it != inputTargets.end() && it->dispatchMode == InputTarget::DispatchMode::HOVER_EXIT) {
+ // Force the code below to create a new input target
+ it = inputTargets.end();
+ }
+
+ const WindowInfo* windowInfo = windowHandle->getInfo();
+
+ if (it == inputTargets.end()) {
+ std::optional<InputTarget> target =
+ createInputTargetLocked(windowHandle, dispatchMode, targetFlags,
+ firstDownTimeInTarget);
+ if (!target) {
+ return;
+ }
+ inputTargets.push_back(*target);
it = inputTargets.end() - 1;
}
- ALOG_ASSERT(it->flags == targetFlags);
- ALOG_ASSERT(it->globalScaleFactor == windowInfo->globalScaleFactor);
+ if (it->dispatchMode != dispatchMode) {
+ LOG(ERROR) << __func__ << ": DispatchMode doesn't match! ignoring new mode="
+ << ftl::enum_string(dispatchMode) << ", it=" << *it;
+ }
+ if (it->flags != targetFlags) {
+ LOG(ERROR) << __func__ << ": Flags don't match! new targetFlags=" << targetFlags.string()
+ << ", it=" << *it;
+ }
+ if (it->globalScaleFactor != windowInfo->globalScaleFactor) {
+ LOG(ERROR) << __func__ << ": Mismatch! it->globalScaleFactor=" << it->globalScaleFactor
+ << ", windowInfo->globalScaleFactor=" << windowInfo->globalScaleFactor;
+ }
it->addPointers(pointerIds, windowInfo->transform);
}
@@ -2643,7 +2957,8 @@ void InputDispatcher::addGlobalMonitoringTargetsLocked(std::vector<InputTarget>&
for (const Monitor& monitor : selectResponsiveMonitorsLocked(monitorsIt->second)) {
InputTarget target;
target.inputChannel = monitor.inputChannel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
+ // target.firstDownTimeInTarget is not set for global monitors. It is only required in split
+ // touch and global monitoring works as intended even without setting firstDownTimeInTarget
if (const auto& it = mDisplayInfos.find(displayId); it != mDisplayInfos.end()) {
target.displayTransform = it->second.transform;
}
@@ -2673,7 +2988,7 @@ static bool canBeObscuredBy(const sp<WindowInfoHandle>& windowHandle,
// We do want to potentially flag touchable windows even if they have 0
// opacity, since they can consume touches and alter the effects of the
// user interaction (eg. apps that rely on
- // FLAG_WINDOW_IS_PARTIALLY_OBSCURED should still be told about those
+ // Flags::WINDOW_IS_PARTIALLY_OBSCURED should still be told about those
// windows), hence we also check for FLAG_NOT_TOUCHABLE.
return false;
} else if (info->ownerUid == otherInfo->ownerUid) {
@@ -2713,8 +3028,8 @@ InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLo
TouchOcclusionInfo info;
info.hasBlockingOcclusion = false;
info.obscuringOpacity = 0;
- info.obscuringUid = -1;
- std::map<int32_t, float> opacityByUid;
+ info.obscuringUid = gui::Uid::INVALID;
+ std::map<gui::Uid, float> opacityByUid;
for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
if (windowHandle == otherHandle) {
break; // All future windows are below us. Exit early.
@@ -2724,7 +3039,7 @@ InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLo
!haveSameApplicationToken(windowInfo, otherInfo)) {
if (DEBUG_TOUCH_OCCLUSION) {
info.debugInfo.push_back(
- dumpWindowForTouchOcclusion(otherInfo, /* isTouchedWindow */ false));
+ dumpWindowForTouchOcclusion(otherInfo, /*isTouchedWindow=*/false));
}
// canBeObscuredBy() has returned true above, which means this window is untrusted, so
// we perform the checks below to see if the touch can be propagated or not based on the
@@ -2736,7 +3051,7 @@ InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLo
break;
}
if (otherInfo->touchOcclusionMode == TouchOcclusionMode::USE_OPACITY) {
- uint32_t uid = otherInfo->ownerUid;
+ const auto uid = otherInfo->ownerUid;
float opacity =
(opacityByUid.find(uid) == opacityByUid.end()) ? 0 : opacityByUid[uid];
// Given windows A and B:
@@ -2752,37 +3067,37 @@ InputDispatcher::TouchOcclusionInfo InputDispatcher::computeTouchOcclusionInfoLo
}
}
if (DEBUG_TOUCH_OCCLUSION) {
- info.debugInfo.push_back(
- dumpWindowForTouchOcclusion(windowInfo, /* isTouchedWindow */ true));
+ info.debugInfo.push_back(dumpWindowForTouchOcclusion(windowInfo, /*isTouchedWindow=*/true));
}
return info;
}
std::string InputDispatcher::dumpWindowForTouchOcclusion(const WindowInfo* info,
bool isTouchedWindow) const {
- return StringPrintf(INDENT2 "* %spackage=%s/%" PRId32 ", id=%" PRId32 ", mode=%s, alpha=%.2f, "
+ return StringPrintf(INDENT2 "* %spackage=%s/%s, id=%" PRId32 ", mode=%s, alpha=%.2f, "
"frame=[%" PRId32 ",%" PRId32 "][%" PRId32 ",%" PRId32
"], touchableRegion=%s, window={%s}, inputConfig={%s}, "
"hasToken=%s, applicationInfo.name=%s, applicationInfo.token=%s\n",
isTouchedWindow ? "[TOUCHED] " : "", info->packageName.c_str(),
- info->ownerUid, info->id, toString(info->touchOcclusionMode).c_str(),
- info->alpha, info->frameLeft, info->frameTop, info->frameRight,
- info->frameBottom, dumpRegion(info->touchableRegion).c_str(),
- info->name.c_str(), info->inputConfig.string().c_str(),
- toString(info->token != nullptr), info->applicationInfo.name.c_str(),
- toString(info->applicationInfo.token).c_str());
+ info->ownerUid.toString().c_str(), info->id,
+ toString(info->touchOcclusionMode).c_str(), info->alpha, info->frame.left,
+ info->frame.top, info->frame.right, info->frame.bottom,
+ dumpRegion(info->touchableRegion).c_str(), info->name.c_str(),
+ info->inputConfig.string().c_str(), toString(info->token != nullptr),
+ info->applicationInfo.name.c_str(),
+ binderToString(info->applicationInfo.token).c_str());
}
bool InputDispatcher::isTouchTrustedLocked(const TouchOcclusionInfo& occlusionInfo) const {
if (occlusionInfo.hasBlockingOcclusion) {
- ALOGW("Untrusted touch due to occlusion by %s/%d", occlusionInfo.obscuringPackage.c_str(),
- occlusionInfo.obscuringUid);
+ ALOGW("Untrusted touch due to occlusion by %s/%s", occlusionInfo.obscuringPackage.c_str(),
+ occlusionInfo.obscuringUid.toString().c_str());
return false;
}
if (occlusionInfo.obscuringOpacity > mMaximumObscuringOpacityForTouch) {
- ALOGW("Untrusted touch due to occlusion by %s/%d (obscuring opacity = "
+ ALOGW("Untrusted touch due to occlusion by %s/%s (obscuring opacity = "
"%.2f, maximum allowed = %.2f)",
- occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid,
+ occlusionInfo.obscuringPackage.c_str(), occlusionInfo.obscuringUid.toString().c_str(),
occlusionInfo.obscuringOpacity, mMaximumObscuringOpacityForTouch);
return false;
}
@@ -2845,13 +3160,11 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
}
int32_t displayId = getTargetDisplayId(eventEntry);
sp<WindowInfoHandle> focusedWindowHandle = getFocusedWindowHandleLocked(displayId);
+ const WindowInfo* windowDisablingUserActivityInfo = nullptr;
if (focusedWindowHandle != nullptr) {
const WindowInfo* info = focusedWindowHandle->getInfo();
if (info->inputConfig.test(WindowInfo::InputConfig::DISABLE_USER_ACTIVITY)) {
- if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("Not poking user activity: disabled by window '%s'.", info->name.c_str());
- }
- return;
+ windowDisablingUserActivityInfo = info;
}
}
@@ -2862,7 +3175,13 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
if (motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) {
return;
}
-
+ if (windowDisablingUserActivityInfo != nullptr) {
+ if (DEBUG_DISPATCH_CYCLE) {
+ ALOGD("Not poking user activity: disabled by window '%s'.",
+ windowDisablingUserActivityInfo->name.c_str());
+ }
+ return;
+ }
if (MotionEvent::isTouchEvent(motionEntry.source, motionEntry.action)) {
eventType = USER_ACTIVITY_EVENT_TOUCH;
}
@@ -2873,6 +3192,22 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
return;
}
+ // If the key code is unknown, we don't consider it user activity
+ if (keyEntry.keyCode == AKEYCODE_UNKNOWN) {
+ return;
+ }
+ // Don't inhibit events that were intercepted or are not passed to
+ // the apps, like system shortcuts
+ if (windowDisablingUserActivityInfo != nullptr &&
+ keyEntry.interceptKeyResult != KeyEntry::InterceptKeyResult::SKIP &&
+ keyEntry.policyFlags & POLICY_FLAG_PASS_TO_USER) {
+ if (DEBUG_DISPATCH_CYCLE) {
+ ALOGD("Not poking user activity: disabled by window '%s'.",
+ windowDisablingUserActivityInfo->name.c_str());
+ }
+ return;
+ }
+
eventType = USER_ACTIVITY_EVENT_BUTTON;
break;
}
@@ -2886,26 +3221,23 @@ void InputDispatcher::pokeUserActivityLocked(const EventEntry& eventEntry) {
auto command = [this, eventTime = eventEntry.eventTime, eventType, displayId]()
REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->pokeUserActivity(eventTime, eventType, displayId);
+ mPolicy.pokeUserActivity(eventTime, eventType, displayId);
};
postCommandLocked(std::move(command));
}
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection,
- std::shared_ptr<EventEntry> eventEntry,
+ const std::shared_ptr<Connection>& connection,
+ std::shared_ptr<const EventEntry> eventEntry,
const InputTarget& inputTarget) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
- connection->getInputChannelName().c_str(), eventEntry->id);
- ATRACE_NAME(message.c_str());
- }
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("prepareDispatchCycleLocked(inputChannel=%s, id=0x%" PRIx32 ")",
+ connection->getInputChannelName().c_str(), eventEntry->id));
if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ prepareDispatchCycle - flags=0x%08x, "
- "globalScaleFactor=%f, pointerIds=0x%x %s",
- connection->getInputChannelName().c_str(), inputTarget.flags,
- inputTarget.globalScaleFactor, inputTarget.pointerIds.value,
+ ALOGD("channel '%s' ~ prepareDispatchCycle - flags=%s, "
+ "globalScaleFactor=%f, pointerIds=%s %s",
+ connection->getInputChannelName().c_str(), inputTarget.flags.string().c_str(),
+ inputTarget.globalScaleFactor, bitsetToString(inputTarget.pointerIds).c_str(),
inputTarget.getPointerInfoString().c_str());
}
@@ -2921,15 +3253,23 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
}
// Split a motion event if needed.
- if (inputTarget.flags & InputTarget::FLAG_SPLIT) {
+ if (inputTarget.flags.test(InputTarget::Flags::SPLIT)) {
LOG_ALWAYS_FATAL_IF(eventEntry->type != EventEntry::Type::MOTION,
- "Entry type %s should not have FLAG_SPLIT",
+ "Entry type %s should not have Flags::SPLIT",
ftl::enum_string(eventEntry->type).c_str());
const MotionEntry& originalMotionEntry = static_cast<const MotionEntry&>(*eventEntry);
- if (inputTarget.pointerIds.count() != originalMotionEntry.pointerCount) {
+ if (inputTarget.pointerIds.count() != originalMotionEntry.getPointerCount()) {
+ if (!inputTarget.firstDownTimeInTarget.has_value()) {
+ logDispatchStateLocked();
+ LOG(FATAL) << "Splitting motion events requires a down time to be set for the "
+ "target on connection "
+ << connection->getInputChannelName() << " for "
+ << originalMotionEntry.getDescription();
+ }
std::unique_ptr<MotionEntry> splitMotionEntry =
- splitMotionEvent(originalMotionEntry, inputTarget.pointerIds);
+ splitMotionEvent(originalMotionEntry, inputTarget.pointerIds,
+ inputTarget.firstDownTimeInTarget.value());
if (!splitMotionEntry) {
return; // split event was dropped
}
@@ -2943,42 +3283,29 @@ void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,
connection->getInputChannelName().c_str());
logOutboundMotionDetails(" ", *splitMotionEntry);
}
- enqueueDispatchEntriesLocked(currentTime, connection, std::move(splitMotionEntry),
- inputTarget);
+ enqueueDispatchEntryAndStartDispatchCycleLocked(currentTime, connection,
+ std::move(splitMotionEntry),
+ inputTarget);
return;
}
}
// Not splitting. Enqueue dispatch entries for the event as is.
- enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
+ enqueueDispatchEntryAndStartDispatchCycleLocked(currentTime, connection, eventEntry,
+ inputTarget);
}
-void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
- const sp<Connection>& connection,
- std::shared_ptr<EventEntry> eventEntry,
- const InputTarget& inputTarget) {
- if (ATRACE_ENABLED()) {
- std::string message =
- StringPrintf("enqueueDispatchEntriesLocked(inputChannel=%s, id=0x%" PRIx32 ")",
- connection->getInputChannelName().c_str(), eventEntry->id);
- ATRACE_NAME(message.c_str());
- }
-
- bool wasEmpty = connection->outboundQueue.empty();
-
- // Enqueue dispatch entries for the requested modes.
- enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
- InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);
- enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
- InputTarget::FLAG_DISPATCH_AS_OUTSIDE);
- enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
- InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);
- enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
- InputTarget::FLAG_DISPATCH_AS_IS);
- enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
- InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);
- enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,
- InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);
+void InputDispatcher::enqueueDispatchEntryAndStartDispatchCycleLocked(
+ nsecs_t currentTime, const std::shared_ptr<Connection>& connection,
+ std::shared_ptr<const EventEntry> eventEntry, const InputTarget& inputTarget) {
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("enqueueDispatchEntryAndStartDispatchCycleLocked(inputChannel=%s, "
+ "id=0x%" PRIx32 ")",
+ connection->getInputChannelName().c_str(), eventEntry->id));
+
+ const bool wasEmpty = connection->outboundQueue.empty();
+
+ enqueueDispatchEntryLocked(connection, eventEntry, inputTarget);
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty && !connection->outboundQueue.empty()) {
@@ -2986,120 +3313,139 @@ void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,
}
}
-void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connection,
- std::shared_ptr<EventEntry> eventEntry,
- const InputTarget& inputTarget,
- int32_t dispatchMode) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("enqueueDispatchEntry(inputChannel=%s, dispatchMode=%s)",
- connection->getInputChannelName().c_str(),
- dispatchModeToString(dispatchMode).c_str());
- ATRACE_NAME(message.c_str());
- }
- int32_t inputTargetFlags = inputTarget.flags;
- if (!(inputTargetFlags & dispatchMode)) {
- return;
- }
- inputTargetFlags = (inputTargetFlags & ~InputTarget::FLAG_DISPATCH_MASK) | dispatchMode;
-
+void InputDispatcher::enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
+ std::shared_ptr<const EventEntry> eventEntry,
+ const InputTarget& inputTarget) {
// This is a new event.
// Enqueue a new dispatch entry onto the outbound queue for this connection.
std::unique_ptr<DispatchEntry> dispatchEntry =
- createDispatchEntry(inputTarget, eventEntry, inputTargetFlags);
+ createDispatchEntry(inputTarget, eventEntry, inputTarget.flags);
// Use the eventEntry from dispatchEntry since the entry may have changed and can now be a
// different EventEntry than what was passed in.
- EventEntry& newEntry = *(dispatchEntry->eventEntry);
+ eventEntry = dispatchEntry->eventEntry;
// Apply target flags and update the connection's input state.
- switch (newEntry.type) {
+ switch (eventEntry->type) {
case EventEntry::Type::KEY: {
- const KeyEntry& keyEntry = static_cast<const KeyEntry&>(newEntry);
- dispatchEntry->resolvedEventId = keyEntry.id;
- dispatchEntry->resolvedAction = keyEntry.action;
- dispatchEntry->resolvedFlags = keyEntry.flags;
-
- if (!connection->inputState.trackKey(keyEntry, dispatchEntry->resolvedAction,
- dispatchEntry->resolvedFlags)) {
- if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent key "
- "event",
- connection->getInputChannelName().c_str());
- }
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*eventEntry);
+ if (!connection->inputState.trackKey(keyEntry, keyEntry.flags)) {
+ LOG(WARNING) << "channel " << connection->getInputChannelName()
+ << "~ dropping inconsistent event: " << *dispatchEntry;
return; // skip the inconsistent event
}
break;
}
case EventEntry::Type::MOTION: {
- const MotionEntry& motionEntry = static_cast<const MotionEntry&>(newEntry);
- // Assign a default value to dispatchEntry that will never be generated by InputReader,
- // and assign a InputDispatcher value if it doesn't change in the if-else chain below.
- constexpr int32_t DEFAULT_RESOLVED_EVENT_ID =
- static_cast<int32_t>(IdGenerator::Source::OTHER);
- dispatchEntry->resolvedEventId = DEFAULT_RESOLVED_EVENT_ID;
- if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
- dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
- } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT) {
- dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
- } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER) {
- dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
- } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
- dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
- } else if (dispatchMode & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER) {
- dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_DOWN;
- } else {
- dispatchEntry->resolvedAction = motionEntry.action;
- dispatchEntry->resolvedEventId = motionEntry.id;
- }
- if (dispatchEntry->resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
- !connection->inputState.isHovering(motionEntry.deviceId, motionEntry.source,
- motionEntry.displayId)) {
- if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: filling in missing hover "
- "enter event",
- connection->getInputChannelName().c_str());
+ std::shared_ptr<const MotionEntry> resolvedMotion =
+ std::static_pointer_cast<const MotionEntry>(eventEntry);
+ {
+ // Determine the resolved motion entry.
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(*eventEntry);
+ int32_t resolvedAction = motionEntry.action;
+ int32_t resolvedFlags = motionEntry.flags;
+
+ if (inputTarget.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
+ resolvedAction = AMOTION_EVENT_ACTION_OUTSIDE;
+ } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::HOVER_EXIT) {
+ resolvedAction = AMOTION_EVENT_ACTION_HOVER_EXIT;
+ } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::HOVER_ENTER) {
+ resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::SLIPPERY_EXIT) {
+ resolvedAction = AMOTION_EVENT_ACTION_CANCEL;
+ } else if (inputTarget.dispatchMode == InputTarget::DispatchMode::SLIPPERY_ENTER) {
+ resolvedAction = AMOTION_EVENT_ACTION_DOWN;
}
- // We keep the 'resolvedEventId' here equal to the original 'motionEntry.id' because
- // this is a one-to-one event conversion.
- dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
- }
+ if (resolvedAction == AMOTION_EVENT_ACTION_HOVER_MOVE &&
+ !connection->inputState.isHovering(motionEntry.deviceId, motionEntry.source,
+ motionEntry.displayId)) {
+ if (DEBUG_DISPATCH_CYCLE) {
+ LOG(DEBUG) << "channel '" << connection->getInputChannelName().c_str()
+ << "' ~ enqueueDispatchEntryLocked: filling in missing hover "
+ "enter event";
+ }
+ resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
+ }
+
+ if (resolvedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ resolvedFlags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ if (dispatchEntry->targetFlags.test(InputTarget::Flags::WINDOW_IS_OBSCURED)) {
+ resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ }
+ if (dispatchEntry->targetFlags.test(
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED)) {
+ resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+ }
+
+ dispatchEntry->resolvedFlags = resolvedFlags;
+ if (resolvedAction != motionEntry.action) {
+ // Generate a new MotionEntry with a new eventId using the resolved action and
+ // flags.
+ resolvedMotion =
+ std::make_shared<MotionEntry>(mIdGenerator.nextId(),
+ motionEntry.injectionState,
+ motionEntry.eventTime,
+ motionEntry.deviceId, motionEntry.source,
+ motionEntry.displayId,
+ motionEntry.policyFlags, resolvedAction,
+ motionEntry.actionButton, resolvedFlags,
+ motionEntry.metaState,
+ motionEntry.buttonState,
+ motionEntry.classification,
+ motionEntry.edgeFlags,
+ motionEntry.xPrecision,
+ motionEntry.yPrecision,
+ motionEntry.xCursorPosition,
+ motionEntry.yCursorPosition,
+ motionEntry.downTime,
+ motionEntry.pointerProperties,
+ motionEntry.pointerCoords);
+ if (ATRACE_ENABLED()) {
+ std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
+ ") to MotionEvent(id=0x%" PRIx32 ").",
+ motionEntry.id, resolvedMotion->id);
+ ATRACE_NAME(message.c_str());
+ }
- dispatchEntry->resolvedFlags = motionEntry.flags;
- if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_OBSCURED) {
- dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED;
+ // Set the resolved motion entry in the DispatchEntry.
+ dispatchEntry->eventEntry = resolvedMotion;
+ eventEntry = resolvedMotion;
+ }
}
- if (dispatchEntry->targetFlags & InputTarget::FLAG_WINDOW_IS_PARTIALLY_OBSCURED) {
- dispatchEntry->resolvedFlags |= AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
+
+ // Check if we need to cancel any of the ongoing gestures. We don't support multiple
+ // devices being active at the same time in the same window, so if a new device is
+ // active, cancel the gesture from the old device.
+ std::unique_ptr<EventEntry> cancelEvent =
+ connection->inputState.cancelConflictingInputStream(*resolvedMotion);
+ if (cancelEvent != nullptr) {
+ LOG(INFO) << "Canceling pointers for device " << resolvedMotion->deviceId << " in "
+ << connection->getInputChannelName() << " with event "
+ << cancelEvent->getDescription();
+ std::unique_ptr<DispatchEntry> cancelDispatchEntry =
+ createDispatchEntry(inputTarget, std::move(cancelEvent),
+ ftl::Flags<InputTarget::Flags>());
+
+ // Send these cancel events to the queue before sending the event from the new
+ // device.
+ connection->outboundQueue.emplace_back(std::move(cancelDispatchEntry));
}
- if (!connection->inputState.trackMotion(motionEntry, dispatchEntry->resolvedAction,
+ if (!connection->inputState.trackMotion(*resolvedMotion,
dispatchEntry->resolvedFlags)) {
- if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ enqueueDispatchEntryLocked: skipping inconsistent motion "
- "event",
- connection->getInputChannelName().c_str());
- }
+ LOG(WARNING) << "channel " << connection->getInputChannelName()
+ << "~ dropping inconsistent event: " << *dispatchEntry;
return; // skip the inconsistent event
}
- dispatchEntry->resolvedEventId =
- dispatchEntry->resolvedEventId == DEFAULT_RESOLVED_EVENT_ID
- ? mIdGenerator.nextId()
- : motionEntry.id;
- if (ATRACE_ENABLED() && dispatchEntry->resolvedEventId != motionEntry.id) {
- std::string message = StringPrintf("Transmute MotionEvent(id=0x%" PRIx32
- ") to MotionEvent(id=0x%" PRIx32 ").",
- motionEntry.id, dispatchEntry->resolvedEventId);
- ATRACE_NAME(message.c_str());
- }
-
- if ((motionEntry.flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
- (motionEntry.policyFlags & POLICY_FLAG_TRUSTED)) {
+ if ((resolvedMotion->flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
+ (resolvedMotion->policyFlags & POLICY_FLAG_TRUSTED)) {
// Skip reporting pointer down outside focus to the policy.
break;
}
- dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
+ dispatchPointerDownOutsideFocus(resolvedMotion->source, resolvedMotion->action,
inputTarget.inputChannel->getConnectionToken());
break;
@@ -3117,77 +3463,98 @@ void InputDispatcher::enqueueDispatchEntryLocked(const sp<Connection>& connectio
case EventEntry::Type::CONFIGURATION_CHANGED:
case EventEntry::Type::DEVICE_RESET: {
LOG_ALWAYS_FATAL("%s events should not go to apps",
- ftl::enum_string(newEntry.type).c_str());
+ ftl::enum_string(eventEntry->type).c_str());
break;
}
}
// Remember that we are waiting for this dispatch to complete.
if (dispatchEntry->hasForegroundTarget()) {
- incrementPendingForegroundDispatches(newEntry);
+ incrementPendingForegroundDispatches(*eventEntry);
}
// Enqueue the dispatch entry.
- connection->outboundQueue.push_back(dispatchEntry.release());
+ connection->outboundQueue.emplace_back(std::move(dispatchEntry));
traceOutboundQueueLength(*connection);
}
/**
- * This function is purely for debugging. It helps us understand where the user interaction
- * was taking place. For example, if user is touching launcher, we will see a log that user
- * started interacting with launcher. In that example, the event would go to the wallpaper as well.
- * We will see both launcher and wallpaper in that list.
- * Once the interaction with a particular set of connections starts, no new logs will be printed
- * until the set of interacted connections changes.
+ * This function is for debugging and metrics collection. It has two roles.
*
- * The following items are skipped, to reduce the logspam:
- * ACTION_OUTSIDE: any windows that are receiving ACTION_OUTSIDE are not logged
- * ACTION_UP: any windows that receive ACTION_UP are not logged (for both keys and motions).
- * This includes situations like the soft BACK button key. When the user releases (lifts up the
- * finger) the back button, then navigation bar will inject KEYCODE_BACK with ACTION_UP.
- * Both of those ACTION_UP events would not be logged
+ * The first role is to log input interaction with windows, which helps determine what the user was
+ * interacting with. For example, if user is touching launcher, we will see an input_interaction log
+ * that user started interacting with launcher window, as well as any other window that received
+ * that gesture, such as the wallpaper or other spy windows. A new input_interaction is only logged
+ * when the set of tokens that received the event changes. It is not logged again as long as the
+ * user is interacting with the same windows.
+ *
+ * The second role is to track input device activity for metrics collection. For each input event,
+ * we report the set of UIDs that the input device interacted with to the policy. Unlike for the
+ * input_interaction logs, the device interaction is reported even when the set of interaction
+ * tokens do not change.
+ *
+ * For these purposes, we do not count ACTION_OUTSIDE, ACTION_UP and ACTION_CANCEL actions as
+ * interaction. This includes up and cancel events for both keys and motions.
*/
-void InputDispatcher::updateInteractionTokensLocked(const EventEntry& entry,
- const std::vector<InputTarget>& targets) {
+void InputDispatcher::processInteractionsLocked(const EventEntry& entry,
+ const std::vector<InputTarget>& targets) {
+ int32_t deviceId;
+ nsecs_t eventTime;
// Skip ACTION_UP events, and all events other than keys and motions
if (entry.type == EventEntry::Type::KEY) {
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(entry);
if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
return;
}
+ deviceId = keyEntry.deviceId;
+ eventTime = keyEntry.eventTime;
} else if (entry.type == EventEntry::Type::MOTION) {
const MotionEntry& motionEntry = static_cast<const MotionEntry&>(entry);
if (motionEntry.action == AMOTION_EVENT_ACTION_UP ||
- motionEntry.action == AMOTION_EVENT_ACTION_CANCEL) {
+ motionEntry.action == AMOTION_EVENT_ACTION_CANCEL ||
+ MotionEvent::getActionMasked(motionEntry.action) == AMOTION_EVENT_ACTION_POINTER_UP) {
return;
}
+ deviceId = motionEntry.deviceId;
+ eventTime = motionEntry.eventTime;
} else {
return; // Not a key or a motion
}
+ std::set<gui::Uid> interactionUids;
std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> newConnectionTokens;
- std::vector<sp<Connection>> newConnections;
+ std::vector<std::shared_ptr<Connection>> newConnections;
for (const InputTarget& target : targets) {
- if ((target.flags & InputTarget::FLAG_DISPATCH_AS_OUTSIDE) ==
- InputTarget::FLAG_DISPATCH_AS_OUTSIDE) {
+ if (target.dispatchMode == InputTarget::DispatchMode::OUTSIDE) {
continue; // Skip windows that receive ACTION_OUTSIDE
}
sp<IBinder> token = target.inputChannel->getConnectionToken();
- sp<Connection> connection = getConnectionLocked(token);
+ std::shared_ptr<Connection> connection = getConnectionLocked(token);
if (connection == nullptr) {
continue;
}
newConnectionTokens.insert(std::move(token));
newConnections.emplace_back(connection);
+ if (target.windowHandle) {
+ interactionUids.emplace(target.windowHandle->getInfo()->ownerUid);
+ }
}
+
+ auto command = [this, deviceId, eventTime, uids = std::move(interactionUids)]()
+ REQUIRES(mLock) {
+ scoped_unlock unlock(mLock);
+ mPolicy.notifyDeviceInteraction(deviceId, eventTime, uids);
+ };
+ postCommandLocked(std::move(command));
+
if (newConnectionTokens == mInteractionConnectionTokens) {
return; // no change
}
mInteractionConnectionTokens = newConnectionTokens;
std::string targetList;
- for (const sp<Connection>& connection : newConnections) {
+ for (const std::shared_ptr<Connection>& connection : newConnections) {
targetList += connection->getWindowName() + ", ";
}
std::string message = "Interaction with: " + targetList;
@@ -3213,24 +3580,69 @@ void InputDispatcher::dispatchPointerDownOutsideFocus(uint32_t source, int32_t a
auto command = [this, token]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->onPointerDownOutsideFocus(token);
+ mPolicy.onPointerDownOutsideFocus(token);
};
postCommandLocked(std::move(command));
}
-void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection) {
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
- connection->getInputChannelName().c_str());
- ATRACE_NAME(message.c_str());
+status_t InputDispatcher::publishMotionEvent(Connection& connection,
+ DispatchEntry& dispatchEntry) const {
+ const EventEntry& eventEntry = *(dispatchEntry.eventEntry);
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+
+ PointerCoords scaledCoords[MAX_POINTERS];
+ const PointerCoords* usingCoords = motionEntry.pointerCoords.data();
+
+ // Set the X and Y offset and X and Y scale depending on the input source.
+ if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
+ !(dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS))) {
+ float globalScaleFactor = dispatchEntry.globalScaleFactor;
+ if (globalScaleFactor != 1.0f) {
+ for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
+ scaledCoords[i] = motionEntry.pointerCoords[i];
+ // Don't apply window scale here since we don't want scale to affect raw
+ // coordinates. The scale will be sent back to the client and applied
+ // later when requesting relative coordinates.
+ scaledCoords[i].scale(globalScaleFactor, /*windowXScale=*/1, /*windowYScale=*/1);
+ }
+ usingCoords = scaledCoords;
+ }
+ } else if (dispatchEntry.targetFlags.test(InputTarget::Flags::ZERO_COORDS)) {
+ // We don't want the dispatch target to know the coordinates
+ for (uint32_t i = 0; i < motionEntry.getPointerCount(); i++) {
+ scaledCoords[i].clear();
+ }
+ usingCoords = scaledCoords;
}
+
+ std::array<uint8_t, 32> hmac = getSignature(motionEntry, dispatchEntry);
+
+ // Publish the motion event.
+ return connection.inputPublisher
+ .publishMotionEvent(dispatchEntry.seq, motionEntry.id, motionEntry.deviceId,
+ motionEntry.source, motionEntry.displayId, std::move(hmac),
+ motionEntry.action, motionEntry.actionButton,
+ dispatchEntry.resolvedFlags, motionEntry.edgeFlags,
+ motionEntry.metaState, motionEntry.buttonState,
+ motionEntry.classification, dispatchEntry.transform,
+ motionEntry.xPrecision, motionEntry.yPrecision,
+ motionEntry.xCursorPosition, motionEntry.yCursorPosition,
+ dispatchEntry.rawTransform, motionEntry.downTime,
+ motionEntry.eventTime, motionEntry.getPointerCount(),
+ motionEntry.pointerProperties.data(), usingCoords);
+}
+
+void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
+ const std::shared_ptr<Connection>& connection) {
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("startDispatchCycleLocked(inputChannel=%s)",
+ connection->getInputChannelName().c_str()));
if (DEBUG_DISPATCH_CYCLE) {
ALOGD("channel '%s' ~ startDispatchCycle", connection->getInputChannelName().c_str());
}
while (connection->status == Connection::Status::NORMAL && !connection->outboundQueue.empty()) {
- DispatchEntry* dispatchEntry = connection->outboundQueue.front();
+ std::unique_ptr<DispatchEntry>& dispatchEntry = connection->outboundQueue.front();
dispatchEntry->deliveryTime = currentTime;
const std::chrono::nanoseconds timeout = getDispatchingTimeoutLocked(connection);
dispatchEntry->timeoutTime = currentTime + timeout.count();
@@ -3242,73 +3654,29 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
case EventEntry::Type::KEY: {
const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
std::array<uint8_t, 32> hmac = getSignature(keyEntry, *dispatchEntry);
+ if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+ LOG(INFO) << "Publishing " << *dispatchEntry << " to "
+ << connection->getInputChannelName();
+ }
// Publish the key event.
status = connection->inputPublisher
- .publishKeyEvent(dispatchEntry->seq,
- dispatchEntry->resolvedEventId, keyEntry.deviceId,
- keyEntry.source, keyEntry.displayId,
- std::move(hmac), dispatchEntry->resolvedAction,
- dispatchEntry->resolvedFlags, keyEntry.keyCode,
- keyEntry.scanCode, keyEntry.metaState,
- keyEntry.repeatCount, keyEntry.downTime,
- keyEntry.eventTime);
+ .publishKeyEvent(dispatchEntry->seq, keyEntry.id,
+ keyEntry.deviceId, keyEntry.source,
+ keyEntry.displayId, std::move(hmac),
+ keyEntry.action, dispatchEntry->resolvedFlags,
+ keyEntry.keyCode, keyEntry.scanCode,
+ keyEntry.metaState, keyEntry.repeatCount,
+ keyEntry.downTime, keyEntry.eventTime);
break;
}
case EventEntry::Type::MOTION: {
- const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
-
- PointerCoords scaledCoords[MAX_POINTERS];
- const PointerCoords* usingCoords = motionEntry.pointerCoords;
-
- // Set the X and Y offset and X and Y scale depending on the input source.
- if ((motionEntry.source & AINPUT_SOURCE_CLASS_POINTER) &&
- !(dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS)) {
- float globalScaleFactor = dispatchEntry->globalScaleFactor;
- if (globalScaleFactor != 1.0f) {
- for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
- scaledCoords[i] = motionEntry.pointerCoords[i];
- // Don't apply window scale here since we don't want scale to affect raw
- // coordinates. The scale will be sent back to the client and applied
- // later when requesting relative coordinates.
- scaledCoords[i].scale(globalScaleFactor, 1 /* windowXScale */,
- 1 /* windowYScale */);
- }
- usingCoords = scaledCoords;
- }
- } else {
- // We don't want the dispatch target to know.
- if (dispatchEntry->targetFlags & InputTarget::FLAG_ZERO_COORDS) {
- for (uint32_t i = 0; i < motionEntry.pointerCount; i++) {
- scaledCoords[i].clear();
- }
- usingCoords = scaledCoords;
- }
+ if (DEBUG_OUTBOUND_EVENT_DETAILS) {
+ LOG(INFO) << "Publishing " << *dispatchEntry << " to "
+ << connection->getInputChannelName();
}
-
- std::array<uint8_t, 32> hmac = getSignature(motionEntry, *dispatchEntry);
-
- // Publish the motion event.
- status = connection->inputPublisher
- .publishMotionEvent(dispatchEntry->seq,
- dispatchEntry->resolvedEventId,
- motionEntry.deviceId, motionEntry.source,
- motionEntry.displayId, std::move(hmac),
- dispatchEntry->resolvedAction,
- motionEntry.actionButton,
- dispatchEntry->resolvedFlags,
- motionEntry.edgeFlags, motionEntry.metaState,
- motionEntry.buttonState,
- motionEntry.classification,
- dispatchEntry->transform,
- motionEntry.xPrecision, motionEntry.yPrecision,
- motionEntry.xCursorPosition,
- motionEntry.yCursorPosition,
- dispatchEntry->rawTransform,
- motionEntry.downTime, motionEntry.eventTime,
- motionEntry.pointerCount,
- motionEntry.pointerProperties, usingCoords);
+ status = publishMotionEvent(*connection, *dispatchEntry);
break;
}
@@ -3367,7 +3735,7 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
"event to it, status=%s(%d)",
connection->getInputChannelName().c_str(), statusToString(status).c_str(),
status);
- abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
} else {
// Pipe is full and we are waiting for the app to finish process some events
// before sending more events to it.
@@ -3382,20 +3750,18 @@ void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
"status=%s(%d)",
connection->getInputChannelName().c_str(), statusToString(status).c_str(),
status);
- abortBrokenDispatchCycleLocked(currentTime, connection, true /*notify*/);
+ abortBrokenDispatchCycleLocked(currentTime, connection, /*notify=*/true);
}
return;
}
// Re-enqueue the event on the wait queue.
- connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
- connection->outboundQueue.end(),
- dispatchEntry));
+ const nsecs_t timeoutTime = dispatchEntry->timeoutTime;
+ connection->waitQueue.emplace_back(std::move(dispatchEntry));
+ connection->outboundQueue.erase(connection->outboundQueue.begin());
traceOutboundQueueLength(*connection);
- connection->waitQueue.push_back(dispatchEntry);
if (connection->responsive) {
- mAnrTracker.insert(dispatchEntry->timeoutTime,
- connection->inputChannel->getConnectionToken());
+ mAnrTracker.insert(timeoutTime, connection->inputChannel->getConnectionToken());
}
traceWaitQueueLength(*connection);
}
@@ -3419,7 +3785,7 @@ std::array<uint8_t, 32> InputDispatcher::sign(const VerifiedInputEvent& event) c
const std::array<uint8_t, 32> InputDispatcher::getSignature(
const MotionEntry& motionEntry, const DispatchEntry& dispatchEntry) const {
- const int32_t actionMasked = dispatchEntry.resolvedAction & AMOTION_EVENT_ACTION_MASK;
+ const int32_t actionMasked = MotionEvent::getActionMasked(motionEntry.action);
if (actionMasked != AMOTION_EVENT_ACTION_UP && actionMasked != AMOTION_EVENT_ACTION_DOWN) {
// Only sign events up and down events as the purely move events
// are tied to their up/down counterparts so signing would be redundant.
@@ -3437,13 +3803,12 @@ const std::array<uint8_t, 32> InputDispatcher::getSignature(
const KeyEntry& keyEntry, const DispatchEntry& dispatchEntry) const {
VerifiedKeyEvent verifiedEvent = verifiedKeyEventFromKeyEntry(keyEntry);
verifiedEvent.flags = dispatchEntry.resolvedFlags & VERIFIED_KEY_EVENT_FLAGS;
- verifiedEvent.action = dispatchEntry.resolvedAction;
return sign(verifiedEvent);
}
void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection, uint32_t seq,
- bool handled, nsecs_t consumeTime) {
+ const std::shared_ptr<Connection>& connection,
+ uint32_t seq, bool handled, nsecs_t consumeTime) {
if (DEBUG_DISPATCH_CYCLE) {
ALOGD("channel '%s' ~ finishDispatchCycle - seq=%u, handled=%s",
connection->getInputChannelName().c_str(), seq, toString(handled));
@@ -3462,11 +3827,11 @@ void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,
}
void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
- const sp<Connection>& connection,
+ const std::shared_ptr<Connection>& connection,
bool notify) {
if (DEBUG_DISPATCH_CYCLE) {
- ALOGD("channel '%s' ~ abortBrokenDispatchCycle - notify=%s",
- connection->getInputChannelName().c_str(), toString(notify));
+ LOG(INFO) << "channel '" << connection->getInputChannelName() << "'~ " << __func__
+ << " - notify=" << toString(notify);
}
// Clear the dispatch queues.
@@ -3487,31 +3852,29 @@ void InputDispatcher::abortBrokenDispatchCycleLocked(nsecs_t currentTime,
auto command = [this, connection]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
+ mPolicy.notifyInputChannelBroken(connection->inputChannel->getConnectionToken());
};
postCommandLocked(std::move(command));
}
}
}
-void InputDispatcher::drainDispatchQueue(std::deque<DispatchEntry*>& queue) {
+void InputDispatcher::drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue) {
while (!queue.empty()) {
- DispatchEntry* dispatchEntry = queue.front();
+ releaseDispatchEntry(std::move(queue.front()));
queue.pop_front();
- releaseDispatchEntry(dispatchEntry);
}
}
-void InputDispatcher::releaseDispatchEntry(DispatchEntry* dispatchEntry) {
+void InputDispatcher::releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry) {
if (dispatchEntry->hasForegroundTarget()) {
decrementPendingForegroundDispatches(*(dispatchEntry->eventEntry));
}
- delete dispatchEntry;
}
int InputDispatcher::handleReceiveCallback(int events, sp<IBinder> connectionToken) {
std::scoped_lock _l(mLock);
- sp<Connection> connection = getConnectionLocked(connectionToken);
+ std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
if (connection == nullptr) {
ALOGW("Received looper callback for unknown input channel token %p. events=0x%x",
connectionToken.get(), events);
@@ -3603,7 +3966,7 @@ void InputDispatcher::synthesizeCancelationEventsForMonitorsLocked(
void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options) {
- sp<Connection> connection = getConnectionLocked(channel->getConnectionToken());
+ std::shared_ptr<Connection> connection = getConnectionLocked(channel->getConnectionToken());
if (connection == nullptr) {
return;
}
@@ -3612,7 +3975,7 @@ void InputDispatcher::synthesizeCancelationEventsForInputChannelLocked(
}
void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
- const sp<Connection>& connection, const CancelationOptions& options) {
+ const std::shared_ptr<Connection>& connection, const CancelationOptions& options) {
if (connection->status == Connection::Status::BROKEN) {
return;
}
@@ -3625,41 +3988,74 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
if (cancelationEvents.empty()) {
return;
}
+
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("channel '%s' ~ Synthesized %zu cancelation events to bring channel back in sync "
- "with reality: %s, mode=%d.",
+ "with reality: %s, mode=%s.",
connection->getInputChannelName().c_str(), cancelationEvents.size(), options.reason,
- options.mode);
+ ftl::enum_string(options.mode).c_str());
}
std::string reason = std::string("reason=").append(options.reason);
android_log_event_list(LOGTAG_INPUT_CANCEL)
<< connection->getInputChannelName().c_str() << reason << LOG_ID_EVENTS;
- InputTarget target;
- sp<WindowInfoHandle> windowHandle =
- getWindowHandleLocked(connection->inputChannel->getConnectionToken());
- if (windowHandle != nullptr) {
- const WindowInfo* windowInfo = windowHandle->getInfo();
- target.setDefaultPointerTransform(windowInfo->transform);
- target.globalScaleFactor = windowInfo->globalScaleFactor;
- }
- target.inputChannel = connection->inputChannel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
-
const bool wasEmpty = connection->outboundQueue.empty();
+ // The target to use if we don't find a window associated with the channel.
+ const InputTarget fallbackTarget{.inputChannel = connection->inputChannel};
+ const auto& token = connection->inputChannel->getConnectionToken();
for (size_t i = 0; i < cancelationEvents.size(); i++) {
std::unique_ptr<EventEntry> cancelationEventEntry = std::move(cancelationEvents[i]);
+ std::vector<InputTarget> targets{};
+
switch (cancelationEventEntry->type) {
case EventEntry::Type::KEY: {
- logOutboundKeyDetails("cancel - ",
- static_cast<const KeyEntry&>(*cancelationEventEntry));
+ const auto& keyEntry = static_cast<const KeyEntry&>(*cancelationEventEntry);
+ const std::optional<int32_t> targetDisplay = keyEntry.displayId != ADISPLAY_ID_NONE
+ ? std::make_optional(keyEntry.displayId)
+ : std::nullopt;
+ if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+ addWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS,
+ /*targetFlags=*/{}, keyEntry.downTime, targets);
+ } else {
+ targets.emplace_back(fallbackTarget);
+ }
+ logOutboundKeyDetails("cancel - ", keyEntry);
break;
}
case EventEntry::Type::MOTION: {
- logOutboundMotionDetails("cancel - ",
- static_cast<const MotionEntry&>(*cancelationEventEntry));
+ const auto& motionEntry = static_cast<const MotionEntry&>(*cancelationEventEntry);
+ const std::optional<int32_t> targetDisplay =
+ motionEntry.displayId != ADISPLAY_ID_NONE
+ ? std::make_optional(motionEntry.displayId)
+ : std::nullopt;
+ if (const auto& window = getWindowHandleLocked(token, targetDisplay); window) {
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
+ pointerIndex++) {
+ pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
+ }
+ if (mDragState && mDragState->dragWindow->getToken() == token &&
+ pointerIds.test(mDragState->pointerId)) {
+ LOG(INFO) << __func__
+ << ": Canceling drag and drop because the pointers for the drag "
+ "window are being canceled.";
+ sendDropWindowCommandLocked(nullptr, /*x=*/0, /*y=*/0);
+ mDragState.reset();
+ }
+ addPointerWindowTargetLocked(window, InputTarget::DispatchMode::AS_IS,
+ ftl::Flags<InputTarget::Flags>(), pointerIds,
+ motionEntry.downTime, targets);
+ } else {
+ targets.emplace_back(fallbackTarget);
+ const auto it = mDisplayInfos.find(motionEntry.displayId);
+ if (it != mDisplayInfos.end()) {
+ targets.back().displayTransform = it->second.transform;
+ targets.back().setDefaultPointerTransform(it->second.transform);
+ }
+ }
+ logOutboundMotionDetails("cancel - ", motionEntry);
break;
}
case EventEntry::Type::FOCUS:
@@ -3679,8 +4075,8 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
}
}
- enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), target,
- InputTarget::FLAG_DISPATCH_AS_IS);
+ if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+ enqueueDispatchEntryLocked(connection, std::move(cancelationEventEntry), targets[0]);
}
// If the outbound queue was previously empty, start the dispatch cycle going.
@@ -3690,15 +4086,14 @@ void InputDispatcher::synthesizeCancelationEventsForConnectionLocked(
}
void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
- const sp<Connection>& connection) {
+ const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
+ ftl::Flags<InputTarget::Flags> targetFlags) {
if (connection->status == Connection::Status::BROKEN) {
return;
}
- nsecs_t currentTime = now();
-
std::vector<std::unique_ptr<EventEntry>> downEvents =
- connection->inputState.synthesizePointerDownEvents(currentTime);
+ connection->inputState.synthesizePointerDownEvents(downTime);
if (downEvents.empty()) {
return;
@@ -3709,24 +4104,34 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
connection->getInputChannelName().c_str(), downEvents.size());
}
- InputTarget target;
sp<WindowInfoHandle> windowHandle =
getWindowHandleLocked(connection->inputChannel->getConnectionToken());
- if (windowHandle != nullptr) {
- const WindowInfo* windowInfo = windowHandle->getInfo();
- target.setDefaultPointerTransform(windowInfo->transform);
- target.globalScaleFactor = windowInfo->globalScaleFactor;
- }
- target.inputChannel = connection->inputChannel;
- target.flags = InputTarget::FLAG_DISPATCH_AS_IS;
const bool wasEmpty = connection->outboundQueue.empty();
-
for (std::unique_ptr<EventEntry>& downEventEntry : downEvents) {
+ std::vector<InputTarget> targets{};
switch (downEventEntry->type) {
case EventEntry::Type::MOTION: {
- logOutboundMotionDetails("down - ",
- static_cast<const MotionEntry&>(*downEventEntry));
+ const auto& motionEntry = static_cast<const MotionEntry&>(*downEventEntry);
+ if (windowHandle != nullptr) {
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ for (uint32_t pointerIndex = 0; pointerIndex < motionEntry.getPointerCount();
+ pointerIndex++) {
+ pointerIds.set(motionEntry.pointerProperties[pointerIndex].id);
+ }
+ addPointerWindowTargetLocked(windowHandle, InputTarget::DispatchMode::AS_IS,
+ targetFlags, pointerIds, motionEntry.downTime,
+ targets);
+ } else {
+ targets.emplace_back(InputTarget{.inputChannel = connection->inputChannel,
+ .flags = targetFlags});
+ const auto it = mDisplayInfos.find(motionEntry.displayId);
+ if (it != mDisplayInfos.end()) {
+ targets.back().displayTransform = it->second.transform;
+ targets.back().setDefaultPointerTransform(it->second.transform);
+ }
+ }
+ logOutboundMotionDetails("down - ", motionEntry);
break;
}
@@ -3744,24 +4149,37 @@ void InputDispatcher::synthesizePointerDownEventsForConnectionLocked(
}
}
- enqueueDispatchEntryLocked(connection, std::move(downEventEntry), target,
- InputTarget::FLAG_DISPATCH_AS_IS);
+ if (targets.size() != 1) LOG(FATAL) << __func__ << ": InputTarget not created";
+ enqueueDispatchEntryLocked(connection, std::move(downEventEntry), targets[0]);
}
+
// If the outbound queue was previously empty, start the dispatch cycle going.
if (wasEmpty && !connection->outboundQueue.empty()) {
- startDispatchCycleLocked(currentTime, connection);
+ startDispatchCycleLocked(downTime, connection);
+ }
+}
+
+void InputDispatcher::synthesizeCancelationEventsForWindowLocked(
+ const sp<WindowInfoHandle>& windowHandle, const CancelationOptions& options) {
+ if (windowHandle != nullptr) {
+ std::shared_ptr<Connection> wallpaperConnection =
+ getConnectionLocked(windowHandle->getToken());
+ if (wallpaperConnection != nullptr) {
+ synthesizeCancelationEventsForConnectionLocked(wallpaperConnection, options);
+ }
}
}
std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
- const MotionEntry& originalMotionEntry, BitSet32 pointerIds) {
- ALOG_ASSERT(pointerIds.value != 0);
+ const MotionEntry& originalMotionEntry, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t splitDownTime) {
+ ALOG_ASSERT(pointerIds.any());
uint32_t splitPointerIndexMap[MAX_POINTERS];
- PointerProperties splitPointerProperties[MAX_POINTERS];
- PointerCoords splitPointerCoords[MAX_POINTERS];
+ std::vector<PointerProperties> splitPointerProperties;
+ std::vector<PointerCoords> splitPointerCoords;
- uint32_t originalPointerCount = originalMotionEntry.pointerCount;
+ uint32_t originalPointerCount = originalMotionEntry.getPointerCount();
uint32_t splitPointerCount = 0;
for (uint32_t originalPointerIndex = 0; originalPointerIndex < originalPointerCount;
@@ -3769,11 +4187,10 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
const PointerProperties& pointerProperties =
originalMotionEntry.pointerProperties[originalPointerIndex];
uint32_t pointerId = uint32_t(pointerProperties.id);
- if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.test(pointerId)) {
splitPointerIndexMap[splitPointerCount] = originalPointerIndex;
- splitPointerProperties[splitPointerCount].copyFrom(pointerProperties);
- splitPointerCoords[splitPointerCount].copyFrom(
- originalMotionEntry.pointerCoords[originalPointerIndex]);
+ splitPointerProperties.push_back(pointerProperties);
+ splitPointerCoords.push_back(originalMotionEntry.pointerCoords[originalPointerIndex]);
splitPointerCount += 1;
}
}
@@ -3785,9 +4202,9 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
// or ACTION_POINTER_DOWN events that caused us to decide to split the pointers
// in this way.
ALOGW("Dropping split motion event because the pointer count is %d but "
- "we expected there to be %d pointers. This probably means we received "
- "a broken sequence of pointer ids from the input device.",
- splitPointerCount, pointerIds.count());
+ "we expected there to be %zu pointers. This probably means we received "
+ "a broken sequence of pointer ids from the input device: %s",
+ splitPointerCount, pointerIds.count(), originalMotionEntry.getDescription().c_str());
return nullptr;
}
@@ -3795,11 +4212,11 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
int32_t maskedAction = action & AMOTION_EVENT_ACTION_MASK;
if (maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN ||
maskedAction == AMOTION_EVENT_ACTION_POINTER_UP) {
- int32_t originalPointerIndex = getMotionEventActionPointerIndex(action);
+ int32_t originalPointerIndex = MotionEvent::getActionIndex(action);
const PointerProperties& pointerProperties =
originalMotionEntry.pointerProperties[originalPointerIndex];
uint32_t pointerId = uint32_t(pointerProperties.id);
- if (pointerIds.hasBit(pointerId)) {
+ if (pointerIds.test(pointerId)) {
if (pointerIds.count() == 1) {
// The first/last pointer went down/up.
action = maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN
@@ -3822,15 +4239,21 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
}
}
- int32_t newId = mIdGenerator.nextId();
- if (ATRACE_ENABLED()) {
- std::string message = StringPrintf("Split MotionEvent(id=0x%" PRIx32
- ") to MotionEvent(id=0x%" PRIx32 ").",
- originalMotionEntry.id, newId);
- ATRACE_NAME(message.c_str());
+ if (action == AMOTION_EVENT_ACTION_DOWN && splitDownTime != originalMotionEntry.eventTime) {
+ logDispatchStateLocked();
+ LOG_ALWAYS_FATAL("Split motion event has mismatching downTime and eventTime for "
+ "ACTION_DOWN, motionEntry=%s, splitDownTime=%" PRId64,
+ originalMotionEntry.getDescription().c_str(), splitDownTime);
}
+
+ int32_t newId = mIdGenerator.nextId();
+ ATRACE_NAME_IF(ATRACE_ENABLED(),
+ StringPrintf("Split MotionEvent(id=0x%" PRIx32 ") to MotionEvent(id=0x%" PRIx32
+ ").",
+ originalMotionEntry.id, newId));
std::unique_ptr<MotionEntry> splitMotionEntry =
- std::make_unique<MotionEntry>(newId, originalMotionEntry.eventTime,
+ std::make_unique<MotionEntry>(newId, originalMotionEntry.injectionState,
+ originalMotionEntry.eventTime,
originalMotionEntry.deviceId, originalMotionEntry.source,
originalMotionEntry.displayId,
originalMotionEntry.policyFlags, action,
@@ -3842,21 +4265,20 @@ std::unique_ptr<MotionEntry> InputDispatcher::splitMotionEvent(
originalMotionEntry.xPrecision,
originalMotionEntry.yPrecision,
originalMotionEntry.xCursorPosition,
- originalMotionEntry.yCursorPosition,
- originalMotionEntry.downTime, splitPointerCount,
+ originalMotionEntry.yCursorPosition, splitDownTime,
splitPointerProperties, splitPointerCoords);
- if (originalMotionEntry.injectionState) {
- splitMotionEntry->injectionState = originalMotionEntry.injectionState;
- splitMotionEntry->injectionState->refCount += 1;
- }
-
return splitMotionEntry;
}
-void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args->eventTime);
+void InputDispatcher::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ std::scoped_lock _l(mLock);
+ mLatencyTracker.setInputDevices(args.inputDeviceInfos);
+}
+
+void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+ if (debugInboundEventDetails()) {
+ ALOGD("notifyConfigurationChanged - eventTime=%" PRId64, args.eventTime);
}
bool needWake = false;
@@ -3864,7 +4286,7 @@ void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChange
std::scoped_lock _l(mLock);
std::unique_ptr<ConfigurationChangedEntry> newEntry =
- std::make_unique<ConfigurationChangedEntry>(args->id, args->eventTime);
+ std::make_unique<ConfigurationChangedEntry>(args.id, args.eventTime);
needWake = enqueueInboundEventLocked(std::move(newEntry));
} // release lock
@@ -3873,59 +4295,24 @@ void InputDispatcher::notifyConfigurationChanged(const NotifyConfigurationChange
}
}
-/**
- * If one of the meta shortcuts is detected, process them here:
- * Meta + Backspace -> generate BACK
- * Meta + Enter -> generate HOME
- * This will potentially overwrite keyCode and metaState.
- */
-void InputDispatcher::accelerateMetaShortcuts(const int32_t deviceId, const int32_t action,
- int32_t& keyCode, int32_t& metaState) {
- if (metaState & AMETA_META_ON && action == AKEY_EVENT_ACTION_DOWN) {
- int32_t newKeyCode = AKEYCODE_UNKNOWN;
- if (keyCode == AKEYCODE_DEL) {
- newKeyCode = AKEYCODE_BACK;
- } else if (keyCode == AKEYCODE_ENTER) {
- newKeyCode = AKEYCODE_HOME;
- }
- if (newKeyCode != AKEYCODE_UNKNOWN) {
- std::scoped_lock _l(mLock);
- struct KeyReplacement replacement = {keyCode, deviceId};
- mReplacedKeys[replacement] = newKeyCode;
- keyCode = newKeyCode;
- metaState &= ~(AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
- }
- } else if (action == AKEY_EVENT_ACTION_UP) {
- // In order to maintain a consistent stream of up and down events, check to see if the key
- // going up is one we've replaced in a down event and haven't yet replaced in an up event,
- // even if the modifier was released between the down and the up events.
- std::scoped_lock _l(mLock);
- struct KeyReplacement replacement = {keyCode, deviceId};
- auto replacementIt = mReplacedKeys.find(replacement);
- if (replacementIt != mReplacedKeys.end()) {
- keyCode = replacementIt->second;
- mReplacedKeys.erase(replacementIt);
- metaState &= ~(AMETA_META_ON | AMETA_META_LEFT_ON | AMETA_META_RIGHT_ON);
- }
- }
-}
-
-void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("notifyKey - eventTime=%" PRId64 ", deviceId=%d, source=0x%x, displayId=%" PRId32
- "policyFlags=0x%x, action=0x%x, "
- "flags=0x%x, keyCode=0x%x, scanCode=0x%x, metaState=0x%x, downTime=%" PRId64,
- args->eventTime, args->deviceId, args->source, args->displayId, args->policyFlags,
- args->action, args->flags, args->keyCode, args->scanCode, args->metaState,
- args->downTime);
- }
- if (!validateKeyEvent(args->action)) {
+void InputDispatcher::notifyKey(const NotifyKeyArgs& args) {
+ ALOGD_IF(debugInboundEventDetails(),
+ "notifyKey - id=%" PRIx32 ", eventTime=%" PRId64
+ ", deviceId=%d, source=%s, displayId=%" PRId32
+ "policyFlags=0x%x, action=%s, flags=0x%x, keyCode=%s, scanCode=0x%x, metaState=0x%x, "
+ "downTime=%" PRId64,
+ args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
+ args.displayId, args.policyFlags, KeyEvent::actionToString(args.action), args.flags,
+ KeyEvent::getLabel(args.keyCode), args.scanCode, args.metaState, args.downTime);
+ Result<void> keyCheck = validateKeyEvent(args.action);
+ if (!keyCheck.ok()) {
+ LOG(ERROR) << "invalid key event: " << keyCheck.error();
return;
}
- uint32_t policyFlags = args->policyFlags;
- int32_t flags = args->flags;
- int32_t metaState = args->metaState;
+ uint32_t policyFlags = args.policyFlags;
+ int32_t flags = args.flags;
+ int32_t metaState = args.metaState;
// InputDispatcher tracks and generates key repeats on behalf of
// whatever notifies it, so repeatCount should always be set to 0
constexpr int32_t repeatCount = 0;
@@ -3939,16 +4326,14 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
policyFlags |= POLICY_FLAG_TRUSTED;
- int32_t keyCode = args->keyCode;
- accelerateMetaShortcuts(args->deviceId, args->action, keyCode, metaState);
-
+ int32_t keyCode = args.keyCode;
KeyEvent event;
- event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
- args->action, flags, keyCode, args->scanCode, metaState, repeatCount,
- args->downTime, args->eventTime);
+ event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC, args.action,
+ flags, keyCode, args.scanCode, metaState, repeatCount, args.downTime,
+ args.eventTime);
android::base::Timer t;
- mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
+ mPolicy.interceptKeyBeforeQueueing(event, /*byref*/ policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
@@ -3962,7 +4347,7 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
mLock.unlock();
policyFlags |= POLICY_FLAG_FILTERED;
- if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ if (!mPolicy.filterInputEvent(event, policyFlags)) {
return; // event was consumed by the filter
}
@@ -3970,10 +4355,10 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
}
std::unique_ptr<KeyEntry> newEntry =
- std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source,
- args->displayId, policyFlags, args->action, flags,
- keyCode, args->scanCode, metaState, repeatCount,
- args->downTime);
+ std::make_unique<KeyEntry>(args.id, /*injectionState=*/nullptr, args.eventTime,
+ args.deviceId, args.source, args.displayId, policyFlags,
+ args.action, flags, keyCode, args.scanCode, metaState,
+ repeatCount, args.downTime);
needWake = enqueueInboundEventLocked(std::move(newEntry));
mLock.unlock();
@@ -3984,48 +4369,65 @@ void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
}
}
-bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) {
+bool InputDispatcher::shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) {
return mInputFilterEnabled;
}
-void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
+void InputDispatcher::notifyMotion(const NotifyMotionArgs& args) {
+ if (debugInboundEventDetails()) {
+ ALOGD("notifyMotion - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=%s, "
"displayId=%" PRId32 ", policyFlags=0x%x, "
- "action=0x%x, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
+ "action=%s, actionButton=0x%x, flags=0x%x, metaState=0x%x, buttonState=0x%x, "
"edgeFlags=0x%x, xPrecision=%f, yPrecision=%f, xCursorPosition=%f, "
"yCursorPosition=%f, downTime=%" PRId64,
- args->id, args->eventTime, args->deviceId, args->source, args->displayId,
- args->policyFlags, args->action, args->actionButton, args->flags, args->metaState,
- args->buttonState, args->edgeFlags, args->xPrecision, args->yPrecision,
- args->xCursorPosition, args->yCursorPosition, args->downTime);
- for (uint32_t i = 0; i < args->pointerCount; i++) {
- ALOGD(" Pointer %d: id=%d, toolType=%d, "
- "x=%f, y=%f, pressure=%f, size=%f, "
- "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, "
- "orientation=%f",
- i, args->pointerProperties[i].id, args->pointerProperties[i].toolType,
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
- args->pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
- }
- }
- if (!validateMotionEvent(args->action, args->actionButton, args->pointerCount,
- args->pointerProperties)) {
+ args.id, args.eventTime, args.deviceId, inputEventSourceToString(args.source).c_str(),
+ args.displayId, args.policyFlags, MotionEvent::actionToString(args.action).c_str(),
+ args.actionButton, args.flags, args.metaState, args.buttonState, args.edgeFlags,
+ args.xPrecision, args.yPrecision, args.xCursorPosition, args.yCursorPosition,
+ args.downTime);
+ for (uint32_t i = 0; i < args.getPointerCount(); i++) {
+ ALOGD(" Pointer %d: id=%d, toolType=%s, x=%f, y=%f, pressure=%f, size=%f, "
+ "touchMajor=%f, touchMinor=%f, toolMajor=%f, toolMinor=%f, orientation=%f",
+ i, args.pointerProperties[i].id,
+ ftl::enum_string(args.pointerProperties[i].toolType).c_str(),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_X),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_Y),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_PRESSURE),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_SIZE),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR),
+ args.pointerCoords[i].getAxisValue(AMOTION_EVENT_AXIS_ORIENTATION));
+ }
+ }
+
+ Result<void> motionCheck =
+ validateMotionEvent(args.action, args.actionButton, args.getPointerCount(),
+ args.pointerProperties.data());
+ if (!motionCheck.ok()) {
+ LOG(FATAL) << "Invalid event: " << args.dump() << "; reason: " << motionCheck.error();
return;
}
- uint32_t policyFlags = args->policyFlags;
+ if (DEBUG_VERIFY_EVENTS) {
+ auto [it, _] =
+ mVerifiersByDisplay.try_emplace(args.displayId,
+ StringPrintf("display %" PRId32, args.displayId));
+ Result<void> result =
+ it->second.processMovement(args.deviceId, args.source, args.action,
+ args.getPointerCount(), args.pointerProperties.data(),
+ args.pointerCoords.data(), args.flags);
+ if (!result.ok()) {
+ LOG(FATAL) << "Bad stream: " << result.error() << " caused by " << args.dump();
+ }
+ }
+
+ uint32_t policyFlags = args.policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;
android::base::Timer t;
- mPolicy->interceptMotionBeforeQueueing(args->displayId, args->eventTime, /*byref*/ policyFlags);
+ mPolicy.interceptMotionBeforeQueueing(args.displayId, args.eventTime, policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
@@ -4034,26 +4436,38 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
bool needWake = false;
{ // acquire lock
mLock.lock();
+ if (!(policyFlags & POLICY_FLAG_PASS_TO_USER)) {
+ // Set the flag anyway if we already have an ongoing gesture. That would allow us to
+ // complete the processing of the current stroke.
+ const auto touchStateIt = mTouchStatesByDisplay.find(args.displayId);
+ if (touchStateIt != mTouchStatesByDisplay.end()) {
+ const TouchState& touchState = touchStateIt->second;
+ if (touchState.hasTouchingPointers(args.deviceId) ||
+ touchState.hasHoveringPointers(args.deviceId)) {
+ policyFlags |= POLICY_FLAG_PASS_TO_USER;
+ }
+ }
+ }
if (shouldSendMotionToInputFilterLocked(args)) {
ui::Transform displayTransform;
- if (const auto it = mDisplayInfos.find(args->displayId); it != mDisplayInfos.end()) {
+ if (const auto it = mDisplayInfos.find(args.displayId); it != mDisplayInfos.end()) {
displayTransform = it->second.transform;
}
mLock.unlock();
MotionEvent event;
- event.initialize(args->id, args->deviceId, args->source, args->displayId, INVALID_HMAC,
- args->action, args->actionButton, args->flags, args->edgeFlags,
- args->metaState, args->buttonState, args->classification,
- displayTransform, args->xPrecision, args->yPrecision,
- args->xCursorPosition, args->yCursorPosition, displayTransform,
- args->downTime, args->eventTime, args->pointerCount,
- args->pointerProperties, args->pointerCoords);
+ event.initialize(args.id, args.deviceId, args.source, args.displayId, INVALID_HMAC,
+ args.action, args.actionButton, args.flags, args.edgeFlags,
+ args.metaState, args.buttonState, args.classification,
+ displayTransform, args.xPrecision, args.yPrecision,
+ args.xCursorPosition, args.yCursorPosition, displayTransform,
+ args.downTime, args.eventTime, args.getPointerCount(),
+ args.pointerProperties.data(), args.pointerCoords.data());
policyFlags |= POLICY_FLAG_FILTERED;
- if (!mPolicy->filterInputEvent(&event, policyFlags)) {
+ if (!mPolicy.filterInputEvent(event, policyFlags)) {
return; // event was consumed by the filter
}
@@ -4062,21 +4476,22 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
// Just enqueue a new motion event.
std::unique_ptr<MotionEntry> newEntry =
- std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
- args->source, args->displayId, policyFlags,
- args->action, args->actionButton, args->flags,
- args->metaState, args->buttonState,
- args->classification, args->edgeFlags,
- args->xPrecision, args->yPrecision,
- args->xCursorPosition, args->yCursorPosition,
- args->downTime, args->pointerCount,
- args->pointerProperties, args->pointerCoords);
-
- if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
- IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER &&
+ std::make_unique<MotionEntry>(args.id, /*injectionState=*/nullptr, args.eventTime,
+ args.deviceId, args.source, args.displayId,
+ policyFlags, args.action, args.actionButton,
+ args.flags, args.metaState, args.buttonState,
+ args.classification, args.edgeFlags, args.xPrecision,
+ args.yPrecision, args.xCursorPosition,
+ args.yCursorPosition, args.downTime,
+ args.pointerProperties, args.pointerCoords);
+
+ if (args.id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
+ IdGenerator::getSource(args.id) == IdGenerator::Source::INPUT_READER &&
!mInputFilterEnabled) {
- const bool isDown = args->action == AMOTION_EVENT_ACTION_DOWN;
- mLatencyTracker.trackListener(args->id, isDown, args->eventTime, args->readTime);
+ const bool isDown = args.action == AMOTION_EVENT_ACTION_DOWN;
+ std::set<InputDeviceUsageSource> sources = getUsageSourcesForMotionArgs(args);
+ mLatencyTracker.trackListener(args.id, isDown, args.eventTime, args.readTime,
+ args.deviceId, sources);
}
needWake = enqueueInboundEventLocked(std::move(newEntry));
@@ -4088,12 +4503,12 @@ void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
}
}
-void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
+void InputDispatcher::notifySensor(const NotifySensorArgs& args) {
+ if (debugInboundEventDetails()) {
ALOGD("notifySensor - id=%" PRIx32 " eventTime=%" PRId64 ", deviceId=%d, source=0x%x, "
" sensorType=%s",
- args->id, args->eventTime, args->deviceId, args->source,
- ftl::enum_string(args->sensorType).c_str());
+ args.id, args.eventTime, args.deviceId, args.source,
+ ftl::enum_string(args.sensorType).c_str());
}
bool needWake = false;
@@ -4102,10 +4517,9 @@ void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
// Just enqueue a new sensor event.
std::unique_ptr<SensorEntry> newEntry =
- std::make_unique<SensorEntry>(args->id, args->eventTime, args->deviceId,
- args->source, 0 /* policyFlags*/, args->hwTimestamp,
- args->sensorType, args->accuracy,
- args->accuracyChanged, args->values);
+ std::make_unique<SensorEntry>(args.id, args.eventTime, args.deviceId, args.source,
+ /* policyFlags=*/0, args.hwTimestamp, args.sensorType,
+ args.accuracy, args.accuracyChanged, args.values);
needWake = enqueueInboundEventLocked(std::move(newEntry));
mLock.unlock();
@@ -4116,34 +4530,34 @@ void InputDispatcher::notifySensor(const NotifySensorArgs* args) {
}
}
-void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d, isOn=%d", args->eventTime,
- args->deviceId, args->isOn);
+void InputDispatcher::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ if (debugInboundEventDetails()) {
+ ALOGD("notifyVibratorState - eventTime=%" PRId64 ", device=%d, isOn=%d", args.eventTime,
+ args.deviceId, args.isOn);
}
- mPolicy->notifyVibratorState(args->deviceId, args->isOn);
+ mPolicy.notifyVibratorState(args.deviceId, args.isOn);
}
-bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) {
+bool InputDispatcher::shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) {
return mInputFilterEnabled;
}
-void InputDispatcher::notifySwitch(const NotifySwitchArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
+void InputDispatcher::notifySwitch(const NotifySwitchArgs& args) {
+ if (debugInboundEventDetails()) {
ALOGD("notifySwitch - eventTime=%" PRId64 ", policyFlags=0x%x, switchValues=0x%08x, "
"switchMask=0x%08x",
- args->eventTime, args->policyFlags, args->switchValues, args->switchMask);
+ args.eventTime, args.policyFlags, args.switchValues, args.switchMask);
}
- uint32_t policyFlags = args->policyFlags;
+ uint32_t policyFlags = args.policyFlags;
policyFlags |= POLICY_FLAG_TRUSTED;
- mPolicy->notifySwitch(args->eventTime, args->switchValues, args->switchMask, policyFlags);
+ mPolicy.notifySwitch(args.eventTime, args.switchValues, args.switchMask, policyFlags);
}
-void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args->eventTime,
- args->deviceId);
+void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ if (debugInboundEventDetails()) {
+ ALOGD("notifyDeviceReset - eventTime=%" PRId64 ", deviceId=%d", args.eventTime,
+ args.deviceId);
}
bool needWake = false;
@@ -4151,8 +4565,12 @@ void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
std::scoped_lock _l(mLock);
std::unique_ptr<DeviceResetEntry> newEntry =
- std::make_unique<DeviceResetEntry>(args->id, args->eventTime, args->deviceId);
+ std::make_unique<DeviceResetEntry>(args.id, args.eventTime, args.deviceId);
needWake = enqueueInboundEventLocked(std::move(newEntry));
+
+ for (auto& [_, verifier] : mVerifiersByDisplay) {
+ verifier.resetDevice(args.deviceId);
+ }
} // release lock
if (needWake) {
@@ -4160,17 +4578,17 @@ void InputDispatcher::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
}
}
-void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args->eventTime,
- args->request.enable ? "true" : "false");
+void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+ if (debugInboundEventDetails()) {
+ ALOGD("notifyPointerCaptureChanged - eventTime=%" PRId64 ", enabled=%s", args.eventTime,
+ args.request.enable ? "true" : "false");
}
bool needWake = false;
{ // acquire lock
std::scoped_lock _l(mLock);
- auto entry = std::make_unique<PointerCaptureChangedEntry>(args->id, args->eventTime,
- args->request);
+ auto entry =
+ std::make_unique<PointerCaptureChangedEntry>(args.id, args.eventTime, args.request);
needWake = enqueueInboundEventLocked(std::move(entry));
} // release lock
@@ -4180,15 +4598,21 @@ void InputDispatcher::notifyPointerCaptureChanged(const NotifyPointerCaptureChan
}
InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* event,
- std::optional<int32_t> targetUid,
+ std::optional<gui::Uid> targetUid,
InputEventInjectionSync syncMode,
std::chrono::milliseconds timeout,
uint32_t policyFlags) {
- if (DEBUG_INBOUND_EVENT_DETAILS) {
- ALOGD("injectInputEvent - eventType=%d, targetUid=%s, syncMode=%d, timeout=%lld, "
- "policyFlags=0x%08x",
- event->getType(), targetUid ? std::to_string(*targetUid).c_str() : "none", syncMode,
- timeout.count(), policyFlags);
+ Result<void> eventValidation = validateInputEvent(*event);
+ if (!eventValidation.ok()) {
+ LOG(INFO) << "Injection failed: invalid event: " << eventValidation.error();
+ return InputEventInjectionResult::FAILED;
+ }
+
+ if (debugInboundEventDetails()) {
+ LOG(INFO) << __func__ << ": targetUid=" << toString(targetUid, &uidString)
+ << ", syncMode=" << ftl::enum_string(syncMode) << ", timeout=" << timeout.count()
+ << "ms, policyFlags=0x" << std::hex << policyFlags << std::dec
+ << ", event=" << *event;
}
nsecs_t endTime = now() + std::chrono::duration_cast<std::chrono::nanoseconds>(timeout).count();
@@ -4200,28 +4624,25 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
// the injected event, it is responsible for setting POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY.
// For those events, we will set FLAG_IS_ACCESSIBILITY_EVENT to allow apps to distinguish them
// from events that originate from actual hardware.
- int32_t resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
+ DeviceId resolvedDeviceId = VIRTUAL_KEYBOARD_ID;
if (policyFlags & POLICY_FLAG_FILTERED) {
resolvedDeviceId = event->getDeviceId();
}
+ const bool isAsync = syncMode == InputEventInjectionSync::NONE;
+ auto injectionState = std::make_shared<InjectionState>(targetUid, isAsync);
+
std::queue<std::unique_ptr<EventEntry>> injectedEntries;
switch (event->getType()) {
- case AINPUT_EVENT_TYPE_KEY: {
+ case InputEventType::KEY: {
const KeyEvent& incomingKey = static_cast<const KeyEvent&>(*event);
- int32_t action = incomingKey.getAction();
- if (!validateKeyEvent(action)) {
- return InputEventInjectionResult::FAILED;
- }
-
+ const int32_t action = incomingKey.getAction();
int32_t flags = incomingKey.getFlags();
if (policyFlags & POLICY_FLAG_INJECTED_FROM_ACCESSIBILITY) {
flags |= AKEY_EVENT_FLAG_IS_ACCESSIBILITY_EVENT;
}
int32_t keyCode = incomingKey.getKeyCode();
int32_t metaState = incomingKey.getMetaState();
- accelerateMetaShortcuts(resolvedDeviceId, action,
- /*byref*/ keyCode, /*byref*/ metaState);
KeyEvent keyEvent;
keyEvent.initialize(incomingKey.getId(), resolvedDeviceId, incomingKey.getSource(),
incomingKey.getDisplayId(), INVALID_HMAC, action, flags, keyCode,
@@ -4234,7 +4655,7 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
if (!(policyFlags & POLICY_FLAG_FILTERED)) {
android::base::Timer t;
- mPolicy->interceptKeyBeforeQueueing(&keyEvent, /*byref*/ policyFlags);
+ mPolicy.interceptKeyBeforeQueueing(keyEvent, /*byref*/ policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptKeyBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
@@ -4243,37 +4664,31 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
mLock.lock();
std::unique_ptr<KeyEntry> injectedEntry =
- std::make_unique<KeyEntry>(incomingKey.getId(), incomingKey.getEventTime(),
- resolvedDeviceId, incomingKey.getSource(),
- incomingKey.getDisplayId(), policyFlags, action,
- flags, keyCode, incomingKey.getScanCode(), metaState,
+ std::make_unique<KeyEntry>(incomingKey.getId(), injectionState,
+ incomingKey.getEventTime(), resolvedDeviceId,
+ incomingKey.getSource(), incomingKey.getDisplayId(),
+ policyFlags, action, flags, keyCode,
+ incomingKey.getScanCode(), metaState,
incomingKey.getRepeatCount(),
incomingKey.getDownTime());
injectedEntries.push(std::move(injectedEntry));
break;
}
- case AINPUT_EVENT_TYPE_MOTION: {
+ case InputEventType::MOTION: {
const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
- const int32_t action = motionEvent.getAction();
const bool isPointerEvent =
isFromSource(event->getSource(), AINPUT_SOURCE_CLASS_POINTER);
// If a pointer event has no displayId specified, inject it to the default display.
- const uint32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE)
+ const int32_t displayId = isPointerEvent && (event->getDisplayId() == ADISPLAY_ID_NONE)
? ADISPLAY_ID_DEFAULT
: event->getDisplayId();
- const size_t pointerCount = motionEvent.getPointerCount();
- const PointerProperties* pointerProperties = motionEvent.getPointerProperties();
- const int32_t actionButton = motionEvent.getActionButton();
int32_t flags = motionEvent.getFlags();
- if (!validateMotionEvent(action, actionButton, pointerCount, pointerProperties)) {
- return InputEventInjectionResult::FAILED;
- }
if (!(policyFlags & POLICY_FLAG_FILTERED)) {
nsecs_t eventTime = motionEvent.getEventTime();
android::base::Timer t;
- mPolicy->interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
+ mPolicy.interceptMotionBeforeQueueing(displayId, eventTime, /*byref*/ policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptMotionBeforeQueueing; took %s ms",
std::to_string(t.duration().count()).c_str());
@@ -4286,12 +4701,19 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
mLock.lock();
const nsecs_t* sampleEventTimes = motionEvent.getSampleEventTimes();
+ const size_t pointerCount = motionEvent.getPointerCount();
+ const std::vector<PointerProperties>
+ pointerProperties(motionEvent.getPointerProperties(),
+ motionEvent.getPointerProperties() + pointerCount);
+
const PointerCoords* samplePointerCoords = motionEvent.getSamplePointerCoords();
std::unique_ptr<MotionEntry> injectedEntry =
- std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
- resolvedDeviceId, motionEvent.getSource(),
- displayId, policyFlags, action, actionButton,
- flags, motionEvent.getMetaState(),
+ std::make_unique<MotionEntry>(motionEvent.getId(), injectionState,
+ *sampleEventTimes, resolvedDeviceId,
+ motionEvent.getSource(), displayId, policyFlags,
+ motionEvent.getAction(),
+ motionEvent.getActionButton(), flags,
+ motionEvent.getMetaState(),
motionEvent.getButtonState(),
motionEvent.getClassification(),
motionEvent.getEdgeFlags(),
@@ -4299,28 +4721,29 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
motionEvent.getYPrecision(),
motionEvent.getRawXCursorPosition(),
motionEvent.getRawYCursorPosition(),
- motionEvent.getDownTime(), uint32_t(pointerCount),
- pointerProperties, samplePointerCoords);
+ motionEvent.getDownTime(), pointerProperties,
+ std::vector<PointerCoords>(samplePointerCoords,
+ samplePointerCoords +
+ pointerCount));
transformMotionEntryForInjectionLocked(*injectedEntry, motionEvent.getTransform());
injectedEntries.push(std::move(injectedEntry));
for (size_t i = motionEvent.getHistorySize(); i > 0; i--) {
sampleEventTimes += 1;
- samplePointerCoords += pointerCount;
- std::unique_ptr<MotionEntry> nextInjectedEntry =
- std::make_unique<MotionEntry>(motionEvent.getId(), *sampleEventTimes,
- resolvedDeviceId, motionEvent.getSource(),
- displayId, policyFlags, action, actionButton,
- flags, motionEvent.getMetaState(),
- motionEvent.getButtonState(),
- motionEvent.getClassification(),
- motionEvent.getEdgeFlags(),
- motionEvent.getXPrecision(),
- motionEvent.getYPrecision(),
- motionEvent.getRawXCursorPosition(),
- motionEvent.getRawYCursorPosition(),
- motionEvent.getDownTime(),
- uint32_t(pointerCount), pointerProperties,
- samplePointerCoords);
+ samplePointerCoords += motionEvent.getPointerCount();
+ std::unique_ptr<MotionEntry> nextInjectedEntry = std::make_unique<
+ MotionEntry>(motionEvent.getId(), injectionState, *sampleEventTimes,
+ resolvedDeviceId, motionEvent.getSource(), displayId,
+ policyFlags, motionEvent.getAction(),
+ motionEvent.getActionButton(), flags,
+ motionEvent.getMetaState(), motionEvent.getButtonState(),
+ motionEvent.getClassification(), motionEvent.getEdgeFlags(),
+ motionEvent.getXPrecision(), motionEvent.getYPrecision(),
+ motionEvent.getRawXCursorPosition(),
+ motionEvent.getRawYCursorPosition(), motionEvent.getDownTime(),
+ pointerProperties,
+ std::vector<PointerCoords>(samplePointerCoords,
+ samplePointerCoords +
+ pointerCount));
transformMotionEntryForInjectionLocked(*nextInjectedEntry,
motionEvent.getTransform());
injectedEntries.push(std::move(nextInjectedEntry));
@@ -4329,20 +4752,15 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
}
default:
- ALOGW("Cannot inject %s events", inputEventTypeToString(event->getType()));
+ LOG(WARNING) << "Cannot inject " << ftl::enum_string(event->getType()) << " events";
return InputEventInjectionResult::FAILED;
}
- InjectionState* injectionState = new InjectionState(targetUid);
- if (syncMode == InputEventInjectionSync::NONE) {
- injectionState->injectionIsAsync = true;
- }
-
- injectionState->refCount += 1;
- injectedEntries.back()->injectionState = injectionState;
-
bool needWake = false;
while (!injectedEntries.empty()) {
+ if (DEBUG_INJECTION) {
+ LOG(INFO) << "Injecting " << injectedEntries.front()->getDescription();
+ }
needWake |= enqueueInboundEventLocked(std::move(injectedEntries.front()));
injectedEntries.pop();
}
@@ -4400,12 +4818,11 @@ InputEventInjectionResult InputDispatcher::injectInputEvent(const InputEvent* ev
}
}
}
-
- injectionState->release();
} // release lock
if (DEBUG_INJECTION) {
- ALOGD("injectInputEvent - Finished with result %d.", injectionResult);
+ LOG(INFO) << "injectInputEvent - Finished with result "
+ << ftl::enum_string(injectionResult);
}
return injectionResult;
@@ -4415,14 +4832,14 @@ std::unique_ptr<VerifiedInputEvent> InputDispatcher::verifyInputEvent(const Inpu
std::array<uint8_t, 32> calculatedHmac;
std::unique_ptr<VerifiedInputEvent> result;
switch (event.getType()) {
- case AINPUT_EVENT_TYPE_KEY: {
+ case InputEventType::KEY: {
const KeyEvent& keyEvent = static_cast<const KeyEvent&>(event);
VerifiedKeyEvent verifiedKeyEvent = verifiedKeyEventFromKeyEvent(keyEvent);
result = std::make_unique<VerifiedKeyEvent>(verifiedKeyEvent);
calculatedHmac = sign(verifiedKeyEvent);
break;
}
- case AINPUT_EVENT_TYPE_MOTION: {
+ case InputEventType::MOTION: {
const MotionEvent& motionEvent = static_cast<const MotionEvent&>(event);
VerifiedMotionEvent verifiedMotionEvent =
verifiedMotionEventFromMotionEvent(motionEvent);
@@ -4438,44 +4855,48 @@ std::unique_ptr<VerifiedInputEvent> InputDispatcher::verifyInputEvent(const Inpu
if (calculatedHmac == INVALID_HMAC) {
return nullptr;
}
- if (calculatedHmac != event.getHmac()) {
+ if (0 != CRYPTO_memcmp(calculatedHmac.data(), event.getHmac().data(), calculatedHmac.size())) {
return nullptr;
}
return result;
}
-void InputDispatcher::setInjectionResult(EventEntry& entry,
+void InputDispatcher::setInjectionResult(const EventEntry& entry,
InputEventInjectionResult injectionResult) {
- InjectionState* injectionState = entry.injectionState;
- if (injectionState) {
- if (DEBUG_INJECTION) {
- ALOGD("Setting input event injection result to %d.", injectionResult);
- }
+ if (!entry.injectionState) {
+ // Not an injected event.
+ return;
+ }
- if (injectionState->injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
- // Log the outcome since the injector did not wait for the injection result.
- switch (injectionResult) {
- case InputEventInjectionResult::SUCCEEDED:
- ALOGV("Asynchronous input event injection succeeded.");
- break;
- case InputEventInjectionResult::TARGET_MISMATCH:
- ALOGV("Asynchronous input event injection target mismatch.");
- break;
- case InputEventInjectionResult::FAILED:
- ALOGW("Asynchronous input event injection failed.");
- break;
- case InputEventInjectionResult::TIMED_OUT:
- ALOGW("Asynchronous input event injection timed out.");
- break;
- case InputEventInjectionResult::PENDING:
- ALOGE("Setting result to 'PENDING' for asynchronous injection");
- break;
- }
- }
+ InjectionState& injectionState = *entry.injectionState;
+ if (DEBUG_INJECTION) {
+ LOG(INFO) << "Setting input event injection result to "
+ << ftl::enum_string(injectionResult);
+ }
- injectionState->injectionResult = injectionResult;
- mInjectionResultAvailable.notify_all();
+ if (injectionState.injectionIsAsync && !(entry.policyFlags & POLICY_FLAG_FILTERED)) {
+ // Log the outcome since the injector did not wait for the injection result.
+ switch (injectionResult) {
+ case InputEventInjectionResult::SUCCEEDED:
+ ALOGV("Asynchronous input event injection succeeded.");
+ break;
+ case InputEventInjectionResult::TARGET_MISMATCH:
+ ALOGV("Asynchronous input event injection target mismatch.");
+ break;
+ case InputEventInjectionResult::FAILED:
+ ALOGW("Asynchronous input event injection failed.");
+ break;
+ case InputEventInjectionResult::TIMED_OUT:
+ ALOGW("Asynchronous input event injection timed out.");
+ break;
+ case InputEventInjectionResult::PENDING:
+ ALOGE("Setting result to 'PENDING' for asynchronous injection");
+ break;
+ }
}
+
+ injectionState.injectionResult = injectionResult;
+ mInjectionResultAvailable.notify_all();
}
void InputDispatcher::transformMotionEntryForInjectionLocked(
@@ -4494,26 +4915,24 @@ void InputDispatcher::transformMotionEntryForInjectionLocked(
entry.xCursorPosition = cursor.x;
entry.yCursorPosition = cursor.y;
}
- for (uint32_t i = 0; i < entry.pointerCount; i++) {
+ for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
entry.pointerCoords[i] =
MotionEvent::calculateTransformedCoords(entry.source, transformToDisplay,
entry.pointerCoords[i]);
}
}
-void InputDispatcher::incrementPendingForegroundDispatches(EventEntry& entry) {
- InjectionState* injectionState = entry.injectionState;
- if (injectionState) {
- injectionState->pendingForegroundDispatches += 1;
+void InputDispatcher::incrementPendingForegroundDispatches(const EventEntry& entry) {
+ if (entry.injectionState) {
+ entry.injectionState->pendingForegroundDispatches += 1;
}
}
-void InputDispatcher::decrementPendingForegroundDispatches(EventEntry& entry) {
- InjectionState* injectionState = entry.injectionState;
- if (injectionState) {
- injectionState->pendingForegroundDispatches -= 1;
+void InputDispatcher::decrementPendingForegroundDispatches(const EventEntry& entry) {
+ if (entry.injectionState) {
+ entry.injectionState->pendingForegroundDispatches -= 1;
- if (injectionState->pendingForegroundDispatches == 0) {
+ if (entry.injectionState->pendingForegroundDispatches == 0) {
mInjectionSyncFinished.notify_all();
}
}
@@ -4527,29 +4946,26 @@ const std::vector<sp<WindowInfoHandle>>& InputDispatcher::getWindowHandlesLocked
}
sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(
- const sp<IBinder>& windowHandleToken) const {
+ const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId) const {
if (windowHandleToken == nullptr) {
return nullptr;
}
- for (auto& it : mWindowHandlesByDisplay) {
- const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
- for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
- if (windowHandle->getToken() == windowHandleToken) {
- return windowHandle;
+ if (!displayId) {
+ // Look through all displays.
+ for (auto& it : mWindowHandlesByDisplay) {
+ const std::vector<sp<WindowInfoHandle>>& windowHandles = it.second;
+ for (const sp<WindowInfoHandle>& windowHandle : windowHandles) {
+ if (windowHandle->getToken() == windowHandleToken) {
+ return windowHandle;
+ }
}
}
- }
- return nullptr;
-}
-
-sp<WindowInfoHandle> InputDispatcher::getWindowHandleLocked(const sp<IBinder>& windowHandleToken,
- int displayId) const {
- if (windowHandleToken == nullptr) {
return nullptr;
}
- for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(displayId)) {
+ // Only look through the requested display.
+ for (const sp<WindowInfoHandle>& windowHandle : getWindowHandlesLocked(*displayId)) {
if (windowHandle->getToken() == windowHandleToken) {
return windowHandle;
}
@@ -4582,26 +4998,64 @@ sp<WindowInfoHandle> InputDispatcher::getFocusedWindowHandleLocked(int displayId
return getWindowHandleLocked(focusedToken, displayId);
}
-bool InputDispatcher::hasResponsiveConnectionLocked(WindowInfoHandle& windowHandle) const {
- sp<Connection> connection = getConnectionLocked(windowHandle.getToken());
- const bool noInputChannel =
- windowHandle.getInfo()->inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL);
- if (connection != nullptr && noInputChannel) {
- ALOGW("%s has feature NO_INPUT_CHANNEL, but it matched to connection %s",
- windowHandle.getName().c_str(), connection->inputChannel->getName().c_str());
+ui::Transform InputDispatcher::getTransformLocked(int32_t displayId) const {
+ auto displayInfoIt = mDisplayInfos.find(displayId);
+ return displayInfoIt != mDisplayInfos.end() ? displayInfoIt->second.transform
+ : kIdentityTransform;
+}
+
+bool InputDispatcher::canWindowReceiveMotionLocked(const sp<WindowInfoHandle>& window,
+ const MotionEntry& motionEntry) const {
+ const WindowInfo& info = *window->getInfo();
+
+ // Skip spy window targets that are not valid for targeted injection.
+ if (const auto err = verifyTargetedInjection(window, motionEntry); err) {
return false;
}
+ if (info.inputConfig.test(WindowInfo::InputConfig::PAUSE_DISPATCHING)) {
+ ALOGI("Not sending touch event to %s because it is paused", window->getName().c_str());
+ return false;
+ }
+
+ if (info.inputConfig.test(WindowInfo::InputConfig::NO_INPUT_CHANNEL)) {
+ ALOGW("Not sending touch gesture to %s because it has config NO_INPUT_CHANNEL",
+ window->getName().c_str());
+ return false;
+ }
+
+ std::shared_ptr<Connection> connection = getConnectionLocked(window->getToken());
if (connection == nullptr) {
- if (!noInputChannel) {
- ALOGI("Could not find connection for %s", windowHandle.getName().c_str());
- }
+ ALOGW("Not sending touch to %s because there's no corresponding connection",
+ window->getName().c_str());
return false;
}
+
if (!connection->responsive) {
- ALOGW("Window %s is not responsive", windowHandle.getName().c_str());
+ ALOGW("Not sending touch to %s because it is not responsive", window->getName().c_str());
+ return false;
+ }
+
+ // Drop events that can't be trusted due to occlusion
+ const auto [x, y] = resolveTouchedPosition(motionEntry);
+ TouchOcclusionInfo occlusionInfo = computeTouchOcclusionInfoLocked(window, x, y);
+ if (!isTouchTrustedLocked(occlusionInfo)) {
+ if (DEBUG_TOUCH_OCCLUSION) {
+ ALOGD("Stack of obscuring windows during untrusted touch (%.1f, %.1f):", x, y);
+ for (const auto& log : occlusionInfo.debugInfo) {
+ ALOGD("%s", log.c_str());
+ }
+ }
+ ALOGW("Dropping untrusted touch event due to %s/%s", occlusionInfo.obscuringPackage.c_str(),
+ occlusionInfo.obscuringUid.toString().c_str());
+ return false;
+ }
+
+ // Drop touch events if requested by input feature
+ if (shouldDropInput(motionEntry, window)) {
return false;
}
+
return true;
}
@@ -4666,19 +5120,6 @@ void InputDispatcher::updateWindowHandlesForDisplayLocked(
mWindowHandlesByDisplay[displayId] = newHandles;
}
-void InputDispatcher::setInputWindows(
- const std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>>& handlesPerDisplay) {
- // TODO(b/198444055): Remove setInputWindows from InputDispatcher.
- { // acquire lock
- std::scoped_lock _l(mLock);
- for (const auto& [displayId, handles] : handlesPerDisplay) {
- setInputWindowsLocked(handles, displayId);
- }
- }
- // Wake up poll loop since it may need to make new input dispatching choices.
- mLooper->wake();
-}
-
/**
* Called from InputManagerService, update window handle list by displayId that can receive input.
* A window handle contains information about InputChannel, Touch Region, Types, Focused,...
@@ -4693,7 +5134,7 @@ void InputDispatcher::setInputWindowsLocked(
for (const sp<WindowInfoHandle>& iwh : windowInfoHandles) {
windowList += iwh->getName() + " ";
}
- ALOGD("setInputWindows displayId=%" PRId32 " %s", displayId, windowList.c_str());
+ LOG(INFO) << "setInputWindows displayId=" << displayId << " " << windowList;
}
// Check preconditions for new input windows
@@ -4726,24 +5167,9 @@ void InputDispatcher::setInputWindowsLocked(
// Copy old handles for release if they are no longer present.
const std::vector<sp<WindowInfoHandle>> oldWindowHandles = getWindowHandlesLocked(displayId);
- // Save the old windows' orientation by ID before it gets updated.
- std::unordered_map<int32_t, uint32_t> oldWindowOrientations;
- for (const sp<WindowInfoHandle>& handle : oldWindowHandles) {
- oldWindowOrientations.emplace(handle->getId(),
- handle->getInfo()->transform.getOrientation());
- }
-
updateWindowHandlesForDisplayLocked(windowInfoHandles, displayId);
const std::vector<sp<WindowInfoHandle>>& windowHandles = getWindowHandlesLocked(displayId);
- if (mLastHoverWindowHandle) {
- const WindowInfo* lastHoverWindowInfo = mLastHoverWindowHandle->getInfo();
- if (lastHoverWindowInfo->displayId == displayId &&
- std::find(windowHandles.begin(), windowHandles.end(), mLastHoverWindowHandle) ==
- windowHandles.end()) {
- mLastHoverWindowHandle = nullptr;
- }
- }
std::optional<FocusResolver::FocusChanges> changes =
mFocusResolver.setInputWindows(displayId, windowHandles);
@@ -4758,30 +5184,21 @@ void InputDispatcher::setInputWindowsLocked(
for (size_t i = 0; i < state.windows.size();) {
TouchedWindow& touchedWindow = state.windows[i];
if (getWindowHandleLocked(touchedWindow.windowHandle) == nullptr) {
- if (DEBUG_FOCUS) {
- ALOGD("Touched window was removed: %s in display %" PRId32,
- touchedWindow.windowHandle->getName().c_str(), displayId);
- }
+ LOG(INFO) << "Touched window was removed: " << touchedWindow.windowHandle->getName()
+ << " in display %" << displayId;
std::shared_ptr<InputChannel> touchedInputChannel =
getInputChannelLocked(touchedWindow.windowHandle->getToken());
if (touchedInputChannel != nullptr) {
- CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
"touched window was removed");
synthesizeCancelationEventsForInputChannelLocked(touchedInputChannel, options);
// Since we are about to drop the touch, cancel the events for the wallpaper as
// well.
- if (touchedWindow.targetFlags & InputTarget::FLAG_FOREGROUND &&
+ if (touchedWindow.targetFlags.test(InputTarget::Flags::FOREGROUND) &&
touchedWindow.windowHandle->getInfo()->inputConfig.test(
gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER)) {
sp<WindowInfoHandle> wallpaper = state.getWallpaperWindow();
- if (wallpaper != nullptr) {
- sp<Connection> wallpaperConnection =
- getConnectionLocked(wallpaper->getToken());
- if (wallpaperConnection != nullptr) {
- synthesizeCancelationEventsForConnectionLocked(wallpaperConnection,
- options);
- }
- }
+ synthesizeCancelationEventsForWindowLocked(wallpaper, options);
}
}
state.windows.erase(state.windows.begin() + i);
@@ -4801,23 +5218,6 @@ void InputDispatcher::setInputWindowsLocked(
}
}
- // Determine if the orientation of any of the input windows have changed, and cancel all
- // pointer events if necessary.
- for (const sp<WindowInfoHandle>& oldWindowHandle : oldWindowHandles) {
- const sp<WindowInfoHandle> newWindowHandle = getWindowHandleLocked(oldWindowHandle);
- if (newWindowHandle != nullptr &&
- newWindowHandle->getInfo()->transform.getOrientation() !=
- oldWindowOrientations[oldWindowHandle->getId()]) {
- std::shared_ptr<InputChannel> inputChannel =
- getInputChannelLocked(newWindowHandle->getToken());
- if (inputChannel != nullptr) {
- CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
- "touched window's orientation changed");
- synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
- }
- }
- }
-
// Release information for windows that are no longer present.
// This ensures that unused input channels are released promptly.
// Otherwise, they might stick around until the window handle is destroyed
@@ -4892,7 +5292,7 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) {
getInputChannelLocked(oldFocusedWindowToken);
if (inputChannel != nullptr) {
CancelationOptions
- options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+ options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
"The display which contains this window no longer has focus.");
options.displayId = ADISPLAY_ID_NONE;
synthesizeCancelationEventsForInputChannelLocked(inputChannel, options);
@@ -4912,10 +5312,6 @@ void InputDispatcher::setFocusedDisplay(int32_t displayId) {
}
}
}
-
- if (DEBUG_FOCUS) {
- logDispatchStateLocked();
- }
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
@@ -4946,10 +5342,6 @@ void InputDispatcher::setInputDispatchMode(bool enabled, bool frozen) {
} else {
changed = false;
}
-
- if (DEBUG_FOCUS) {
- logDispatchStateLocked();
- }
} // release lock
if (changed) {
@@ -4978,33 +5370,36 @@ void InputDispatcher::setInputFilterEnabled(bool enabled) {
mLooper->wake();
}
-bool InputDispatcher::setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid,
- bool hasPermission) {
+bool InputDispatcher::setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid,
+ bool hasPermission, int32_t displayId) {
bool needWake = false;
{
std::scoped_lock lock(mLock);
- if (mInTouchMode == inTouchMode) {
+ ALOGD_IF(DEBUG_TOUCH_MODE,
+ "Request to change touch mode to %s (calling pid=%s, uid=%s, "
+ "hasPermission=%s, target displayId=%d, mTouchModePerDisplay[displayId]=%s)",
+ toString(inTouchMode), pid.toString().c_str(), uid.toString().c_str(),
+ toString(hasPermission), displayId,
+ mTouchModePerDisplay.count(displayId) == 0
+ ? "not set"
+ : std::to_string(mTouchModePerDisplay[displayId]).c_str());
+
+ auto touchModeIt = mTouchModePerDisplay.find(displayId);
+ if (touchModeIt != mTouchModePerDisplay.end() && touchModeIt->second == inTouchMode) {
return false;
}
- if (DEBUG_TOUCH_MODE) {
- ALOGD("Request to change touch mode from %s to %s (calling pid=%d, uid=%d, "
- "hasPermission=%s)",
- toString(mInTouchMode), toString(inTouchMode), pid, uid, toString(hasPermission));
- }
if (!hasPermission) {
if (!focusedWindowIsOwnedByLocked(pid, uid) &&
!recentWindowsAreOwnedByLocked(pid, uid)) {
- ALOGD("Touch mode switch rejected, caller (pid=%d, uid=%d) doesn't own the focused "
+ ALOGD("Touch mode switch rejected, caller (pid=%s, uid=%s) doesn't own the focused "
"window nor none of the previously interacted window",
- pid, uid);
+ pid.toString().c_str(), uid.toString().c_str());
return false;
}
}
-
- // TODO(b/198499018): Store touch mode per display.
- mInTouchMode = inTouchMode;
-
- auto entry = std::make_unique<TouchModeEntry>(mIdGenerator.nextId(), now(), inTouchMode);
+ mTouchModePerDisplay[displayId] = inTouchMode;
+ auto entry = std::make_unique<TouchModeEntry>(mIdGenerator.nextId(), now(), inTouchMode,
+ displayId);
needWake = enqueueInboundEventLocked(std::move(entry));
} // release lock
@@ -5014,7 +5409,7 @@ bool InputDispatcher::setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid,
return true;
}
-bool InputDispatcher::focusedWindowIsOwnedByLocked(int32_t pid, int32_t uid) {
+bool InputDispatcher::focusedWindowIsOwnedByLocked(gui::Pid pid, gui::Uid uid) {
const sp<IBinder> focusedToken = mFocusResolver.getFocusedWindowToken(mFocusedDisplayId);
if (focusedToken == nullptr) {
return false;
@@ -5023,7 +5418,7 @@ bool InputDispatcher::focusedWindowIsOwnedByLocked(int32_t pid, int32_t uid) {
return isWindowOwnedBy(windowHandle, pid, uid);
}
-bool InputDispatcher::recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) {
+bool InputDispatcher::recentWindowsAreOwnedByLocked(gui::Pid pid, gui::Uid uid) {
return std::find_if(mInteractionConnectionTokens.begin(), mInteractionConnectionTokens.end(),
[&](const sp<IBinder>& connectionToken) REQUIRES(mLock) {
const sp<WindowInfoHandle> windowHandle =
@@ -5042,21 +5437,16 @@ void InputDispatcher::setMaximumObscuringOpacityForTouch(float opacity) {
mMaximumObscuringOpacityForTouch = opacity;
}
-void InputDispatcher::setBlockUntrustedTouchesMode(BlockUntrustedTouchesMode mode) {
- std::scoped_lock lock(mLock);
- mBlockUntrustedTouchesMode = mode;
-}
-
-std::pair<TouchState*, TouchedWindow*> InputDispatcher::findTouchStateAndWindowLocked(
- const sp<IBinder>& token) {
+std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+InputDispatcher::findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) {
for (auto& [displayId, state] : mTouchStatesByDisplay) {
for (TouchedWindow& w : state.windows) {
if (w.windowHandle->getToken() == token) {
- return std::make_pair(&state, &w);
+ return std::make_tuple(&state, &w, displayId);
}
}
}
- return std::make_pair(nullptr, nullptr);
+ return std::make_tuple(nullptr, nullptr, ADISPLAY_ID_DEFAULT);
}
bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
@@ -5072,16 +5462,23 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<
std::scoped_lock _l(mLock);
// Find the target touch state and touched window by fromToken.
- auto [state, touchedWindow] = findTouchStateAndWindowLocked(fromToken);
+ auto [state, touchedWindow, displayId] = findTouchStateWindowAndDisplayLocked(fromToken);
+
if (state == nullptr || touchedWindow == nullptr) {
- ALOGD("Focus transfer failed because from window is not being touched.");
+ ALOGD("Touch transfer failed because from window is not being touched.");
+ return false;
+ }
+ std::set<int32_t> deviceIds = touchedWindow->getTouchingDeviceIds();
+ if (deviceIds.size() != 1) {
+ LOG(INFO) << "Can't transfer touch. Currently touching devices: " << dumpSet(deviceIds)
+ << " for window: " << touchedWindow->dump();
return false;
}
+ const int32_t deviceId = *deviceIds.begin();
- const int32_t displayId = state->displayId;
sp<WindowInfoHandle> toWindowHandle = getWindowHandleLocked(toToken, displayId);
if (toWindowHandle == nullptr) {
- ALOGW("Cannot transfer focus because to window not found.");
+ ALOGW("Cannot transfer touch because to window not found.");
return false;
}
@@ -5092,17 +5489,20 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<
}
// Erase old window.
- int32_t oldTargetFlags = touchedWindow->targetFlags;
- BitSet32 pointerIds = touchedWindow->pointerIds;
+ ftl::Flags<InputTarget::Flags> oldTargetFlags = touchedWindow->targetFlags;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds = touchedWindow->getTouchingPointers(deviceId);
+ sp<WindowInfoHandle> fromWindowHandle = touchedWindow->windowHandle;
state->removeWindowByToken(fromToken);
// Add new window.
- int32_t newTargetFlags =
- oldTargetFlags & (InputTarget::FLAG_SPLIT | InputTarget::FLAG_DISPATCH_AS_IS);
+ nsecs_t downTimeInTarget = now();
+ ftl::Flags<InputTarget::Flags> newTargetFlags =
+ oldTargetFlags & (InputTarget::Flags::SPLIT);
if (canReceiveForegroundTouches(*toWindowHandle->getInfo())) {
- newTargetFlags |= InputTarget::FLAG_FOREGROUND;
+ newTargetFlags |= InputTarget::Flags::FOREGROUND;
}
- state->addOrUpdateWindow(toWindowHandle, newTargetFlags, pointerIds);
+ state->addOrUpdateWindow(toWindowHandle, InputTarget::DispatchMode::AS_IS, newTargetFlags,
+ deviceId, pointerIds, downTimeInTarget);
// Store the dragging window.
if (isDragDrop) {
@@ -5112,24 +5512,24 @@ bool InputDispatcher::transferTouchFocus(const sp<IBinder>& fromToken, const sp<
return false;
}
// Track the pointer id for drag window and generate the drag state.
- const int32_t id = pointerIds.firstMarkedBit();
+ const size_t id = firstMarkedBit(pointerIds);
mDragState = std::make_unique<DragState>(toWindowHandle, id);
}
// Synthesize cancel for old window and down for new window.
- sp<Connection> fromConnection = getConnectionLocked(fromToken);
- sp<Connection> toConnection = getConnectionLocked(toToken);
+ std::shared_ptr<Connection> fromConnection = getConnectionLocked(fromToken);
+ std::shared_ptr<Connection> toConnection = getConnectionLocked(toToken);
if (fromConnection != nullptr && toConnection != nullptr) {
fromConnection->inputState.mergePointerStateTo(toConnection->inputState);
- CancelationOptions
- options(CancelationOptions::CANCEL_POINTER_EVENTS,
- "transferring touch focus from this window to another window");
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+ "transferring touch from this window to another window");
synthesizeCancelationEventsForConnectionLocked(fromConnection, options);
- synthesizePointerDownEventsForConnectionLocked(toConnection);
- }
+ synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, toConnection,
+ newTargetFlags);
- if (DEBUG_FOCUS) {
- logDispatchStateLocked();
+ // Check if the wallpaper window should deliver the corresponding event.
+ transferWallpaperTouch(oldTargetFlags, newTargetFlags, fromWindowHandle, toWindowHandle,
+ *state, deviceId, pointerIds);
}
} // release lock
@@ -5154,7 +5554,7 @@ sp<WindowInfoHandle> InputDispatcher::findTouchedForegroundWindowLocked(int32_t
sp<WindowInfoHandle> touchedForegroundWindow;
// If multiple foreground windows are touched, return nullptr
for (const TouchedWindow& window : state.windows) {
- if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
if (touchedForegroundWindow != nullptr) {
ALOGI("Two or more foreground windows: %s and %s",
touchedForegroundWindow->getName().c_str(),
@@ -5196,7 +5596,7 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
ALOGD("Resetting and dropping all events (%s).", reason);
}
- CancelationOptions options(CancelationOptions::CANCEL_ALL_EVENTS, reason);
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_ALL_EVENTS, reason);
synthesizeCancelationEventsForAllConnectionsLocked(options);
resetKeyRepeatLocked();
@@ -5206,11 +5606,9 @@ void InputDispatcher::resetAndDropEverythingLocked(const char* reason) {
mAnrTracker.clear();
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
- mReplacedKeys.clear();
}
-void InputDispatcher::logDispatchStateLocked() {
+void InputDispatcher::logDispatchStateLocked() const {
std::string dump;
dumpDispatchStateLocked(dump);
@@ -5218,11 +5616,11 @@ void InputDispatcher::logDispatchStateLocked() {
std::string line;
while (std::getline(stream, line, '\n')) {
- ALOGD("%s", line.c_str());
+ ALOGI("%s", line.c_str());
}
}
-std::string InputDispatcher::dumpPointerCaptureStateLocked() {
+std::string InputDispatcher::dumpPointerCaptureStateLocked() const {
std::string dump;
dump += StringPrintf(INDENT "Pointer Capture Requested: %s\n",
@@ -5240,7 +5638,7 @@ std::string InputDispatcher::dumpPointerCaptureStateLocked() {
return dump;
}
-void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
+void InputDispatcher::dumpDispatchStateLocked(std::string& dump) const {
dump += StringPrintf(INDENT "DispatchEnabled: %s\n", toString(mDispatchEnabled));
dump += StringPrintf(INDENT "DispatchFrozen: %s\n", toString(mDispatchFrozen));
dump += StringPrintf(INDENT "InputFilterEnabled: %s\n", toString(mInputFilterEnabled));
@@ -5266,23 +5664,9 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
if (!mTouchStatesByDisplay.empty()) {
dump += StringPrintf(INDENT "TouchStatesByDisplay:\n");
- for (const std::pair<int32_t, TouchState>& pair : mTouchStatesByDisplay) {
- const TouchState& state = pair.second;
- dump += StringPrintf(INDENT2 "%d: down=%s, split=%s, deviceId=%d, source=0x%08x\n",
- state.displayId, toString(state.down), toString(state.split),
- state.deviceId, state.source);
- if (!state.windows.empty()) {
- dump += INDENT3 "Windows:\n";
- for (size_t i = 0; i < state.windows.size(); i++) {
- const TouchedWindow& touchedWindow = state.windows[i];
- dump += StringPrintf(INDENT4
- "%zu: name='%s', pointerIds=0x%0x, targetFlags=0x%x\n",
- i, touchedWindow.windowHandle->getName().c_str(),
- touchedWindow.pointerIds.value, touchedWindow.targetFlags);
- }
- } else {
- dump += INDENT3 "Windows: <none>\n";
- }
+ for (const auto& [displayId, state] : mTouchStatesByDisplay) {
+ std::string touchStateDump = addLinePrefix(state.dump(), INDENT2);
+ dump += INDENT2 + std::to_string(displayId) + " : " + touchStateDump;
}
} else {
dump += INDENT "TouchStates: <no displays touched>\n";
@@ -5308,32 +5692,8 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
if (!windowHandles.empty()) {
dump += INDENT2 "Windows:\n";
for (size_t i = 0; i < windowHandles.size(); i++) {
- const sp<WindowInfoHandle>& windowHandle = windowHandles[i];
- const WindowInfo* windowInfo = windowHandle->getInfo();
-
- dump += StringPrintf(INDENT3 "%zu: name='%s', id=%" PRId32 ", displayId=%d, "
- "inputConfig=%s, alpha=%.2f, "
- "frame=[%d,%d][%d,%d], globalScale=%f, "
- "applicationInfo.name=%s, "
- "applicationInfo.token=%s, "
- "touchableRegion=",
- i, windowInfo->name.c_str(), windowInfo->id,
- windowInfo->displayId,
- windowInfo->inputConfig.string().c_str(),
- windowInfo->alpha, windowInfo->frameLeft,
- windowInfo->frameTop, windowInfo->frameRight,
- windowInfo->frameBottom, windowInfo->globalScaleFactor,
- windowInfo->applicationInfo.name.c_str(),
- toString(windowInfo->applicationInfo.token).c_str());
- dump += dumpRegion(windowInfo->touchableRegion);
- dump += StringPrintf(", ownerPid=%d, ownerUid=%d, dispatchingTimeout=%" PRId64
- "ms, hasToken=%s, "
- "touchOcclusionMode=%s\n",
- windowInfo->ownerPid, windowInfo->ownerUid,
- millis(windowInfo->dispatchingTimeout),
- toString(windowInfo->token != nullptr),
- toString(windowInfo->touchOcclusionMode).c_str());
- windowInfo->transform.dump(dump, "transform", INDENT4);
+ dump += StringPrintf(INDENT3 "%zu: %s", i,
+ streamableToString(*windowHandles[i]).c_str());
}
} else {
dump += INDENT2 "Windows: <none>\n";
@@ -5357,7 +5717,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
// Dump recently dispatched or dropped events from oldest to newest.
if (!mRecentQueue.empty()) {
dump += StringPrintf(INDENT "RecentQueue: length=%zu\n", mRecentQueue.size());
- for (std::shared_ptr<EventEntry>& entry : mRecentQueue) {
+ for (const std::shared_ptr<const EventEntry>& entry : mRecentQueue) {
dump += INDENT2;
dump += entry->getDescription();
dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
@@ -5380,7 +5740,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
// Dump inbound events from oldest to newest.
if (!mInboundQueue.empty()) {
dump += StringPrintf(INDENT "InboundQueue: length=%zu\n", mInboundQueue.size());
- for (std::shared_ptr<EventEntry>& entry : mInboundQueue) {
+ for (const std::shared_ptr<const EventEntry>& entry : mInboundQueue) {
dump += INDENT2;
dump += entry->getDescription();
dump += StringPrintf(", age=%" PRId64 "ms\n", ns2ms(currentTime - entry->eventTime));
@@ -5389,18 +5749,6 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
dump += INDENT "InboundQueue: <empty>\n";
}
- if (!mReplacedKeys.empty()) {
- dump += INDENT "ReplacedKeys:\n";
- for (const std::pair<KeyReplacement, int32_t>& pair : mReplacedKeys) {
- const KeyReplacement& replacement = pair.first;
- int32_t newKeyCode = pair.second;
- dump += StringPrintf(INDENT2 "originalKeyCode=%d, deviceId=%d -> newKeyCode=%d\n",
- replacement.keyCode, replacement.deviceId, newKeyCode);
- }
- } else {
- dump += INDENT "ReplacedKeys: <empty>\n";
- }
-
if (!mCommandQueue.empty()) {
dump += StringPrintf(INDENT "CommandQueue: size=%zu\n", mCommandQueue.size());
} else {
@@ -5434,11 +5782,19 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
} else {
dump += INDENT3 "WaitQueue: <empty>\n";
}
+ std::string inputStateDump = streamableToString(connection->inputState);
+ if (!inputStateDump.empty()) {
+ dump += INDENT3 "InputState: ";
+ dump += inputStateDump + "\n";
+ }
}
} else {
dump += INDENT "Connections: <none>\n";
}
+ dump += "input_flags::remove_app_switch_drops() = ";
+ dump += toString(REMOVE_APP_SWITCH_DROPS);
+ dump += "\n";
if (isAppSwitchPendingLocked()) {
dump += StringPrintf(INDENT "AppSwitch: pending, due in %" PRId64 "ms\n",
ns2ms(mAppSwitchDueTime - now()));
@@ -5446,6 +5802,16 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
dump += INDENT "AppSwitch: not pending\n";
}
+ if (!mTouchModePerDisplay.empty()) {
+ dump += INDENT "TouchModePerDisplay:\n";
+ for (const auto& [displayId, touchMode] : mTouchModePerDisplay) {
+ dump += StringPrintf(INDENT2 "Display: %" PRId32 " TouchMode: %s\n", displayId,
+ std::to_string(touchMode).c_str());
+ }
+ } else {
+ dump += INDENT "TouchModePerDisplay: <none>\n";
+ }
+
dump += INDENT "Configuration:\n";
dump += StringPrintf(INDENT2 "KeyRepeatDelay: %" PRId64 "ms\n", ns2ms(mConfig.keyRepeatDelay));
dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n",
@@ -5454,7 +5820,7 @@ void InputDispatcher::dumpDispatchStateLocked(std::string& dump) {
dump += mLatencyAggregator.dump(INDENT2);
}
-void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) {
+void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const {
const size_t numMonitors = monitors.size();
for (size_t i = 0; i < numMonitors; i++) {
const Monitor& monitor = monitors[i];
@@ -5489,9 +5855,10 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const
{ // acquire lock
std::scoped_lock _l(mLock);
const sp<IBinder>& token = serverChannel->getConnectionToken();
- int fd = serverChannel->getFd();
- sp<Connection> connection =
- new Connection(std::move(serverChannel), false /*monitor*/, mIdGenerator);
+ auto&& fd = serverChannel->getFd();
+ std::shared_ptr<Connection> connection =
+ std::make_shared<Connection>(std::move(serverChannel), /*monitor=*/false,
+ mIdGenerator);
if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
ALOGE("Created a new connection, but the token %p is already known", token.get());
@@ -5501,7 +5868,8 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const
std::function<int(int events)> callback = std::bind(&InputDispatcher::handleReceiveCallback,
this, std::placeholders::_1, token);
- mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
+ mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+ nullptr);
} // release lock
// Wake the looper because some connections have changed.
@@ -5511,7 +5879,7 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputChannel(const
Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_t displayId,
const std::string& name,
- int32_t pid) {
+ gui::Pid pid) {
std::shared_ptr<InputChannel> serverChannel;
std::unique_ptr<InputChannel> clientChannel;
status_t result = openInputChannelPair(name, serverChannel, clientChannel);
@@ -5527,9 +5895,10 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_
<< " without a specified display.";
}
- sp<Connection> connection = new Connection(serverChannel, true /*monitor*/, mIdGenerator);
+ std::shared_ptr<Connection> connection =
+ std::make_shared<Connection>(serverChannel, /*monitor=*/true, mIdGenerator);
const sp<IBinder>& token = serverChannel->getConnectionToken();
- const int fd = serverChannel->getFd();
+ auto&& fd = serverChannel->getFd();
if (mConnectionsByToken.find(token) != mConnectionsByToken.end()) {
ALOGE("Created a new connection, but the token %p is already known", token.get());
@@ -5540,7 +5909,8 @@ Result<std::unique_ptr<InputChannel>> InputDispatcher::createInputMonitor(int32_
mGlobalMonitorsByDisplay[displayId].emplace_back(serverChannel, pid);
- mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
+ mLooper->addFd(fd.get(), 0, ALOOPER_EVENT_INPUT, sp<LooperEventCallback>::make(callback),
+ nullptr);
}
// Wake the looper because some connections have changed.
@@ -5552,7 +5922,7 @@ status_t InputDispatcher::removeInputChannel(const sp<IBinder>& connectionToken)
{ // acquire lock
std::scoped_lock _l(mLock);
- status_t status = removeInputChannelLocked(connectionToken, false /*notify*/);
+ status_t status = removeInputChannelLocked(connectionToken, /*notify=*/false);
if (status) {
return status;
}
@@ -5566,7 +5936,7 @@ status_t InputDispatcher::removeInputChannel(const sp<IBinder>& connectionToken)
status_t InputDispatcher::removeInputChannelLocked(const sp<IBinder>& connectionToken,
bool notify) {
- sp<Connection> connection = getConnectionLocked(connectionToken);
+ std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
if (connection == nullptr) {
// Connection can be removed via socket hang up or an explicit call to 'removeInputChannel'
return BAD_VALUE;
@@ -5578,7 +5948,7 @@ status_t InputDispatcher::removeInputChannelLocked(const sp<IBinder>& connection
removeMonitorChannelLocked(connectionToken);
}
- mLooper->removeFd(connection->inputChannel->getFd());
+ mLooper->removeFd(connection->inputChannel->getFd().get());
nsecs_t currentTime = now();
abortBrokenDispatchCycleLocked(currentTime, connection, notify);
@@ -5604,44 +5974,61 @@ void InputDispatcher::removeMonitorChannelLocked(const sp<IBinder>& connectionTo
status_t InputDispatcher::pilferPointers(const sp<IBinder>& token) {
std::scoped_lock _l(mLock);
+ return pilferPointersLocked(token);
+}
+status_t InputDispatcher::pilferPointersLocked(const sp<IBinder>& token) {
const std::shared_ptr<InputChannel> requestingChannel = getInputChannelLocked(token);
if (!requestingChannel) {
- ALOGW("Attempted to pilfer pointers from an un-registered channel or invalid token");
+ LOG(WARNING)
+ << "Attempted to pilfer pointers from an un-registered channel or invalid token";
return BAD_VALUE;
}
- auto [statePtr, windowPtr] = findTouchStateAndWindowLocked(token);
- if (statePtr == nullptr || windowPtr == nullptr || !statePtr->down) {
- ALOGW("Attempted to pilfer points from a channel without any on-going pointer streams."
- " Ignoring.");
+ auto [statePtr, windowPtr, displayId] = findTouchStateWindowAndDisplayLocked(token);
+ if (statePtr == nullptr || windowPtr == nullptr) {
+ LOG(WARNING)
+ << "Attempted to pilfer points from a channel without any on-going pointer streams."
+ " Ignoring.";
return BAD_VALUE;
}
-
- TouchState& state = *statePtr;
-
- // Send cancel events to all the input channels we're stealing from.
- CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
- "input channel stole pointer stream");
- options.deviceId = state.deviceId;
- options.displayId = state.displayId;
- std::string canceledWindows;
- for (const TouchedWindow& window : state.windows) {
- const std::shared_ptr<InputChannel> channel =
- getInputChannelLocked(window.windowHandle->getToken());
- if (channel != nullptr && channel->getConnectionToken() != token) {
- synthesizeCancelationEventsForInputChannelLocked(channel, options);
- canceledWindows += canceledWindows.empty() ? "[" : ", ";
- canceledWindows += channel->getName();
- }
+ std::set<int32_t> deviceIds = windowPtr->getTouchingDeviceIds();
+ if (deviceIds.empty()) {
+ LOG(WARNING) << "Can't pilfer: no touching devices in window: " << windowPtr->dump();
+ return BAD_VALUE;
}
- canceledWindows += canceledWindows.empty() ? "[]" : "]";
- ALOGI("Channel %s is stealing touch from %s", requestingChannel->getName().c_str(),
- canceledWindows.c_str());
- // Prevent the gesture from being sent to any other windows.
- state.filterWindowsExcept(token);
- state.preventNewTargets = true;
+ for (const DeviceId deviceId : deviceIds) {
+ TouchState& state = *statePtr;
+ TouchedWindow& window = *windowPtr;
+ // Send cancel events to all the input channels we're stealing from.
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+ "input channel stole pointer stream");
+ options.deviceId = deviceId;
+ options.displayId = displayId;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds = window.getTouchingPointers(deviceId);
+ options.pointerIds = pointerIds;
+ std::string canceledWindows;
+ for (const TouchedWindow& w : state.windows) {
+ const std::shared_ptr<InputChannel> channel =
+ getInputChannelLocked(w.windowHandle->getToken());
+ if (channel != nullptr && channel->getConnectionToken() != token) {
+ synthesizeCancelationEventsForInputChannelLocked(channel, options);
+ canceledWindows += canceledWindows.empty() ? "[" : ", ";
+ canceledWindows += channel->getName();
+ }
+ }
+ canceledWindows += canceledWindows.empty() ? "[]" : "]";
+ LOG(INFO) << "Channel " << requestingChannel->getName()
+ << " is stealing input gesture for device " << deviceId << " from "
+ << canceledWindows;
+
+ // Prevent the gesture from being sent to any other windows.
+ // This only blocks relevant pointers to be sent to other windows
+ window.addPilferingPointers(deviceId, pointerIds);
+
+ state.cancelPointersForWindowsExcept(deviceId, pointerIds, token);
+ }
return OK;
}
@@ -5695,7 +6082,7 @@ void InputDispatcher::setDisplayEligibilityForPointerCapture(int32_t displayId,
} // release lock
}
-std::optional<int32_t> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
+std::optional<gui::Pid> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBinder>& token) {
for (const auto& [_, monitors] : mGlobalMonitorsByDisplay) {
for (const Monitor& monitor : monitors) {
if (monitor.inputChannel->getConnectionToken() == token) {
@@ -5706,7 +6093,8 @@ std::optional<int32_t> InputDispatcher::findMonitorPidByTokenLocked(const sp<IBi
return std::nullopt;
}
-sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConnectionToken) const {
+std::shared_ptr<Connection> InputDispatcher::getConnectionLocked(
+ const sp<IBinder>& inputConnectionToken) const {
if (inputConnectionToken == nullptr) {
return nullptr;
}
@@ -5721,59 +6109,63 @@ sp<Connection> InputDispatcher::getConnectionLocked(const sp<IBinder>& inputConn
}
std::string InputDispatcher::getConnectionNameLocked(const sp<IBinder>& connectionToken) const {
- sp<Connection> connection = getConnectionLocked(connectionToken);
+ std::shared_ptr<Connection> connection = getConnectionLocked(connectionToken);
if (connection == nullptr) {
return "<nullptr>";
}
return connection->getInputChannelName();
}
-void InputDispatcher::removeConnectionLocked(const sp<Connection>& connection) {
+void InputDispatcher::removeConnectionLocked(const std::shared_ptr<Connection>& connection) {
mAnrTracker.eraseToken(connection->inputChannel->getConnectionToken());
mConnectionsByToken.erase(connection->inputChannel->getConnectionToken());
}
void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
- const sp<Connection>& connection, uint32_t seq,
- bool handled, nsecs_t consumeTime) {
+ const std::shared_ptr<Connection>& connection,
+ uint32_t seq, bool handled,
+ nsecs_t consumeTime) {
// Handle post-event policy actions.
- std::deque<DispatchEntry*>::iterator dispatchEntryIt = connection->findWaitQueueEntry(seq);
- if (dispatchEntryIt == connection->waitQueue.end()) {
- return;
- }
- DispatchEntry* dispatchEntry = *dispatchEntryIt;
- const nsecs_t eventDuration = finishTime - dispatchEntry->deliveryTime;
- if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
- ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
- ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
- }
- if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
- mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
- connection->inputChannel->getConnectionToken(),
- dispatchEntry->deliveryTime, consumeTime, finishTime);
- }
-
- bool restartEvent;
- if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
- KeyEntry& keyEntry = static_cast<KeyEntry&>(*(dispatchEntry->eventEntry));
- restartEvent =
- afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
- } else if (dispatchEntry->eventEntry->type == EventEntry::Type::MOTION) {
- MotionEntry& motionEntry = static_cast<MotionEntry&>(*(dispatchEntry->eventEntry));
- restartEvent = afterMotionEventLockedInterruptable(connection, dispatchEntry, motionEntry,
- handled);
- } else {
- restartEvent = false;
- }
+ std::unique_ptr<const KeyEntry> fallbackKeyEntry;
+
+ { // Start critical section
+ auto dispatchEntryIt =
+ std::find_if(connection->waitQueue.begin(), connection->waitQueue.end(),
+ [seq](auto& e) { return e->seq == seq; });
+ if (dispatchEntryIt == connection->waitQueue.end()) {
+ return;
+ }
+
+ DispatchEntry& dispatchEntry = **dispatchEntryIt;
+
+ const nsecs_t eventDuration = finishTime - dispatchEntry.deliveryTime;
+ if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {
+ ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
+ ns2ms(eventDuration), dispatchEntry.eventEntry->getDescription().c_str());
+ }
+ if (shouldReportFinishedEvent(dispatchEntry, *connection)) {
+ mLatencyTracker.trackFinishedEvent(dispatchEntry.eventEntry->id,
+ connection->inputChannel->getConnectionToken(),
+ dispatchEntry.deliveryTime, consumeTime, finishTime);
+ }
+
+ if (dispatchEntry.eventEntry->type == EventEntry::Type::KEY) {
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(*(dispatchEntry.eventEntry));
+ fallbackKeyEntry =
+ afterKeyEventLockedInterruptable(connection, dispatchEntry, keyEntry, handled);
+ }
+ } // End critical section: The -LockedInterruptable methods may have released the lock.
// Dequeue the event and start the next cycle.
// Because the lock might have been released, it is possible that the
// contents of the wait queue to have been drained, so we need to double-check
// a few things.
- dispatchEntryIt = connection->findWaitQueueEntry(seq);
- if (dispatchEntryIt != connection->waitQueue.end()) {
- dispatchEntry = *dispatchEntryIt;
- connection->waitQueue.erase(dispatchEntryIt);
+ auto entryIt = std::find_if(connection->waitQueue.begin(), connection->waitQueue.end(),
+ [seq](auto& e) { return e->seq == seq; });
+ if (entryIt != connection->waitQueue.end()) {
+ std::unique_ptr<DispatchEntry> dispatchEntry = std::move(*entryIt);
+ connection->waitQueue.erase(entryIt);
+
const sp<IBinder>& connectionToken = connection->inputChannel->getConnectionToken();
mAnrTracker.erase(dispatchEntry->timeoutTime, connectionToken);
if (!connection->responsive) {
@@ -5784,12 +6176,12 @@ void InputDispatcher::doDispatchCycleFinishedCommand(nsecs_t finishTime,
}
}
traceWaitQueueLength(*connection);
- if (restartEvent && connection->status == Connection::Status::NORMAL) {
- connection->outboundQueue.push_front(dispatchEntry);
- traceOutboundQueueLength(*connection);
- } else {
- releaseDispatchEntry(dispatchEntry);
+ if (fallbackKeyEntry && connection->status == Connection::Status::NORMAL) {
+ const InputTarget target{.inputChannel = connection->inputChannel,
+ .flags = dispatchEntry->targetFlags};
+ enqueueDispatchEntryLocked(connection, std::move(fallbackKeyEntry), target);
}
+ releaseDispatchEntry(std::move(dispatchEntry));
}
// Start the next dispatch cycle for this connection.
@@ -5800,7 +6192,7 @@ void InputDispatcher::sendFocusChangedCommandLocked(const sp<IBinder>& oldToken,
const sp<IBinder>& newToken) {
auto command = [this, oldToken, newToken]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyFocusChanged(oldToken, newToken);
+ mPolicy.notifyFocusChanged(oldToken, newToken);
};
postCommandLocked(std::move(command));
}
@@ -5808,20 +6200,12 @@ void InputDispatcher::sendFocusChangedCommandLocked(const sp<IBinder>& oldToken,
void InputDispatcher::sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) {
auto command = [this, token, x, y]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyDropWindow(token, x, y);
- };
- postCommandLocked(std::move(command));
-}
-
-void InputDispatcher::sendUntrustedTouchCommandLocked(const std::string& obscuringPackage) {
- auto command = [this, obscuringPackage]() REQUIRES(mLock) {
- scoped_unlock unlock(mLock);
- mPolicy->notifyUntrustedTouch(obscuringPackage);
+ mPolicy.notifyDropWindow(token, x, y);
};
postCommandLocked(std::move(command));
}
-void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
+void InputDispatcher::onAnrLocked(const std::shared_ptr<Connection>& connection) {
if (connection == nullptr) {
LOG_ALWAYS_FATAL("Caller must check for nullness");
}
@@ -5840,13 +6224,13 @@ void InputDispatcher::onAnrLocked(const sp<Connection>& connection) {
* processes the events linearly. So providing information about the oldest entry seems to be
* most useful.
*/
- DispatchEntry* oldestEntry = *connection->waitQueue.begin();
- const nsecs_t currentWait = now() - oldestEntry->deliveryTime;
+ DispatchEntry& oldestEntry = *connection->waitQueue.front();
+ const nsecs_t currentWait = now() - oldestEntry.deliveryTime;
std::string reason =
android::base::StringPrintf("%s is not responding. Waited %" PRId64 "ms for %s",
connection->inputChannel->getName().c_str(),
ns2ms(currentWait),
- oldestEntry->eventEntry->getDescription().c_str());
+ oldestEntry.eventEntry->getDescription().c_str());
sp<IBinder> connectionToken = connection->inputChannel->getConnectionToken();
updateLastAnrStateLocked(getWindowHandleLocked(connectionToken), reason);
@@ -5861,9 +6245,9 @@ void InputDispatcher::onAnrLocked(std::shared_ptr<InputApplicationHandle> applic
StringPrintf("%s does not have a focused window", application->getName().c_str());
updateLastAnrStateLocked(*application, reason);
- auto command = [this, application = std::move(application)]() REQUIRES(mLock) {
+ auto command = [this, app = std::move(application)]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyNoFocusedWindowAnr(application);
+ mPolicy.notifyNoFocusedWindowAnr(app);
};
postCommandLocked(std::move(command));
}
@@ -5897,14 +6281,13 @@ void InputDispatcher::updateLastAnrStateLocked(const std::string& windowLabel,
}
void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
- KeyEntry& entry) {
+ const KeyEntry& entry) {
const KeyEvent event = createKeyEvent(entry);
nsecs_t delay = 0;
{ // release lock
scoped_unlock unlock(mLock);
android::base::Timer t;
- delay = mPolicy->interceptKeyBeforeDispatching(focusedWindowToken, &event,
- entry.policyFlags);
+ delay = mPolicy.interceptKeyBeforeDispatching(focusedWindowToken, event, entry.policyFlags);
if (t.duration() > SLOW_INTERCEPTION_THRESHOLD) {
ALOGW("Excessive delay in interceptKeyBeforeDispatching; took %s ms",
std::to_string(t.duration().count()).c_str());
@@ -5912,30 +6295,30 @@ void InputDispatcher::doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>&
} // acquire lock
if (delay < 0) {
- entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_SKIP;
+ entry.interceptKeyResult = KeyEntry::InterceptKeyResult::SKIP;
} else if (delay == 0) {
- entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_CONTINUE;
+ entry.interceptKeyResult = KeyEntry::InterceptKeyResult::CONTINUE;
} else {
- entry.interceptKeyResult = KeyEntry::INTERCEPT_KEY_RESULT_TRY_AGAIN_LATER;
+ entry.interceptKeyResult = KeyEntry::InterceptKeyResult::TRY_AGAIN_LATER;
entry.interceptKeyWakeupTime = now() + delay;
}
}
void InputDispatcher::sendWindowUnresponsiveCommandLocked(const sp<IBinder>& token,
- std::optional<int32_t> pid,
+ std::optional<gui::Pid> pid,
std::string reason) {
- auto command = [this, token, pid, reason = std::move(reason)]() REQUIRES(mLock) {
+ auto command = [this, token, pid, r = std::move(reason)]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyWindowUnresponsive(token, pid, reason);
+ mPolicy.notifyWindowUnresponsive(token, pid, r);
};
postCommandLocked(std::move(command));
}
void InputDispatcher::sendWindowResponsiveCommandLocked(const sp<IBinder>& token,
- std::optional<int32_t> pid) {
+ std::optional<gui::Pid> pid) {
auto command = [this, token, pid]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->notifyWindowResponsive(token, pid);
+ mPolicy.notifyWindowResponsive(token, pid);
};
postCommandLocked(std::move(command));
}
@@ -5948,7 +6331,7 @@ void InputDispatcher::sendWindowResponsiveCommandLocked(const sp<IBinder>& token
void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& connection,
std::string reason) {
const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
- std::optional<int32_t> pid;
+ std::optional<gui::Pid> pid;
if (connection.monitor) {
ALOGW("Monitor %s is unresponsive: %s", connection.inputChannel->getName().c_str(),
reason.c_str());
@@ -5970,7 +6353,7 @@ void InputDispatcher::processConnectionUnresponsiveLocked(const Connection& conn
*/
void InputDispatcher::processConnectionResponsiveLocked(const Connection& connection) {
const sp<IBinder>& connectionToken = connection.inputChannel->getConnectionToken();
- std::optional<int32_t> pid;
+ std::optional<gui::Pid> pid;
if (connection.monitor) {
pid = findMonitorPidByTokenLocked(connectionToken);
} else {
@@ -5983,30 +6366,30 @@ void InputDispatcher::processConnectionResponsiveLocked(const Connection& connec
sendWindowResponsiveCommandLocked(connectionToken, pid);
}
-bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& connection,
- DispatchEntry* dispatchEntry,
- KeyEntry& keyEntry, bool handled) {
+std::unique_ptr<const KeyEntry> InputDispatcher::afterKeyEventLockedInterruptable(
+ const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
+ const KeyEntry& keyEntry, bool handled) {
if (keyEntry.flags & AKEY_EVENT_FLAG_FALLBACK) {
if (!handled) {
// Report the key as unhandled, since the fallback was not handled.
mReporter->reportUnhandledKey(keyEntry.id);
}
- return false;
+ return {};
}
// Get the fallback key state.
// Clear it out after dispatching the UP.
int32_t originalKeyCode = keyEntry.keyCode;
- int32_t fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
+ std::optional<int32_t> fallbackKeyCode = connection->inputState.getFallbackKey(originalKeyCode);
if (keyEntry.action == AKEY_EVENT_ACTION_UP) {
connection->inputState.removeFallbackKey(originalKeyCode);
}
- if (handled || !dispatchEntry->hasForegroundTarget()) {
+ if (handled || !dispatchEntry.hasForegroundTarget()) {
// If the application handles the original key for which we previously
// generated a fallback or if the window is not a foreground window,
// then cancel the associated fallback key, if any.
- if (fallbackKeyCode != -1) {
+ if (fallbackKeyCode) {
// Dispatch the unhandled key to the policy with the cancel flag.
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("Unhandled key event: Asking policy to cancel fallback action. "
@@ -6019,18 +6402,22 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& con
mLock.unlock();
- mPolicy->dispatchUnhandledKey(connection->inputChannel->getConnectionToken(), &event,
- keyEntry.policyFlags, &event);
+ if (const auto unhandledKeyFallback =
+ mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
+ event, keyEntry.policyFlags);
+ unhandledKeyFallback) {
+ event = *unhandledKeyFallback;
+ }
mLock.lock();
// Cancel the fallback key.
- if (fallbackKeyCode != AKEYCODE_UNKNOWN) {
- CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ if (*fallbackKeyCode != AKEYCODE_UNKNOWN) {
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
"application handled the original non-fallback key "
"or is no longer a foreground target, "
"canceling previously dispatched fallback key");
- options.keyCode = fallbackKeyCode;
+ options.keyCode = *fallbackKeyCode;
synthesizeCancelationEventsForConnectionLocked(connection, options);
}
connection->inputState.removeFallbackKey(originalKeyCode);
@@ -6040,14 +6427,14 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& con
// that we are in a good state to perform unhandled key event processing
// Then ask the policy what to do with it.
bool initialDown = keyEntry.action == AKEY_EVENT_ACTION_DOWN && keyEntry.repeatCount == 0;
- if (fallbackKeyCode == -1 && !initialDown) {
+ if (!fallbackKeyCode && !initialDown) {
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("Unhandled key event: Skipping unhandled key event processing "
"since this is not an initial down. "
"keyCode=%d, action=%d, repeatCount=%d, policyFlags=0x%08x",
originalKeyCode, keyEntry.action, keyEntry.repeatCount, keyEntry.policyFlags);
}
- return false;
+ return {};
}
// Dispatch the unhandled key to the policy.
@@ -6060,68 +6447,72 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& con
mLock.unlock();
- bool fallback =
- mPolicy->dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
- &event, keyEntry.policyFlags, &event);
+ bool fallback = false;
+ if (auto fb = mPolicy.dispatchUnhandledKey(connection->inputChannel->getConnectionToken(),
+ event, keyEntry.policyFlags);
+ fb) {
+ fallback = true;
+ event = *fb;
+ }
mLock.lock();
if (connection->status != Connection::Status::NORMAL) {
connection->inputState.removeFallbackKey(originalKeyCode);
- return false;
+ return {};
}
// Latch the fallback keycode for this key on an initial down.
// The fallback keycode cannot change at any other point in the lifecycle.
if (initialDown) {
if (fallback) {
- fallbackKeyCode = event.getKeyCode();
+ *fallbackKeyCode = event.getKeyCode();
} else {
- fallbackKeyCode = AKEYCODE_UNKNOWN;
+ *fallbackKeyCode = AKEYCODE_UNKNOWN;
}
- connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+ connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode);
}
- ALOG_ASSERT(fallbackKeyCode != -1);
+ ALOG_ASSERT(fallbackKeyCode);
// Cancel the fallback key if the policy decides not to send it anymore.
// We will continue to dispatch the key to the policy but we will no
// longer dispatch a fallback key to the application.
- if (fallbackKeyCode != AKEYCODE_UNKNOWN &&
- (!fallback || fallbackKeyCode != event.getKeyCode())) {
+ if (*fallbackKeyCode != AKEYCODE_UNKNOWN &&
+ (!fallback || *fallbackKeyCode != event.getKeyCode())) {
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
if (fallback) {
ALOGD("Unhandled key event: Policy requested to send key %d"
"as a fallback for %d, but on the DOWN it had requested "
"to send %d instead. Fallback canceled.",
- event.getKeyCode(), originalKeyCode, fallbackKeyCode);
+ event.getKeyCode(), originalKeyCode, *fallbackKeyCode);
} else {
ALOGD("Unhandled key event: Policy did not request fallback for %d, "
"but on the DOWN it had requested to send %d. "
"Fallback canceled.",
- originalKeyCode, fallbackKeyCode);
+ originalKeyCode, *fallbackKeyCode);
}
}
- CancelationOptions options(CancelationOptions::CANCEL_FALLBACK_EVENTS,
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS,
"canceling fallback, policy no longer desires it");
- options.keyCode = fallbackKeyCode;
+ options.keyCode = *fallbackKeyCode;
synthesizeCancelationEventsForConnectionLocked(connection, options);
fallback = false;
- fallbackKeyCode = AKEYCODE_UNKNOWN;
+ *fallbackKeyCode = AKEYCODE_UNKNOWN;
if (keyEntry.action != AKEY_EVENT_ACTION_UP) {
- connection->inputState.setFallbackKey(originalKeyCode, fallbackKeyCode);
+ connection->inputState.setFallbackKey(originalKeyCode, *fallbackKeyCode);
}
}
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
{
std::string msg;
- const KeyedVector<int32_t, int32_t>& fallbackKeys =
+ const std::map<int32_t, int32_t>& fallbackKeys =
connection->inputState.getFallbackKeys();
- for (size_t i = 0; i < fallbackKeys.size(); i++) {
- msg += StringPrintf(", %d->%d", fallbackKeys.keyAt(i), fallbackKeys.valueAt(i));
+ for (const auto& [key, value] : fallbackKeys) {
+ msg += StringPrintf(", %d->%d", key, value);
}
ALOGD("Unhandled key event: %zu currently tracked fallback keys%s.",
fallbackKeys.size(), msg.c_str());
@@ -6129,25 +6520,22 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& con
}
if (fallback) {
- // Restart the dispatch cycle using the fallback key.
- keyEntry.eventTime = event.getEventTime();
- keyEntry.deviceId = event.getDeviceId();
- keyEntry.source = event.getSource();
- keyEntry.displayId = event.getDisplayId();
- keyEntry.flags = event.getFlags() | AKEY_EVENT_FLAG_FALLBACK;
- keyEntry.keyCode = fallbackKeyCode;
- keyEntry.scanCode = event.getScanCode();
- keyEntry.metaState = event.getMetaState();
- keyEntry.repeatCount = event.getRepeatCount();
- keyEntry.downTime = event.getDownTime();
- keyEntry.syntheticRepeat = false;
-
+ // Return the fallback key that we want dispatched to the channel.
+ std::unique_ptr<KeyEntry> newEntry =
+ std::make_unique<KeyEntry>(mIdGenerator.nextId(), keyEntry.injectionState,
+ event.getEventTime(), event.getDeviceId(),
+ event.getSource(), event.getDisplayId(),
+ keyEntry.policyFlags, keyEntry.action,
+ event.getFlags() | AKEY_EVENT_FLAG_FALLBACK,
+ *fallbackKeyCode, event.getScanCode(),
+ event.getMetaState(), event.getRepeatCount(),
+ event.getDownTime());
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("Unhandled key event: Dispatching fallback key. "
"originalKeyCode=%d, fallbackKeyCode=%d, fallbackMetaState=%08x",
- originalKeyCode, fallbackKeyCode, keyEntry.metaState);
+ originalKeyCode, *fallbackKeyCode, keyEntry.metaState);
}
- return true; // restart the event
+ return newEntry;
} else {
if (DEBUG_OUTBOUND_EVENT_DETAILS) {
ALOGD("Unhandled key event: No fallback key.");
@@ -6157,13 +6545,7 @@ bool InputDispatcher::afterKeyEventLockedInterruptable(const sp<Connection>& con
mReporter->reportUnhandledKey(keyEntry.id);
}
}
- return false;
-}
-
-bool InputDispatcher::afterMotionEventLockedInterruptable(const sp<Connection>& connection,
- DispatchEntry* dispatchEntry,
- MotionEntry& motionEntry, bool handled) {
- return false;
+ return {};
}
void InputDispatcher::traceInboundQueueLengthLocked() {
@@ -6188,7 +6570,7 @@ void InputDispatcher::traceWaitQueueLength(const Connection& connection) {
}
}
-void InputDispatcher::dump(std::string& dump) {
+void InputDispatcher::dump(std::string& dump) const {
std::scoped_lock _l(mLock);
dump += "Input Dispatcher State:\n";
@@ -6213,7 +6595,7 @@ void InputDispatcher::monitor() {
* this method can be safely called from any thread, as long as you've ensured that
* the work you are interested in completing has already been queued.
*/
-bool InputDispatcher::waitForIdle() {
+bool InputDispatcher::waitForIdle() const {
/**
* Timeout should represent the longest possible time that a device might spend processing
* events and commands.
@@ -6256,14 +6638,15 @@ void InputDispatcher::onFocusChangedLocked(const FocusResolver::FocusChanges& ch
if (changes.oldFocus) {
std::shared_ptr<InputChannel> focusedInputChannel = getInputChannelLocked(changes.oldFocus);
if (focusedInputChannel) {
- CancelationOptions options(CancelationOptions::CANCEL_NON_POINTER_EVENTS,
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS,
"focus left window");
synthesizeCancelationEventsForInputChannelLocked(focusedInputChannel, options);
- enqueueFocusEventLocked(changes.oldFocus, false /*hasFocus*/, changes.reason);
+ enqueueFocusEventLocked(changes.oldFocus, /*hasFocus=*/false, changes.reason);
}
}
if (changes.newFocus) {
- enqueueFocusEventLocked(changes.newFocus, true /*hasFocus*/, changes.reason);
+ resetNoFocusedWindowTimeoutLocked();
+ enqueueFocusEventLocked(changes.newFocus, /*hasFocus=*/true, changes.reason);
}
// If a window has pointer capture, then it must have focus. We need to ensure that this
@@ -6314,7 +6697,7 @@ void InputDispatcher::setPointerCaptureLocked(bool enable) {
mCurrentPointerCaptureRequest.seq++;
auto command = [this, request = mCurrentPointerCaptureRequest]() REQUIRES(mLock) {
scoped_unlock unlock(mLock);
- mPolicy->setPointerCapture(request);
+ mPolicy.setPointerCapture(request);
};
postCommandLocked(std::move(command));
}
@@ -6323,27 +6706,29 @@ void InputDispatcher::displayRemoved(int32_t displayId) {
{ // acquire lock
std::scoped_lock _l(mLock);
// Set an empty list to remove all handles from the specific display.
- setInputWindowsLocked(/* window handles */ {}, displayId);
+ setInputWindowsLocked(/*windowInfoHandles=*/{}, displayId);
setFocusedApplicationLocked(displayId, nullptr);
// Call focus resolver to clean up stale requests. This must be called after input windows
// have been removed for the removed display.
mFocusResolver.displayRemoved(displayId);
// Reset pointer capture eligibility, regardless of previous state.
std::erase(mIneligibleDisplaysForPointerCapture, displayId);
+ // Remove the associated touch mode state.
+ mTouchModePerDisplay.erase(displayId);
+ mVerifiersByDisplay.erase(displayId);
} // release lock
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
}
-void InputDispatcher::onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
- const std::vector<DisplayInfo>& displayInfos) {
+void InputDispatcher::onWindowInfosChanged(const gui::WindowInfosUpdate& update) {
// The listener sends the windows as a flattened array. Separate the windows by display for
// more convenient parsing.
std::unordered_map<int32_t, std::vector<sp<WindowInfoHandle>>> handlesPerDisplay;
- for (const auto& info : windowInfos) {
+ for (const auto& info : update.windowInfos) {
handlesPerDisplay.emplace(info.displayId, std::vector<sp<WindowInfoHandle>>());
- handlesPerDisplay[info.displayId].push_back(new WindowInfoHandle(info));
+ handlesPerDisplay[info.displayId].push_back(sp<WindowInfoHandle>::make(info));
}
{ // acquire lock
@@ -6356,13 +6741,20 @@ void InputDispatcher::onWindowInfosChanged(const std::vector<WindowInfo>& window
}
mDisplayInfos.clear();
- for (const auto& displayInfo : displayInfos) {
+ for (const auto& displayInfo : update.displayInfos) {
mDisplayInfos.emplace(displayInfo.displayId, displayInfo);
}
for (const auto& [displayId, handles] : handlesPerDisplay) {
setInputWindowsLocked(handles, displayId);
}
+
+ if (update.vsyncId < mWindowInfosVsyncId) {
+ ALOGE("Received out of order window infos update. Last update vsync id: %" PRId64
+ ", current update vsync id: %" PRId64,
+ mWindowInfosVsyncId, update.vsyncId);
+ }
+ mWindowInfosVsyncId = update.vsyncId;
}
// Wake up poll loop since it may need to make new input dispatching choices.
mLooper->wake();
@@ -6385,21 +6777,19 @@ bool InputDispatcher::shouldDropInput(
}
void InputDispatcher::DispatcherWindowListener::onWindowInfosChanged(
- const std::vector<gui::WindowInfo>& windowInfos,
- const std::vector<DisplayInfo>& displayInfos) {
- mDispatcher.onWindowInfosChanged(windowInfos, displayInfos);
+ const gui::WindowInfosUpdate& update) {
+ mDispatcher.onWindowInfosChanged(update);
}
void InputDispatcher::cancelCurrentTouch() {
{
std::scoped_lock _l(mLock);
ALOGD("Canceling all ongoing pointer gestures on all displays.");
- CancelationOptions options(CancelationOptions::CANCEL_POINTER_EVENTS,
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
"cancel current touch");
synthesizeCancelationEventsForAllConnectionsLocked(options);
mTouchStatesByDisplay.clear();
- mLastHoverWindowHandle.clear();
}
// Wake up poll loop since there might be work to do.
mLooper->wake();
@@ -6410,4 +6800,133 @@ void InputDispatcher::setMonitorDispatchingTimeoutForTest(std::chrono::nanosecon
mMonitorDispatchingTimeout = timeout;
}
+void InputDispatcher::slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
+ const sp<WindowInfoHandle>& oldWindowHandle,
+ const sp<WindowInfoHandle>& newWindowHandle,
+ TouchState& state, int32_t deviceId, int32_t pointerId,
+ std::vector<InputTarget>& targets) const {
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(pointerId);
+ const bool oldHasWallpaper = oldWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const bool newHasWallpaper = targetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ newWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const sp<WindowInfoHandle> oldWallpaper =
+ oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+ const sp<WindowInfoHandle> newWallpaper =
+ newHasWallpaper ? findWallpaperWindowBelow(newWindowHandle) : nullptr;
+ if (oldWallpaper == newWallpaper) {
+ return;
+ }
+
+ if (oldWallpaper != nullptr) {
+ const TouchedWindow& oldTouchedWindow = state.getTouchedWindow(oldWallpaper);
+ addPointerWindowTargetLocked(oldWallpaper, InputTarget::DispatchMode::SLIPPERY_EXIT,
+ oldTouchedWindow.targetFlags, pointerIds,
+ oldTouchedWindow.getDownTimeInTarget(deviceId), targets);
+ state.removeTouchingPointerFromWindow(deviceId, pointerId, oldWallpaper);
+ }
+
+ if (newWallpaper != nullptr) {
+ state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::SLIPPERY_ENTER,
+ InputTarget::Flags::WINDOW_IS_OBSCURED |
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED,
+ deviceId, pointerIds);
+ }
+}
+
+void InputDispatcher::transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
+ ftl::Flags<InputTarget::Flags> newTargetFlags,
+ const sp<WindowInfoHandle> fromWindowHandle,
+ const sp<WindowInfoHandle> toWindowHandle,
+ TouchState& state, int32_t deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds) {
+ const bool oldHasWallpaper = oldTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ fromWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+ const bool newHasWallpaper = newTargetFlags.test(InputTarget::Flags::FOREGROUND) &&
+ toWindowHandle->getInfo()->inputConfig.test(
+ gui::WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER);
+
+ const sp<WindowInfoHandle> oldWallpaper =
+ oldHasWallpaper ? state.getWallpaperWindow() : nullptr;
+ const sp<WindowInfoHandle> newWallpaper =
+ newHasWallpaper ? findWallpaperWindowBelow(toWindowHandle) : nullptr;
+ if (oldWallpaper == newWallpaper) {
+ return;
+ }
+
+ if (oldWallpaper != nullptr) {
+ CancelationOptions options(CancelationOptions::Mode::CANCEL_POINTER_EVENTS,
+ "transferring touch focus to another window");
+ state.removeWindowByToken(oldWallpaper->getToken());
+ synthesizeCancelationEventsForWindowLocked(oldWallpaper, options);
+ }
+
+ if (newWallpaper != nullptr) {
+ nsecs_t downTimeInTarget = now();
+ ftl::Flags<InputTarget::Flags> wallpaperFlags = oldTargetFlags & InputTarget::Flags::SPLIT;
+ wallpaperFlags |= InputTarget::Flags::WINDOW_IS_OBSCURED |
+ InputTarget::Flags::WINDOW_IS_PARTIALLY_OBSCURED;
+ state.addOrUpdateWindow(newWallpaper, InputTarget::DispatchMode::AS_IS, wallpaperFlags,
+ deviceId, pointerIds, downTimeInTarget);
+ std::shared_ptr<Connection> wallpaperConnection =
+ getConnectionLocked(newWallpaper->getToken());
+ if (wallpaperConnection != nullptr) {
+ std::shared_ptr<Connection> toConnection =
+ getConnectionLocked(toWindowHandle->getToken());
+ toConnection->inputState.mergePointerStateTo(wallpaperConnection->inputState);
+ synthesizePointerDownEventsForConnectionLocked(downTimeInTarget, wallpaperConnection,
+ wallpaperFlags);
+ }
+ }
+}
+
+sp<WindowInfoHandle> InputDispatcher::findWallpaperWindowBelow(
+ const sp<WindowInfoHandle>& windowHandle) const {
+ const std::vector<sp<WindowInfoHandle>>& windowHandles =
+ getWindowHandlesLocked(windowHandle->getInfo()->displayId);
+ bool foundWindow = false;
+ for (const sp<WindowInfoHandle>& otherHandle : windowHandles) {
+ if (!foundWindow && otherHandle != windowHandle) {
+ continue;
+ }
+ if (windowHandle == otherHandle) {
+ foundWindow = true;
+ continue;
+ }
+
+ if (otherHandle->getInfo()->inputConfig.test(WindowInfo::InputConfig::IS_WALLPAPER)) {
+ return otherHandle;
+ }
+ }
+ return nullptr;
+}
+
+void InputDispatcher::setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+ std::chrono::nanoseconds delay) {
+ std::scoped_lock _l(mLock);
+
+ mConfig.keyRepeatTimeout = timeout.count();
+ mConfig.keyRepeatDelay = delay.count();
+}
+
+bool InputDispatcher::isPointerInWindow(const sp<android::IBinder>& token, int32_t displayId,
+ DeviceId deviceId, int32_t pointerId) {
+ std::scoped_lock _l(mLock);
+ auto touchStateIt = mTouchStatesByDisplay.find(displayId);
+ if (touchStateIt == mTouchStatesByDisplay.end()) {
+ return false;
+ }
+ for (const TouchedWindow& window : touchStateIt->second.windows) {
+ if (window.windowHandle->getToken() == token &&
+ (window.hasTouchingPointer(deviceId, pointerId) ||
+ window.hasHoveringPointer(deviceId, pointerId))) {
+ return true;
+ }
+ }
+ return false;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index ed89ed0b0f..3f99b2d40e 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_DISPATCHER_H
-#define _UI_INPUT_DISPATCHER_H
+#pragma once
#include "AnrTracker.h"
#include "CancelationOptions.h"
@@ -26,7 +25,6 @@
#include "InputDispatcherConfiguration.h"
#include "InputDispatcherInterface.h"
#include "InputDispatcherPolicyInterface.h"
-#include "InputState.h"
#include "InputTarget.h"
#include "InputThread.h"
#include "LatencyAggregator.h"
@@ -37,17 +35,17 @@
#include <attestation/HmacKeyManager.h>
#include <gui/InputApplication.h>
-#include <gui/WindowInfo.h>
+#include <gui/WindowInfosUpdate.h>
#include <input/Input.h>
#include <input/InputTransport.h>
#include <limits.h>
#include <stddef.h>
-#include <ui/Region.h>
#include <unistd.h>
#include <utils/BitSet.h>
#include <utils/Looper.h>
#include <utils/Timers.h>
#include <utils/threads.h>
+#include <bitset>
#include <condition_variable>
#include <deque>
#include <optional>
@@ -83,45 +81,41 @@ class InputDispatcher : public android::InputDispatcherInterface {
public:
static constexpr bool kDefaultInTouchMode = true;
- explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy);
- explicit InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy,
- std::chrono::nanoseconds staleEventTimeout);
+ explicit InputDispatcher(InputDispatcherPolicyInterface& policy);
~InputDispatcher() override;
- void dump(std::string& dump) override;
+ void dump(std::string& dump) const override;
void monitor() override;
- bool waitForIdle() override;
+ bool waitForIdle() const override;
status_t start() override;
status_t stop() 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;
+ 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;
android::os::InputEventInjectionResult injectInputEvent(
- const InputEvent* event, std::optional<int32_t> targetUid,
+ const InputEvent* event, std::optional<gui::Uid> targetUid,
android::os::InputEventInjectionSync syncMode, std::chrono::milliseconds timeout,
uint32_t policyFlags) override;
std::unique_ptr<VerifiedInputEvent> verifyInputEvent(const InputEvent& event) override;
- void setInputWindows(
- const std::unordered_map<int32_t, std::vector<sp<android::gui::WindowInfoHandle>>>&
- handlesPerDisplay) override;
void setFocusedApplication(
int32_t displayId,
const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle) override;
void setFocusedDisplay(int32_t displayId) override;
void setInputDispatchMode(bool enabled, bool frozen) override;
void setInputFilterEnabled(bool enabled) override;
- bool setInTouchMode(bool inTouchMode, int32_t pid, int32_t uid, bool hasPermission) override;
+ bool setInTouchMode(bool inTouchMode, gui::Pid pid, gui::Uid uid, bool hasPermission,
+ int32_t displayId) override;
void setMaximumObscuringOpacityForTouch(float opacity) override;
- void setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode mode) override;
bool transferTouchFocus(const sp<IBinder>& fromToken, const sp<IBinder>& toToken,
bool isDragDrop = false) override;
@@ -132,7 +126,7 @@ public:
void setFocusedWindow(const android::gui::FocusRequest&) override;
base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
const std::string& name,
- int32_t pid) override;
+ gui::Pid pid) override;
status_t removeInputChannel(const sp<IBinder>& connectionToken) override;
status_t pilferPointers(const sp<IBinder>& token) override;
void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled) override;
@@ -144,14 +138,19 @@ public:
void displayRemoved(int32_t displayId) override;
// Public because it's also used by tests to simulate the WindowInfosListener callback
- void onWindowInfosChanged(const std::vector<android::gui::WindowInfo>& windowInfos,
- const std::vector<android::gui::DisplayInfo>& displayInfos);
+ void onWindowInfosChanged(const gui::WindowInfosUpdate&);
void cancelCurrentTouch() override;
// Public to allow tests to verify that a Monitor can get ANR.
void setMonitorDispatchingTimeoutForTest(std::chrono::nanoseconds timeout);
+ void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+ std::chrono::nanoseconds delay) override;
+
+ bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
+ int32_t pointerId) override;
+
private:
enum class DropReason {
NOT_DROPPED,
@@ -165,19 +164,19 @@ private:
std::unique_ptr<InputThread> mThread;
- sp<InputDispatcherPolicyInterface> mPolicy;
- android::InputDispatcherConfiguration mConfig;
+ InputDispatcherPolicyInterface& mPolicy;
+ android::InputDispatcherConfiguration mConfig GUARDED_BY(mLock);
- std::mutex mLock;
+ mutable std::mutex mLock;
std::condition_variable mDispatcherIsAlive;
- std::condition_variable mDispatcherEnteredIdle;
+ mutable std::condition_variable mDispatcherEnteredIdle;
sp<Looper> mLooper;
- std::shared_ptr<EventEntry> mPendingEvent GUARDED_BY(mLock);
- std::deque<std::shared_ptr<EventEntry>> mInboundQueue GUARDED_BY(mLock);
- std::deque<std::shared_ptr<EventEntry>> mRecentQueue GUARDED_BY(mLock);
+ std::shared_ptr<const EventEntry> mPendingEvent GUARDED_BY(mLock);
+ std::deque<std::shared_ptr<const EventEntry>> mInboundQueue GUARDED_BY(mLock);
+ std::deque<std::shared_ptr<const EventEntry>> mRecentQueue GUARDED_BY(mLock);
// A command entry captures state and behavior for an action to be performed in the
// dispatch loop after the initial processing has taken place. It is essentially
@@ -201,7 +200,9 @@ private:
DropReason mLastDropReason GUARDED_BY(mLock);
- const IdGenerator mIdGenerator;
+ const IdGenerator mIdGenerator GUARDED_BY(mLock);
+
+ int64_t mWindowInfosVsyncId GUARDED_BY(mLock);
// With each iteration, InputDispatcher nominally processes one queued event,
// a timeout, or a response from an input consumer.
@@ -225,36 +226,41 @@ private:
REQUIRES(mLock);
// Adds an event to a queue of recent events for debugging purposes.
- void addRecentEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
+ void addRecentEventLocked(std::shared_ptr<const EventEntry> entry) REQUIRES(mLock);
// App switch latency optimization.
bool mAppSwitchSawKeyDown GUARDED_BY(mLock);
nsecs_t mAppSwitchDueTime GUARDED_BY(mLock);
bool isAppSwitchKeyEvent(const KeyEntry& keyEntry);
- bool isAppSwitchPendingLocked() REQUIRES(mLock);
+ bool isAppSwitchPendingLocked() const REQUIRES(mLock);
void resetPendingAppSwitchLocked(bool handled) REQUIRES(mLock);
// Blocked event latency optimization. Drops old events when the user intends
// to transfer focus to a new application.
- std::shared_ptr<EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
+ std::shared_ptr<const EventEntry> mNextUnblockedEvent GUARDED_BY(mLock);
sp<android::gui::WindowInfoHandle> findTouchedWindowAtLocked(
- int32_t displayId, int32_t x, int32_t y, TouchState* touchState, bool isStylus = false,
- bool addOutsideTargets = false, bool ignoreDragWindow = false) REQUIRES(mLock);
+ int32_t displayId, float x, float y, bool isStylus = false,
+ bool ignoreDragWindow = false) const REQUIRES(mLock);
+ std::vector<InputTarget> findOutsideTargetsLocked(
+ int32_t displayId, const sp<android::gui::WindowInfoHandle>& touchedWindow,
+ int32_t pointerId) const REQUIRES(mLock);
std::vector<sp<android::gui::WindowInfoHandle>> findTouchedSpyWindowsAtLocked(
- int32_t displayId, int32_t x, int32_t y, bool isStylus) const REQUIRES(mLock);
+ int32_t displayId, float x, float y, bool isStylus) const REQUIRES(mLock);
sp<android::gui::WindowInfoHandle> findTouchedForegroundWindowLocked(int32_t displayId) const
REQUIRES(mLock);
- sp<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
+ std::shared_ptr<Connection> getConnectionLocked(const sp<IBinder>& inputConnectionToken) const
REQUIRES(mLock);
std::string getConnectionNameLocked(const sp<IBinder>& connectionToken) const REQUIRES(mLock);
- void removeConnectionLocked(const sp<Connection>& connection) REQUIRES(mLock);
+ void removeConnectionLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
+
+ status_t pilferPointersLocked(const sp<IBinder>& token) REQUIRES(mLock);
template <typename T>
struct StrongPointerHash {
@@ -262,11 +268,11 @@ private:
};
// All registered connections mapped by input channel token.
- std::unordered_map<sp<IBinder>, sp<Connection>, StrongPointerHash<IBinder>> mConnectionsByToken
- GUARDED_BY(mLock);
+ std::unordered_map<sp<IBinder>, std::shared_ptr<Connection>, StrongPointerHash<IBinder>>
+ mConnectionsByToken GUARDED_BY(mLock);
// Find a monitor pid by the provided token.
- std::optional<int32_t> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
+ std::optional<gui::Pid> findMonitorPidByTokenLocked(const sp<IBinder>& token) REQUIRES(mLock);
// Input channels that will receive a copy of all input events sent to the provided display.
std::unordered_map<int32_t, std::vector<Monitor>> mGlobalMonitorsByDisplay GUARDED_BY(mLock);
@@ -279,44 +285,25 @@ private:
// Event injection and synchronization.
std::condition_variable mInjectionResultAvailable;
- void setInjectionResult(EventEntry& entry,
+ void setInjectionResult(const EventEntry& entry,
android::os::InputEventInjectionResult injectionResult);
void transformMotionEntryForInjectionLocked(MotionEntry&,
const ui::Transform& injectedTransform) const
REQUIRES(mLock);
std::condition_variable mInjectionSyncFinished;
- void incrementPendingForegroundDispatches(EventEntry& entry);
- void decrementPendingForegroundDispatches(EventEntry& entry);
+ void incrementPendingForegroundDispatches(const EventEntry& entry);
+ void decrementPendingForegroundDispatches(const EventEntry& entry);
// Key repeat tracking.
struct KeyRepeatState {
- std::shared_ptr<KeyEntry> lastKeyEntry; // or null if no repeat
+ std::shared_ptr<const KeyEntry> lastKeyEntry; // or null if no repeat
nsecs_t nextRepeatTime;
} mKeyRepeatState GUARDED_BY(mLock);
void resetKeyRepeatLocked() REQUIRES(mLock);
std::shared_ptr<KeyEntry> synthesizeKeyRepeatLocked(nsecs_t currentTime) REQUIRES(mLock);
- // Key replacement tracking
- struct KeyReplacement {
- int32_t keyCode;
- int32_t deviceId;
- bool operator==(const KeyReplacement& rhs) const {
- return keyCode == rhs.keyCode && deviceId == rhs.deviceId;
- }
- };
- struct KeyReplacementHash {
- size_t operator()(const KeyReplacement& key) const {
- return std::hash<int32_t>()(key.keyCode) ^ (std::hash<int32_t>()(key.deviceId) << 1);
- }
- };
- // Maps the key code replaced, device id tuple to the key code it was replaced with
- std::unordered_map<KeyReplacement, int32_t, KeyReplacementHash> mReplacedKeys GUARDED_BY(mLock);
- // Process certain Meta + Key combinations
- void accelerateMetaShortcuts(const int32_t deviceId, const int32_t action, int32_t& keyCode,
- int32_t& metaState);
-
// Deferred command processing.
bool haveCommandsLocked() const REQUIRES(mLock);
bool runCommandsLockedInterruptable() REQUIRES(mLock);
@@ -326,32 +313,33 @@ private:
std::chrono::nanoseconds mMonitorDispatchingTimeout GUARDED_BY(mLock);
nsecs_t processAnrsLocked() REQUIRES(mLock);
- std::chrono::nanoseconds getDispatchingTimeoutLocked(const sp<Connection>& connection)
- REQUIRES(mLock);
+ std::chrono::nanoseconds getDispatchingTimeoutLocked(
+ const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
// Input filter processing.
- bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs* args) REQUIRES(mLock);
- bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs* args) REQUIRES(mLock);
+ bool shouldSendKeyToInputFilterLocked(const NotifyKeyArgs& args) REQUIRES(mLock);
+ bool shouldSendMotionToInputFilterLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
// Inbound event processing.
void drainInboundQueueLocked() REQUIRES(mLock);
void releasePendingEventLocked() REQUIRES(mLock);
- void releaseInboundEventLocked(std::shared_ptr<EventEntry> entry) REQUIRES(mLock);
+ void releaseInboundEventLocked(std::shared_ptr<const EventEntry> entry) REQUIRES(mLock);
// Dispatch state.
bool mDispatchEnabled GUARDED_BY(mLock);
bool mDispatchFrozen GUARDED_BY(mLock);
bool mInputFilterEnabled GUARDED_BY(mLock);
- bool mInTouchMode GUARDED_BY(mLock);
float mMaximumObscuringOpacityForTouch GUARDED_BY(mLock);
- android::os::BlockUntrustedTouchesMode mBlockUntrustedTouchesMode GUARDED_BY(mLock);
+
+ // This map is not really needed, but it helps a lot with debugging (dumpsys input).
+ // In the java layer, touch mode states are spread across multiple DisplayContent objects,
+ // making harder to snapshot and retrieve them.
+ std::map<int32_t /*displayId*/, bool /*inTouchMode*/> mTouchModePerDisplay GUARDED_BY(mLock);
class DispatcherWindowListener : public gui::WindowInfosListener {
public:
explicit DispatcherWindowListener(InputDispatcher& dispatcher) : mDispatcher(dispatcher){};
- void onWindowInfosChanged(
- const std::vector<android::gui::WindowInfo>& windowInfos,
- const std::vector<android::gui::DisplayInfo>& displayInfos) override;
+ void onWindowInfosChanged(const gui::WindowInfosUpdate&) override;
private:
InputDispatcher& mDispatcher;
@@ -368,23 +356,21 @@ private:
// Get a reference to window handles by display, return an empty vector if not found.
const std::vector<sp<android::gui::WindowInfoHandle>>& getWindowHandlesLocked(
int32_t displayId) const REQUIRES(mLock);
- sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
- const sp<IBinder>& windowHandleToken) const REQUIRES(mLock);
+ ui::Transform getTransformLocked(int32_t displayId) const REQUIRES(mLock);
- // Same function as above, but faster. Since displayId is provided, this avoids the need
- // to loop through all displays.
- sp<android::gui::WindowInfoHandle> getWindowHandleLocked(const sp<IBinder>& windowHandleToken,
- int displayId) const REQUIRES(mLock);
+ sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
+ const sp<IBinder>& windowHandleToken, std::optional<int32_t> displayId = {}) const
+ REQUIRES(mLock);
sp<android::gui::WindowInfoHandle> getWindowHandleLocked(
const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
std::shared_ptr<InputChannel> getInputChannelLocked(const sp<IBinder>& windowToken) const
REQUIRES(mLock);
sp<android::gui::WindowInfoHandle> getFocusedWindowHandleLocked(int displayId) const
REQUIRES(mLock);
- bool hasResponsiveConnectionLocked(android::gui::WindowInfoHandle& windowHandle) const
- REQUIRES(mLock);
+ bool canWindowReceiveMotionLocked(const sp<android::gui::WindowInfoHandle>& window,
+ const MotionEntry& motionEntry) const REQUIRES(mLock);
- // Gets all the input targets (with their respective input channels) from the window handles
+ // Returns all the input targets (with their respective input channels) from the window handles
// passed as argument.
std::vector<InputTarget> getInputTargetsFromWindowHandlesLocked(
const std::vector<sp<android::gui::WindowInfoHandle>>& windowHandles) const
@@ -440,31 +426,32 @@ private:
// when switching touch mode state).
std::unordered_set<sp<IBinder>, StrongPointerHash<IBinder>> mInteractionConnectionTokens
GUARDED_BY(mLock);
- void updateInteractionTokensLocked(const EventEntry& entry,
- const std::vector<InputTarget>& targets) REQUIRES(mLock);
+ void processInteractionsLocked(const EventEntry& entry, const std::vector<InputTarget>& targets)
+ REQUIRES(mLock);
// Dispatch inbound events.
bool dispatchConfigurationChangedLocked(nsecs_t currentTime,
const ConfigurationChangedEntry& entry) REQUIRES(mLock);
bool dispatchDeviceResetLocked(nsecs_t currentTime, const DeviceResetEntry& entry)
REQUIRES(mLock);
- bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<KeyEntry> entry,
+ bool dispatchKeyLocked(nsecs_t currentTime, std::shared_ptr<const KeyEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
- bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<MotionEntry> entry,
+ bool dispatchMotionLocked(nsecs_t currentTime, std::shared_ptr<const MotionEntry> entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
- void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<FocusEntry> entry)
+ void dispatchFocusLocked(nsecs_t currentTime, std::shared_ptr<const FocusEntry> entry)
REQUIRES(mLock);
void dispatchPointerCaptureChangedLocked(
- nsecs_t currentTime, const std::shared_ptr<PointerCaptureChangedEntry>& entry,
+ nsecs_t currentTime, const std::shared_ptr<const PointerCaptureChangedEntry>& entry,
DropReason& dropReason) REQUIRES(mLock);
void dispatchTouchModeChangeLocked(nsecs_t currentTime,
- const std::shared_ptr<TouchModeEntry>& entry)
+ const std::shared_ptr<const TouchModeEntry>& entry)
REQUIRES(mLock);
- void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<EventEntry> entry,
+ void dispatchEventLocked(nsecs_t currentTime, std::shared_ptr<const EventEntry> entry,
const std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
- void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<SensorEntry>& entry,
+ void dispatchSensorLocked(nsecs_t currentTime, const std::shared_ptr<const SensorEntry>& entry,
DropReason* dropReason, nsecs_t* nextWakeupTime) REQUIRES(mLock);
- void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<DragEntry> entry) REQUIRES(mLock);
+ void dispatchDragLocked(nsecs_t currentTime, std::shared_ptr<const DragEntry> entry)
+ REQUIRES(mLock);
void logOutboundKeyDetails(const char* prefix, const KeyEntry& entry);
void logOutboundMotionDetails(const char* prefix, const MotionEntry& entry);
@@ -477,9 +464,6 @@ private:
*/
std::optional<nsecs_t> mNoFocusedWindowTimeoutTime GUARDED_BY(mLock);
- // Amount of time to allow for an event to be dispatched (measured since its eventTime)
- // before considering it stale and dropping it.
- const std::chrono::nanoseconds mStaleEventTimeout;
bool isStaleEvent(nsecs_t currentTime, const EventEntry& entry);
bool shouldPruneInboundQueueLocked(const MotionEntry& motionEntry) REQUIRES(mLock);
@@ -515,10 +499,10 @@ private:
void processConnectionResponsiveLocked(const Connection& connection) REQUIRES(mLock);
void sendWindowUnresponsiveCommandLocked(const sp<IBinder>& connectionToken,
- std::optional<int32_t> pid, std::string reason)
+ std::optional<gui::Pid> pid, std::string reason)
REQUIRES(mLock);
void sendWindowResponsiveCommandLocked(const sp<IBinder>& connectionToken,
- std::optional<int32_t> pid) REQUIRES(mLock);
+ std::optional<gui::Pid> pid) REQUIRES(mLock);
// Optimization: AnrTracker is used to quickly find which connection is due for a timeout next.
// AnrTracker must be kept in-sync with all responsive connection.waitQueues.
@@ -527,10 +511,7 @@ private:
// prevent unneeded wakeups.
AnrTracker mAnrTracker GUARDED_BY(mLock);
- // Contains the last window which received a hover event.
- sp<android::gui::WindowInfoHandle> mLastHoverWindowHandle GUARDED_BY(mLock);
-
- void cancelEventsForAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
+ void cancelEventsForAnrLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
// If a focused application changes, we should stop counting down the "no focused window" time,
// because we will have no way of knowing when the previous application actually added a window.
// This also means that we will miss cases like pulling down notification shade when the
@@ -539,18 +520,30 @@ private:
void resetNoFocusedWindowTimeoutLocked() REQUIRES(mLock);
int32_t getTargetDisplayId(const EventEntry& entry);
- android::os::InputEventInjectionResult findFocusedWindowTargetsLocked(
- nsecs_t currentTime, const EventEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime) REQUIRES(mLock);
- android::os::InputEventInjectionResult findTouchedWindowTargetsLocked(
- nsecs_t currentTime, const MotionEntry& entry, std::vector<InputTarget>& inputTargets,
- nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) REQUIRES(mLock);
+ sp<android::gui::WindowInfoHandle> findFocusedWindowTargetLocked(
+ nsecs_t currentTime, const EventEntry& entry, nsecs_t* nextWakeupTime,
+ android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
+ std::vector<InputTarget> findTouchedWindowTargetsLocked(
+ nsecs_t currentTime, const MotionEntry& entry,
+ android::os::InputEventInjectionResult& outInjectionResult) REQUIRES(mLock);
std::vector<Monitor> selectResponsiveMonitorsLocked(
const std::vector<Monitor>& gestureMonitors) const REQUIRES(mLock);
+ std::optional<InputTarget> createInputTargetLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ InputTarget::DispatchMode dispatchMode, ftl::Flags<InputTarget::Flags> targetFlags,
+ std::optional<nsecs_t> firstDownTimeInTarget) const REQUIRES(mLock);
void addWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
- int32_t targetFlags, BitSet32 pointerIds,
- std::vector<InputTarget>& inputTargets) REQUIRES(mLock);
+ InputTarget::DispatchMode dispatchMode,
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::optional<nsecs_t> firstDownTimeInTarget,
+ std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
+ void addPointerWindowTargetLocked(const sp<android::gui::WindowInfoHandle>& windowHandle,
+ InputTarget::DispatchMode dispatchMode,
+ ftl::Flags<InputTarget::Flags> targetFlags,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ std::optional<nsecs_t> firstDownTimeInTarget,
+ std::vector<InputTarget>& inputTargets) const REQUIRES(mLock);
void addGlobalMonitoringTargetsLocked(std::vector<InputTarget>& inputTargets, int32_t displayId)
REQUIRES(mLock);
void pokeUserActivityLocked(const EventEntry& eventEntry) REQUIRES(mLock);
@@ -563,7 +556,7 @@ private:
bool hasBlockingOcclusion;
float obscuringOpacity;
std::string obscuringPackage;
- int32_t obscuringUid;
+ gui::Uid obscuringUid = gui::Uid::INVALID;
std::vector<std::string> debugInfo;
};
@@ -588,23 +581,27 @@ private:
// These methods are deliberately not Interruptible because doing all of the work
// with the mutex held makes it easier to ensure that connection invariants are maintained.
// If needed, the methods post commands to run later once the critical bits are done.
- void prepareDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
- std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
- REQUIRES(mLock);
- void enqueueDispatchEntriesLocked(nsecs_t currentTime, const sp<Connection>& connection,
- std::shared_ptr<EventEntry>, const InputTarget& inputTarget)
- REQUIRES(mLock);
- void enqueueDispatchEntryLocked(const sp<Connection>& connection, std::shared_ptr<EventEntry>,
- const InputTarget& inputTarget, int32_t dispatchMode)
- REQUIRES(mLock);
- void startDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection)
- REQUIRES(mLock);
- void finishDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
- uint32_t seq, bool handled, nsecs_t consumeTime) REQUIRES(mLock);
- void abortBrokenDispatchCycleLocked(nsecs_t currentTime, const sp<Connection>& connection,
- bool notify) REQUIRES(mLock);
- void drainDispatchQueue(std::deque<DispatchEntry*>& queue);
- void releaseDispatchEntry(DispatchEntry* dispatchEntry);
+ void prepareDispatchCycleLocked(nsecs_t currentTime,
+ const std::shared_ptr<Connection>& connection,
+ std::shared_ptr<const EventEntry>,
+ const InputTarget& inputTarget) REQUIRES(mLock);
+ void enqueueDispatchEntryAndStartDispatchCycleLocked(
+ nsecs_t currentTime, const std::shared_ptr<Connection>& connection,
+ std::shared_ptr<const EventEntry>, const InputTarget& inputTarget) REQUIRES(mLock);
+ void enqueueDispatchEntryLocked(const std::shared_ptr<Connection>& connection,
+ std::shared_ptr<const EventEntry>,
+ const InputTarget& inputTarget) REQUIRES(mLock);
+ status_t publishMotionEvent(Connection& connection, DispatchEntry& dispatchEntry) const;
+ void startDispatchCycleLocked(nsecs_t currentTime,
+ const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
+ void finishDispatchCycleLocked(nsecs_t currentTime,
+ const std::shared_ptr<Connection>& connection, uint32_t seq,
+ bool handled, nsecs_t consumeTime) REQUIRES(mLock);
+ void abortBrokenDispatchCycleLocked(nsecs_t currentTime,
+ const std::shared_ptr<Connection>& connection, bool notify)
+ REQUIRES(mLock);
+ void drainDispatchQueue(std::deque<std::unique_ptr<DispatchEntry>>& queue);
+ void releaseDispatchEntry(std::unique_ptr<DispatchEntry> dispatchEntry);
int handleReceiveCallback(int events, sp<IBinder> connectionToken);
// The action sent should only be of type AMOTION_EVENT_*
void dispatchPointerDownOutsideFocus(uint32_t source, int32_t action,
@@ -617,25 +614,32 @@ private:
void synthesizeCancelationEventsForInputChannelLocked(
const std::shared_ptr<InputChannel>& channel, const CancelationOptions& options)
REQUIRES(mLock);
- void synthesizeCancelationEventsForConnectionLocked(const sp<Connection>& connection,
- const CancelationOptions& options)
+ void synthesizeCancelationEventsForConnectionLocked(
+ const std::shared_ptr<Connection>& connection, const CancelationOptions& options)
REQUIRES(mLock);
- void synthesizePointerDownEventsForConnectionLocked(const sp<Connection>& connection)
- REQUIRES(mLock);
+ void synthesizePointerDownEventsForConnectionLocked(
+ const nsecs_t downTime, const std::shared_ptr<Connection>& connection,
+ ftl::Flags<InputTarget::Flags> targetFlags) REQUIRES(mLock);
+
+ void synthesizeCancelationEventsForWindowLocked(
+ const sp<android::gui::WindowInfoHandle>& windowHandle,
+ const CancelationOptions& options) REQUIRES(mLock);
- // Splitting motion events across windows.
+ // Splitting motion events across windows. When splitting motion event for a target,
+ // splitDownTime refers to the time of first 'down' event on that particular target
std::unique_ptr<MotionEntry> splitMotionEvent(const MotionEntry& originalMotionEntry,
- BitSet32 pointerIds);
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t splitDownTime) REQUIRES(mLock);
// Reset and drop everything the dispatcher is doing.
void resetAndDropEverythingLocked(const char* reason) REQUIRES(mLock);
// Dump state.
- void dumpDispatchStateLocked(std::string& dump) REQUIRES(mLock);
- void dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors);
- void logDispatchStateLocked() REQUIRES(mLock);
- std::string dumpPointerCaptureStateLocked() REQUIRES(mLock);
+ void dumpDispatchStateLocked(std::string& dump) const REQUIRES(mLock);
+ void dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) const;
+ void logDispatchStateLocked() const REQUIRES(mLock);
+ std::string dumpPointerCaptureStateLocked() const REQUIRES(mLock);
// Registration.
void removeMonitorChannelLocked(const sp<IBinder>& connectionToken) REQUIRES(mLock);
@@ -643,17 +647,16 @@ private:
REQUIRES(mLock);
// Interesting events that we might like to log or tell the framework about.
- void doDispatchCycleFinishedCommand(nsecs_t finishTime, const sp<Connection>& connection,
- uint32_t seq, bool handled, nsecs_t consumeTime)
- REQUIRES(mLock);
+ void doDispatchCycleFinishedCommand(nsecs_t finishTime,
+ const std::shared_ptr<Connection>& connection, uint32_t seq,
+ bool handled, nsecs_t consumeTime) REQUIRES(mLock);
void doInterceptKeyBeforeDispatchingCommand(const sp<IBinder>& focusedWindowToken,
- KeyEntry& entry) REQUIRES(mLock);
+ const KeyEntry& entry) REQUIRES(mLock);
void onFocusChangedLocked(const FocusResolver::FocusChanges& changes) REQUIRES(mLock);
void sendFocusChangedCommandLocked(const sp<IBinder>& oldToken, const sp<IBinder>& newToken)
REQUIRES(mLock);
void sendDropWindowCommandLocked(const sp<IBinder>& token, float x, float y) REQUIRES(mLock);
- void sendUntrustedTouchCommandLocked(const std::string& obscuringPackage) REQUIRES(mLock);
- void onAnrLocked(const sp<Connection>& connection) REQUIRES(mLock);
+ void onAnrLocked(const std::shared_ptr<Connection>& connection) REQUIRES(mLock);
void onAnrLocked(std::shared_ptr<InputApplicationHandle> application) REQUIRES(mLock);
void updateLastAnrStateLocked(const sp<android::gui::WindowInfoHandle>& window,
const std::string& reason) REQUIRES(mLock);
@@ -661,16 +664,15 @@ private:
const std::string& reason) REQUIRES(mLock);
void updateLastAnrStateLocked(const std::string& windowLabel, const std::string& reason)
REQUIRES(mLock);
- bool afterKeyEventLockedInterruptable(const sp<Connection>& connection,
- DispatchEntry* dispatchEntry, KeyEntry& keyEntry,
- bool handled) REQUIRES(mLock);
- bool afterMotionEventLockedInterruptable(const sp<Connection>& connection,
- DispatchEntry* dispatchEntry, MotionEntry& motionEntry,
- bool handled) REQUIRES(mLock);
+ std::map<int32_t /*displayId*/, InputVerifier> mVerifiersByDisplay;
+ // Returns a fallback KeyEntry that should be sent to the connection, if required.
+ std::unique_ptr<const KeyEntry> afterKeyEventLockedInterruptable(
+ const std::shared_ptr<Connection>& connection, DispatchEntry& dispatchEntry,
+ const KeyEntry& keyEntry, bool handled) REQUIRES(mLock);
// Find touched state and touched window by token.
- std::pair<TouchState*, TouchedWindow*> findTouchStateAndWindowLocked(const sp<IBinder>& token)
- REQUIRES(mLock);
+ std::tuple<TouchState*, TouchedWindow*, int32_t /*displayId*/>
+ findTouchStateWindowAndDisplayLocked(const sp<IBinder>& token) REQUIRES(mLock);
// Statistics gathering.
LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
@@ -680,12 +682,25 @@ private:
void traceWaitQueueLength(const Connection& connection);
// Check window ownership
- bool focusedWindowIsOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
- bool recentWindowsAreOwnedByLocked(int32_t pid, int32_t uid) REQUIRES(mLock);
+ bool focusedWindowIsOwnedByLocked(gui::Pid pid, gui::Uid uid) REQUIRES(mLock);
+ bool recentWindowsAreOwnedByLocked(gui::Pid pid, gui::Uid uid) REQUIRES(mLock);
sp<InputReporterInterface> mReporter;
+
+ void slipWallpaperTouch(ftl::Flags<InputTarget::Flags> targetFlags,
+ const sp<android::gui::WindowInfoHandle>& oldWindowHandle,
+ const sp<android::gui::WindowInfoHandle>& newWindowHandle,
+ TouchState& state, int32_t deviceId, int32_t pointerId,
+ std::vector<InputTarget>& targets) const REQUIRES(mLock);
+ void transferWallpaperTouch(ftl::Flags<InputTarget::Flags> oldTargetFlags,
+ ftl::Flags<InputTarget::Flags> newTargetFlags,
+ const sp<android::gui::WindowInfoHandle> fromWindowHandle,
+ const sp<android::gui::WindowInfoHandle> toWindowHandle,
+ TouchState& state, int32_t deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds) REQUIRES(mLock);
+
+ sp<android::gui::WindowInfoHandle> findWallpaperWindowBelow(
+ const sp<android::gui::WindowInfoHandle>& windowHandle) const REQUIRES(mLock);
};
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_DISPATCHER_H
diff --git a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
index bca1600db9..3ef8419221 100644
--- a/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcherFactory.cpp
@@ -20,7 +20,7 @@
namespace android {
std::unique_ptr<InputDispatcherInterface> createInputDispatcher(
- const sp<InputDispatcherPolicyInterface>& policy) {
+ InputDispatcherPolicyInterface& policy) {
return std::make_unique<android::inputdispatcher::InputDispatcher>(policy);
}
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.cpp b/services/inputflinger/dispatcher/InputEventTimeline.cpp
index 3edb6381d4..a7c6d162d8 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.cpp
+++ b/services/inputflinger/dispatcher/InputEventTimeline.cpp
@@ -16,6 +16,8 @@
#include "InputEventTimeline.h"
+#include "../InputDeviceMetricsSource.h"
+
namespace android::inputdispatcher {
ConnectionTimeline::ConnectionTimeline(nsecs_t deliveryTime, nsecs_t consumeTime,
@@ -64,8 +66,15 @@ bool ConnectionTimeline::operator!=(const ConnectionTimeline& rhs) const {
return !operator==(rhs);
}
-InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime)
- : isDown(isDown), eventTime(eventTime), readTime(readTime) {}
+InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime,
+ uint16_t vendorId, uint16_t productId,
+ std::set<InputDeviceUsageSource> sources)
+ : isDown(isDown),
+ eventTime(eventTime),
+ readTime(readTime),
+ vendorId(vendorId),
+ productId(productId),
+ sources(sources) {}
bool InputEventTimeline::operator==(const InputEventTimeline& rhs) const {
if (connectionTimelines.size() != rhs.connectionTimelines.size()) {
@@ -80,7 +89,8 @@ bool InputEventTimeline::operator==(const InputEventTimeline& rhs) const {
return false;
}
}
- return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime;
+ return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime &&
+ vendorId == rhs.vendorId && productId == rhs.productId && sources == rhs.sources;
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h
index 77b8472f89..e9deb2d3cf 100644
--- a/services/inputflinger/dispatcher/InputEventTimeline.h
+++ b/services/inputflinger/dispatcher/InputEventTimeline.h
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
+#pragma once
+
+#include "../InputDeviceMetricsSource.h"
#include <binder/IBinder.h>
#include <input/Input.h>
@@ -74,10 +75,14 @@ private:
};
struct InputEventTimeline {
- InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime);
+ InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime, uint16_t vendorId,
+ uint16_t productId, std::set<InputDeviceUsageSource> sources);
const bool isDown; // True if this is an ACTION_DOWN event
const nsecs_t eventTime;
const nsecs_t readTime;
+ const uint16_t vendorId;
+ const uint16_t productId;
+ const std::set<InputDeviceUsageSource> sources;
struct IBinderHash {
std::size_t operator()(const sp<IBinder>& b) const {
@@ -104,5 +109,3 @@ public:
} // namespace inputdispatcher
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
diff --git a/services/inputflinger/dispatcher/InputState.cpp b/services/inputflinger/dispatcher/InputState.cpp
index f46a8bce7e..a4ac4fbe6c 100644
--- a/services/inputflinger/dispatcher/InputState.cpp
+++ b/services/inputflinger/dispatcher/InputState.cpp
@@ -28,11 +28,7 @@ InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerato
InputState::~InputState() {}
-bool InputState::isNeutral() const {
- return mKeyMementos.empty() && mMotionMementos.empty();
-}
-
-bool InputState::isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const {
+bool InputState::isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const {
for (const MotionMemento& memento : mMotionMementos) {
if (memento.deviceId == deviceId && memento.source == source &&
memento.displayId == displayId && memento.hovering) {
@@ -42,17 +38,12 @@ bool InputState::isHovering(int32_t deviceId, uint32_t source, int32_t displayId
return false;
}
-bool InputState::trackKey(const KeyEntry& entry, int32_t action, int32_t flags) {
- switch (action) {
+bool InputState::trackKey(const KeyEntry& entry, int32_t flags) {
+ switch (entry.action) {
case AKEY_EVENT_ACTION_UP: {
if (entry.flags & AKEY_EVENT_FLAG_FALLBACK) {
- for (size_t i = 0; i < mFallbackKeys.size();) {
- if (mFallbackKeys.valueAt(i) == entry.keyCode) {
- mFallbackKeys.removeItemsAt(i);
- } else {
- i += 1;
- }
- }
+ std::erase_if(mFallbackKeys,
+ [&entry](const auto& item) { return item.second == entry.keyCode; });
}
ssize_t index = findKeyMemento(entry);
if (index >= 0) {
@@ -92,30 +83,46 @@ bool InputState::trackKey(const KeyEntry& entry, int32_t action, int32_t flags)
}
}
-bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t flags) {
- int32_t actionMasked = action & AMOTION_EVENT_ACTION_MASK;
+/**
+ * Return:
+ * true if the incoming event was correctly tracked,
+ * false if the incoming event should be dropped.
+ */
+bool InputState::trackMotion(const MotionEntry& entry, int32_t flags) {
+ // Don't track non-pointer events
+ if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+ // This is a focus-dispatched event; we don't track its state.
+ return true;
+ }
+
+ if (!mMotionMementos.empty()) {
+ const MotionMemento& lastMemento = mMotionMementos.back();
+ if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) &&
+ !isStylusEvent(entry.source, entry.pointerProperties)) {
+ // We already have a stylus stream, and the new event is not from stylus.
+ return false;
+ }
+ }
+
+ int32_t actionMasked = entry.action & AMOTION_EVENT_ACTION_MASK;
switch (actionMasked) {
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL: {
- ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/false);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
return true;
}
- if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("Dropping inconsistent motion up or cancel event: deviceId=%d, source=%08x, "
- "displayId=%" PRId32 ", actionMasked=%d",
- entry.deviceId, entry.source, entry.displayId, actionMasked);
- }
+
return false;
}
case AMOTION_EVENT_ACTION_DOWN: {
- ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/false);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
}
- addMotionMemento(entry, flags, false /*hovering*/);
+ addMotionMemento(entry, flags, /*hovering=*/false);
return true;
}
@@ -129,7 +136,7 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f
return true;
}
- ssize_t index = findMotionMemento(entry, false /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/false);
if (entry.source & AINPUT_SOURCE_CLASS_JOYSTICK) {
// Joysticks can send MOVE events without a corresponding DOWN or UP. Since all
@@ -145,7 +152,7 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f
memento.setPointers(entry);
}
} else if (!entry.pointerCoords[0].isEmpty()) {
- addMotionMemento(entry, flags, false /*hovering*/);
+ addMotionMemento(entry, flags, /*hovering=*/false);
}
// Joysticks and trackballs can send MOVE events without corresponding DOWN or UP.
@@ -159,35 +166,27 @@ bool InputState::trackMotion(const MotionEntry& entry, int32_t action, int32_t f
return true;
}
}
- if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("Dropping inconsistent motion pointer up/down or move event: "
- "deviceId=%d, source=%08x, displayId=%" PRId32 ", actionMasked=%d",
- entry.deviceId, entry.source, entry.displayId, actionMasked);
- }
+
return false;
}
case AMOTION_EVENT_ACTION_HOVER_EXIT: {
- ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/true);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
return true;
}
- if (DEBUG_OUTBOUND_EVENT_DETAILS) {
- ALOGD("Dropping inconsistent motion hover exit event: deviceId=%d, source=%08x, "
- "displayId=%" PRId32,
- entry.deviceId, entry.source, entry.displayId);
- }
+
return false;
}
case AMOTION_EVENT_ACTION_HOVER_ENTER:
case AMOTION_EVENT_ACTION_HOVER_MOVE: {
- ssize_t index = findMotionMemento(entry, true /*hovering*/);
+ ssize_t index = findMotionMemento(entry, /*hovering=*/true);
if (index >= 0) {
mMotionMementos.erase(mMotionMementos.begin() + index);
}
- addMotionMemento(entry, flags, true /*hovering*/);
+ addMotionMemento(entry, flags, /*hovering=*/true);
return true;
}
@@ -251,54 +250,188 @@ void InputState::addMotionMemento(const MotionEntry& entry, int32_t flags, bool
}
void InputState::MotionMemento::setPointers(const MotionEntry& entry) {
- pointerCount = entry.pointerCount;
- for (uint32_t i = 0; i < entry.pointerCount; i++) {
- pointerProperties[i].copyFrom(entry.pointerProperties[i]);
- pointerCoords[i].copyFrom(entry.pointerCoords[i]);
+ pointerProperties.clear();
+ pointerCoords.clear();
+
+ for (uint32_t i = 0; i < entry.getPointerCount(); i++) {
+ if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) {
+ // In POINTER_UP events, the pointer is leaving. Since the action is not stored,
+ // this departing pointer should not be recorded.
+ const uint8_t actionIndex = MotionEvent::getActionIndex(entry.action);
+ if (i == actionIndex) {
+ continue;
+ }
+ }
+ pointerProperties.push_back(entry.pointerProperties[i]);
+ pointerCoords.push_back(entry.pointerCoords[i]);
}
}
void InputState::MotionMemento::mergePointerStateTo(MotionMemento& other) const {
- for (uint32_t i = 0; i < pointerCount; i++) {
+ for (uint32_t i = 0; i < getPointerCount(); i++) {
if (other.firstNewPointerIdx < 0) {
- other.firstNewPointerIdx = other.pointerCount;
+ other.firstNewPointerIdx = other.getPointerCount();
}
- other.pointerProperties[other.pointerCount].copyFrom(pointerProperties[i]);
- other.pointerCoords[other.pointerCount].copyFrom(pointerCoords[i]);
- other.pointerCount++;
+ other.pointerProperties.push_back(pointerProperties[i]);
+ other.pointerCoords.push_back(pointerCoords[i]);
}
}
+size_t InputState::MotionMemento::getPointerCount() const {
+ return pointerProperties.size();
+}
+
+bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) const {
+ if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) {
+ // This is a focus-dispatched event that should not affect the previous stream.
+ return false;
+ }
+
+ // New MotionEntry pointer event is coming in.
+
+ // If this is a new gesture, and it's from a different device, then, in general, we will cancel
+ // the current gesture.
+ // However, because stylus should be preferred over touch, we need to treat some cases in a
+ // special way.
+ if (mMotionMementos.empty()) {
+ // There is no ongoing pointer gesture, so there is nothing to cancel
+ return false;
+ }
+
+ const MotionMemento& lastMemento = mMotionMementos.back();
+ const int32_t actionMasked = MotionEvent::getActionMasked(motionEntry.action);
+
+ // For compatibility, only one input device can be active at a time in the same window.
+ if (lastMemento.deviceId == motionEntry.deviceId) {
+ // In general, the same device should produce self-consistent streams so nothing needs to
+ // be canceled. But there is one exception:
+ // Sometimes ACTION_DOWN is received without a corresponding HOVER_EXIT. To account for
+ // that, cancel the previous hovering stream
+ if (actionMasked == AMOTION_EVENT_ACTION_DOWN && lastMemento.hovering) {
+ return true;
+ }
+
+ // Use the previous stream cancellation logic to generate all HOVER_EXIT events.
+ // If this hover event was generated as a result of the pointer leaving the window,
+ // the HOVER_EXIT event should have the same coordinates as the previous
+ // HOVER_MOVE event in this stream. Ensure that all HOVER_EXITs have the same
+ // coordinates as the previous event by cancelling the stream here. With this approach, the
+ // HOVER_EXIT event is generated from the previous event.
+ if (actionMasked == AMOTION_EVENT_ACTION_HOVER_EXIT && lastMemento.hovering) {
+ return true;
+ }
+
+ // If the stream changes its source, just cancel the current gesture to be safe. It's
+ // possible that the app isn't handling source changes properly
+ if (motionEntry.source != lastMemento.source) {
+ LOG(INFO) << "Canceling stream: last source was "
+ << inputEventSourceToString(lastMemento.source) << " and new event is "
+ << motionEntry;
+ return true;
+ }
+
+ // If the injection is happening into two different displays, the same injected device id
+ // could be going into both. And at this time, if mirroring is active, the same connection
+ // would receive different events from each display. Since the TouchStates are per-display,
+ // it's unlikely that those two streams would be consistent with each other. Therefore,
+ // cancel the previous gesture if the display id changes.
+ if (motionEntry.displayId != lastMemento.displayId) {
+ LOG(INFO) << "Canceling stream: last displayId was "
+ << inputEventSourceToString(lastMemento.displayId) << " and new event is "
+ << motionEntry;
+ return true;
+ }
+
+ return false;
+ }
+
+ if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) {
+ // A stylus is already active.
+ if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) &&
+ actionMasked == AMOTION_EVENT_ACTION_DOWN) {
+ // If this new event is from a different device, then cancel the old
+ // stylus and allow the new stylus to take over, but only if it's going down.
+ // Otherwise, they will start to race each other.
+ return true;
+ }
+
+ // Keep the current stylus gesture.
+ return false;
+ }
+
+ // Cancel the current gesture if this is a start of a new gesture from a new device.
+ if (actionMasked == AMOTION_EVENT_ACTION_DOWN ||
+ actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) {
+ return true;
+ }
+ // By default, don't cancel any events.
+ return false;
+}
+
+std::unique_ptr<EventEntry> InputState::cancelConflictingInputStream(
+ const MotionEntry& motionEntry) {
+ if (!shouldCancelPreviousStream(motionEntry)) {
+ return {};
+ }
+
+ const MotionMemento& memento = mMotionMementos.back();
+
+ // Cancel the last device stream
+ std::unique_ptr<MotionEntry> cancelEntry =
+ createCancelEntryForMemento(memento, motionEntry.eventTime);
+
+ if (!trackMotion(*cancelEntry, cancelEntry->flags)) {
+ LOG(FATAL) << "Generated inconsistent cancel event!";
+ }
+ return cancelEntry;
+}
+
+std::unique_ptr<MotionEntry> InputState::createCancelEntryForMemento(const MotionMemento& memento,
+ nsecs_t eventTime) const {
+ const int32_t action =
+ memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+ int32_t flags = memento.flags;
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ return std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+ eventTime, memento.deviceId, memento.source,
+ memento.displayId, memento.policyFlags, action,
+ /*actionButton=*/0, flags, AMETA_NONE,
+ /*buttonState=*/0, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+ memento.yPrecision, memento.xCursorPosition,
+ memento.yCursorPosition, memento.downTime,
+ memento.pointerProperties, memento.pointerCoords);
+}
+
std::vector<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents(
nsecs_t currentTime, const CancelationOptions& options) {
std::vector<std::unique_ptr<EventEntry>> events;
for (KeyMemento& memento : mKeyMementos) {
if (shouldCancelKey(memento, options)) {
events.push_back(
- std::make_unique<KeyEntry>(mIdGenerator.nextId(), currentTime, memento.deviceId,
- memento.source, memento.displayId,
- memento.policyFlags, AKEY_EVENT_ACTION_UP,
+ std::make_unique<KeyEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+ currentTime, memento.deviceId, memento.source,
+ memento.displayId, memento.policyFlags,
+ AKEY_EVENT_ACTION_UP,
memento.flags | AKEY_EVENT_FLAG_CANCELED,
memento.keyCode, memento.scanCode, memento.metaState,
- 0 /*repeatCount*/, memento.downTime));
+ /*repeatCount=*/0, memento.downTime));
}
}
for (const MotionMemento& memento : mMotionMementos) {
if (shouldCancelMotion(memento, options)) {
- const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT
- : AMOTION_EVENT_ACTION_CANCEL;
- events.push_back(
- std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
- memento.deviceId, memento.source,
- memento.displayId, memento.policyFlags, action,
- 0 /*actionButton*/, memento.flags, AMETA_NONE,
- 0 /*buttonState*/, MotionClassification::NONE,
- AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
- memento.yPrecision, memento.xCursorPosition,
- memento.yCursorPosition, memento.downTime,
- memento.pointerCount, memento.pointerProperties,
- memento.pointerCoords));
+ if (options.pointerIds == std::nullopt) {
+ events.push_back(createCancelEntryForMemento(memento, currentTime));
+ } else {
+ std::vector<std::unique_ptr<MotionEntry>> pointerCancelEvents =
+ synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(),
+ currentTime);
+ events.insert(events.end(), std::make_move_iterator(pointerCancelEvents.begin()),
+ std::make_move_iterator(pointerCancelEvents.end()));
+ }
}
}
return events;
@@ -308,7 +441,7 @@ std::vector<std::unique_ptr<EventEntry>> InputState::synthesizePointerDownEvents
nsecs_t currentTime) {
std::vector<std::unique_ptr<EventEntry>> events;
for (MotionMemento& memento : mMotionMementos) {
- if (!(memento.source & AINPUT_SOURCE_CLASS_POINTER)) {
+ if (!isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
continue;
}
@@ -316,24 +449,22 @@ std::vector<std::unique_ptr<EventEntry>> InputState::synthesizePointerDownEvents
continue;
}
- uint32_t pointerCount = 0;
- PointerProperties pointerProperties[MAX_POINTERS];
- PointerCoords pointerCoords[MAX_POINTERS];
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
// We will deliver all pointers the target already knows about
for (uint32_t i = 0; i < static_cast<uint32_t>(memento.firstNewPointerIdx); i++) {
- pointerProperties[i].copyFrom(memento.pointerProperties[i]);
- pointerCoords[i].copyFrom(memento.pointerCoords[i]);
- pointerCount++;
+ pointerProperties.push_back(memento.pointerProperties[i]);
+ pointerCoords.push_back(memento.pointerCoords[i]);
}
// We will send explicit events for all pointers the target doesn't know about
for (uint32_t i = static_cast<uint32_t>(memento.firstNewPointerIdx);
- i < memento.pointerCount; i++) {
+ i < memento.getPointerCount(); i++) {
+ pointerProperties.push_back(memento.pointerProperties[i]);
+ pointerCoords.push_back(memento.pointerCoords[i]);
- pointerProperties[i].copyFrom(memento.pointerProperties[i]);
- pointerCoords[i].copyFrom(memento.pointerCoords[i]);
- pointerCount++;
+ const size_t pointerCount = pointerProperties.size();
// Down only if the first pointer, pointer down otherwise
const int32_t action = (pointerCount <= 1)
@@ -342,15 +473,15 @@ std::vector<std::unique_ptr<EventEntry>> InputState::synthesizePointerDownEvents
| (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
events.push_back(
- std::make_unique<MotionEntry>(mIdGenerator.nextId(), currentTime,
- memento.deviceId, memento.source,
+ std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+ currentTime, memento.deviceId, memento.source,
memento.displayId, memento.policyFlags, action,
- 0 /*actionButton*/, memento.flags, AMETA_NONE,
- 0 /*buttonState*/, MotionClassification::NONE,
+ /*actionButton=*/0, memento.flags, AMETA_NONE,
+ /*buttonState=*/0, MotionClassification::NONE,
AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
memento.yPrecision, memento.xCursorPosition,
memento.yCursorPosition, memento.downTime,
- pointerCount, pointerProperties, pointerCoords));
+ pointerProperties, pointerCoords));
}
memento.firstNewPointerIdx = INVALID_POINTER_INDEX;
@@ -359,6 +490,76 @@ std::vector<std::unique_ptr<EventEntry>> InputState::synthesizePointerDownEvents
return events;
}
+std::vector<std::unique_ptr<MotionEntry>> InputState::synthesizeCancelationEventsForPointers(
+ const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t currentTime) {
+ std::vector<std::unique_ptr<MotionEntry>> events;
+ std::vector<uint32_t> canceledPointerIndices;
+ std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
+ std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
+ for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) {
+ uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
+ pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx];
+ pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx];
+ if (pointerIds.test(pointerId)) {
+ canceledPointerIndices.push_back(pointerIdx);
+ }
+ }
+
+ if (canceledPointerIndices.size() == memento.getPointerCount()) {
+ const int32_t action =
+ memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL;
+ int32_t flags = memento.flags;
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ events.push_back(
+ std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+ currentTime, memento.deviceId, memento.source,
+ memento.displayId, memento.policyFlags, action,
+ /*actionButton=*/0, flags, AMETA_NONE,
+ /*buttonState=*/0, MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+ memento.yPrecision, memento.xCursorPosition,
+ memento.yCursorPosition, memento.downTime,
+ memento.pointerProperties, memento.pointerCoords));
+ } else {
+ // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with
+ // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the
+ // previously canceled pointers from PointerProperties and PointerCoords, and update
+ // pointerCount appropriately. For convenience, sort the canceled pointer indices so that we
+ // can just slide the remaining pointers to the beginning of the array when a pointer is
+ // canceled.
+ std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(),
+ std::greater<uint32_t>());
+
+ uint32_t pointerCount = memento.getPointerCount();
+ for (const uint32_t pointerIdx : canceledPointerIndices) {
+ const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
+ : AMOTION_EVENT_ACTION_POINTER_UP |
+ (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ events.push_back(
+ std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
+ currentTime, memento.deviceId, memento.source,
+ memento.displayId, memento.policyFlags, action,
+ /*actionButton=*/0,
+ memento.flags | AMOTION_EVENT_FLAG_CANCELED,
+ AMETA_NONE, /*buttonState=*/0,
+ MotionClassification::NONE,
+ AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision,
+ memento.yPrecision, memento.xCursorPosition,
+ memento.yCursorPosition, memento.downTime,
+ pointerProperties, pointerCoords));
+
+ // Cleanup pointer information
+ pointerProperties.erase(pointerProperties.begin() + pointerIdx);
+ pointerCoords.erase(pointerCoords.begin() + pointerIdx);
+ pointerCount--;
+ }
+ }
+ return events;
+}
+
void InputState::clear() {
mKeyMementos.clear();
mMotionMementos.clear();
@@ -370,7 +571,7 @@ void InputState::mergePointerStateTo(InputState& other) {
MotionMemento& memento = mMotionMementos[i];
// Since we support split pointers we need to merge touch events
// from the same source + device + screen.
- if (memento.source & AINPUT_SOURCE_CLASS_POINTER) {
+ if (isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
bool merged = false;
for (size_t j = 0; j < other.mMotionMementos.size(); j++) {
MotionMemento& otherMemento = other.mMotionMementos[j];
@@ -390,22 +591,20 @@ void InputState::mergePointerStateTo(InputState& other) {
}
}
-int32_t InputState::getFallbackKey(int32_t originalKeyCode) {
- ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
- return index >= 0 ? mFallbackKeys.valueAt(index) : -1;
+std::optional<int32_t> InputState::getFallbackKey(int32_t originalKeyCode) {
+ auto it = mFallbackKeys.find(originalKeyCode);
+ if (it == mFallbackKeys.end()) {
+ return {};
+ }
+ return it->second;
}
void InputState::setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode) {
- ssize_t index = mFallbackKeys.indexOfKey(originalKeyCode);
- if (index >= 0) {
- mFallbackKeys.replaceValueAt(index, fallbackKeyCode);
- } else {
- mFallbackKeys.add(originalKeyCode, fallbackKeyCode);
- }
+ mFallbackKeys.insert_or_assign(originalKeyCode, fallbackKeyCode);
}
void InputState::removeFallbackKey(int32_t originalKeyCode) {
- mFallbackKeys.removeItem(originalKeyCode);
+ mFallbackKeys.erase(originalKeyCode);
}
bool InputState::shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options) {
@@ -422,10 +621,10 @@ bool InputState::shouldCancelKey(const KeyMemento& memento, const CancelationOpt
}
switch (options.mode) {
- case CancelationOptions::CANCEL_ALL_EVENTS:
- case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ case CancelationOptions::Mode::CANCEL_ALL_EVENTS:
+ case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
return true;
- case CancelationOptions::CANCEL_FALLBACK_EVENTS:
+ case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS:
return memento.flags & AKEY_EVENT_FLAG_FALLBACK;
default:
return false;
@@ -443,15 +642,25 @@ bool InputState::shouldCancelMotion(const MotionMemento& memento,
}
switch (options.mode) {
- case CancelationOptions::CANCEL_ALL_EVENTS:
+ case CancelationOptions::Mode::CANCEL_ALL_EVENTS:
return true;
- case CancelationOptions::CANCEL_POINTER_EVENTS:
+ case CancelationOptions::Mode::CANCEL_POINTER_EVENTS:
return memento.source & AINPUT_SOURCE_CLASS_POINTER;
- case CancelationOptions::CANCEL_NON_POINTER_EVENTS:
+ case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS:
return !(memento.source & AINPUT_SOURCE_CLASS_POINTER);
default:
return false;
}
}
+std::ostream& operator<<(std::ostream& out, const InputState& state) {
+ if (!state.mMotionMementos.empty()) {
+ out << "mMotionMementos: ";
+ for (const InputState::MotionMemento& memento : state.mMotionMementos) {
+ out << "{deviceId= " << memento.deviceId << ", hovering=" << memento.hovering << "}, ";
+ }
+ }
+ return out;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputState.h b/services/inputflinger/dispatcher/InputState.h
index 74ae21f76a..b0e4209882 100644
--- a/services/inputflinger/dispatcher/InputState.h
+++ b/services/inputflinger/dispatcher/InputState.h
@@ -14,15 +14,16 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H
+#pragma once
#include "CancelationOptions.h"
#include "Entry.h"
#include <utils/Timers.h>
+#include <bitset>
-namespace android::inputdispatcher {
+namespace android {
+namespace inputdispatcher {
static constexpr int32_t INVALID_POINTER_INDEX = -1;
@@ -33,22 +34,22 @@ public:
explicit InputState(const IdGenerator& idGenerator);
~InputState();
- // Returns true if there is no state to be canceled.
- bool isNeutral() const;
-
// Returns true if the specified source is known to have received a hover enter
// motion event.
- bool isHovering(int32_t deviceId, uint32_t source, int32_t displayId) const;
+ bool isHovering(DeviceId deviceId, uint32_t source, int32_t displayId) const;
// Records tracking information for a key event that has just been published.
// Returns true if the event should be delivered, false if it is inconsistent
// and should be skipped.
- bool trackKey(const KeyEntry& entry, int32_t action, int32_t flags);
+ bool trackKey(const KeyEntry& entry, int32_t flags);
// Records tracking information for a motion event that has just been published.
// Returns true if the event should be delivered, false if it is inconsistent
// and should be skipped.
- bool trackMotion(const MotionEntry& entry, int32_t action, int32_t flags);
+ bool trackMotion(const MotionEntry& entry, int32_t flags);
+
+ // Create cancel events for the previous stream if the current motionEntry requires it.
+ std::unique_ptr<EventEntry> cancelConflictingInputStream(const MotionEntry& motionEntry);
// Synthesizes cancelation events for the current state and resets the tracked state.
std::vector<std::unique_ptr<EventEntry>> synthesizeCancelationEvents(
@@ -64,9 +65,9 @@ public:
void mergePointerStateTo(InputState& other);
// Gets the fallback key associated with a keycode.
- // Returns -1 if none.
+ // Returns std::nullopt if none.
// Returns AKEYCODE_UNKNOWN if we are only dispatching the unhandled key to the policy.
- int32_t getFallbackKey(int32_t originalKeyCode);
+ std::optional<int32_t> getFallbackKey(int32_t originalKeyCode);
// Sets the fallback key for a particular keycode.
void setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode);
@@ -74,11 +75,11 @@ public:
// Removes the fallback key for a particular keycode.
void removeFallbackKey(int32_t originalKeyCode);
- inline const KeyedVector<int32_t, int32_t>& getFallbackKeys() const { return mFallbackKeys; }
+ inline const std::map<int32_t, int32_t>& getFallbackKeys() const { return mFallbackKeys; }
private:
struct KeyMemento {
- int32_t deviceId;
+ DeviceId deviceId;
uint32_t source;
int32_t displayId;
int32_t keyCode;
@@ -90,7 +91,7 @@ private:
};
struct MotionMemento {
- int32_t deviceId;
+ DeviceId deviceId;
uint32_t source;
int32_t displayId;
int32_t flags;
@@ -99,9 +100,8 @@ private:
float xCursorPosition;
float yCursorPosition;
nsecs_t downTime;
- uint32_t pointerCount;
- PointerProperties pointerProperties[MAX_POINTERS];
- PointerCoords pointerCoords[MAX_POINTERS];
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
// Track for which pointers the target doesn't know about.
int32_t firstNewPointerIdx = INVALID_POINTER_INDEX;
bool hovering;
@@ -109,13 +109,14 @@ private:
void setPointers(const MotionEntry& entry);
void mergePointerStateTo(MotionMemento& other) const;
+ size_t getPointerCount() const;
};
const IdGenerator& mIdGenerator; // InputDispatcher owns it so we won't have dangling reference.
std::vector<KeyMemento> mKeyMementos;
std::vector<MotionMemento> mMotionMementos;
- KeyedVector<int32_t, int32_t> mFallbackKeys;
+ std::map</*originalKeyCode*/int32_t, /*fallbackKeyCode*/int32_t> mFallbackKeys;
ssize_t findKeyMemento(const KeyEntry& entry) const;
ssize_t findMotionMemento(const MotionEntry& entry, bool hovering) const;
@@ -125,8 +126,18 @@ private:
static bool shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options);
static bool shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options);
+ bool shouldCancelPreviousStream(const MotionEntry& motionEntry) const;
+ std::unique_ptr<MotionEntry> createCancelEntryForMemento(const MotionMemento& memento,
+ nsecs_t eventTime) const;
+
+ // Synthesizes pointer cancel events for a particular set of pointers.
+ std::vector<std::unique_ptr<MotionEntry>> synthesizeCancelationEventsForPointers(
+ const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ nsecs_t currentTime);
+ friend std::ostream& operator<<(std::ostream& out, const InputState& state);
};
-} // namespace android::inputdispatcher
+std::ostream& operator<<(std::ostream& out, const InputState& state);
-#endif // _UI_INPUT_INPUTDISPATCHER_INPUTSTATE_H
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/dispatcher/InputTarget.cpp b/services/inputflinger/dispatcher/InputTarget.cpp
index 2df97d9a12..c02c5d67a2 100644
--- a/services/inputflinger/dispatcher/InputTarget.cpp
+++ b/services/inputflinger/dispatcher/InputTarget.cpp
@@ -16,7 +16,9 @@
#include "InputTarget.h"
+#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
#include <inttypes.h>
#include <string>
@@ -24,49 +26,37 @@ using android::base::StringPrintf;
namespace android::inputdispatcher {
-std::string dispatchModeToString(int32_t dispatchMode) {
- switch (dispatchMode) {
- case InputTarget::FLAG_DISPATCH_AS_IS:
- return "DISPATCH_AS_IS";
- case InputTarget::FLAG_DISPATCH_AS_OUTSIDE:
- return "DISPATCH_AS_OUTSIDE";
- case InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER:
- return "DISPATCH_AS_HOVER_ENTER";
- case InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT:
- return "DISPATCH_AS_HOVER_EXIT";
- case InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT:
- return "DISPATCH_AS_SLIPPERY_EXIT";
- case InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER:
- return "DISPATCH_AS_SLIPPERY_ENTER";
- }
- return StringPrintf("%" PRId32, dispatchMode);
-}
-
-void InputTarget::addPointers(BitSet32 newPointerIds, const ui::Transform& transform) {
+void InputTarget::addPointers(std::bitset<MAX_POINTER_ID + 1> newPointerIds,
+ const ui::Transform& transform) {
// The pointerIds can be empty, but still a valid InputTarget. This can happen when there is no
// valid pointer property from the input event.
- if (newPointerIds.isEmpty()) {
+ if (newPointerIds.none()) {
setDefaultPointerTransform(transform);
return;
}
// Ensure that the new set of pointers doesn't overlap with the current set of pointers.
- ALOG_ASSERT((pointerIds & newPointerIds) == 0);
+ if ((pointerIds & newPointerIds).any()) {
+ LOG(FATAL) << __func__ << " - overlap with incoming pointers "
+ << bitsetToString(newPointerIds) << " in " << *this;
+ }
pointerIds |= newPointerIds;
- while (!newPointerIds.isEmpty()) {
- int32_t pointerId = newPointerIds.clearFirstMarkedBit();
- pointerTransforms[pointerId] = transform;
+ for (size_t i = 0; i < newPointerIds.size(); i++) {
+ if (!newPointerIds.test(i)) {
+ continue;
+ }
+ pointerTransforms[i] = transform;
}
}
void InputTarget::setDefaultPointerTransform(const ui::Transform& transform) {
- pointerIds.clear();
+ pointerIds.reset();
pointerTransforms[0] = transform;
}
bool InputTarget::useDefaultPointerTransform() const {
- return pointerIds.isEmpty();
+ return pointerIds.none();
}
const ui::Transform& InputTarget::getDefaultPointerTransform() const {
@@ -81,8 +71,8 @@ std::string InputTarget::getPointerInfoString() const {
return out;
}
- for (uint32_t i = pointerIds.firstMarkedBit(); i <= pointerIds.lastMarkedBit(); i++) {
- if (!pointerIds.hasBit(i)) {
+ for (uint32_t i = 0; i < pointerIds.size(); i++) {
+ if (!pointerIds.test(i)) {
continue;
}
@@ -91,4 +81,25 @@ std::string InputTarget::getPointerInfoString() const {
}
return out;
}
+
+std::ostream& operator<<(std::ostream& out, const InputTarget& target) {
+ out << "{inputChannel=";
+ if (target.inputChannel != nullptr) {
+ out << target.inputChannel->getName();
+ } else {
+ out << "<null>";
+ }
+ out << ", windowHandle=";
+ if (target.windowHandle != nullptr) {
+ out << target.windowHandle->getName();
+ } else {
+ out << "<null>";
+ }
+ out << ", dispatchMode=" << ftl::enum_string(target.dispatchMode).c_str();
+ out << ", targetFlags=" << target.flags.string();
+ out << ", pointers=" << target.getPointerInfoString();
+ out << "}";
+ return out;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputTarget.h b/services/inputflinger/dispatcher/InputTarget.h
index 0725389ed6..aef866baaf 100644
--- a/services/inputflinger/dispatcher/InputTarget.h
+++ b/services/inputflinger/dispatcher/InputTarget.h
@@ -14,13 +14,15 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTTARGET_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTTARGET_H
+#pragma once
+#include <ftl/flags.h>
+#include <gui/WindowInfo.h>
#include <gui/constants.h>
#include <input/InputTransport.h>
#include <ui/Transform.h>
#include <utils/BitSet.h>
+#include <bitset>
namespace android::inputdispatcher {
@@ -31,70 +33,66 @@ namespace android::inputdispatcher {
* window area.
*/
struct InputTarget {
- enum {
+ enum class Flags : uint32_t {
/* This flag indicates that the event is being delivered to a foreground application. */
- FLAG_FOREGROUND = 1 << 0,
+ FOREGROUND = 1 << 0,
/* This flag indicates that the MotionEvent falls within the area of the target
* obscured by another visible window above it. The motion event should be
* delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED. */
- FLAG_WINDOW_IS_OBSCURED = 1 << 1,
+ WINDOW_IS_OBSCURED = 1 << 1,
/* This flag indicates that a motion event is being split across multiple windows. */
- FLAG_SPLIT = 1 << 2,
+ SPLIT = 1 << 2,
/* This flag indicates that the pointer coordinates dispatched to the application
* will be zeroed out to avoid revealing information to an application. This is
* used in conjunction with FLAG_DISPATCH_AS_OUTSIDE to prevent apps not sharing
* the same UID from watching all touches. */
- FLAG_ZERO_COORDS = 1 << 3,
+ ZERO_COORDS = 1 << 3,
+ /* This flag indicates that the target of a MotionEvent is partly or wholly
+ * obscured by another visible window above it. The motion event should be
+ * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
+ WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
+ };
+
+ enum class DispatchMode {
/* This flag indicates that the event should be sent as is.
* Should always be set unless the event is to be transmuted. */
- FLAG_DISPATCH_AS_IS = 1 << 8,
-
+ AS_IS,
/* This flag indicates that a MotionEvent with AMOTION_EVENT_ACTION_DOWN falls outside
* of the area of this target and so should instead be delivered as an
* AMOTION_EVENT_ACTION_OUTSIDE to this target. */
- FLAG_DISPATCH_AS_OUTSIDE = 1 << 9,
-
+ OUTSIDE,
/* This flag indicates that a hover sequence is starting in the given window.
* The event is transmuted into ACTION_HOVER_ENTER. */
- FLAG_DISPATCH_AS_HOVER_ENTER = 1 << 10,
-
+ HOVER_ENTER,
/* This flag indicates that a hover event happened outside of a window which handled
* previous hover events, signifying the end of the current hover sequence for that
* window.
* The event is transmuted into ACTION_HOVER_ENTER. */
- FLAG_DISPATCH_AS_HOVER_EXIT = 1 << 11,
-
+ HOVER_EXIT,
/* This flag indicates that the event should be canceled.
* It is used to transmute ACTION_MOVE into ACTION_CANCEL when a touch slips
* outside of a window. */
- FLAG_DISPATCH_AS_SLIPPERY_EXIT = 1 << 12,
-
+ SLIPPERY_EXIT,
/* This flag indicates that the event should be dispatched as an initial down.
* It is used to transmute ACTION_MOVE into ACTION_DOWN when a touch slips
* into a new window. */
- FLAG_DISPATCH_AS_SLIPPERY_ENTER = 1 << 13,
-
- /* Mask for all dispatch modes. */
- FLAG_DISPATCH_MASK = FLAG_DISPATCH_AS_IS | FLAG_DISPATCH_AS_OUTSIDE |
- FLAG_DISPATCH_AS_HOVER_ENTER | FLAG_DISPATCH_AS_HOVER_EXIT |
- FLAG_DISPATCH_AS_SLIPPERY_EXIT | FLAG_DISPATCH_AS_SLIPPERY_ENTER,
-
- /* This flag indicates that the target of a MotionEvent is partly or wholly
- * obscured by another visible window above it. The motion event should be
- * delivered with flag AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED. */
- FLAG_WINDOW_IS_PARTIALLY_OBSCURED = 1 << 14,
+ SLIPPERY_ENTER,
+ ftl_last = SLIPPERY_ENTER,
};
// The input channel to be targeted.
std::shared_ptr<InputChannel> inputChannel;
// Flags for the input target.
- int32_t flags = 0;
+ ftl::Flags<Flags> flags;
+
+ // The dispatch mode that should be used for this target.
+ DispatchMode dispatchMode = DispatchMode::AS_IS;
// Scaling factor to apply to MotionEvent as it is delivered.
// (ignored for KeyEvents)
@@ -105,12 +103,19 @@ struct InputTarget {
// The subset of pointer ids to include in motion events dispatched to this input target
// if FLAG_SPLIT is set.
- BitSet32 pointerIds;
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ // Event time for the first motion event (ACTION_DOWN) dispatched to this input target if
+ // FLAG_SPLIT is set.
+ std::optional<nsecs_t> firstDownTimeInTarget;
// The data is stored by the pointerId. Use the bit position of pointerIds to look up
// Transform per pointerId.
ui::Transform pointerTransforms[MAX_POINTERS];
- void addPointers(BitSet32 pointerIds, const ui::Transform& transform);
+ // The window that this input target is being dispatched to. It is possible for this to be
+ // null for cases like global monitors.
+ sp<gui::WindowInfoHandle> windowHandle;
+
+ void addPointers(std::bitset<MAX_POINTER_ID + 1> pointerIds, const ui::Transform& transform);
void setDefaultPointerTransform(const ui::Transform& transform);
/**
@@ -130,8 +135,6 @@ struct InputTarget {
std::string getPointerInfoString() const;
};
-std::string dispatchModeToString(int32_t dispatchMode);
+std::ostream& operator<<(std::ostream& out, const InputTarget& target);
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_INPUTTARGET_H
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
index a5bfc25e78..e09d97a13b 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.cpp
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -126,6 +126,7 @@ void LatencyAggregator::processTimeline(const InputEventTimeline& timeline) {
}
void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
+ std::scoped_lock lock(mLock);
// Before we do any processing, check that we have not yet exceeded MAX_SIZE
if (mNumSketchEventsProcessed >= MAX_EVENTS_FOR_STATISTICS) {
return;
@@ -167,6 +168,7 @@ void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
}
AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullData(AStatsEventList* data) {
+ std::scoped_lock lock(mLock);
std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedDownData;
std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedMoveData;
for (size_t i = 0; i < SketchIndex::SIZE; i++) {
@@ -256,7 +258,8 @@ void LatencyAggregator::processSlowEvent(const InputEventTimeline& timeline) {
}
}
-std::string LatencyAggregator::dump(const char* prefix) {
+std::string LatencyAggregator::dump(const char* prefix) const {
+ std::scoped_lock lock(mLock);
std::string sketchDump = StringPrintf("%s Sketches:\n", prefix);
for (size_t i = 0; i < SketchIndex::SIZE; i++) {
const int64_t numDown = mDownSketches[i]->num_values();
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
index ed5731f8d9..d6d168602a 100644
--- a/services/inputflinger/dispatcher/LatencyAggregator.h
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
-#define _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
+#pragma once
+#include <android-base/thread_annotations.h>
#include <kll.h>
#include <statslog.h>
#include <utils/Timers.h>
@@ -57,15 +57,18 @@ public:
*/
void processTimeline(const InputEventTimeline& timeline) override;
- std::string dump(const char* prefix);
+ std::string dump(const char* prefix) const;
~LatencyAggregator();
private:
+ // Binder call -- called on a binder thread. This is different from the thread where the rest of
+ // the public API is called.
static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atom_tag,
AStatsEventList* data,
void* cookie);
AStatsManager_PullAtomCallbackReturn pullData(AStatsEventList* data);
+
// ---------- Slow event handling ----------
void processSlowEvent(const InputEventTimeline& timeline);
nsecs_t mLastSlowEventTime = 0;
@@ -75,16 +78,17 @@ private:
size_t mNumEventsSinceLastSlowEventReport = 0;
// ---------- Statistics handling ----------
+ // Statistics is pulled rather than pushed. It's pulled on a binder thread, and therefore will
+ // be accessed by two different threads. The lock is needed to protect the pulled data.
+ mutable std::mutex mLock;
void processStatistics(const InputEventTimeline& timeline);
// Sketches
std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
- mDownSketches;
+ mDownSketches GUARDED_BY(mLock);
std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
- mMoveSketches;
+ mMoveSketches GUARDED_BY(mLock);
// How many events have been processed so far
- size_t mNumSketchEventsProcessed = 0;
+ size_t mNumSketchEventsProcessed GUARDED_BY(mLock) = 0;
};
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
index 52f189c5c5..698bd9ff08 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.cpp
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -16,6 +16,7 @@
#define LOG_TAG "LatencyTracker"
#include "LatencyTracker.h"
+#include "../InputDeviceMetricsSource.h"
#include <inttypes.h>
@@ -23,6 +24,7 @@
#include <android-base/stringprintf.h>
#include <android/os/IInputConstants.h>
#include <input/Input.h>
+#include <input/InputDevice.h>
#include <log/log.h>
using android::base::HwTimeoutMultiplier;
@@ -66,7 +68,8 @@ LatencyTracker::LatencyTracker(InputEventTimelineProcessor* processor)
}
void LatencyTracker::trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime,
- nsecs_t readTime) {
+ nsecs_t readTime, DeviceId deviceId,
+ const std::set<InputDeviceUsageSource>& sources) {
reportAndPruneMatureRecords(eventTime);
const auto it = mTimelines.find(inputEventId);
if (it != mTimelines.end()) {
@@ -78,7 +81,29 @@ void LatencyTracker::trackListener(int32_t inputEventId, bool isDown, nsecs_t ev
eraseByValue(mEventTimes, inputEventId);
return;
}
- mTimelines.emplace(inputEventId, InputEventTimeline(isDown, eventTime, readTime));
+
+ // Create an InputEventTimeline for the device ID. The vendorId and productId
+ // can be obtained from the InputDeviceIdentifier of the particular device.
+ const InputDeviceIdentifier* identifier = nullptr;
+ for (auto& inputDevice : mInputDevices) {
+ if (deviceId == inputDevice.getId()) {
+ identifier = &inputDevice.getIdentifier();
+ break;
+ }
+ }
+
+ // If no matching ids can be found for the device from among the input devices connected,
+ // the call to trackListener will be dropped.
+ // Note: there generally isn't expected to be a situation where we can't find an InputDeviceInfo
+ // but a possibility of it is handled in case of race conditions
+ if (identifier == nullptr) {
+ ALOGE("Could not find input device identifier. Dropping call to LatencyTracker.");
+ return;
+ }
+
+ mTimelines.emplace(inputEventId,
+ InputEventTimeline(isDown, eventTime, readTime, identifier->vendor,
+ identifier->product, sources));
mEventTimes.emplace(eventTime, inputEventId);
}
@@ -148,7 +173,7 @@ void LatencyTracker::trackGraphicsLatency(
void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
while (!mEventTimes.empty()) {
const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
- if (isMatureEvent(oldestEventTime, newEventTime /*now*/)) {
+ if (isMatureEvent(oldestEventTime, /*now=*/newEventTime)) {
// Report and drop this event
const auto it = mTimelines.find(oldestInputEventId);
LOG_ALWAYS_FATAL_IF(it == mTimelines.end(),
@@ -165,10 +190,14 @@ void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
}
}
-std::string LatencyTracker::dump(const char* prefix) {
+std::string LatencyTracker::dump(const char* prefix) const {
return StringPrintf("%sLatencyTracker:\n", prefix) +
StringPrintf("%s mTimelines.size() = %zu\n", prefix, mTimelines.size()) +
StringPrintf("%s mEventTimes.size() = %zu\n", prefix, mEventTimes.size());
}
+void LatencyTracker::setInputDevices(const std::vector<InputDeviceInfo>& inputDevices) {
+ mInputDevices = inputDevices;
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
index 4b0c618590..890d61d431 100644
--- a/services/inputflinger/dispatcher/LatencyTracker.h
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -14,8 +14,9 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
-#define _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
+#pragma once
+
+#include "../InputDeviceMetricsSource.h"
#include <map>
#include <unordered_map>
@@ -24,6 +25,7 @@
#include <input/Input.h>
#include "InputEventTimeline.h"
+#include "NotifyArgs.h"
namespace android::inputdispatcher {
@@ -50,13 +52,15 @@ public:
* duplicate events that happen to have the same eventTime and inputEventId. Therefore, we
* must drop all duplicate data.
*/
- void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime);
+ void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime,
+ DeviceId deviceId, const std::set<InputDeviceUsageSource>& sources);
void trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
- std::string dump(const char* prefix);
+ std::string dump(const char* prefix) const;
+ void setInputDevices(const std::vector<InputDeviceInfo>& inputDevices);
private:
/**
@@ -77,9 +81,8 @@ private:
std::multimap<nsecs_t /*eventTime*/, int32_t /*inputEventId*/> mEventTimes;
InputEventTimelineProcessor* mTimelineProcessor;
+ std::vector<InputDeviceInfo> mInputDevices;
void reportAndPruneMatureRecords(nsecs_t newEventTime);
};
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
diff --git a/services/inputflinger/dispatcher/Monitor.cpp b/services/inputflinger/dispatcher/Monitor.cpp
index 43a82d5cca..204791eb03 100644
--- a/services/inputflinger/dispatcher/Monitor.cpp
+++ b/services/inputflinger/dispatcher/Monitor.cpp
@@ -19,7 +19,7 @@
namespace android::inputdispatcher {
// --- Monitor ---
-Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid)
+Monitor::Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid)
: inputChannel(inputChannel), pid(pid) {}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/Monitor.h b/services/inputflinger/dispatcher/Monitor.h
index 365d5bee4c..1b1eb3a593 100644
--- a/services/inputflinger/dispatcher/Monitor.h
+++ b/services/inputflinger/dispatcher/Monitor.h
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_MONITOR_H
-#define _UI_INPUT_INPUTDISPATCHER_MONITOR_H
+#pragma once
+#include <gui/PidUid.h>
#include <input/InputTransport.h>
namespace android::inputdispatcher {
@@ -24,11 +24,9 @@ namespace android::inputdispatcher {
struct Monitor {
std::shared_ptr<InputChannel> inputChannel; // never null
- int32_t pid;
+ gui::Pid pid;
- explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, int32_t pid);
+ explicit Monitor(const std::shared_ptr<InputChannel>& inputChannel, gui::Pid pid);
};
} // namespace android::inputdispatcher
-
-#endif // _UI_INPUT_INPUTDISPATCHER_MONITOR_H
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 61e78ccbe4..e8d8c18e4e 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -14,12 +14,15 @@
* limitations under the License.
*/
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
#include <gui/WindowInfo.h>
#include "InputTarget.h"
-
#include "TouchState.h"
+using namespace android::ftl::flag_operators;
+using android::base::StringPrintf;
using android::gui::WindowInfo;
using android::gui::WindowInfoHandle;
@@ -29,30 +32,95 @@ void TouchState::reset() {
*this = TouchState();
}
-void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle, int32_t targetFlags,
- BitSet32 pointerIds) {
- if (targetFlags & InputTarget::FLAG_SPLIT) {
- split = true;
+bool TouchState::hasTouchingPointers(DeviceId deviceId) const {
+ return std::any_of(windows.begin(), windows.end(), [&](const TouchedWindow& window) {
+ return window.hasTouchingPointers(deviceId);
+ });
+}
+
+void TouchState::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ touchedWindow.removeTouchingPointer(deviceId, pointerId);
}
+ clearWindowsWithoutPointers();
+}
- for (size_t i = 0; i < windows.size(); i++) {
- TouchedWindow& touchedWindow = windows[i];
+void TouchState::removeTouchingPointerFromWindow(
+ DeviceId deviceId, int32_t pointerId,
+ const sp<android::gui::WindowInfoHandle>& windowHandle) {
+ for (TouchedWindow& touchedWindow : windows) {
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.removeTouchingPointer(deviceId, pointerId);
+ clearWindowsWithoutPointers();
+ return;
+ }
+ }
+}
+
+void TouchState::clearHoveringPointers(DeviceId deviceId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ touchedWindow.removeAllHoveringPointersForDevice(deviceId);
+ }
+ clearWindowsWithoutPointers();
+}
+
+void TouchState::clearWindowsWithoutPointers() {
+ std::erase_if(windows, [](const TouchedWindow& w) {
+ return !w.hasTouchingPointers() && !w.hasHoveringPointers();
+ });
+}
+
+void TouchState::addOrUpdateWindow(const sp<WindowInfoHandle>& windowHandle,
+ InputTarget::DispatchMode dispatchMode,
+ ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
+ std::optional<nsecs_t> firstDownTimeInTarget) {
+ if (touchingPointerIds.none()) {
+ LOG(FATAL) << __func__ << "No pointers specified for " << windowHandle->getName();
+ return;
+ }
+ for (TouchedWindow& touchedWindow : windows) {
+ // We do not compare windows by token here because two windows that share the same token
+ // may have a different transform. They will be combined later when we create InputTargets.
+ // At that point, per-pointer window transform will be considered.
+ // An alternative design choice here would have been to compare here by token, but to
+ // store per-pointer transform.
if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.dispatchMode = dispatchMode;
touchedWindow.targetFlags |= targetFlags;
- if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
- touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS;
+ // For cases like hover enter/exit or DISPATCH_AS_OUTSIDE a touch window might not have
+ // downTime set initially. Need to update existing window when a pointer is down for the
+ // window.
+ touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+ if (firstDownTimeInTarget) {
+ touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
}
- touchedWindow.pointerIds.value |= pointerIds.value;
return;
}
}
+ TouchedWindow touchedWindow;
+ touchedWindow.windowHandle = windowHandle;
+ touchedWindow.dispatchMode = dispatchMode;
+ touchedWindow.targetFlags = targetFlags;
+ touchedWindow.addTouchingPointers(deviceId, touchingPointerIds);
+ if (firstDownTimeInTarget) {
+ touchedWindow.trySetDownTimeInTarget(deviceId, *firstDownTimeInTarget);
+ }
+ windows.push_back(touchedWindow);
+}
- if (preventNewTargets) return; // Don't add new TouchedWindows.
+void TouchState::addHoveringPointerToWindow(const sp<WindowInfoHandle>& windowHandle,
+ DeviceId deviceId, int32_t hoveringPointerId) {
+ for (TouchedWindow& touchedWindow : windows) {
+ if (touchedWindow.windowHandle == windowHandle) {
+ touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
+ return;
+ }
+ }
TouchedWindow touchedWindow;
touchedWindow.windowHandle = windowHandle;
- touchedWindow.targetFlags = targetFlags;
- touchedWindow.pointerIds = pointerIds;
+ touchedWindow.addHoveringPointer(deviceId, hoveringPointerId);
windows.push_back(touchedWindow);
}
@@ -65,29 +133,55 @@ void TouchState::removeWindowByToken(const sp<IBinder>& token) {
}
}
-void TouchState::filterNonAsIsTouchWindows() {
- for (size_t i = 0; i < windows.size();) {
- TouchedWindow& window = windows[i];
- if (window.targetFlags &
- (InputTarget::FLAG_DISPATCH_AS_IS | InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER)) {
- window.targetFlags &= ~InputTarget::FLAG_DISPATCH_MASK;
- window.targetFlags |= InputTarget::FLAG_DISPATCH_AS_IS;
- i += 1;
- } else {
- windows.erase(windows.begin() + i);
+void TouchState::cancelPointersForWindowsExcept(DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ const sp<IBinder>& token) {
+ std::for_each(windows.begin(), windows.end(), [&](TouchedWindow& w) {
+ if (w.windowHandle->getToken() != token) {
+ w.removeTouchingPointers(deviceId, pointerIds);
}
- }
+ });
+ clearWindowsWithoutPointers();
}
-void TouchState::filterWindowsExcept(const sp<IBinder>& token) {
- std::erase_if(windows,
- [&token](const TouchedWindow& w) { return w.windowHandle->getToken() != token; });
+/**
+ * For any pointer that's being pilfered, remove it from all of the other windows that currently
+ * aren't pilfering it. For example, if we determined that pointer 1 is going to both window A and
+ * window B, but window A is currently pilfering pointer 1, then pointer 1 should not go to window
+ * B.
+ */
+void TouchState::cancelPointersForNonPilferingWindows() {
+ // First, find all pointers that are being pilfered, across all windows
+ std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> allPilferedPointerIdsByDevice;
+ for (const TouchedWindow& w : windows) {
+ for (const auto& [deviceId, pilferedPointerIds] : w.getPilferingPointers()) {
+ allPilferedPointerIdsByDevice[deviceId] |= pilferedPointerIds;
+ }
+ };
+
+ // Optimization: most of the time, pilfering does not occur
+ if (allPilferedPointerIdsByDevice.empty()) return;
+
+ // Now, remove all pointers from every window that's being pilfered by other windows.
+ // For example, if window A is pilfering pointer 1 (only), and window B is pilfering pointer 2
+ // (only), the remove pointer 2 from window A and pointer 1 from window B. Usually, the set of
+ // pilfered pointers will be disjoint across all windows, but there's no reason to cause that
+ // limitation here.
+ for (const auto& [deviceId, allPilferedPointerIds] : allPilferedPointerIdsByDevice) {
+ std::for_each(windows.begin(), windows.end(), [&](TouchedWindow& w) {
+ std::bitset<MAX_POINTER_ID + 1> pilferedByOtherWindows =
+ w.getPilferingPointers(deviceId) ^ allPilferedPointerIds;
+ // Remove all pointers pilfered by other windows
+ w.removeTouchingPointers(deviceId, pilferedByOtherWindows);
+ });
+ }
+ clearWindowsWithoutPointers();
}
sp<WindowInfoHandle> TouchState::getFirstForegroundWindowHandle() const {
for (size_t i = 0; i < windows.size(); i++) {
const TouchedWindow& window = windows[i];
- if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
return window.windowHandle;
}
}
@@ -98,7 +192,7 @@ bool TouchState::isSlippery() const {
// Must have exactly one foreground window.
bool haveSlipperyForegroundWindow = false;
for (const TouchedWindow& window : windows) {
- if (window.targetFlags & InputTarget::FLAG_FOREGROUND) {
+ if (window.targetFlags.test(InputTarget::Flags::FOREGROUND)) {
if (haveSlipperyForegroundWindow ||
!window.windowHandle->getInfo()->inputConfig.test(
WindowInfo::InputConfig::SLIPPERY)) {
@@ -121,14 +215,69 @@ sp<WindowInfoHandle> TouchState::getWallpaperWindow() const {
return nullptr;
}
-sp<WindowInfoHandle> TouchState::getWindow(const sp<IBinder>& token) const {
- for (const TouchedWindow& touchedWindow : windows) {
- const auto& windowHandle = touchedWindow.windowHandle;
- if (windowHandle->getToken() == token) {
- return windowHandle;
+const TouchedWindow& TouchState::getTouchedWindow(const sp<WindowInfoHandle>& windowHandle) const {
+ auto it = std::find_if(windows.begin(), windows.end(),
+ [&](const TouchedWindow& w) { return w.windowHandle == windowHandle; });
+ LOG_ALWAYS_FATAL_IF(it == windows.end(), "Could not find %s", windowHandle->getName().c_str());
+ return *it;
+}
+
+bool TouchState::isDown(DeviceId deviceId) const {
+ return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+ return window.hasTouchingPointers(deviceId);
+ });
+}
+
+bool TouchState::hasHoveringPointers(DeviceId deviceId) const {
+ return std::any_of(windows.begin(), windows.end(), [&deviceId](const TouchedWindow& window) {
+ return window.hasHoveringPointers(deviceId);
+ });
+}
+
+std::set<sp<WindowInfoHandle>> TouchState::getWindowsWithHoveringPointer(DeviceId deviceId,
+ int32_t pointerId) const {
+ std::set<sp<WindowInfoHandle>> out;
+ for (const TouchedWindow& window : windows) {
+ if (window.hasHoveringPointer(deviceId, pointerId)) {
+ out.insert(window.windowHandle);
+ }
+ }
+ return out;
+}
+
+void TouchState::removeHoveringPointer(int32_t hoveringDeviceId, int32_t hoveringPointerId) {
+ for (TouchedWindow& window : windows) {
+ window.removeHoveringPointer(hoveringDeviceId, hoveringPointerId);
+ }
+ clearWindowsWithoutPointers();
+}
+
+void TouchState::removeAllPointersForDevice(DeviceId deviceId) {
+ for (TouchedWindow& window : windows) {
+ window.removeAllHoveringPointersForDevice(deviceId);
+ window.removeAllTouchingPointersForDevice(deviceId);
+ }
+
+ clearWindowsWithoutPointers();
+}
+
+std::string TouchState::dump() const {
+ std::string out;
+ if (!windows.empty()) {
+ out += " Windows:\n";
+ for (size_t i = 0; i < windows.size(); i++) {
+ const TouchedWindow& touchedWindow = windows[i];
+ out += StringPrintf(" %zu : ", i) + touchedWindow.dump();
}
+ } else {
+ out += " Windows: <none>\n";
}
- return nullptr;
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const TouchState& state) {
+ out << state.dump();
+ return out;
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index 9efb2808c5..e0a84e8f45 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_TOUCHSTATE_H
-#define _UI_INPUT_INPUTDISPATCHER_TOUCHSTATE_H
+#pragma once
-#include "Monitor.h"
+#include <bitset>
+#include <ostream>
+#include <set>
#include "TouchedWindow.h"
namespace android {
@@ -29,17 +30,6 @@ class WindowInfoHandle;
namespace inputdispatcher {
struct TouchState {
- bool down = false;
- bool split = false;
- bool preventNewTargets = false;
-
- // id of the device that is currently down, others are rejected
- int32_t deviceId = -1;
- // source of the device that is current down, others are rejected
- uint32_t source = 0;
- // id to the display that currently has a touch, others are rejected
- int32_t displayId = ADISPLAY_ID_NONE;
-
std::vector<TouchedWindow> windows;
TouchState() = default;
@@ -47,18 +37,48 @@ struct TouchState {
TouchState& operator=(const TouchState&) = default;
void reset();
+ void clearWindowsWithoutPointers();
+
+ bool hasTouchingPointers(DeviceId deviceId) const;
+ void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
+ void removeTouchingPointerFromWindow(DeviceId deviceId, int32_t pointerId,
+ const sp<android::gui::WindowInfoHandle>& windowHandle);
void addOrUpdateWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
- int32_t targetFlags, BitSet32 pointerIds);
+ InputTarget::DispatchMode dispatchMode,
+ ftl::Flags<InputTarget::Flags> targetFlags, DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> touchingPointerIds,
+ std::optional<nsecs_t> firstDownTimeInTarget = std::nullopt);
+ void addHoveringPointerToWindow(const sp<android::gui::WindowInfoHandle>& windowHandle,
+ DeviceId deviceId, int32_t hoveringPointerId);
+ void removeHoveringPointer(DeviceId deviceId, int32_t hoveringPointerId);
+ void clearHoveringPointers(DeviceId deviceId);
+
+ void removeAllPointersForDevice(DeviceId deviceId);
void removeWindowByToken(const sp<IBinder>& token);
- void filterNonAsIsTouchWindows();
- void filterWindowsExcept(const sp<IBinder>& token);
+
+ // Cancel pointers for current set of windows except the window with particular binder token.
+ void cancelPointersForWindowsExcept(DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds,
+ const sp<IBinder>& token);
+ // Cancel pointers for current set of non-pilfering windows i.e. windows with isPilferingWindow
+ // set to false.
+ void cancelPointersForNonPilferingWindows();
+
sp<android::gui::WindowInfoHandle> getFirstForegroundWindowHandle() const;
bool isSlippery() const;
sp<android::gui::WindowInfoHandle> getWallpaperWindow() const;
- sp<android::gui::WindowInfoHandle> getWindow(const sp<IBinder>&) const;
+ const TouchedWindow& getTouchedWindow(
+ const sp<android::gui::WindowInfoHandle>& windowHandle) const;
+ // Whether any of the windows are currently being touched
+ bool isDown(DeviceId deviceId) const;
+ bool hasHoveringPointers(DeviceId deviceId) const;
+
+ std::set<sp<android::gui::WindowInfoHandle>> getWindowsWithHoveringPointer(
+ DeviceId deviceId, int32_t pointerId) const;
+ std::string dump() const;
};
+std::ostream& operator<<(std::ostream& out, const TouchState& state);
+
} // namespace inputdispatcher
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_TOUCHSTATE_H
diff --git a/services/inputflinger/dispatcher/TouchedWindow.cpp b/services/inputflinger/dispatcher/TouchedWindow.cpp
new file mode 100644
index 0000000000..cd0500c872
--- /dev/null
+++ b/services/inputflinger/dispatcher/TouchedWindow.cpp
@@ -0,0 +1,263 @@
+/*
+ * 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 "TouchedWindow.h"
+
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <input/PrintTools.h>
+
+using android::base::StringPrintf;
+
+namespace android {
+
+namespace inputdispatcher {
+
+bool TouchedWindow::hasHoveringPointers() const {
+ for (const auto& [_, state] : mDeviceStates) {
+ if (state.hoveringPointerIds.any()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TouchedWindow::hasHoveringPointers(DeviceId deviceId) const {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return false;
+ }
+ const DeviceState& state = stateIt->second;
+
+ return state.hoveringPointerIds.any();
+}
+
+void TouchedWindow::clearHoveringPointers(DeviceId deviceId) {
+ auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return;
+ }
+ DeviceState& state = stateIt->second;
+ state.hoveringPointerIds.reset();
+ if (!state.hasPointers()) {
+ mDeviceStates.erase(stateIt);
+ }
+}
+
+bool TouchedWindow::hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return false;
+ }
+ const DeviceState& state = stateIt->second;
+
+ return state.hoveringPointerIds.test(pointerId);
+}
+
+void TouchedWindow::addHoveringPointer(DeviceId deviceId, int32_t pointerId) {
+ mDeviceStates[deviceId].hoveringPointerIds.set(pointerId);
+}
+
+void TouchedWindow::addTouchingPointers(DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointers) {
+ mDeviceStates[deviceId].touchingPointerIds |= pointers;
+}
+
+bool TouchedWindow::hasTouchingPointers() const {
+ for (const auto& [_, state] : mDeviceStates) {
+ if (state.touchingPointerIds.any()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool TouchedWindow::hasTouchingPointers(DeviceId deviceId) const {
+ return getTouchingPointers(deviceId).any();
+}
+
+bool TouchedWindow::hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const {
+ return getTouchingPointers(deviceId).test(pointerId);
+}
+
+std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getTouchingPointers(DeviceId deviceId) const {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return {};
+ }
+ const DeviceState& state = stateIt->second;
+
+ return state.touchingPointerIds;
+}
+
+void TouchedWindow::removeTouchingPointer(DeviceId deviceId, int32_t pointerId) {
+ std::bitset<MAX_POINTER_ID + 1> pointerIds;
+ pointerIds.set(pointerId, true);
+
+ removeTouchingPointers(deviceId, pointerIds);
+}
+
+void TouchedWindow::removeTouchingPointers(DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointers) {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return;
+ }
+ DeviceState& state = stateIt->second;
+
+ state.touchingPointerIds &= ~pointers;
+ state.pilferingPointerIds &= ~pointers;
+
+ if (!state.hasPointers()) {
+ mDeviceStates.erase(stateIt);
+ }
+}
+
+std::set<DeviceId> TouchedWindow::getTouchingDeviceIds() const {
+ std::set<DeviceId> deviceIds;
+ for (const auto& [deviceId, deviceState] : mDeviceStates) {
+ if (deviceState.touchingPointerIds.any()) {
+ deviceIds.insert(deviceId);
+ }
+ }
+ return deviceIds;
+}
+
+bool TouchedWindow::hasPilferingPointers(DeviceId deviceId) const {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return false;
+ }
+ const DeviceState& state = stateIt->second;
+
+ return state.pilferingPointerIds.any();
+}
+
+void TouchedWindow::addPilferingPointers(DeviceId deviceId,
+ std::bitset<MAX_POINTER_ID + 1> pointerIds) {
+ mDeviceStates[deviceId].pilferingPointerIds |= pointerIds;
+}
+
+void TouchedWindow::addPilferingPointer(DeviceId deviceId, int32_t pointerId) {
+ mDeviceStates[deviceId].pilferingPointerIds.set(pointerId);
+}
+
+std::bitset<MAX_POINTER_ID + 1> TouchedWindow::getPilferingPointers(DeviceId deviceId) const {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return {};
+ }
+ const DeviceState& state = stateIt->second;
+
+ return state.pilferingPointerIds;
+}
+
+std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> TouchedWindow::getPilferingPointers() const {
+ std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> out;
+ for (const auto& [deviceId, state] : mDeviceStates) {
+ out.emplace(deviceId, state.pilferingPointerIds);
+ }
+ return out;
+}
+
+std::optional<nsecs_t> TouchedWindow::getDownTimeInTarget(DeviceId deviceId) const {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return {};
+ }
+ const DeviceState& state = stateIt->second;
+ return state.downTimeInTarget;
+}
+
+void TouchedWindow::trySetDownTimeInTarget(DeviceId deviceId, nsecs_t downTime) {
+ auto [stateIt, _] = mDeviceStates.try_emplace(deviceId);
+ DeviceState& state = stateIt->second;
+
+ if (!state.downTimeInTarget) {
+ state.downTimeInTarget = downTime;
+ }
+}
+
+void TouchedWindow::removeAllTouchingPointersForDevice(DeviceId deviceId) {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return;
+ }
+ DeviceState& state = stateIt->second;
+
+ state.touchingPointerIds.reset();
+ state.pilferingPointerIds.reset();
+ state.downTimeInTarget.reset();
+
+ if (!state.hasPointers()) {
+ mDeviceStates.erase(stateIt);
+ }
+}
+
+void TouchedWindow::removeHoveringPointer(DeviceId deviceId, int32_t pointerId) {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return;
+ }
+ DeviceState& state = stateIt->second;
+
+ state.hoveringPointerIds.set(pointerId, false);
+
+ if (!state.hasPointers()) {
+ mDeviceStates.erase(stateIt);
+ }
+}
+
+void TouchedWindow::removeAllHoveringPointersForDevice(DeviceId deviceId) {
+ const auto stateIt = mDeviceStates.find(deviceId);
+ if (stateIt == mDeviceStates.end()) {
+ return;
+ }
+ DeviceState& state = stateIt->second;
+
+ state.hoveringPointerIds.reset();
+
+ if (!state.hasPointers()) {
+ mDeviceStates.erase(stateIt);
+ }
+}
+
+std::string TouchedWindow::deviceStateToString(const TouchedWindow::DeviceState& state) {
+ return StringPrintf("[touchingPointerIds=%s, "
+ "downTimeInTarget=%s, hoveringPointerIds=%s, pilferingPointerIds=%s]",
+ bitsetToString(state.touchingPointerIds).c_str(),
+ toString(state.downTimeInTarget).c_str(),
+ bitsetToString(state.hoveringPointerIds).c_str(),
+ bitsetToString(state.pilferingPointerIds).c_str());
+}
+
+std::string TouchedWindow::dump() const {
+ std::string out;
+ std::string deviceStates =
+ dumpMap(mDeviceStates, constToString, TouchedWindow::deviceStateToString);
+ out += StringPrintf("name='%s', targetFlags=%s, mDeviceStates=%s\n",
+ windowHandle->getName().c_str(), targetFlags.string().c_str(),
+ deviceStates.c_str());
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window) {
+ out << window.dump();
+ return out;
+}
+
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/dispatcher/TouchedWindow.h b/services/inputflinger/dispatcher/TouchedWindow.h
index 6783022ca1..c604353593 100644
--- a/services/inputflinger/dispatcher/TouchedWindow.h
+++ b/services/inputflinger/dispatcher/TouchedWindow.h
@@ -14,25 +14,79 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_TOUCHEDWINDOW_H
-#define _UI_INPUT_INPUTDISPATCHER_TOUCHEDWINDOW_H
+#pragma once
-namespace android {
+#include <gui/WindowInfo.h>
+#include <input/Input.h>
+#include <utils/BitSet.h>
+#include <bitset>
+#include <ostream>
+#include <set>
+#include "InputTarget.h"
-namespace gui {
-class WindowInfoHandle;
-}
+namespace android {
namespace inputdispatcher {
// Focus tracking for touch.
struct TouchedWindow {
sp<gui::WindowInfoHandle> windowHandle;
- int32_t targetFlags;
- BitSet32 pointerIds;
+ InputTarget::DispatchMode dispatchMode = InputTarget::DispatchMode::AS_IS;
+ ftl::Flags<InputTarget::Flags> targetFlags;
+
+ // Hovering
+ bool hasHoveringPointers() const;
+ bool hasHoveringPointers(DeviceId deviceId) const;
+ bool hasHoveringPointer(DeviceId deviceId, int32_t pointerId) const;
+ void addHoveringPointer(DeviceId deviceId, int32_t pointerId);
+ void removeHoveringPointer(DeviceId deviceId, int32_t pointerId);
+
+ // Touching
+ bool hasTouchingPointer(DeviceId deviceId, int32_t pointerId) const;
+ bool hasTouchingPointers() const;
+ bool hasTouchingPointers(DeviceId deviceId) const;
+ std::bitset<MAX_POINTER_ID + 1> getTouchingPointers(DeviceId deviceId) const;
+ void addTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+ void removeTouchingPointer(DeviceId deviceId, int32_t pointerId);
+ void removeTouchingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointers);
+ std::set<DeviceId> getTouchingDeviceIds() const;
+
+ // Pilfering pointers
+ bool hasPilferingPointers(DeviceId deviceId) const;
+ void addPilferingPointers(DeviceId deviceId, std::bitset<MAX_POINTER_ID + 1> pointerIds);
+ void addPilferingPointer(DeviceId deviceId, int32_t pointerId);
+ std::bitset<MAX_POINTER_ID + 1> getPilferingPointers(DeviceId deviceId) const;
+ std::map<DeviceId, std::bitset<MAX_POINTER_ID + 1>> getPilferingPointers() const;
+
+ // Down time
+ std::optional<nsecs_t> getDownTimeInTarget(DeviceId deviceId) const;
+ void trySetDownTimeInTarget(DeviceId deviceId, nsecs_t downTime);
+
+ void removeAllTouchingPointersForDevice(DeviceId deviceId);
+ void removeAllHoveringPointersForDevice(DeviceId deviceId);
+ void clearHoveringPointers(DeviceId deviceId);
+ std::string dump() const;
+
+private:
+ struct DeviceState {
+ std::bitset<MAX_POINTER_ID + 1> touchingPointerIds;
+ // The pointer ids of the pointers that this window is currently pilfering, by device
+ std::bitset<MAX_POINTER_ID + 1> pilferingPointerIds;
+ // Time at which the first action down occurred on this window, for each device
+ // NOTE: This is not initialized in case of HOVER entry/exit and DISPATCH_AS_OUTSIDE
+ // scenario.
+ std::optional<nsecs_t> downTimeInTarget;
+ std::bitset<MAX_POINTER_ID + 1> hoveringPointerIds;
+
+ bool hasPointers() const { return touchingPointerIds.any() || hoveringPointerIds.any(); };
+ };
+
+ std::map<DeviceId, DeviceState> mDeviceStates;
+
+ static std::string deviceStateToString(const TouchedWindow::DeviceState& state);
};
+std::ostream& operator<<(std::ostream& out, const TouchedWindow& window);
+
} // namespace inputdispatcher
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_TOUCHEDWINDOW_H
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
index 00abf47cd2..5eb3a32ef9 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherConfiguration.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERCONFIGURATION_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERCONFIGURATION_H
+#pragma once
#include <utils/Timers.h>
@@ -40,5 +39,3 @@ struct InputDispatcherConfiguration {
};
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERCONFIGURATION_H
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
index 38d0c32529..6b298c2dac 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherFactory.h
@@ -14,8 +14,7 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H
+#pragma once
#include <utils/StrongPointer.h>
@@ -26,8 +25,6 @@ namespace android {
// This factory method is used to encapsulate implementation details in internal header files.
std::unique_ptr<InputDispatcherInterface> createInputDispatcher(
- const sp<InputDispatcherPolicyInterface>& policy);
+ InputDispatcherPolicyInterface& policy);
} // namespace android
-
-#endif // _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERFACTORY_H
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
index 67fed8b4f1..001dc6cf7b 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherInterface.h
@@ -14,13 +14,12 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERINTERFACE_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERINTERFACE_H
+#pragma once
#include <InputListener.h>
#include <android-base/result.h>
#include <android/gui/FocusRequest.h>
-#include <android/os/BlockUntrustedTouchesMode.h>
+
#include <android/os/InputEventInjectionResult.h>
#include <android/os/InputEventInjectionSync.h>
#include <gui/InputApplication.h>
@@ -40,7 +39,7 @@ public:
/* Dumps the state of the input dispatcher.
*
* This method may be called on any thread (usually by the input manager). */
- virtual void dump(std::string& dump) = 0;
+ virtual void dump(std::string& dump) const = 0;
/* Called by the heatbeat to ensures that the dispatcher has not deadlocked. */
virtual void monitor() = 0;
@@ -51,7 +50,7 @@ public:
* Return true if the dispatcher is idle.
* Return false if the timeout waiting for the dispatcher to become idle has expired.
*/
- virtual bool waitForIdle() = 0;
+ virtual bool waitForIdle() const = 0;
/* Make the dispatcher start processing events.
*
@@ -77,7 +76,7 @@ public:
* perform all necessary permission checks prior to injecting events.
*/
virtual android::os::InputEventInjectionResult injectInputEvent(
- const InputEvent* event, std::optional<int32_t> targetUid,
+ const InputEvent* event, std::optional<gui::Uid> targetUid,
android::os::InputEventInjectionSync syncMode, std::chrono::milliseconds timeout,
uint32_t policyFlags) = 0;
@@ -88,14 +87,6 @@ public:
*/
virtual std::unique_ptr<VerifiedInputEvent> verifyInputEvent(const InputEvent& event) = 0;
- /* Sets the list of input windows per display.
- *
- * This method may be called on any thread (usually by the input manager).
- */
- virtual void setInputWindows(
- const std::unordered_map<int32_t, std::vector<sp<gui::WindowInfoHandle>>>&
- handlesPerDisplay) = 0;
-
/* Sets the focused application on the given display.
*
* This method may be called on any thread (usually by the input manager).
@@ -126,13 +117,17 @@ public:
/**
* Set the touch mode state.
- * Touch mode is a global state that apps may enter / exit based on specific
- * user interactions with input devices.
- * If true, the device is in touch mode.
+ * Touch mode is a per display state that apps may enter / exit based on specific user
+ * interactions with input devices. If <code>inTouchMode</code> is set to true, the display
+ * identified by <code>displayId</code> 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, gui::Pid pid, gui::Uid uid, bool hasPermission,
+ int32_t displayId) = 0;
/**
* Sets the maximum allowed obscuring opacity by UID to propagate touches.
@@ -142,13 +137,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.
@@ -186,7 +174,7 @@ public:
*/
virtual base::Result<std::unique_ptr<InputChannel>> createInputMonitor(int32_t displayId,
const std::string& name,
- int32_t pid) = 0;
+ gui::Pid pid) = 0;
/* Removes input channels that will no longer receive input events.
*
@@ -229,8 +217,18 @@ public:
* Abort the current touch stream.
*/
virtual void cancelCurrentTouch() = 0;
+
+ /*
+ * Updates key repeat configuration timeout and delay.
+ */
+ virtual void setKeyRepeatConfiguration(std::chrono::nanoseconds timeout,
+ std::chrono::nanoseconds delay) = 0;
+
+ /*
+ * Determine if a pointer from a device is being dispatched to the given window.
+ */
+ virtual bool isPointerInWindow(const sp<IBinder>& token, int32_t displayId, DeviceId deviceId,
+ int32_t pointerId) = 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 575b3d7059..1c23720ecb 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -14,34 +14,35 @@
* limitations under the License.
*/
-#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H
-#define _UI_INPUT_INPUTDISPATCHER_INPUTDISPATCHERPOLICYINTERFACE_H
+#pragma once
#include "InputDispatcherConfiguration.h"
+#include <android-base/properties.h>
#include <binder/IBinder.h>
#include <gui/InputApplication.h>
+#include <gui/PidUid.h>
#include <input/Input.h>
+#include <input/InputDevice.h>
#include <utils/RefBase.h>
+#include <set>
namespace android {
-
/*
* Input dispatcher policy interface.
*
- * The input reader policy is used by the input reader to interact with the Window Manager
+ * The input dispatcher policy is used by the input dispatcher to interact with the Window Manager
* and other system components.
*
* 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;
@@ -55,7 +56,7 @@ public:
* pid of the owner. The string reason contains information about the input event that we
* haven't received a response for.
*/
- virtual void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<int32_t> pid,
+ virtual void notifyWindowUnresponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid,
const std::string& reason) = 0;
/* Notifies the system that a window just became responsive. This is only called after the
@@ -63,7 +64,7 @@ public:
* no longer should be shown to the user. The window is eligible to cause a new ANR in the
* future.
*/
- virtual void notifyWindowResponsive(const sp<IBinder>& token, std::optional<int32_t> pid) = 0;
+ virtual void notifyWindowResponsive(const sp<IBinder>& token, std::optional<gui::Pid> pid) = 0;
/* Notifies the system that an input channel is unrecoverably broken. */
virtual void notifyInputChannelBroken(const sp<IBinder>& token) = 0;
@@ -75,18 +76,12 @@ 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;
-
/* 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
@@ -95,7 +90,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
@@ -104,18 +99,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<IBinder>& 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<IBinder>& 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<KeyEvent> dispatchUnhandledKey(const sp<IBinder>& token,
+ const KeyEvent& keyEvent,
+ uint32_t policyFlags) = 0;
/* Notifies the policy about switch events.
*/
@@ -125,6 +121,16 @@ public:
/* Poke user activity for an event dispatched to a window. */
virtual void pokeUserActivity(nsecs_t eventTime, int32_t eventType, int32_t displayId) = 0;
+ /*
+ * Return true if the provided event is stale, and false otherwise. Used for determining
+ * whether the dispatcher should drop the event.
+ */
+ virtual bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) {
+ static const std::chrono::duration STALE_EVENT_TIMEOUT =
+ std::chrono::seconds(10) * android::base::HwTimeoutMultiplier();
+ return std::chrono::nanoseconds(currentTime - eventTime) >= STALE_EVENT_TIMEOUT;
+ }
+
/* Notifies the policy that a pointer down event has occurred outside the current focused
* window.
*
@@ -140,8 +146,10 @@ public:
/* Notifies the policy that the drag window has moved over to another window */
virtual void notifyDropWindow(const sp<IBinder>& token, float x, float y) = 0;
+
+ /* Notifies the policy that there was an input device interaction with apps. */
+ virtual void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
+ const std::set<gui::Uid>& uids) = 0;
};
} // 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 0000000000..7795710936
--- /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
deleted file mode 100644
index 743587c6f8..0000000000
--- a/services/inputflinger/host/Android.bp
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (C) 2015 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- // See: http://go/android-license-faq
- // A large-scale-change added 'default_applicable_licenses' to import
- // all of the 'license_kinds' from "frameworks_native_license"
- // to get the below license kinds:
- // SPDX-license-identifier-Apache-2.0
- default_applicable_licenses: ["frameworks_native_license"],
-}
-
-cc_library_shared {
- name: "libinputflingerhost",
-
- srcs: [
- "InputFlinger.cpp",
- "InputDriver.cpp",
- "InputHost.cpp",
- ],
-
- header_libs: ["jni_headers"],
- shared_libs: [
- "libbase",
- "libbinder",
- "libcrypto",
- "libcutils",
- "libinput",
- "liblog",
- "libutils",
- "libhardware",
- ],
- static_libs: [
- "libarect",
- ],
-
- cflags: [
- "-Wall",
- "-Werror",
- "-Wno-unused-parameter",
- // TODO: Move inputflinger to its own process and mark it hidden
- //-fvisibility=hidden
- ],
-
- export_header_lib_headers: ["jni_headers"],
- export_include_dirs: ["."],
-}
-
-//#######################################################################
-// build input flinger executable
-cc_binary {
- name: "inputflinger",
-
- srcs: ["main.cpp"],
-
- cflags: ["-Wall", "-Werror"],
-
- shared_libs: [
- "libbase",
- "libbinder",
- "libinputflingerhost",
- "libutils",
- "libinput"
- ],
- static_libs: [
- "libarect",
- "libui-types",
- ],
-}
diff --git a/services/inputflinger/host/InputDriver.cpp b/services/inputflinger/host/InputDriver.cpp
deleted file mode 100644
index 2ebdbcfcc4..0000000000
--- a/services/inputflinger/host/InputDriver.cpp
+++ /dev/null
@@ -1,414 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 <functional>
-#include <stdint.h>
-#include <sys/types.h>
-#include <unordered_map>
-#include <vector>
-
-#define LOG_TAG "InputDriver"
-
-#define LOG_NDEBUG 0
-
-#include "InputDriver.h"
-#include "InputHost.h"
-
-#include <hardware/input.h>
-#include <input/InputDevice.h>
-#include <input/PropertyMap.h>
-#include <utils/Log.h>
-#include <utils/String8.h>
-
-#define INDENT2 " "
-
-struct input_property_map {
- std::unique_ptr<android::PropertyMap> propertyMap;
-};
-
-struct input_property {
- android::String8 key;
- android::String8 value;
-};
-
-struct input_device_identifier {
- const char* name;
- const char* uniqueId;
- input_bus_t bus;
- int32_t vendorId;
- int32_t productId;
- int32_t version;
-};
-
-struct input_device_definition {
- std::vector<input_report_definition*> reportDefs;
-};
-
-struct input_device_handle {
- input_device_identifier_t* id;
- input_device_definition_t* def;
-};
-
-struct input_int_usage {
- input_usage_t usage;
- int32_t min;
- int32_t max;
- float resolution;
-};
-
-struct input_collection {
- int32_t arity;
- std::vector<input_int_usage> intUsages;
- std::vector<input_usage_t> boolUsages;
-};
-
-struct InputCollectionIdHasher {
- std::size_t operator()(const input_collection_id& id) const {
- return std::hash<int>()(static_cast<int>(id));
- }
-};
-
-struct input_report_definition {
- std::unordered_map<input_collection_id_t, input_collection, InputCollectionIdHasher> collections;
-};
-
-
-namespace android {
-
-static input_host_callbacks_t kCallbacks = {
- .create_device_identifier = create_device_identifier,
- .create_device_definition = create_device_definition,
- .create_input_report_definition = create_input_report_definition,
- .create_output_report_definition = create_output_report_definition,
- .free_report_definition = free_report_definition,
- .input_device_definition_add_report = input_device_definition_add_report,
- .input_report_definition_add_collection = input_report_definition_add_collection,
- .input_report_definition_declare_usage_int = input_report_definition_declare_usage_int,
- .input_report_definition_declare_usages_bool = input_report_definition_declare_usages_bool,
- .register_device = register_device,
- .input_allocate_report = input_allocate_report,
- .input_report_set_usage_int = input_report_set_usage_int,
- .input_report_set_usage_bool = input_report_set_usage_bool,
- .report_event = report_event,
- .input_get_device_property_map = input_get_device_property_map,
- .input_get_device_property = input_get_device_property,
- .input_get_property_key = input_get_property_key,
- .input_get_property_value = input_get_property_value,
- .input_free_device_property = input_free_device_property,
- .input_free_device_property_map = input_free_device_property_map,
-};
-
-InputDriver::InputDriver(const char* name) : mName(String8(name)) {
- const hw_module_t* module;
- int err = input_open(&module, name);
- LOG_ALWAYS_FATAL_IF(err != 0, "Input module %s not found", name);
- mHal = reinterpret_cast<const input_module_t*>(module);
-}
-
-void InputDriver::init() {
- mHal->init(mHal, static_cast<input_host_t*>(this), kCallbacks);
-}
-
-input_device_identifier_t* InputDriver::createDeviceIdentifier(
- const char* name, int32_t productId, int32_t vendorId,
- input_bus_t bus, const char* uniqueId) {
- auto identifier = new ::input_device_identifier {
- .name = name,
- .uniqueId = uniqueId,
- .bus = bus,
- .vendorId = vendorId,
- .productId = productId,
- };
- // TODO: store this identifier somewhere
- return identifier;
-}
-
-input_device_definition_t* InputDriver::createDeviceDefinition() {
- return new ::input_device_definition;
-}
-
-input_report_definition_t* InputDriver::createInputReportDefinition() {
- return new ::input_report_definition;
-}
-
-input_report_definition_t* InputDriver::createOutputReportDefinition() {
- return new ::input_report_definition;
-}
-
-void InputDriver::freeReportDefinition(input_report_definition_t* reportDef) {
- delete reportDef;
-}
-
-void InputDriver::inputDeviceDefinitionAddReport(input_device_definition_t* d,
- input_report_definition_t* r) {
- d->reportDefs.push_back(r);
-}
-
-void InputDriver::inputReportDefinitionAddCollection(input_report_definition_t* report,
- input_collection_id_t id, int32_t arity) {
- report->collections[id] = {.arity = arity};
-}
-
-void InputDriver::inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
- input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
- float resolution) {
- if (report->collections.find(id) != report->collections.end()) {
- report->collections[id].intUsages.push_back({
- .usage = usage, .min = min, .max = max, .resolution = resolution});
- }
-}
-
-void InputDriver::inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
- input_collection_id_t id, input_usage_t* usage, size_t usageCount) {
- if (report->collections.find(id) != report->collections.end()) {
- for (size_t i = 0; i < usageCount; ++i) {
- report->collections[id].boolUsages.push_back(usage[i]);
- }
- }
-}
-
-input_device_handle_t* InputDriver::registerDevice(input_device_identifier_t* id,
- input_device_definition_t* d) {
- ALOGD("Registering device %s with %zu input reports", id->name, d->reportDefs.size());
- // TODO: save this device handle
- return new input_device_handle{ .id = id, .def = d };
-}
-
-void InputDriver::unregisterDevice(input_device_handle_t* handle) {
- delete handle;
-}
-
-input_report_t* InputDriver::inputAllocateReport(input_report_definition_t* r) {
- ALOGD("Allocating input report for definition %p", r);
- return nullptr;
-}
-
-void InputDriver::inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
- input_usage_t usage, int32_t value, int32_t arity_index) {
-}
-
-void InputDriver::inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
- input_usage_t usage, bool value, int32_t arity_index) {
-}
-
-void InputDriver::reportEvent(input_device_handle_t* d, input_report_t* report) {
- ALOGD("report_event %p for handle %p", report, d);
-}
-
-input_property_map_t* InputDriver::inputGetDevicePropertyMap(input_device_identifier_t* id) {
- InputDeviceIdentifier idi;
- idi.name = id->name;
- idi.uniqueId = id->uniqueId;
- idi.bus = id->bus;
- idi.vendor = id->vendorId;
- idi.product = id->productId;
- idi.version = id->version;
-
- std::string configFile =
- getInputDeviceConfigurationFilePathByDeviceIdentifier(idi,
- InputDeviceConfigurationFileType::
- CONFIGURATION);
- if (configFile.empty()) {
- ALOGD("No input device configuration file found for device '%s'.",
- idi.name.c_str());
- } else {
- std::unique_ptr<input_property_map_t> propMap = std::make_unique<input_property_map_t>();
- android::base::Result<std::unique_ptr<PropertyMap>> result =
- PropertyMap::load(configFile.c_str());
- if (!result.ok()) {
- ALOGE("Error loading input device configuration file for device '%s'. "
- "Using default configuration.",
- idi.name.c_str());
- return nullptr;
- }
- propMap->propertyMap = std::move(*result);
- return propMap.release();
- }
- return nullptr;
-}
-
-input_property_t* InputDriver::inputGetDeviceProperty(input_property_map_t* map,
- const char* key) {
- String8 keyString(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;
- }
- }
- return nullptr;
-}
-
-const char* InputDriver::inputGetPropertyKey(input_property_t* property) {
- if (property != nullptr) {
- return property->key.string();
- }
- return nullptr;
-}
-
-const char* InputDriver::inputGetPropertyValue(input_property_t* property) {
- if (property != nullptr) {
- return property->value.string();
- }
- return nullptr;
-}
-
-void InputDriver::inputFreeDeviceProperty(input_property_t* property) {
- if (property != nullptr) {
- delete property;
- }
-}
-
-void InputDriver::inputFreeDevicePropertyMap(input_property_map_t* map) {
- if (map != nullptr) {
- delete map;
- }
-}
-
-void InputDriver::dump(String8& result) {
- result.appendFormat(INDENT2 "HAL Input Driver (%s)\n", mName.string());
-}
-
-} // namespace android
-
-// HAL wrapper functions
-
-namespace android {
-
-::input_device_identifier_t* create_device_identifier(input_host_t* host,
- const char* name, int32_t product_id, int32_t vendor_id,
- input_bus_t bus, const char* unique_id) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->createDeviceIdentifier(name, product_id, vendor_id, bus, unique_id);
-}
-
-input_device_definition_t* create_device_definition(input_host_t* host) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->createDeviceDefinition();
-}
-
-input_report_definition_t* create_input_report_definition(input_host_t* host) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->createInputReportDefinition();
-}
-
-input_report_definition_t* create_output_report_definition(input_host_t* host) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->createOutputReportDefinition();
-}
-
-void free_report_definition(input_host_t* host, input_report_definition_t* report_def) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->freeReportDefinition(report_def);
-}
-
-void input_device_definition_add_report(input_host_t* host,
- input_device_definition_t* d, input_report_definition_t* r) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputDeviceDefinitionAddReport(d, r);
-}
-
-void input_report_definition_add_collection(input_host_t* host,
- input_report_definition_t* report, input_collection_id_t id, int32_t arity) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputReportDefinitionAddCollection(report, id, arity);
-}
-
-void input_report_definition_declare_usage_int(input_host_t* host,
- input_report_definition_t* report, input_collection_id_t id,
- input_usage_t usage, int32_t min, int32_t max, float resolution) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputReportDefinitionDeclareUsageInt(report, id, usage, min, max, resolution);
-}
-
-void input_report_definition_declare_usages_bool(input_host_t* host,
- input_report_definition_t* report, input_collection_id_t id,
- input_usage_t* usage, size_t usage_count) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputReportDefinitionDeclareUsagesBool(report, id, usage, usage_count);
-}
-
-input_device_handle_t* register_device(input_host_t* host,
- input_device_identifier_t* id, input_device_definition_t* d) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->registerDevice(id, d);
-}
-
-void unregister_device(input_host_t* host, input_device_handle_t* handle) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->unregisterDevice(handle);
-}
-
-input_report_t* input_allocate_report(input_host_t* host, input_report_definition_t* r) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->inputAllocateReport(r);
-}
-
-void input_report_set_usage_int(input_host_t* host, input_report_t* r,
- input_collection_id_t id, input_usage_t usage, int32_t value, int32_t arity_index) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputReportSetUsageInt(r, id, usage, value, arity_index);
-}
-
-void input_report_set_usage_bool(input_host_t* host, input_report_t* r,
- input_collection_id_t id, input_usage_t usage, bool value, int32_t arity_index) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputReportSetUsageBool(r, id, usage, value, arity_index);
-}
-
-void report_event(input_host_t* host, input_device_handle_t* d, input_report_t* report) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->reportEvent(d, report);
-}
-
-input_property_map_t* input_get_device_property_map(input_host_t* host,
- input_device_identifier_t* id) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->inputGetDevicePropertyMap(id);
-}
-
-input_property_t* input_get_device_property(input_host_t* host, input_property_map_t* map,
- const char* key) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->inputGetDeviceProperty(map, key);
-}
-
-const char* input_get_property_key(input_host_t* host, input_property_t* property) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->inputGetPropertyKey(property);
-}
-
-const char* input_get_property_value(input_host_t* host, input_property_t* property) {
- auto driver = static_cast<InputDriverInterface*>(host);
- return driver->inputGetPropertyValue(property);
-}
-
-void input_free_device_property(input_host_t* host, input_property_t* property) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputFreeDeviceProperty(property);
-}
-
-void input_free_device_property_map(input_host_t* host, input_property_map_t* map) {
- auto driver = static_cast<InputDriverInterface*>(host);
- driver->inputFreeDevicePropertyMap(map);
-}
-
-} // namespace android
diff --git a/services/inputflinger/host/InputDriver.h b/services/inputflinger/host/InputDriver.h
deleted file mode 100644
index e56673b4d3..0000000000
--- a/services/inputflinger/host/InputDriver.h
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_INPUT_DRIVER_H
-#define ANDROID_INPUT_DRIVER_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "InputHost.h"
-
-#include <hardware/input.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-
-// Declare a concrete type for the HAL
-struct input_host {
-};
-
-namespace android {
-
-class InputDriverInterface : public input_host_t, public virtual RefBase {
-protected:
- InputDriverInterface() = default;
- virtual ~InputDriverInterface() = default;
-
-public:
- virtual void init() = 0;
-
- virtual input_device_identifier_t* createDeviceIdentifier(
- const char* name, int32_t productId, int32_t vendorId,
- input_bus_t bus, const char* uniqueId) = 0;
- virtual input_device_definition_t* createDeviceDefinition() = 0;
- virtual input_report_definition_t* createInputReportDefinition() = 0;
- virtual input_report_definition_t* createOutputReportDefinition() = 0;
- virtual void freeReportDefinition(input_report_definition_t* reportDef) = 0;
-
- virtual void inputDeviceDefinitionAddReport(input_device_definition_t* d,
- input_report_definition_t* r) = 0;
- virtual void inputReportDefinitionAddCollection(input_report_definition_t* report,
- input_collection_id_t id, int32_t arity) = 0;
- virtual void inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
- input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
- float resolution) = 0;
- virtual void inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
- input_collection_id_t id, input_usage_t* usage, size_t usageCount) = 0;
-
- virtual input_device_handle_t* registerDevice(input_device_identifier_t* id,
- input_device_definition_t* d) = 0;
- virtual void unregisterDevice(input_device_handle_t* handle) = 0;
-
- virtual input_report_t* inputAllocateReport(input_report_definition_t* r) = 0;
- virtual void inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
- input_usage_t usage, int32_t value, int32_t arity_index) = 0;
- virtual void inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
- input_usage_t usage, bool value, int32_t arity_index) = 0;
- virtual void reportEvent(input_device_handle_t* d, input_report_t* report) = 0;
-
- virtual input_property_map_t* inputGetDevicePropertyMap(input_device_identifier_t* id) = 0;
- virtual input_property_t* inputGetDeviceProperty(input_property_map_t* map,
- const char* key) = 0;
- virtual const char* inputGetPropertyKey(input_property_t* property) = 0;
- virtual const char* inputGetPropertyValue(input_property_t* property) = 0;
- virtual void inputFreeDeviceProperty(input_property_t* property) = 0;
- virtual void inputFreeDevicePropertyMap(input_property_map_t* map) = 0;
-
- virtual void dump(String8& result) = 0;
-};
-
-class InputDriver : public InputDriverInterface {
-public:
- explicit InputDriver(const char* name);
- virtual ~InputDriver() = default;
-
- virtual void init() override;
-
- virtual input_device_identifier_t* createDeviceIdentifier(
- const char* name, int32_t productId, int32_t vendorId,
- input_bus_t bus, const char* uniqueId) override;
- virtual input_device_definition_t* createDeviceDefinition() override;
- virtual input_report_definition_t* createInputReportDefinition() override;
- virtual input_report_definition_t* createOutputReportDefinition() override;
- virtual void freeReportDefinition(input_report_definition_t* reportDef) override;
-
- virtual void inputDeviceDefinitionAddReport(input_device_definition_t* d,
- input_report_definition_t* r) override;
- virtual void inputReportDefinitionAddCollection(input_report_definition_t* report,
- input_collection_id_t id, int32_t arity) override;
- virtual void inputReportDefinitionDeclareUsageInt(input_report_definition_t* report,
- input_collection_id_t id, input_usage_t usage, int32_t min, int32_t max,
- float resolution) override;
- virtual void inputReportDefinitionDeclareUsagesBool(input_report_definition_t* report,
- input_collection_id_t id, input_usage_t* usage, size_t usageCount) override;
-
- virtual input_device_handle_t* registerDevice(input_device_identifier_t* id,
- input_device_definition_t* d) override;
- virtual void unregisterDevice(input_device_handle_t* handle) override;
-
- virtual input_report_t* inputAllocateReport(input_report_definition_t* r) override;
- virtual void inputReportSetUsageInt(input_report_t* r, input_collection_id_t id,
- input_usage_t usage, int32_t value, int32_t arity_index) override;
- virtual void inputReportSetUsageBool(input_report_t* r, input_collection_id_t id,
- input_usage_t usage, bool value, int32_t arity_index) override;
- virtual void reportEvent(input_device_handle_t* d, input_report_t* report) override;
-
- virtual input_property_map_t* inputGetDevicePropertyMap(input_device_identifier_t* id) override;
- virtual input_property_t* inputGetDeviceProperty(input_property_map_t* map,
- const char* key) override;
- virtual const char* inputGetPropertyKey(input_property_t* property) override;
- virtual const char* inputGetPropertyValue(input_property_t* property) override;
- virtual void inputFreeDeviceProperty(input_property_t* property) override;
- virtual void inputFreeDevicePropertyMap(input_property_map_t* map) override;
-
- virtual void dump(String8& result) override;
-
-private:
- String8 mName;
- const input_module_t* mHal;
-};
-
-
-extern "C" {
-
-input_device_identifier_t* create_device_identifier(input_host_t* host,
- const char* name, int32_t product_id, int32_t vendor_id,
- input_bus_t bus, const char* unique_id);
-
-input_device_definition_t* create_device_definition(input_host_t* host);
-
-input_report_definition_t* create_input_report_definition(input_host_t* host);
-
-input_report_definition_t* create_output_report_definition(input_host_t* host);
-
-void free_report_definition(input_host_t* host, input_report_definition_t* report_def);
-
-void input_device_definition_add_report(input_host_t* host,
- input_device_definition_t* d, input_report_definition_t* r);
-
-void input_report_definition_add_collection(input_host_t* host,
- input_report_definition_t* report, input_collection_id_t id, int32_t arity);
-
-void input_report_definition_declare_usage_int(input_host_t* host,
- input_report_definition_t* report, input_collection_id_t id,
- input_usage_t usage, int32_t min, int32_t max, float resolution);
-
-void input_report_definition_declare_usages_bool(input_host_t* host,
- input_report_definition_t* report, input_collection_id_t id,
- input_usage_t* usage, size_t usage_count);
-
-
-input_device_handle_t* register_device(input_host_t* host,
- input_device_identifier_t* id, input_device_definition_t* d);
-
-void unregister_device(input_host_t* host, input_device_handle_t* handle);
-
-input_report_t* input_allocate_report(input_host_t* host, input_report_definition_t* r);
-
-void input_report_set_usage_int(input_host_t* host, input_report_t* r,
- input_collection_id_t id, input_usage_t usage, int32_t value, int32_t arity_index);
-
-void input_report_set_usage_bool(input_host_t* host, input_report_t* r,
- input_collection_id_t id, input_usage_t usage, bool value, int32_t arity_index);
-
-void report_event(input_host_t* host, input_device_handle_t* d, input_report_t* report);
-
-input_property_map_t* input_get_device_property_map(input_host_t* host,
- input_device_identifier_t* id);
-
-input_property_t* input_get_device_property(input_host_t* host, input_property_map_t* map,
- const char* key);
-
-const char* input_get_property_key(input_host_t* host, input_property_t* property);
-
-const char* input_get_property_value(input_host_t* host, input_property_t* property);
-
-void input_free_device_property(input_host_t* host, input_property_t* property);
-
-void input_free_device_property_map(input_host_t* host, input_property_map_t* map);
-}
-
-} // namespace android
-#endif // ANDROID_INPUT_DRIVER_H
diff --git a/services/inputflinger/host/InputFlinger.cpp b/services/inputflinger/host/InputFlinger.cpp
deleted file mode 100644
index 2da2a70c03..0000000000
--- a/services/inputflinger/host/InputFlinger.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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 "InputFlinger"
-
-#include <stdint.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionCache.h>
-#include <hardware/input.h>
-#include <log/log.h>
-#include <private/android_filesystem_config.h>
-
-#include "InputFlinger.h"
-#include "InputDriver.h"
-
-namespace android {
-
-const String16 sAccessInputFlingerPermission("android.permission.ACCESS_INPUT_FLINGER");
-const String16 sDumpPermission("android.permission.DUMP");
-
-
-InputFlinger::InputFlinger() :
- BnInputFlinger() {
- ALOGI("InputFlinger is starting");
- mHost = new InputHost();
- mHost->registerInputDriver(new InputDriver(INPUT_INSTANCE_EVDEV));
-}
-
-InputFlinger::~InputFlinger() {
-}
-
-status_t InputFlinger::dump(int fd, const Vector<String16>& args) {
- String8 result;
- const IPCThreadState* ipc = IPCThreadState::self();
- const int pid = ipc->getCallingPid();
- const int uid = ipc->getCallingUid();
- if ((uid != AID_SHELL)
- && !PermissionCache::checkPermission(sDumpPermission, pid, uid)) {
- result.appendFormat("Permission Denial: "
- "can't dump InputFlinger from pid=%d, uid=%d\n", pid, uid);
- } else {
- dumpInternal(result);
- }
- write(fd, result.string(), result.size());
- return OK;
-}
-
-void InputFlinger::dumpInternal(String8& result) {
- result.append("INPUT FLINGER (dumpsys inputflinger)\n");
- mHost->dump(result);
-}
-
-}; // namespace android
diff --git a/services/inputflinger/host/InputFlinger.h b/services/inputflinger/host/InputFlinger.h
deleted file mode 100644
index 3cf1b2b797..0000000000
--- a/services/inputflinger/host/InputFlinger.h
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_INPUT_FLINGER_H
-#define ANDROID_INPUT_FLINGER_H
-
-#include <stdint.h>
-#include <sys/types.h>
-
-#include "InputHost.h"
-
-#include <android/os/BnInputFlinger.h>
-#include <binder/Binder.h>
-#include <cutils/compiler.h>
-#include <utils/String16.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-using android::gui::FocusRequest;
-using android::os::BnInputFlinger;
-
-namespace android {
-
-class InputFlinger : public BnInputFlinger {
-public:
- static char const* getServiceName() ANDROID_API {
- return "inputflinger";
- }
-
- InputFlinger() ANDROID_API;
-
- status_t dump(int fd, const Vector<String16>& args) override;
- binder::Status createInputChannel(const std::string&, InputChannel*) override {
- return binder::Status::ok();
- }
- binder::Status removeInputChannel(const sp<IBinder>&) override { return binder::Status::ok(); }
- binder::Status setFocusedWindow(const FocusRequest&) override { return binder::Status::ok(); }
-
-private:
- ~InputFlinger() override;
-
- void dumpInternal(String8& result);
-
- sp<InputHostInterface> mHost;
-};
-
-} // namespace android
-
-#endif // ANDROID_INPUT_FLINGER_H
diff --git a/services/inputflinger/host/InputHost.h b/services/inputflinger/host/InputHost.h
deleted file mode 100644
index eda4a89c73..0000000000
--- a/services/inputflinger/host/InputHost.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT 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_INPUT_HOST_H
-#define ANDROID_INPUT_HOST_H
-
-#include <vector>
-
-#include <hardware/input.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-#include <utils/StrongPointer.h>
-
-#include "InputDriver.h"
-
-namespace android {
-
-class InputDriverInterface;
-
-class InputHostInterface : public virtual RefBase {
-protected:
- InputHostInterface() = default;
- virtual ~InputHostInterface() = default;
-
-public:
-
- virtual void registerInputDriver(InputDriverInterface* driver) = 0;
-
- virtual void dump(String8& result) = 0;
-};
-
-class InputHost : public InputHostInterface {
-public:
- InputHost() = default;
-
- virtual void registerInputDriver(InputDriverInterface* driver) override;
-
- virtual void dump(String8& result) override;
-
-private:
- std::vector<sp<InputDriverInterface>> mDrivers;
-};
-
-} // namespace android
-#endif // ANDRIOD_INPUT_HOST_H
diff --git a/services/inputflinger/host/inputflinger.rc b/services/inputflinger/host/inputflinger.rc
deleted file mode 100644
index 4130ddc095..0000000000
--- a/services/inputflinger/host/inputflinger.rc
+++ /dev/null
@@ -1,5 +0,0 @@
-service inputflinger /system/bin/inputflinger
- class main
- user system
- group input wakelock
-# onrestart restart zygote
diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h
index d9822ce156..0b7f7c2831 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 <vector>
#include <input/Input.h>
#include <input/InputDevice.h>
#include <input/TouchVideoFrame.h>
+#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<TouchVideoFrame> 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<TouchVideoFrame>& 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<float> 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<float> 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<NotifyArgs>& operator+=(std::list<NotifyArgs>& keep, std::list<NotifyArgs>&& 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,43 @@ 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<std::unique_ptr<NotifyArgs>> mArgsQueue;
+ std::vector<NotifyArgs> mArgsQueue;
};
-} // namespace android
+/*
+ * An implementation of the listener interface that traces the calls to its inner listener.
+ */
+class TracedInputListener : public InputListenerInterface {
+public:
+ explicit TracedInputListener(const char* name, InputListenerInterface& innerListener);
+
+ 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;
-#endif // _UI_INPUT_LISTENER_H
+private:
+ InputListenerInterface& mInnerListener;
+ const char* mName;
+};
+
+} // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 41ecef365d..efc8b260f3 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 <android/os/IInputConstants.h>
#include <input/DisplayViewport.h>
@@ -24,6 +23,7 @@
#include <input/VelocityControl.h>
#include <input/VelocityTracker.h>
#include <stddef.h>
+#include <ui/Rotation.h>
#include <unistd.h>
#include <utils/Errors.h>
#include <utils/RefBase.h>
@@ -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<InputDeviceInfo> 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<int32_t> getVibratorIds(int32_t deviceId) = 0;
- /* Get battery level of a particular input device. */
- virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) = 0;
- /* Get battery status of a particular input device. */
- virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
-
- virtual std::vector<InputDeviceLightInfo> getLights(int32_t deviceId) = 0;
-
- virtual std::vector<InputDeviceSensorInfo> 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<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
- /* Get light player ID */
- virtual std::optional<int32_t> 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<std::string, uint8_t> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, KeyboardLayoutInfo> 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<int32_t> 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<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueDisplayId)
@@ -318,7 +262,6 @@ struct InputReaderConfiguration {
std::optional<DisplayViewport> getDisplayViewportById(int32_t displayId) const;
void setDisplayViewports(const std::vector<DisplayViewport>& viewports);
-
void dump(std::string& dump) const;
void dumpViewport(std::string& dump, const DisplayViewport& viewport) const;
@@ -326,6 +269,116 @@ private:
std::vector<DisplayViewport> mDisplays;
};
+using ConfigurationChanges = ftl::Flags<InputReaderConfiguration::Change>;
+
+// --- 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<InputDeviceInfo> 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<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(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<int32_t> getVibratorIds(int32_t deviceId) = 0;
+ /* Get battery level of a particular input device. */
+ virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) = 0;
+ /* Get battery status of a particular input device. */
+ virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
+ /* Get the device path for the battery of an input device. */
+ virtual std::optional<std::string> getBatteryDevicePath(int32_t deviceId) = 0;
+
+ virtual std::vector<InputDeviceLightInfo> getLights(int32_t deviceId) = 0;
+
+ virtual std::vector<InputDeviceSensorInfo> 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<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
+ /* Get light player ID */
+ virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
+
+ /* Get the Bluetooth address of an input device, if known. */
+ virtual std::optional<std::string> 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 {
@@ -384,16 +437,29 @@ public:
/* Gets the keyboard layout for a particular input device. */
virtual std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
- const InputDeviceIdentifier& identifier) = 0;
+ const InputDeviceIdentifier& identifier,
+ const std::optional<KeyboardLayoutInfo> keyboardLayoutInfo) = 0;
/* Gets a user-supplied alias for a particular input device, or an empty string if none. */
virtual std::string getDeviceAlias(const InputDeviceIdentifier& identifier) = 0;
/* 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;
+
+ /* Returns true if any InputConnection is currently active. */
+ virtual bool isInputMethodConnectionActive() = 0;
+
+ /* Gets the viewport of a particular display that the pointer device is associated with. If
+ * the pointer device is not associated with any display, it should ADISPLAY_IS_NONE to get
+ * the viewport that should be used. The device should get a new viewport using this method
+ * every time there is a display configuration change. The logical bounds of the viewport should
+ * be used as the range of possible values for pointing devices, like mice and touchpads.
+ */
+ virtual std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
+ int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
};
} // namespace android
-
-#endif // _UI_INPUT_READER_COMMON_H
diff --git a/services/inputflinger/include/InputThread.h b/services/inputflinger/include/InputThread.h
index 407365a269..5e75027056 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 <utils/Thread.h>
@@ -42,5 +41,3 @@ private:
};
} // namespace android
-
-#endif // _UI_INPUT_THREAD_H \ No newline at end of file
diff --git a/services/inputflinger/include/KeyCodeClassifications.h b/services/inputflinger/include/KeyCodeClassifications.h
new file mode 100644
index 0000000000..a09b02e17b
--- /dev/null
+++ b/services/inputflinger/include/KeyCodeClassifications.h
@@ -0,0 +1,55 @@
+/*
+ * 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 <android/input.h>
+#include <set>
+
+namespace android {
+
+/** The set of all Android key codes that are required for a device to be classified as a D-pad. */
+static const std::set<int32_t> DPAD_REQUIRED_KEYCODES = {
+ AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT,
+ AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_CENTER,
+};
+
+/** The set of all Android key codes that correspond to D-pad keys. */
+static const std::set<int32_t> DPAD_ALL_KEYCODES = {
+ AKEYCODE_DPAD_UP, AKEYCODE_DPAD_DOWN, AKEYCODE_DPAD_LEFT,
+ AKEYCODE_DPAD_RIGHT, AKEYCODE_DPAD_CENTER, AKEYCODE_DPAD_UP_LEFT,
+ AKEYCODE_DPAD_UP_RIGHT, AKEYCODE_DPAD_DOWN_LEFT, AKEYCODE_DPAD_DOWN_RIGHT,
+};
+
+/** The set of all Android key codes that correspond to gamepad buttons. */
+static const std::set<int32_t> GAMEPAD_KEYCODES = {
+ AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C, //
+ AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z, //
+ AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1, //
+ AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2, //
+ AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR, //
+ AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE, //
+};
+
+/** The set of all Android key codes that correspond to buttons (bit-switches) on a stylus. */
+static const std::set<int32_t> STYLUS_BUTTON_KEYCODES = {
+ AKEYCODE_STYLUS_BUTTON_PRIMARY,
+ AKEYCODE_STYLUS_BUTTON_SECONDARY,
+ AKEYCODE_STYLUS_BUTTON_TERTIARY,
+ AKEYCODE_STYLUS_BUTTON_TAIL,
+};
+
+} // namespace android
diff --git a/services/inputflinger/include/NotifyArgs.h b/services/inputflinger/include/NotifyArgs.h
new file mode 100644
index 0000000000..736b1e07b7
--- /dev/null
+++ b/services/inputflinger/include/NotifyArgs.h
@@ -0,0 +1,243 @@
+/*
+ * 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 <vector>
+
+#include <input/Input.h>
+#include <input/InputDevice.h>
+#include <input/TouchVideoFrame.h>
+
+namespace android {
+
+/* Describes a change in any of the connected input devices. */
+struct NotifyInputDevicesChangedArgs {
+ int32_t id;
+ std::vector<InputDeviceInfo> inputDeviceInfos;
+
+ inline NotifyInputDevicesChangedArgs() {}
+
+ NotifyInputDevicesChangedArgs(int32_t id, std::vector<InputDeviceInfo> 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;
+
+ // Vectors 'pointerProperties' and 'pointerCoords' must always have the same number of elements
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
+ 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<TouchVideoFrame> 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<TouchVideoFrame>& videoFrames);
+
+ NotifyMotionArgs(const NotifyMotionArgs& other) = default;
+ NotifyMotionArgs& operator=(const android::NotifyMotionArgs&) = default;
+
+ bool operator==(const NotifyMotionArgs& rhs) const;
+
+ inline size_t getPointerCount() const { return pointerProperties.size(); }
+
+ 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<float> 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<float> 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<NotifyInputDevicesChangedArgs, NotifyConfigurationChangedArgs, NotifyKeyArgs,
+ NotifyMotionArgs, NotifySensorArgs, NotifySwitchArgs, NotifyDeviceResetArgs,
+ NotifyPointerCaptureChangedArgs, NotifyVibratorStateArgs>;
+
+const char* toString(const NotifyArgs& args);
+
+} // namespace android
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
new file mode 100644
index 0000000000..e4363a416c
--- /dev/null
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -0,0 +1,236 @@
+/*
+ * 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 <NotifyArgs.h>
+#include <android/input.h>
+#include <attestation/HmacKeyManager.h>
+#include <gui/constants.h>
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <utils/Timers.h> // for nsecs_t, systemTime
+
+#include <vector>
+
+namespace android {
+
+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> pointerProperties;
+ std::vector<PointerCoords> 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();
+ }
+
+ if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+ addFlag(AMOTION_EVENT_FLAG_CANCELED);
+ }
+
+ return {InputEvent::nextId(),
+ mEventTime,
+ /*readTime=*/mEventTime,
+ mDeviceId,
+ mSource,
+ mDisplayId,
+ mPolicyFlags,
+ mAction,
+ mActionButton,
+ mFlags,
+ AMETA_NONE,
+ mButtonState,
+ mClassification,
+ /*edgeFlags=*/0,
+ static_cast<uint32_t>(mPointers.size()),
+ pointerProperties.data(),
+ pointerCoords.data(),
+ /*xPrecision=*/0,
+ /*yPrecision=*/0,
+ mRawXCursorPosition,
+ mRawYCursorPosition,
+ mDownTime,
+ /*videoFrames=*/{}};
+ }
+
+private:
+ int32_t mAction;
+ int32_t mDeviceId{DEFAULT_DEVICE_ID};
+ uint32_t mSource;
+ nsecs_t mDownTime;
+ nsecs_t mEventTime;
+ int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+ uint32_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};
+
+ std::vector<PointerBuilder> mPointers;
+};
+
+class KeyArgsBuilder {
+public:
+ KeyArgsBuilder(int32_t action, int32_t source) {
+ mAction = action;
+ mSource = source;
+ mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDownTime = mEventTime;
+ }
+
+ KeyArgsBuilder& deviceId(int32_t deviceId) {
+ mDeviceId = deviceId;
+ return *this;
+ }
+
+ KeyArgsBuilder& downTime(nsecs_t downTime) {
+ mDownTime = downTime;
+ return *this;
+ }
+
+ KeyArgsBuilder& eventTime(nsecs_t eventTime) {
+ mEventTime = eventTime;
+ return *this;
+ }
+
+ KeyArgsBuilder& displayId(int32_t displayId) {
+ mDisplayId = displayId;
+ return *this;
+ }
+
+ KeyArgsBuilder& policyFlags(int32_t policyFlags) {
+ mPolicyFlags = policyFlags;
+ return *this;
+ }
+
+ KeyArgsBuilder& addFlag(uint32_t flags) {
+ mFlags |= flags;
+ return *this;
+ }
+
+ KeyArgsBuilder& keyCode(int32_t keyCode) {
+ mKeyCode = keyCode;
+ return *this;
+ }
+
+ NotifyKeyArgs build() const {
+ return {InputEvent::nextId(),
+ mEventTime,
+ /*readTime=*/mEventTime,
+ mDeviceId,
+ mSource,
+ mDisplayId,
+ mPolicyFlags,
+ mAction,
+ mFlags,
+ mKeyCode,
+ mScanCode,
+ mMetaState,
+ mDownTime};
+ }
+
+private:
+ int32_t mAction;
+ int32_t mDeviceId = DEFAULT_DEVICE_ID;
+ uint32_t mSource;
+ nsecs_t mDownTime;
+ nsecs_t mEventTime;
+ int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+ uint32_t mPolicyFlags = DEFAULT_POLICY_FLAGS;
+ int32_t mFlags{0};
+ int32_t mKeyCode{AKEYCODE_UNKNOWN};
+ int32_t mScanCode{0};
+ int32_t mMetaState{AMETA_NONE};
+};
+
+} // namespace android
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
new file mode 100644
index 0000000000..8b47b555e5
--- /dev/null
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -0,0 +1,53 @@
+/*
+ * 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 "PointerControllerInterface.h"
+
+namespace android {
+
+/**
+ * The PointerChoreographer policy interface.
+ *
+ * This is the interface that PointerChoreographer uses to talk to Window Manager and other
+ * system components.
+ */
+class PointerChoreographerPolicyInterface {
+public:
+ virtual ~PointerChoreographerPolicyInterface() = default;
+
+ /**
+ * A factory method for PointerController. The PointerController implementation has
+ * dependencies on a graphical library - libgui, used to draw icons on the screen - which
+ * isn't available for the host. Since we want libinputflinger and its test to be buildable
+ * for and runnable on the host, the PointerController implementation must be in a separate
+ * library, libinputservice, that has the additional dependencies. The PointerController
+ * will be mocked when testing PointerChoreographer.
+ */
+ virtual std::shared_ptr<PointerControllerInterface> createPointerController(
+ PointerControllerInterface::ControllerType type) = 0;
+
+ /**
+ * Notifies the policy that the default pointer displayId has changed. PointerChoreographer is
+ * the single source of truth for all pointers on screen.
+ * @param displayId The updated display on which the mouse cursor is shown
+ * @param position The new position of the mouse cursor on the logical display
+ */
+ virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
+};
+
+} // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index db4228d862..c44486fc01 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 <input/DisplayViewport.h>
#include <input/Input.h>
@@ -23,6 +22,22 @@
namespace android {
+struct SpriteIcon;
+
+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 <typename T, typename U>
+ operator std::tuple<T, U>() {
+ return {x, y};
+ }
+};
+
/**
* Interface for tracking a mouse / touch pad pointer and touch pad spots.
*
@@ -35,29 +50,42 @@ namespace android {
*/
class PointerControllerInterface {
protected:
- PointerControllerInterface() { }
- virtual ~PointerControllerInterface() { }
+ PointerControllerInterface() {}
+ virtual ~PointerControllerInterface() {}
public:
+ /**
+ * Enum used to differentiate various types of PointerControllers for the transition to
+ * using PointerChoreographer.
+ *
+ * TODO(b/293587049): Refactor the PointerController class into different controller types.
+ */
+ enum class ControllerType {
+ // The PointerController that is responsible for drawing all icons.
+ LEGACY,
+ // Represents a single mouse pointer.
+ MOUSE,
+ // Represents multiple touch spots.
+ TOUCH,
+ // Represents a single stylus pointer.
+ STYLUS,
+ };
+
+ /* Dumps the state of the pointer controller. */
+ virtual std::string dump() = 0;
+
/* 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<FloatRect> 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 +108,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. */
@@ -95,7 +127,7 @@ public:
* pressed (not hovering).
*/
virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex,
- BitSet32 spotIdBits, int32_t displayId) = 0;
+ BitSet32 spotIdBits, int32_t displayId) = 0;
/* Removes all spots. */
virtual void clearSpots() = 0;
@@ -105,8 +137,12 @@ public:
/* Sets the associated display of this pointer. Pointer should show on that display. */
virtual void setDisplayViewport(const DisplayViewport& displayViewport) = 0;
+
+ /* Sets the pointer icon type for mice or styluses. */
+ virtual void updatePointerIcon(PointerIconStyle iconId) = 0;
+
+ /* Sets the custom pointer icon for mice or styluses. */
+ virtual void setCustomPointerIcon(const SpriteIcon& icon) = 0;
};
} // namespace android
-
-#endif // _INPUTFLINGER_POINTER_CONTROLLER_INTERFACE_H
diff --git a/services/inputflinger/include/UnwantedInteractionBlockerInterface.h b/services/inputflinger/include/UnwantedInteractionBlockerInterface.h
index 1a6f8472a5..64c6114ceb 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<InputDeviceInfo>& 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 736041e7fd..04c2e394c8 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 <array>
#include <chrono>
@@ -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 01146a3c8b..f954370928 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",
@@ -50,11 +52,24 @@ filegroup {
"mapper/RotaryEncoderInputMapper.cpp",
"mapper/SensorInputMapper.cpp",
"mapper/SingleTouchInputMapper.cpp",
+ "mapper/SlopController.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/HardwareProperties.cpp",
+ "mapper/gestures/HardwareStateConverter.cpp",
+ "mapper/gestures/PropertyProvider.cpp",
+ "mapper/gestures/TimerProvider.cpp",
],
}
@@ -66,24 +81,55 @@ cc_defaults {
"libcap",
"libcrypto",
"libcutils",
+ "libjsoncpp",
"libinput",
"liblog",
+ "libPlatformProperties",
"libstatslog",
- "libui",
+ "libstatspull",
"libutils",
- "libPlatformProperties",
],
static_libs: [
"libc++fs",
+ "libchrome-gestures",
+ "libui-types",
],
header_libs: [
"libbatteryservice_headers",
+ "libchrome-gestures_headers",
"libinputreader_headers",
],
+ target: {
+ host: {
+ static_libs: [
+ "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 +141,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 20baa42a20..f7bbc511f9 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -19,6 +19,7 @@
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
+#include <linux/ioctl.h>
#include <memory.h>
#include <stdint.h>
#include <stdio.h>
@@ -28,7 +29,6 @@
#include <sys/epoll.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
-#include <sys/limits.h>
#include <sys/stat.h>
#include <sys/sysmacros.h>
#include <unistd.h>
@@ -43,6 +43,7 @@
#include <ftl/enum.h>
#include <input/KeyCharacterMap.h>
#include <input/KeyLayoutMap.h>
+#include <input/PrintTools.h>
#include <input/VirtualKeyMap.h>
#include <openssl/sha.h>
#include <statslog.h>
@@ -51,10 +52,14 @@
#include <utils/Timers.h>
#include <filesystem>
+#include <optional>
#include <regex>
+#include <utility>
#include "EventHub.h"
+#include "KeyCodeClassifications.h"
+
#define INDENT " "
#define INDENT2 " "
#define INDENT3 " "
@@ -74,6 +79,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<std::string, InputBatteryClass> BATTERY_CLASSES =
@@ -115,7 +122,8 @@ static const std::unordered_map<std::string, InputLightClass> 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 +137,45 @@ const std::unordered_map<std::string, LightColor> 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<std::int32_t, RawLayoutInfo> 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;
@@ -171,7 +215,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 +231,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<std::filesystem::path> getSysfsRootPath(const char* devicePath) {
std::error_code errorCode;
@@ -301,6 +344,118 @@ static std::optional<std::array<LightColor, COLOR_NUM>> getColorIndexArray(
return colors;
}
+/**
+ * Read country code information exposed through the sysfs path and convert it to Layout info.
+ */
+static std::optional<RawLayoutInfo> 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<int32_t /*batteryId*/, RawBatteryInfo> readBatteryConfiguration(
+ const std::filesystem::path& sysfsRootPath) {
+ std::unordered_map<int32_t, RawBatteryInfo> 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<int32_t /*lightId*/, RawLightInfo> readLightsConfiguration(
+ const std::filesystem::path& sysfsRootPath) {
+ std::unordered_map<int32_t, RawLightInfo> 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 <name>:<color>:<function>
+ // 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 <name> at index 1. For RawLightInfo
+ // we only care about sections <color> and <function> 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 <function> 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<InputDeviceClass> getAbsAxisUsage(int32_t axis,
@@ -355,23 +510,36 @@ ftl::Flags<InputDeviceClass> 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<const AssociatedDevice> 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) {}
+ isVirtual(fd < 0),
+ currentFrameDropped(false) {}
EventHub::Device::~Device() {
close();
@@ -445,6 +613,45 @@ void EventHub::Device::configureFd() {
}
bool usingClockIoctl = !ioctl(fd, EVIOCSCLOCKID, &clockId);
ALOGI("usingClockIoctl=%s", toString(usingClockIoctl));
+
+ // Query the initial state of keys and switches, which is tracked by EventHub.
+ readDeviceState();
+}
+
+void EventHub::Device::readDeviceState() {
+ if (readDeviceBitMask(EVIOCGKEY(0), keyState) < 0) {
+ ALOGD("Unable to query the global key state for %s: %s", path.c_str(), strerror(errno));
+ }
+ if (readDeviceBitMask(EVIOCGSW(0), swState) < 0) {
+ ALOGD("Unable to query the global switch state for %s: %s", path.c_str(), strerror(errno));
+ }
+
+ // Read absolute axis info and values for all available axes for the device.
+ populateAbsoluteAxisStates();
+}
+
+void EventHub::Device::populateAbsoluteAxisStates() {
+ absState.clear();
+
+ for (int axis = 0; axis <= ABS_MAX; axis++) {
+ if (!absBitmask.test(axis)) {
+ continue;
+ }
+ struct input_absinfo info {};
+ if (ioctl(fd, EVIOCGABS(axis), &info)) {
+ ALOGE("Error reading absolute controller %d for device %s fd %d: %s", axis,
+ identifier.name.c_str(), fd, strerror(errno));
+ continue;
+ }
+ auto& [axisInfo, value] = absState[axis];
+ axisInfo.valid = true;
+ axisInfo.minValue = info.minimum;
+ axisInfo.maxValue = info.maximum;
+ axisInfo.flat = info.flat;
+ axisInfo.fuzz = info.fuzz;
+ axisInfo.resolution = info.resolution;
+ value = info.value;
+ }
}
bool EventHub::Device::hasKeycodeLocked(int keycode) const {
@@ -461,6 +668,11 @@ bool EventHub::Device::hasKeycodeLocked(int keycode) const {
}
}
+ std::vector<int32_t> usageCodes = keyMap.keyLayoutMap->findUsageCodesForKey(keycode);
+ if (usageCodes.size() > 0 && mscBitmask.test(MSC_SCAN)) {
+ return true;
+ }
+
return false;
}
@@ -501,9 +713,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<bool> isInternal = configuration->getBool("device.internal");
+ if (isInternal.has_value()) {
+ return !isInternal.value();
}
}
return identifier.bus == BUS_USB || identifier.bus == BUS_BLUETOOTH;
@@ -511,9 +723,9 @@ bool EventHub::Device::isExternalDeviceLocked() {
bool EventHub::Device::deviceHasMicLocked() {
if (configuration) {
- bool value;
- if (configuration->tryGetProperty(String8("audio.mic"), value)) {
- return value;
+ std::optional<bool> hasMic = configuration->getBool("audio.mic");
+ if (hasMic.has_value()) {
+ return hasMic.value();
}
}
return false;
@@ -523,8 +735,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;
@@ -557,73 +769,64 @@ 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;
- }
+void EventHub::Device::trackInputEvent(const struct input_event& event) {
+ switch (event.type) {
+ case EV_KEY: {
+ LOG_ALWAYS_FATAL_IF(!currentFrameDropped &&
+ !keyState.set(static_cast<size_t>(event.code),
+ event.value != 0),
+ "%s: device '%s' received invalid EV_KEY event code: %s value: %d",
+ __func__, identifier.name.c_str(),
+ InputEventLookup::getLinuxEvdevLabel(EV_KEY, event.code, 1)
+ .code.c_str(),
+ event.value);
+ break;
}
- 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;
+ case EV_SW: {
+ LOG_ALWAYS_FATAL_IF(!currentFrameDropped &&
+ !swState.set(static_cast<size_t>(event.code),
+ event.value != 0),
+ "%s: device '%s' received invalid EV_SW event code: %s value: %d",
+ __func__, identifier.name.c_str(),
+ InputEventLookup::getLinuxEvdevLabel(EV_SW, event.code, 1)
+ .code.c_str(),
+ event.value);
+ break;
+ }
+ case EV_ABS: {
+ if (currentFrameDropped) {
+ break;
}
+ auto it = absState.find(event.code);
+ LOG_ALWAYS_FATAL_IF(it == absState.end(),
+ "%s: device '%s' received invalid EV_ABS event code: %s value: %d",
+ __func__, identifier.name.c_str(),
+ InputEventLookup::getLinuxEvdevLabel(EV_ABS, event.code, 0)
+ .code.c_str(),
+ event.value);
+ it->second.value = event.value;
+ break;
}
- // 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);
- }
- }
+ case EV_SYN: {
+ switch (event.code) {
+ case SYN_REPORT:
+ currentFrameDropped = false;
+ break;
+ case SYN_DROPPED:
+ // When we receive SYN_DROPPED, all events in the current frame should be
+ // dropped. We query the state of the device to synchronize our device state
+ // with the kernel's to account for the dropped events.
+ currentFrameDropped = true;
+ readDeviceState();
+ break;
+ default:
+ break;
}
+ break;
}
- lightInfos.insert_or_assign(info.id, info);
- ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str());
+ default:
+ break;
}
- return !lightInfos.empty();
}
/**
@@ -687,6 +890,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;
@@ -778,44 +982,35 @@ 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<PropertyMap> 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,
RawAbsoluteAxisInfo* outAxisInfo) const {
outAxisInfo->clear();
-
- if (axis >= 0 && axis <= ABS_MAX) {
- std::scoped_lock _l(mLock);
-
- Device* device = getDeviceLocked(deviceId);
- if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
- struct input_absinfo info;
- if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
- ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
- device->identifier.name.c_str(), device->fd, errno);
- return -errno;
- }
-
- if (info.minimum != info.maximum) {
- outAxisInfo->valid = true;
- outAxisInfo->minValue = info.minimum;
- outAxisInfo->maxValue = info.maximum;
- outAxisInfo->flat = info.flat;
- outAxisInfo->fuzz = info.fuzz;
- outAxisInfo->resolution = info.resolution;
- }
- return OK;
- }
+ if (axis < 0 || axis > ABS_MAX) {
+ return NAME_NOT_FOUND;
+ }
+ std::scoped_lock _l(mLock);
+ const Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr) {
+ return NAME_NOT_FOUND;
+ }
+ // We can read the RawAbsoluteAxisInfo even if the device is disabled and doesn't have a valid
+ // fd, because the info is populated once when the device is first opened, and it doesn't change
+ // throughout the device lifecycle.
+ auto it = device->absState.find(axis);
+ if (it == device->absState.end()) {
+ return NAME_NOT_FOUND;
}
- return -1;
+ *outAxisInfo = it->second.info;
+ return OK;
}
bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
@@ -846,38 +1041,34 @@ bool EventHub::hasMscEvent(int32_t deviceId, int mscEvent) const {
}
int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
- if (scanCode >= 0 && scanCode <= KEY_MAX) {
- std::scoped_lock _l(mLock);
-
- Device* device = getDeviceLocked(deviceId);
- if (device != nullptr && device->hasValidFd() && device->keyBitmask.test(scanCode)) {
- if (device->readDeviceBitMask(EVIOCGKEY(0), device->keyState) >= 0) {
- return device->keyState.test(scanCode) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
- }
- }
+ if (scanCode < 0 || scanCode > KEY_MAX) {
+ return AKEY_STATE_UNKNOWN;
}
- return AKEY_STATE_UNKNOWN;
+ std::scoped_lock _l(mLock);
+ const Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr || !device->hasValidFd() || !device->keyBitmask.test(scanCode)) {
+ return AKEY_STATE_UNKNOWN;
+ }
+ return device->keyState.test(scanCode) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
}
int32_t EventHub::getKeyCodeState(int32_t deviceId, int32_t keyCode) const {
std::scoped_lock _l(mLock);
-
- Device* device = getDeviceLocked(deviceId);
- if (device != nullptr && device->hasValidFd() && device->keyMap.haveKeyLayout()) {
- std::vector<int32_t> scanCodes = device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode);
- if (scanCodes.size() != 0) {
- if (device->readDeviceBitMask(EVIOCGKEY(0), device->keyState) >= 0) {
- for (size_t i = 0; i < scanCodes.size(); i++) {
- int32_t sc = scanCodes[i];
- if (sc >= 0 && sc <= KEY_MAX && device->keyState.test(sc)) {
- return AKEY_STATE_DOWN;
- }
- }
- return AKEY_STATE_UP;
- }
- }
+ const Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr || !device->hasValidFd() || !device->keyMap.haveKeyLayout()) {
+ return AKEY_STATE_UNKNOWN;
}
- return AKEY_STATE_UNKNOWN;
+ const std::vector<int32_t> scanCodes =
+ device->keyMap.keyLayoutMap->findScanCodesForKey(keyCode);
+ if (scanCodes.empty()) {
+ return AKEY_STATE_UNKNOWN;
+ }
+ return std::any_of(scanCodes.begin(), scanCodes.end(),
+ [&device](const int32_t sc) {
+ return sc >= 0 && sc <= KEY_MAX && device->keyState.test(sc);
+ })
+ ? AKEY_STATE_DOWN
+ : AKEY_STATE_UP;
}
int32_t EventHub::getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const {
@@ -902,73 +1093,63 @@ 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 {
- if (sw >= 0 && sw <= SW_MAX) {
- std::scoped_lock _l(mLock);
-
- Device* device = getDeviceLocked(deviceId);
- if (device != nullptr && device->hasValidFd() && device->swBitmask.test(sw)) {
- if (device->readDeviceBitMask(EVIOCGSW(0), device->swState) >= 0) {
- return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
- }
- }
+ if (sw < 0 || sw > SW_MAX) {
+ return AKEY_STATE_UNKNOWN;
+ }
+ std::scoped_lock _l(mLock);
+ const Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr || !device->hasValidFd() || !device->swBitmask.test(sw)) {
+ return AKEY_STATE_UNKNOWN;
}
- return AKEY_STATE_UNKNOWN;
+ return device->swState.test(sw) ? AKEY_STATE_DOWN : AKEY_STATE_UP;
}
status_t EventHub::getAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t* outValue) const {
*outValue = 0;
-
- if (axis >= 0 && axis <= ABS_MAX) {
- std::scoped_lock _l(mLock);
-
- Device* device = getDeviceLocked(deviceId);
- if (device != nullptr && device->hasValidFd() && device->absBitmask.test(axis)) {
- struct input_absinfo info;
- if (ioctl(device->fd, EVIOCGABS(axis), &info)) {
- ALOGW("Error reading absolute controller %d for device %s fd %d, errno=%d", axis,
- device->identifier.name.c_str(), device->fd, errno);
- return -errno;
- }
-
- *outValue = info.value;
- return OK;
- }
+ if (axis < 0 || axis > ABS_MAX) {
+ return NAME_NOT_FOUND;
+ }
+ std::scoped_lock _l(mLock);
+ const Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr || !device->hasValidFd()) {
+ return NAME_NOT_FOUND;
}
- return -1;
+ const auto it = device->absState.find(axis);
+ if (it == device->absState.end()) {
+ return NAME_NOT_FOUND;
+ }
+ *outValue = it->second.value;
+ return OK;
}
-bool EventHub::markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
+bool EventHub::markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& 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<int32_t> 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;
@@ -976,6 +1157,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<KeyCharacterMap> 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);
@@ -1001,7 +1194,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;
}
@@ -1033,7 +1232,7 @@ status_t EventHub::mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxis
}
base::Result<std::pair<InputDeviceSensorType, int32_t>> EventHub::mapSensor(int32_t deviceId,
- int32_t absCode) {
+ int32_t absCode) const {
std::scoped_lock _l(mLock);
Device* device = getDeviceLocked(deviceId);
@@ -1055,18 +1254,19 @@ const std::unordered_map<int32_t, RawBatteryInfo>& EventHub::getBatteryInfoLocke
return device->associatedDevice->batteryInfos;
}
-const std::vector<int32_t> EventHub::getRawBatteryIds(int32_t deviceId) {
+std::vector<int32_t> EventHub::getRawBatteryIds(int32_t deviceId) const {
std::scoped_lock _l(mLock);
std::vector<int32_t> batteryIds;
- for (const auto [id, info] : getBatteryInfoLocked(deviceId)) {
+ for (const auto& [id, info] : getBatteryInfoLocked(deviceId)) {
batteryIds.push_back(id);
}
return batteryIds;
}
-std::optional<RawBatteryInfo> EventHub::getRawBatteryInfo(int32_t deviceId, int32_t batteryId) {
+std::optional<RawBatteryInfo> EventHub::getRawBatteryInfo(int32_t deviceId,
+ int32_t batteryId) const {
std::scoped_lock _l(mLock);
const auto infos = getBatteryInfoLocked(deviceId);
@@ -1080,7 +1280,7 @@ std::optional<RawBatteryInfo> 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<int32_t, RawLightInfo>& EventHub::getLightInfoLocked(
int32_t deviceId) const {
static const std::unordered_map<int32_t, RawLightInfo> EMPTY_LIGHT_INFO = {};
@@ -1091,18 +1291,18 @@ const std::unordered_map<int32_t, RawLightInfo>& EventHub::getLightInfoLocked(
return device->associatedDevice->lightInfos;
}
-const std::vector<int32_t> EventHub::getRawLightIds(int32_t deviceId) {
+std::vector<int32_t> EventHub::getRawLightIds(int32_t deviceId) const {
std::scoped_lock _l(mLock);
std::vector<int32_t> lightIds;
- for (const auto [id, info] : getLightInfoLocked(deviceId)) {
+ for (const auto& [id, info] : getLightInfoLocked(deviceId)) {
lightIds.push_back(id);
}
return lightIds;
}
-std::optional<RawLightInfo> EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) {
+std::optional<RawLightInfo> EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) const {
std::scoped_lock _l(mLock);
const auto infos = getLightInfoLocked(deviceId);
@@ -1115,7 +1315,7 @@ std::optional<RawLightInfo> EventHub::getRawLightInfo(int32_t deviceId, int32_t
return std::nullopt;
}
-std::optional<int32_t> EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) {
+std::optional<int32_t> EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) const {
std::scoped_lock _l(mLock);
const auto infos = getLightInfoLocked(deviceId);
@@ -1132,7 +1332,7 @@ std::optional<int32_t> EventHub::getLightBrightness(int32_t deviceId, int32_t li
}
std::optional<std::unordered_map<LightColor, int32_t>> 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);
@@ -1228,6 +1428,15 @@ void EventHub::setLightIntensities(int32_t deviceId, int32_t lightId,
}
}
+std::optional<RawLayoutInfo> 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<std::string>& devices) {
std::scoped_lock _l(mLock);
@@ -1292,12 +1501,17 @@ const std::shared_ptr<KeyCharacterMap> 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<KeyCharacterMap> 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;
}
@@ -1357,6 +1571,53 @@ void EventHub::assignDescriptorLocked(InputDeviceIdentifier& identifier) {
identifier.descriptor.c_str());
}
+std::shared_ptr<const EventHub::AssociatedDevice> EventHub::obtainAssociatedDeviceLocked(
+ const std::filesystem::path& devicePath) const {
+ const std::optional<std::filesystem::path> sysfsRootPathOpt =
+ getSysfsRootPath(devicePath.c_str());
+ if (!sysfsRootPathOpt) {
+ return nullptr;
+ }
+
+ const auto& path = *sysfsRootPathOpt;
+
+ std::shared_ptr<const AssociatedDevice> associatedDevice = std::make_shared<AssociatedDevice>(
+ 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<int32_t, RawBatteryInfo> newBatteryInfos =
+ readBatteryConfiguration(sysfsRootPath);
+ std::unordered_map<int32_t, RawLightInfo> newLightInfos =
+ readLightsConfiguration(sysfsRootPath);
+ std::optional<RawLayoutInfo> 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);
@@ -1378,8 +1639,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;
@@ -1400,8 +1661,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;
@@ -1414,7 +1675,7 @@ void EventHub::cancelVibrate(int32_t deviceId) {
}
}
-std::vector<int32_t> EventHub::getVibratorIds(int32_t deviceId) {
+std::vector<int32_t> EventHub::getVibratorIds(int32_t deviceId) const {
std::scoped_lock _l(mLock);
std::vector<int32_t> vibrators;
Device* device = getDeviceLocked(deviceId);
@@ -1495,7 +1756,7 @@ std::optional<int32_t> 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;
@@ -1536,7 +1797,7 @@ std::optional<int32_t> 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;
@@ -1561,15 +1822,12 @@ std::optional<int32_t> 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<RawEvent> EventHub::getEvents(int timeoutMillis) {
std::scoped_lock _l(mLock);
- struct input_event readBuffer[bufferSize];
+ std::array<input_event, EVENT_BUFFER_SIZE> readBuffer;
- RawEvent* event = buffer;
- size_t capacity = bufferSize;
+ std::vector<RawEvent> events;
bool awoken = false;
for (;;) {
nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
@@ -1589,15 +1847,17 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz
for (auto it = mClosingDevices.begin(); it != mClosingDevices.end();) {
std::unique_ptr<Device> 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;
}
}
@@ -1612,10 +1872,12 @@ size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSiz
std::unique_ptr<Device> 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();
@@ -1633,17 +1895,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;
}
}
@@ -1707,12 +1970,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) {
@@ -1722,21 +1986,22 @@ 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;
+ device->trackInputEvent(iev);
+ 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;
@@ -1759,7 +2024,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;
}
@@ -1769,7 +2037,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;
}
@@ -1815,7 +2083,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<TouchVideoFrame> EventHub::getVideoFrames(int32_t deviceId) {
@@ -1869,15 +2137,6 @@ void EventHub::scanDevicesLocked() {
// ----------------------------------------------------------------------------
-static const int32_t GAMEPAD_KEYCODES[] = {
- AKEYCODE_BUTTON_A, AKEYCODE_BUTTON_B, AKEYCODE_BUTTON_C, //
- AKEYCODE_BUTTON_X, AKEYCODE_BUTTON_Y, AKEYCODE_BUTTON_Z, //
- AKEYCODE_BUTTON_L1, AKEYCODE_BUTTON_R1, //
- AKEYCODE_BUTTON_L2, AKEYCODE_BUTTON_R2, //
- AKEYCODE_BUTTON_THUMBL, AKEYCODE_BUTTON_THUMBR, //
- AKEYCODE_BUTTON_START, AKEYCODE_BUTTON_SELECT, AKEYCODE_BUTTON_MODE, //
-};
-
status_t EventHub::registerFdForEpoll(int fd) {
// TODO(b/121395353) - consider adding EPOLLRDHUP
struct epoll_event eventItem = {};
@@ -2037,12 +2296,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> device = std::make_unique<Device>(fd, deviceId, devicePath, identifier);
+ std::unique_ptr<Device> device =
+ std::make_unique<Device>(fd, deviceId, devicePath, identifier,
+ obtainAssociatedDeviceLocked(devicePath));
ALOGV("add device %d: %s\n", deviceId, devicePath.c_str());
ALOGV(" bus: %04x\n"
@@ -2060,27 +2332,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<std::filesystem::path> sysfsRootPath = getSysfsRootPath(devicePath.c_str());
- if (sysfsRootPath.has_value()) {
- std::shared_ptr<AssociatedDevice> 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<AssociatedDevice>(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);
@@ -2091,13 +2342,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;
}
@@ -2107,12 +2360,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;
}
}
@@ -2124,19 +2378,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.
@@ -2205,21 +2459,24 @@ void EventHub::openDeviceLocked(const std::string& devicePath) {
device->classes |= InputDeviceClass::ALPHAKEY;
}
- // See if this device has a DPAD.
- if (device->hasKeycodeLocked(AKEYCODE_DPAD_UP) &&
- device->hasKeycodeLocked(AKEYCODE_DPAD_DOWN) &&
- device->hasKeycodeLocked(AKEYCODE_DPAD_LEFT) &&
- device->hasKeycodeLocked(AKEYCODE_DPAD_RIGHT) &&
- device->hasKeycodeLocked(AKEYCODE_DPAD_CENTER)) {
+ // See if this device has a D-pad.
+ if (std::all_of(DPAD_REQUIRED_KEYCODES.begin(), DPAD_REQUIRED_KEYCODES.end(),
+ [&](int32_t keycode) { return device->hasKeycodeLocked(keycode); })) {
device->classes |= InputDeviceClass::DPAD;
}
// See if this device has a gamepad.
- for (size_t i = 0; i < sizeof(GAMEPAD_KEYCODES) / sizeof(GAMEPAD_KEYCODES[0]); i++) {
- if (device->hasKeycodeLocked(GAMEPAD_KEYCODES[i])) {
- device->classes |= InputDeviceClass::GAMEPAD;
- break;
- }
+ if (std::any_of(GAMEPAD_KEYCODES.begin(), GAMEPAD_KEYCODES.end(),
+ [&](int32_t keycode) { return device->hasKeycodeLocked(keycode); })) {
+ device->classes |= InputDeviceClass::GAMEPAD;
+ }
+
+ // 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) &&
+ !device->classes.any(InputDeviceClass::ALPHAKEY) &&
+ std::any_of(STYLUS_BUTTON_KEYCODES.begin(), STYLUS_BUTTON_KEYCODES.end(),
+ [&](int32_t keycode) { return device->hasKeycodeLocked(keycode); })) {
+ device->classes |= InputDeviceClass::EXTERNAL_STYLUS;
}
}
@@ -2231,12 +2488,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;
}
@@ -2304,7 +2561,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) {
@@ -2351,6 +2608,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>& 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<Device*> 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";
@@ -2359,7 +2652,7 @@ void EventHub::createVirtualKeyboardLocked() {
std::unique_ptr<Device> device =
std::make_unique<Device>(-1, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, "<virtual>",
- identifier);
+ identifier, /*associatedDevice=*/nullptr);
device->classes = InputDeviceClass::KEYBOARD | InputDeviceClass::ALPHAKEY |
InputDeviceClass::DPAD | InputDeviceClass::VIRTUAL;
device->loadKeyMapLocked();
@@ -2449,53 +2742,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<void> EventHub::readNotifyLocked() {
+ static constexpr auto EVENT_SIZE = static_cast<ssize_t>(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) {
@@ -2525,7 +2821,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
@@ -2551,20 +2847,53 @@ 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 += "<none>\n";
+ dump += StringPrintf(INDENT3 "VideoDevice: %s\n",
+ device->videoDevice ? device->videoDevice->dump().c_str()
+ : "<none>");
+ dump += StringPrintf(INDENT3 "SysfsDevicePath: %s\n",
+ device->associatedDevice
+ ? device->associatedDevice->sysfsRootPath.c_str()
+ : "<none>");
+ if (device->keyBitmask.any(0, KEY_MAX + 1)) {
+ const auto pressedKeys = device->keyState.dumpSetIndices(", ", [](int i) {
+ return InputEventLookup::getLinuxEvdevLabel(EV_KEY, i, 1).code;
+ });
+ dump += StringPrintf(INDENT3 "KeyState (pressed): %s\n", pressedKeys.c_str());
+ }
+ if (device->swBitmask.any(0, SW_MAX + 1)) {
+ const auto pressedSwitches = device->swState.dumpSetIndices(", ", [](int i) {
+ return InputEventLookup::getLinuxEvdevLabel(EV_SW, i, 1).code;
+ });
+ dump += StringPrintf(INDENT3 "SwState (pressed): %s\n", pressedSwitches.c_str());
+ }
+ if (!device->absState.empty()) {
+ std::string axisValues;
+ for (const auto& [axis, state] : device->absState) {
+ if (!axisValues.empty()) {
+ axisValues += ", ";
+ }
+ axisValues += StringPrintf("%s=%d",
+ InputEventLookup::getLinuxEvdevLabel(EV_ABS, axis, 0)
+ .code.c_str(),
+ state.value);
+ }
+ dump += INDENT3 "AbsState: " + axisValues + "\n";
}
}
@@ -2578,9 +2907,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<std::mutex> 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 3f26aaaebe..fb32f96482 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -20,6 +20,7 @@
#include <algorithm>
+#include <android/sysprop/InputProperties.sysprop.h>
#include <ftl/flags.h>
#include "CursorInputMapper.h"
@@ -33,6 +34,7 @@
#include "SensorInputMapper.h"
#include "SingleTouchInputMapper.h"
#include "SwitchInputMapper.h"
+#include "TouchpadInputMapper.h"
#include "VibratorInputMapper.h"
namespace android {
@@ -46,6 +48,7 @@ InputDevice::InputDevice(InputReaderContext* context, int32_t id, int32_t genera
mIdentifier(identifier),
mClasses(0),
mSources(0),
+ mIsWaking(false),
mIsExternal(false),
mHasMic(false),
mDropUntilNextSync(false) {}
@@ -63,30 +66,47 @@ bool InputDevice::isEnabled() {
return enabled;
}
-void InputDevice::setEnabled(bool enabled, nsecs_t when) {
- if (enabled && mAssociatedDisplayPort && !mAssociatedViewport) {
- ALOGW("Cannot enable input device %s because it is associated with port %" PRIu8 ", "
- "but the corresponding viewport is not found",
- getName().c_str(), *mAssociatedDisplayPort);
- enabled = false;
+std::list<NotifyArgs> InputDevice::updateEnableState(nsecs_t when,
+ const InputReaderConfiguration& readerConfig,
+ bool forceEnable) {
+ bool enable = forceEnable;
+ if (!forceEnable) {
+ // If the device was explicitly disabled by the user, it would be present in the
+ // "disabledDevices" list. This device should be disabled.
+ enable = readerConfig.disabledDevices.find(mId) == readerConfig.disabledDevices.end();
+
+ // If a device is associated with a specific display but there is no
+ // associated DisplayViewport, don't enable the device.
+ if (enable && (mAssociatedDisplayPort || mAssociatedDisplayUniqueId) &&
+ !mAssociatedViewport) {
+ const std::string desc = mAssociatedDisplayPort
+ ? "port " + std::to_string(*mAssociatedDisplayPort)
+ : "uniqueId " + *mAssociatedDisplayUniqueId;
+ ALOGW("Cannot enable input device %s because it is associated "
+ "with %s, but the corresponding viewport is not found",
+ getName().c_str(), desc.c_str());
+ enable = false;
+ }
}
- if (isEnabled() == enabled) {
- return;
+ std::list<NotifyArgs> out;
+ if (isEnabled() == enable) {
+ return out;
}
// When resetting some devices, the driver needs to be queried to ensure that a proper reset is
// performed. The querying must happen when the device is enabled, so we reset after enabling
// but before disabling the device. See MultiTouchMotionAccumulator::reset for more information.
- if (enabled) {
+ if (enable) {
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) {
@@ -97,6 +117,7 @@ void InputDevice::dump(std::string& dump, const std::string& eventHubDevStr) {
dump += StringPrintf(INDENT "%s", eventHubDevStr.c_str());
dump += StringPrintf(INDENT2 "Generation: %d\n", mGeneration);
dump += StringPrintf(INDENT2 "IsExternal: %s\n", toString(mIsExternal));
+ dump += StringPrintf(INDENT2 "IsWaking: %s\n", toString(mIsWaking));
dump += StringPrintf(INDENT2 "AssociatedDisplayPort: ");
if (mAssociatedDisplayPort) {
dump += StringPrintf("%" PRIu8 "\n", *mAssociatedDisplayPort);
@@ -142,93 +163,35 @@ 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<InputDeviceContext> contextPtr(new InputDeviceContext(*this, eventHubId));
- ftl::Flags<InputDeviceClass> classes = contextPtr->getDeviceClasses();
std::vector<std::unique_ptr<InputMapper>> 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<SwitchInputMapper>(*contextPtr));
- }
-
- // Scroll wheel-like devices.
- if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
- mappers.push_back(std::make_unique<RotaryEncoderInputMapper>(*contextPtr));
- }
-
- // Vibrator-like devices.
- if (classes.test(InputDeviceClass::VIBRATOR)) {
- mappers.push_back(std::make_unique<VibratorInputMapper>(*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<PeripheralController>(*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<KeyboardInputMapper>(*contextPtr, keyboardSource, keyboardType));
- }
-
- // Cursor-like devices.
- if (classes.test(InputDeviceClass::CURSOR)) {
- mappers.push_back(std::make_unique<CursorInputMapper>(*contextPtr));
- }
-
- // Touchscreens and touchpad devices.
- if (classes.test(InputDeviceClass::TOUCH_MT)) {
- mappers.push_back(std::make_unique<MultiTouchInputMapper>(*contextPtr));
- } else if (classes.test(InputDeviceClass::TOUCH)) {
- mappers.push_back(std::make_unique<SingleTouchInputMapper>(*contextPtr));
- }
+ mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
+}
- // Joystick-like devices.
- if (classes.test(InputDeviceClass::JOYSTICK)) {
- mappers.push_back(std::make_unique<JoystickInputMapper>(*contextPtr));
+[[nodiscard]] std::list<NotifyArgs> InputDevice::addEventHubDevice(
+ nsecs_t when, int32_t eventHubId, const InputReaderConfiguration& readerConfig) {
+ if (mDevices.find(eventHubId) != mDevices.end()) {
+ return {};
}
- // Motion sensor enabled devices.
- if (classes.test(InputDeviceClass::SENSOR)) {
- mappers.push_back(std::make_unique<SensorInputMapper>(*contextPtr));
- }
+ // Add an empty device configure and keep it enabled to allow mapper population with correct
+ // configuration/context,
+ // Note: we need to ensure device is kept enabled till mappers are configured
+ // TODO: b/281852638 refactor tests to remove this flag and reliance on the empty device
+ addEmptyEventHubDevice(eventHubId);
+ std::list<NotifyArgs> out = configureInternal(when, readerConfig, {}, /*forceEnable=*/true);
- // External stylus-like devices.
- if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
- mappers.push_back(std::make_unique<ExternalStylusInputMapper>(*contextPtr));
- }
+ DevicePair& devicePair = mDevices[eventHubId];
+ devicePair.second = createMappers(*devicePair.first, readerConfig);
- // insert the context into the devices set
- mDevices.insert({eventHubId, std::make_pair(std::move(contextPtr), std::move(mappers))});
// Must change generation to flag this device as changed
bumpGeneration();
+ return out;
}
void InputDevice::removeEventHubDevice(int32_t eventHubId) {
@@ -239,8 +202,16 @@ void InputDevice::removeEventHubDevice(int32_t eventHubId) {
mDevices.erase(eventHubId);
}
-void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config,
- uint32_t changes) {
+std::list<NotifyArgs> InputDevice::configure(nsecs_t when,
+ const InputReaderConfiguration& readerConfig,
+ ConfigurationChanges changes) {
+ return configureInternal(when, readerConfig, changes);
+}
+std::list<NotifyArgs> InputDevice::configureInternal(nsecs_t when,
+ const InputReaderConfiguration& readerConfig,
+ ConfigurationChanges changes,
+ bool forceEnable) {
+ std::list<NotifyArgs> out;
mSources = 0;
mClasses = ftl::Flags<InputDeviceClass>(0);
mControllerNumber = 0;
@@ -260,34 +231,29 @@ 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<PropertyMap> configuration =
+ getEventHub()->getConfiguration(context.getEventHubId());
+ if (configuration) {
+ mConfiguration.addAll(&(*configuration));
+ }
});
- }
- if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
- if (!mClasses.test(InputDeviceClass::VIRTUAL)) {
- std::shared_ptr<KeyCharacterMap> keyboardLayout =
- mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
- bool shouldBumpGeneration = false;
- for_each_subdevice(
- [&keyboardLayout, &shouldBumpGeneration](InputDeviceContext& context) {
- if (context.setKeyboardLayoutOverlay(keyboardLayout)) {
- shouldBumpGeneration = true;
- }
- });
- if (shouldBumpGeneration) {
- bumpGeneration();
- }
- }
+ mAssociatedDeviceType =
+ getValueByKey(readerConfig.deviceTypeAssociations, mIdentifier.location);
+ mIsWaking = mConfiguration.getBool("device.wake").value_or(false);
}
- 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 +263,7 @@ 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 || (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 +271,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<std::string, uint8_t>& ports = config->portAssociations;
+ const std::unordered_map<std::string, uint8_t>& ports =
+ readerConfig.portAssociations;
const auto& displayPort = ports.find(inputPort);
if (displayPort != ports.end()) {
mAssociatedDisplayPort = std::make_optional(displayPort->second);
} else {
const std::unordered_map<std::string, std::string>& displayUniqueIds =
- config->uniqueIdAssociations;
+ readerConfig.uniqueIdAssociations;
const auto& displayUniqueId = displayUniqueIds.find(inputPort);
if (displayUniqueId != displayUniqueIds.end()) {
mAssociatedDisplayUniqueId = displayUniqueId->second;
@@ -325,110 +286,125 @@ void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config
}
}
- // If the device was explicitly disabled by the user, it would be present in the
- // "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());
+ // If it is associated with a specific display, then find the corresponding viewport
+ // which will be used to enable/disable the device.
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.",
getName().c_str(), *mAssociatedDisplayPort);
- enabled = false;
}
} 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",
getName().c_str(), mAssociatedDisplayUniqueId->c_str());
- enabled = false;
}
}
-
- if (changes) {
- // 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);
- }
}
- 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() || changes.test(Change::ENABLED_STATE) ||
+ changes.test(Change::DISPLAY_INFO)) {
+ // Whether a device is enabled can depend on the display association,
+ // so update the enabled state when there is a change in display info.
+ out += updateEnableState(when, readerConfig, forceEnable);
}
}
+ return out;
}
-void InputDevice::reset(nsecs_t when) {
- for_each_mapper([when](InputMapper& mapper) { mapper.reset(when); });
+std::list<NotifyArgs> InputDevice::reset(nsecs_t when) {
+ std::list<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> 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;
}
+ postProcess(out);
+ return out;
}
-void InputDevice::timeoutExpired(nsecs_t when) {
- for_each_mapper([when](InputMapper& mapper) { mapper.timeoutExpired(when); });
+void InputDevice::postProcess(std::list<NotifyArgs>& args) const {
+ if (mIsWaking) {
+ // Update policy flags to request wake for the `NotifyArgs` that come from waking devices.
+ for (auto& arg : args) {
+ if (const auto notifyMotionArgs = std::get_if<NotifyMotionArgs>(&arg)) {
+ notifyMotionArgs->policyFlags |= POLICY_FLAG_WAKE;
+ } else if (const auto notifySwitchArgs = std::get_if<NotifySwitchArgs>(&arg)) {
+ notifySwitchArgs->policyFlags |= POLICY_FLAG_WAKE;
+ } else if (const auto notifyKeyArgs = std::get_if<NotifyKeyArgs>(&arg)) {
+ notifyKeyArgs->policyFlags |= POLICY_FLAG_WAKE;
+ }
+ }
+ }
}
-void InputDevice::updateExternalStylusState(const StylusState& state) {
- for_each_mapper([state](InputMapper& mapper) { mapper.updateExternalStylusState(state); });
+std::list<NotifyArgs> InputDevice::timeoutExpired(nsecs_t when) {
+ std::list<NotifyArgs> out;
+ for_each_mapper([&](InputMapper& mapper) { out += mapper.timeoutExpired(when); });
+ return out;
+}
+
+std::list<NotifyArgs> InputDevice::updateExternalStylusState(const StylusState& state) {
+ std::list<NotifyArgs> 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 +446,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<std::unique_ptr<InputMapper>> InputDevice::createMappers(
+ InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig) {
+ ftl::Flags<InputDeviceClass> classes = contextPtr.getDeviceClasses();
+ std::vector<std::unique_ptr<InputMapper>> mappers;
+
+ // Switch-like devices.
+ if (classes.test(InputDeviceClass::SWITCH)) {
+ mappers.push_back(createInputMapper<SwitchInputMapper>(contextPtr, readerConfig));
+ }
+
+ // Scroll wheel-like devices.
+ if (classes.test(InputDeviceClass::ROTARY_ENCODER)) {
+ mappers.push_back(createInputMapper<RotaryEncoderInputMapper>(contextPtr, readerConfig));
+ }
+
+ // Vibrator-like devices.
+ if (classes.test(InputDeviceClass::VIBRATOR)) {
+ mappers.push_back(createInputMapper<VibratorInputMapper>(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<PeripheralController>(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<KeyboardInputMapper>(contextPtr, readerConfig,
+ keyboardSource, keyboardType));
+ }
+
+ // Cursor-like devices.
+ if (classes.test(InputDeviceClass::CURSOR)) {
+ mappers.push_back(createInputMapper<CursorInputMapper>(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<TouchpadInputMapper>(contextPtr, readerConfig));
+ } else if (classes.test(InputDeviceClass::TOUCH_MT)) {
+ mappers.push_back(createInputMapper<MultiTouchInputMapper>(contextPtr, readerConfig));
+ } else if (classes.test(InputDeviceClass::TOUCH)) {
+ mappers.push_back(createInputMapper<SingleTouchInputMapper>(contextPtr, readerConfig));
+ }
+
+ // Joystick-like devices.
+ if (classes.test(InputDeviceClass::JOYSTICK)) {
+ mappers.push_back(createInputMapper<JoystickInputMapper>(contextPtr, readerConfig));
+ }
+
+ // Motion sensor enabled devices.
+ if (classes.test(InputDeviceClass::SENSOR)) {
+ mappers.push_back(createInputMapper<SensorInputMapper>(contextPtr, readerConfig));
+ }
+
+ // External stylus-like devices.
+ if (classes.test(InputDeviceClass::EXTERNAL_STYLUS)) {
+ mappers.push_back(createInputMapper<ExternalStylusInputMapper>(contextPtr, readerConfig));
+ }
+ return mappers;
+}
+
+bool InputDevice::markSupportedKeyCodes(uint32_t sourceMask, const std::vector<int32_t>& 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 +560,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<NotifyArgs> InputDevice::vibrate(const VibrationSequence& sequence, ssize_t repeat,
+ int32_t token) {
+ std::list<NotifyArgs> 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<NotifyArgs> InputDevice::cancelVibrate(int32_t token) {
+ std::list<NotifyArgs> out;
+ for_each_mapper([&](InputMapper& mapper) { out += mapper.cancelVibrate(token); });
+ return out;
}
bool InputDevice::isVibrating() {
@@ -548,8 +613,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<NotifyArgs> InputDevice::cancelTouch(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ for_each_mapper([&](InputMapper& mapper) { out += mapper.cancelTouch(when, readTime); });
+ return out;
}
bool InputDevice::setLightColor(int32_t lightId, int32_t color) {
@@ -584,13 +651,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<int32_t> InputDevice::getAssociatedDisplayId() {
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 905b348caa..0582649d38 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<EventHubInterface> eventHub,
@@ -66,7 +77,7 @@ InputReader::InputReader(std::shared_ptr<EventHubInterface> eventHub,
: mContext(this),
mEventHub(eventHub),
mPolicy(policy),
- mQueuedListener(listener),
+ mNextListener(listener),
mGlobalMetaState(AMETA_NONE),
mLedMetaState(AMETA_NONE),
mGeneration(1),
@@ -74,7 +85,7 @@ InputReader::InputReader(std::shared_ptr<EventHubInterface> 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<InputDeviceInfo> inputDevices;
+ std::list<NotifyArgs> 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,31 +133,35 @@ void InputReader::loopOnce() {
}
} // release lock
- size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
+ std::vector<RawEvent> events = mEventHub->getEvents(timeoutMillis);
{ // acquire lock
std::scoped_lock _l(mLock);
mReaderIsAliveCondition.notify_all();
- if (count) {
- processEventsLocked(mEventBuffer, count);
+ if (!events.empty()) {
+ mPendingArgs += 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);
+ mPendingArgs += timeoutExpiredLocked(now);
}
}
if (oldGeneration != mGeneration) {
inputDevicesChanged = true;
inputDevices = getInputDevicesLocked();
+ mPendingArgs.emplace_back(
+ NotifyInputDevicesChangedArgs{mContext.getNextId(), inputDevices});
}
+
+ std::swap(notifyArgs, mPendingArgs);
} // release lock
// Send out a message that the describes the changed input devices.
@@ -152,6 +169,14 @@ 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<NotifyMotionArgs>(&args);
+ if (motionArgs != nullptr && isStylusPointerGestureStart(*motionArgs)) {
+ mPolicy->notifyStylusGestureStarted(motionArgs->deviceId, motionArgs->eventTime);
+ }
+ }
+
// 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
@@ -159,10 +184,13 @@ void InputReader::loopOnce() {
// resulting in a deadlock. This situation is actually quite plausible because the
// listener is actually the input dispatcher, which calls into the window manager,
// which occasionally calls into the input reader.
- mQueuedListener.flush();
+ for (const NotifyArgs& args : notifyArgs) {
+ mNextListener.notify(args);
+ }
}
-void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
+std::list<NotifyArgs> InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
+ std::list<NotifyArgs> out;
for (const RawEvent* rawEvent = rawEvents; count;) {
int32_t type = rawEvent->type;
size_t batchSize = 1;
@@ -175,10 +203,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 +226,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) {
@@ -207,9 +236,10 @@ void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
}
InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(eventHubId);
- std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
- device->configure(when, &mConfig, 0);
- device->reset(when);
+ std::shared_ptr<InputDevice> device = createDeviceLocked(when, eventHubId, identifier);
+
+ mPendingArgs += device->configure(when, mConfig, /*changes=*/{});
+ mPendingArgs += device->reset(when);
if (device->isIgnored()) {
ALOGI("Device added: id=%d, eventHubId=%d, name='%s', descriptor='%s' "
@@ -283,13 +313,13 @@ void InputReader::removeDeviceLocked(nsecs_t when, int32_t eventHubId) {
}
if (device->hasEventHubDevices()) {
- device->configure(when, &mConfig, 0);
+ mPendingArgs += device->configure(when, mConfig, /*changes=*/{});
}
- device->reset(when);
+ mPendingArgs += device->reset(when);
}
std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
- int32_t eventHubId, const InputDeviceIdentifier& identifier) {
+ nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier) {
auto deviceIt = std::find_if(mDevices.begin(), mDevices.end(), [identifier](auto& devicePair) {
const InputDeviceIdentifier identifier2 =
devicePair.second->getDeviceInfo().getIdentifier();
@@ -304,25 +334,26 @@ std::shared_ptr<InputDevice> InputReader::createDeviceLocked(
device = std::make_shared<InputDevice>(&mContext, deviceId, bumpGenerationLocked(),
identifier);
}
- device->addEventHubDevice(eventHubId);
+ mPendingArgs += device->addEventHubDevice(when, eventHubId, mConfig);
return device;
}
-void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
- size_t count) {
+std::list<NotifyArgs> 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<InputDevice>& 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<NotifyArgs> InputReader::timeoutExpiredLocked(nsecs_t when) {
+ std::list<NotifyArgs> out;
for (auto& devicePair : mDevices) {
std::shared_ptr<InputDevice>& device = devicePair.second;
if (!device->isIgnored()) {
- device->timeoutExpired(when);
+ out += device->timeoutExpired(when);
}
}
+ return out;
}
int32_t InputReader::nextInputDeviceIdLocked() {
@@ -354,42 +387,41 @@ void InputReader::handleConfigurationChangedLocked(nsecs_t when) {
updateGlobalMetaStateLocked();
// Enqueue configuration changed.
- NotifyConfigurationChangedArgs args(mContext.getNextId(), when);
- mQueuedListener.notifyConfigurationChanged(&args);
+ mPendingArgs.emplace_back(NotifyConfigurationChangedArgs{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<InputDevice>& device = devicePair.second;
- device->configure(now, &mConfig, changes);
+ mPendingArgs += 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);
+ mPendingArgs.emplace_back(
+ NotifyPointerCaptureChangedArgs{mContext.getNextId(), now,
+ mCurrentPointerCaptureRequest});
}
}
}
@@ -420,7 +452,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<InputDeviceInfo>& outDevices) {
@@ -432,11 +464,13 @@ void InputReader::getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& o
}
}
-void InputReader::dispatchExternalStylusStateLocked(const StylusState& state) {
+std::list<NotifyArgs> InputReader::dispatchExternalStylusStateLocked(const StylusState& state) {
+ std::list<NotifyArgs> out;
for (auto& devicePair : mDevices) {
std::shared_ptr<InputDevice>& device = devicePair.second;
- device->updateExternalStylusState(state);
+ out += device->updateExternalStylusState(state);
}
+ return out;
}
void InputReader::disableVirtualKeysUntilLocked(nsecs_t time) {
@@ -583,34 +617,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<int32_t>& 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<int32_t>& 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<InputDevice>& 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 +666,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 +685,7 @@ void InputReader::vibrate(int32_t deviceId, const VibrationSequence& sequence, s
InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
- device->vibrate(sequence, repeat, token);
+ mPendingArgs += device->vibrate(sequence, repeat, token);
}
}
@@ -651,7 +694,7 @@ void InputReader::cancelVibrate(int32_t deviceId, int32_t token) {
InputDevice* device = findInputDeviceLocked(deviceId);
if (device) {
- device->cancelVibrate(token);
+ mPendingArgs += device->cancelVibrate(token);
}
}
@@ -721,7 +764,10 @@ std::optional<int32_t> 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 +787,35 @@ std::optional<int32_t> 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<std::string> InputReader::getBatteryDevicePath(int32_t deviceId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (!device) return {};
+
+ std::optional<int32_t> 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<InputDeviceLightInfo> InputReader::getLights(int32_t deviceId) {
std::scoped_lock _l(mLock);
@@ -807,6 +878,16 @@ std::optional<int32_t> InputReader::getLightPlayerId(int32_t deviceId, int32_t l
return std::nullopt;
}
+std::optional<std::string> 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 +923,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);
@@ -861,6 +946,7 @@ void InputReader::dump(std::string& dump) {
device->dump(dump, eventHubDevStr);
}
+ dump += StringPrintf(INDENT "NextTimeout: %" PRId64 "\n", mNextTimeout);
dump += INDENT "Configuration:\n";
dump += INDENT2 "ExcludedDeviceNames: [";
for (size_t i = 0; i < mConfig.excludedDeviceNames.size(); i++) {
@@ -950,6 +1036,24 @@ int32_t InputReader::ContextImpl::getLedMetaState() {
return mReader->getLedMetaStateLocked();
}
+void InputReader::ContextImpl::setPreventingTouchpadTaps(bool prevent) {
+ // lock is already held by the input loop
+ mReader->mPreventingTouchpadTaps = prevent;
+}
+
+bool InputReader::ContextImpl::isPreventingTouchpadTaps() {
+ // lock is already held by the input loop
+ return mReader->mPreventingTouchpadTaps;
+}
+
+void InputReader::ContextImpl::setLastKeyDownTimestamp(nsecs_t when) {
+ mReader->mLastKeyDownTimestamp = when;
+}
+
+nsecs_t InputReader::ContextImpl::getLastKeyDownTimestamp() {
+ return mReader->mLastKeyDownTimestamp;
+}
+
void InputReader::ContextImpl::disableVirtualKeysUntil(nsecs_t time) {
// lock is already held by the input loop
mReader->disableVirtualKeysUntilLocked(time);
@@ -987,18 +1091,15 @@ void InputReader::ContextImpl::getExternalStylusDevices(std::vector<InputDeviceI
mReader->getExternalStylusDevicesLocked(outDevices);
}
-void InputReader::ContextImpl::dispatchExternalStylusState(const StylusState& state) {
- mReader->dispatchExternalStylusStateLocked(state);
+std::list<NotifyArgs> 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/host/InputHost.cpp b/services/inputflinger/reader/Macros.cpp
index 094200af7d..8841d0f14a 100644
--- a/services/inputflinger/host/InputHost.cpp
+++ b/services/inputflinger/reader/Macros.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 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,29 +14,30 @@
* limitations under the License.
*/
-#include <vector>
+#include "Macros.h"
-#include "InputDriver.h"
-#include "InputHost.h"
+#include <android-base/properties.h>
-#include <utils/Log.h>
-#include <utils/String8.h>
+namespace {
-#define INDENT " "
+const bool IS_DEBUGGABLE_BUILD =
+#if defined(__ANDROID__)
+ android::base::GetBoolProperty("ro.debuggable", false);
+#else
+ true;
+#endif
-namespace android {
+} // namespace
-void InputHost::registerInputDriver(InputDriverInterface* driver) {
- LOG_ALWAYS_FATAL_IF(driver == nullptr, "Cannot register a nullptr as an InputDriver!");
- driver->init();
- mDrivers.push_back(driver);
-}
+namespace android {
-void InputHost::dump(String8& result) {
- result.append(INDENT "Input Drivers:\n");
- for (size_t i = 0; i < mDrivers.size(); i++) {
- mDrivers[i]->dump(result);
+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 d8376894f6..2bce215b3c 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 <log/log.h>
+#include <log/log_event_list.h>
-// Log debug messages for each raw event received from the EventHub.
-static constexpr bool DEBUG_RAW_EVENTS = false;
+#include <unordered_map>
-// 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 <typename K, typename V>
+static inline std::optional<V> getValueByKey(const std::unordered_map<K, V>& map, K key) {
+ auto it = map.find(key);
+ std::optional<V> 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 2f8138b832..627dcba9cf 100644
--- a/services/inputflinger/reader/TouchVideoDevice.cpp
+++ b/services/inputflinger/reader/TouchVideoDevice.cpp
@@ -198,8 +198,9 @@ std::optional<TouchVideoFrame> 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<long long>(buf.timestamp.tv_sec),
+ static_cast<long long>(buf.timestamp.tv_usec));
}
std::vector<int16_t> 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 8065f57524..eabf591dbf 100644
--- a/services/inputflinger/reader/controller/PeripheralController.cpp
+++ b/services/inputflinger/reader/controller/PeripheralController.cpp
@@ -16,15 +16,15 @@
#include <locale>
#include <regex>
+#include <sstream>
+#include <string>
+#include <android/sysprop/InputProperties.sysprop.h>
#include <ftl/enum.h>
#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) {
@@ -47,6 +47,10 @@ static inline int32_t toArgb(int32_t brightness, int32_t red, int32_t green, int
return (brightness & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
}
+static inline bool isKeyboardBacklightCustomLevelsEnabled() {
+ return sysprop::InputProperties::enable_keyboard_backlight_custom_levels().value_or(true);
+}
+
/**
* Input controller owned by InputReader device, implements the native API for querying input
* lights, getting and setting the lights brightness and color, by interacting with EventHub
@@ -73,7 +77,7 @@ std::optional<std::int32_t> 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<float>(MAX_BRIGHTNESS) / rawMaxBrightness;
// Scale the returned brightness in [0, rawMaxBrightness] to [0, 255]
if (rawMaxBrightness != MAX_BRIGHTNESS) {
brightness = brightness * ratio;
@@ -92,7 +96,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<float>(MAX_BRIGHTNESS) / rawMaxBrightness;
// Scale the requested brightness in [0, 255] to [0, rawMaxBrightness]
if (rawMaxBrightness != MAX_BRIGHTNESS) {
brightness = ceil(brightness / ratio);
@@ -154,7 +158,7 @@ std::optional<int32_t> 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<int32_t> PeripheralController::RgbLight::getLightColor() {
@@ -199,13 +203,12 @@ std::optional<int32_t> PeripheralController::MultiColorLight::getLightColor() {
}
std::unordered_map<LightColor, int32_t> 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<int32_t> 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,11 +277,44 @@ 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, getPreferredBrightnessLevels(light.get()));
deviceInfo->addLightInfo(lightInfo);
}
}
+// TODO(b/281822656): Move to constructor and add as a parameter to avoid parsing repeatedly.
+// Need to change lifecycle of Peripheral controller so that Input device configuration map is
+// available at construction time before moving this logic to constructor.
+std::set<BrightnessLevel> PeripheralController::getPreferredBrightnessLevels(
+ const Light* light) const {
+ std::set<BrightnessLevel> levels;
+ if (!isKeyboardBacklightCustomLevelsEnabled() ||
+ light->type != InputDeviceLightType::KEYBOARD_BACKLIGHT) {
+ return levels;
+ }
+ std::optional<std::string> keyboardBacklightLevels =
+ mDeviceContext.getConfiguration().getString("keyboard.backlight.brightnessLevels");
+ if (!keyboardBacklightLevels) {
+ return levels;
+ }
+ std::stringstream ss(*keyboardBacklightLevels);
+ while (ss.good()) {
+ std::string substr;
+ std::getline(ss, substr, ',');
+ char* end;
+ int32_t value = static_cast<int32_t>(strtol(substr.c_str(), &end, 10));
+ if (*end != '\0' || value < 0 || value > 255) {
+ ALOGE("Error parsing keyboard backlight brightness levels, provided levels = %s",
+ keyboardBacklightLevels->c_str());
+ levels.clear();
+ break;
+ }
+ levels.insert(BrightnessLevel(value));
+ }
+ return levels;
+}
+
void PeripheralController::dump(std::string& dump) {
dump += INDENT2 "Input Controller:\n";
if (!mLights.empty()) {
@@ -287,6 +323,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 +404,8 @@ void PeripheralController::configureLights() {
std::unordered_map<LightColor, int32_t /* rawLightId */> rawRgbIds;
// Map from player Id to raw light Id
std::unordered_map<int32_t, int32_t> playerIdLightIds;
+ // Set of Keyboard backlights
+ std::set<int32_t> keyboardBacklightIds;
// Check raw lights
const std::vector<int32_t> rawLightIds = getDeviceContext().getRawLightIds();
@@ -394,6 +434,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 +478,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> light =
- std::make_unique<RgbLight>(getDeviceContext(), ++mNextId, rawRgbIds, rawGlobalId);
+ std::make_unique<RgbLight>(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 +505,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 +517,7 @@ void PeripheralController::configureLights() {
}
std::unique_ptr<Light> light =
std::make_unique<MultiColorLight>(getDeviceContext(), rawInfo.name, ++mNextId,
- rawInfo.id);
+ type, rawInfo.id);
mLights.insert_or_assign(light->id, std::move(light));
continue;
}
@@ -465,7 +526,7 @@ void PeripheralController::configureLights() {
ALOGD("Mono light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
}
std::unique_ptr<Light> light = std::make_unique<MonoLight>(getDeviceContext(), rawInfo.name,
- ++mNextId, rawInfo.id);
+ ++mNextId, type, rawInfo.id);
mLights.insert_or_assign(light->id, std::move(light));
}
@@ -527,5 +588,4 @@ std::optional<int32_t> PeripheralController::getLightPlayerId(int32_t lightId) {
int32_t PeripheralController::getEventHubId() const {
return getDeviceContext().getEventHubId();
}
-
} // namespace android
diff --git a/services/inputflinger/reader/controller/PeripheralController.h b/services/inputflinger/reader/controller/PeripheralController.h
index ac951ebe2a..07ade7c931 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<InputDeviceLightCapability> capabilityFlags;
virtual bool setLightColor(int32_t color) { return false; }
virtual std::optional<int32_t> getLightColor() { return std::nullopt; }
@@ -76,14 +76,17 @@ private:
virtual void dump(std::string& dump) {}
+ void configureSuggestedBrightnessLevels();
std::optional<std::int32_t> getRawLightBrightness(int32_t rawLightId);
void setRawLightBrightness(int32_t rawLightId, int32_t brightness);
};
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 +95,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<LightColor, int32_t>& rawRgbIds,
std::optional<int32_t> 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<LightColor, int32_t /* rawLightId */> rawRgbIds;
@@ -115,8 +118,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 +138,7 @@ private:
// Map from player Id to raw light Id
std::unordered_map<int32_t, int32_t> rawLightIds;
- bool setLightPlayerId(int32_t palyerId) override;
+ bool setLightPlayerId(int32_t playerId) override;
std::optional<int32_t> getLightPlayerId() override;
void dump(std::string& dump) override;
};
@@ -147,8 +153,8 @@ private:
// Battery map from battery ID to battery
std::unordered_map<int32_t, std::unique_ptr<Battery>> mBatteries;
+
+ std::set<BrightnessLevel> getPreferredBrightnessLevels(const Light* light) const;
};
} // 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 306e36119b..76ed1ca038 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 54c6810a33..0bcab42417 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -14,13 +14,17 @@
* limitations under the License.
*/
-#ifndef _RUNTIME_EVENT_HUB_H
-#define _RUNTIME_EVENT_HUB_H
+#pragma once
#include <bitset>
#include <climits>
#include <filesystem>
+#include <functional>
+#include <map>
+#include <ostream>
+#include <string>
#include <unordered_map>
+#include <utility>
#include <vector>
#include <batteryservice/BatteryService.h>
@@ -36,7 +40,6 @@
#include <sys/epoll.h>
#include <utils/BitSet.h>
#include <utils/Errors.h>
-#include <utils/KeyedVector.h>
#include <utils/List.h>
#include <utils/Log.h>
#include <utils/Mutex.h>
@@ -44,6 +47,8 @@
#include "TouchVideoDevice.h"
#include "VibrationElement.h"
+struct inotify_event;
+
namespace android {
/* Number of colors : {red, green, blue} */
@@ -65,24 +70,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 +99,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 +135,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 +175,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 +196,9 @@ struct RawLightInfo {
ftl::Flags<InputLightClass> flags;
std::array<int32_t, COLOR_NUM> rgbIndex;
std::filesystem::path path;
+
+ bool operator==(const RawLightInfo&) const = default;
+ bool operator!=(const RawLightInfo&) const = default;
};
/* Describes a raw battery. */
@@ -199,6 +207,18 @@ struct RawBatteryInfo {
std::string name;
ftl::Flags<InputBatteryClass> 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 +265,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<PropertyMap> getConfiguration(int32_t deviceId) const = 0;
virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
RawAbsoluteAxisInfo* outAxisInfo) const = 0;
@@ -256,6 +282,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 +307,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<RawEvent> getEvents(int timeoutMillis) = 0;
virtual std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) = 0;
- virtual base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId,
- int32_t absCode) = 0;
+ virtual base::Result<std::pair<InputDeviceSensorType, int32_t>> 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<int32_t> getRawBatteryIds(int32_t deviceId) = 0;
+ virtual std::vector<int32_t> getRawBatteryIds(int32_t deviceId) const = 0;
virtual std::optional<RawBatteryInfo> 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<int32_t> getRawLightIds(int32_t deviceId) = 0;
- virtual std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) = 0;
- virtual std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) = 0;
+ virtual std::vector<int32_t> getRawLightIds(int32_t deviceId) const = 0;
+ virtual std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId,
+ int32_t lightId) const = 0;
+ virtual std::optional<int32_t> 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<std::unordered_map<LightColor, int32_t>> 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<LightColor, int32_t> intensities) = 0;
- /*
- * Query current input state.
- */
+ /* Query Layout info associated with the input device. */
+ virtual std::optional<RawLayoutInfo> 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 +341,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<int32_t>& keyCodes,
uint8_t* outFlags) const = 0;
virtual bool hasScanCode(int32_t deviceId, int32_t scanCode) const = 0;
@@ -331,7 +361,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<int32_t> getVibratorIds(int32_t deviceId) = 0;
+ virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) const = 0;
/* Query battery level. */
virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId,
@@ -347,19 +377,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 <std::size_t BITS>
@@ -379,7 +413,17 @@ public:
* Note the parameter "bit" is an index to the bit, 0 <= bit < BITS.
*/
inline bool test(size_t bit) const {
- return (bit < BITS) ? mData[bit / WIDTH].test(bit % WIDTH) : false;
+ return (bit < BITS) && mData[bit / WIDTH].test(bit % WIDTH);
+ }
+ /* Sets the given bit in the bit array to given value.
+ * Returns true if the given bit is a valid index and thus was set successfully.
+ */
+ inline bool set(size_t bit, bool value) {
+ if (bit >= BITS) {
+ return false;
+ }
+ mData[bit / WIDTH].set(bit % WIDTH, value);
+ return true;
}
/* Returns total number of bytes needed for the array */
inline size_t bytes() { return (BITS + CHAR_BIT - 1) / CHAR_BIT; }
@@ -427,6 +471,20 @@ public:
mData[i] = std::bitset<WIDTH>(buffer[i]);
}
}
+ /* Dump the indices in the bit array that are set. */
+ inline std::string dumpSetIndices(std::string separator,
+ std::function<std::string(size_t /*index*/)> format) {
+ std::string dmp;
+ for (size_t i = 0; i < BITS; i++) {
+ if (test(i)) {
+ if (!dmp.empty()) {
+ dmp += separator;
+ }
+ dmp += format(i);
+ }
+ }
+ return dmp.empty() ? "<none>" : dmp;
+ }
private:
std::array<std::bitset<WIDTH>, COUNT> mData;
@@ -442,7 +500,7 @@ public:
int32_t getDeviceControllerNumber(int32_t deviceId) const override final;
- void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override final;
+ std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override final;
status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
RawAbsoluteAxisInfo* outAxisInfo) const override final;
@@ -453,6 +511,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 +522,27 @@ public:
AxisInfo* outAxisInfo) const override final;
base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
- int32_t deviceId, int32_t absCode) override final;
+ int32_t deviceId, int32_t absCode) const override final;
- const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) override final;
+ std::vector<int32_t> getRawBatteryIds(int32_t deviceId) const override final;
std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
- int32_t BatteryId) override final;
+ int32_t BatteryId) const override final;
- const std::vector<int32_t> getRawLightIds(int32_t deviceId) override final;
+ std::vector<int32_t> getRawLightIds(int32_t deviceId) const override final;
- std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override final;
+ std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId,
+ int32_t lightId) const override final;
- std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override final;
+ std::optional<int32_t> 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<std::unordered_map<LightColor, int32_t>> 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<LightColor, int32_t> intensities) override final;
+ std::optional<RawLayoutInfo> getRawLayoutInfo(int32_t deviceId) const override final;
+
void setExcludedDevices(const std::vector<std::string>& devices) override final;
int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final;
@@ -488,10 +553,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<int32_t>& keyCodes,
uint8_t* outFlags) const override final;
- size_t getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) override final;
+ std::vector<RawEvent> getEvents(int timeoutMillis) override final;
std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override final;
bool hasScanCode(int32_t deviceId, int32_t scanCode) const override final;
@@ -510,15 +575,15 @@ public:
void vibrate(int32_t deviceId, const VibrationElement& effect) override final;
void cancelVibrate(int32_t deviceId) override final;
- std::vector<int32_t> getVibratorIds(int32_t deviceId) override final;
+ std::vector<int32_t> 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<int32_t> getBatteryCapacity(int32_t deviceId,
int32_t batteryId) const override final;
@@ -526,29 +591,29 @@ public:
std::optional<int32_t> 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<int32_t, RawBatteryInfo> batteryInfos;
- std::unordered_map<int32_t, RawLightInfo> lightInfos;
- explicit AssociatedDevice(std::filesystem::path sysfsRootPath)
- : sysfsRootPath(sysfsRootPath), nextBatteryId(0), nextLightId(0) {}
- bool configureBatteryLocked();
- bool configureLightsLocked();
+ std::unordered_map<int32_t /*batteryId*/, RawBatteryInfo> batteryInfos;
+ std::unordered_map<int32_t /*lightId*/, RawLightInfo> lightInfos;
+ std::optional<RawLayoutInfo> layoutInfo;
+
+ bool isChanged() const;
+ bool operator==(const AssociatedDevice&) const = default;
+ bool operator!=(const AssociatedDevice&) const = default;
+ std::string dump() const;
};
struct Device {
@@ -561,16 +626,21 @@ private:
ftl::Flags<InputDeviceClass> classes;
- BitArray<KEY_MAX> keyBitmask;
- BitArray<KEY_MAX> keyState;
- BitArray<ABS_MAX> absBitmask;
- BitArray<REL_MAX> relBitmask;
- BitArray<SW_MAX> swBitmask;
- BitArray<SW_MAX> swState;
- BitArray<LED_MAX> ledBitmask;
- BitArray<FF_MAX> ffBitmask;
- BitArray<INPUT_PROP_MAX> propBitmask;
- BitArray<MSC_MAX> mscBitmask;
+ BitArray<KEY_CNT> keyBitmask;
+ BitArray<KEY_CNT> keyState;
+ BitArray<REL_CNT> relBitmask;
+ BitArray<SW_CNT> swBitmask;
+ BitArray<SW_CNT> swState;
+ BitArray<LED_CNT> ledBitmask;
+ BitArray<FF_CNT> ffBitmask;
+ BitArray<INPUT_PROP_CNT> propBitmask;
+ BitArray<MSC_CNT> mscBitmask;
+ BitArray<ABS_CNT> absBitmask;
+ struct AxisState {
+ RawAbsoluteAxisInfo info;
+ int value;
+ };
+ std::map<int /*axis*/, AxisState> absState;
std::string configurationFile;
std::unique_ptr<PropertyMap> configuration;
@@ -581,13 +651,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> associatedDevice;
+ // The input devices that have the same sysfs path have the same associated device.
+ std::shared_ptr<const AssociatedDevice> 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<const AssociatedDevice> assocDev);
~Device();
void close();
@@ -604,6 +674,7 @@ private:
status_t readDeviceBitMask(unsigned long ioctlCode, BitArray<N>& bitArray);
void configureFd();
+ void populateAbsoluteAxisStates();
bool hasKeycodeLocked(int keycode) const;
void loadConfigurationLocked();
bool loadVirtualKeyMapLocked();
@@ -613,6 +684,10 @@ private:
void setLedForControllerLocked();
status_t mapLed(int32_t led, int32_t* outScanCode) const;
void setLedStateLocked(int32_t led, bool on);
+
+ bool currentFrameDropped;
+ void trackInputEvent(const struct input_event& event);
+ void readDeviceState();
};
/**
@@ -632,6 +707,8 @@ private:
void createVirtualKeyboardLocked() REQUIRES(mLock);
void addDeviceLocked(std::unique_ptr<Device> device) REQUIRES(mLock);
void assignDescriptorLocked(InputDeviceIdentifier& identifier) REQUIRES(mLock);
+ std::shared_ptr<const AssociatedDevice> 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 +725,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<void> 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 +806,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 b3a24af8a9..31dcb2e7a8 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 <ftl/flags.h>
#include <input/DisplayViewport.h>
@@ -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<std::string> getBluetoothAddress() const {
+ return mIdentifier.bluetoothAddress;
+ }
+ inline const std::string getLocation() const { return mIdentifier.location; }
inline ftl::Flags<InputDeviceClass> getClasses() const { return mClasses; }
inline uint32_t getSources() const { return mSources; }
inline bool hasEventHubDevices() const { return !mDevices.empty(); }
@@ -62,37 +66,44 @@ public:
inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
return mAssociatedDisplayUniqueId;
}
+ inline std::optional<std::string> getDeviceTypeAssociation() const {
+ return mAssociatedDeviceType;
+ }
inline std::optional<DisplayViewport> getAssociatedViewport() const {
return mAssociatedViewport;
}
inline bool hasMic() const { return mHasMic; }
- inline bool isIgnored() { return !getMapperCount(); }
+ inline bool isIgnored() { return !getMapperCount() && !mController; }
bool isEnabled();
- void 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);
+ [[nodiscard]] std::list<NotifyArgs> addEventHubDevice(
+ nsecs_t when, 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<NotifyArgs> configure(nsecs_t when,
+ const InputReaderConfiguration& readerConfig,
+ ConfigurationChanges changes);
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when);
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvents, size_t count);
+ [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when);
+ [[nodiscard]] std::list<NotifyArgs> 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<int32_t>& keyCodes,
uint8_t* outFlags);
- void vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token);
- void cancelVibrate(int32_t token);
+ [[nodiscard]] std::list<NotifyArgs> vibrate(const VibrationSequence& sequence, ssize_t repeat,
+ int32_t token);
+ [[nodiscard]] std::list<NotifyArgs> cancelVibrate(int32_t token);
bool isVibrating();
std::vector<int32_t> getVibratorIds();
- void cancelTouch(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> 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 <class T, typename... Args>
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 <class T, typename... Args>
+ 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<T>(*deviceContext, args...));
+ return static_cast<T&>(*mappers.back());
+ }
+
// construct and add a controller to the input device
template <class T>
T& addController(int32_t eventHubId) {
// ensure a device entry exists for this eventHubId
- addEventHubDevice(eventHubId, false);
+ addEmptyEventHubDevice(eventHubId);
// create controller
auto& devicePair = mDevices[eventHubId];
@@ -168,9 +191,11 @@ private:
std::unique_ptr<PeripheralControllerInterface> mController;
uint32_t mSources;
+ bool mIsWaking;
bool mIsExternal;
std::optional<uint8_t> mAssociatedDisplayPort;
std::optional<std::string> mAssociatedDisplayUniqueId;
+ std::optional<std::string> mAssociatedDeviceType;
std::optional<DisplayViewport> mAssociatedViewport;
bool mHasMic;
bool mDropUntilNextSync;
@@ -178,8 +203,22 @@ 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<std::unique_ptr<InputMapper>> createMappers(
+ InputDeviceContext& contextPtr, const InputReaderConfiguration& readerConfig);
+
+ [[nodiscard]] std::list<NotifyArgs> configureInternal(
+ nsecs_t when, const InputReaderConfiguration& readerConfig,
+ ConfigurationChanges changes, bool forceEnable = false);
+
+ [[nodiscard]] std::list<NotifyArgs> updateEnableState(
+ nsecs_t when, const InputReaderConfiguration& readerConfig, bool forceEnable = false);
+
PropertyMap mConfiguration;
+ // Runs logic post a `process` call. This can be used to update the generated `NotifyArgs` as
+ // per the properties of the InputDevice.
+ void postProcess(std::list<NotifyArgs>& args) const;
+
// helpers to interate over the devices collection
// run a function against every mapper on every subdevice
inline void for_each_mapper(std::function<void(InputMapper&)> f) {
@@ -256,11 +295,19 @@ 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);
+ if (const auto status = mEventHub->getAbsoluteAxisInfo(mId, code, axisInfo); status != OK) {
+ return status;
+ }
+
+ // Validate axis info for InputDevice.
+ if (axisInfo->valid && axisInfo->minValue == axisInfo->maxValue) {
+ // Historically, we deem axes with the same min and max values as invalid to avoid
+ // dividing by zero when scaling by max - min.
+ // TODO(b/291772515): Perform axis info validation on a per-axis basis when it is used.
+ axisInfo->valid = false;
+ }
+ return OK;
}
inline bool hasRelativeAxis(int32_t code) const {
return mEventHub->hasRelativeAxis(mId, code);
@@ -271,6 +318,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 +372,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<int32_t>& 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 +391,9 @@ public:
inline bool setKeyboardLayoutOverlay(std::shared_ptr<KeyCharacterMap> map) {
return mEventHub->setKeyboardLayoutOverlay(mId, map);
}
+ inline const std::optional<RawLayoutInfo> getRawLayoutInfo() {
+ return mEventHub->getRawLayoutInfo(mId);
+ }
inline void vibrate(const VibrationElement& element) {
return mEventHub->vibrate(mId, element);
}
@@ -368,8 +422,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 +437,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<uint8_t> getAssociatedDisplayPort() const {
return mDevice.getAssociatedDisplayPort();
}
inline std::optional<std::string> getAssociatedDisplayUniqueId() const {
return mDevice.getAssociatedDisplayUniqueId();
}
+ inline std::optional<std::string> getDeviceTypeAssociation() const {
+ return mDevice.getDeviceTypeAssociation();
+ }
inline std::optional<DisplayViewport> getAssociatedViewport() const {
return mDevice.getAssociatedViewport();
}
- inline void cancelTouch(nsecs_t when, nsecs_t readTime) { mDevice.cancelTouch(when, readTime); }
+ [[nodiscard]] inline std::list<NotifyArgs> 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 +468,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 daeaa1dbe3..4c78db38cc 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 <PointerControllerInterface.h>
#include <android-base/thread_annotations.h>
@@ -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<int32_t>& 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<int32_t> getBatteryStatus(int32_t deviceId) override;
+ std::optional<std::string> getBatteryDevicePath(int32_t deviceId) override;
+
std::vector<InputDeviceLightInfo> getLights(int32_t deviceId) override;
std::vector<InputDeviceSensorInfo> getSensors(int32_t deviceId) override;
@@ -112,9 +115,13 @@ public:
std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override;
+ std::optional<std::string> 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<InputDevice> createDeviceLocked(int32_t deviceId,
+ virtual std::shared_ptr<InputDevice> createDeviceLocked(nsecs_t when, int32_t deviceId,
const InputDeviceIdentifier& identifier)
REQUIRES(mLock);
@@ -141,14 +148,19 @@ protected:
int32_t bumpGeneration() NO_THREAD_SAFETY_ANALYSIS override;
void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices)
REQUIRES(mReader->mLock) override;
- void dispatchExternalStylusState(const StylusState& outState)
+ [[nodiscard]] std::list<NotifyArgs> 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;
int32_t getLedMetaState() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
+ void setPreventingTouchpadTaps(bool prevent) REQUIRES(mReader->mLock)
+ REQUIRES(mLock) override;
+ bool isPreventingTouchpadTaps() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
+ void setLastKeyDownTimestamp(nsecs_t when) REQUIRES(mReader->mLock)
+ REQUIRES(mLock) override;
+ nsecs_t getLastKeyDownTimestamp() REQUIRES(mReader->mLock) REQUIRES(mLock) override;
} mContext;
friend class ContextImpl;
@@ -165,13 +177,16 @@ private:
// in parallel to passing it to the InputReader.
std::shared_ptr<EventHubInterface> mEventHub;
sp<InputReaderPolicyInterface> mPolicy;
- QueuedInputListener mQueuedListener;
- InputReaderConfiguration mConfig GUARDED_BY(mLock);
+ // The next stage that should receive the events generated inside InputReader.
+ InputListenerInterface& mNextListener;
+ // As various events are generated inside InputReader, they are stored inside this list. The
+ // list can only be accessed with the lock, so the events inside it are well-ordered.
+ // Once the reader is done working, these events will be swapped into a temporary storage and
+ // sent to the 'mNextListener' without holding the lock.
+ std::list<NotifyArgs> mPendingArgs GUARDED_BY(mLock);
- // The event queue.
- static const int EVENT_BUFFER_SIZE = 256;
- RawEvent mEventBuffer[EVENT_BUFFER_SIZE] GUARDED_BY(mLock);
+ InputReaderConfiguration mConfig 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.
@@ -183,14 +198,22 @@ private:
std::unordered_map<std::shared_ptr<InputDevice>, std::vector<int32_t> /*eventHubId*/>
mDeviceToEventHubIdsMap GUARDED_BY(mLock);
+ // true if tap-to-click on touchpad currently disabled
+ bool mPreventingTouchpadTaps GUARDED_BY(mLock){false};
+
+ // records timestamp of the last key press on the physical keyboard
+ nsecs_t mLastKeyDownTimestamp GUARDED_BY(mLock){0};
+
// low-level input event decoding and device management
- void processEventsLocked(const RawEvent* rawEvents, size_t count) REQUIRES(mLock);
+ [[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> processEventsForDeviceLocked(int32_t eventHubId,
+ const RawEvent* rawEvents,
+ size_t count) REQUIRES(mLock);
+ [[nodiscard]] std::list<NotifyArgs> timeoutExpiredLocked(nsecs_t when) REQUIRES(mLock);
void handleConfigurationChangedLocked(nsecs_t when) REQUIRES(mLock);
@@ -204,7 +227,8 @@ private:
void notifyExternalStylusPresenceChangedLocked() REQUIRES(mLock);
void getExternalStylusDevicesLocked(std::vector<InputDeviceInfo>& outDevices) REQUIRES(mLock);
- void dispatchExternalStylusStateLocked(const StylusState& state) REQUIRES(mLock);
+ [[nodiscard]] std::list<NotifyArgs> dispatchExternalStylusStateLocked(const StylusState& state)
+ REQUIRES(mLock);
// The PointerController that is shared among all the input devices that need it.
std::weak_ptr<PointerControllerInterface> mPointerController;
@@ -228,8 +252,8 @@ 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);
PointerCaptureRequest mCurrentPointerCaptureRequest GUARDED_BY(mLock);
@@ -237,13 +261,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<int32_t>& 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 823d160f86..69b2315a6c 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 <input/InputDevice.h>
+#include "NotifyArgs.h"
#include <vector>
@@ -52,18 +52,22 @@ public:
virtual int32_t bumpGeneration() = 0;
virtual void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) = 0;
- virtual void dispatchExternalStylusState(const StylusState& outState) = 0;
+ [[nodiscard]] virtual std::list<NotifyArgs> dispatchExternalStylusState(
+ const StylusState& outState) = 0;
virtual InputReaderPolicyInterface* getPolicy() = 0;
- virtual InputListenerInterface& getListener() = 0;
virtual EventHubInterface* getEventHub() = 0;
virtual int32_t getNextId() = 0;
virtual void updateLedMetaState(int32_t metaState) = 0;
virtual int32_t getLedMetaState() = 0;
+
+ virtual void setPreventingTouchpadTaps(bool prevent) = 0;
+ virtual bool isPreventingTouchpadTaps() = 0;
+
+ virtual void setLastKeyDownTimestamp(nsecs_t when) = 0;
+ virtual nsecs_t getLastKeyDownTimestamp() = 0;
};
} // 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 17f158c9e1..d042784c9e 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 <input/Input.h>
@@ -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<float> 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 7de9b830b2..08eba31199 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 <android-base/unique_fd.h>
#include <input/TouchVideoFrame.h>
@@ -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 0000000000..061c6a3e75
--- /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 <sstream>
+
+#include <android-base/stringprintf.h>
+#include <gui/constants.h>
+#include <input/PrintTools.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+
+namespace android {
+
+namespace {
+
+int32_t actionWithIndex(int32_t action, int32_t index) {
+ return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+}
+
+template <typename T>
+size_t firstUnmarkedBit(T set) {
+ // TODO: replace with std::countr_one from <bit> 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<NotifyArgs> CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ std::vector<PointerCoords> coords;
+ std::vector<PointerProperties> properties;
+ std::map<size_t, size_t> 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<size_t> 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<PointerCoords>& coords,
+ const std::vector<PointerProperties>& 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 0000000000..9b6df7a222
--- /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 <bitset>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android/input.h>
+#include <input/Input.h>
+#include <utils/Timers.h>
+
+#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<NotifyArgs> process(const RawEvent& rawEvent);
+
+private:
+ void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+ int32_t evdevAxis) const;
+ [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+ const std::vector<PointerCoords>& coords,
+ const std::vector<PointerProperties>& 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<MAX_POINTER_ID + 1> mPointerIdsInUse;
+ std::map<size_t, int32_t> 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 f4f3ae95a1..58e35a6aba 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -20,6 +20,9 @@
#include "CursorInputMapper.h"
+#include <com_android_input_flags.h>
+#include <optional>
+
#include "CursorButtonAccumulator.h"
#include "CursorScrollAccumulator.h"
#include "PointerControllerInterface.h"
@@ -27,6 +30,8 @@
#include "input/PrintTools.h"
+namespace input_flags = com::android::input::flags;
+
namespace android {
// The default velocity control parameters that has no effect.
@@ -66,8 +71,11 @@ void CursorMotionAccumulator::finishSync() {
// --- CursorInputMapper ---
-CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext)
- : InputMapper(deviceContext) {}
+CursorInputMapper::CursorInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig)
+ : InputMapper(deviceContext, readerConfig),
+ mLastEventTime(std::numeric_limits<nsecs_t>::min()),
+ mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {}
CursorInputMapper::~CursorInputMapper() {
if (mPointerController != nullptr) {
@@ -79,30 +87,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 (!mBoundsInLogicalDisplay.isEmpty()) {
+ info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, mBoundsInLogicalDisplay.left,
+ mBoundsInLogicalDisplay.right, 0.0f, 0.0f, 0.0f);
+ info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, mBoundsInLogicalDisplay.top,
+ mBoundsInLogicalDisplay.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 +126,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 +139,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<NotifyArgs> CursorInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& readerConfig,
+ ConfigurationChanges changes) {
+ std::list<NotifyArgs> 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.string());
+CursorInputMapper::Parameters CursorInputMapper::computeParameters(
+ const InputDeviceContext& deviceContext) {
+ Parameters parameters;
+ parameters.mode = Parameters::Mode::POINTER;
+ const PropertyMap& config = deviceContext.getConfiguration();
+ std::optional<std::string> 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 +199,10 @@ void CursorInputMapper::dumpParameters(std::string& dump) {
dump += StringPrintf(INDENT4 "OrientationAware: %s\n", toString(mParameters.orientationAware));
}
-void CursorInputMapper::reset(nsecs_t when) {
+std::list<NotifyArgs> CursorInputMapper::reset(nsecs_t when) {
mButtonState = 0;
mDownTime = 0;
+ mLastEventTime = std::numeric_limits<nsecs_t>::min();
mPointerVelocityControl.reset();
mWheelXVelocityControl.reset();
@@ -285,23 +212,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<NotifyArgs> CursorInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
if (!mDisplayId) {
// Ignore events when there is no target display configured.
- return;
+ return out;
}
int32_t lastButtonState = mButtonState;
@@ -335,7 +270,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();
@@ -352,24 +287,22 @@ void CursorInputMapper::sync(nsecs_t when, nsecs_t readTime) {
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
if (mSource == AINPUT_SOURCE_MOUSE) {
- if (moved || scrolled || buttonsChanged) {
- mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
-
- if (moved) {
- mPointerController->move(deltaX, deltaY);
+ if (!mEnablePointerChoreographer) {
+ if (moved || scrolled || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::Presentation::POINTER);
+
+ if (moved) {
+ mPointerController->move(deltaX, deltaY);
+ }
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
}
- if (buttonsChanged) {
- mPointerController->setButtonState(currentButtonState);
- }
+ std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
- mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
}
-
- mPointerController->getPosition(&xCursorPosition, &yCursorPosition);
-
- pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
- pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
} else {
@@ -392,8 +325,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 +347,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 +386,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 +401,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 +434,128 @@ std::optional<int32_t> 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;
+ std::optional<DisplayViewport> resolvedViewport;
+ bool isBoundsSet = false;
+ if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
+ // This InputDevice is associated with a viewport.
+ // Only generate events for the associated display.
+ mDisplayId = assocViewport->displayId;
+ resolvedViewport = *assocViewport;
+ if (!mEnablePointerChoreographer) {
+ const bool mismatchedPointerDisplay =
+ isPointer && (assocViewport->displayId != mPointerController->getDisplayId());
+ if (mismatchedPointerDisplay) {
+ // This device's associated display doesn't match PointerController's current
+ // display. Do not associate it with any display.
+ mDisplayId.reset();
+ }
+ }
+ } else if (isPointer) {
+ // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
+ if (mEnablePointerChoreographer) {
+ // Always use DISPLAY_ID_NONE for mouse events.
+ // PointerChoreographer will make it target the correct the displayId later.
+ resolvedViewport = getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+ mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
+ } else {
+ mDisplayId = mPointerController->getDisplayId();
+ if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
+ resolvedViewport = *v;
+ }
+ if (auto bounds = mPointerController->getBounds(); bounds) {
+ mBoundsInLogicalDisplay = *bounds;
+ isBoundsSet = true;
+ }
+ }
+ }
+
+ mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) ||
+ mParameters.mode == Parameters::Mode::POINTER_RELATIVE || !resolvedViewport
+ ? ui::ROTATION_0
+ : getInverseRotation(resolvedViewport->orientation);
+
+ if (!isBoundsSet) {
+ mBoundsInLogicalDisplay = resolvedViewport
+ ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+ static_cast<float>(resolvedViewport->logicalTop),
+ static_cast<float>(resolvedViewport->logicalRight - 1),
+ static_cast<float>(resolvedViewport->logicalBottom - 1)}
+ : FloatRect{0, 0, 0, 0};
+ }
+
+ bumpGeneration();
+}
+
} // namespace android
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index 60b3dd9ee0..308adaa463 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 <PointerControllerInterface.h>
#include <input/VelocityControl.h>
+#include <ui/Rotation.h>
namespace android {
@@ -53,16 +53,20 @@ private:
class CursorInputMapper : public InputMapper {
public:
- explicit CursorInputMapper(InputDeviceContext& deviceContext);
+ template <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& readerConfig,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
virtual int32_t getScanCodeState(uint32_t sourceMask, int32_t scanCode) override;
@@ -115,19 +119,28 @@ 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<int32_t> mDisplayId;
- int32_t mOrientation;
+ ui::Rotation mOrientation{ui::ROTATION_0};
+ FloatRect mBoundsInLogicalDisplay{};
std::shared_ptr<PointerControllerInterface> mPointerController;
int32_t mButtonState;
nsecs_t mDownTime;
+ nsecs_t mLastEventTime;
- void configureParameters();
+ const bool mEnablePointerChoreographer;
+
+ 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);
+
+ [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
- void 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 6b5d37f8d5..987d2d0221 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<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> ExternalStylusInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> 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<float>(mSingleTouchMotionAccumulator.getAbsolutePressure());
+ mStylusState.pressure = (rawPressure - mRawPressureAxis.minValue) /
+ static_cast<float>(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 516aa51a14..97df02b69f 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 <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> 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 7b185e029c..0692dbbe0a 100644
--- a/services/inputflinger/reader/mapper/InputMapper.cpp
+++ b/services/inputflinger/reader/mapper/InputMapper.cpp
@@ -18,26 +18,37 @@
#include "InputMapper.h"
+#include <sstream>
+
#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<NotifyArgs> InputMapper::reconfigure(nsecs_t when, const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ return {};
+}
-void InputMapper::reset(nsecs_t when) {}
+std::list<NotifyArgs> InputMapper::reset(nsecs_t when) {
+ return {};
+}
-void InputMapper::timeoutExpired(nsecs_t when) {}
+std::list<NotifyArgs> 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<int32_t>& keyCodes,
+ uint8_t* outFlags) {
return false;
}
-void InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat, int32_t token) {}
+std::list<NotifyArgs> InputMapper::vibrate(const VibrationSequence& sequence, ssize_t repeat,
+ int32_t token) {
+ return {};
+}
-void InputMapper::cancelVibrate(int32_t token) {}
+std::list<NotifyArgs> InputMapper::cancelVibrate(int32_t token) {
+ return {};
+}
bool InputMapper::isVibrating() {
return false;
@@ -72,7 +88,9 @@ std::vector<int32_t> InputMapper::getVibratorIds() {
return {};
}
-void InputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {}
+std::list<NotifyArgs> 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<NotifyArgs> 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 fce6409b3f..06de4c25e3 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 <class T, class... Args>
+std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig, Args... args) {
+ // Using `new` to access non-public constructors.
+ std::unique_ptr<T> 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<NotifyArgs> 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 <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes);
+ [[nodiscard]] virtual std::list<NotifyArgs> reset(nsecs_t when);
+ [[nodiscard]] virtual std::list<NotifyArgs> process(const RawEvent* rawEvent) = 0;
+ [[nodiscard]] virtual std::list<NotifyArgs> 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<int32_t>& keyCodes,
+ uint8_t* outFlags);
+ [[nodiscard]] virtual std::list<NotifyArgs> vibrate(const VibrationSequence& sequence,
+ ssize_t repeat, int32_t token);
+ [[nodiscard]] virtual std::list<NotifyArgs> cancelVibrate(int32_t token);
virtual bool isVibrating();
virtual std::vector<int32_t> getVibratorIds();
- virtual void cancelTouch(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] virtual std::list<NotifyArgs> 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<NotifyArgs> updateExternalStylusState(const StylusState& state);
virtual std::optional<int32_t> 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 7d30d0c1eb..8a9ea75a97 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<NotifyArgs> JoystickInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ std::list<NotifyArgs> 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<NotifyArgs> JoystickInputMapper::reset(nsecs_t when) {
// Recenter all axes.
for (std::pair<const int32_t, Axis>& pair : mAxes) {
Axis& axis = pair.second;
axis.resetValue();
}
- InputMapper::reset(when);
+ return InputMapper::reset(when);
}
-void JoystickInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> JoystickInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> JoystickInputMapper::sync(nsecs_t when, nsecs_t readTime, bool force) {
+ std::list<NotifyArgs> 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 307bf5b38b..313f0922b7 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 <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> 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<int32_t, Axis> mAxes;
- void sync(nsecs_t when, nsecs_t readTime, bool force);
+ [[nodiscard]] std::list<NotifyArgs> 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 2ac81781fa..f068cc8aa4 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -20,90 +20,63 @@
#include "KeyboardInputMapper.h"
+#include <ui/Rotation.h>
+
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];
+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<size_t>(ui::ROTATION_0)] == keyCode) {
+ return rotation[static_cast<size_t>(orientation)];
}
}
}
- return value;
+ return keyCode;
}
-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];
- }
- }
- }
- return value;
-}
-
-// 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,27 @@ int32_t KeyboardInputMapper::getDisplayId() {
return ADISPLAY_ID_NONE;
}
-void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+std::optional<KeyboardLayoutInfo> KeyboardInputMapper::getKeyboardLayoutInfo() const {
+ if (mKeyboardLayoutInfo) {
+ return mKeyboardLayoutInfo;
+ }
+ std::optional<RawLayoutInfo> layoutInfo = getDeviceContext().getRawLayoutInfo();
+ if (!layoutInfo) {
+ return std::nullopt;
+ }
+ return KeyboardLayoutInfo(layoutInfo->languageTag, layoutInfo->layoutType);
+}
+
+void KeyboardInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
InputMapper::populateDeviceInfo(info);
- info->setKeyboardType(mKeyboardType);
- info->setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
+ info.setKeyboardType(mKeyboardType);
+ info.setKeyCharacterMap(getDeviceContext().getKeyCharacterMap());
+
+ std::optional keyboardLayoutInfo = getKeyboardLayoutInfo();
+ if (keyboardLayoutInfo) {
+ info.setKeyboardLayoutInfo(*keyboardLayoutInfo);
+ }
}
void KeyboardInputMapper::dump(std::string& dump) {
@@ -127,152 +116,120 @@ 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 += "<not set>\n";
+ }
}
std::optional<DisplayViewport> 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<NotifyArgs> KeyboardInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ std::list<NotifyArgs> 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<KeyboardLayoutInfo> newKeyboardLayoutInfo =
+ getValueByKey(config.keyboardLayoutAssociations, getDeviceContext().getLocation());
+ if (mKeyboardLayoutInfo != newKeyboardLayoutInfo) {
+ mKeyboardLayoutInfo = newKeyboardLayoutInfo;
+ // Also update keyboard layout overlay as soon as we find the new layout info
+ updateKeyboardLayoutOverlay();
+ bumpGeneration();
}
}
-}
-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");
+ if (!changes.any() || changes.test(InputReaderConfiguration::Change::KEYBOARD_LAYOUTS)) {
+ if (!getDeviceContext().getDeviceClasses().test(InputDeviceClass::VIRTUAL) &&
+ updateKeyboardLayoutOverlay()) {
+ bumpGeneration();
+ }
}
+ return out;
+}
- mParameters.handlesKeyRepeat = false;
- config.tryGetProperty(String8("keyboard.handlesKeyRepeat"), mParameters.handlesKeyRepeat);
+bool KeyboardInputMapper::updateKeyboardLayoutOverlay() {
+ std::shared_ptr<KeyCharacterMap> keyboardLayout =
+ getDeviceContext()
+ .getContext()
+ ->getPolicy()
+ ->getKeyboardLayoutOverlay(getDeviceContext().getDeviceIdentifier(),
+ getKeyboardLayoutInfo());
+ return getDeviceContext().setKeyboardLayoutOverlay(keyboardLayout);
+}
- mParameters.doNotWakeByDefault = false;
- config.tryGetProperty(String8("keyboard.doNotWakeByDefault"), mParameters.doNotWakeByDefault);
+void KeyboardInputMapper::configureParameters() {
+ const PropertyMap& config = getDeviceContext().getConfiguration();
+ 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<NotifyArgs> KeyboardInputMapper::reset(nsecs_t when) {
+ std::list<NotifyArgs> out = cancelAllDownKeys(when);
+ mHidUsageAccumulator.reset();
resetLedState();
- InputMapper::reset(when);
+ out += InputMapper::reset(when);
+ return out;
}
-void KeyboardInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> KeyboardInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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);
+ if (isSupportedScanCode(scanCode)) {
+ out += processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0,
+ scanCode, mHidUsageAccumulator.consumeCurrentHidUsage());
}
break;
}
- case EV_MSC: {
- if (rawEvent->code == MSC_SCAN) {
- mCurrentHidUsage = rawEvent->value;
- }
- break;
- }
- case EV_SYN: {
- if (rawEvent->code == SYN_REPORT) {
- mCurrentHidUsage = 0;
- }
- }
- }
-}
-
-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;
+ return out;
}
-void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,
- int32_t usageCode) {
+std::list<NotifyArgs> KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down,
+ int32_t scanCode, int32_t usageCode) {
+ std::list<NotifyArgs> out;
int32_t keyCode;
int32_t keyMetaState;
uint32_t policyFlags;
+ int32_t flags = AKEY_EVENT_FLAG_FROM_SYSTEM;
if (getDeviceContext().mapKey(scanCode, usageCode, mMetaState, &keyCode, &keyMetaState,
&policyFlags)) {
@@ -281,6 +238,8 @@ void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down,
policyFlags = 0;
}
+ nsecs_t downTime = when;
+ std::optional<size_t> keyDownIndex = findKeyDownIndex(scanCode);
if (down) {
// Rotate key codes according to orientation if needed.
if (mParameters.orientationAware) {
@@ -288,40 +247,44 @@ 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;
+ flags = mKeyDowns[*keyDownIndex].flags;
} 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);
+ flags |= AKEY_EVENT_FLAG_KEEP_TOUCH_MODE;
}
KeyDown keyDown;
keyDown.keyCode = keyCode;
keyDown.scanCode = scanCode;
+ keyDown.downTime = when;
+ keyDown.flags = flags;
mKeyDowns.push_back(keyDown);
}
-
- mDownTime = when;
+ onKeyDownProcessed(downTime);
} 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;
+ flags = mKeyDowns[*keyDownIndex].flags;
+ 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 +296,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 +309,21 @@ 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, flags,
+ keyCode, scanCode, keyMetaState, downTime));
+ return out;
}
-ssize_t KeyboardInputMapper::findKeyDown(int32_t scanCode) {
+std::optional<size_t> 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 +338,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<int32_t>& keyCodes,
+ uint8_t* outFlags) {
+ return getDeviceContext().markSupportedKeyCodes(keyCodes, outFlags);
}
int32_t KeyboardInputMapper::getMetaState() {
@@ -433,13 +393,12 @@ void KeyboardInputMapper::updateLedState(bool reset) {
mMetaState |= getContext()->getLedMetaState();
constexpr int32_t META_NUM = 3;
- const std::array<int32_t, META_NUM> keyCodes = {AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK,
- AKEYCODE_SCROLL_LOCK};
+ const std::vector<int32_t> keyCodes{AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK,
+ AKEYCODE_SCROLL_LOCK};
const std::array<int32_t, META_NUM> metaCodes = {AMETA_CAPS_LOCK_ON, AMETA_NUM_LOCK_ON,
AMETA_SCROLL_LOCK_ON};
std::array<uint8_t, META_NUM> 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 +432,37 @@ std::optional<int32_t> KeyboardInputMapper::getAssociatedDisplayId() {
return std::nullopt;
}
+std::list<NotifyArgs> KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) {
+ std::list<NotifyArgs> out;
+ size_t n = mKeyDowns.size();
+ for (size_t i = 0; i < n; i++) {
+ out.emplace_back(NotifyKeyArgs(getContext()->getNextId(), when,
+ systemTime(SYSTEM_TIME_MONOTONIC), getDeviceId(), mSource,
+ getDisplayId(), /*policyFlags=*/0, AKEY_EVENT_ACTION_UP,
+ mKeyDowns[i].flags | AKEY_EVENT_FLAG_CANCELED,
+ mKeyDowns[i].keyCode, mKeyDowns[i].scanCode, AMETA_NONE,
+ mKeyDowns[i].downTime));
+ }
+ mKeyDowns.clear();
+ mMetaState = AMETA_NONE;
+ return out;
+}
+
+void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
+ InputReaderContext& context = *getContext();
+ context.setLastKeyDownTimestamp(downTime);
+ if (context.isPreventingTouchpadTaps()) {
+ // avoid pinging java service unnecessarily, just fade pointer again if it became visible
+ context.fadePointer();
+ return;
+ }
+ // Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
+ // shortcuts
+ bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode);
+ if (shouldHideCursor && context.getPolicy()->isInputMethodConnectionActive()) {
+ context.fadePointer();
+ context.setPreventingTouchpadTaps(true);
+ }
+}
+
} // namespace android
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.h b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
index 378769639e..500256b21f 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.h
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.h
@@ -14,92 +14,100 @@
* 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<int32_t> getAssociatedDisplayId() override;
- virtual void updateLedState(bool reset);
+ template <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> 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<int32_t>& 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<int32_t> getAssociatedDisplayId() override;
+ void updateLedState(bool reset) override;
private:
// The current viewport.
- std::optional<DisplayViewport> mViewport;
+ std::optional<DisplayViewport> mViewport{};
struct KeyDown {
- int32_t keyCode;
- int32_t scanCode;
+ nsecs_t downTime{};
+ int32_t keyCode{};
+ int32_t scanCode{};
+ int32_t flags{};
};
- uint32_t mSource;
- int32_t mKeyboardType;
+ uint32_t mSource{};
+ int32_t mKeyboardType{};
+ std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo;
- std::vector<KeyDown> mKeyDowns; // keys that are down
- int32_t mMetaState;
- nsecs_t mDownTime; // time of most recent key down
+ std::vector<KeyDown> 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<NotifyArgs> 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<size_t> findKeyDownIndex(int32_t scanCode);
+ std::optional<KeyboardLayoutInfo> getKeyboardLayoutInfo() const;
+ bool updateKeyboardLayoutOverlay();
void resetLedState();
void initializeLedState(LedState& ledState, int32_t led);
void updateLedStateForModifier(LedState& ledState, int32_t led, int32_t modifier, bool reset);
- std::optional<DisplayViewport> findViewport(nsecs_t when,
- const InputReaderConfiguration* config);
+ std::optional<DisplayViewport> findViewport(const InputReaderConfiguration& readerConfig);
+ [[nodiscard]] std::list<NotifyArgs> cancelAllDownKeys(nsecs_t when);
+ void onKeyDownProcessed(nsecs_t downTime);
};
} // 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 41a8426e42..5a74a42446 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 <android/sysprop/InputProperties.sysprop.h>
+#include "MultiTouchInputMapper.h"
namespace android {
@@ -27,220 +26,27 @@ 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];
-}
-
-void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) {
- // Unfortunately there is no way to read the initial contents of the slots.
- // So when we reset the accumulator, we must assume they are all zeroes.
- 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;
- status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot);
- if (status) {
- ALOGD("Could not retrieve current multitouch slot index. status=%d", status);
- initialSlot = -1;
- }
- clearSlots(initialSlot);
- } else {
- clearSlots(-1);
- }
-}
-
-void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) {
- if (mSlots) {
- for (size_t i = 0; i < mSlotCount; i++) {
- mSlots[i].clear();
- }
- }
- mCurrentSlot = initialSlot;
-}
-
-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) {
- clearSlots(-1);
- }
-}
-
-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<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) {
+ // TODO(b/291626046): Sync the MT state with the kernel using EVIOCGMTSLOTS.
mMultiTouchMotionAccumulator.reset(getDeviceContext());
-
mPointerIdBits.clear();
- TouchInputMapper::reset(when);
+ return TouchInputMapper::reset(when);
}
-void MultiTouchInputMapper::process(const RawEvent* rawEvent) {
- TouchInputMapper::process(rawEvent);
+std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent);
mMultiTouchMotionAccumulator.process(rawEvent);
+ return out;
}
std::optional<int32_t> MultiTouchInputMapper::getActiveBitId(
@@ -264,14 +70,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<int32_t> id = getActiveBitId(*inSlot);
+ if (inSlot.getToolType() == ToolType::PALM) {
+ std::optional<int32_t> id = getActiveBitId(inSlot);
if (id) {
outState->rawPointerData.canceledIdBits.markBit(id.value());
}
@@ -292,38 +98,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 (mShouldSimulateStylusWithTouch && 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();) {
@@ -358,6 +175,18 @@ void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) {
mMultiTouchMotionAccumulator.finishSync();
}
+std::list<NotifyArgs> MultiTouchInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ const bool simulateStylusWithTouch =
+ sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
+ if (simulateStylusWithTouch != mShouldSimulateStylusWithTouch) {
+ mShouldSimulateStylusWithTouch = simulateStylusWithTouch;
+ bumpGeneration();
+ }
+ return TouchInputMapper::reconfigure(when, config, changes);
+}
+
void MultiTouchInputMapper::configureRawPointerAxes() {
TouchInputMapper::configureRawPointerAxes();
@@ -383,23 +212,16 @@ 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() ||
- shouldSimulateStylusWithTouch();
-}
-
-bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const {
- static const bool SIMULATE_STYLUS_WITH_TOUCH =
- sysprop::InputProperties::simulate_stylus_with_touch().value_or(false);
- return SIMULATE_STYLUS_WITH_TOUCH &&
- mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN;
+ return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() ||
+ mShouldSimulateStylusWithTouch;
}
} // namespace android
diff --git a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
index b7c3457285..5c173f365e 100644
--- a/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/MultiTouchInputMapper.h
@@ -14,89 +14,27 @@
* 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 reset(InputDeviceContext& deviceContext);
- 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 clearSlots(int32_t initialSlot);
- void warnIfNotInUse(const RawEvent& event, const Slot& slot);
-};
-
class MultiTouchInputMapper : public TouchInputMapper {
public:
- explicit MultiTouchInputMapper(InputDeviceContext& deviceContext);
+ template <class T, class... Args>
+ friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig,
+ Args... args);
+
~MultiTouchInputMapper() override;
- void reset(nsecs_t when) override;
- void process(const RawEvent* rawEvent) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+ [[nodiscard]] std::list<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
protected:
void syncTouch(nsecs_t when, RawState* outState) override;
@@ -104,13 +42,8 @@ protected:
bool hasStylus() const override;
private:
- // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
- // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
- // It is used to simulate stylus events for debugging and testing on a device that does not
- // support styluses. It can be enabled using
- // "adb shell setprop persist.debug.input.simulate_stylus_with_touch true",
- // and requires a reboot to take effect.
- inline bool shouldSimulateStylusWithTouch() const;
+ explicit MultiTouchInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig);
// If the slot is in use, return the bit id. Return std::nullopt otherwise.
std::optional<int32_t> getActiveBitId(const MultiTouchMotionAccumulator::Slot& inSlot);
@@ -119,8 +52,17 @@ 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};
+
+ // simulate_stylus_with_touch is a debug mode that converts all finger pointers reported by this
+ // mapper's touchscreen into stylus pointers, and adds SOURCE_STYLUS to the input device.
+ // It is used to simulate stylus events for debugging and testing on a device that does not
+ // support styluses. It can be enabled using
+ // "adb shell setprop debug.input.simulate_stylus_with_touch true".
+ // After enabling, the touchscreen will need to be reconfigured. A reconfiguration usually
+ // happens when turning the screen on/off or by rotating the device orientation.
+ bool mShouldSimulateStylusWithTouch{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 eca25f6e6a..07ae5b1cac 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.cpp
@@ -20,12 +20,16 @@
#include "RotaryEncoderInputMapper.h"
+#include <utils/Timers.h>
+#include <optional>
+
#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 +39,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<float> 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<float> 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);
}
}
@@ -58,79 +63,99 @@ void RotaryEncoderInputMapper::dump(std::string& dump) {
dump += INDENT2 "Rotary Encoder Input Mapper:\n";
dump += StringPrintf(INDENT3 "HaveWheel: %s\n",
toString(mRotaryEncoderScrollAccumulator.haveRelativeVWheel()));
+ dump += StringPrintf(INDENT3 "HaveSlopController: %s\n", toString(mSlopController != nullptr));
}
-void RotaryEncoderInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
- uint32_t changes) {
- InputMapper::configure(when, config, changes);
- if (!changes) {
+std::list<NotifyArgs> RotaryEncoderInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ std::list<NotifyArgs> out = InputMapper::reconfigure(when, config, changes);
+ if (!changes.any()) {
mRotaryEncoderScrollAccumulator.configure(getDeviceContext());
+
+ const PropertyMap& propertyMap = getDeviceContext().getConfiguration();
+ float slopThreshold = propertyMap.getInt("rotary_encoder.slop_threshold").value_or(0);
+ int32_t slopDurationNs = milliseconds_to_nanoseconds(
+ propertyMap.getInt("rotary_encoder.slop_duration_ms").value_or(0));
+ if (slopThreshold > 0 && slopDurationNs > 0) {
+ mSlopController = std::make_unique<SlopController>(slopThreshold, slopDurationNs);
+ } else {
+ mSlopController = nullptr;
+ }
}
- if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+ if (!changes.any() || changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
std::optional<DisplayViewport> 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<NotifyArgs> RotaryEncoderInputMapper::reset(nsecs_t when) {
mRotaryEncoderScrollAccumulator.reset(getDeviceContext());
- InputMapper::reset(when);
+ return InputMapper::reset(when);
}
-void RotaryEncoderInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> RotaryEncoderInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> RotaryEncoderInputMapper::sync(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> 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 (mSlopController) {
+ scroll = mSlopController->consumeEvent(when, scroll);
}
- if (mOrientation == DISPLAY_ORIENTATION_180) {
- scroll = -scroll;
- }
+ bool scrolled = scroll != 0;
// 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 1859355a93..fe5d152cef 100644
--- a/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
+++ b/services/inputflinger/reader/mapper/RotaryEncoderInputMapper.h
@@ -14,37 +14,44 @@
* limitations under the License.
*/
-#ifndef _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H
-#define _UI_INPUTREADER_ROTARY_ENCODER_INPUT_MAPPER_H
+#pragma once
+
+#include <ui/Rotation.h>
#include "CursorScrollAccumulator.h"
#include "InputMapper.h"
+#include "SlopController.h"
namespace android {
class RotaryEncoderInputMapper : public InputMapper {
public:
- explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext);
+ template <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
private:
CursorScrollAccumulator mRotaryEncoderScrollAccumulator;
int32_t mSource;
float mScalingFactor;
- int32_t mOrientation;
+ ui::Rotation mOrientation;
+ std::unique_ptr<SlopController> mSlopController;
- void sync(nsecs_t when, nsecs_t readTime);
+ explicit RotaryEncoderInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig);
+ [[nodiscard]] std::list<NotifyArgs> 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 b01c2bc13f..a131e3598f 100644
--- a/services/inputflinger/reader/mapper/SensorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/SensorInputMapper.cpp
@@ -52,8 +52,9 @@ static void convertFromLinuxToAndroid(std::vector<float>& 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 <typename T>
-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<NotifyArgs> SensorInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ std::list<NotifyArgs> 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<NotifyArgs> SensorInputMapper::reset(nsecs_t when) {
// Recenter all axes.
for (std::pair<const int32_t, Axis>& 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<int32_t> 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<uint32_t>(mscTime);
}
-void SensorInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> SensorInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> SensorInputMapper::sync(nsecs_t when, bool force) {
+ std::list<NotifyArgs> 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 27a61771f2..a55dcd1905 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 <optional>
+#include <string>
#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 <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> 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<InputDeviceSensorType, Sensor> mSensors;
- void sync(nsecs_t when, bool force);
-
- template <typename T>
- bool tryGetProperty(std::string keyName, T& outValue);
+ [[nodiscard]] std::list<NotifyArgs> 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 4fff9bebb5..ed0e27067f 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<NotifyArgs> 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<NotifyArgs> SingleTouchInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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 9cb3f67f20..7726bfb159 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,15 @@ namespace android {
class SingleTouchInputMapper : public TouchInputMapper {
public:
- explicit SingleTouchInputMapper(InputDeviceContext& deviceContext);
+ template <class T, class... Args>
+ friend std::unique_ptr<T> createInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig,
+ Args... args);
+
~SingleTouchInputMapper() override;
- void reset(nsecs_t when) override;
- void process(const RawEvent* rawEvent) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
protected:
void syncTouch(nsecs_t when, RawState* outState) override;
@@ -37,8 +40,8 @@ protected:
private:
SingleTouchMotionAccumulator mSingleTouchMotionAccumulator;
+ explicit SingleTouchInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig);
};
} // namespace android
-
-#endif // _UI_INPUTREADER_SINGLE_TOUCH_INPUT_MAPPER_H \ No newline at end of file
diff --git a/services/inputflinger/reader/mapper/SlopController.cpp b/services/inputflinger/reader/mapper/SlopController.cpp
new file mode 100644
index 0000000000..f79219f151
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SlopController.cpp
@@ -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.
+ */
+
+// clang-format off
+#include "../Macros.h"
+// clang-format on
+
+#include "SlopController.h"
+
+namespace {
+int signOf(float value) {
+ if (value == 0) return 0;
+ if (value > 0) return 1;
+ return -1;
+}
+} // namespace
+
+namespace android {
+
+SlopController::SlopController(float slopThreshold, nsecs_t slopDurationNanos)
+ : mSlopThreshold(slopThreshold), mSlopDurationNanos(slopDurationNanos) {}
+
+float SlopController::consumeEvent(nsecs_t eventTimeNanos, float value) {
+ if (mSlopDurationNanos == 0) {
+ return value;
+ }
+
+ if (shouldResetSlopTracking(eventTimeNanos, value)) {
+ mCumulativeValue = 0;
+ mHasSlopBeenMet = false;
+ }
+
+ mLastEventTimeNanos = eventTimeNanos;
+
+ if (mHasSlopBeenMet) {
+ // Since slop has already been met, we know that all of the current value would pass the
+ // slop threshold. So return that, without any further processing.
+ return value;
+ }
+
+ mCumulativeValue += value;
+
+ if (abs(mCumulativeValue) >= mSlopThreshold) {
+ mHasSlopBeenMet = true;
+ // Return the amount of value that exceeds the slop.
+ return signOf(value) * (abs(mCumulativeValue) - mSlopThreshold);
+ }
+
+ return 0;
+}
+
+bool SlopController::shouldResetSlopTracking(nsecs_t eventTimeNanos, float value) const {
+ const nsecs_t ageNanos = eventTimeNanos - mLastEventTimeNanos;
+ if (ageNanos >= mSlopDurationNanos) {
+ return true;
+ }
+ if (value == 0) {
+ return false;
+ }
+ if (signOf(mCumulativeValue) != signOf(value)) {
+ return true;
+ }
+ return false;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/SlopController.h b/services/inputflinger/reader/mapper/SlopController.h
new file mode 100644
index 0000000000..c1064102e8
--- /dev/null
+++ b/services/inputflinger/reader/mapper/SlopController.h
@@ -0,0 +1,54 @@
+/*
+ * 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 <utils/Timers.h>
+
+namespace android {
+
+/**
+ * Controls a slop logic. Slop here refers to an approach to try and drop insignificant input
+ * events. This is helpful in cases where unintentional input events may cause unintended outcomes,
+ * like scrolling a screen or keeping the screen awake.
+ *
+ * Current slop logic:
+ * "If time since last event > Xns, then discard the next N values."
+ */
+class SlopController final {
+public:
+ SlopController(float slopThreshold, nsecs_t slopDurationNanos);
+
+ /**
+ * Consumes an event with a given time and value for slop processing.
+ * Returns an amount <=value that should be consumed.
+ */
+ float consumeEvent(nsecs_t eventTime, float value);
+
+private:
+ bool shouldResetSlopTracking(nsecs_t eventTimeNanos, float value) const;
+
+ /** The amount of event values ignored after an inactivity of the slop duration. */
+ const float mSlopThreshold;
+ /** The duration of inactivity that resets slop controlling. */
+ const nsecs_t mSlopDurationNanos;
+
+ nsecs_t mLastEventTimeNanos = 0;
+ float mCumulativeValue = 0;
+ bool mHasSlopBeenMet = false;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/SwitchInputMapper.cpp b/services/inputflinger/reader/mapper/SwitchInputMapper.cpp
index ebb5de66ed..05338da146 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<NotifyArgs> SwitchInputMapper::process(const RawEvent* rawEvent) {
+ std::list<NotifyArgs> 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<NotifyArgs> SwitchInputMapper::sync(nsecs_t when) {
+ std::list<NotifyArgs> 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 64b9aa27b4..2fb48bbf25 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 <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> 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<NotifyArgs> 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 0000000000..c12e95dfa0
--- /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 <input/DisplayViewport.h>
+#include <stdint.h>
+#include <ui/Rotation.h>
+
+#include "EventHub.h"
+#include "InputListener.h"
+#include "InputReaderContext.h"
+
+namespace android {
+
+namespace {
+
+[[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> 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<nsecs_t /*eventTime*/, nsecs_t /*readTime*/> 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 31a3d2e172..3023e686c2 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 <input/DisplayViewport.h>
#include <stdint.h>
+#include <ui/Rotation.h>
#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<NotifyArgs> 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<nsecs_t /*eventTime*/, nsecs_t /*readTime*/> 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 2ddacef02d..3c26d1d73e 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -21,31 +21,44 @@
#include "TouchInputMapper.h"
#include <ftl/enum.h>
+#include <input/PrintTools.h>
#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 <typename T>
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,125 +119,82 @@ 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() {}
uint32_t TouchInputMapper::getSources() const {
- return mSource;
+ // The SOURCE_BLUETOOTH_STYLUS is added to events dynamically if the current stream is modified
+ // by the external stylus state. That's why we don't add it directly to mSource during
+ // configuration.
+ return mSource | (hasExternalStylus() ? AINPUT_SOURCE_BLUETOOTH_STYLUS : 0);
}
-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 +208,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 +231,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 +251,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 +263,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\n",
+ mExternalStylusButtonsApplied);
dump += INDENT3 "External Stylus State:\n";
dumpStylusState(dump, mExternalStylusState);
@@ -344,19 +289,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<NotifyArgs> TouchInputMapper::reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) {
+ std::list<NotifyArgs> 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 +315,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 +362,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.string());
+ const PropertyMap& config = deviceContext.getConfiguration();
+ std::optional<std::string> 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.string());
- }
- }
+ 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<std::string> 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.string());
+ } 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<int32_t> usiVersionMajor = config.getInt("touch.usiVersionMajor");
+ std::optional<int32_t> 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 +491,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 +602,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 +613,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 +672,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 +698,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 +707,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 +744,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 +782,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<float>(mRawPointerAxes.x.flat),
+ static_cast<float>(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<float>(mRawPointerAxes.x.fuzz),
+ static_cast<float>(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<float>(mRawPointerAxes.x.resolution),
+ static_cast<float>(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<float>(mViewport.deviceWidth) / rotatedRawSize.width;
+ const float yScale = static_cast<float>(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) {
@@ -901,9 +935,6 @@ void TouchInputMapper::configureInputDevice(nsecs_t when, bool* outResetNeeded)
if (hasStylus()) {
mSource |= AINPUT_SOURCE_STYLUS;
}
- if (hasExternalStylus()) {
- mSource |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
- }
} else if (mParameters.deviceType == Parameters::DeviceType::TOUCH_NAVIGATION) {
mSource = AINPUT_SOURCE_TOUCH_NAVIGATION;
mDeviceMode = DeviceMode::NAVIGATION;
@@ -926,18 +957,33 @@ 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;
+ bool viewportChanged;
+ if (mParameters.enableForInactiveViewport) {
+ // When touch is enabled for an inactive viewport, ignore the
+ // viewport active status when checking whether the viewport has
+ // changed.
+ DisplayViewport tempViewport = mViewport;
+ tempViewport.isActive = newViewport.isActive;
+ viewportChanged = tempViewport != newViewport;
+ } else {
+ viewportChanged = mViewport != newViewport;
+ }
+
bool skipViewportUpdate = false;
if (viewportChanged) {
const bool viewportOrientationChanged = mViewport.orientation != newViewport.orientation;
@@ -945,96 +991,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<int32_t>(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;
- }
-
- mPhysicalWidth = naturalPhysicalWidth;
- mPhysicalHeight = naturalPhysicalHeight;
- mPhysicalLeft = naturalPhysicalLeft;
- mPhysicalTop = naturalPhysicalTop;
+ const auto oldDisplayBounds = mDisplayBounds;
- 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<int32_t>(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 +1032,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 +1059,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 +1073,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 +1092,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 +1110,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 +1151,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 +1187,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<std::string> 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.string());
+ } 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<std::string> 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.string());
+ 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<std::string> 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.string());
+ orientationCalibrationString->c_str());
}
}
// Distance
out.distanceCalibration = Calibration::DistanceCalibration::DEFAULT;
- String8 distanceCalibrationString;
- if (in.tryGetProperty(String8("touch.distance.calibration"), distanceCalibrationString)) {
- if (distanceCalibrationString == "none") {
+ std::optional<std::string> 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.string());
+ 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.string());
- }
- }
+ out.distanceScale = in.getFloat("touch.distance.scale");
}
void TouchInputMapper::resolveCalibration() {
@@ -1312,11 +1298,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 +1306,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 +1334,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 +1365,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 +1386,13 @@ void TouchInputMapper::updateAffineTransformation() {
mInputDeviceOrientation);
}
-void TouchInputMapper::reset(nsecs_t when) {
+std::list<NotifyArgs> TouchInputMapper::reset(nsecs_t when) {
+ std::list<NotifyArgs> out = cancelTouch(when, when);
+ updateTouchSpots();
+
mCursorButtonAccumulator.reset(getDeviceContext());
mCursorScrollAccumulator.reset(getDeviceContext());
- mTouchButtonAccumulator.reset(getDeviceContext());
+ mTouchButtonAccumulator.reset();
mPointerVelocityControl.reset();
mWheelXVelocityControl.reset();
@@ -1447,14 +1420,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 +1436,24 @@ void TouchInputMapper::clearStylusDataPendingFlags() {
mExternalStylusFusionTimeout = LLONG_MAX;
}
-void TouchInputMapper::process(const RawEvent* rawEvent) {
+std::list<NotifyArgs> TouchInputMapper::process(const RawEvent* rawEvent) {
mCursorButtonAccumulator.process(rawEvent);
mCursorScrollAccumulator.process(rawEvent);
mTouchButtonAccumulator.process(rawEvent);
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ if (mDeviceMode == DeviceMode::DISABLED) {
+ // Only save the last pending state when the device is disabled.
+ mRawStatesPending.clear();
+ }
// Push a new state.
mRawStatesPending.emplace_back();
@@ -1482,8 +1463,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();
@@ -1497,19 +1479,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() &&
@@ -1518,16 +1503,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<NotifyArgs> TouchInputMapper::processRawTouches(bool timeout) {
+ std::list<NotifyArgs> 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
@@ -1547,12 +1531,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);
@@ -1562,20 +1546,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<NotifyArgs> TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
// Always start with a clean state.
mCurrentCookedState.clear();
@@ -1601,7 +1586,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();
}
@@ -1614,9 +1601,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) {
@@ -1624,13 +1611,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);
}
}
@@ -1638,8 +1624,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);
}
}
@@ -1658,15 +1643,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) {
@@ -1675,17 +1660,22 @@ 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);
+
+ if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
+ mCurrentStreamModifiedByExternalStylus = false;
+ }
// 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() {
@@ -1703,10 +1693,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);
}
@@ -1716,29 +1706,49 @@ 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;
+
+ if (mExternalStylusButtonsApplied != 0 || releasedButtons != 0) {
+ mCurrentStreamModifiedByExternalStylus = true;
+ }
}
}
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);
+ mCurrentStreamModifiedByExternalStylus = true;
+
+ 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;
}
}
@@ -1747,86 +1757,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;
}
- return false;
+ if (timeout) {
+ ALOGD_IF(DEBUG_STYLUS_FUSION, "Timeout expired, assuming touch is not a stylus.");
+ mFusedStylusPointerId.reset();
+ mExternalStylusFusionTimeout = LLONG_MAX;
+ 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<NotifyArgs> TouchInputMapper::timeoutExpired(nsecs_t when) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::updateExternalStylusState(const StylusState& state) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::consumeRawTouches(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags, bool& outConsumed) {
+ outConsumed = false;
+ std::list<NotifyArgs> 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) {
@@ -1836,7 +1863,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;
}
}
@@ -1846,13 +1874,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;
}
}
@@ -1862,11 +1910,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) {
@@ -1879,17 +1927,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;
}
}
@@ -1911,43 +1960,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<NotifyArgs> TouchInputMapper::abortTouches(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> 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;
+}
+
+// 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 = curInProperties;
+ changed = true;
+ }
+
+ if (curInCoords != curOutCoords) {
+ curOutCoords = curInCoords;
+ changed = true;
+ }
+ }
+ return changed;
}
-void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
+std::list<NotifyArgs> TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> out;
BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
int32_t metaState = getContext()->getGlobalMetaState();
@@ -1957,12 +2043,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
@@ -1992,12 +2080,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);
}
@@ -2007,12 +2099,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.
@@ -2025,59 +2119,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<NotifyArgs> TouchInputMapper::dispatchHoverExit(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::dispatchButtonRelease(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> out;
BitSet32 releasedButtons(mLastCookedState.buttonState & ~mCurrentCookedState.buttonState);
const BitSet32& idBits = findActiveIdBits(mLastCookedState.cookedPointerData);
const int32_t metaState = getContext()->getGlobalMetaState();
@@ -2085,16 +2195,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<NotifyArgs> TouchInputMapper::dispatchButtonPress(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> out;
BitSet32 pressedButtons(mCurrentCookedState.buttonState & ~mLastCookedState.buttonState);
const BitSet32& idBits = findActiveIdBits(mCurrentCookedState.cookedPointerData);
const int32_t metaState = getContext()->getGlobalMetaState();
@@ -2102,13 +2217,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<NotifyArgs> TouchInputMapper::dispatchGestureButtonRelease(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::dispatchGestureButtonPress(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime) {
+ std::list<NotifyArgs> 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) {
@@ -2178,7 +2343,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;
@@ -2204,13 +2369,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;
@@ -2237,20 +2405,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;
@@ -2277,79 +2445,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);
@@ -2357,23 +2462,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);
}
@@ -2390,54 +2488,62 @@ void TouchInputMapper::cookPointerData() {
}
}
-void TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
- PointerUsage pointerUsage) {
+std::list<NotifyArgs> TouchInputMapper::dispatchPointerUsage(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags,
+ PointerUsage pointerUsage) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::abortPointerUsage(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> 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<NotifyArgs> TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags,
+ bool isTimeout) {
+ std::list<NotifyArgs> out;
// Update current gesture coordinates.
bool cancelPreviousGesture, finishPreviousGesture;
bool sendEvents =
preparePointerGestures(when, &cancelPreviousGesture, &finishPreviousGesture, isTimeout);
if (!sendEvents) {
- return;
+ return {};
}
if (finishPreviousGesture) {
cancelPreviousGesture = false;
@@ -2451,8 +2557,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());
}
@@ -2494,6 +2600,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;
@@ -2530,11 +2640,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 {
@@ -2546,14 +2660,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);
}
@@ -2562,12 +2683,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.
@@ -2582,35 +2704,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();
@@ -2618,12 +2748,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.
@@ -2635,25 +2766,34 @@ void TouchInputMapper::dispatchPointerGestures(nsecs_t when, nsecs_t readTime, u
for (BitSet32 idBits(mPointerGesture.currentGestureIdBits); !idBits.isEmpty();) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = mPointerGesture.currentGestureIdToIndex[id];
- mPointerGesture.lastGestureProperties[index].copyFrom(
- mPointerGesture.currentGestureProperties[index]);
- mPointerGesture.lastGestureCoords[index].copyFrom(
- mPointerGesture.currentGestureCoords[index]);
+ mPointerGesture.lastGestureProperties[index] =
+ mPointerGesture.currentGestureProperties[index];
+ mPointerGesture.lastGestureCoords[index] = mPointerGesture.currentGestureCoords[index];
mPointerGesture.lastGestureIdToIndex[id] = index;
}
}
+ return out;
}
-void TouchInputMapper::abortPointerGestures(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
+std::list<NotifyArgs> 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<NotifyArgs> 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.
@@ -2665,6 +2805,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,
@@ -2674,9 +2815,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) {
@@ -2685,9 +2824,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;
@@ -2708,17 +2845,15 @@ bool TouchInputMapper::preparePointerGestures(nsecs_t when, bool* outCancelPrevi
// Update the velocity tracker.
{
- std::vector<VelocityTracker::Position> 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
@@ -2734,60 +2869,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;
}
@@ -2811,11 +2910,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;
@@ -2825,51 +2922,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();
@@ -2877,7 +2948,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);
@@ -2895,13 +2966,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 +
@@ -2915,8 +2983,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);
@@ -2927,10 +2994,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) {
@@ -2947,9 +3012,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();
@@ -2964,56 +3027,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;
}
@@ -3021,15 +3063,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);
@@ -3044,391 +3085,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<int32_t, float> TouchInputMapper::getFastestFinger() {
+ int32_t bestId = -1;
+ float bestSpeed = mConfig.pointerGestureDragMinSwitchSpeed;
+ for (BitSet32 idBits(mCurrentCookedState.fingerIdBits); !idBits.isEmpty();) {
+ uint32_t id = idBits.clearFirstMarkedBit();
+ std::optional<float> vx =
+ mPointerGesture.velocityTracker.getVelocity(AMOTION_EVENT_AXIS_X, id);
+ std::optional<float> 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<float>(commonDeltaRawX) /
+ (mRawPointerAxes.x.maxValue - mRawPointerAxes.x.minValue);
+ float yOffset = static_cast<float>(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<NotifyArgs> TouchInputMapper::dispatchPointerStylus(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
mPointerSimple.currentCoords.clear();
mPointerSimple.currentProperties.clear();
@@ -3436,17 +3539,20 @@ 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);
- mPointerSimple.currentCoords.copyFrom(
- mCurrentCookedState.cookedPointerData.pointerCoords[index]);
+ float x = mCurrentCookedState.cookedPointerData.pointerCoords[index].getX();
+ float y = mCurrentCookedState.cookedPointerData.pointerCoords[index].getY();
+ // Styluses are configured specifically for one display. We only update the
+ // PointerController for this stylus if the PointerController is configured for
+ // the same display as this stylus,
+ if (getAssociatedDisplayId() == mViewport.displayId) {
+ mPointerController->setPosition(x, y);
+ std::tie(x, y) = mPointerController->getPosition();
+ }
+
+ mPointerSimple.currentCoords = mCurrentCookedState.cookedPointerData.pointerCoords[index];
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
mPointerSimple.currentProperties.id = 0;
@@ -3457,35 +3563,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<NotifyArgs> 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<NotifyArgs> 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();
}
@@ -3493,10 +3588,10 @@ void TouchInputMapper::dispatchPointerMouse(nsecs_t when, nsecs_t readTime, uint
down = isPointerDown(mCurrentRawState.buttonState);
hovering = !down;
- float x, y;
- mPointerController->getPosition(&x, &y);
- mPointerSimple.currentCoords.copyFrom(
- mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex]);
+ const auto [x, y] = mPointerController->getPosition();
+ const uint32_t currentIndex = mCurrentRawState.rawPointerData.idToIndex[id];
+ mPointerSimple.currentCoords =
+ mCurrentCookedState.cookedPointerData.pointerCoords[currentIndex];
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_X, x);
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, y);
mPointerSimple.currentCoords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE,
@@ -3511,58 +3606,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<NotifyArgs> TouchInputMapper::abortPointerMouse(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> 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) {
@@ -3571,25 +3675,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) {
@@ -3597,25 +3702,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) {
@@ -3625,58 +3731,77 @@ void TouchInputMapper::dispatchPointerSimple(nsecs_t when, nsecs_t readTime, uin
mWheelXVelocityControl.move(when, &hscroll, nullptr);
// Send scroll.
- PointerCoords pointerCoords;
- pointerCoords.copyFrom(mPointerSimple.currentCoords);
+ PointerCoords pointerCoords = mPointerSimple.currentCoords;
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.
if (down || hovering) {
- mPointerSimple.lastCoords.copyFrom(mPointerSimple.currentCoords);
- mPointerSimple.lastProperties.copyFrom(mPointerSimple.currentProperties);
+ mPointerSimple.lastCoords = mPointerSimple.currentCoords;
+ mPointerSimple.lastProperties = mPointerSimple.currentProperties;
+ mPointerSimple.displayId = displayId;
+ mPointerSimple.source = mSource;
+ 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();
-
- dispatchPointerSimple(when, readTime, policyFlags, false, false);
+std::list<NotifyArgs> TouchInputMapper::abortPointerSimple(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags) {
+ std::list<NotifyArgs> out;
+ if (mPointerSimple.down || mPointerSimple.hovering) {
+ int32_t metaState = getContext()->getGlobalMetaState();
+ 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) {
- PointerCoords pointerCoords[MAX_POINTERS];
- PointerProperties pointerProperties[MAX_POINTERS];
+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) {
+ std::vector<PointerCoords> pointerCoords;
+ std::vector<PointerProperties> pointerProperties;
uint32_t pointerCount = 0;
while (!idBits.isEmpty()) {
uint32_t id = idBits.clearFirstMarkedBit();
uint32_t index = idToIndex[id];
- pointerProperties[pointerCount].copyFrom(properties[index]);
- pointerCoords[pointerCount].copyFrom(coords[index]);
+ pointerProperties.push_back(properties[index]);
+ pointerCoords.push_back(coords[index]);
if (changedId >= 0 && id == uint32_t(changedId)) {
action |= pointerCount << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
}
- pointerCount += 1;
+ pointerCount++;
}
ALOG_ASSERT(pointerCount != 0);
@@ -3698,112 +3823,69 @@ void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t p
ALOG_ASSERT(false);
}
}
+ if (mCurrentStreamModifiedByExternalStylus) {
+ source |= AINPUT_SOURCE_BLUETOOTH_STYLUS;
+ }
+
+ const int32_t displayId = getAssociatedDisplayId().value_or(ADISPLAY_ID_NONE);
+ const bool showDirectStylusPointer = mConfig.stylusPointerIconEnabled &&
+ mDeviceMode == DeviceMode::DIRECT && isStylusEvent(source, pointerProperties) &&
+ mPointerController && displayId != ADISPLAY_ID_NONE &&
+ displayId == mPointerController->getDisplayId();
+ if (showDirectStylusPointer) {
+ switch (action & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ mPointerController->setPresentation(
+ PointerControllerInterface::Presentation::STYLUS_HOVER);
+ mPointerController
+ ->setPosition(mCurrentCookedState.cookedPointerData.pointerCoords[0].getX(),
+ mCurrentCookedState.cookedPointerData.pointerCoords[0]
+ .getY());
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+ break;
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ break;
+ }
+ }
+
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
if (mDeviceMode == DeviceMode::POINTER) {
- 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<TouchVideoFrame> 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.data(),
+ pointerCoords.data(), 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<NotifyArgs> TouchInputMapper::cancelTouch(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> 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;
@@ -3974,11 +4056,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;
}
}
@@ -3993,10 +4074,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);
}
}
@@ -4028,10 +4108,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<int32_t>& 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 c948f565d9..bd9371d263 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 <optional>
+#include <string>
#include <stdint.h>
+#include <ui/Rotation.h>
#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<PointerProperties, MAX_POINTERS>;
+using CoordsArray = std::array<PointerCoords, MAX_POINTERS>;
+using IdToIndexArray = std::array<uint32_t, MAX_POINTER_ID + 1>;
+
/* 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<Pointer, MAX_POINTERS> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> 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<int32_t>& 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<NotifyArgs> cancelTouch(nsecs_t when, nsecs_t readTime) override;
+ [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> updateExternalStylusState(
+ const StylusState& state) override;
std::optional<int32_t> getAssociatedDisplayId() override;
protected:
@@ -177,18 +190,18 @@ protected:
};
// Input sources and device mode.
- uint32_t mSource;
+ uint32_t mSource{0};
enum class DeviceMode {
DISABLED, // input is disabled
DIRECT, // direct mapping (touchscreen)
- UNSCALED, // unscaled mapping (touchpad)
+ UNSCALED, // unscaled mapping (e.g. captured touchpad)
NAVIGATION, // unscaled mapping with assist gesture (touch navigation)
- POINTER, // pointer mapping (pointer)
+ POINTER, // pointer mapping (e.g. uncaptured touchpad, drawing tablet)
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<InputDeviceUsiVersion> 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<float> sizeScale;
+ std::optional<float> sizeBias;
+ std::optional<bool> sizeIsSummed;
// Pressure
enum class PressureCalibration {
@@ -264,8 +271,7 @@ protected:
};
PressureCalibration pressureCalibration;
- bool havePressureScale;
- float pressureScale;
+ std::optional<float> pressureScale;
// Orientation
enum class OrientationCalibration {
@@ -285,26 +291,17 @@ protected:
};
DistanceCalibration distanceCalibration;
- bool haveDistanceScale;
- float distanceScale;
+ std::optional<float> 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,63 +312,33 @@ protected:
RawPointerAxes mRawPointerAxes;
struct RawState {
- nsecs_t when;
- nsecs_t readTime;
+ nsecs_t when{std::numeric_limits<nsecs_t>::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;
-
- 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<RawState> mRawStatesPending;
@@ -382,28 +349,37 @@ 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<uint32_t> 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 the current cooked pointer data was modified due to the state of an external stylus.
+ bool mCurrentStreamModifiedByExternalStylus{false};
// 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<PointerControllerInterface> mPointerController;
std::vector<VirtualKey> 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);
@@ -427,29 +403,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;
@@ -475,35 +453,29 @@ private:
InputDeviceInfo::MotionRange y;
InputDeviceInfo::MotionRange pressure;
- bool haveSize;
- InputDeviceInfo::MotionRange size;
+ std::optional<InputDeviceInfo::MotionRange> size;
- bool haveTouchSize;
- InputDeviceInfo::MotionRange touchMajor;
- InputDeviceInfo::MotionRange touchMinor;
+ std::optional<InputDeviceInfo::MotionRange> touchMajor;
+ std::optional<InputDeviceInfo::MotionRange> touchMinor;
- bool haveToolSize;
- InputDeviceInfo::MotionRange toolMajor;
- InputDeviceInfo::MotionRange toolMinor;
+ std::optional<InputDeviceInfo::MotionRange> toolMajor;
+ std::optional<InputDeviceInfo::MotionRange> toolMinor;
- bool haveOrientation;
- InputDeviceInfo::MotionRange orientation;
+ std::optional<InputDeviceInfo::MotionRange> orientation;
- bool haveDistance;
- InputDeviceInfo::MotionRange distance;
+ std::optional<InputDeviceInfo::MotionRange> distance;
- bool haveTilt;
- InputDeviceInfo::MotionRange tilt;
-
- OrientedRanges() { clear(); }
+ std::optional<InputDeviceInfo::MotionRange> 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;
@@ -527,13 +499,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 {
@@ -542,7 +516,7 @@ private:
STYLUS,
MOUSE,
};
- PointerUsage mPointerUsage;
+ PointerUsage mPointerUsage{PointerUsage::NONE};
struct PointerGesture {
enum class Mode {
@@ -630,15 +604,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;
@@ -712,6 +686,12 @@ private:
// Time the pointer last went down.
nsecs_t downTime;
+ // Values reported for the last pointer event.
+ uint32_t source;
+ int32_t displayId;
+ float lastCursorX;
+ float lastCursorY;
+
void reset() {
currentCoords.clear();
currentProperties.clear();
@@ -720,6 +700,10 @@ private:
down = false;
hovering = false;
downTime = 0;
+ source = 0;
+ displayId = ADISPLAY_ID_NONE;
+ lastCursorX = 0.f;
+ lastCursorY = 0.f;
}
} mPointerSimple;
@@ -737,43 +721,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<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+
+ [[nodiscard]] std::list<NotifyArgs> consumeRawTouches(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags, bool& outConsumed);
+ [[nodiscard]] std::list<NotifyArgs> processRawTouches(bool timeout);
+ [[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> dispatchTouches(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> dispatchHoverExit(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> dispatchHoverEnterAndMove(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> dispatchButtonRelease(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> dispatchButtonPress(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> dispatchGestureButtonPress(nsecs_t when,
+ uint32_t policyFlags,
+ BitSet32 idBits,
+ nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> abortTouches(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+
+ [[nodiscard]] std::list<NotifyArgs> dispatchPointerUsage(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags,
+ PointerUsage pointerUsage);
+ [[nodiscard]] std::list<NotifyArgs> abortPointerUsage(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+
+ [[nodiscard]] std::list<NotifyArgs> dispatchPointerGestures(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags,
+ bool isTimeout);
+ [[nodiscard]] std::list<NotifyArgs> 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<int32_t, float> 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<NotifyArgs> dispatchPointerStylus(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> abortPointerStylus(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+
+ [[nodiscard]] std::list<NotifyArgs> dispatchPointerMouse(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+ [[nodiscard]] std::list<NotifyArgs> abortPointerMouse(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags);
+
+ [[nodiscard]] std::list<NotifyArgs> dispatchPointerSimple(nsecs_t when, nsecs_t readTime,
+ uint32_t policyFlags, bool down,
+ bool hovering, int32_t displayId);
+ [[nodiscard]] std::list<NotifyArgs> 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);
@@ -782,18 +806,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();
@@ -806,10 +824,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 0000000000..34ca0b3767
--- /dev/null
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -0,0 +1,529 @@
+/*
+ * 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 <algorithm>
+#include <chrono>
+#include <iterator>
+#include <limits>
+#include <map>
+#include <optional>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
+#include <com_android_input_flags.h>
+#include <ftl/enum.h>
+#include <input/PrintTools.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+#include <stats_pull_atom_callback.h>
+#include <statslog.h>
+#include "TouchCursorInputMapperCommon.h"
+#include "TouchpadInputMapper.h"
+#include "gestures/HardwareProperties.h"
+#include "gestures/TimerProvider.h"
+#include "ui/Rotation.h"
+
+namespace input_flags = com::android::input::flags;
+
+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<CurveSegment> segments = {
+ {32.002, 3.19, 0},
+ {52.83, 4.79, -51.254},
+ {119.124, 7.28, -182.737},
+ {std::numeric_limits<double>::infinity(), 15.04, -1107.556},
+};
+
+const std::vector<double> sensitivityFactors = {1, 2, 4, 6, 7, 8, 9, 10,
+ 11, 12, 13, 14, 16, 18, 20};
+
+std::vector<double> createAccelerationCurveForSensitivity(int32_t sensitivity,
+ size_t propertySize) {
+ LOG_ALWAYS_FATAL_IF(propertySize < 4 * segments.size());
+ std::vector<double> 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;
+}
+
+void gestureInterpreterCallback(void* clientData, const Gesture* gesture) {
+ TouchpadInputMapper* mapper = static_cast<TouchpadInputMapper*>(clientData);
+ mapper->consumeGesture(gesture);
+}
+
+int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus, bool isUsiStylus) {
+ if (isUsiStylus) {
+ // This is a stylus connected over the Universal Stylus Initiative (USI) protocol.
+ // For metrics purposes, we treat this protocol as a separate bus.
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USI;
+ }
+
+ // When adding cases to this switch, also add them to the copy of this method in
+ // InputDeviceMetricsCollector.cpp.
+ // TODO(b/286394420): deduplicate this method with the one in InputDeviceMetricsCollector.cpp.
+ switch (linuxBus) {
+ case BUS_USB:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
+ case BUS_BLUETOOTH:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
+ default:
+ return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
+ }
+}
+
+class MetricsAccumulator {
+public:
+ static MetricsAccumulator& getInstance() {
+ static MetricsAccumulator sAccumulator;
+ return sAccumulator;
+ }
+
+ void recordFinger(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].fingers++; }
+
+ void recordPalm(const TouchpadInputMapper::MetricsIdentifier& id) { mCounters[id].palms++; }
+
+ // Checks whether a Gesture struct is for the end of a gesture that we log metrics for, and
+ // records it if so.
+ void processGesture(const TouchpadInputMapper::MetricsIdentifier& id, const Gesture& gesture) {
+ switch (gesture.type) {
+ case kGestureTypeFling:
+ if (gesture.details.fling.fling_state == GESTURES_FLING_START) {
+ // Indicates the end of a two-finger scroll gesture.
+ mCounters[id].twoFingerSwipeGestures++;
+ }
+ break;
+ case kGestureTypeSwipeLift:
+ mCounters[id].threeFingerSwipeGestures++;
+ break;
+ case kGestureTypeFourFingerSwipeLift:
+ mCounters[id].fourFingerSwipeGestures++;
+ break;
+ case kGestureTypePinch:
+ if (gesture.details.pinch.zoom_state == GESTURES_ZOOM_END) {
+ mCounters[id].pinchGestures++;
+ }
+ break;
+ default:
+ // We're not interested in any other gestures.
+ break;
+ }
+ }
+
+private:
+ MetricsAccumulator() {
+ AStatsManager_setPullAtomCallback(android::util::TOUCHPAD_USAGE, /*metadata=*/nullptr,
+ MetricsAccumulator::pullAtomCallback, /*cookie=*/nullptr);
+ }
+
+ ~MetricsAccumulator() { AStatsManager_clearPullAtomCallback(android::util::TOUCHPAD_USAGE); }
+
+ static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag,
+ AStatsEventList* outEventList,
+ void* cookie) {
+ LOG_ALWAYS_FATAL_IF(atomTag != android::util::TOUCHPAD_USAGE);
+ MetricsAccumulator& accumulator = MetricsAccumulator::getInstance();
+ accumulator.produceAtoms(outEventList);
+ accumulator.resetCounters();
+ return AStatsManager_PULL_SUCCESS;
+ }
+
+ void produceAtoms(AStatsEventList* outEventList) const {
+ for (auto& [id, counters] : mCounters) {
+ auto [busId, vendorId, productId, versionId] = id;
+ addAStatsEvent(outEventList, android::util::TOUCHPAD_USAGE, vendorId, productId,
+ versionId, linuxBusToInputDeviceBusEnum(busId, /*isUsi=*/false),
+ counters.fingers, counters.palms, counters.twoFingerSwipeGestures,
+ counters.threeFingerSwipeGestures, counters.fourFingerSwipeGestures,
+ counters.pinchGestures);
+ }
+ }
+
+ void resetCounters() { mCounters.clear(); }
+
+ // Stores the counters for a specific touchpad model. Fields have the same meanings as those of
+ // the TouchpadUsage atom; see that definition for detailed documentation.
+ struct Counters {
+ int32_t fingers = 0;
+ int32_t palms = 0;
+
+ int32_t twoFingerSwipeGestures = 0;
+ int32_t threeFingerSwipeGestures = 0;
+ int32_t fourFingerSwipeGestures = 0;
+ int32_t pinchGestures = 0;
+ };
+
+ // Metrics are aggregated by device model and version, so if two devices of the same model and
+ // version are connected at once, they will have the same counters.
+ std::map<TouchpadInputMapper::MetricsIdentifier, Counters> mCounters;
+};
+
+} // namespace
+
+TouchpadInputMapper::TouchpadInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig)
+ : InputMapper(deviceContext, readerConfig),
+ mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
+ mPointerController(getContext()->getPointerController(getDeviceId())),
+ mTimerProvider(*getContext()),
+ mStateConverter(deviceContext, mMotionAccumulator),
+ mGestureConverter(*getContext(), deviceContext, getDeviceId()),
+ mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()),
+ mMetricsId(metricsIdFromInputDeviceIdentifier(deviceContext.getDeviceIdentifier())),
+ mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
+ 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<GesturesPropProvider*>(&gesturePropProvider),
+ &mPropertyProvider);
+ if (input_flags::enable_gestures_library_timer_provider()) {
+ mGestureInterpreter->SetTimerProvider(const_cast<GesturesTimerProvider*>(
+ &kGestureTimerProvider),
+ &mTimerProvider);
+ }
+ mGestureInterpreter->SetCallback(gestureInterpreterCallback, this);
+}
+
+TouchpadInputMapper::~TouchpadInputMapper() {
+ if (mPointerController != nullptr) {
+ mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+
+ // The gesture interpreter's destructor will try to free its property and timer providers,
+ // calling PropertyProvider::freeProperty and TimerProvider::freeTimer using a raw pointers.
+ // Depending on the declaration order in TouchpadInputMapper.h, those providers may have already
+ // been freed, causing allocation errors or use-after-free bugs. Depending on declaration order
+ // to avoid this seems rather fragile, so explicitly clear the providers here to ensure all the
+ // freeProperty and freeTimer calls happen before the providers are destructed.
+ mGestureInterpreter->SetPropProvider(nullptr, nullptr);
+ mGestureInterpreter->SetTimerProvider(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 (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);
+ if (input_flags::enable_gestures_library_timer_provider()) {
+ dump += INDENT3 "Timer provider:\n";
+ dump += addLinePrefix(mTimerProvider.dump(), INDENT4);
+ } else {
+ dump += INDENT3 "Timer provider: disabled by flag\n";
+ }
+ dump += INDENT3 "Captured event converter:\n";
+ dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
+ dump += StringPrintf(INDENT3 "DisplayId: %s\n", toString(mDisplayId).c_str());
+}
+
+std::list<NotifyArgs> 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)) {
+ mDisplayId = ADISPLAY_ID_NONE;
+ std::optional<DisplayViewport> resolvedViewport;
+ std::optional<FloatRect> boundsInLogicalDisplay;
+ if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
+ // This InputDevice is associated with a viewport.
+ // Only generate events for the associated display.
+ mDisplayId = assocViewport->displayId;
+ resolvedViewport = *assocViewport;
+ if (!mEnablePointerChoreographer) {
+ const bool mismatchedPointerDisplay =
+ (assocViewport->displayId != mPointerController->getDisplayId());
+ if (mismatchedPointerDisplay) {
+ ALOGW("Touchpad \"%s\" associated viewport display does not match pointer "
+ "controller",
+ mDeviceContext.getName().c_str());
+ mDisplayId.reset();
+ }
+ }
+ } else {
+ // The InputDevice is not associated with a viewport, but it controls the mouse pointer.
+ if (mEnablePointerChoreographer) {
+ // Always use DISPLAY_ID_NONE for touchpad events.
+ // PointerChoreographer will make it target the correct the displayId later.
+ resolvedViewport =
+ getContext()->getPolicy()->getPointerViewportForAssociatedDisplay();
+ mDisplayId = resolvedViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
+ } else {
+ mDisplayId = mPointerController->getDisplayId();
+ if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
+ resolvedViewport = *v;
+ }
+ if (auto bounds = mPointerController->getBounds(); bounds) {
+ boundsInLogicalDisplay = *bounds;
+ }
+ }
+ }
+
+ mGestureConverter.setDisplayId(mDisplayId);
+ mGestureConverter.setOrientation(resolvedViewport
+ ? getInverseRotation(resolvedViewport->orientation)
+ : ui::ROTATION_0);
+
+ if (!boundsInLogicalDisplay) {
+ boundsInLogicalDisplay = resolvedViewport
+ ? FloatRect{static_cast<float>(resolvedViewport->logicalLeft),
+ static_cast<float>(resolvedViewport->logicalTop),
+ static_cast<float>(resolvedViewport->logicalRight - 1),
+ static_cast<float>(resolvedViewport->logicalBottom - 1)}
+ : FloatRect{0, 0, 0, 0};
+ }
+ mGestureConverter.setBoundsInLogicalDisplay(*boundsInLogicalDisplay);
+ }
+ 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<NotifyArgs> 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<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
+ mStateConverter.reset();
+ resetGestureInterpreter(when);
+ std::list<NotifyArgs> 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<stime_t>(std::chrono::nanoseconds(when)).count();
+ mResettingInterpreter = true;
+ mGestureInterpreter->PushHardwareState(&state);
+ mResettingInterpreter = false;
+}
+
+std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
+ if (mPointerCaptured) {
+ return mCapturedEventConverter.process(*rawEvent);
+ }
+ std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
+ if (state) {
+ updatePalmDetectionMetrics();
+ return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
+ } else {
+ return {};
+ }
+}
+
+void TouchpadInputMapper::updatePalmDetectionMetrics() {
+ std::set<int32_t> currentTrackingIds;
+ for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+ const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i);
+ if (!slot.isInUse()) {
+ continue;
+ }
+ currentTrackingIds.insert(slot.getTrackingId());
+ if (slot.getToolType() == ToolType::PALM) {
+ mPalmTrackingIds.insert(slot.getTrackingId());
+ }
+ }
+ std::vector<int32_t> liftedTouches;
+ std::set_difference(mLastFrameTrackingIds.begin(), mLastFrameTrackingIds.end(),
+ currentTrackingIds.begin(), currentTrackingIds.end(),
+ std::inserter(liftedTouches, liftedTouches.begin()));
+ for (int32_t trackingId : liftedTouches) {
+ if (mPalmTrackingIds.erase(trackingId) > 0) {
+ MetricsAccumulator::getInstance().recordPalm(mMetricsId);
+ } else {
+ MetricsAccumulator::getInstance().recordFinger(mMetricsId);
+ }
+ }
+ mLastFrameTrackingIds = currentTrackingIds;
+}
+
+std::list<NotifyArgs> TouchpadInputMapper::sendHardwareState(nsecs_t when, nsecs_t readTime,
+ SelfContainedHardwareState schs) {
+ ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "New hardware state: %s", schs.state.String().c_str());
+ mGestureInterpreter->PushHardwareState(&schs.state);
+ return processGestures(when, readTime);
+}
+
+std::list<NotifyArgs> TouchpadInputMapper::timeoutExpired(nsecs_t when) {
+ if (!input_flags::enable_gestures_library_timer_provider()) {
+ return {};
+ }
+ mTimerProvider.triggerCallbacks(when);
+ return processGestures(when, when);
+}
+
+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;
+ }
+ mGesturesToProcess.push_back(*gesture);
+}
+
+std::list<NotifyArgs> TouchpadInputMapper::processGestures(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out = {};
+ if (mDisplayId) {
+ MetricsAccumulator& metricsAccumulator = MetricsAccumulator::getInstance();
+ for (Gesture& gesture : mGesturesToProcess) {
+ out += mGestureConverter.handleGesture(when, readTime, gesture);
+ metricsAccumulator.processGesture(mMetricsId, gesture);
+ }
+ }
+ mGesturesToProcess.clear();
+ return out;
+}
+
+std::optional<int32_t> TouchpadInputMapper::getAssociatedDisplayId() {
+ return mDisplayId;
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
new file mode 100644
index 0000000000..ece0eca0e7
--- /dev/null
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -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.
+ */
+
+#pragma once
+
+#include <list>
+#include <memory>
+#include <set>
+#include <vector>
+
+#include <PointerControllerInterface.h>
+#include <utils/Timers.h>
+
+#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 "gestures/TimerProvider.h"
+
+#include "include/gestures.h"
+
+namespace android {
+
+class TouchpadInputMapper : public InputMapper {
+public:
+ template <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> reconfigure(nsecs_t when,
+ const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override;
+ [[nodiscard]] std::list<NotifyArgs> reset(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent* rawEvent) override;
+ [[nodiscard]] std::list<NotifyArgs> timeoutExpired(nsecs_t when) override;
+
+ void consumeGesture(const Gesture* gesture);
+
+ // A subset of InputDeviceIdentifier used for logging metrics, to avoid storing a copy of the
+ // strings in that bigger struct.
+ using MetricsIdentifier = std::tuple<uint16_t /*busId*/, uint16_t /*vendorId*/,
+ uint16_t /*productId*/, uint16_t /*version*/>;
+
+ std::optional<int32_t> getAssociatedDisplayId() override;
+
+private:
+ void resetGestureInterpreter(nsecs_t when);
+ explicit TouchpadInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig);
+ void updatePalmDetectionMetrics();
+ [[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
+ SelfContainedHardwareState schs);
+ [[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
+
+ std::unique_ptr<gestures::GestureInterpreter, void (*)(gestures::GestureInterpreter*)>
+ mGestureInterpreter;
+ std::shared_ptr<PointerControllerInterface> mPointerController;
+
+ PropertyProvider mPropertyProvider;
+ TimerProvider mTimerProvider;
+
+ // 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 mResettingInterpreter = false;
+ std::vector<Gesture> mGesturesToProcess;
+
+ static MetricsIdentifier metricsIdFromInputDeviceIdentifier(const InputDeviceIdentifier& id) {
+ return std::make_tuple(id.bus, id.vendor, id.product, id.version);
+ }
+ const MetricsIdentifier mMetricsId;
+ // Tracking IDs for touches on the pad in the last evdev frame.
+ std::set<int32_t> mLastFrameTrackingIds;
+ // Tracking IDs for touches that have at some point been reported as palms by the touchpad.
+ std::set<int32_t> mPalmTrackingIds;
+
+ const bool mEnablePointerChoreographer;
+
+ // The display that events generated by this mapper should target. This can be set to
+ // ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
+ // std::nullopt), all events will be ignored.
+ std::optional<int32_t> mDisplayId;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/VibratorInputMapper.cpp b/services/inputflinger/reader/mapper/VibratorInputMapper.cpp
index 33db527db1..8d78d0fd80 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<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> VibratorInputMapper::cancelVibrate(int32_t token) {
if (DEBUG_VIBRATOR) {
ALOGD("cancelVibrate: deviceId=%d, token=%d", getDeviceId(), token);
}
+ std::list<NotifyArgs> out;
if (mVibrating && mToken == token) {
- stopVibrating();
+ out.push_back(stopVibrating());
}
+ return out;
}
bool VibratorInputMapper::isVibrating() {
@@ -76,26 +82,29 @@ std::vector<int32_t> VibratorInputMapper::getVibratorIds() {
return getDeviceContext().getVibratorIds();
}
-void VibratorInputMapper::timeoutExpired(nsecs_t when) {
+std::list<NotifyArgs> VibratorInputMapper::timeoutExpired(nsecs_t when) {
+ std::list<NotifyArgs> out;
if (mVibrating) {
if (when >= mNextStepTime) {
- nextStep();
+ out += nextStep();
} else {
getContext()->requestTimeoutAtTime(mNextStepTime);
}
}
+ return out;
}
-void VibratorInputMapper::nextStep() {
+std::list<NotifyArgs> VibratorInputMapper::nextStep() {
if (DEBUG_VIBRATOR) {
ALOGD("nextStep: index=%d, vibrate deviceId=%d", (int)mIndex, getDeviceId());
}
+ std::list<NotifyArgs> 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 d3c22b60a8..9079c73f8e 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 <class T, class... Args>
+ friend std::unique_ptr<T> 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<NotifyArgs> 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<NotifyArgs> vibrate(const VibrationSequence& sequence, ssize_t repeat,
+ int32_t token) override;
+ [[nodiscard]] std::list<NotifyArgs> cancelVibrate(int32_t token) override;
virtual bool isVibrating() override;
virtual std::vector<int32_t> getVibratorIds() override;
- virtual void timeoutExpired(nsecs_t when) override;
+ [[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> 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 2d7d73b4a3..153236c177 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 9e159064fa..6960644a1d 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 <stdint.h>
@@ -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 16495599d7..ae1b7a32f4 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 <stdint.h>
@@ -56,5 +55,3 @@ private:
};
} // namespace android
-
-#endif // _UI_INPUTREADER_CURSOR_SCROLL_ACCUMULATOR_H \ No newline at end of file
diff --git a/libs/gui/include/gui/TransactionTracing.h b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp
index 9efba47a18..2da1d814fa 100644
--- a/libs/gui/include/gui/TransactionTracing.h
+++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright 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,28 +14,26 @@
* limitations under the License.
*/
-#pragma once
-
-#include <android/gui/BnTransactionTraceListener.h>
-#include <utils/Mutex.h>
+#include "HidUsageAccumulator.h"
namespace android {
-class TransactionTraceListener : public gui::BnTransactionTraceListener {
- static std::mutex sMutex;
- static sp<TransactionTraceListener> sInstance;
-
- TransactionTraceListener();
-
-public:
- static sp<TransactionTraceListener> getInstance();
-
- binder::Status onToggled(bool enabled) override;
-
- bool isTracingEnabled();
-
-private:
- bool mTracingEnabled = false;
-};
+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/libs/renderengine/gl/GLShadowTexture.h b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h
index 250a9d77d0..740a710483 100644
--- a/libs/renderengine/gl/GLShadowTexture.h
+++ b/services/inputflinger/reader/mapper/accumulator/HidUsageAccumulator.h
@@ -1,5 +1,5 @@
/*
- * Copyright 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.
@@ -16,29 +16,25 @@
#pragma once
-#include <renderengine/Texture.h>
+#include "EventHub.h"
+
#include <cstdint>
namespace android {
-namespace renderengine {
-namespace gl {
-class GLShadowTexture {
+/* Keeps track of the state of currently reported HID usage code. */
+class HidUsageAccumulator {
public:
- GLShadowTexture();
- ~GLShadowTexture();
+ explicit HidUsageAccumulator() = default;
+ inline void reset() { *this = HidUsageAccumulator(); }
- const Texture& getTexture();
+ void process(const RawEvent& rawEvent);
-private:
- static constexpr int SHADOW_TEXTURE_WIDTH = 128;
- static constexpr int SHADOW_TEXTURE_HEIGHT = 1;
+ /* This must be called when processing the `EV_KEY` event. Returns 0 if invalid. */
+ int32_t consumeCurrentHidUsage();
- GLuint mName;
- Texture mTexture;
- uint8_t mTextureData[SHADOW_TEXTURE_WIDTH];
+private:
+ int32_t mCurrentHidUsage{};
};
-} // namespace gl
-} // namespace renderengine
} // 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 0000000000..d06514acab
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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<Slot>(slotCount);
+ reset(deviceContext);
+}
+
+void MultiTouchMotionAccumulator::reset(const InputDeviceContext& deviceContext) {
+ resetSlots();
+
+ if (!mUsingSlotsProtocol) {
+ return;
+ }
+
+ // 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);
+ }
+}
+
+size_t MultiTouchMotionAccumulator::getActiveSlotsCount() const {
+ if (!mUsingSlotsProtocol) {
+ return mCurrentSlot < 0 ? 0 : mCurrentSlot;
+ }
+ return std::count_if(mSlots.begin(), mSlots.end(),
+ [](const Slot& slot) { return slot.mInUse; });
+}
+
+// --- 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 0000000000..5b55e3d599
--- /dev/null
+++ b/services/inputflinger/reader/mapper/accumulator/MultiTouchMotionAccumulator.h
@@ -0,0 +1,97 @@
+/*
+ * 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 <linux/input-event-codes.h>
+#include <stdint.h>
+#include <vector>
+
+#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();
+
+ size_t getActiveSlotsCount() const;
+ 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];
+ }
+ void reset(const InputDeviceContext& deviceContext);
+
+private:
+ int32_t mCurrentSlot;
+ std::vector<Slot> 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 4c011f16a7..93056f06e6 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 <stdint.h>
@@ -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 86153d3f5e..8c4bed3267 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 22ebb720d5..e829692206 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 <stdint.h>
+#include <cstdint>
+#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 0000000000..955210479f
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.cpp
@@ -0,0 +1,605 @@
+/*
+ * 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 <optional>
+#include <sstream>
+
+#include <android-base/stringprintf.h>
+#include <com_android_input_flags.h>
+#include <ftl/enum.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+#include <ui/FloatRect.h>
+
+#include "TouchCursorInputMapperCommon.h"
+#include "input/Input.h"
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+namespace {
+
+const bool ENABLE_TOUCHPAD_PALM_REJECTION = input_flags::enable_touchpad_typing_palm_rejection();
+
+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)),
+ mEnablePointerChoreographer(input_flags::enable_pointer_choreographer()) {
+ 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<NotifyArgs> GestureConverter::reset(nsecs_t when) {
+ std::list<NotifyArgs> 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 (!mBoundsInLogicalDisplay.isEmpty()) {
+ info.addMotionRange(AMOTION_EVENT_AXIS_X, SOURCE, mBoundsInLogicalDisplay.left,
+ mBoundsInLogicalDisplay.right, 0, 0, 0);
+ info.addMotionRange(AMOTION_EVENT_AXIS_Y, SOURCE, mBoundsInLogicalDisplay.top,
+ mBoundsInLogicalDisplay.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<NotifyArgs> GestureConverter::handleGesture(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ if (!mDisplayId) {
+ // Ignore gestures when there is no target display configured.
+ return {};
+ }
+
+ 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;
+ if (ENABLE_TOUCHPAD_PALM_REJECTION && (std::abs(deltaX) > 0 || std::abs(deltaY) > 0)) {
+ enableTapToClick();
+ }
+ rotateDelta(mOrientation, &deltaX, &deltaY);
+
+ mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+ mPointerController->move(deltaX, deltaY);
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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<NotifyArgs> GestureConverter::handleButtonsChange(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out = {};
+
+ mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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);
+
+ if (ENABLE_TOUCHPAD_PALM_REJECTION && mReaderContext.isPreventingTouchpadTaps()) {
+ enableTapToClick();
+ if (gesture.details.buttons.is_tap) {
+ // return early to prevent this tap
+ return out;
+ }
+ }
+
+ 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<NotifyArgs> 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));
+ // Send a HOVER_MOVE to tell the application that the mouse is hovering again.
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_HOVER_MOVE,
+ /*actionButton=*/0, newButtonState, /*pointerCount=*/1,
+ mFingerProps.data(), &coords, xCursorPosition,
+ yCursorPosition));
+ }
+ mButtonState = newButtonState;
+ return out;
+}
+
+std::list<NotifyArgs> GestureConverter::releaseAllButtons(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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<NotifyArgs> GestureConverter::handleScroll(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ std::list<NotifyArgs> out;
+ PointerCoords& coords = mFakeFingerCoords[0];
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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<NotifyArgs> 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.
+ if (!mReaderContext.isPreventingTouchpadTaps()) {
+ enableTapToClick();
+ }
+ 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] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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<NotifyArgs> GestureConverter::handleMultiFingerSwipe(nsecs_t when,
+ nsecs_t readTime,
+ uint32_t fingerCount,
+ float dx, float dy) {
+ std::list<NotifyArgs> out = {};
+
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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;
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT,
+ fingerCount);
+ 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<NotifyArgs> GestureConverter::handleMultiFingerSwipeLift(nsecs_t when,
+ nsecs_t readTime) {
+ std::list<NotifyArgs> out = {};
+ if (mCurrentClassification != MotionClassification::MULTI_FINGER_SWIPE) {
+ return out;
+ }
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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));
+ mFakeFingerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT, 0);
+ mCurrentClassification = MotionClassification::NONE;
+ mSwipeFingerCount = 0;
+ return out;
+}
+
+[[nodiscard]] std::list<NotifyArgs> GestureConverter::handlePinch(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture) {
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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<NotifyArgs> 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<NotifyArgs> GestureConverter::endPinch(nsecs_t when, nsecs_t readTime) {
+ std::list<NotifyArgs> out;
+ const auto [xCursorPosition, yCursorPosition] =
+ mEnablePointerChoreographer ? FloatPoint{0, 0} : 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,
+ *mDisplayId,
+ /* policyFlags= */ POLICY_FLAG_WAKE,
+ action,
+ /* actionButton= */ actionButton,
+ /* flags= */ action == AMOTION_EVENT_ACTION_CANCEL ? AMOTION_EVENT_FLAG_CANCELED : 0,
+ mReaderContext.getGlobalMetaState(),
+ buttonState,
+ mCurrentClassification,
+ AMOTION_EVENT_EDGE_FLAG_NONE,
+ pointerCount,
+ pointerProperties,
+ pointerCoords,
+ /* xPrecision= */ 1.0f,
+ /* yPrecision= */ 1.0f,
+ xCursorPosition,
+ yCursorPosition,
+ /* downTime= */ mDownTime,
+ /* videoFrames= */ {}};
+}
+
+void GestureConverter::enableTapToClick() {
+ mReaderContext.setPreventingTouchpadTaps(false);
+}
+
+} // 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 0000000000..732a4b2ffb
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/GestureConverter.h
@@ -0,0 +1,124 @@
+/*
+ * 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 <array>
+#include <list>
+#include <memory>
+
+#include <PointerControllerInterface.h>
+#include <android/input.h>
+#include <utils/Timers.h>
+
+#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<NotifyArgs> reset(nsecs_t when);
+
+ void setDisplayId(std::optional<int32_t> displayId) { mDisplayId = displayId; }
+
+ void setBoundsInLogicalDisplay(FloatRect bounds) { mBoundsInLogicalDisplay = bounds; }
+
+ void populateMotionRanges(InputDeviceInfo& info) const;
+
+ [[nodiscard]] std::list<NotifyArgs> 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<NotifyArgs> handleButtonsChange(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> releaseAllButtons(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> handleScroll(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> handleFling(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] NotifyMotionArgs endScroll(nsecs_t when, nsecs_t readTime);
+
+ [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipe(nsecs_t when, nsecs_t readTime,
+ uint32_t fingerCount, float dx,
+ float dy);
+ [[nodiscard]] std::list<NotifyArgs> handleMultiFingerSwipeLift(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] std::list<NotifyArgs> handlePinch(nsecs_t when, nsecs_t readTime,
+ const Gesture& gesture);
+ [[nodiscard]] std::list<NotifyArgs> 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);
+
+ void enableTapToClick();
+
+ const int32_t mDeviceId;
+ InputReaderContext& mReaderContext;
+ std::shared_ptr<PointerControllerInterface> mPointerController;
+ const bool mEnablePointerChoreographer;
+
+ std::optional<int32_t> mDisplayId;
+ FloatRect mBoundsInLogicalDisplay{};
+ 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<PointerProperties, MAX_FAKE_FINGERS> mFingerProps = {{
+ {.id = 0, .toolType = ToolType::FINGER},
+ {.id = 1, .toolType = ToolType::FINGER},
+ {.id = 2, .toolType = ToolType::FINGER},
+ {.id = 3, .toolType = ToolType::FINGER},
+ }};
+ std::array<PointerCoords, MAX_FAKE_FINGERS> 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 0000000000..81b4968df8
--- /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 <stdio.h>
+
+#include <log/log.h>
+
+#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/HardwareProperties.cpp b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
new file mode 100644
index 0000000000..04655dc439
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareProperties.cpp
@@ -0,0 +1,80 @@
+/*
+ * 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 "HardwareProperties.h"
+
+namespace android {
+
+namespace {
+
+unsigned 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;
+}
+
+} // namespace
+
+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;
+
+ RawAbsoluteAxisInfo absMtPressure;
+ context.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &absMtPressure);
+ props.reports_pressure = absMtPressure.valid;
+ return props;
+}
+
+} // namespace android
diff --git a/libs/renderengine/include/renderengine/Image.h b/services/inputflinger/reader/mapper/gestures/HardwareProperties.h
index 3bb47318ef..672f8c1765 100644
--- a/libs/renderengine/include/renderengine/Image.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareProperties.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2017 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,16 +16,14 @@
#pragma once
-struct ANativeWindowBuffer;
+#include "InputDevice.h"
+
+#include "include/gestures.h"
namespace android {
-namespace renderengine {
-class Image {
-public:
- virtual ~Image() = default;
- virtual bool setNativeWindowBuffer(ANativeWindowBuffer* buffer, bool isProtected) = 0;
-};
+// Creates a Gestures library HardwareProperties struct for the touchpad described by the
+// InputDeviceContext.
+HardwareProperties createHardwareProperties(const InputDeviceContext& context);
-} // namespace renderengine
} // namespace android
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
new file mode 100644
index 0000000000..b89b7f38a9
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.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.
+ */
+
+// clang-format off
+#include "../Macros.h"
+// clang-format on
+#include "gestures/HardwareStateConverter.h"
+
+#include <chrono>
+#include <vector>
+
+#include <com_android_input_flags.h>
+#include <linux/input-event-codes.h>
+
+namespace input_flags = com::android::input::flags;
+
+namespace android {
+
+const bool REPORT_PALMS_TO_GESTURES_LIBRARY = input_flags::report_palms_to_gestures_library();
+
+HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext,
+ MultiTouchMotionAccumulator& motionAccumulator)
+ : mDeviceContext(deviceContext),
+ mMotionAccumulator(motionAccumulator),
+ mTouchButtonAccumulator(deviceContext) {
+ mTouchButtonAccumulator.configure();
+}
+
+std::optional<SelfContainedHardwareState> HardwareStateConverter::processRawEvent(
+ const RawEvent* rawEvent) {
+ std::optional<SelfContainedHardwareState> 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<stime_t>(std::chrono::nanoseconds(when)).count();
+ schs.state.msc_timestamp =
+ std::chrono::duration<stime_t>(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 (!REPORT_PALMS_TO_GESTURES_LIBRARY && 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();
+ if (REPORT_PALMS_TO_GESTURES_LIBRARY) {
+ fingerState.tool_type = slot.getToolType() == ToolType::PALM
+ ? FingerState::ToolType::kPalm
+ : FingerState::ToolType::kFinger;
+ }
+ }
+ 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 0000000000..633448e67e
--- /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 <optional>
+#include <set>
+
+#include <utils/Timers.h>
+
+#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<FingerState> fingers;
+};
+
+// Converts RawEvents into the HardwareState structs used by the gestures library.
+class HardwareStateConverter {
+public:
+ HardwareStateConverter(const InputDeviceContext& deviceContext,
+ MultiTouchMotionAccumulator& motionAccumulator);
+
+ std::optional<SelfContainedHardwareState> 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 0000000000..69264f84ed
--- /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 <algorithm>
+#include <optional>
+#include <utility>
+
+#include <android-base/stringprintf.h>
+#include <ftl/enum.h>
+#include <input/PrintTools.h>
+#include <log/log_main.h>
+
+namespace android {
+
+namespace {
+
+GesturesProp* createInt(void* data, const char* name, int* loc, size_t count, const int* init) {
+ return static_cast<PropertyProvider*>(data)->createIntArrayProperty(name, loc, count, init);
+}
+
+GesturesProp* createBool(void* data, const char* name, GesturesPropBool* loc, size_t count,
+ const GesturesPropBool* init) {
+ return static_cast<PropertyProvider*>(data)->createBoolArrayProperty(name, loc, count, init);
+}
+
+GesturesProp* createString(void* data, const char* name, const char** loc, const char* const init) {
+ return static_cast<PropertyProvider*>(data)->createStringProperty(name, loc, init);
+}
+
+GesturesProp* createReal(void* data, const char* name, double* loc, size_t count,
+ const double* init) {
+ return static_cast<PropertyProvider*>(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<PropertyProvider*>(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 <typename T>
+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<const char**>(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<int> GesturesProp::getIntValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer),
+ "Attempt to read ints from \"%s\" gesture property.", mName.c_str());
+ return getValues<int, int>(std::get<int*>(mDataPointer));
+}
+
+std::vector<bool> GesturesProp::getBoolValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer),
+ "Attempt to read bools from \"%s\" gesture property.", mName.c_str());
+ return getValues<bool, GesturesPropBool>(std::get<GesturesPropBool*>(mDataPointer));
+}
+
+std::vector<double> GesturesProp::getRealValues() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer),
+ "Attempt to read reals from \"%s\" gesture property.", mName.c_str());
+ return getValues<double, double>(std::get<double*>(mDataPointer));
+}
+
+std::string GesturesProp::getStringValue() const {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<const char**>(mDataPointer),
+ "Attempt to read a string from \"%s\" gesture property.", mName.c_str());
+ if (mGetter != nullptr) {
+ mGetter(mHandlerData);
+ }
+ return std::string(*std::get<const char**>(mDataPointer));
+}
+
+void GesturesProp::setBoolValues(const std::vector<bool>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<GesturesPropBool*>(mDataPointer),
+ "Attempt to write bools to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<GesturesPropBool*>(mDataPointer), values);
+}
+
+void GesturesProp::setIntValues(const std::vector<int>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<int*>(mDataPointer),
+ "Attempt to write ints to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<int*>(mDataPointer), values);
+}
+
+void GesturesProp::setRealValues(const std::vector<double>& values) {
+ LOG_ALWAYS_FATAL_IF(!std::holds_alternative<double*>(mDataPointer),
+ "Attempt to write reals to \"%s\" gesture property.", mName.c_str());
+ setValues(std::get<double*>(mDataPointer), values);
+}
+
+namespace {
+
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... { using V::operator()...; };
+// explicit deduction guide (not needed as of C++20)
+template <typename... V>
+Visitor(V...) -> Visitor<V...>;
+
+} // 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<int32_t> value = idcProperties.getInt(propertyName); value) {
+ parsedSuccessfully = true;
+ setIntValues({*value});
+ }
+ },
+ [&](GesturesPropBool*) {
+ if (std::optional<bool> value = idcProperties.getBool(propertyName); value) {
+ parsedSuccessfully = true;
+ setBoolValues({*value});
+ }
+ },
+ [&](double*) {
+ if (std::optional<double> 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 <typename T, typename U>
+const std::vector<T> GesturesProp::getValues(U* dataPointer) const {
+ if (mGetter != nullptr) {
+ mGetter(mHandlerData);
+ }
+ std::vector<T> values;
+ values.reserve(mCount);
+ for (size_t i = 0; i < mCount; i++) {
+ values.push_back(dataPointer[i]);
+ }
+ return values;
+}
+
+template <typename T, typename U>
+void GesturesProp::setValues(T* dataPointer, const std::vector<U>& 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 0000000000..c7e0858c6d
--- /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 <map>
+#include <string>
+#include <variant>
+#include <vector>
+
+#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<std::string, GesturesProp> 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 <typename T>
+ 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<int> getIntValues() const;
+ std::vector<bool> getBoolValues() const;
+ std::vector<double> getRealValues() const;
+ std::string getStringValue() const;
+
+ void setIntValues(const std::vector<int>& values);
+ void setBoolValues(const std::vector<bool>& values);
+ void setRealValues(const std::vector<double>& 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 <typename T, typename U>
+ const std::vector<T> getValues(U* dataPointer) const;
+ template <typename T, typename U>
+ void setValues(T* dataPointer, const std::vector<U>& values);
+
+ std::string mName;
+ size_t mCount;
+ std::variant<int*, GesturesPropBool*, const char**, double*> mDataPointer;
+ void* mHandlerData = nullptr;
+ GesturesPropGetHandler mGetter = nullptr;
+ GesturesPropSetHandler mSetter = nullptr;
+};
diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp
new file mode 100644
index 0000000000..df2f260098
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 "TimerProvider.h"
+
+#include <chrono>
+#include <string>
+
+#include <android-base/logging.h>
+#include <input/PrintTools.h>
+
+namespace android {
+
+namespace {
+
+nsecs_t stimeToNsecs(stime_t time) {
+ return std::chrono::duration_cast<std::chrono::nanoseconds>(
+ std::chrono::duration<stime_t>(time))
+ .count();
+}
+
+stime_t nsecsToStime(nsecs_t time) {
+ return std::chrono::duration_cast<std::chrono::duration<stime_t>>(
+ std::chrono::nanoseconds(time))
+ .count();
+}
+
+GesturesTimer* createTimer(void* data) {
+ return static_cast<TimerProvider*>(data)->createTimer();
+}
+
+void setDeadline(void* data, GesturesTimer* timer, stime_t delay, GesturesTimerCallback callback,
+ void* callbackData) {
+ static_cast<TimerProvider*>(data)->setDeadline(timer, stimeToNsecs(delay), callback,
+ callbackData);
+};
+
+void cancelTimer(void* data, GesturesTimer* timer) {
+ static_cast<TimerProvider*>(data)->cancelTimer(timer);
+}
+
+void freeTimer(void* data, GesturesTimer* timer) {
+ static_cast<TimerProvider*>(data)->freeTimer(timer);
+}
+
+} // namespace
+
+const GesturesTimerProvider kGestureTimerProvider = {
+ .create_fn = createTimer,
+ .set_fn = setDeadline,
+ .cancel_fn = cancelTimer,
+ .free_fn = freeTimer,
+};
+
+TimerProvider::TimerProvider(InputReaderContext& context) : mReaderContext(context) {}
+
+std::string TimerProvider::dump() {
+ std::string dump;
+ auto timerPtrToString = [](const std::unique_ptr<GesturesTimer>& timer) {
+ return std::to_string(timer->id);
+ };
+ dump += "Timer IDs: " + dumpVector<std::unique_ptr<GesturesTimer>>(mTimers, timerPtrToString) +
+ "\n";
+ dump += "Deadlines and corresponding timer IDs:\n";
+ dump += addLinePrefix(dumpMap(mDeadlines, constToString,
+ [](const Deadline& deadline) {
+ return std::to_string(deadline.timerId);
+ }),
+ " ") +
+ "\n";
+ return dump;
+}
+
+void TimerProvider::triggerCallbacks(nsecs_t when) {
+ while (!mDeadlines.empty() && when >= mDeadlines.begin()->first) {
+ const auto& deadlinePair = mDeadlines.begin();
+ deadlinePair->second.callback(when);
+ mDeadlines.erase(deadlinePair);
+ }
+ requestTimeout();
+}
+
+GesturesTimer* TimerProvider::createTimer() {
+ mTimers.push_back(std::make_unique<GesturesTimer>());
+ mTimers.back()->id = mNextTimerId;
+ mNextTimerId++;
+ return mTimers.back().get();
+}
+
+void TimerProvider::setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback,
+ void* callbackData) {
+ setDeadlineWithoutRequestingTimeout(timer, delay, callback, callbackData);
+ requestTimeout();
+}
+
+void TimerProvider::setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay,
+ GesturesTimerCallback callback,
+ void* callbackData) {
+ const nsecs_t now = getCurrentTime();
+ const nsecs_t time = now + delay;
+ std::function<void(nsecs_t)> wrappedCallback = [=, this](nsecs_t triggerTime) {
+ stime_t nextDelay = callback(nsecsToStime(triggerTime), callbackData);
+ if (nextDelay >= 0.0) {
+ // When rescheduling a deadline, we know that we're running inside a call to
+ // triggerCallbacks, at the end of which requestTimeout will be called. This means that
+ // we don't want to call the public setDeadline, as that will request a timeout before
+ // triggerCallbacks has removed this current deadline, resulting in a request for a
+ // timeout that has already passed.
+ setDeadlineWithoutRequestingTimeout(timer, stimeToNsecs(nextDelay), callback,
+ callbackData);
+ }
+ };
+ mDeadlines.insert({time, Deadline(wrappedCallback, timer->id)});
+}
+
+void TimerProvider::cancelTimer(GesturesTimer* timer) {
+ int id = timer->id;
+ std::erase_if(mDeadlines, [id](const auto& item) { return item.second.timerId == id; });
+ requestTimeout();
+}
+
+void TimerProvider::freeTimer(GesturesTimer* timer) {
+ cancelTimer(timer);
+ std::erase_if(mTimers, [timer](std::unique_ptr<GesturesTimer>& t) { return t.get() == timer; });
+}
+
+void TimerProvider::requestTimeout() {
+ if (!mDeadlines.empty()) {
+ // Because a std::multimap is sorted by key, we simply use the time for the first entry.
+ mReaderContext.requestTimeoutAtTime(mDeadlines.begin()->first);
+ }
+}
+
+nsecs_t TimerProvider::getCurrentTime() {
+ return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+} // namespace android \ No newline at end of file
diff --git a/services/inputflinger/reader/mapper/gestures/TimerProvider.h b/services/inputflinger/reader/mapper/gestures/TimerProvider.h
new file mode 100644
index 0000000000..7c870e0a0f
--- /dev/null
+++ b/services/inputflinger/reader/mapper/gestures/TimerProvider.h
@@ -0,0 +1,88 @@
+/*
+ * 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 <functional>
+#include <list>
+#include <map>
+#include <memory>
+#include <vector>
+
+#include <utils/Timers.h>
+
+#include "InputReaderContext.h"
+#include "NotifyArgs.h"
+#include "include/gestures.h"
+
+namespace android {
+
+extern const GesturesTimerProvider kGestureTimerProvider;
+
+// Implementation of a gestures library timer provider, which allows the library to set and cancel
+// callbacks.
+class TimerProvider {
+public:
+ TimerProvider(InputReaderContext& context);
+ virtual ~TimerProvider() = default;
+
+ // Disable copy and move, since pointers to TimerProvider objects are used in callbacks.
+ TimerProvider(const TimerProvider&) = delete;
+ TimerProvider& operator=(const TimerProvider&) = delete;
+
+ std::string dump();
+ void triggerCallbacks(nsecs_t when);
+
+ // Methods to be called by the gestures library:
+ GesturesTimer* createTimer();
+ void setDeadline(GesturesTimer* timer, nsecs_t delay, GesturesTimerCallback callback,
+ void* callbackData);
+ void cancelTimer(GesturesTimer* timer);
+ void freeTimer(GesturesTimer* timer);
+
+protected:
+ // A wrapper for the system clock, to allow tests to override it.
+ virtual nsecs_t getCurrentTime();
+
+private:
+ void setDeadlineWithoutRequestingTimeout(GesturesTimer* timer, nsecs_t delay,
+ GesturesTimerCallback callback, void* callbackData);
+ // Requests a timeout from the InputReader for the nearest deadline in mDeadlines. Must be
+ // called whenever mDeadlines is modified.
+ void requestTimeout();
+
+ InputReaderContext& mReaderContext;
+ int mNextTimerId = 0;
+ std::vector<std::unique_ptr<GesturesTimer>> mTimers;
+
+ struct Deadline {
+ Deadline(std::function<void(nsecs_t)> callback, int timerId)
+ : callback(callback), timerId(timerId) {}
+ const std::function<void(nsecs_t)> callback;
+ const int timerId;
+ };
+
+ std::multimap<nsecs_t /*time*/, Deadline> mDeadlines;
+};
+
+} // namespace android
+
+// Represents a "timer" registered by the gestures library. In practice, this just means a set of
+// deadlines that can be cancelled as a group. The library's API requires this to be in the
+// top-level namespace.
+struct GesturesTimer {
+ int id = -1;
+}; \ No newline at end of file
diff --git a/services/inputflinger/reporter/Android.bp b/services/inputflinger/reporter/Android.bp
index 74307310c5..b1e1aee02a 100644
--- a/services/inputflinger/reporter/Android.bp
+++ b/services/inputflinger/reporter/Android.bp
@@ -23,19 +23,21 @@ package {
cc_library_headers {
name: "libinputreporter_headers",
+ host_supported: true,
export_include_dirs: ["."],
}
filegroup {
name: "libinputreporter_sources",
srcs: [
- "InputReporter.cpp",
+ "InputReporter.cpp",
],
}
cc_defaults {
name: "libinputreporter_defaults",
srcs: [":libinputreporter_sources"],
+ host_supported: true,
shared_libs: [
"liblog",
"libutils",
diff --git a/services/inputflinger/reporter/InputReporter.cpp b/services/inputflinger/reporter/InputReporter.cpp
index b591d3f909..6f1eef653c 100644
--- a/services/inputflinger/reporter/InputReporter.cpp
+++ b/services/inputflinger/reporter/InputReporter.cpp
@@ -35,7 +35,7 @@ void InputReporter::reportDroppedKey(uint32_t sequenceNum) {
}
sp<InputReporterInterface> createInputReporter() {
- return new InputReporter();
+ return sp<InputReporter>::make();
}
} // namespace android
diff --git a/services/inputflinger/reporter/InputReporterInterface.h b/services/inputflinger/reporter/InputReporterInterface.h
index e5d360609f..72a4aeb13b 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 <utils/RefBase.h>
@@ -49,5 +48,3 @@ public:
sp<InputReporterInterface> createInputReporter();
} // namespace android
-
-#endif // _UI_INPUT_REPORTER_INTERFACE_H
diff --git a/services/inputflinger/rust/Android.bp b/services/inputflinger/rust/Android.bp
new file mode 100644
index 0000000000..2803805619
--- /dev/null
+++ b/services/inputflinger/rust/Android.bp
@@ -0,0 +1,71 @@
+// 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.
+
+// Generate the C++ code that Rust calls into.
+genrule {
+ name: "inputflinger_rs_bootstrap_bridge_code",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["inputflinger_rs_bootstrap_cxx_generated.cc"],
+}
+
+// Generate a C++ header containing the C++ bindings
+// to the Rust exported functions in lib.rs.
+genrule {
+ name: "inputflinger_rs_bootstrap_bridge_header",
+ tools: ["cxxbridge"],
+ cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+ srcs: ["lib.rs"],
+ out: ["inputflinger_bootstrap.rs.h"],
+}
+
+rust_defaults {
+ name: "libinputflinger_rs_defaults",
+ crate_name: "inputflinger",
+ srcs: ["lib.rs"],
+ rustlibs: [
+ "libcxx",
+ "com.android.server.inputflinger-rust",
+ "android.hardware.input.common-V1-rust",
+ "libbinder_rs",
+ "liblog_rust",
+ "liblogger",
+ ],
+ host_supported: true,
+}
+
+rust_ffi_static {
+ name: "libinputflinger_rs",
+ defaults: ["libinputflinger_rs_defaults"],
+}
+
+rust_test {
+ name: "libinputflinger_rs_test",
+ defaults: ["libinputflinger_rs_defaults"],
+ test_options: {
+ unit_test: true,
+ },
+ test_suites: ["device_tests"],
+ sanitize: {
+ address: true,
+ hwaddress: true,
+ },
+}
+
+cc_library_headers {
+ name: "inputflinger_rs_bootstrap_cxx_headers",
+ host_supported: true,
+ export_include_dirs: ["ffi"],
+}
diff --git a/services/inputflinger/rust/bounce_keys_filter.rs b/services/inputflinger/rust/bounce_keys_filter.rs
new file mode 100644
index 0000000000..894b881638
--- /dev/null
+++ b/services/inputflinger/rust/bounce_keys_filter.rs
@@ -0,0 +1,289 @@
+/*
+ * 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.
+ */
+
+//! Bounce keys input filter implementation.
+//! Bounce keys is an accessibility feature to aid users who have physical disabilities, that
+//! allows the user to configure the device to ignore rapid, repeated key presses of the same key.
+use crate::input_filter::Filter;
+
+use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+};
+use log::debug;
+use std::collections::{HashMap, HashSet};
+
+#[derive(Debug)]
+struct LastUpKeyEvent {
+ keycode: i32,
+ event_time: i64,
+}
+
+#[derive(Debug)]
+struct BlockedEvent {
+ device_id: i32,
+ keycode: i32,
+}
+
+pub struct BounceKeysFilter {
+ next: Box<dyn Filter + Send + Sync>,
+ key_event_map: HashMap<i32, LastUpKeyEvent>,
+ blocked_events: Vec<BlockedEvent>,
+ external_devices: HashSet<i32>,
+ bounce_key_threshold_ns: i64,
+}
+
+impl BounceKeysFilter {
+ /// Create a new BounceKeysFilter instance.
+ pub fn new(
+ next: Box<dyn Filter + Send + Sync>,
+ bounce_key_threshold_ns: i64,
+ ) -> BounceKeysFilter {
+ Self {
+ next,
+ key_event_map: HashMap::new(),
+ blocked_events: Vec::new(),
+ external_devices: HashSet::new(),
+ bounce_key_threshold_ns,
+ }
+ }
+}
+
+impl Filter for BounceKeysFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ if !(self.external_devices.contains(&event.deviceId) && event.source == Source::KEYBOARD) {
+ self.next.notify_key(event);
+ return;
+ }
+ match event.action {
+ KeyEventAction::DOWN => match self.key_event_map.get(&event.deviceId) {
+ None => self.next.notify_key(event),
+ Some(last_up_event) => {
+ if event.keyCode == last_up_event.keycode
+ && event.eventTime < last_up_event.event_time + self.bounce_key_threshold_ns
+ {
+ self.blocked_events.push(BlockedEvent {
+ device_id: event.deviceId,
+ keycode: event.keyCode,
+ });
+ debug!("Event dropped because last up was too recent");
+ } else {
+ self.key_event_map.remove(&event.deviceId);
+ self.next.notify_key(event);
+ }
+ }
+ },
+ KeyEventAction::UP => {
+ self.key_event_map.insert(
+ event.deviceId,
+ LastUpKeyEvent { keycode: event.keyCode, event_time: event.eventTime },
+ );
+ if let Some(index) = self.blocked_events.iter().position(|blocked_event| {
+ blocked_event.device_id == event.deviceId
+ && blocked_event.keycode == event.keyCode
+ }) {
+ self.blocked_events.remove(index);
+ debug!("Event dropped because key down was already dropped");
+ } else {
+ self.next.notify_key(event);
+ }
+ }
+ _ => (),
+ }
+ }
+
+ fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]) {
+ self.key_event_map.retain(|id, _| device_infos.iter().any(|x| *id == x.deviceId));
+ self.blocked_events.retain(|blocked_event| {
+ device_infos.iter().any(|x| blocked_event.device_id == x.deviceId)
+ });
+ self.external_devices.clear();
+ for device_info in device_infos {
+ if device_info.external {
+ self.external_devices.insert(device_info.deviceId);
+ }
+ }
+ self.next.notify_devices_changed(device_infos);
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::bounce_keys_filter::BounceKeysFilter;
+ use crate::input_filter::{test_filter::TestFilter, Filter};
+ use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent, KeyEventAction::KeyEventAction,
+ };
+
+ static BASE_KEY_EVENT: KeyEvent = KeyEvent {
+ id: 1,
+ deviceId: 1,
+ downTime: 0,
+ readTime: 0,
+ eventTime: 0,
+ source: Source::KEYBOARD,
+ displayId: 0,
+ policyFlags: 0,
+ action: KeyEventAction::DOWN,
+ flags: 0,
+ keyCode: 1,
+ scanCode: 0,
+ metaState: 0,
+ };
+
+ #[test]
+ fn test_is_notify_key_for_external_keyboard() {
+ let mut next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ next.clear();
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert!(next.last_event().is_none());
+
+ let event = KeyEvent { eventTime: 100, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert!(next.last_event().is_none());
+
+ let event = KeyEvent { eventTime: 200, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_doesnt_block_for_internal_keyboard() {
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_internal_device(
+ Box::new(next.clone()),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_doesnt_block_for_external_stylus() {
+ let next = TestFilter::new();
+ let mut filter = setup_filter_with_external_device(
+ Box::new(next.clone()),
+ 1, /* device_id */
+ 100, /* threshold */
+ );
+
+ let event =
+ KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event =
+ KeyEvent { action: KeyEventAction::UP, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event =
+ KeyEvent { action: KeyEventAction::DOWN, source: Source::STYLUS, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_is_notify_key_for_multiple_external_keyboards() {
+ let mut next = TestFilter::new();
+ let mut filter = setup_filter_with_devices(
+ Box::new(next.clone()),
+ &[
+ DeviceInfo { deviceId: 1, external: true },
+ DeviceInfo { deviceId: 2, external: true },
+ ],
+ 100, /* threshold */
+ );
+
+ let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ let event = KeyEvent { deviceId: 1, action: KeyEventAction::UP, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+
+ next.clear();
+ let event = KeyEvent { deviceId: 1, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert!(next.last_event().is_none());
+
+ let event = KeyEvent { deviceId: 2, action: KeyEventAction::DOWN, ..BASE_KEY_EVENT };
+ filter.notify_key(&event);
+ assert_eq!(next.last_event().unwrap(), event);
+ }
+
+ fn setup_filter_with_external_device(
+ next: Box<dyn Filter + Send + Sync>,
+ device_id: i32,
+ threshold: i64,
+ ) -> BounceKeysFilter {
+ setup_filter_with_devices(
+ next,
+ &[DeviceInfo { deviceId: device_id, external: true }],
+ threshold,
+ )
+ }
+
+ fn setup_filter_with_internal_device(
+ next: Box<dyn Filter + Send + Sync>,
+ device_id: i32,
+ threshold: i64,
+ ) -> BounceKeysFilter {
+ setup_filter_with_devices(
+ next,
+ &[DeviceInfo { deviceId: device_id, external: false }],
+ threshold,
+ )
+ }
+
+ fn setup_filter_with_devices(
+ next: Box<dyn Filter + Send + Sync>,
+ devices: &[DeviceInfo],
+ threshold: i64,
+ ) -> BounceKeysFilter {
+ let mut filter = BounceKeysFilter::new(next, threshold);
+ filter.notify_devices_changed(devices);
+ filter
+ }
+}
diff --git a/services/inputflinger/rust/ffi/InputFlingerBootstrap.h b/services/inputflinger/rust/ffi/InputFlingerBootstrap.h
new file mode 100644
index 0000000000..eb79ab50da
--- /dev/null
+++ b/services/inputflinger/rust/ffi/InputFlingerBootstrap.h
@@ -0,0 +1,21 @@
+/*
+ * 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 <android/binder_parcel.h>
+
+using IInputFlingerRustBootstrapCallbackAIBinder = AIBinder;
diff --git a/services/inputflinger/rust/input_filter.rs b/services/inputflinger/rust/input_filter.rs
new file mode 100644
index 0000000000..340ff8e296
--- /dev/null
+++ b/services/inputflinger/rust/input_filter.rs
@@ -0,0 +1,274 @@
+/*
+ * 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.
+ */
+
+//! InputFilter manages all the filtering components that can intercept events, modify the events,
+//! block events, etc depending on the situation. This will be used support Accessibility features
+//! like Sticky keys, Slow keys, Bounce keys, etc.
+
+use binder::{Interface, Strong};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo,
+ IInputFilter::{IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+ InputFilterConfiguration::InputFilterConfiguration,
+ KeyEvent::KeyEvent,
+};
+
+use crate::bounce_keys_filter::BounceKeysFilter;
+use log::{error, info};
+use std::sync::{Arc, Mutex, RwLock};
+
+/// Interface for all the sub input filters
+pub trait Filter {
+ fn notify_key(&mut self, event: &KeyEvent);
+ fn notify_devices_changed(&mut self, device_infos: &[DeviceInfo]);
+}
+
+struct InputFilterState {
+ first_filter: Box<dyn Filter + Send + Sync>,
+ enabled: bool,
+}
+
+/// The rust implementation of InputFilter
+pub struct InputFilter {
+ // In order to have multiple immutable references to the callbacks that is thread safe need to
+ // wrap the callbacks in Arc<RwLock<...>>
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+ // Access to mutable references to mutable state (includes access to filters, enabled, etc.) is
+ // guarded by Mutex for thread safety
+ state: Mutex<InputFilterState>,
+}
+
+impl Interface for InputFilter {}
+
+impl InputFilter {
+ /// Create a new InputFilter instance.
+ pub fn new(callbacks: Strong<dyn IInputFilterCallbacks>) -> InputFilter {
+ let ref_callbacks = Arc::new(RwLock::new(callbacks));
+ let base_filter = Box::new(BaseFilter::new(ref_callbacks.clone()));
+ Self::create_input_filter(base_filter, ref_callbacks)
+ }
+
+ /// Create test instance of InputFilter
+ fn create_input_filter(
+ first_filter: Box<dyn Filter + Send + Sync>,
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+ ) -> InputFilter {
+ Self { callbacks, state: Mutex::new(InputFilterState { first_filter, enabled: false }) }
+ }
+}
+
+impl IInputFilter for InputFilter {
+ fn isEnabled(&self) -> binder::Result<bool> {
+ Result::Ok(self.state.lock().unwrap().enabled)
+ }
+
+ fn notifyKey(&self, event: &KeyEvent) -> binder::Result<()> {
+ let first_filter = &mut self.state.lock().unwrap().first_filter;
+ first_filter.notify_key(event);
+ Result::Ok(())
+ }
+
+ fn notifyInputDevicesChanged(&self, device_infos: &[DeviceInfo]) -> binder::Result<()> {
+ let first_filter = &mut self.state.lock().unwrap().first_filter;
+ first_filter.notify_devices_changed(device_infos);
+ Result::Ok(())
+ }
+
+ fn notifyConfigurationChanged(&self, config: &InputFilterConfiguration) -> binder::Result<()> {
+ let mut state = self.state.lock().unwrap();
+ let mut first_filter: Box<dyn Filter + Send + Sync> =
+ Box::new(BaseFilter::new(self.callbacks.clone()));
+ if config.bounceKeysThresholdNs > 0 {
+ first_filter =
+ Box::new(BounceKeysFilter::new(first_filter, config.bounceKeysThresholdNs));
+ state.enabled = true;
+ info!("Bounce keys filter is installed");
+ }
+ state.first_filter = first_filter;
+ Result::Ok(())
+ }
+}
+
+struct BaseFilter {
+ callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>,
+}
+
+impl BaseFilter {
+ fn new(callbacks: Arc<RwLock<Strong<dyn IInputFilterCallbacks>>>) -> BaseFilter {
+ Self { callbacks }
+ }
+}
+
+impl Filter for BaseFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ match self.callbacks.read().unwrap().sendKeyEvent(event) {
+ Ok(_) => (),
+ _ => error!("Failed to send key event back to native C++"),
+ }
+ }
+
+ fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
+ // do nothing
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::input_filter::{test_filter::TestFilter, Filter, InputFilter};
+ use android_hardware_input_common::aidl::android::hardware::input::common::Source::Source;
+ use binder::{Interface, Strong};
+ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, IInputFilter::IInputFilter,
+ IInputFilter::IInputFilterCallbacks::IInputFilterCallbacks,
+ InputFilterConfiguration::InputFilterConfiguration, KeyEvent::KeyEvent,
+ KeyEventAction::KeyEventAction,
+ };
+ use std::sync::{Arc, RwLock};
+
+ struct FakeCallbacks {}
+
+ impl Interface for FakeCallbacks {}
+
+ impl IInputFilterCallbacks for FakeCallbacks {
+ fn sendKeyEvent(&self, _event: &KeyEvent) -> binder::Result<()> {
+ Result::Ok(())
+ }
+ }
+
+ #[test]
+ fn test_not_enabled_with_default_filter() {
+ let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
+ Strong::new(Box::new(FakeCallbacks {}));
+ let input_filter = InputFilter::new(fake_callbacks);
+ let result = input_filter.isEnabled();
+ assert!(result.is_ok());
+ assert!(!result.unwrap());
+ }
+
+ #[test]
+ fn test_notify_key_with_no_filters() {
+ let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
+ Strong::new(Box::new(FakeCallbacks {}));
+ let input_filter = InputFilter::new(fake_callbacks);
+ let event = create_key_event();
+ assert!(input_filter.notifyKey(&event).is_ok());
+ }
+
+ #[test]
+ fn test_notify_key_with_filter() {
+ let test_filter = TestFilter::new();
+ let input_filter = create_input_filter(Box::new(test_filter.clone()));
+ let event = create_key_event();
+ assert!(input_filter.notifyKey(&event).is_ok());
+ assert_eq!(test_filter.last_event().unwrap(), event);
+ }
+
+ #[test]
+ fn test_notify_devices_changed() {
+ let test_filter = TestFilter::new();
+ let input_filter = create_input_filter(Box::new(test_filter.clone()));
+ assert!(input_filter
+ .notifyInputDevicesChanged(&[DeviceInfo { deviceId: 0, external: true }])
+ .is_ok());
+ assert!(test_filter.is_device_changed_called());
+ }
+
+ #[test]
+ fn test_notify_configuration_changed_enabled_bounce_keys() {
+ let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
+ Strong::new(Box::new(FakeCallbacks {}));
+ let input_filter = InputFilter::new(fake_callbacks);
+ let result = input_filter
+ .notifyConfigurationChanged(&InputFilterConfiguration { bounceKeysThresholdNs: 100 });
+ assert!(result.is_ok());
+ let result = input_filter.isEnabled();
+ assert!(result.is_ok());
+ assert!(result.unwrap());
+ }
+
+ fn create_input_filter(filter: Box<dyn Filter + Send + Sync>) -> InputFilter {
+ let fake_callbacks: Strong<dyn IInputFilterCallbacks> =
+ Strong::new(Box::new(FakeCallbacks {}));
+ InputFilter::create_input_filter(filter, Arc::new(RwLock::new(fake_callbacks)))
+ }
+
+ fn create_key_event() -> KeyEvent {
+ KeyEvent {
+ id: 1,
+ deviceId: 1,
+ downTime: 0,
+ readTime: 0,
+ eventTime: 0,
+ source: Source::KEYBOARD,
+ displayId: 0,
+ policyFlags: 0,
+ action: KeyEventAction::DOWN,
+ flags: 0,
+ keyCode: 0,
+ scanCode: 0,
+ metaState: 0,
+ }
+ }
+}
+
+#[cfg(test)]
+pub mod test_filter {
+ use crate::input_filter::Filter;
+ use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ DeviceInfo::DeviceInfo, KeyEvent::KeyEvent,
+ };
+ use std::sync::{Arc, RwLock, RwLockWriteGuard};
+
+ #[derive(Default)]
+ struct TestFilterInner {
+ is_device_changed_called: bool,
+ last_event: Option<KeyEvent>,
+ }
+
+ #[derive(Default, Clone)]
+ pub struct TestFilter(Arc<RwLock<TestFilterInner>>);
+
+ impl TestFilter {
+ pub fn new() -> Self {
+ Default::default()
+ }
+
+ fn inner(&mut self) -> RwLockWriteGuard<'_, TestFilterInner> {
+ self.0.write().unwrap()
+ }
+
+ pub fn last_event(&self) -> Option<KeyEvent> {
+ self.0.read().unwrap().last_event
+ }
+
+ pub fn clear(&mut self) {
+ self.inner().last_event = None
+ }
+
+ pub fn is_device_changed_called(&self) -> bool {
+ self.0.read().unwrap().is_device_changed_called
+ }
+ }
+
+ impl Filter for TestFilter {
+ fn notify_key(&mut self, event: &KeyEvent) {
+ self.inner().last_event = Some(*event);
+ }
+ fn notify_devices_changed(&mut self, _device_infos: &[DeviceInfo]) {
+ self.inner().is_device_changed_called = true;
+ }
+ }
+}
diff --git a/services/inputflinger/rust/lib.rs b/services/inputflinger/rust/lib.rs
new file mode 100644
index 0000000000..5abef68a75
--- /dev/null
+++ b/services/inputflinger/rust/lib.rs
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+
+//! # The rust component of InputFlinger
+//!
+//! We use cxxbridge to create IInputFlingerRust - the Rust component of inputflinger - and
+//! pass it back to C++ as a local AIDL interface.
+
+mod bounce_keys_filter;
+mod input_filter;
+
+use crate::input_filter::InputFilter;
+use binder::{
+ unstable_api::{new_spibinder, AIBinder},
+ BinderFeatures, Interface, StatusCode, Strong,
+};
+use com_android_server_inputflinger::aidl::com::android::server::inputflinger::{
+ IInputFilter::{BnInputFilter, IInputFilter, IInputFilterCallbacks::IInputFilterCallbacks},
+ IInputFlingerRust::{
+ BnInputFlingerRust, IInputFlingerRust,
+ IInputFlingerRustBootstrapCallback::IInputFlingerRustBootstrapCallback,
+ },
+};
+use log::debug;
+
+const LOG_TAG: &str = "inputflinger_bootstrap";
+
+#[cxx::bridge]
+#[allow(unsafe_op_in_unsafe_fn)]
+mod ffi {
+ extern "C++" {
+ include!("InputFlingerBootstrap.h");
+ type IInputFlingerRustBootstrapCallbackAIBinder;
+ }
+
+ extern "Rust" {
+ unsafe fn create_inputflinger_rust(
+ callback: *mut IInputFlingerRustBootstrapCallbackAIBinder,
+ );
+ }
+}
+
+/// Create the IInputFlingerRust implementation.
+/// This is the singular entry point from C++ into Rust.
+/// The `callback` parameter must be a valid pointer to an AIBinder implementation of
+/// the `IInputFlingerRustBootstrapCallback` interface. The IInputFlingerRust implementation that
+/// is created will be passed back through the callback from within this function.
+/// NOTE: This function must not hold a strong reference to the callback beyond its scope.
+///
+/// # Safety
+///
+/// This function is safe iff `callback` is a valid pointer to an `AIBinder` interface of type
+/// `IInputFlingerRustBootstrapCallback`. The pointer must have had its reference count manually
+/// incremented using `AIBinder_incStrong`. See `binder::unstable_api::new_spibinder`.
+unsafe fn create_inputflinger_rust(callback: *mut ffi::IInputFlingerRustBootstrapCallbackAIBinder) {
+ logger::init(
+ logger::Config::default()
+ .with_tag_on_device(LOG_TAG)
+ .with_max_level(log::LevelFilter::Trace),
+ );
+
+ let callback = callback as *mut AIBinder;
+ if callback.is_null() {
+ panic!("create_inputflinger_rust cannot be called with a null callback");
+ }
+
+ // SAFETY: Our caller guaranteed that `callback` is a valid pointer to an `AIBinder` and its
+ // reference count has been incremented..
+ let Some(callback) = (unsafe { new_spibinder(callback) }) else {
+ panic!("Failed to get SpAIBinder from raw callback pointer");
+ };
+
+ let callback: Result<Strong<dyn IInputFlingerRustBootstrapCallback>, StatusCode> =
+ callback.into_interface();
+ match callback {
+ Ok(callback) => {
+ debug!("Creating InputFlingerRust");
+ let service =
+ BnInputFlingerRust::new_binder(InputFlingerRust {}, BinderFeatures::default());
+ callback.onProvideInputFlingerRust(&service).unwrap();
+ }
+ Err(status) => {
+ panic!("Failed to convert AIBinder into the callback interface: {}", status);
+ }
+ }
+}
+
+struct InputFlingerRust {}
+
+impl Interface for InputFlingerRust {}
+
+impl IInputFlingerRust for InputFlingerRust {
+ fn createInputFilter(
+ &self,
+ callbacks: &Strong<dyn IInputFilterCallbacks>,
+ ) -> binder::Result<Strong<dyn IInputFilter>> {
+ debug!("Creating InputFilter");
+ let filter = BnInputFilter::new_binder(
+ InputFilter::new(callbacks.clone()),
+ BinderFeatures::default(),
+ );
+ Result::Ok(filter)
+ }
+}
+
+impl Drop for InputFlingerRust {
+ fn drop(&mut self) {
+ debug!("Destroying InputFlingerRust");
+ }
+}
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 75cd9da782..9c9f643656 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_input_framework",
// 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"
@@ -23,6 +24,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
@@ -37,18 +39,36 @@ 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",
+ "HardwareProperties_test.cpp",
+ "HardwareStateConverter_test.cpp",
+ "InputDeviceMetricsCollector_test.cpp",
+ "InputDeviceMetricsSource_test.cpp",
+ "InputMapperTest.cpp",
+ "InputProcessor_test.cpp",
+ "InputProcessorConverter_test.cpp",
"InputDispatcher_test.cpp",
"InputReader_test.cpp",
- "InputFlingerService_test.cpp",
+ "InstrumentedInputReader.cpp",
"LatencyTracker_test.cpp",
+ "MultiTouchMotionAccumulator_test.cpp",
+ "NotifyArgs_test.cpp",
+ "PointerChoreographer_test.cpp",
"PreferStylusOverTouch_test.cpp",
+ "PropertyProvider_test.cpp",
+ "SlopController_test.cpp",
+ "SyncQueue_test.cpp",
+ "TimerProvider_test.cpp",
"TestInputListener.cpp",
+ "TouchpadInputMapper_test.cpp",
+ "KeyboardInputMapper_test.cpp",
"UinputDevice.cpp",
"UnwantedInteractionBlocker_test.cpp",
],
@@ -58,10 +78,29 @@ cc_test {
"frameworks/native/libs/input",
],
},
+ target: {
+ android: {
+ shared_libs: [
+ "libvintf",
+ ],
+ },
+ },
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
static_libs: [
+ "libflagtest",
"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 b561da107d..25adeea48f 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<IBinder> token1 = new BBinder();
- sp<IBinder> token2 = new BBinder();
+ sp<IBinder> token1 = sp<BBinder>::make();
+ sp<IBinder> token2 = sp<BBinder>::make();
tracker.insert(1, token1);
tracker.insert(2, token2);
@@ -90,8 +90,8 @@ TEST(AnrTrackerTest, SingleToken_MaintainsOrder) {
TEST(AnrTrackerTest, MultipleTokens_MaintainsOrder) {
AnrTracker tracker;
- sp<IBinder> token1 = new BBinder();
- sp<IBinder> token2 = new BBinder();
+ sp<IBinder> token1 = sp<BBinder>::make();
+ sp<IBinder> token2 = sp<BBinder>::make();
tracker.insert(2, token1);
tracker.insert(5, token2);
@@ -104,8 +104,8 @@ TEST(AnrTrackerTest, MultipleTokens_MaintainsOrder) {
TEST(AnrTrackerTest, MultipleTokens_IdenticalTimes) {
AnrTracker tracker;
- sp<IBinder> token1 = new BBinder();
- sp<IBinder> token2 = new BBinder();
+ sp<IBinder> token1 = sp<BBinder>::make();
+ sp<IBinder> token2 = sp<BBinder>::make();
tracker.insert(2, token1);
tracker.insert(2, token2);
@@ -119,8 +119,8 @@ TEST(AnrTrackerTest, MultipleTokens_IdenticalTimes) {
TEST(AnrTrackerTest, MultipleTokens_IdenticalTimesRemove) {
AnrTracker tracker;
- sp<IBinder> token1 = new BBinder();
- sp<IBinder> token2 = new BBinder();
+ sp<IBinder> token1 = sp<BBinder>::make();
+ sp<IBinder> token2 = sp<BBinder>::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<BBinder>::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<BBinder>::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 0000000000..b738abf6d2
--- /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 <CapturedTouchpadEventConverter.h>
+
+#include <list>
+#include <memory>
+
+#include <EventHub.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+#include <linux/input.h>
+#include <utils/StrongPointer.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
+#include "TestEventMatchers.h"
+#include "TestInputListener.h"
+
+namespace android {
+
+using testing::AllOf;
+
+class CapturedTouchpadEventConverterTest : public testing::Test {
+public:
+ CapturedTouchpadEventConverterTest()
+ : mFakeEventHub(std::make_unique<FakeEventHub>()),
+ mFakePolicy(sp<FakeInputReaderPolicy>::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<InputDevice> newDevice() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(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<NotifyArgs> out = conv.process(event);
+ EXPECT_TRUE(out.empty());
+ }
+
+ std::list<NotifyArgs> 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<NotifyArgs> args = processSync(conv);
+ EXPECT_EQ(1u, args.size());
+ return std::get<NotifyMotionArgs>(args.front());
+ }
+
+ std::shared_ptr<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ TestInputListener mFakeListener;
+ InstrumentedInputReader mReader;
+ std::shared_ptr<InputDevice> 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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(255, 202), WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+ WithPointerId(/*index=*/0, /*id=*/0)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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<NotifyMotionArgs>(args.front()),
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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 0000000000..6d6b7d821c
--- /dev/null
+++ b/services/inputflinger/tests/CursorInputMapper_test.cpp
@@ -0,0 +1,216 @@
+/*
+ * 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 <android-base/logging.h>
+#include <com_android_input_flags.h>
+#include <gtest/gtest.h>
+
+#include "FakePointerController.h"
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+#include "TestEventMatchers.h"
+
+#define TAG "CursorInputMapper_test"
+
+namespace android {
+
+using testing::AllOf;
+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;
+constexpr auto INVALID_CURSOR_POSITION = AMOTION_EVENT_INVALID_CURSOR_POSITION;
+constexpr int32_t DISPLAY_ID = 0;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+namespace input_flags = com::android::input::flags;
+
+/**
+ * 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));
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, "local:0", NO_PORT,
+ ViewportType::INTERNAL);
+
+ mMapper = createInputMapper<CursorInputMapper>(*mDeviceContext, mReaderConfiguration);
+ }
+
+ void setPointerCapture(bool enabled) {
+ mReaderConfiguration.pointerCaptureRequest.enable = enabled;
+ mReaderConfiguration.pointerCaptureRequest.seq = 1;
+ int32_t generation = mDevice->getGeneration();
+ std::list<NotifyArgs> args =
+ mMapper->reconfigure(ARBITRARY_TIME, mReaderConfiguration,
+ InputReaderConfiguration::Change::POINTER_CAPTURE);
+ ASSERT_THAT(args,
+ ElementsAre(
+ VariantWith<NotifyDeviceResetArgs>(AllOf(WithDeviceId(DEVICE_ID)))));
+
+ // Check that generation also got bumped
+ ASSERT_GT(mDevice->getGeneration(), generation);
+ }
+};
+
+/**
+ * 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<NotifyArgs> 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<NotifyMotionArgs>(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<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(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<NotifyMotionArgs>(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<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+}
+
+/**
+ * Set pointer capture and check that ACTION_MOVE events are emitted from CursorInputMapper.
+ * During pointer capture, source should be set to MOUSE_RELATIVE. When the capture is disabled,
+ * the events should be generated normally:
+ * 1) The source should return to SOURCE_MOUSE
+ * 2) Cursor position should be incremented by the relative device movements
+ * 3) Cursor position of NotifyMotionArgs should now be getting populated.
+ * When it's not SOURCE_MOUSE, CursorInputMapper doesn't populate cursor position values.
+ */
+TEST_F(CursorInputMapperUnitTest, ProcessPointerCapture) {
+ setPointerCapture(true);
+ std::list<NotifyArgs> args;
+
+ // Move.
+ 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<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(10.0f, 20.0f),
+ WithRelativeMotion(10.0f, 20.0f),
+ WithCursorPosition(INVALID_CURSOR_POSITION,
+ INVALID_CURSOR_POSITION)))));
+
+ // Button press.
+ args.clear();
+ args += process(EV_KEY, BTN_MOUSE, 1);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_DOWN),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(1.0f))),
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_PRESS),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(1.0f)))));
+
+ // Button release.
+ args.clear();
+ args += process(EV_KEY, BTN_MOUSE, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(BUTTON_RELEASE),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(0.0f))),
+ VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_UP),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE),
+ WithCoords(0.0f, 0.0f), WithPressure(0.0f)))));
+
+ // Another move.
+ args.clear();
+ args += process(EV_REL, REL_X, 30);
+ args += process(EV_REL, REL_Y, 40);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args,
+ ElementsAre(VariantWith<NotifyMotionArgs>(
+ AllOf(WithMotionAction(ACTION_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE), WithCoords(30.0f, 40.0f),
+ WithRelativeMotion(30.0f, 40.0f)))));
+
+ // Disable pointer capture. Afterwards, events should be generated the usual way.
+ setPointerCapture(false);
+ const auto expectedCoords = input_flags::enable_pointer_choreographer()
+ ? WithCoords(0, 0)
+ : WithCoords(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
+ const auto expectedCursorPosition = input_flags::enable_pointer_choreographer()
+ ? WithCursorPosition(INVALID_CURSOR_POSITION, INVALID_CURSOR_POSITION)
+ : WithCursorPosition(INITIAL_CURSOR_X + 10.0f, INITIAL_CURSOR_Y + 20.0f);
+ 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<NotifyMotionArgs>(
+ AllOf(WithMotionAction(HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE),
+ expectedCoords, expectedCursorPosition,
+ WithRelativeMotion(10.0f, 20.0f)))));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/EventHub_test.cpp b/services/inputflinger/tests/EventHub_test.cpp
index 6ef6e4498c..2e296daa22 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<EventHub>();
consumeInitialDeviceAddedEvents();
mKeyboard = createUinputDevice<UinputHomeKey>();
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<RawEvent> EventHubTest::getEvents(std::optional<size_t> expectedEvents) {
- static constexpr size_t EVENT_BUFFER_SIZE = 256;
- std::array<RawEvent, EVENT_BUFFER_SIZE> eventBuffer;
std::vector<RawEvent> events;
while (true) {
@@ -108,12 +112,12 @@ std::vector<RawEvent> EventHubTest::getEvents(std::optional<size_t> expectedEven
if (expectedEvents) {
timeout = 2s;
}
- const size_t count =
- mEventHub->getEvents(timeout.count(), eventBuffer.data(), eventBuffer.size());
- if (count == 0) {
+
+ std::vector<RawEvent> 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/FakeApplicationHandle.h b/services/inputflinger/tests/FakeApplicationHandle.h
new file mode 100644
index 0000000000..2f634d58ea
--- /dev/null
+++ b/services/inputflinger/tests/FakeApplicationHandle.h
@@ -0,0 +1,48 @@
+/*
+ * 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 <android-base/properties.h>
+#include <android/os/IInputConstants.h>
+#include <gui/InputApplication.h>
+
+namespace android {
+
+namespace inputdispatcher {
+
+class FakeApplicationHandle : public InputApplicationHandle {
+public:
+ FakeApplicationHandle() {
+ static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::milliseconds(
+ android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+ android::base::HwTimeoutMultiplier());
+ mInfo.name = "Fake Application";
+ mInfo.token = sp<BBinder>::make();
+ mInfo.dispatchingTimeoutMillis =
+ std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
+ }
+ virtual ~FakeApplicationHandle() {}
+
+ bool updateInfo() override { return true; }
+
+ void setDispatchingTimeout(std::chrono::milliseconds timeout) {
+ mInfo.dispatchingTimeoutMillis = timeout.count();
+ }
+};
+
+} // namespace inputdispatcher
+} // namespace android
diff --git a/services/inputflinger/tests/FakeEventHub.cpp b/services/inputflinger/tests/FakeEventHub.cpp
new file mode 100644
index 0000000000..212fcebecd
--- /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 <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+
+#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<InputDeviceClass> 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<MSC_MAX>::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<LightColor, int32_t> intensities) {
+ mLightIntensities.emplace(rawId, std::move(intensities));
+}
+
+bool FakeEventHub::getLedState(int32_t deviceId, int32_t led) {
+ return getDevice(deviceId)->leds.valueFor(led);
+}
+
+std::vector<std::string>& 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<std::mutex> 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<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> videoFrames) {
+ mVideoFrames = std::move(videoFrames);
+}
+
+void FakeEventHub::assertQueueIsEmpty() {
+ std::unique_lock<std::mutex> 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<InputDeviceClass> FakeEventHub::getDeviceClasses(int32_t deviceId) const {
+ Device* device = getDevice(deviceId);
+ return device ? device->classes : ftl::Flags<InputDeviceClass>(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<PropertyMap> 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<std::pair<InputDeviceSensorType, int32_t>> 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<std::string>& devices) {
+ mExcludedDevices = devices;
+}
+
+std::vector<RawEvent> FakeEventHub::getEvents(int) {
+ std::scoped_lock lock(mLock);
+
+ std::vector<RawEvent> buffer;
+ std::swap(buffer, mEvents);
+
+ mEventsCondition.notify_all();
+ return buffer;
+}
+
+std::vector<TouchVideoFrame> FakeEventHub::getVideoFrames(int32_t deviceId) {
+ auto it = mVideoFrames.find(deviceId);
+ if (it != mVideoFrames.end()) {
+ std::vector<TouchVideoFrame> 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<RawLayoutInfo> 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<int32_t>& 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<VirtualKeyDefinition>& outVirtualKeys) const {
+ outVirtualKeys.clear();
+
+ Device* device = getDevice(deviceId);
+ if (device) {
+ outVirtualKeys = device->virtualKeys;
+ }
+}
+
+const std::shared_ptr<KeyCharacterMap> FakeEventHub::getKeyCharacterMap(int32_t) const {
+ return nullptr;
+}
+
+bool FakeEventHub::setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) {
+ return false;
+}
+
+std::vector<int32_t> FakeEventHub::getVibratorIds(int32_t deviceId) const {
+ return mVibrators;
+}
+
+std::optional<int32_t> FakeEventHub::getBatteryCapacity(int32_t, int32_t) const {
+ return BATTERY_CAPACITY;
+}
+
+std::optional<int32_t> FakeEventHub::getBatteryStatus(int32_t, int32_t) const {
+ return BATTERY_STATUS;
+}
+
+std::vector<int32_t> FakeEventHub::getRawBatteryIds(int32_t deviceId) const {
+ return {DEFAULT_BATTERY};
+}
+
+std::optional<RawBatteryInfo> 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<int32_t> FakeEventHub::getRawLightIds(int32_t deviceId) const {
+ std::vector<int32_t> ids;
+ for (const auto& [rawId, info] : mRawLightInfos) {
+ ids.push_back(rawId);
+ }
+ return ids;
+}
+
+std::optional<RawLightInfo> 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<LightColor, int32_t> intensities) {
+ mLightIntensities.emplace(lightId, intensities);
+};
+
+std::optional<int32_t> 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<std::unordered_map<LightColor, int32_t>> 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<InputDeviceClass> 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 0000000000..8e06940aec
--- /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 <condition_variable>
+#include <mutex>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+#include <EventHub.h>
+#include <InputDevice.h>
+#include <ftl/flags.h>
+#include <input/PropertyMap.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/Errors.h>
+#include <utils/KeyedVector.h>
+
+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<InputDeviceClass> classes;
+ PropertyMap configuration;
+ KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
+ KeyedVector<int, bool> relativeAxes;
+ KeyedVector<int32_t, int32_t> keyCodeStates;
+ KeyedVector<int32_t, int32_t> scanCodeStates;
+ KeyedVector<int32_t, int32_t> switchStates;
+ KeyedVector<int32_t, int32_t> absoluteAxisValue;
+ KeyedVector<int32_t, KeyInfo> keysByScanCode;
+ KeyedVector<int32_t, KeyInfo> keysByUsageCode;
+ std::unordered_map<int32_t, int32_t> keyRemapping;
+ KeyedVector<int32_t, bool> leds;
+ // fake mapping which would normally come from keyCharacterMap
+ std::unordered_map<int32_t, int32_t> keyCodeMapping;
+ std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
+ BitArray<MSC_MAX> mscBitmask;
+ std::vector<VirtualKeyDefinition> virtualKeys;
+ bool enabled;
+ std::optional<RawLayoutInfo> layoutInfo;
+ std::string sysfsRootPath;
+
+ status_t enable() {
+ enabled = true;
+ return OK;
+ }
+
+ status_t disable() {
+ enabled = false;
+ return OK;
+ }
+
+ explicit Device(ftl::Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
+ };
+
+ std::mutex mLock;
+ std::condition_variable mEventsCondition;
+
+ KeyedVector<int32_t, Device*> mDevices;
+ std::vector<std::string> mExcludedDevices;
+ std::vector<RawEvent> mEvents GUARDED_BY(mLock);
+ std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
+ std::vector<int32_t> mVibrators = {0, 1};
+ std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
+ // Simulates a device light brightness, from light id to light brightness.
+ std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
+ // Simulates a device light intensities, from light id to light intensities map.
+ std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
+ 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<InputDeviceClass> 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<LightColor, int32_t> intensities);
+ bool getLedState(int32_t deviceId, int32_t led);
+
+ std::vector<std::string>& getExcludedDevices();
+
+ void setVideoFrames(
+ std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> 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<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override;
+ InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override;
+ int32_t getDeviceControllerNumber(int32_t) const override;
+ std::optional<PropertyMap> 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<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
+ int32_t deviceId, int32_t absCode) const override;
+ void setExcludedDevices(const std::vector<std::string>& devices) override;
+ std::vector<RawEvent> getEvents(int) override;
+ std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override;
+ int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override;
+ std::optional<RawLayoutInfo> 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<int32_t>& 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<VirtualKeyDefinition>& outVirtualKeys) const override;
+ const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override;
+ bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override;
+
+ void vibrate(int32_t, const VibrationElement&) override {}
+ void cancelVibrate(int32_t) override {}
+ std::vector<int32_t> getVibratorIds(int32_t deviceId) const override;
+
+ std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override;
+ std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override;
+ std::vector<int32_t> getRawBatteryIds(int32_t deviceId) const override;
+ std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
+ int32_t batteryId) const override;
+
+ std::vector<int32_t> getRawLightIds(int32_t deviceId) const override;
+ std::optional<RawLightInfo> 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<LightColor, int32_t> intensities) override;
+ std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) const override;
+ std::optional<std::unordered_map<LightColor, int32_t>> 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/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
new file mode 100644
index 0000000000..e9d93af67b
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -0,0 +1,91 @@
+/*
+ * 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 <android-base/logging.h>
+#include "InputDispatcherPolicyInterface.h"
+
+namespace android {
+
+// --- FakeInputDispatcherPolicy ---
+
+class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
+public:
+ FakeInputDispatcherPolicy() = default;
+ virtual ~FakeInputDispatcherPolicy() = default;
+
+private:
+ void notifyConfigurationChanged(nsecs_t) override {}
+
+ void notifyNoFocusedWindowAnr(
+ const std::shared_ptr<InputApplicationHandle>& applicationHandle) override {
+ LOG(ERROR) << "There is no focused window for " << applicationHandle->getName();
+ }
+
+ void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
+ const std::string& reason) override {
+ LOG(ERROR) << "Window is not responding: " << reason;
+ }
+
+ void notifyWindowResponsive(const sp<IBinder>& connectionToken,
+ std::optional<gui::Pid> pid) override {}
+
+ void notifyInputChannelBroken(const sp<IBinder>&) override {}
+
+ void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
+
+ void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
+ const std::vector<float>& values) override {}
+
+ void notifySensorAccuracy(int32_t deviceId, InputDeviceSensorType sensorType,
+ InputDeviceSensorAccuracy accuracy) override {}
+
+ void notifyVibratorState(int32_t deviceId, bool isOn) override {}
+
+ bool filterInputEvent(const InputEvent& inputEvent, uint32_t policyFlags) override {
+ return true; // dispatch event normally
+ }
+
+ void interceptKeyBeforeQueueing(const KeyEvent&, uint32_t&) override {}
+
+ void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
+
+ nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent&, uint32_t) override {
+ return 0;
+ }
+
+ std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent&,
+ uint32_t) override {
+ return {};
+ }
+
+ void notifySwitch(nsecs_t, uint32_t, uint32_t, uint32_t) override {}
+
+ void pokeUserActivity(nsecs_t, int32_t, int32_t) override {}
+
+ void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {}
+
+ void setPointerCapture(const PointerCaptureRequest&) override {}
+
+ void notifyDropWindow(const sp<IBinder>&, float x, float y) override {}
+
+ void notifyDeviceInteraction(DeviceId deviceId, nsecs_t timestamp,
+ const std::set<gui::Uid>& uids) override {}
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
new file mode 100644
index 0000000000..88f514f1d4
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -0,0 +1,277 @@
+/*
+ * 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 <android-base/thread_annotations.h>
+#include <gtest/gtest.h>
+
+#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<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByUniqueId(
+ const std::string& uniqueId) const {
+ return mConfig.getDisplayViewportByUniqueId(uniqueId);
+}
+std::optional<DisplayViewport> FakeInputReaderPolicy::getDisplayViewportByType(
+ ViewportType type) const {
+ return mConfig.getDisplayViewportByType(type);
+}
+
+std::optional<DisplayViewport> 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<uint8_t> 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<FakePointerController> controller) {
+ mPointerController = std::move(controller);
+}
+
+const InputReaderConfiguration& FakeInputReaderPolicy::getReaderConfiguration() const {
+ return mConfig;
+}
+
+const std::vector<InputDeviceInfo> FakeInputReaderPolicy::getInputDevices() const {
+ std::scoped_lock lock(mLock);
+ 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::setIsInputMethodConnectionActive(bool active) {
+ mIsInputMethodConnectionActive = active;
+}
+
+bool FakeInputReaderPolicy::isInputMethodConnectionActive() {
+ return mIsInputMethodConnectionActive;
+}
+
+void FakeInputReaderPolicy::getReaderConfiguration(InputReaderConfiguration* outConfig) {
+ *outConfig = mConfig;
+}
+
+std::shared_ptr<PointerControllerInterface> FakeInputReaderPolicy::obtainPointerController(
+ int32_t /*deviceId*/) {
+ return mPointerController;
+}
+
+void FakeInputReaderPolicy::notifyInputDevicesChanged(
+ const std::vector<InputDeviceInfo>& inputDevices) {
+ std::scoped_lock lock(mLock);
+ mInputDevices = inputDevices;
+ mInputDevicesChanged = true;
+ mDevicesChangedCondition.notify_all();
+}
+
+std::shared_ptr<KeyCharacterMap> FakeInputReaderPolicy::getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) {
+ return nullptr;
+}
+
+std::string FakeInputReaderPolicy::getDeviceAlias(const InputDeviceIdentifier&) {
+ return "";
+}
+
+void FakeInputReaderPolicy::waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
+ std::unique_lock<std::mutex> 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;
+}
+
+std::optional<DisplayViewport> FakeInputReaderPolicy::getPointerViewportForAssociatedDisplay(
+ int32_t associatedDisplayId) {
+ if (associatedDisplayId == ADISPLAY_ID_NONE) {
+ associatedDisplayId = mConfig.defaultPointerDisplayId;
+ }
+ for (auto& viewport : mViewports) {
+ if (viewport.displayId == associatedDisplayId) {
+ return std::make_optional(viewport);
+ }
+ }
+ return std::nullopt;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
new file mode 100644
index 0000000000..4ef9c3ee4a
--- /dev/null
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -0,0 +1,111 @@
+/*
+ * 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 <condition_variable>
+#include <memory>
+#include <mutex>
+#include <optional>
+#include <string>
+#include <vector>
+
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+
+#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<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const;
+ std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const;
+ std::optional<DisplayViewport> 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<uint8_t> 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<FakePointerController> controller);
+ const InputReaderConfiguration& getReaderConfiguration() const;
+ const std::vector<InputDeviceInfo> 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);
+ void setIsInputMethodConnectionActive(bool active);
+ bool isInputMethodConnectionActive() override;
+ std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
+ int32_t associatedDisplayId) override;
+
+private:
+ void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
+ std::shared_ptr<PointerControllerInterface> obtainPointerController(
+ int32_t /*deviceId*/) override;
+ void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override;
+ std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier&, const std::optional<KeyboardLayoutInfo>) override;
+ std::string getDeviceAlias(const InputDeviceIdentifier&) override;
+ void waitForInputDevices(std::function<void(bool)> processDevicesChanged);
+ void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
+
+ mutable std::mutex mLock;
+ std::condition_variable mDevicesChangedCondition;
+
+ InputReaderConfiguration mConfig;
+ std::shared_ptr<FakePointerController> mPointerController;
+ std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
+ bool mInputDevicesChanged GUARDED_BY(mLock){false};
+ std::vector<DisplayViewport> mViewports;
+ TouchAffineTransformation transform;
+ std::optional<int32_t /*deviceId*/> mStylusGestureNotified GUARDED_BY(mLock){};
+ bool mIsInputMethodConnectionActive{false};
+
+ 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 0000000000..80319f2487
--- /dev/null
+++ b/services/inputflinger/tests/FakePointerController.cpp
@@ -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.
+ */
+
+#include "FakePointerController.h"
+
+#include <gtest/gtest.h>
+
+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<int32_t, std::vector<int32_t>>& 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 {
+ if (!mDisplayId) {
+ return ADISPLAY_ID_NONE;
+ }
+ return *mDisplayId;
+}
+
+void FakePointerController::setDisplayViewport(const DisplayViewport& viewport) {
+ mDisplayId = viewport.displayId;
+ setBounds(viewport.logicalLeft, viewport.logicalTop, viewport.logicalRight - 1,
+ viewport.logicalBottom - 1);
+}
+
+void FakePointerController::updatePointerIcon(PointerIconStyle iconId) {
+ ASSERT_FALSE(mIconStyle.has_value()) << "Pointer icon was set more than once";
+ mIconStyle = iconId;
+}
+
+void FakePointerController::setCustomPointerIcon(const SpriteIcon& icon) {
+ ASSERT_FALSE(mCustomIconStyle.has_value()) << "Custom pointer icon was set more than once";
+ mCustomIconStyle = icon.style;
+}
+
+void FakePointerController::assertViewportSet(int32_t displayId) {
+ ASSERT_TRUE(mDisplayId);
+ ASSERT_EQ(displayId, mDisplayId);
+}
+
+void FakePointerController::assertViewportNotSet() {
+ ASSERT_EQ(std::nullopt, mDisplayId);
+}
+
+void FakePointerController::assertPosition(float x, float y) {
+ const auto [actualX, actualY] = getPosition();
+ ASSERT_NEAR(x, actualX, 1);
+ ASSERT_NEAR(y, actualY, 1);
+}
+
+void FakePointerController::assertSpotCount(int32_t displayId, int32_t count) {
+ auto it = mSpotsByDisplay.find(displayId);
+ ASSERT_TRUE(it != mSpotsByDisplay.end()) << "Spots not found for display " << displayId;
+ ASSERT_EQ(static_cast<size_t>(count), it->second.size());
+}
+
+void FakePointerController::assertPointerIconSet(PointerIconStyle iconId) {
+ ASSERT_TRUE(mIconStyle) << "Pointer icon style was not set";
+ ASSERT_EQ(iconId, mIconStyle);
+ mIconStyle.reset();
+}
+
+void FakePointerController::assertPointerIconNotSet() {
+ ASSERT_EQ(std::nullopt, mIconStyle);
+}
+
+void FakePointerController::assertCustomPointerIconSet(PointerIconStyle iconId) {
+ ASSERT_TRUE(mCustomIconStyle) << "Custom pointer icon was not set";
+ ASSERT_EQ(iconId, mCustomIconStyle);
+ mCustomIconStyle.reset();
+}
+
+void FakePointerController::assertCustomPointerIconNotSet() {
+ ASSERT_EQ(std::nullopt, mCustomIconStyle);
+}
+
+bool FakePointerController::isPointerShown() {
+ return mIsPointerShown;
+}
+
+std::optional<FloatRect> FakePointerController::getBounds() const {
+ return mHaveBounds ? std::make_optional<FloatRect>(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<int32_t> 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 0000000000..800f8649fd
--- /dev/null
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -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.
+ */
+
+#pragma once
+
+#include <PointerControllerInterface.h>
+#include <gui/constants.h>
+#include <input/DisplayViewport.h>
+#include <input/Input.h>
+#include <utils/BitSet.h>
+
+namespace android {
+
+struct SpriteIcon {
+ PointerIconStyle style;
+};
+
+class FakePointerController : public PointerControllerInterface {
+public:
+ virtual ~FakePointerController() {}
+
+ void setBounds(float minX, float minY, float maxX, float maxY);
+ const std::map<int32_t, std::vector<int32_t>>& getSpots();
+
+ void setPosition(float x, float y) override;
+ FloatPoint getPosition() const override;
+ int32_t getDisplayId() const override;
+ void setDisplayViewport(const DisplayViewport& viewport) override;
+ void updatePointerIcon(PointerIconStyle iconId) override;
+ void setCustomPointerIcon(const SpriteIcon& icon) override;
+ void fade(Transition) override;
+
+ void assertViewportSet(int32_t displayId);
+ void assertViewportNotSet();
+ void assertPosition(float x, float y);
+ void assertSpotCount(int32_t displayId, int32_t count);
+ void assertPointerIconSet(PointerIconStyle iconId);
+ void assertPointerIconNotSet();
+ void assertCustomPointerIconSet(PointerIconStyle iconId);
+ void assertCustomPointerIconNotSet();
+ bool isPointerShown();
+
+private:
+ std::string dump() override { return ""; }
+ std::optional<FloatRect> getBounds() const override;
+ void move(float deltaX, float deltaY) 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};
+ std::optional<int32_t> mDisplayId;
+ bool mIsPointerShown{false};
+ std::optional<PointerIconStyle> mIconStyle;
+ std::optional<PointerIconStyle> mCustomIconStyle;
+
+ std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/FakeWindowHandle.h b/services/inputflinger/tests/FakeWindowHandle.h
new file mode 100644
index 0000000000..fe25130bd8
--- /dev/null
+++ b/services/inputflinger/tests/FakeWindowHandle.h
@@ -0,0 +1,274 @@
+/*
+ * 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 <android-base/logging.h>
+#include "../dispatcher/InputDispatcher.h"
+
+using android::base::Result;
+using android::gui::Pid;
+using android::gui::TouchOcclusionMode;
+using android::gui::Uid;
+using android::gui::WindowInfo;
+using android::gui::WindowInfoHandle;
+
+namespace android {
+namespace inputdispatcher {
+
+namespace {
+
+// The default pid and uid for windows created by the test.
+constexpr gui::Pid WINDOW_PID{999};
+constexpr gui::Uid WINDOW_UID{1001};
+
+static constexpr std::chrono::nanoseconds DISPATCHING_TIMEOUT = 100ms;
+
+} // namespace
+
+class FakeInputReceiver {
+public:
+ std::unique_ptr<InputEvent> consumeEvent(std::chrono::milliseconds timeout) {
+ uint32_t consumeSeq = 0;
+ std::unique_ptr<InputEvent> event;
+
+ std::chrono::time_point start = std::chrono::steady_clock::now();
+ status_t result = WOULD_BLOCK;
+ while (result == WOULD_BLOCK) {
+ InputEvent* rawEventPtr = nullptr;
+ result = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+ &rawEventPtr);
+ event = std::unique_ptr<InputEvent>(rawEventPtr);
+ std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
+ if (elapsed > timeout) {
+ if (timeout != 0ms) {
+ LOG(ERROR) << "Waited too long for consumer to produce an event, giving up";
+ }
+ break;
+ }
+ }
+ // Events produced by this factory are owned pointers.
+ if (result != OK) {
+ if (timeout == 0ms) {
+ // This is likely expected. No need to log.
+ } else {
+ LOG(ERROR) << "Received result = " << result << " from consume";
+ }
+ return nullptr;
+ }
+ result = mConsumer.sendFinishedSignal(consumeSeq, true);
+ if (result != OK) {
+ LOG(ERROR) << "Received result = " << result << " from sendFinishedSignal";
+ }
+ return event;
+ }
+
+ explicit FakeInputReceiver(std::unique_ptr<InputChannel> channel, const std::string name)
+ : mConsumer(std::move(channel)) {}
+
+ virtual ~FakeInputReceiver() {}
+
+private:
+ std::unique_ptr<InputChannel> mClientChannel;
+ InputConsumer mConsumer;
+ DynamicInputEventFactory mEventFactory;
+};
+
+class FakeWindowHandle : public WindowInfoHandle {
+public:
+ static const int32_t WIDTH = 600;
+ static const int32_t HEIGHT = 800;
+
+ FakeWindowHandle(const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
+ InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+ : mName(name) {
+ Result<std::unique_ptr<InputChannel>> channel = dispatcher.createInputChannel(name);
+ mInfo.token = (*channel)->getConnectionToken();
+ mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
+
+ inputApplicationHandle->updateInfo();
+ mInfo.applicationInfo = *inputApplicationHandle->getInfo();
+
+ mInfo.id = sId++;
+ mInfo.name = name;
+ mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
+ mInfo.alpha = 1.0;
+ mInfo.frame.left = 0;
+ mInfo.frame.top = 0;
+ mInfo.frame.right = WIDTH;
+ mInfo.frame.bottom = HEIGHT;
+ mInfo.transform.set(0, 0);
+ mInfo.globalScaleFactor = 1.0;
+ mInfo.touchableRegion.clear();
+ mInfo.addTouchableRegion(Rect(0, 0, WIDTH, HEIGHT));
+ mInfo.ownerPid = WINDOW_PID;
+ mInfo.ownerUid = WINDOW_UID;
+ mInfo.displayId = displayId;
+ mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
+ }
+
+ sp<FakeWindowHandle> clone(int32_t displayId) {
+ sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+ handle->mInfo = mInfo;
+ handle->mInfo.displayId = displayId;
+ handle->mInfo.id = sId++;
+ handle->mInputReceiver = mInputReceiver;
+ return handle;
+ }
+
+ void setTouchable(bool touchable) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::NOT_TOUCHABLE, !touchable);
+ }
+
+ void setFocusable(bool focusable) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::NOT_FOCUSABLE, !focusable);
+ }
+
+ void setVisible(bool visible) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+ }
+
+ void setDispatchingTimeout(std::chrono::nanoseconds timeout) {
+ mInfo.dispatchingTimeout = timeout;
+ }
+
+ void setPaused(bool paused) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::PAUSE_DISPATCHING, paused);
+ }
+
+ void setPreventSplitting(bool preventSplitting) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::PREVENT_SPLITTING, preventSplitting);
+ }
+
+ void setSlippery(bool slippery) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::SLIPPERY, slippery);
+ }
+
+ void setWatchOutsideTouch(bool watchOutside) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::WATCH_OUTSIDE_TOUCH, watchOutside);
+ }
+
+ void setSpy(bool spy) { mInfo.setInputConfig(WindowInfo::InputConfig::SPY, spy); }
+
+ void setInterceptsStylus(bool interceptsStylus) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::INTERCEPTS_STYLUS, interceptsStylus);
+ }
+
+ void setDropInput(bool dropInput) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT, dropInput);
+ }
+
+ void setDropInputIfObscured(bool dropInputIfObscured) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::DROP_INPUT_IF_OBSCURED, dropInputIfObscured);
+ }
+
+ void setNoInputChannel(bool noInputChannel) {
+ 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; }
+
+ void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
+
+ void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
+ mInfo.frame.left = frame.left;
+ mInfo.frame.top = frame.top;
+ mInfo.frame.right = frame.right;
+ mInfo.frame.bottom = frame.bottom;
+ mInfo.touchableRegion.clear();
+ mInfo.addTouchableRegion(frame);
+
+ const Rect logicalDisplayFrame = displayTransform.transform(frame);
+ ui::Transform translate;
+ translate.set(-logicalDisplayFrame.left, -logicalDisplayFrame.top);
+ mInfo.transform = translate * displayTransform;
+ }
+
+ void setTouchableRegion(const Region& region) { mInfo.touchableRegion = region; }
+
+ void setIsWallpaper(bool isWallpaper) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::IS_WALLPAPER, isWallpaper);
+ }
+
+ void setDupTouchToWallpaper(bool hasWallpaper) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::DUPLICATE_TOUCH_TO_WALLPAPER, hasWallpaper);
+ }
+
+ void setTrustedOverlay(bool trustedOverlay) {
+ mInfo.setInputConfig(WindowInfo::InputConfig::TRUSTED_OVERLAY, trustedOverlay);
+ }
+
+ void setWindowTransform(float dsdx, float dtdx, float dtdy, float dsdy) {
+ mInfo.transform.set(dsdx, dtdx, dtdy, dsdy);
+ }
+
+ void setWindowScale(float xScale, float yScale) { setWindowTransform(xScale, 0, 0, yScale); }
+
+ void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
+
+ std::unique_ptr<InputEvent> consume(std::chrono::milliseconds timeout) {
+ if (mInputReceiver == nullptr) {
+ return nullptr;
+ }
+ return mInputReceiver->consumeEvent(timeout);
+ }
+
+ void consumeMotion() {
+ std::unique_ptr<InputEvent> event = consume(100ms);
+
+ if (event == nullptr) {
+ LOG(FATAL) << mName << ": expected a MotionEvent, but didn't get one.";
+ return;
+ }
+
+ if (event->getType() != InputEventType::MOTION) {
+ LOG(FATAL) << mName << " expected a MotionEvent, got " << *event;
+ return;
+ }
+ }
+
+ sp<IBinder> getToken() { return mInfo.token; }
+
+ const std::string& getName() { return mName; }
+
+ void setOwnerInfo(Pid ownerPid, Uid ownerUid) {
+ mInfo.ownerPid = ownerPid;
+ mInfo.ownerUid = ownerUid;
+ }
+
+ Pid getPid() const { return mInfo.ownerPid; }
+
+ void destroyReceiver() { mInputReceiver = nullptr; }
+
+private:
+ FakeWindowHandle(std::string name) : mName(name){};
+ const std::string mName;
+ std::shared_ptr<FakeInputReceiver> mInputReceiver;
+ static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+ friend class sp<FakeWindowHandle>;
+};
+
+std::atomic<int32_t> FakeWindowHandle::sId{1};
+
+} // namespace inputdispatcher
+
+} // namespace android
diff --git a/services/inputflinger/tests/FocusResolver_test.cpp b/services/inputflinger/tests/FocusResolver_test.cpp
index 91be4a30bf..2ff9c3c784 100644
--- a/services/inputflinger/tests/FocusResolver_test.cpp
+++ b/services/inputflinger/tests/FocusResolver_test.cpp
@@ -31,6 +31,8 @@ using android::gui::WindowInfoHandle;
namespace android::inputdispatcher {
+namespace {
+
class FakeWindowHandle : public WindowInfoHandle {
public:
FakeWindowHandle(const std::string& name, const sp<IBinder>& token, bool focusable,
@@ -49,17 +51,19 @@ public:
}
};
+} // namespace
+
TEST(FocusResolverTest, SetFocusedWindow) {
- sp<IBinder> focusableWindowToken = new BBinder();
- sp<IBinder> invisibleWindowToken = new BBinder();
- sp<IBinder> unfocusableWindowToken = new BBinder();
+ sp<IBinder> focusableWindowToken = sp<BBinder>::make();
+ sp<IBinder> invisibleWindowToken = sp<BBinder>::make();
+ sp<IBinder> unfocusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> 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<FakeWindowHandle>::make("Focusable", focusableWindowToken,
+ /*focusable=*/true, /*visible=*/true));
+ windows.push_back(sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken,
+ /*focusable=*/true, /*visible=*/false));
+ windows.push_back(sp<FakeWindowHandle>::make("unfocusable", unfocusableWindowToken,
+ /*focusable=*/false, /*visible=*/true));
// focusable window can get focused
FocusRequest request;
@@ -85,10 +89,10 @@ TEST(FocusResolverTest, SetFocusedWindow) {
}
TEST(FocusResolverTest, RemoveFocusFromFocusedWindow) {
- sp<IBinder> focusableWindowToken = new BBinder();
+ sp<IBinder> focusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
- windows.push_back(new FakeWindowHandle("Focusable", focusableWindowToken, true /* focusable */,
- true /* visible */));
+ windows.push_back(sp<FakeWindowHandle>::make("Focusable", focusableWindowToken,
+ /*focusable=*/true, /*visible=*/true));
FocusRequest request;
request.displayId = 42;
@@ -109,24 +113,24 @@ TEST(FocusResolverTest, RemoveFocusFromFocusedWindow) {
}
TEST(FocusResolverTest, SetFocusedMirroredWindow) {
- sp<IBinder> focusableWindowToken = new BBinder();
- sp<IBinder> invisibleWindowToken = new BBinder();
- sp<IBinder> unfocusableWindowToken = new BBinder();
+ sp<IBinder> focusableWindowToken = sp<BBinder>::make();
+ sp<IBinder> invisibleWindowToken = sp<BBinder>::make();
+ sp<IBinder> unfocusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> 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<FakeWindowHandle>::make("Mirror1", focusableWindowToken,
+ /*focusable=*/true, /*visible=*/true));
+ windows.push_back(sp<FakeWindowHandle>::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<FakeWindowHandle>::make("Mirror2Visible", invisibleWindowToken,
+ /*focusable=*/true, /*visible=*/true));
+ windows.push_back(sp<FakeWindowHandle>::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<FakeWindowHandle>::make("Mirror3Focusable", unfocusableWindowToken,
+ /*focusable=*/true, /*visible=*/true));
+ windows.push_back(sp<FakeWindowHandle>::make("Mirror3Unfocusable", unfocusableWindowToken,
+ /*focusable=*/false, /*visible=*/true));
// mirrored window can get focused
FocusRequest request;
@@ -149,10 +153,11 @@ TEST(FocusResolverTest, SetFocusedMirroredWindow) {
}
TEST(FocusResolverTest, SetInputWindows) {
- sp<IBinder> focusableWindowToken = new BBinder();
+ sp<IBinder> focusableWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
- sp<FakeWindowHandle> window = new FakeWindowHandle("Focusable", focusableWindowToken,
- true /* focusable */, true /* visible */);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make("Focusable", focusableWindowToken, /*focusable=*/true,
+ /*visible=*/true);
windows.push_back(window);
// focusable window can get focused
@@ -171,12 +176,12 @@ TEST(FocusResolverTest, SetInputWindows) {
}
TEST(FocusResolverTest, FocusRequestsCanBePending) {
- sp<IBinder> invisibleWindowToken = new BBinder();
+ sp<IBinder> invisibleWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> invisibleWindow =
- new FakeWindowHandle("Invisible", invisibleWindowToken, true /* focusable */,
- false /* visible */);
+ sp<FakeWindowHandle>::make("Invisible", invisibleWindowToken, /*focusable=*/true,
+ /*visible=*/false);
windows.push_back(invisibleWindow);
// invisible window cannot get focused
@@ -195,11 +200,12 @@ TEST(FocusResolverTest, FocusRequestsCanBePending) {
}
TEST(FocusResolverTest, FocusRequestsArePersistent) {
- sp<IBinder> windowToken = new BBinder();
+ sp<IBinder> windowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
- sp<FakeWindowHandle> window = new FakeWindowHandle("Test Window", windowToken,
- false /* focusable */, true /* visible */);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make("Test Window", windowToken, /*focusable=*/false,
+ /*visible=*/true);
windows.push_back(window);
// non-focusable window cannot get focused
@@ -235,63 +241,134 @@ TEST(FocusResolverTest, FocusRequestsArePersistent) {
ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ windowToken);
}
-TEST(FocusResolverTest, ConditionalFocusRequestsAreNotPersistent) {
- sp<IBinder> hostWindowToken = new BBinder();
+TEST(FocusResolverTest, FocusTransferTarget) {
+ sp<IBinder> hostWindowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
sp<FakeWindowHandle> hostWindow =
- new FakeWindowHandle("Host Window", hostWindowToken, true /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+ /*visible=*/true);
windows.push_back(hostWindow);
- sp<IBinder> embeddedWindowToken = new BBinder();
+ sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
sp<FakeWindowHandle> embeddedWindow =
- new FakeWindowHandle("Embedded Window", embeddedWindowToken, true /* focusable */,
- true /* visible */);
+ sp<FakeWindowHandle>::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<FocusResolver::FocusChanges> 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<BBinder>::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<IBinder> hostWindowToken = sp<BBinder>::make();
+ std::vector<sp<WindowInfoHandle>> windows;
+
+ sp<FakeWindowHandle> hostWindow =
+ sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+ /*visible=*/true);
+ windows.push_back(hostWindow);
+ sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
+ sp<FakeWindowHandle> embeddedWindow =
+ sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, /*focusable=*/true,
+ /*visible=*/true);
+ windows.push_back(embeddedWindow);
+
+ sp<IBinder> embeddedWindowToken2 = sp<BBinder>::make();
+ sp<FakeWindowHandle> embeddedWindow2 =
+ sp<FakeWindowHandle>::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<FocusResolver::FocusChanges> changes =
+ focusResolver.setFocusedWindow(request, windows);
+ ASSERT_FOCUS_CHANGE(changes, /*from*/ nullptr, /*to*/ embeddedWindowToken2);
+}
+
+TEST(FocusResolverTest, FocusTransferTargetCycle) {
+ sp<IBinder> hostWindowToken = sp<BBinder>::make();
+ std::vector<sp<WindowInfoHandle>> windows;
+
+ sp<FakeWindowHandle> hostWindow =
+ sp<FakeWindowHandle>::make("Host Window", hostWindowToken, /*focusable=*/true,
+ /*visible=*/true);
+ windows.push_back(hostWindow);
+ sp<IBinder> embeddedWindowToken = sp<BBinder>::make();
+ sp<FakeWindowHandle> embeddedWindow =
+ sp<FakeWindowHandle>::make("Embedded Window", embeddedWindowToken, /*focusable=*/true,
+ /*visible=*/true);
+ windows.push_back(embeddedWindow);
+
+ sp<IBinder> embeddedWindowToken2 = sp<BBinder>::make();
+ sp<FakeWindowHandle> embeddedWindow2 =
+ sp<FakeWindowHandle>::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<FocusResolver::FocusChanges> 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<IBinder> windowToken = new BBinder();
+ sp<IBinder> windowToken = sp<BBinder>::make();
std::vector<sp<WindowInfoHandle>> windows;
- sp<FakeWindowHandle> window = new FakeWindowHandle("Test Window", windowToken,
- true /* focusable */, true /* visible */);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::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 0000000000..d2b68dd93e
--- /dev/null
+++ b/services/inputflinger/tests/GestureConverter_test.cpp
@@ -0,0 +1,2486 @@
+/*
+ * 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 <memory>
+
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
+#include <gestures/GestureConverter.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "FakePointerController.h"
+#include "InstrumentedInputReader.h"
+#include "NotifyArgs.h"
+#include "TestConstants.h"
+#include "TestEventMatchers.h"
+#include "TestInputListener.h"
+#include "include/gestures.h"
+#include "ui/Rotation.h"
+
+namespace android {
+
+namespace input_flags = com::android::input::flags;
+
+namespace {
+
+const auto TOUCHPAD_PALM_REJECTION =
+ ACONFIG_FLAG(input_flags, enable_touchpad_typing_palm_rejection);
+
+} // namespace
+
+using testing::AllOf;
+
+class GestureConverterTestBase : 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<FakeEventHub>();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+ mFakeListener = std::make_unique<TestInputListener>();
+ mReader = std::make_unique<InstrumentedInputReader>(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<FakePointerController>();
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(POINTER_X, POINTER_Y);
+ mFakePolicy->setPointerController(mFakePointerController);
+ }
+
+ std::shared_ptr<InputDevice> newDevice() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(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<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ std::unique_ptr<TestInputListener> mFakeListener;
+ std::unique_ptr<InstrumentedInputReader> mReader;
+ std::shared_ptr<InputDevice> mDevice;
+ std::shared_ptr<FakePointerController> mFakePointerController;
+};
+
+class GestureConverterTest : public GestureConverterTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(false);
+ GestureConverterTestBase::SetUp();
+ }
+};
+
+TEST_F(GestureConverterTest, Move) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ 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);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X + 10, POINTER_Y + 5));
+}
+
+TEST_F(GestureConverterTest, ButtonsChange) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ // Press left and right buttons at once
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+ ASSERT_EQ(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Then release the left button
+ Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Finally release the right button
+ Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
+ ASSERT_EQ(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, DragWithButton) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ // Press the button
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
+ /* is_tap= */ false);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // 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<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+
+ // Release the button
+ Gesture upGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, upGesture);
+ ASSERT_EQ(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
+ WithCoords(POINTER_X - 5, POINTER_Y + 10), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Scroll) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ 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<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Scroll_Rotated) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ 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<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Scroll_ClearsClassificationAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> 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());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionClassification(MotionClassification::NONE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Scroll_ClearsScrollDistanceAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> 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);
+
+ // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+ // need to use another gesture type, like pinch.
+ Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+ ASSERT_FALSE(args.empty());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+ /*dy=*/0);
+ std::list<NotifyArgs> 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());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionClassification(MotionClassification::NONE));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
+ /*dy=*/5);
+ std::list<NotifyArgs> 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);
+
+ // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+ // need to use another gesture type, like pinch.
+ Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+ ASSERT_FALSE(args.empty());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_Vertical) {
+ // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
+ // start swiping up and then start moving left or right, it'll return gesture events with only Y
+ // deltas until you lift your fingers and start swiping again. That's why each of these tests
+ // only checks movement in one dimension.
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 10);
+ std::list<NotifyArgs> 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, ThreeFingerSwipe_Rotated) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 10);
+ std::list<NotifyArgs> 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, FourFingerSwipe_Horizontal) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dx= */ 10, /* dy= */ 0);
+ std::list<NotifyArgs> 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger3Start = arg.pointerCoords[3];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+ EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(4u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Inwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Pinch_Outwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 1.2, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Pinch_ClearsClassificationAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> 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());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionClassification(MotionClassification::NONE));
+}
+
+TEST_F(GestureConverterTest, Pinch_ClearsScaleFactorAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> 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);
+
+ // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+ // need to use another gesture type, like scroll.
+ Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
+ /*dy=*/0);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+ ASSERT_FALSE(args.empty());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
+}
+
+TEST_F(GestureConverterTest, ResetWithButtonPressed) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+ /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(3u, args.size());
+
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, ResetDuringScroll) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, ResetDuringThreeFingerSwipe) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+ /*dy=*/10);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(3u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, ResetDuringPinch) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, FlingTapDown) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X, POINTER_Y));
+ ASSERT_TRUE(mFakePointerController->isPointerShown());
+}
+
+TEST_F(GestureConverterTest, Tap) {
+ // Tap should produce button press/release events
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+
+ ASSERT_EQ(5u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTest, Click) {
+ // Click should produce button press/release events
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTest, TapWithTapToClickDisabled,
+ REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+ // Tap should be ignored when disabled
+ mReader->getContext()->setPreventingTouchpadTaps(true);
+
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+
+ Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+
+ // no events should be generated
+ ASSERT_EQ(0u, args.size());
+
+ // Future taps should be re-enabled
+ ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTest, ClickWithTapToClickDisabled,
+ REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+ // Click should still produce button press/release events
+ mReader->getContext()->setPreventingTouchpadTaps(true);
+
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(POINTER_X, POINTER_Y),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithPressure(1.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(POINTER_X, POINTER_Y),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithCoords(POINTER_X, POINTER_Y), WithRelativeMotion(0, 0),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(0.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Future taps should be re-enabled
+ ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTest, MoveEnablesTapToClick,
+ REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+ // initially disable tap-to-click
+ mReader->getContext()->setPreventingTouchpadTaps(true);
+
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(POINTER_X - 5, POINTER_Y + 10));
+
+ // Future taps should be re-enabled
+ ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+// logic can be removed.
+class GestureConverterTestWithChoreographer : public GestureConverterTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(true);
+ GestureConverterTestBase::SetUp();
+ }
+};
+
+TEST_F(GestureConverterTestWithChoreographer, Move) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Move_Rotated) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(10, 5), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ButtonsChange) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ // Press left and right buttons at once
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+ ASSERT_EQ(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+ AMOTION_EVENT_BUTTON_SECONDARY),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY |
+ AMOTION_EVENT_BUTTON_SECONDARY),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Then release the left button
+ Gesture leftUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_LEFT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, leftUpGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Finally release the right button
+ Gesture rightUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE, /* up= */ GESTURES_BUTTON_RIGHT,
+ /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, rightUpGesture);
+ ASSERT_EQ(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, DragWithButton) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ // Press the button
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT, /* up= */ GESTURES_BUTTON_NONE,
+ /* is_tap= */ false);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Move
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // 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(3u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER), WithDownTime(downTime),
+ WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -10),
+ WithGestureScrollDistance(0, 10, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER),
+ WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ 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<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(0, -15),
+ WithGestureScrollDistance(0, 5, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER),
+ WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0 - 15),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER),
+ WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_Rotated) {
+ const nsecs_t downTime = 12345;
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> args = converter.handleGesture(downTime, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER), WithDownTime(downTime),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-10, 0),
+ WithGestureScrollDistance(0, 10, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture continueGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -5);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, continueGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithCoords(-15, 0),
+ WithGestureScrollDistance(0, 5, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 1, 1,
+ GESTURES_FLING_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(-15, 0),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsClassificationAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> 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());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionClassification(MotionClassification::NONE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Scroll_ClearsScrollDistanceAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ std::list<NotifyArgs> 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);
+
+ // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+ // need to use another gesture type, like pinch.
+ Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+ ASSERT_FALSE(args.empty());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGestureScrollDistance(0, 0, EPSILON));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsClassificationAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+ /*dy=*/0);
+ std::list<NotifyArgs> 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());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionClassification(MotionClassification::NONE));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_ClearsGestureAxesAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/5,
+ /*dy=*/5);
+ std::list<NotifyArgs> 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);
+
+ // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+ // need to use another gesture type, like pinch.
+ Gesture pinchGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, pinchGesture);
+ ASSERT_FALSE(args.empty());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(0)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Vertical) {
+ // The gestures library will "lock" a swipe into the dimension it starts in. For example, if you
+ // start swiping up and then start moving left or right, it'll return gesture events with only Y
+ // deltas until you lift your fingers and start swiping again. That's why each of these tests
+ // only checks movement in one dimension.
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 10);
+ std::list<NotifyArgs> 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.01, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.005, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX());
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX());
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX());
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY() - 15);
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY() - 15);
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY() - 15);
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(3),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ThreeFingerSwipe_Rotated) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setOrientation(ui::ROTATION_90);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dx= */ 0,
+ /* dy= */ 10);
+ std::list<NotifyArgs> 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.01, EPSILON), WithPointerCount(3u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0, -0.005, EPSILON), WithPointerCount(3u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() - 15);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(3u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithPointerCount(2u),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithPointerCount(1u), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, FourFingerSwipe_Horizontal) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureFourFingerSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dx= */ 10, /* dy= */ 0);
+ std::list<NotifyArgs> 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger0Start = arg.pointerCoords[0];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger1Start = arg.pointerCoords[1];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger2Start = arg.pointerCoords[2];
+ args.pop_front();
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ PointerCoords finger3Start = arg.pointerCoords[3];
+ args.pop_front();
+
+ arg = std::get<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0.01, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 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<NotifyMotionArgs>(args.front());
+ ASSERT_THAT(arg,
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithGestureOffset(0.005, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ EXPECT_EQ(arg.pointerCoords[0].getX(), finger0Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[1].getX(), finger1Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[2].getX(), finger2Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[3].getX(), finger3Start.getX() + 15);
+ EXPECT_EQ(arg.pointerCoords[0].getY(), finger0Start.getY());
+ EXPECT_EQ(arg.pointerCoords[1].getY(), finger1Start.getY());
+ EXPECT_EQ(arg.pointerCoords[2].getY(), finger2Start.getY());
+ EXPECT_EQ(arg.pointerCoords[3].getY(), finger3Start.getY());
+
+ Gesture liftGesture(kGestureSwipeLift, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, liftGesture);
+ ASSERT_EQ(4u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 3 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(4u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(3u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithGestureOffset(0, 0, EPSILON), WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithGestureSwipeFingerCount(4),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_Inwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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, 100, 0),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 0.8, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(0.8f, EPSILON), WithPointerCoords(0, -80, 0),
+ WithPointerCoords(1, 80, 0), WithPointerCount(2u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_Outwards) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithCoords(-100, 0),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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, 100, 0),
+ WithPointerCount(2u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture updateGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* dz= */ 1.1, GESTURES_ZOOM_UPDATE);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, updateGesture);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.1f, EPSILON), WithPointerCoords(0, -110, 0),
+ WithPointerCoords(1, 110, 0), WithPointerCount(2u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture endGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* dz= */ 1,
+ GESTURES_ZOOM_END);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, endGesture);
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsClassificationAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> 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());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ WithMotionClassification(MotionClassification::NONE));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Pinch_ClearsScaleFactorAfterGesture) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ std::list<NotifyArgs> 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);
+
+ // Move gestures don't use the fake finger array, so to test that gesture axes are cleared we
+ // need to use another gesture type, like scroll.
+ Gesture scrollGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/1,
+ /*dy=*/0);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, scrollGesture);
+ ASSERT_FALSE(args.empty());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()), WithGesturePinchScaleFactor(0, EPSILON));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetWithButtonPressed) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture downGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /*down=*/GESTURES_BUTTON_LEFT | GESTURES_BUTTON_RIGHT,
+ /*up=*/GESTURES_BUTTON_NONE, /*is_tap=*/false);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, downGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(3u, args.size());
+
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_SECONDARY), WithCoords(0, 0),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_SECONDARY), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithButtonState(0),
+ WithCoords(0, 0), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringScroll) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureScroll, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, 0, -10);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, -10),
+ WithGestureScrollDistance(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::TWO_FINGER_SWIPE),
+ WithToolType(ToolType::FINGER),
+ WithFlags(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringThreeFingerSwipe) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGestureSwipe, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dx=*/0,
+ /*dy=*/10);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(3u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithGestureOffset(0, 0, EPSILON),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCount(1u), WithToolType(ToolType::FINGER),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, ResetDuringPinch) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture startGesture(kGesturePinch, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /*dz=*/1,
+ GESTURES_ZOOM_START);
+ (void)converter.handleGesture(ARBITRARY_TIME, READ_TIME, startGesture);
+
+ std::list<NotifyArgs> args = converter.reset(ARBITRARY_TIME);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(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), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithMotionClassification(MotionClassification::PINCH),
+ WithGesturePinchScaleFactor(1.0f, EPSILON), WithPointerCount(1u),
+ WithToolType(ToolType::FINGER), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, FlingTapDown) {
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture tapDownGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /*vx=*/0.f, /*vy=*/0.f, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapDownGesture);
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Tap) {
+ // Tap should produce button press/release events
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+
+ ASSERT_EQ(5u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F(GestureConverterTestWithChoreographer, Click) {
+ // Click should produce button press/release events
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+
+ ASSERT_EQ(2u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, TapWithTapToClickDisabled,
+ REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+ // Tap should be ignored when disabled
+ mReader->getContext()->setPreventingTouchpadTaps(true);
+
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+
+ Gesture tapGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ true);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, tapGesture);
+
+ // no events should be generated
+ ASSERT_EQ(0u, args.size());
+
+ // Future taps should be re-enabled
+ ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, ClickWithTapToClickDisabled,
+ REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+ // Click should still produce button press/release events
+ mReader->getContext()->setPreventingTouchpadTaps(true);
+
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture flingGesture(kGestureFling, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, /* vx= */ 0,
+ /* vy= */ 0, GESTURES_FLING_TAP_DOWN);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, flingGesture);
+
+ ASSERT_EQ(1u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonDownGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_LEFT,
+ /* up= */ GESTURES_BUTTON_NONE, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonDownGesture);
+ ASSERT_EQ(2u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_PRESS),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(AMOTION_EVENT_BUTTON_PRIMARY), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ Gesture buttonUpGesture(kGestureButtonsChange, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME,
+ /* down= */ GESTURES_BUTTON_NONE,
+ /* up= */ GESTURES_BUTTON_LEFT, /* is_tap= */ false);
+ args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, buttonUpGesture);
+
+ ASSERT_EQ(3u, args.size());
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_BUTTON_RELEASE),
+ WithActionButton(AMOTION_EVENT_BUTTON_PRIMARY), WithButtonState(0),
+ WithCoords(0, 0), WithRelativeMotion(0.f, 0.f),
+ WithToolType(ToolType::FINGER), WithButtonState(0), WithPressure(1.0f),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithCoords(0, 0),
+ WithRelativeMotion(0.f, 0.f), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ args.pop_front();
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(0, 0), WithToolType(ToolType::FINGER), WithButtonState(0),
+ WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Future taps should be re-enabled
+ ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+TEST_F_WITH_FLAGS(GestureConverterTestWithChoreographer, MoveEnablesTapToClick,
+ REQUIRES_FLAGS_ENABLED(TOUCHPAD_PALM_REJECTION)) {
+ // initially disable tap-to-click
+ mReader->getContext()->setPreventingTouchpadTaps(true);
+
+ InputDeviceContext deviceContext(*mDevice, EVENTHUB_ID);
+ GestureConverter converter(*mReader->getContext(), deviceContext, DEVICE_ID);
+ converter.setDisplayId(ADISPLAY_ID_DEFAULT);
+
+ Gesture moveGesture(kGestureMove, ARBITRARY_GESTURE_TIME, ARBITRARY_GESTURE_TIME, -5, 10);
+ std::list<NotifyArgs> args = converter.handleGesture(ARBITRARY_TIME, READ_TIME, moveGesture);
+ ASSERT_EQ(1u, args.size());
+
+ ASSERT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithCoords(0, 0),
+ WithRelativeMotion(-5, 10), WithToolType(ToolType::FINGER),
+ WithButtonState(0), WithPressure(0.0f), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Future taps should be re-enabled
+ ASSERT_FALSE(mReader->getContext()->isPreventingTouchpadTaps());
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/HardwareProperties_test.cpp b/services/inputflinger/tests/HardwareProperties_test.cpp
new file mode 100644
index 0000000000..8dfa8c8e0c
--- /dev/null
+++ b/services/inputflinger/tests/HardwareProperties_test.cpp
@@ -0,0 +1,165 @@
+/*
+ * 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 <gestures/HardwareProperties.h>
+
+#include <memory>
+#include <set>
+
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "InterfaceMocks.h"
+#include "TestConstants.h"
+#include "include/gestures.h"
+
+namespace android {
+
+using testing::Return;
+
+class HardwarePropertiesTest : public testing::Test {
+public:
+ HardwarePropertiesTest() {
+ EXPECT_CALL(mMockInputReaderContext, getEventHub()).WillRepeatedly(Return(&mMockEventHub));
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ mDevice = std::make_unique<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
+ /*generation=*/2, identifier);
+ mDeviceContext = std::make_unique<InputDeviceContext>(*mDevice, EVENTHUB_ID);
+ }
+
+protected:
+ static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ static constexpr int32_t EVENTHUB_ID = 1;
+
+ void setupValidAxis(int axis, 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 = true;
+ outAxisInfo->minValue = min;
+ outAxisInfo->maxValue = max;
+ outAxisInfo->flat = 0;
+ outAxisInfo->fuzz = 0;
+ outAxisInfo->resolution = resolution;
+ return OK;
+ });
+ }
+
+ void setupInvalidAxis(int axis) {
+ EXPECT_CALL(mMockEventHub, getAbsoluteAxisInfo(EVENTHUB_ID, axis, testing::_))
+ .WillRepeatedly([=](int32_t, int32_t, RawAbsoluteAxisInfo* outAxisInfo) {
+ outAxisInfo->valid = false;
+ return -1;
+ });
+ }
+
+ void setProperty(int property, bool value) {
+ EXPECT_CALL(mMockEventHub, hasInputProperty(EVENTHUB_ID, property))
+ .WillRepeatedly(Return(value));
+ }
+
+ void setButtonsPresent(std::set<int> buttonCodes, bool present) {
+ for (const auto& buttonCode : buttonCodes) {
+ EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, buttonCode))
+ .WillRepeatedly(Return(present));
+ }
+ }
+
+ MockEventHubInterface mMockEventHub;
+ MockInputReaderContext mMockInputReaderContext;
+ std::unique_ptr<InputDevice> mDevice;
+ std::unique_ptr<InputDeviceContext> mDeviceContext;
+};
+
+TEST_F(HardwarePropertiesTest, FancyTouchpad) {
+ setupValidAxis(ABS_MT_POSITION_X, 0, 2048, 27);
+ setupValidAxis(ABS_MT_POSITION_Y, 0, 1500, 30);
+ setupValidAxis(ABS_MT_ORIENTATION, -3, 4, 0);
+ setupValidAxis(ABS_MT_SLOT, 0, 15, 0);
+ setupValidAxis(ABS_MT_PRESSURE, 0, 256, 0);
+
+ setProperty(INPUT_PROP_SEMI_MT, false);
+ setProperty(INPUT_PROP_BUTTONPAD, true);
+
+ setButtonsPresent({BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP, BTN_TOOL_QUADTAP,
+ BTN_TOOL_QUINTTAP},
+ true);
+
+ HardwareProperties hwprops = createHardwareProperties(*mDeviceContext);
+ EXPECT_NEAR(0, hwprops.left, EPSILON);
+ EXPECT_NEAR(0, hwprops.top, EPSILON);
+ EXPECT_NEAR(2048, hwprops.right, EPSILON);
+ EXPECT_NEAR(1500, hwprops.bottom, EPSILON);
+
+ EXPECT_NEAR(27, hwprops.res_x, EPSILON);
+ EXPECT_NEAR(30, hwprops.res_y, EPSILON);
+
+ EXPECT_NEAR(-3, hwprops.orientation_minimum, EPSILON);
+ EXPECT_NEAR(4, hwprops.orientation_maximum, EPSILON);
+
+ EXPECT_EQ(16, hwprops.max_finger_cnt);
+ EXPECT_EQ(5, hwprops.max_touch_cnt);
+
+ EXPECT_FALSE(hwprops.supports_t5r2);
+ EXPECT_FALSE(hwprops.support_semi_mt);
+ EXPECT_TRUE(hwprops.is_button_pad);
+ EXPECT_FALSE(hwprops.has_wheel);
+ EXPECT_FALSE(hwprops.wheel_is_hi_res);
+ EXPECT_FALSE(hwprops.is_haptic_pad);
+ EXPECT_TRUE(hwprops.reports_pressure);
+}
+
+TEST_F(HardwarePropertiesTest, BasicTouchpad) {
+ setupValidAxis(ABS_MT_POSITION_X, 0, 1024, 0);
+ setupValidAxis(ABS_MT_POSITION_Y, 0, 768, 0);
+ setupValidAxis(ABS_MT_SLOT, 0, 7, 0);
+
+ setupInvalidAxis(ABS_MT_ORIENTATION);
+ setupInvalidAxis(ABS_MT_PRESSURE);
+
+ setProperty(INPUT_PROP_SEMI_MT, false);
+ setProperty(INPUT_PROP_BUTTONPAD, false);
+
+ setButtonsPresent({BTN_TOOL_FINGER, BTN_TOOL_DOUBLETAP, BTN_TOOL_TRIPLETAP}, true);
+ setButtonsPresent({BTN_TOOL_QUADTAP, BTN_TOOL_QUINTTAP}, false);
+
+ HardwareProperties hwprops = createHardwareProperties(*mDeviceContext);
+ EXPECT_NEAR(0, hwprops.left, EPSILON);
+ EXPECT_NEAR(0, hwprops.top, EPSILON);
+ EXPECT_NEAR(1024, hwprops.right, EPSILON);
+ EXPECT_NEAR(768, hwprops.bottom, EPSILON);
+
+ EXPECT_NEAR(0, hwprops.res_x, EPSILON);
+ EXPECT_NEAR(0, hwprops.res_y, EPSILON);
+
+ EXPECT_NEAR(0, hwprops.orientation_minimum, EPSILON);
+ EXPECT_NEAR(0, hwprops.orientation_maximum, EPSILON);
+
+ EXPECT_EQ(8, hwprops.max_finger_cnt);
+ EXPECT_EQ(3, hwprops.max_touch_cnt);
+
+ EXPECT_FALSE(hwprops.supports_t5r2);
+ EXPECT_FALSE(hwprops.support_semi_mt);
+ EXPECT_FALSE(hwprops.is_button_pad);
+ EXPECT_FALSE(hwprops.has_wheel);
+ EXPECT_FALSE(hwprops.wheel_is_hi_res);
+ EXPECT_FALSE(hwprops.is_haptic_pad);
+ EXPECT_FALSE(hwprops.reports_pressure);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
new file mode 100644
index 0000000000..ff9bd9e75b
--- /dev/null
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -0,0 +1,349 @@
+/*
+ * 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/HardwareStateConverter.h>
+
+#include <memory>
+
+#include <EventHub.h>
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+#include <utils/StrongPointer.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "MultiTouchMotionAccumulator.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+
+namespace android {
+
+namespace {
+
+const auto REPORT_PALMS =
+ ACONFIG_FLAG(com::android::input::flags, report_palms_to_gestures_library);
+
+} // namespace
+
+class HardwareStateConverterTest : public testing::Test {
+public:
+ HardwareStateConverterTest()
+ : mFakeEventHub(std::make_shared<FakeEventHub>()),
+ mFakePolicy(sp<FakeInputReaderPolicy>::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<HardwareStateConverter>(mDeviceContext, mAccumulator);
+ }
+
+protected:
+ static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ static constexpr int32_t EVENTHUB_ID = 1;
+
+ std::shared_ptr<InputDevice> newDevice() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(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<SelfContainedHardwareState> schs = mConverter->processRawEvent(&event);
+ EXPECT_FALSE(schs.has_value());
+ }
+
+ std::optional<SelfContainedHardwareState> 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<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ TestInputListener mFakeListener;
+ InstrumentedInputReader mReader;
+ std::shared_ptr<InputDevice> mDevice;
+ InputDeviceContext mDeviceContext;
+ MultiTouchMotionAccumulator mAccumulator;
+ std::unique_ptr<HardwareStateConverter> 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<SelfContainedHardwareState> 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<SelfContainedHardwareState> 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_WITH_FLAGS(HardwareStateConverterTest, OnePalmDisableReportPalms,
+ REQUIRES_FLAGS_DISABLED(REPORT_PALMS)) {
+ 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<SelfContainedHardwareState> 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_WITH_FLAGS(HardwareStateConverterTest, OnePalmEnableReportPalms,
+ REQUIRES_FLAGS_ENABLED(REPORT_PALMS)) {
+ 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<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
+ ASSERT_TRUE(schs.has_value());
+ EXPECT_EQ(1, schs->state.touch_cnt);
+ EXPECT_EQ(1, schs->state.finger_cnt);
+ EXPECT_EQ(FingerState::ToolType::kPalm, schs->state.fingers[0].tool_type);
+}
+
+TEST_F_WITH_FLAGS(HardwareStateConverterTest, OneFingerTurningIntoAPalmDisableReportPalms,
+ REQUIRES_FLAGS_DISABLED(REPORT_PALMS)) {
+ 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<SelfContainedHardwareState> 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_WITH_FLAGS(HardwareStateConverterTest, OneFingerTurningIntoAPalmEnableReportPalms,
+ REQUIRES_FLAGS_ENABLED(REPORT_PALMS)) {
+ 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<SelfContainedHardwareState> schs = processSync(ARBITRARY_TIME);
+ ASSERT_TRUE(schs.has_value());
+ EXPECT_EQ(1, schs->state.touch_cnt);
+ EXPECT_EQ(1, schs->state.finger_cnt);
+ EXPECT_EQ(FingerState::ToolType::kFinger, schs->state.fingers[0].tool_type);
+
+ 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(1, schs->state.touch_cnt);
+ ASSERT_EQ(1, schs->state.finger_cnt);
+ EXPECT_EQ(FingerState::ToolType::kPalm, schs->state.fingers[0].tool_type);
+
+ 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(1, schs->state.touch_cnt);
+ EXPECT_EQ(1, schs->state.finger_cnt);
+ EXPECT_EQ(FingerState::ToolType::kPalm, schs->state.fingers[0].tool_type);
+
+ 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(FingerState::ToolType::kFinger, newFinger.tool_type);
+ 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<SelfContainedHardwareState> 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<SelfContainedHardwareState> 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/InputDeviceMetricsCollector_test.cpp b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
new file mode 100644
index 0000000000..85e055d98e
--- /dev/null
+++ b/services/inputflinger/tests/InputDeviceMetricsCollector_test.cpp
@@ -0,0 +1,530 @@
+/*
+ * 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 "../InputDeviceMetricsCollector.h"
+
+#include <NotifyArgsBuilders.h>
+#include <gtest/gtest.h>
+#include <gui/constants.h>
+#include <input/InputEventBuilders.h>
+#include <linux/input.h>
+
+#include <array>
+#include <tuple>
+
+#include "TestInputListener.h"
+
+namespace android {
+
+using std::chrono_literals::operator""ns;
+using std::chrono::nanoseconds;
+
+namespace {
+
+constexpr auto USAGE_TIMEOUT = 8765309ns;
+constexpr auto TIME = 999999ns;
+
+constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t DEVICE_ID_2 = 4;
+constexpr int32_t VID = 0xFEED;
+constexpr int32_t PID = 0xDEAD;
+constexpr int32_t VERSION = 0xBEEF;
+const std::string DEVICE_NAME = "Half Dome";
+const std::string LOCATION = "California";
+const std::string UNIQUE_ID = "Yosemite";
+constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
+constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS;
+
+InputDeviceIdentifier generateTestIdentifier(int32_t id = DEVICE_ID) {
+ InputDeviceIdentifier identifier;
+ identifier.name = DEVICE_NAME + "_" + std::to_string(id);
+ identifier.location = LOCATION;
+ identifier.uniqueId = UNIQUE_ID;
+ identifier.vendor = VID;
+ identifier.product = PID;
+ identifier.version = VERSION;
+ identifier.bus = BUS_USB;
+ return identifier;
+}
+
+InputDeviceInfo generateTestDeviceInfo(int32_t id = DEVICE_ID,
+ uint32_t sources = TOUCHSCREEN | STYLUS) {
+ auto info = InputDeviceInfo();
+ info.initialize(id, /*generation=*/1, /*controllerNumber=*/1, generateTestIdentifier(id),
+ "alias", /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+ info.addSource(sources);
+ return info;
+}
+
+const InputDeviceInfo TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID);
+const InputDeviceInfo SECOND_TOUCHSCREEN_STYLUS_INFO = generateTestDeviceInfo(DEVICE_ID_2);
+
+std::set<gui::Uid> uids(std::initializer_list<int32_t> vals) {
+ std::set<gui::Uid> set;
+ for (const auto val : vals) {
+ set.emplace(val);
+ }
+ return set;
+}
+
+} // namespace
+
+// --- InputDeviceMetricsCollectorTest ---
+
+class InputDeviceMetricsCollectorTest : public testing::Test, public InputDeviceMetricsLogger {
+protected:
+ TestInputListener mTestListener;
+ InputDeviceMetricsCollector mMetricsCollector{mTestListener, *this, USAGE_TIMEOUT};
+
+ void assertUsageLogged(const InputDeviceInfo& info, nanoseconds duration,
+ std::optional<SourceUsageBreakdown> sourceBreakdown = {},
+ std::optional<UidUsageBreakdown> uidBreakdown = {}) {
+ ASSERT_GE(mLoggedUsageSessions.size(), 1u);
+ const auto& [loggedInfo, report] = *mLoggedUsageSessions.begin();
+ const auto& i = info.getIdentifier();
+ ASSERT_EQ(info.getId(), loggedInfo.deviceId);
+ ASSERT_EQ(i.vendor, loggedInfo.vendor);
+ ASSERT_EQ(i.product, loggedInfo.product);
+ ASSERT_EQ(i.version, loggedInfo.version);
+ ASSERT_EQ(i.bus, loggedInfo.bus);
+ ASSERT_EQ(info.getUsiVersion().has_value(), loggedInfo.isUsiStylus);
+ ASSERT_EQ(duration, report.usageDuration);
+ if (sourceBreakdown) {
+ ASSERT_EQ(sourceBreakdown, report.sourceBreakdown);
+ }
+ if (uidBreakdown) {
+ ASSERT_EQ(uidBreakdown, report.uidBreakdown);
+ }
+ mLoggedUsageSessions.erase(mLoggedUsageSessions.begin());
+ }
+
+ void assertUsageNotLogged() { ASSERT_TRUE(mLoggedUsageSessions.empty()); }
+
+ void setCurrentTime(nanoseconds time) { mCurrentTime = time; }
+
+ nsecs_t currentTime() const { return mCurrentTime.count(); }
+
+ NotifyMotionArgs generateMotionArgs(int32_t deviceId,
+ uint32_t source = AINPUT_SOURCE_TOUCHSCREEN,
+ std::vector<ToolType> toolTypes = {ToolType::FINGER}) {
+ MotionArgsBuilder builder(AMOTION_EVENT_ACTION_MOVE, source);
+ for (size_t i = 0; i < toolTypes.size(); i++) {
+ builder.pointer(PointerBuilder(i, toolTypes[i]));
+ }
+ return builder.deviceId(deviceId)
+ .eventTime(mCurrentTime.count())
+ .downTime(mCurrentTime.count())
+ .build();
+ }
+
+private:
+ std::vector<std::tuple<MetricsDeviceInfo, DeviceUsageReport>> mLoggedUsageSessions;
+ nanoseconds mCurrentTime{TIME};
+
+ nanoseconds getCurrentTime() override { return mCurrentTime; }
+
+ void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
+ const DeviceUsageReport& report) override {
+ mLoggedUsageSessions.emplace_back(info, report);
+ }
+};
+
+TEST_F(InputDeviceMetricsCollectorTest, DontLogUsageWhenDeviceNotRegistered) {
+ // Device was used.
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mTestListener.assertNotifyMotionWasCalled();
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device was used again after the usage timeout expired, but we still don't log usage.
+ setCurrentTime(TIME + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mTestListener.assertNotifyMotionWasCalled();
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, DontLogUsageForIgnoredDevices) {
+ constexpr static std::array<int32_t, 2> ignoredDevices{
+ {INVALID_INPUT_DEVICE_ID, VIRTUAL_KEYBOARD_ID}};
+
+ for (int32_t ignoredDeviceId : ignoredDevices) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(ignoredDeviceId)}});
+
+ // Device was used.
+ mMetricsCollector.notifyMotion(generateMotionArgs(ignoredDeviceId));
+ mTestListener.assertNotifyMotionWasCalled();
+ mMetricsCollector.notifyDeviceInteraction(ignoredDeviceId, TIME.count(), uids({0, 1, 2}));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device was used again after the usage timeout expired, but we still don't log usage.
+ setCurrentTime(TIME + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(ignoredDeviceId));
+ mTestListener.assertNotifyMotionWasCalled();
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Remove the ignored device, and ensure we still don't log usage.
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {}});
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+ }
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, LogsSingleEventUsageSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+
+ // Device was used.
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device was used again after the usage timeout.
+ setCurrentTime(TIME + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ // The usage session has zero duration because it consisted of only one event.
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 0ns));
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, LogsMultipleEventUsageSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+
+ // Device was used.
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device was used again after some time.
+ setCurrentTime(TIME + 21ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ setCurrentTime(TIME + 42ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // Device was used again after the usage timeout.
+ setCurrentTime(TIME + 42ns + 2 * USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 42ns));
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, RemovingDeviceEndsUsageSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+
+ // Device was used.
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device was used again after some time.
+ setCurrentTime(TIME + 21ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // The device was removed before the usage timeout expired.
+ setCurrentTime(TIME + 42ns);
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {}});
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 21ns));
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, TracksUsageFromDifferentDevicesIndependently) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}});
+
+ // Device 1 was used.
+ setCurrentTime(TIME);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device 2 was used.
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ setCurrentTime(TIME + 400ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device 1 was used after its usage timeout expired. Its usage session is reported.
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns));
+
+ // Device 2 was used.
+ setCurrentTime(TIME + 350ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device 1 was used.
+ setCurrentTime(TIME + 500ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Device 2 is not used for a while, but Device 1 is used again.
+ setCurrentTime(TIME + 400ns + (2 * USAGE_TIMEOUT));
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ // Since Device 2's usage session ended, its usage should be reported.
+ ASSERT_NO_FATAL_FAILURE(
+ assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 150ns + USAGE_TIMEOUT));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+ InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown;
+
+ // Use touchscreen.
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN));
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Use a stylus with the same input device.
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, STYLUS, {ToolType::STYLUS}));
+ setCurrentTime(TIME + 400ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, STYLUS, {ToolType::STYLUS}));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Touchscreen was used again after its usage timeout expired.
+ // This should be tracked as a separate usage of the source in the breakdown.
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 100ns);
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Continue stylus and touchscreen usages.
+ setCurrentTime(TIME + 350ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, STYLUS, {ToolType::STYLUS}));
+ setCurrentTime(TIME + 450ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Touchscreen was used after the stylus's usage timeout expired.
+ // The stylus usage should be tracked in the source breakdown.
+ setCurrentTime(TIME + 400ns + USAGE_TIMEOUT + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN));
+ expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT,
+ 150ns + USAGE_TIMEOUT);
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Remove all devices to force the usage session to be logged.
+ setCurrentTime(TIME + 500ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyInputDevicesChanged({});
+ expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN,
+ 100ns + USAGE_TIMEOUT);
+ // Verify that only one usage session was logged for the device, and that session was broken
+ // down by source correctly.
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO,
+ 400ns + USAGE_TIMEOUT + USAGE_TIMEOUT,
+ expectedSourceBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_TrackSourceByDevice) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}});
+ InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown1;
+ InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown2;
+
+ // Use both devices, with different sources.
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN));
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2, STYLUS, {ToolType::STYLUS}));
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN));
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2, STYLUS, {ToolType::STYLUS}));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Remove all devices to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ expectedSourceBreakdown1.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 100ns);
+ expectedSourceBreakdown2.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 100ns);
+ ASSERT_NO_FATAL_FAILURE(
+ assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns, expectedSourceBreakdown1));
+ ASSERT_NO_FATAL_FAILURE(
+ assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 100ns, expectedSourceBreakdown2));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageBySource_MultiSourceEvent) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+ InputDeviceMetricsLogger::SourceUsageBreakdown expectedSourceBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, //
+ {ToolType::STYLUS}));
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, //
+ {ToolType::STYLUS, ToolType::FINGER}));
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, //
+ {ToolType::STYLUS, ToolType::FINGER}));
+ setCurrentTime(TIME + 300ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, //
+ {ToolType::FINGER}));
+ setCurrentTime(TIME + 400ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID, TOUCHSCREEN | STYLUS, //
+ {ToolType::FINGER}));
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+
+ // Remove all devices to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::STYLUS_DIRECT, 200ns);
+ expectedSourceBreakdown.emplace_back(InputDeviceUsageSource::TOUCHSCREEN, 300ns);
+ ASSERT_NO_FATAL_FAILURE(
+ assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 400ns, expectedSourceBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, UidsNotTrackedWhenThereIsNoActiveSession) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+
+ // Notify interaction with UIDs before the device is used.
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1}));
+
+ // Use the device.
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // Notify interaction for the wrong device.
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), uids({42}));
+
+ // Notify interaction after usage session would have expired.
+ // This interaction should not be tracked.
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({2, 3}));
+
+ // Use the device again, by starting a new usage session.
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+
+ // The first usage session is logged.
+ static const UidUsageBreakdown emptyBreakdown;
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 100ns,
+ /*sourceBreakdown=*/{},
+ /*uidBreakdown=*/emptyBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+ UidUsageBreakdown expectedUidBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1}));
+
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2}));
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2, 3}));
+
+ expectedUidBreakdown.emplace_back(1, 200ns);
+ expectedUidBreakdown.emplace_back(2, 100ns);
+ expectedUidBreakdown.emplace_back(3, 0ns);
+
+ // Remove the device to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 200ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksMultipleSessionsForUid) {
+ mMetricsCollector.notifyInputDevicesChanged({/*id=*/0, {TOUCHSCREEN_STYLUS_INFO}});
+ UidUsageBreakdown expectedUidBreakdown;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2}));
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2}));
+
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1}));
+
+ setCurrentTime(TIME + 300ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 3}));
+ setCurrentTime(TIME + 400ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 3}));
+
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(2, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({4}));
+
+ setCurrentTime(TIME + 300ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 4}));
+
+ setCurrentTime(TIME + 400ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(3, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({2, 3}));
+
+ setCurrentTime(TIME + 500ns + USAGE_TIMEOUT);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({3}));
+
+ // Remove the device to force the usage session to be logged.
+ mMetricsCollector.notifyInputDevicesChanged({});
+ expectedUidBreakdown.emplace_back(1, 300ns + USAGE_TIMEOUT);
+ expectedUidBreakdown.emplace_back(2, 0ns);
+ expectedUidBreakdown.emplace_back(3, 100ns);
+ expectedUidBreakdown.emplace_back(4, 100ns);
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 500ns + USAGE_TIMEOUT,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+TEST_F(InputDeviceMetricsCollectorTest, BreakdownUsageByUid_TracksUidsByDevice) {
+ mMetricsCollector.notifyInputDevicesChanged(
+ {/*id=*/0, {TOUCHSCREEN_STYLUS_INFO, SECOND_TOUCHSCREEN_STYLUS_INFO}});
+ UidUsageBreakdown expectedUidBreakdown1;
+ UidUsageBreakdown expectedUidBreakdown2;
+
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2}));
+
+ setCurrentTime(TIME + 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), uids({1, 3}));
+
+ setCurrentTime(TIME + 200ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID, currentTime(), uids({1, 2}));
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID_2));
+ mMetricsCollector.notifyDeviceInteraction(DEVICE_ID_2, currentTime(), uids({1, 3}));
+
+ setCurrentTime(TIME + 200ns + USAGE_TIMEOUT);
+ expectedUidBreakdown1.emplace_back(1, 200ns);
+ expectedUidBreakdown1.emplace_back(2, 200ns);
+ expectedUidBreakdown2.emplace_back(1, 100ns);
+ expectedUidBreakdown2.emplace_back(3, 100ns);
+ mMetricsCollector.notifyMotion(generateMotionArgs(DEVICE_ID));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(TOUCHSCREEN_STYLUS_INFO, 200ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown1));
+ ASSERT_NO_FATAL_FAILURE(assertUsageLogged(SECOND_TOUCHSCREEN_STYLUS_INFO, 100ns,
+ /*sourceBreakdown=*/{}, expectedUidBreakdown2));
+
+ ASSERT_NO_FATAL_FAILURE(assertUsageNotLogged());
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp b/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp
new file mode 100644
index 0000000000..84ef52c148
--- /dev/null
+++ b/services/inputflinger/tests/InputDeviceMetricsSource_test.cpp
@@ -0,0 +1,296 @@
+/*
+ * 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 "../InputDeviceMetricsSource.h"
+
+#include <NotifyArgsBuilders.h>
+
+#include <android/input.h>
+#include <ftl/enum.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/InputEventBuilders.h>
+#include <linux/input.h>
+
+#include <set>
+
+namespace android {
+
+namespace {
+
+constexpr auto ALL_USAGE_SOURCES = ftl::enum_range<InputDeviceUsageSource>();
+constexpr uint32_t TOUCHSCREEN = AINPUT_SOURCE_TOUCHSCREEN;
+constexpr uint32_t STYLUS = AINPUT_SOURCE_STYLUS;
+constexpr uint32_t KEY_SOURCES =
+ AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_DPAD | AINPUT_SOURCE_GAMEPAD;
+constexpr int32_t POINTER_1_DOWN =
+ AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+
+} // namespace
+
+// --- InputDeviceMetricsSourceDeviceClassificationTest ---
+
+class DeviceClassificationFixture : public ::testing::Test,
+ public ::testing::WithParamInterface<InputDeviceUsageSource> {};
+
+TEST_P(DeviceClassificationFixture, ValidClassifications) {
+ const InputDeviceUsageSource usageSource = GetParam();
+
+ // Use a switch to ensure a test is added for all source classifications.
+ switch (usageSource) {
+ case InputDeviceUsageSource::UNKNOWN: {
+ ASSERT_EQ(InputDeviceUsageSource::UNKNOWN,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NONE,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, TOUCHSCREEN)
+ .build()));
+
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::UNKNOWN};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_KEYBOARD)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::PALM)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::BUTTONS: {
+ ASSERT_EQ(InputDeviceUsageSource::BUTTONS,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+ .keyCode(AKEYCODE_STYLUS_BUTTON_TAIL)
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::KEYBOARD: {
+ ASSERT_EQ(InputDeviceUsageSource::KEYBOARD,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::DPAD: {
+ ASSERT_EQ(InputDeviceUsageSource::DPAD,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+ .keyCode(AKEYCODE_DPAD_CENTER)
+ .build()));
+
+ ASSERT_EQ(InputDeviceUsageSource::DPAD,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+ .keyCode(AKEYCODE_DPAD_CENTER)
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::GAMEPAD: {
+ ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+ .keyCode(AKEYCODE_BUTTON_A)
+ .build()));
+
+ ASSERT_EQ(InputDeviceUsageSource::GAMEPAD,
+ getUsageSourceForKeyArgs(AINPUT_KEYBOARD_TYPE_ALPHABETIC,
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, KEY_SOURCES)
+ .keyCode(AKEYCODE_BUTTON_A)
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::JOYSTICK: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::JOYSTICK};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_JOYSTICK)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+ .axis(AMOTION_EVENT_AXIS_GAS, 1.f))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::MOUSE: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::MOUSE_CAPTURED: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::MOUSE_CAPTURED};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+ AINPUT_SOURCE_MOUSE_RELATIVE)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::MOUSE)
+ .x(100)
+ .y(200)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 100)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::TOUCHPAD: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::TOUCHPAD_CAPTURED: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHPAD_CAPTURED};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHPAD)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+ .x(100)
+ .y(200)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 1)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 2))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::ROTARY_ENCODER: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::ROTARY_ENCODER};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
+ AINPUT_SOURCE_ROTARY_ENCODER)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+ .axis(AMOTION_EVENT_AXIS_SCROLL, 10)
+ .axis(AMOTION_EVENT_AXIS_VSCROLL, 10))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::STYLUS_DIRECT: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_DIRECT};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ STYLUS | TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::STYLUS_INDIRECT: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_INDIRECT};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ STYLUS | TOUCHSCREEN | AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::STYLUS_FUSED: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::STYLUS_FUSED};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER,
+ AINPUT_SOURCE_BLUETOOTH_STYLUS | TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::STYLUS)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::TOUCH_NAVIGATION: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCH_NAVIGATION};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE,
+ AINPUT_SOURCE_TOUCH_NAVIGATION)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+ .x(100)
+ .y(200))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::TOUCHSCREEN: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER)
+ .x(100)
+ .y(200))
+ .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER)
+ .x(300)
+ .y(400))
+ .build()));
+ break;
+ }
+
+ case InputDeviceUsageSource::TRACKBALL: {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TRACKBALL};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_SCROLL,
+ AINPUT_SOURCE_TRACKBALL)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::UNKNOWN)
+ .axis(AMOTION_EVENT_AXIS_VSCROLL, 100)
+ .axis(AMOTION_EVENT_AXIS_HSCROLL, 200))
+ .build()));
+ break;
+ }
+ }
+}
+
+INSTANTIATE_TEST_SUITE_P(InputDeviceMetricsSourceDeviceClassificationTest,
+ DeviceClassificationFixture,
+ ::testing::ValuesIn(ALL_USAGE_SOURCES.begin(), ALL_USAGE_SOURCES.end()),
+ [](const testing::TestParamInfo<InputDeviceUsageSource>& testParamInfo) {
+ return ftl::enum_string(testParamInfo.param);
+ });
+
+TEST(InputDeviceMetricsSourceDeviceClassificationTest, MixedClassificationTouchscreenStylus) {
+ std::set<InputDeviceUsageSource> srcs{InputDeviceUsageSource::TOUCHSCREEN,
+ InputDeviceUsageSource::STYLUS_DIRECT};
+ ASSERT_EQ(srcs,
+ getUsageSourcesForMotionArgs(
+ MotionArgsBuilder(POINTER_1_DOWN, TOUCHSCREEN | STYLUS)
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(100).y(200))
+ .pointer(PointerBuilder(/*id=*/2, ToolType::STYLUS).x(300).y(400))
+ .build()));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index fce0f999b5..c2e67fa788 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -15,19 +15,29 @@
*/
#include "../dispatcher/InputDispatcher.h"
+#include "FakeApplicationHandle.h"
+#include "TestEventMatchers.h"
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/silent_death_test.h>
#include <android-base/stringprintf.h>
#include <android-base/thread_annotations.h>
#include <binder/Binder.h>
+#include <com_android_input_flags.h>
#include <fcntl.h>
+#include <flag_macros.h>
+#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <input/BlockingQueue.h>
#include <input/Input.h>
+#include <input/PrintTools.h>
#include <linux/input.h>
#include <sys/epoll.h>
#include <cinttypes>
+#include <compare>
#include <thread>
#include <unordered_set>
#include <vector>
@@ -43,40 +53,89 @@ using android::os::InputEventInjectionSync;
namespace android::inputdispatcher {
using namespace ftl::flag_operators;
+using testing::AllOf;
+using testing::Not;
+
+namespace {
// 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 DEVICE_ID = DEFAULT_DEVICE_ID;
+static constexpr int32_t SECOND_DEVICE_ID = 2;
// An arbitrary display id.
static constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT;
static constexpr int32_t SECOND_DISPLAY_ID = 1;
+// Ensure common actions are interchangeable between keys and motions for convenience.
+static_assert(AMOTION_EVENT_ACTION_DOWN == AKEY_EVENT_ACTION_DOWN);
+static_assert(AMOTION_EVENT_ACTION_UP == AKEY_EVENT_ACTION_UP);
+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_SCROLL = AMOTION_EVENT_ACTION_SCROLL;
+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.
-static constexpr int32_t WINDOW_PID = 999;
-static constexpr int32_t WINDOW_UID = 1001;
+// The default pid and uid for windows created on the primary display by the test.
+static constexpr gui::Pid WINDOW_PID{999};
+static constexpr gui::Uid WINDOW_UID{1001};
-// 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;
+// The default pid and uid for the windows created on the secondary display by the test.
+static constexpr gui::Pid SECONDARY_WINDOW_PID{1010};
+static constexpr gui::Uid SECONDARY_WINDOW_UID{1012};
// An arbitrary pid of the gesture monitor window
-static constexpr int32_t MONITOR_PID = 2001;
+static constexpr gui::Pid MONITOR_PID{2001};
+
+/**
+ * If we expect to receive the event, the timeout can be made very long. When the test are running
+ * correctly, we will actually never wait until the end of the timeout because the wait will end
+ * when the event comes in. Still, this value shouldn't be infinite. During development, a local
+ * change may cause the test to fail. This timeout should be short enough to not annoy so that the
+ * developer can see the failure quickly (on human scale).
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_EVENT_EXPECTED = 1000ms;
+/**
+ * When no event is expected, we can have a very short timeout. A large value here would slow down
+ * the tests. In the unlikely event of system being too slow, the event may still be present but the
+ * timeout would complete before it is consumed. This would result in test flakiness. If this
+ * occurs, the flakiness rate would be high. Since the flakes are treated with high priority, this
+ * would get noticed and addressed quickly.
+ */
+static constexpr std::chrono::duration CONSUME_TIMEOUT_NO_EVENT_EXPECTED = 10ms;
-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;
-struct PointF {
- float x;
- float y;
-};
+using ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID;
/**
* Return a DOWN key event with KEYCODE_A.
@@ -90,28 +149,21 @@ static KeyEvent getTestKeyEvent() {
return event;
}
-static void assertMotionAction(int32_t expectedAction, int32_t receivedAction) {
- ASSERT_EQ(expectedAction, receivedAction)
- << "expected " << MotionEvent::actionToString(expectedAction) << ", got "
- << MotionEvent::actionToString(receivedAction);
-}
-
// --- FakeInputDispatcherPolicy ---
class FakeInputDispatcherPolicy : public InputDispatcherPolicyInterface {
- InputDispatcherConfiguration mConfig;
-
- using AnrResult = std::pair<sp<IBinder>, int32_t /*pid*/>;
-
-protected:
- virtual ~FakeInputDispatcherPolicy() {}
+ struct AnrResult {
+ sp<IBinder> token{};
+ gui::Pid pid{gui::Pid::INVALID};
+ };
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<const KeyEvent&>(event);
@@ -122,16 +174,16 @@ 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<const MotionEvent&>(event);
EXPECT_EQ(motionEvent.getEventTime(), args.eventTime);
EXPECT_EQ(motionEvent.getAction(), args.action);
- EXPECT_EQ(motionEvent.getX(0), point.x);
- EXPECT_EQ(motionEvent.getY(0), point.y);
- EXPECT_EQ(motionEvent.getRawX(0), point.x);
- EXPECT_EQ(motionEvent.getRawY(0), point.y);
+ EXPECT_NEAR(motionEvent.getX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+ EXPECT_NEAR(motionEvent.getY(0), point.y, MotionEvent::ROUNDING_PRECISION);
+ EXPECT_NEAR(motionEvent.getRawX(0), point.x, MotionEvent::ROUNDING_PRECISION);
+ EXPECT_NEAR(motionEvent.getRawY(0), point.y, MotionEvent::ROUNDING_PRECISION);
});
}
@@ -193,15 +245,14 @@ public:
void assertNotifyWindowUnresponsiveWasCalled(std::chrono::nanoseconds timeout,
const sp<IBinder>& expectedToken,
- int32_t expectedPid) {
+ gui::Pid expectedPid) {
std::unique_lock lock(mLock);
android::base::ScopedLockAssertion assumeLocked(mLock);
AnrResult result;
ASSERT_NO_FATAL_FAILURE(result =
getAnrTokenLockedInterruptible(timeout, mAnrWindows, lock));
- const auto& [token, pid] = result;
- ASSERT_EQ(expectedToken, token);
- ASSERT_EQ(expectedPid, pid);
+ ASSERT_EQ(expectedToken, result.token);
+ ASSERT_EQ(expectedPid, result.pid);
}
/** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
@@ -214,15 +265,14 @@ public:
}
void assertNotifyWindowResponsiveWasCalled(const sp<IBinder>& expectedToken,
- int32_t expectedPid) {
+ gui::Pid expectedPid) {
std::unique_lock lock(mLock);
android::base::ScopedLockAssertion assumeLocked(mLock);
AnrResult result;
ASSERT_NO_FATAL_FAILURE(
result = getAnrTokenLockedInterruptible(0s, mResponsiveWindows, lock));
- const auto& [token, pid] = result;
- ASSERT_EQ(expectedToken, token);
- ASSERT_EQ(expectedPid, pid);
+ ASSERT_EQ(expectedToken, result.token);
+ ASSERT_EQ(expectedPid, result.pid);
}
/** Wrap call with ASSERT_NO_FATAL_FAILURE() to ensure the return value is valid. */
@@ -243,11 +293,6 @@ public:
"signal";
}
- void setKeyRepeatConfiguration(nsecs_t timeout, nsecs_t delay) {
- mConfig.keyRepeatTimeout = timeout;
- mConfig.keyRepeatDelay = delay;
- }
-
PointerCaptureRequest assertSetPointerCaptureCalled(bool enabled) {
std::unique_lock lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
@@ -278,7 +323,9 @@ public:
mPointerCaptureRequest.reset();
}
- void assertDropTargetEquals(const sp<IBinder>& targetToken) {
+ void assertDropTargetEquals(const InputDispatcherInterface& dispatcher,
+ const sp<IBinder>& targetToken) {
+ dispatcher.waitForIdle();
std::scoped_lock lock(mLock);
ASSERT_TRUE(mNotifyDropWindowWasCalled);
ASSERT_EQ(targetToken, mDropTargetWindowToken);
@@ -291,7 +338,7 @@ public:
std::optional<sp<IBinder>> receivedToken =
getItemFromStorageLockedInterruptible(100ms, mBrokenInputChannels, lock,
mNotifyInputChannelBroken);
- ASSERT_TRUE(receivedToken.has_value());
+ ASSERT_TRUE(receivedToken.has_value()) << "Did not receive the broken channel token";
ASSERT_EQ(token, *receivedToken);
}
@@ -302,6 +349,50 @@ public:
mInterceptKeyTimeout = timeout;
}
+ void setStaleEventTimeout(std::chrono::nanoseconds timeout) { mStaleEventTimeout = 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";
+ }
+
+ void assertNotifyDeviceInteractionWasCalled(int32_t deviceId, std::set<gui::Uid> uids) {
+ ASSERT_EQ(std::make_pair(deviceId, uids), mNotifiedInteractions.popWithTimeout(100ms));
+ }
+
+ void assertNotifyDeviceInteractionWasNotCalled() {
+ ASSERT_FALSE(mNotifiedInteractions.popWithTimeout(10ms));
+ }
+
+ void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler) {
+ std::scoped_lock lock(mLock);
+ mUnhandledKeyHandler = handler;
+ }
+
+ void assertUnhandledKeyReported(int32_t keycode) {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+ std::optional<int32_t> unhandledKeycode =
+ getItemFromStorageLockedInterruptible(100ms, mReportedUnhandledKeycodes, lock,
+ mNotifyUnhandledKey);
+ ASSERT_TRUE(unhandledKeycode) << "Expected unhandled key to be reported";
+ ASSERT_EQ(unhandledKeycode, keycode);
+ }
+
+ void assertUnhandledKeyNotReported() {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+ std::optional<int32_t> unhandledKeycode =
+ getItemFromStorageLockedInterruptible(10ms, mReportedUnhandledKeycodes, lock,
+ mNotifyUnhandledKey);
+ ASSERT_FALSE(unhandledKeycode) << "Expected unhandled key NOT to be reported";
+ }
+
private:
std::mutex mLock;
std::unique_ptr<InputEvent> mFilteredEvent GUARDED_BY(mLock);
@@ -323,9 +414,18 @@ private:
sp<IBinder> mDropTargetWindowToken GUARDED_BY(mLock);
bool mNotifyDropWindowWasCalled GUARDED_BY(mLock) = false;
+ bool mPokedUserActivity GUARDED_BY(mLock) = false;
std::chrono::milliseconds mInterceptKeyTimeout = 0ms;
+ std::chrono::nanoseconds mStaleEventTimeout = 1000ms;
+
+ BlockingQueue<std::pair<int32_t /*deviceId*/, std::set<gui::Uid>>> mNotifiedInteractions;
+
+ std::condition_variable mNotifyUnhandledKey;
+ std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock);
+ std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock);
+
// All three ANR-related callbacks behave the same way, so we use this generic function to wait
// for a specific container to become non-empty. When the container is non-empty, return the
// first entry from the container and erase it.
@@ -368,7 +468,6 @@ private:
condition.wait_for(lock, timeout,
[&storage]() REQUIRES(mLock) { return !storage.empty(); });
if (storage.empty()) {
- ADD_FAILURE() << "Did not receive the expected callback";
return std::nullopt;
}
T item = storage.front();
@@ -381,7 +480,7 @@ private:
mConfigurationChangedTime = when;
}
- void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<int32_t> pid,
+ void notifyWindowUnresponsive(const sp<IBinder>& connectionToken, std::optional<gui::Pid> pid,
const std::string&) override {
std::scoped_lock lock(mLock);
ASSERT_TRUE(pid.has_value());
@@ -390,7 +489,7 @@ private:
}
void notifyWindowResponsive(const sp<IBinder>& connectionToken,
- std::optional<int32_t> pid) override {
+ std::optional<gui::Pid> pid) override {
std::scoped_lock lock(mLock);
ASSERT_TRUE(pid.has_value());
mResponsiveWindows.push({connectionToken, *pid});
@@ -412,7 +511,6 @@ private:
void notifyFocusChanged(const sp<IBinder>&, const sp<IBinder>&) override {}
- void notifyUntrustedTouch(const std::string& obscuringPackage) override {}
void notifySensorEvent(int32_t deviceId, InputDeviceSensorType sensorType,
InputDeviceSensorAccuracy accuracy, nsecs_t timestamp,
const std::vector<float>& values) override {}
@@ -422,30 +520,30 @@ private:
void notifyVibratorState(int32_t deviceId, bool isOn) override {}
- void getDispatcherConfiguration(InputDispatcherConfiguration* outConfig) override {
- *outConfig = 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<const KeyEvent*>(inputEvent);
- mFilteredEvent = std::make_unique<KeyEvent>(*keyEvent);
+ switch (inputEvent.getType()) {
+ case InputEventType::KEY: {
+ const KeyEvent& keyEvent = static_cast<const KeyEvent&>(inputEvent);
+ mFilteredEvent = std::make_unique<KeyEvent>(keyEvent);
break;
}
- case AINPUT_EVENT_TYPE_MOTION: {
- const MotionEvent* motionEvent = static_cast<const MotionEvent*>(inputEvent);
- mFilteredEvent = std::make_unique<MotionEvent>(*motionEvent);
+ case InputEventType::MOTION: {
+ const MotionEvent& motionEvent = static_cast<const MotionEvent&>(inputEvent);
+ mFilteredEvent = std::make_unique<MotionEvent>(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;
}
@@ -453,15 +551,19 @@ private:
void interceptMotionBeforeQueueing(int32_t, nsecs_t, uint32_t&) override {}
- nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, const KeyEvent*, uint32_t) override {
+ nsecs_t interceptKeyBeforeDispatching(const sp<IBinder>&, 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<IBinder>&, const KeyEvent*, uint32_t, KeyEvent*) override {
- return false;
+ std::optional<KeyEvent> dispatchUnhandledKey(const sp<IBinder>&, const KeyEvent& event,
+ uint32_t) override {
+ std::scoped_lock lock(mLock);
+ mReportedUnhandledKeycodes.emplace(event.getKeyCode());
+ mNotifyUnhandledKey.notify_all();
+ return mUnhandledKeyHandler != nullptr ? mUnhandledKeyHandler(event) : std::nullopt;
}
void notifySwitch(nsecs_t when, uint32_t switchValues, uint32_t switchMask,
@@ -470,10 +572,17 @@ 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;
+ }
+
+ bool isStaleEvent(nsecs_t currentTime, nsecs_t eventTime) override {
+ return std::chrono::nanoseconds(currentTime - eventTime) >= mStaleEventTimeout;
+ }
void onPointerDownOutsideFocus(const sp<IBinder>& newToken) override {
std::scoped_lock lock(mLock);
@@ -492,6 +601,11 @@ private:
mDropTargetWindowToken = token;
}
+ void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
+ const std::set<gui::Uid>& uids) override {
+ ASSERT_TRUE(mNotifiedInteractions.emplace(deviceId, uids));
+ }
+
void assertFilterInputEventWasCalledInternal(
const std::function<void(const InputEvent&)>& verify) {
std::scoped_lock lock(mLock);
@@ -500,25 +614,27 @@ private:
mFilteredEvent = nullptr;
}
};
+} // namespace
// --- InputDispatcherTest ---
class InputDispatcherTest : public testing::Test {
protected:
- sp<FakeInputDispatcherPolicy> mFakePolicy;
+ std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy;
std::unique_ptr<InputDispatcher> mDispatcher;
void SetUp() override {
- mFakePolicy = new FakeInputDispatcherPolicy();
- mDispatcher = std::make_unique<InputDispatcher>(mFakePolicy, STALE_EVENT_TIMEOUT);
- mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+ mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+ mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
+
+ mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
// Start InputDispatcher thread
ASSERT_EQ(OK, mDispatcher->start());
}
void TearDown() override {
ASSERT_EQ(OK, mDispatcher->stop());
- mFakePolicy.clear();
+ mFakePolicy.reset();
mDispatcher.reset();
}
@@ -536,14 +652,10 @@ protected:
}
}
- void setFocusedWindow(const sp<WindowInfoHandle>& window,
- const sp<WindowInfoHandle>& focusedWindow = nullptr) {
+ void setFocusedWindow(const sp<WindowInfoHandle>& 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);
@@ -556,10 +668,10 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesKeyEvents) {
// Rejects undefined key actions.
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
INVALID_HMAC,
- /*action*/ -1, 0, AKEYCODE_A, KEY_A, AMETA_NONE, 0, ARBITRARY_TIME,
+ /*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.";
@@ -568,7 +680,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.";
}
@@ -592,13 +704,13 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
ui::Transform identityTransform;
// Rejects undefined motion actions.
event.initialize(InputEvent::nextId(), DEVICE_ID, source, DISPLAY_ID, INVALID_HMAC,
- /*action*/ -1, 0, 0, edgeFlags, metaState, 0, classification,
+ /*action=*/-1, 0, 0, edgeFlags, metaState, 0, classification,
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -608,9 +720,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -620,9 +732,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -632,9 +744,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -644,9 +756,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
0, 0, edgeFlags, metaState, 0, classification, identityTransform, 0, 0,
AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION,
identityTransform, ARBITRARY_TIME, ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -656,9 +768,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 0, pointerProperties, pointerCoords);
+ /*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.";
@@ -667,9 +779,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ MAX_POINTERS + 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -680,9 +792,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -692,9 +804,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*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.";
@@ -706,9 +818,9 @@ TEST_F(InputDispatcherTest, InjectInputEvent_ValidatesMotionEvents) {
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, ARBITRARY_TIME,
ARBITRARY_TIME,
- /*pointerCount*/ 2, pointerProperties, pointerCoords);
+ /*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.";
}
@@ -717,24 +829,24 @@ 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;
mFakePolicy->assertNotifySwitchWasCalled(args);
}
-// --- InputDispatcherTest SetInputWindowTest ---
+namespace {
+
static constexpr std::chrono::duration INJECT_EVENT_TIMEOUT = 500ms;
// Default input dispatching timeout if there is no focused application or paused window
// from which to determine an appropriate dispatching timeout.
@@ -742,37 +854,18 @@ static const std::chrono::duration DISPATCHING_TIMEOUT = std::chrono::millisecon
android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
android::base::HwTimeoutMultiplier());
-class FakeApplicationHandle : public InputApplicationHandle {
-public:
- FakeApplicationHandle() {
- mInfo.name = "Fake Application";
- mInfo.token = new BBinder();
- mInfo.dispatchingTimeoutMillis =
- std::chrono::duration_cast<std::chrono::milliseconds>(DISPATCHING_TIMEOUT).count();
- }
- virtual ~FakeApplicationHandle() {}
-
- virtual bool updateInfo() override { return true; }
-
- void setDispatchingTimeout(std::chrono::milliseconds timeout) {
- mInfo.dispatchingTimeoutMillis = timeout.count();
- }
-};
-
class FakeInputReceiver {
public:
explicit FakeInputReceiver(std::unique_ptr<InputChannel> clientChannel, const std::string name)
- : mName(name) {
- mConsumer = std::make_unique<InputConsumer>(std::move(clientChannel));
- }
+ : mConsumer(std::move(clientChannel)), mName(name) {}
- InputEvent* consume() {
+ InputEvent* consume(std::chrono::milliseconds timeout, bool handled = false) {
InputEvent* event;
- std::optional<uint32_t> consumeSeq = receiveEvent(&event);
+ std::optional<uint32_t> consumeSeq = receiveEvent(timeout, &event);
if (!consumeSeq) {
return nullptr;
}
- finishEvent(*consumeSeq);
+ finishEvent(*consumeSeq, handled);
return event;
}
@@ -780,17 +873,18 @@ public:
* Receive an event without acknowledging it.
* Return the sequence number that could later be used to send finished signal.
*/
- std::optional<uint32_t> receiveEvent(InputEvent** outEvent = nullptr) {
+ std::optional<uint32_t> receiveEvent(std::chrono::milliseconds timeout,
+ InputEvent** outEvent = nullptr) {
uint32_t consumeSeq;
InputEvent* event;
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,
- &event);
+ status = mConsumer.consume(&mEventFactory, /*consumeBatches=*/true, -1, &consumeSeq,
+ &event);
std::chrono::duration elapsed = std::chrono::steady_clock::now() - start;
- if (elapsed > 100ms) {
+ if (elapsed > timeout) {
break;
}
}
@@ -817,74 +911,90 @@ public:
/**
* To be used together with "receiveEvent" to complete the consumption of an event.
*/
- void finishEvent(uint32_t consumeSeq) {
- const status_t status = mConsumer->sendFinishedSignal(consumeSeq, true);
+ void finishEvent(uint32_t consumeSeq, bool handled = true) {
+ const status_t status = mConsumer.sendFinishedSignal(consumeSeq, handled);
ASSERT_EQ(OK, status) << mName.c_str() << ": consumer sendFinishedSignal should return OK.";
}
void sendTimeline(int32_t inputEventId, std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
- const status_t status = mConsumer->sendTimeline(inputEventId, timeline);
+ const status_t status = mConsumer.sendTimeline(inputEventId, timeline);
ASSERT_EQ(OK, status);
}
- void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
+ void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
std::optional<int32_t> expectedDisplayId,
std::optional<int32_t> expectedFlags) {
- InputEvent* event = consume();
+ InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
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<const KeyEvent&>(*event);
- EXPECT_EQ(expectedAction, keyEvent.getAction());
+ ASSERT_THAT(keyEvent, WithKeyAction(expectedAction));
if (expectedFlags.has_value()) {
EXPECT_EQ(expectedFlags.value(), keyEvent.getFlags());
}
break;
}
- case AINPUT_EVENT_TYPE_MOTION: {
+ case InputEventType::MOTION: {
const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
- assertMotionAction(expectedAction, motionEvent.getAction());
-
+ ASSERT_THAT(motionEvent, WithMotionAction(expectedAction));
if (expectedFlags.has_value()) {
EXPECT_EQ(expectedFlags.value(), motionEvent.getFlags());
}
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(CONSUME_TIMEOUT_EVENT_EXPECTED);
+
+ 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<MotionEvent*>(event);
+ }
+
+ void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& 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();
+ InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
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.";
@@ -894,12 +1004,11 @@ public:
}
void consumeCaptureEvent(bool hasCapture) {
- const InputEvent* event = consume();
+ const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
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.";
@@ -909,12 +1018,10 @@ public:
}
void consumeDragEvent(bool isExiting, float x, float y) {
- const InputEvent* event = consume();
+ const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
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.";
@@ -926,12 +1033,11 @@ public:
}
void consumeTouchModeEvent(bool inTouchMode) {
- const InputEvent* event = consume();
+ const InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
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.";
@@ -940,27 +1046,25 @@ public:
}
void assertNoEvents() {
- InputEvent* event = consume();
+ InputEvent* event = consume(CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
if (event == nullptr) {
return;
}
- if (event->getType() == AINPUT_EVENT_TYPE_KEY) {
+ if (event->getType() == InputEventType::KEY) {
KeyEvent& keyEvent = static_cast<KeyEvent&>(*event);
- ADD_FAILURE() << "Received key event "
- << KeyEvent::actionToString(keyEvent.getAction());
- } else if (event->getType() == AINPUT_EVENT_TYPE_MOTION) {
+ ADD_FAILURE() << "Received key event " << keyEvent;
+ } else if (event->getType() == InputEventType::MOTION) {
MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
- ADD_FAILURE() << "Received motion event "
- << MotionEvent::actionToString(motionEvent.getAction());
- } else if (event->getType() == AINPUT_EVENT_TYPE_FOCUS) {
+ ADD_FAILURE() << "Received motion event " << motionEvent;
+ } else if (event->getType() == InputEventType::FOCUS) {
FocusEvent& focusEvent = static_cast<FocusEvent&>(*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<CaptureEvent&>(*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<TouchModeEvent&>(*event);
ADD_FAILURE() << "Received touch mode event, inTouchMode = "
<< (touchModeEvent.isInTouchMode() ? "true" : "false");
@@ -969,12 +1073,12 @@ public:
<< ": should not have received any events, so consume() should return NULL";
}
- sp<IBinder> getToken() { return mConsumer->getChannel()->getConnectionToken(); }
+ sp<IBinder> getToken() { return mConsumer.getChannel()->getConnectionToken(); }
- int getChannelFd() { return mConsumer->getChannel()->getFd().get(); }
+ int getChannelFd() { return mConsumer.getChannel()->getFd().get(); }
-protected:
- std::unique_ptr<InputConsumer> mConsumer;
+private:
+ InputConsumer mConsumer;
PreallocatedInputEventFactory mEventFactory;
std::string mName;
@@ -1004,10 +1108,7 @@ public:
mInfo.name = name;
mInfo.dispatchingTimeout = DISPATCHING_TIMEOUT;
mInfo.alpha = 1.0;
- mInfo.frameLeft = 0;
- mInfo.frameTop = 0;
- mInfo.frameRight = WIDTH;
- mInfo.frameBottom = HEIGHT;
+ mInfo.frame = Rect(0, 0, WIDTH, HEIGHT);
mInfo.transform.set(0, 0);
mInfo.globalScaleFactor = 1.0;
mInfo.touchableRegion.clear();
@@ -1018,12 +1119,12 @@ public:
mInfo.inputConfig = WindowInfo::InputConfig::DEFAULT;
}
- sp<FakeWindowHandle> clone(
- const std::shared_ptr<InputApplicationHandle>& inputApplicationHandle,
- const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId) {
- sp<FakeWindowHandle> handle =
- new FakeWindowHandle(inputApplicationHandle, dispatcher, mInfo.name + "(Mirror)",
- displayId, mInfo.token);
+ sp<FakeWindowHandle> clone(int32_t displayId) {
+ sp<FakeWindowHandle> handle = sp<FakeWindowHandle>::make(mInfo.name + "(Mirror)");
+ handle->mInfo = mInfo;
+ handle->mInfo.displayId = displayId;
+ handle->mInfo.id = sId++;
+ handle->mInputReceiver = mInputReceiver;
return handle;
}
@@ -1077,6 +1178,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; }
@@ -1084,10 +1189,7 @@ public:
void setApplicationToken(sp<IBinder> token) { mInfo.applicationInfo.token = token; }
void setFrame(const Rect& frame, const ui::Transform& displayTransform = ui::Transform()) {
- mInfo.frameLeft = frame.left;
- mInfo.frameTop = frame.top;
- mInfo.frameRight = frame.right;
- mInfo.frameBottom = frame.bottom;
+ mInfo.frame = frame;
mInfo.touchableRegion.clear();
mInfo.addTouchableRegion(frame);
@@ -1119,25 +1221,43 @@ public:
void setWindowOffset(float offsetX, float offsetY) { mInfo.transform.set(offsetX, offsetY); }
+ KeyEvent* consumeKey(bool handled = true) {
+ InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED, handled);
+ if (event == nullptr) {
+ ADD_FAILURE() << "Consume failed : no event";
+ return nullptr;
+ }
+ if (event->getType() != InputEventType::KEY) {
+ ADD_FAILURE() << "Instead of key event, got " << *event;
+ return nullptr;
+ }
+ return static_cast<KeyEvent*>(event);
+ }
+
+ void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) {
+ KeyEvent* keyEvent = consumeKey();
+ ASSERT_NE(nullptr, keyEvent) << "Did not get a key event, but expected " << matcher;
+ ASSERT_THAT(*keyEvent, matcher);
+ }
+
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,
@@ -1147,7 +1267,7 @@ public:
void consumeAnyMotionDown(std::optional<int32_t> expectedDisplayId = std::nullopt,
std::optional<int32_t> expectedFlags = std::nullopt) {
- consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
+ consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN, expectedDisplayId,
expectedFlags);
}
@@ -1156,36 +1276,35 @@ 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());
- const MotionEvent& motionEvent = static_cast<MotionEvent&>(*event);
- EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent.getActionMasked());
- EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getX());
- EXPECT_EQ(0.f, motionEvent.getRawPointerCoords(0)->getY());
+ MotionEvent* motionEvent = consumeMotion();
+ ASSERT_NE(nullptr, motionEvent);
+ EXPECT_EQ(AMOTION_EVENT_ACTION_OUTSIDE, motionEvent->getActionMasked());
+ EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getX());
+ EXPECT_EQ(0.f, motionEvent->getRawPointerCoords(0)->getY());
}
void consumeFocusEvent(bool hasFocus, bool inTouchMode = true) {
@@ -1200,7 +1319,16 @@ public:
mInputReceiver->consumeCaptureEvent(hasCapture);
}
- void consumeEvent(int32_t expectedEventType, int32_t expectedAction,
+ const MotionEvent& consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+ MotionEvent* motionEvent = consumeMotion();
+ if (nullptr == motionEvent) {
+ LOG(FATAL) << "Did not get a motion event, but expected " << matcher;
+ }
+ EXPECT_THAT(*motionEvent, matcher);
+ return *motionEvent;
+ }
+
+ void consumeEvent(InputEventType expectedEventType, int32_t expectedAction,
std::optional<int32_t> expectedDisplayId,
std::optional<int32_t> expectedFlags) {
ASSERT_NE(mInputReceiver, nullptr) << "Invalid consume event on window with no receiver";
@@ -1223,7 +1351,7 @@ public:
ADD_FAILURE() << "Invalid receive event on window with no receiver";
return std::nullopt;
}
- return mInputReceiver->receiveEvent(outEvent);
+ return mInputReceiver->receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED, outEvent);
}
void finishEvent(uint32_t sequenceNum) {
@@ -1236,22 +1364,21 @@ public:
mInputReceiver->sendTimeline(inputEventId, timeline);
}
- InputEvent* consume() {
+ InputEvent* consume(std::chrono::milliseconds timeout, bool handled = true) {
if (mInputReceiver == nullptr) {
return nullptr;
}
- return mInputReceiver->consume();
+ return mInputReceiver->consume(timeout, handled);
}
MotionEvent* consumeMotion() {
- InputEvent* event = consume();
+ InputEvent* event = consume(CONSUME_TIMEOUT_EVENT_EXPECTED);
if (event == nullptr) {
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<MotionEvent*>(event);
@@ -1271,225 +1398,180 @@ public:
const std::string& getName() { return mName; }
- void setOwnerInfo(int32_t ownerPid, int32_t ownerUid) {
+ void setOwnerInfo(gui::Pid ownerPid, gui::Uid ownerUid) {
mInfo.ownerPid = ownerPid;
mInfo.ownerUid = ownerUid;
}
- int32_t getPid() const { return mInfo.ownerPid; }
+ gui::Pid getPid() const { return mInfo.ownerPid; }
void destroyReceiver() { mInputReceiver = nullptr; }
int getChannelFd() { return mInputReceiver->getChannelFd(); }
private:
+ FakeWindowHandle(std::string name) : mName(name){};
const std::string mName;
- std::unique_ptr<FakeInputReceiver> mInputReceiver;
+ std::shared_ptr<FakeInputReceiver> mInputReceiver;
static std::atomic<int32_t> sId; // each window gets a unique id, like in surfaceflinger
+ friend class sp<FakeWindowHandle>;
};
std::atomic<int32_t> FakeWindowHandle::sId{1};
+class FakeMonitorReceiver {
+public:
+ FakeMonitorReceiver(InputDispatcher& dispatcher, const std::string name, int32_t displayId)
+ : mInputReceiver(*dispatcher.createInputMonitor(displayId, name, MONITOR_PID), name) {}
+
+ sp<IBinder> getToken() { return mInputReceiver.getToken(); }
+
+ void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+ mInputReceiver.consumeEvent(InputEventType::KEY, AKEY_EVENT_ACTION_DOWN, expectedDisplayId,
+ expectedFlags);
+ }
+
+ std::optional<int32_t> receiveEvent() {
+ return mInputReceiver.receiveEvent(CONSUME_TIMEOUT_EVENT_EXPECTED);
+ }
+
+ void finishEvent(uint32_t consumeSeq) { return mInputReceiver.finishEvent(consumeSeq); }
+
+ void consumeMotionDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+ mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_DOWN,
+ expectedDisplayId, expectedFlags);
+ }
+
+ void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+ mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_MOVE,
+ expectedDisplayId, expectedFlags);
+ }
+
+ void consumeMotionUp(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+ mInputReceiver.consumeEvent(InputEventType::MOTION, AMOTION_EVENT_ACTION_UP,
+ expectedDisplayId, expectedFlags);
+ }
+
+ void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
+ 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(InputEventType::MOTION, action, ADISPLAY_ID_DEFAULT,
+ /*expectedFlags=*/0);
+ }
+
+ void consumeMotionEvent(const ::testing::Matcher<MotionEvent>& matcher) {
+ mInputReceiver.consumeMotionEvent(matcher);
+ }
+
+ MotionEvent* consumeMotion() { return mInputReceiver.consumeMotion(); }
+
+ void assertNoEvents() { mInputReceiver.assertNoEvents(); }
+
+private:
+ FakeInputReceiver mInputReceiver;
+};
+
static InputEventInjectionResult injectKey(
- const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t repeatCount,
+ InputDispatcher& dispatcher, int32_t action, int32_t repeatCount,
int32_t displayId = ADISPLAY_ID_NONE,
InputEventInjectionSync syncMode = InputEventInjectionSync::WAIT_FOR_RESULT,
std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
- bool allowKeyRepeat = true, std::optional<int32_t> targetUid = {},
+ bool allowKeyRepeat = true, std::optional<gui::Uid> targetUid = {},
uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
KeyEvent event;
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
// 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;
}
// Inject event until dispatch out.
- return dispatcher->injectInputEvent(&event, targetUid, syncMode, injectionTimeout, policyFlags);
+ return dispatcher.injectInputEvent(&event, targetUid, syncMode, injectionTimeout, policyFlags);
}
-static InputEventInjectionResult injectKeyDown(const std::unique_ptr<InputDispatcher>& dispatcher,
+static void assertInjectedKeyTimesOut(InputDispatcher& dispatcher) {
+ InputEventInjectionResult result =
+ injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_NONE,
+ InputEventInjectionSync::WAIT_FOR_RESULT, CONSUME_TIMEOUT_NO_EVENT_EXPECTED);
+ if (result != InputEventInjectionResult::TIMED_OUT) {
+ FAIL() << "Injection should have timed out, but got " << ftl::enum_string(result);
+ }
+}
+
+static InputEventInjectionResult injectKeyDown(InputDispatcher& dispatcher,
int32_t displayId = ADISPLAY_ID_NONE) {
- 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
// sending a subsequent key up. When key repeat is enabled, the dispatcher cannot idle because it
// has to be woken up to process the repeating key.
-static InputEventInjectionResult injectKeyDownNoRepeat(
- const std::unique_ptr<InputDispatcher>& dispatcher, int32_t displayId = ADISPLAY_ID_NONE) {
- return injectKey(dispatcher, AKEY_EVENT_ACTION_DOWN, /* repeatCount */ 0, displayId,
+static InputEventInjectionResult injectKeyDownNoRepeat(InputDispatcher& dispatcher,
+ int32_t displayId = ADISPLAY_ID_NONE) {
+ 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<InputDispatcher>& dispatcher,
+static InputEventInjectionResult injectKeyUp(InputDispatcher& 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) {
- mProperties.clear();
- mProperties.id = id;
- mProperties.toolType = toolType;
- mCoords.clear();
- }
-
- PointerBuilder& x(float x) { return axis(AMOTION_EVENT_AXIS_X, x); }
-
- PointerBuilder& y(float y) { return axis(AMOTION_EVENT_AXIS_Y, y); }
-
- PointerBuilder& axis(int32_t axis, float value) {
- mCoords.setAxisValue(axis, value);
- return *this;
- }
-
- PointerProperties buildProperties() const { return mProperties; }
-
- PointerCoords buildCoords() const { return mCoords; }
-
-private:
- PointerProperties mProperties;
- PointerCoords mCoords;
-};
-
-class MotionEventBuilder {
-public:
- MotionEventBuilder(int32_t action, int32_t source) {
- mAction = action;
- mSource = source;
- mEventTime = systemTime(SYSTEM_TIME_MONOTONIC);
- }
-
- MotionEventBuilder& eventTime(nsecs_t eventTime) {
- mEventTime = eventTime;
- return *this;
- }
-
- MotionEventBuilder& displayId(int32_t displayId) {
- mDisplayId = displayId;
- return *this;
- }
-
- MotionEventBuilder& actionButton(int32_t actionButton) {
- mActionButton = actionButton;
- return *this;
- }
-
- MotionEventBuilder& buttonState(int32_t buttonState) {
- mButtonState = buttonState;
- return *this;
- }
-
- MotionEventBuilder& rawXCursorPosition(float rawXCursorPosition) {
- mRawXCursorPosition = rawXCursorPosition;
- return *this;
- }
-
- MotionEventBuilder& rawYCursorPosition(float rawYCursorPosition) {
- mRawYCursorPosition = rawYCursorPosition;
- return *this;
- }
-
- MotionEventBuilder& pointer(PointerBuilder pointer) {
- mPointers.push_back(pointer);
- return *this;
- }
-
- MotionEventBuilder& addFlag(uint32_t flags) {
- mFlags |= flags;
- return *this;
- }
-
- MotionEvent build() {
- std::vector<PointerProperties> pointerProperties;
- std::vector<PointerCoords> 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) &&
- mPointers.size() == 1) {
- mRawXCursorPosition = pointerCoords[0].getX();
- mRawYCursorPosition = pointerCoords[0].getY();
- }
-
- MotionEvent event;
- ui::Transform identityTransform;
- event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
- mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
- mButtonState, MotionClassification::NONE, identityTransform,
- /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
- mRawYCursorPosition, identityTransform, mEventTime, mEventTime,
- mPointers.size(), pointerProperties.data(), pointerCoords.data());
-
- return event;
- }
-
-private:
- int32_t mAction;
- int32_t mSource;
- 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<PointerBuilder> mPointers;
-};
-
static InputEventInjectionResult injectMotionEvent(
- const std::unique_ptr<InputDispatcher>& dispatcher, const MotionEvent& event,
+ InputDispatcher& dispatcher, const MotionEvent& event,
std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT,
- std::optional<int32_t> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
- return dispatcher->injectInputEvent(&event, targetUid, injectionMode, injectionTimeout,
- policyFlags);
+ std::optional<gui::Uid> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
+ return dispatcher.injectInputEvent(&event, targetUid, injectionMode, injectionTimeout,
+ policyFlags);
}
static InputEventInjectionResult injectMotionEvent(
- const std::unique_ptr<InputDispatcher>& dispatcher, int32_t action, int32_t source,
- int32_t displayId, const PointF& position = {100, 200},
+ InputDispatcher& dispatcher, int32_t action, int32_t source, int32_t displayId,
+ const PointF& position = {100, 200},
const PointF& cursorPosition = {AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION},
std::chrono::milliseconds injectionTimeout = INJECT_EVENT_TIMEOUT,
InputEventInjectionSync injectionMode = InputEventInjectionSync::WAIT_FOR_RESULT,
nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC),
- std::optional<int32_t> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
- MotionEvent event = MotionEventBuilder(action, source)
- .displayId(displayId)
- .eventTime(eventTime)
- .rawXCursorPosition(cursorPosition.x)
- .rawYCursorPosition(cursorPosition.y)
- .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER)
- .x(position.x)
- .y(position.y))
- .build();
+ std::optional<gui::Uid> targetUid = {}, uint32_t policyFlags = DEFAULT_POLICY_FLAGS) {
+ MotionEventBuilder motionBuilder =
+ MotionEventBuilder(action, source)
+ .displayId(displayId)
+ .eventTime(eventTime)
+ .rawXCursorPosition(cursorPosition.x)
+ .rawYCursorPosition(cursorPosition.y)
+ .pointer(
+ PointerBuilder(/*id=*/0, ToolType::FINGER).x(position.x).y(position.y));
+ if (MotionEvent::getActionMasked(action) == ACTION_DOWN) {
+ motionBuilder.downTime(eventTime);
+ }
// Inject event until dispatch out.
- return injectMotionEvent(dispatcher, event, injectionTimeout, injectionMode, targetUid,
- policyFlags);
+ return injectMotionEvent(dispatcher, motionBuilder.build(), injectionTimeout, injectionMode,
+ targetUid, policyFlags);
}
-static InputEventInjectionResult injectMotionDown(
- const std::unique_ptr<InputDispatcher>& dispatcher, int32_t source, int32_t displayId,
- const PointF& location = {100, 200}) {
+static InputEventInjectionResult injectMotionDown(InputDispatcher& dispatcher, int32_t source,
+ int32_t displayId,
+ const PointF& location = {100, 200}) {
return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_DOWN, source, displayId, location);
}
-static InputEventInjectionResult injectMotionUp(const std::unique_ptr<InputDispatcher>& dispatcher,
- int32_t source, int32_t displayId,
+static InputEventInjectionResult injectMotionUp(InputDispatcher& dispatcher, int32_t source,
+ int32_t displayId,
const PointF& location = {100, 200}) {
return injectMotionEvent(dispatcher, AMOTION_EVENT_ACTION_UP, source, displayId, location);
}
@@ -1497,15 +1579,38 @@ static InputEventInjectionResult injectMotionUp(const std::unique_ptr<InputDispa
static NotifyKeyArgs generateKeyArgs(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, 0 /*readTime*/, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
- displayId, POLICY_FLAG_PASS_TO_USER, action, /* flags */ 0, AKEYCODE_A,
- KEY_A, AMETA_NONE, currentTime);
+ NotifyKeyArgs args(/*id=*/0, currentTime, /*readTime=*/0, DEVICE_ID, AINPUT_SOURCE_KEYBOARD,
+ displayId, POLICY_FLAG_PASS_TO_USER, action, /*flags=*/0, AKEYCODE_A, KEY_A,
+ AMETA_NONE, currentTime);
+
+ return args;
+}
+
+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;
}
-static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source, int32_t displayId,
- const std::vector<PointF>& points) {
+[[nodiscard]] static NotifyMotionArgs generateMotionArgs(int32_t action, int32_t source,
+ int32_t displayId,
+ const std::vector<PointF>& 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";
@@ -1517,7 +1622,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);
@@ -1526,13 +1631,13 @@ 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,
- POLICY_FLAG_PASS_TO_USER, action, /* actionButton */ 0, /* flags */ 0,
- AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
+ 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,
- pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+ pointerCoords, /*xPrecision=*/0, /*yPrecision=*/0,
AMOTION_EVENT_INVALID_CURSOR_POSITION,
- AMOTION_EVENT_INVALID_CURSOR_POSITION, currentTime, /* videoFrames */ {});
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, currentTime, /*videoFrames=*/{});
return args;
}
@@ -1547,9 +1652,11 @@ 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);
}
+} // namespace
+
/**
* When a window unexpectedly disposes of its input channel, policy should be notified about the
* broken channel.
@@ -1557,10 +1664,10 @@ static NotifyPointerCaptureChangedArgs generatePointerCaptureChangedArgs(
TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Window that breaks its input channel",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Window that breaks its input channel", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
// Window closes its channel, but the window remains.
window->destroyReceiver();
@@ -1569,12 +1676,12 @@ TEST_F(InputDispatcherTest, WhenInputChannelBreaks_PolicyIsNotified) {
TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Window should receive motion event.
@@ -1583,13 +1690,13 @@ TEST_F(InputDispatcherTest, SetInputWindow_SingleWindowTouch) {
TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
// Inject a MotionEvent to an unknown display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_NONE))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Window should receive motion event.
@@ -1597,19 +1704,19 @@ TEST_F(InputDispatcherTest, WhenDisplayNotSpecified_InjectMotionToDefaultDisplay
}
/**
- * Calling setInputWindows once should not cause any issues.
- * This test serves as a sanity check for the next test, where setInputWindows is
+ * Calling onWindowInfosChanged once should not cause any issues.
+ * This test serves as a sanity check for the next test, where onWindowInfosChanged is
* called twice.
*/
TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
window->setFrame(Rect(0, 0, 100, 100));
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -1618,18 +1725,18 @@ TEST_F(InputDispatcherTest, SetInputWindowOnceWithSingleTouchWindow) {
}
/**
- * Calling setInputWindows twice, with the same info, should not cause any issues.
+ * Calling onWindowInfosChanged twice, with the same info, should not cause any issues.
*/
TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
window->setFrame(Rect(0, 0, 100, 100));
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -1641,13 +1748,14 @@ TEST_F(InputDispatcherTest, SetInputWindowTwice_SingleWindowTouch) {
TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowTop =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
sp<FakeWindowHandle> windowSecond =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Top window should receive the touch down event. Second window should not receive anything.
@@ -1665,18 +1773,19 @@ TEST_F(InputDispatcherTest, SetInputWindow_MultiWindowsTouch) {
TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCanceled) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> foregroundWindow =
- new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
foregroundWindow->setDupTouchToWallpaper(true);
sp<FakeWindowHandle> wallpaperWindow =
- new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {100, 200}))
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(200))
+ .build()))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Both foreground window and its wallpaper should receive the touch down
@@ -1684,21 +1793,59 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
- ADISPLAY_ID_DEFAULT, {110, 200}))
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(110).y(200))
+ .build()))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- foregroundWindow->consumeMotionMove();
+ foregroundWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
wallpaperWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Now the foreground window goes away, but the wallpaper stays
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
foregroundWindow->consumeMotionCancel();
// Since the "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
}
/**
+ * 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ // 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->onWindowInfosChanged({{}, {}, 0, 0});
+ const std::map<int32_t, PointF> expectedPointers{{1, PointF{110, 100}}};
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedPointers)));
+}
+
+/**
* Same test as WhenForegroundWindowDisappears_WallpaperTouchIsCanceled above,
* with the following differences:
* After ACTION_DOWN, Wallpaper window hangs up its channel, which forces the dispatcher to
@@ -1709,17 +1856,16 @@ TEST_F(InputDispatcherTest, WhenForegroundWindowDisappears_WallpaperTouchIsCance
TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> foregroundWindow =
- new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
foregroundWindow->setDupTouchToWallpaper(true);
sp<FakeWindowHandle> wallpaperWindow =
- new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {foregroundWindow, wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{100, 200}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -1728,7 +1874,7 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) {
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {110, 200}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -1741,60 +1887,87 @@ TEST_F(InputDispatcherTest, WhenWallpaperDisappears_NoCrash) {
// Now the foreground window goes away, but the wallpaper stays, even though its channel
// is no longer valid.
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged({{*wallpaperWindow->getInfo()}, {}, 0, 0});
foregroundWindow->consumeMotionCancel();
}
+class ShouldSplitTouchFixture : public InputDispatcherTest,
+ public ::testing::WithParamInterface<bool> {};
+INSTANTIATE_TEST_SUITE_P(InputDispatcherTest, ShouldSplitTouchFixture,
+ ::testing::Values(true, false));
/**
* A single window that receives touch (on top), and a wallpaper window underneath it.
* The top window gets a multitouch gesture.
* Ensure that wallpaper gets the same gesture.
*/
-TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
+TEST_P(ShouldSplitTouchFixture, WallpaperWindowReceivesMultiTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
- window->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> foregroundWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+ foregroundWindow->setDupTouchToWallpaper(true);
+ foregroundWindow->setPreventSplitting(GetParam());
sp<FakeWindowHandle> wallpaperWindow =
- new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*foregroundWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
// Touch down on top window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{100, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Both top window and its wallpaper should receive the touch down
- window->consumeMotionDown();
+ foregroundWindow->consumeMotionDown();
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Second finger down on the top window
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,
+ injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1 /* pointerIndex */);
- wallpaperWindow->consumeMotionPointerDown(1 /* pointerIndex */, ADISPLAY_ID_DEFAULT,
+ foregroundWindow->consumeMotionPointerDown(/*pointerIndex=*/1);
+ wallpaperWindow->consumeMotionPointerDown(/*pointerIndex=*/1, ADISPLAY_ID_DEFAULT,
expectedWallpaperFlags);
- window->assertNoEvents();
- wallpaperWindow->assertNoEvents();
+
+ const MotionEvent secondFingerUpEvent =
+ MotionEventBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(100))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ foregroundWindow->consumeMotionPointerUp(0);
+ wallpaperWindow->consumeMotionPointerUp(0, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ 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);
}
/**
@@ -1808,28 +1981,29 @@ TEST_F(InputDispatcherTest, WallpaperWindow_ReceivesMultiTouch) {
TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> leftWindow =
- new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
leftWindow->setFrame(Rect(0, 0, 200, 200));
leftWindow->setDupTouchToWallpaper(true);
sp<FakeWindowHandle> rightWindow =
- new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
rightWindow->setFrame(Rect(200, 0, 400, 200));
rightWindow->setDupTouchToWallpaper(true);
sp<FakeWindowHandle> wallpaperWindow =
- new FakeWindowHandle(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setFrame(Rect(0, 0, 400, 200));
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- mDispatcher->setInputWindows(
- {{ADISPLAY_ID_DEFAULT, {leftWindow, rightWindow, wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo(), *wallpaperWindow->getInfo()},
+ {},
+ 0,
+ 0});
// Touch down on left window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{100, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -1841,26 +2015,23 @@ 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,
+ injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
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.
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {rightWindow, wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*rightWindow->getInfo(), *wallpaperWindow->getInfo()}, {}, 0, 0});
leftWindow->consumeMotionCancel();
// Since a "parent" window of the wallpaper is gone, wallpaper should receive cancel, too.
wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
@@ -1870,15 +2041,11 @@ 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,
+ injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT));
rightWindow->consumeMotionMove();
@@ -1887,62 +2054,1502 @@ TEST_F(InputDispatcherTest, TwoWindows_SplitWallpaperTouch) {
wallpaperWindow->assertNoEvents();
}
-TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
+/**
+ * Two windows: a window on the left with dup touch to wallpaper and window on the right without it.
+ * The touch slips to the right window. so left window and wallpaper should receive ACTION_CANCEL
+ * The right window should receive ACTION_DOWN.
+ */
+TEST_F(InputDispatcherTest, WallpaperWindowWhenSlippery) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
- window->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+ leftWindow->setDupTouchToWallpaper(true);
+ leftWindow->setSlippery(true);
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
sp<FakeWindowHandle> wallpaperWindow =
sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
wallpaperWindow->setIsWallpaper(true);
- constexpr int expectedWallpaperFlags =
- AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED | AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED;
- wallpaperWindow->setPreventSplitting(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, wallpaperWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo(), *wallpaperWindow->getInfo()},
+ {},
+ 0,
+ 0});
+ // Touch down on left window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {50, 50}))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {100, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+ // Both foreground window and its wallpaper should receive the touch down
+ leftWindow->consumeMotionDown();
wallpaperWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
- 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(10).y(10))
- .build();
+ // Move to right window, the left window should receive cancel.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {201, 100}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerDown(1);
- wallpaperWindow->consumeMotionPointerDown(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ leftWindow->consumeMotionCancel();
+ rightWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+ wallpaperWindow->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
- const MotionEvent secondFingerUpEvent =
- MotionEventBuilder(POINTER_1_UP, 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(10).y(10))
- .build();
+/**
+ * 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, TwoPointerCancelInconsistentPolicy) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+ 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();
+}
+
+/**
+ * Same as the above 'TwoPointerCancelInconsistentPolicy' test, but for hovers.
+ * 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.
+ * We've already ensured the consistency of the touch event in this case, and we should also ensure
+ * the consistency of the hover event in this case.
+ *
+ * Test procedure:
+ * HOVER_ENTER -> HOVER_MOVE -> (stop sending POLICY_FLAG_PASS_TO_USER) -> HOVER_EXIT
+ * HOVER_ENTER -> HOVER_MOVE -> HOVER_EXIT
+ *
+ * We expect to receive two full streams of hover events.
+ */
+TEST_F(InputDispatcherTest, HoverEventInconsistentPolicy) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 300, 300));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+ // Send hover exit without the default policy flags.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .policyFlags(0)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+ // Send a simple hover event stream, ensure dispatcher not crashed and window can receive
+ // right event.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(200).y(201))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(201).y(202))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .policyFlags(DEFAULT_POLICY_FLAGS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(201).y(202))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+}
+
+/**
+ * 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, HoverFromLeftToRightAndTap) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+ // 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, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
- InputEventInjectionSync::WAIT_FOR_RESULT))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionPointerUp(1);
- wallpaperWindow->consumeMotionPointerUp(1, ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_MOUSE)
+ .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,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- wallpaperWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ // Only a single window is present at first
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // 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<FakeWindowHandle> obscuringWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
+ ADISPLAY_ID_DEFAULT,
+ /*token=*/std::make_optional<sp<IBinder>>(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->onWindowInfosChanged(
+ {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ // Only a single window is present at first
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // 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<FakeWindowHandle> obscuringWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Obscuring window",
+ ADISPLAY_ID_DEFAULT,
+ /*token=*/std::make_optional<sp<IBinder>>(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->onWindowInfosChanged(
+ {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // 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));
+}
+
+/**
+ * Hover mouse over a window, and then send ACTION_SCROLL. Ensure both the hover and the scroll
+ * events are delivered to the window.
+ */
+TEST_F(InputDispatcherTest, HoverMoveAndScroll) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // Start hovering in the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+ // Scroll with the mouse
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_SCROLL, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_SCROLL));
+}
+
+using InputDispatcherMultiDeviceTest = InputDispatcherTest;
+
+/**
+ * One window. Stylus down on the window. Next, touch from another device goes down. Ensure that
+ * touch is dropped, because stylus should be preferred over touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownBlocksTouchDown) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Stylus down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Touch down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+
+ // Touch move
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ // Touch is ignored because stylus is already down
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+
+ window->assertNoEvents();
+}
+
+/**
+ * One window and one spy window. Stylus down on the window. Next, touch from another device goes
+ * down. Ensure that touch is dropped, because stylus should be preferred over touch.
+ * Similar test as above, but with added SPY window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusDownWithSpyBlocksTouchDown) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Stylus down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Touch down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+
+ // Touch move
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+
+ // Touch is ignored because stylus is already down
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+ spyWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+
+ window->assertNoEvents();
+ spyWindow->assertNoEvents();
+}
+
+/**
+ * One window. Stylus hover on the window. Next, touch from another device goes down. Ensure that
+ * touch is dropped, because stylus hover takes precedence.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, StylusHoverBlocksTouchDown) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Stylus down on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+
+ // Touch is ignored because stylus is hovering
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+ WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+ // and subsequent touches continue to be ignored
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+ .build());
+ window->assertNoEvents();
+}
+
+/**
+ * One window. Touch down on the window. Then, stylus hover on the window from another device.
+ * Ensure that touch is canceled, because stylus hover should take precedence.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusHover) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Stylus hover on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ // Stylus hover movement causes touch to be canceled
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId),
+ WithCoords(141, 146)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ WithDeviceId(stylusDeviceId), WithCoords(100, 110)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE),
+ WithDeviceId(stylusDeviceId), WithCoords(101, 111)));
+
+ // Subsequent touch movements are ignored
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(142).y(147))
+ .build());
+
+ window->assertNoEvents();
+}
+
+/**
+ * One window. Stylus down on the window. Then, stylus from another device goes down. Ensure that
+ * the latest stylus takes over. That is, old stylus should be canceled and the new stylus should
+ * become active.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, LatestStylusWins) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t stylusDeviceId1 = 3;
+ constexpr int32_t stylusDeviceId2 = 5;
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId1)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(99).y(100))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId1)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(101))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId1)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId1)));
+
+ // Second stylus down
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId2)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(9).y(10))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId2)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(10).y(11))
+ .build());
+
+ // First stylus is canceled, second one takes over.
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId1)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId2)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId2)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId1)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(102))
+ .build());
+ // Subsequent stylus movements are delivered correctly
+ window->assertNoEvents();
+}
+
+/**
+ * One window. Touch down on the window. Then, stylus down on the window from another device.
+ * Ensure that is canceled, because stylus down should be preferred over touch.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, TouchIsCanceledByStylusDown) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ constexpr int32_t touchDeviceId = 4;
+ constexpr int32_t stylusDeviceId = 2;
+
+ // Touch down on window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(140).y(145))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(141).y(146))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ // Stylus down on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Subsequent stylus movements are delivered correctly
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(101).y(111))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId),
+ WithCoords(101, 111)));
+}
+
+/**
+ * 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(InputDispatcherMultiDeviceTest, MultiDeviceSplitTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ const int32_t mouseDeviceId = 6;
+
+ // 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->assertNoEvents();
+
+ 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());
+ // Since this is now a new splittable pointer going down on the left window, and it's coming
+ // from a different device, the current gesture in the left window (pointer down) should first
+ // be canceled.
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(mouseDeviceId)));
+ 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<int32_t, PointF> expectedPointers{{0, PointF{100, 100}}};
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithPointers(expectedPointers)));
+
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
+ * Two windows: a window on the left and a window on the right.
+ * Mouse is hovered on the left window and stylus is hovered on the right window.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHover) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 3;
+ const int32_t mouseDeviceId = 6;
+
+ // 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(110))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(mouseDeviceId)));
+
+ // Stylus hovered on right window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(300).y(100))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+ // Subsequent HOVER_MOVE events are dispatched correctly.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(110).y(120))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(mouseDeviceId)));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(310).y(110))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
+ * Three windows: a window on the left and a window on the right.
+ * And a spy window that's positioned above all of them.
+ * Stylus down on the left window and remains down. Touch goes down on the right and remains down.
+ * Check the stream that's received by the spy.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceWithSpy) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 400, 400));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 1;
+ const int32_t touchDeviceId = 2;
+
+ // Stylus down on the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Touch down on the right window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ leftWindow->assertNoEvents();
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Spy window does not receive touch events, because stylus events take precedence, and it
+ // already has an active stylus gesture.
+
+ // Stylus movements continue. They should be delivered to the left window and to the spy window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
+
+ // Further MOVE events keep going to the right window only
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(310).y(110))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ spyWindow->assertNoEvents();
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+/**
+ * Three windows: a window on the left, a window on the right, and a spy window positioned above
+ * both.
+ * Check hover in left window and touch down in the right window.
+ * At first, spy should receive hover. Spy shouldn't receive touch while stylus is hovering.
+ * At the same time, left and right should be getting independent streams of hovering and touch,
+ * respectively.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, MultiDeviceHoverBlocksTouchWithSpy) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 400, 400));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindow->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 1;
+ const int32_t touchDeviceId = 2;
+
+ // Stylus hover on the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(100).y(100))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), WithDeviceId(stylusDeviceId)));
+
+ // Touch down on the right window. Spy doesn't receive this touch because it already has
+ // stylus hovering there.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(300).y(100))
+ .build());
+ leftWindow->assertNoEvents();
+ spyWindow->assertNoEvents();
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+
+ // Stylus movements continue. They should be delivered to the left window and the spy.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(110).y(110))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+ spyWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+ // Touch movements continue. They should be delivered to the right window only
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(301).y(101))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(touchDeviceId)));
+
+ spyWindow->assertNoEvents();
+ 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(InputDispatcherMultiDeviceTest, MixedTouchAndMouseWithPointerDown) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ const int32_t mouseDeviceId = 6;
+
+ // 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());
+ // Since we already canceled this touch gesture, it will be ignored until a completely new
+ // gesture is started. This is easier to implement than trying to keep track of the new pointer
+ // and generating an ACTION_DOWN instead of ACTION_POINTER_DOWN.
+ // However, mouse movements should continue to work.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .deviceId(mouseDeviceId)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(330).y(110))
+ .build());
+ window->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(mouseDeviceId)));
+
+ window->assertNoEvents();
+}
+
+/**
+ * Inject a touch down and then send a new event via 'notifyMotion'. Ensure the new event cancels
+ * the injected event.
+ */
+TEST_F(InputDispatcherMultiDeviceTest, UnfinishedInjectedEvent) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t touchDeviceId = 4;
+ // 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(InputDispatcherMultiDeviceTest, HoverTapAndSplitTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 200, 200));
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(200, 0, 400, 200));
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ 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(InputDispatcherMultiDeviceTest, StylusHoverIgnoresTouchTap) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const int32_t stylusDeviceId = 5;
+ const int32_t touchDeviceId = 4;
+ // Start hovering with stylus
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+ .build()));
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ // Finger down on the window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build()));
+ // The touch device should be ignored!
+
+ // Continue hovering with stylus.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(60).y(60))
+ .build()));
+ // Hovers continue to work
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+
+ // 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()));
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(70).y(70))
+ .build()));
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE), WithDeviceId(stylusDeviceId)));
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setNoInputChannel(true);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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();
+}
+
+/**
+ * A stale stylus HOVER_EXIT event is injected. Since it's a stale event, it should generally be
+ * rejected. But since we already have an ongoing gesture, this event should be processed.
+ * This prevents inconsistent events being handled inside the dispatcher.
+ */
+TEST_F(InputDispatcherTest, StaleStylusHoverGestureIsComplete) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // Start hovering with stylus
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ NotifyMotionArgs hoverExit = MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+ .build();
+ // Make this 'hoverExit' event stale
+ mFakePolicy->setStaleEventTimeout(100ms);
+ std::this_thread::sleep_for(100ms);
+
+ // It shouldn't be dropped by the dispatcher, even though it's stale.
+ mDispatcher->notifyMotion(hoverExit);
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+ // Stylus starts hovering again! There should be no crash.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(51))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+}
+
+/**
+ * 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 200, 200));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ 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();
}
/**
@@ -1953,19 +3560,18 @@ TEST_F(InputDispatcherTest, WallpaperWindowReceivesMultiTouch) {
TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Window", DISPLAY_ID);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", DISPLAY_ID);
- mDispatcher->setInputWindows({{DISPLAY_ID, {window}}});
- NotifyMotionArgs args;
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
// Touch down on the empty space
- mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}})));
+ mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{-1, -1}}));
mDispatcher->waitForIdle();
window->assertNoEvents();
// Now touch down on the window with another pointer
- mDispatcher->notifyMotion(&(args = generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}})));
+ mDispatcher->notifyMotion(generateTouchArgs(POINTER_1_DOWN, {{-1, -1}, {10, 10}}));
mDispatcher->waitForIdle();
window->consumeMotionDown();
}
@@ -1977,134 +3583,369 @@ TEST_F(InputDispatcherTest, SplitWorksWhenEmptyAreaIsTouched) {
TEST_F(InputDispatcherTest, SplitWorksWhenNonTouchableWindowIsTouched) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window1 =
- new FakeWindowHandle(application, mDispatcher, "Window1", DISPLAY_ID);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
window1->setTouchableRegion(Region{{0, 0, 100, 100}});
window1->setTouchable(false);
sp<FakeWindowHandle> window2 =
- new FakeWindowHandle(application, mDispatcher, "Window2", DISPLAY_ID);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
window2->setTouchableRegion(Region{{100, 0, 200, 100}});
- mDispatcher->setInputWindows({{DISPLAY_ID, {window1, window2}}});
+ mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
- NotifyMotionArgs args;
// Touch down on the non-touchable window
- mDispatcher->notifyMotion(&(args = generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}})));
+ 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(&(args = generateTouchArgs(POINTER_1_DOWN, {{50, 50}, {150, 50}})));
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window1 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window1", DISPLAY_ID);
+ window1->setTouchableRegion(Region{{0, 0, 100, 100}});
+ sp<FakeWindowHandle> window2 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window2", DISPLAY_ID);
+ window2->setTouchableRegion(Region{{100, 0, 200, 100}});
+
+ mDispatcher->onWindowInfosChanged({{*window1->getInfo(), *window2->getInfo()}, {}, 0, 0});
+
+ // Touch down on the first window
+ mDispatcher->notifyMotion(generateTouchArgs(AMOTION_EVENT_ACTION_DOWN, {{50, 50}}));
+
+ mDispatcher->waitForIdle();
+
+ MotionEvent* motionEvent1 = window1->consumeMotion();
+ ASSERT_NE(motionEvent1, nullptr);
+ window2->assertNoEvents();
+ 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();
+ MotionEvent* motionEvent2 = window2->consumeMotion();
+ ASSERT_NE(motionEvent2, nullptr);
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowLeft =
- new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
windowLeft->setFrame(Rect(0, 0, 600, 800));
sp<FakeWindowHandle> windowRight =
- new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::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}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
// Start cursor position in right window so that we can move the cursor to left window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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->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,
+ 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,
+ 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,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
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,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
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,
+ 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);
// Move mouse cursor back to right window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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()));
- 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 */);
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 600, 800));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindow->setFrame(Rect(0, 0, 600, 800));
+ spyWindow->setTrustedOverlay(true);
+ spyWindow->setSpy(true);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 600, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+ mDispatcher->onWindowInfosChanged({{*spyWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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()));
+ 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
@@ -2112,81 +3953,193 @@ TEST_F(InputDispatcherTest, HoverMoveEnterMouseClickAndHoverMoveExit) {
TEST_F(InputDispatcherTest, HoverEnterMouseClickAndHoverExit) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::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}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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,
+ 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,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_PRESS,
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,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_BUTTON_RELEASE,
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,
+ 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,
- injectMotionEvent(mDispatcher,
+ // 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)
- .x(300)
- .y(400))
+ .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->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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 1200, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ 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->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER));
+
+ // Remove the window, but keep the channel.
+ mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
+ window->consumeMotionEvent(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT));
+}
+
+/**
+ * Test that invalid HOVER events sent by accessibility do not cause a fatal crash.
+ */
+TEST_F_WITH_FLAGS(InputDispatcherTest, InvalidA11yHoverStreamDoesNotCrash,
+ REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(com::android::input::flags,
+ a11y_crash_on_inconsistent_event_stream))) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 1200, 800));
+
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(50).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithSource(AINPUT_SOURCE_MOUSE))));
+
+ // Tap on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+ .build());
+ 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(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(
+ window->consumeMotionEvent(AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN))));
}
TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) {
@@ -2200,45 +4153,40 @@ TEST_F(InputDispatcherTest, HoverEnterMoveRemoveWindowsInSecondDisplay) {
SECOND_DISPLAY_ID);
windowSecondDisplay->setFrame(Rect(0, 0, 600, 800));
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}},
- {SECOND_DISPLAY_ID, {windowSecondDisplay}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowDefaultDisplay->getInfo(), *windowSecondDisplay->getInfo()}, {}, 0, 0});
// Set cursor position in window in default display and check that hover enter and move
// events are generated.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
AINPUT_SOURCE_MOUSE)
.displayId(ADISPLAY_ID_DEFAULT)
- .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
- .x(300)
- .y(600))
+ .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->onWindowInfosChanged({{*windowDefaultDisplay->getInfo()}, {}, 0, 0});
+
windowDefaultDisplay->assertNoEvents();
// Move cursor position in window in default display and check that only hover move
// event is generated and not hover enter event.
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowDefaultDisplay}},
- {SECOND_DISPLAY_ID, {windowSecondDisplay}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowDefaultDisplay->getInfo(), *windowSecondDisplay->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE,
AINPUT_SOURCE_MOUSE)
.displayId(ADISPLAY_ID_DEFAULT)
- .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_MOUSE)
- .x(400)
- .y(700))
+ .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();
}
@@ -2246,20 +4194,21 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowLeft =
- new FakeWindowHandle(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
windowLeft->setFrame(Rect(0, 0, 600, 800));
sp<FakeWindowHandle> windowRight =
- new FakeWindowHandle(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::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}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
// Inject an event with coordinate in the area of right window, with mouse cursor in the area of
// left window. This event should be dispatched to the left window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE,
ADISPLAY_ID_DEFAULT, {610, 400}, {599, 400}));
windowLeft->consumeMotionDown(ADISPLAY_ID_DEFAULT);
windowRight->assertNoEvents();
@@ -2267,97 +4216,157 @@ TEST_F(InputDispatcherTest, DispatchMouseEventsUnderCursor) {
TEST_F(InputDispatcherTest, NotifyDeviceReset_CancelsKeyStream) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
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);
// 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
- 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
setFocusedWindow(window);
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) >=
std::chrono::nanoseconds(interceptKeyTimeout).count());
}
+/**
+ * Keys with ACTION_UP are delivered immediately, even if a long 'intercept key timeout' is set.
+ */
TEST_F(InputDispatcherTest, InterceptKeyIfKeyUp) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ 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);
- mFakePolicy->setInterceptKeyTimeout(150ms);
- mDispatcher->notifyKey(&keyDown);
- mDispatcher->notifyKey(&keyUp);
+ mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_DOWN, ADISPLAY_ID_DEFAULT));
+ window->consumeKeyDown(ADISPLAY_ID_DEFAULT);
+ // Set a value that's significantly larger than the default consumption timeout. If the
+ // implementation is correct, the actual value doesn't matter; it won't slow down the test.
+ mFakePolicy->setInterceptKeyTimeout(600ms);
+ mDispatcher->notifyKey(generateKeyArgs(AKEY_EVENT_ACTION_UP, ADISPLAY_ID_DEFAULT));
// 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect{0, 0, 100, 100});
+
+ sp<FakeWindowHandle> outsideWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Outside 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->onWindowInfosChanged({{*outsideWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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)));
+
+ // Ensure outsideWindow doesn't get any more events for the gesture.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {PointF{51, 51}}));
+ window->consumeMotionMove();
+ outsideWindow->assertNoEvents();
+}
+
+/**
* 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
* ACTION_OUTSIDE event is sent per gesture.
@@ -2365,40 +4374,44 @@ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "First Window", ADISPLAY_ID_DEFAULT);
window->setWatchOutsideTouch(true);
window->setFrame(Rect{0, 0, 100, 100});
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
secondWindow->setFrame(Rect{100, 100, 200, 200});
sp<FakeWindowHandle> thirdWindow =
- new FakeWindowHandle(application, mDispatcher, "Third Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Third Window",
+ ADISPLAY_ID_DEFAULT);
thirdWindow->setFrame(Rect{200, 200, 300, 300});
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, secondWindow, thirdWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*window->getInfo(), *secondWindow->getInfo(), *thirdWindow->getInfo()}, {}, 0, 0});
// 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<int32_t, PointF> 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();
@@ -2406,30 +4419,30 @@ TEST_F(InputDispatcherTest, ActionOutsideSentOnlyWhenAWindowIsTouched) {
TEST_F(InputDispatcherTest, OnWindowInfosChanged_RemoveAllWindowsOnDisplay) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::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();
}
@@ -2440,10 +4453,10 @@ 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,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -2452,13 +4465,11 @@ 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,
+ injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -2471,6 +4482,280 @@ TEST_F(InputDispatcherTest, NonSplitTouchableWindowReceivesMultiTouch) {
}
/**
+ * Two windows: a splittable and a non-splittable.
+ * The non-splittable window shouldn't receive any "incomplete" gestures.
+ * Send the first pointer to the splittable window, and then touch the non-splittable window.
+ * The second pointer should be dropped because the initial window is splittable, so it won't get
+ * any pointers outside of it, and the second window is non-splittable, so it shouldn't get any
+ * "incomplete" gestures.
+ */
+TEST_F(InputDispatcherTest, SplittableAndNonSplittableWindows) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left splittable Window",
+ ADISPLAY_ID_DEFAULT);
+ leftWindow->setPreventSplitting(false);
+ leftWindow->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right non-splittable Window",
+ ADISPLAY_ID_DEFAULT);
+ rightWindow->setPreventSplitting(true);
+ rightWindow->setFrame(Rect(100, 100, 200, 200));
+ mDispatcher->onWindowInfosChanged(
+ {{*leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ // Touch down on left, splittable window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+ leftWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(150))
+ .build());
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, TouchpadThreeFingerSwipeOnlySentToTrustedOverlays) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+ sp<FakeWindowHandle> trustedOverlay =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Trusted Overlay",
+ ADISPLAY_ID_DEFAULT);
+ trustedOverlay->setSpy(true);
+ trustedOverlay->setTrustedOverlay(true);
+
+ mDispatcher->onWindowInfosChanged({{*trustedOverlay->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ // 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();
+}
+
+/**
+ * Send a two-pointer gesture to a single window. The window's orientation changes in response to
+ * the first pointer.
+ * Ensure that the second pointer and the subsequent gesture is correctly delivered to the window.
+ */
+TEST_F(InputDispatcherTest, MultiplePointersWithRotatingWindow) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 400, 400));
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ const nsecs_t baseTime = systemTime(SYSTEM_TIME_MONOTONIC);
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 10)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+ // Change the transform so that the orientation is now different from original.
+ window->setWindowTransform(0, -1, 1, 0);
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 30)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+ // Finish the gesture and start a new one. Ensure all events are sent to the window.
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 40)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(200).y(200))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 10)
+ .eventTime(baseTime + 50)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(100))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(ACTION_UP));
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .downTime(baseTime + 60)
+ .eventTime(baseTime + 60)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(40).y(40))
+ .build());
+
+ window->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
+/**
+ * When there are multiple screens, such as screen projection to TV or screen recording, if the
+ * cancel event occurs, the coordinates of the cancel event should be sent to the target screen, and
+ * its coordinates should be converted by the transform of the windows of target screen.
+ */
+TEST_F(InputDispatcherTest, WhenMultiDisplayWindowSameToken_DispatchCancelToTargetDisplay) {
+ // This case will create a window and a spy window on the default display and mirror
+ // window on the second display. cancel event is sent through spy window pilferPointers
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> spyWindowDefaultDisplay =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ spyWindowDefaultDisplay->setTrustedOverlay(true);
+ spyWindowDefaultDisplay->setSpy(true);
+
+ sp<FakeWindowHandle> windowDefaultDisplay =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "DefaultDisplay",
+ ADISPLAY_ID_DEFAULT);
+ windowDefaultDisplay->setWindowTransform(1, 0, 0, 1);
+
+ sp<FakeWindowHandle> windowSecondDisplay = windowDefaultDisplay->clone(SECOND_DISPLAY_ID);
+ windowSecondDisplay->setWindowTransform(2, 0, 0, 2);
+
+ // Add the windows to the dispatcher
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindowDefaultDisplay->getInfo(), *windowDefaultDisplay->getInfo(),
+ *windowSecondDisplay->getInfo()},
+ {},
+ 0,
+ 0});
+
+ // Send down to ADISPLAY_ID_DEFAULT
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {100, 100}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ spyWindowDefaultDisplay->consumeMotionDown();
+ windowDefaultDisplay->consumeMotionDown();
+
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spyWindowDefaultDisplay->getToken()));
+
+ // windowDefaultDisplay gets cancel
+ MotionEvent* event = windowDefaultDisplay->consumeMotion();
+ EXPECT_EQ(AMOTION_EVENT_ACTION_CANCEL, event->getAction());
+
+ // The cancel event is sent to windowDefaultDisplay of the ADISPLAY_ID_DEFAULT display, so the
+ // coordinates of the cancel are converted by windowDefaultDisplay's transform, the x and y
+ // coordinates are both 100, otherwise if the cancel event is sent to windowSecondDisplay of
+ // SECOND_DISPLAY_ID, the x and y coordinates are 200
+ EXPECT_EQ(100, event->getX(0));
+ EXPECT_EQ(100, event->getY(0));
+}
+
+/**
* Ensure the correct coordinate spaces are used by InputDispatcher.
*
* InputDispatcher works in the display space, so its coordinate system is relative to the display
@@ -2481,8 +4766,7 @@ class InputDispatcherDisplayProjectionTest : public InputDispatcherTest {
public:
void SetUp() override {
InputDispatcherTest::SetUp();
- mDisplayInfos.clear();
- mWindowInfos.clear();
+ removeAllWindowsAndDisplays();
}
void addDisplayInfo(int displayId, const ui::Transform& transform) {
@@ -2490,12 +4774,17 @@ 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<WindowInfoHandle>& windowHandle) {
mWindowInfos.push_back(*windowHandle->getInfo());
- mDispatcher->onWindowInfosChanged(mWindowInfos, mDisplayInfos);
+ mDispatcher->onWindowInfosChanged({mWindowInfos, mDisplayInfos, 0, 0});
+ }
+
+ void removeAllWindowsAndDisplays() {
+ mDisplayInfos.clear();
+ mWindowInfos.clear();
}
// Set up a test scenario where the display has a scaled projection and there are two windows
@@ -2512,13 +4801,14 @@ public:
// Add two windows to the display. Their frames are represented in the display space.
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
firstWindow->setFrame(Rect(0, 0, 100, 200), displayTransform);
addWindow(firstWindow);
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::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)};
@@ -2529,15 +4819,14 @@ private:
std::vector<gui::WindowInfo> mWindowInfos;
};
-TEST_F(InputDispatcherDisplayProjectionTest, HitTestsInDisplaySpace) {
+TEST_F(InputDispatcherDisplayProjectionTest, HitTestCoordinateSpaceConsistency) {
auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
// 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 done with the transform applied to it, then it 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);
+ // selected so that if the hit test was performed with the point and the bounds being in
+ // different coordinate spaces, the event would end up in the incorrect window.
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {PointF{75, 55}}));
firstWindow->consumeMotionDown();
secondWindow->assertNoEvents();
@@ -2550,7 +4839,7 @@ TEST_F(InputDispatcherDisplayProjectionTest, InjectionInLogicalDisplaySpace) {
// Send down to the first window. The point is represented in the logical display space. The
// point is selected so that if the hit test was done in logical display space, then it would
// end up in the incorrect window.
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
PointF{75 * 2, 55 * 4});
firstWindow->consumeMotionDown();
@@ -2571,13 +4860,13 @@ 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();
event.transform(matrix);
- injectMotionEvent(mDispatcher, event, INJECT_EVENT_TIMEOUT,
+ injectMotionEvent(*mDispatcher, event, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT);
firstWindow->consumeMotionDown();
@@ -2588,13 +4877,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.
@@ -2608,6 +4897,226 @@ TEST_F(InputDispatcherDisplayProjectionTest, WindowGetsEventsInCorrectCoordinate
EXPECT_EQ(80, event->getY(0));
}
+TEST_F(InputDispatcherDisplayProjectionTest, CancelMotionWithCorrectCoordinates) {
+ auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+ // The monitor will always receive events in the logical display's coordinate space, because
+ // it does not have a window.
+ FakeMonitorReceiver monitor{*mDispatcher, "Monitor", ADISPLAY_ID_DEFAULT};
+
+ // Send down to the first window.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+ firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
+ monitor.consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
+
+ // Second pointer goes down on second window.
+ mDispatcher->notifyMotion(generateMotionArgs(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT,
+ {PointF{50, 100}, PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80)));
+ const std::map<int32_t, PointF> expectedMonitorPointers{{0, PointF{100, 400}},
+ {1, PointF{300, 880}}};
+ monitor.consumeMotionEvent(
+ AllOf(WithMotionAction(POINTER_1_DOWN), WithPointers(expectedMonitorPointers)));
+
+ mDispatcher->cancelCurrentTouch();
+
+ firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 400)));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 80)));
+ monitor.consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithPointers(expectedMonitorPointers)));
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeDownWithCorrectCoordinates) {
+ auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+ // Send down to the first window.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {PointF{50, 100}}));
+ firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 400)));
+
+ // The pointer is transferred to the second window, and the second window receives it in the
+ // correct coordinate space.
+ mDispatcher->transferTouchFocus(firstWindow->getToken(), secondWindow->getToken());
+ firstWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_CANCEL), WithCoords(100, 400)));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithCoords(-100, -400)));
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverEnterExitWithCorrectCoordinates) {
+ auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+ // Send hover move to the second window, and ensure it shows up as hover enter.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ WithCoords(100, 80), WithRawCoords(300, 880)));
+
+ // Touch down at the same location and ensure a hover exit is synthesized.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+ WithRawCoords(300, 880)));
+ secondWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80), WithRawCoords(300, 880)));
+ secondWindow->assertNoEvents();
+ firstWindow->assertNoEvents();
+}
+
+// Same as above, but while the window is being mirrored.
+TEST_F(InputDispatcherDisplayProjectionTest,
+ SynthesizeHoverEnterExitWithCorrectCoordinatesWhenMirrored) {
+ auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+ const std::array<float, 9> matrix = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0.0, 0.0, 1.0};
+ ui::Transform secondDisplayTransform;
+ secondDisplayTransform.set(matrix);
+ addDisplayInfo(SECOND_DISPLAY_ID, secondDisplayTransform);
+
+ sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+ secondWindowClone->setWindowTransform(1.1, 2.2, 3.3, 4.4);
+ addWindow(secondWindowClone);
+
+ // Send hover move to the second window, and ensure it shows up as hover enter.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ WithCoords(100, 80), WithRawCoords(300, 880)));
+
+ // Touch down at the same location and ensure a hover exit is synthesized for the correct
+ // display.
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_DOWN, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+ WithRawCoords(300, 880)));
+ secondWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithCoords(100, 80), WithRawCoords(300, 880)));
+ secondWindow->assertNoEvents();
+ firstWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherDisplayProjectionTest, SynthesizeHoverCancelationWithCorrectCoordinates) {
+ auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+ // Send hover enter to second window
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ WithCoords(100, 80), WithRawCoords(300, 880)));
+
+ mDispatcher->cancelCurrentTouch();
+
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+ WithRawCoords(300, 880)));
+ secondWindow->assertNoEvents();
+ firstWindow->assertNoEvents();
+}
+
+// Same as above, but while the window is being mirrored.
+TEST_F(InputDispatcherDisplayProjectionTest,
+ SynthesizeHoverCancelationWithCorrectCoordinatesWhenMirrored) {
+ auto [firstWindow, secondWindow] = setupScaledDisplayScenario();
+
+ const std::array<float, 9> matrix = {1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 0.0, 0.0, 1.0};
+ ui::Transform secondDisplayTransform;
+ secondDisplayTransform.set(matrix);
+ addDisplayInfo(SECOND_DISPLAY_ID, secondDisplayTransform);
+
+ sp<FakeWindowHandle> secondWindowClone = secondWindow->clone(SECOND_DISPLAY_ID);
+ secondWindowClone->setWindowTransform(1.1, 2.2, 3.3, 4.4);
+ addWindow(secondWindowClone);
+
+ // Send hover enter to second window
+ mDispatcher->notifyMotion(generateMotionArgs(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{150, 220}}));
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ WithCoords(100, 80), WithRawCoords(300, 880),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ mDispatcher->cancelCurrentTouch();
+
+ // Ensure the cancelation happens with the correct displayId and the correct coordinates.
+ secondWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_EXIT), WithCoords(100, 80),
+ WithRawCoords(300, 880),
+ WithDisplayId(ADISPLAY_ID_DEFAULT)));
+ secondWindow->assertNoEvents();
+ firstWindow->assertNoEvents();
+}
+
+/** Ensure consistent behavior of InputDispatcher in all orientations. */
+class InputDispatcherDisplayOrientationFixture
+ : public InputDispatcherDisplayProjectionTest,
+ public ::testing::WithParamInterface<ui::Rotation> {};
+
+// This test verifies the touchable region of a window for all rotations of the display by tapping
+// in different locations on the display, specifically points close to the four corners of a
+// window.
+TEST_P(InputDispatcherDisplayOrientationFixture, HitTestInDifferentOrientations) {
+ constexpr static int32_t displayWidth = 400;
+ constexpr static int32_t displayHeight = 800;
+
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ const auto rotation = GetParam();
+
+ // Set up the display with the specified rotation.
+ const bool isRotated = rotation == ui::ROTATION_90 || rotation == ui::ROTATION_270;
+ const int32_t logicalDisplayWidth = isRotated ? displayHeight : displayWidth;
+ const int32_t logicalDisplayHeight = isRotated ? displayWidth : displayHeight;
+ const ui::Transform displayTransform(ui::Transform::toRotationFlags(rotation),
+ logicalDisplayWidth, logicalDisplayHeight);
+ addDisplayInfo(ADISPLAY_ID_DEFAULT, displayTransform);
+
+ // Create a window with its bounds determined in the logical display.
+ const Rect frameInLogicalDisplay(100, 100, 200, 300);
+ const Rect frameInDisplay = displayTransform.inverse().transform(frameInLogicalDisplay);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(frameInDisplay, displayTransform);
+ addWindow(window);
+
+ // The following points in logical display space should be inside the window.
+ static const std::array<vec2, 4> insidePoints{
+ {{100, 100}, {199.99, 100}, {100, 299.99}, {199.99, 299.99}}};
+ for (const auto pointInsideWindow : insidePoints) {
+ const vec2 p = displayTransform.inverse().transform(pointInsideWindow);
+ const PointF pointInDisplaySpace{p.x, p.y};
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ window->consumeMotionDown();
+
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ window->consumeMotionUp();
+ }
+
+ // The following points in logical display space should be outside the window.
+ static const std::array<vec2, 5> outsidePoints{
+ {{200, 100}, {100, 300}, {200, 300}, {100, 99.99}, {99.99, 100}}};
+ for (const auto pointOutsideWindow : outsidePoints) {
+ const vec2 p = displayTransform.inverse().transform(pointOutsideWindow);
+ const PointF pointInDisplaySpace{p.x, p.y};
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {pointInDisplaySpace}));
+ }
+ window->assertNoEvents();
+}
+
+// Run the precision tests for all rotations.
+INSTANTIATE_TEST_SUITE_P(InputDispatcherDisplayOrientationTests,
+ InputDispatcherDisplayOrientationFixture,
+ ::testing::Values(ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180,
+ ui::ROTATION_270),
+ [](const testing::TestParamInfo<ui::Rotation>& testParamInfo) {
+ return ftl::enum_string(testParamInfo.param);
+ });
+
using TransferFunction = std::function<bool(const std::unique_ptr<InputDispatcher>& dispatcher,
sp<IBinder>, sp<IBinder>)>;
@@ -2619,21 +5128,27 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) {
// Create a couple of windows
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
+ firstWindow->setDupTouchToWallpaper(true);
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
-
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> wallpaper =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper", ADISPLAY_ID_DEFAULT);
+ wallpaper->setIsWallpaper(true);
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindow->getInfo(), *secondWindow->getInfo(), *wallpaper->getInfo()}, {}, 0, 0});
// 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();
secondWindow->assertNoEvents();
+ wallpaper->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
// Transfer touch to the second window
TransferFunction f = GetParam();
@@ -2642,15 +5157,15 @@ TEST_P(TransferTouchFixture, TransferTouch_OnePointer) {
// The first window gets cancel and the second gets down
firstWindow->consumeMotionCancel();
secondWindow->consumeMotionDown();
+ 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();
+ wallpaper->assertNoEvents();
}
/**
@@ -2670,22 +5185,21 @@ TEST_P(TransferTouchFixture, TransferTouch_MultipleWindowsWithSpy) {
// Create a couple of windows + a spy window
sp<FakeWindowHandle> spyWindow =
- new FakeWindowHandle(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
spyWindow->setTrustedOverlay(true);
spyWindow->setSpy(true);
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First", ADISPLAY_ID_DEFAULT);
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spyWindow, firstWindow, secondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*spyWindow->getInfo(), *firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
// 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();
@@ -2700,10 +5214,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();
@@ -2717,29 +5229,29 @@ TEST_P(TransferTouchFixture, TransferTouch_TwoPointersNonSplitTouch) {
// Create a couple of windows
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
firstWindow->setPreventSplitting(true);
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
secondWindow->setPreventSplitting(true);
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
// 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();
@@ -2754,24 +5266,78 @@ 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();
}
+TEST_P(TransferTouchFixture, TransferTouch_MultipleWallpapers) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ // Create a couple of windows
+ sp<FakeWindowHandle> firstWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
+ firstWindow->setDupTouchToWallpaper(true);
+ sp<FakeWindowHandle> secondWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
+ secondWindow->setDupTouchToWallpaper(true);
+
+ sp<FakeWindowHandle> wallpaper1 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper1", ADISPLAY_ID_DEFAULT);
+ wallpaper1->setIsWallpaper(true);
+
+ sp<FakeWindowHandle> wallpaper2 =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Wallpaper2", ADISPLAY_ID_DEFAULT);
+ wallpaper2->setIsWallpaper(true);
+ // Add the windows to the dispatcher
+ mDispatcher->onWindowInfosChanged({{*firstWindow->getInfo(), *wallpaper1->getInfo(),
+ *secondWindow->getInfo(), *wallpaper2->getInfo()},
+ {},
+ 0,
+ 0});
+
+ // Send down to the first window
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT));
+
+ // Only the first window should get the down event
+ firstWindow->consumeMotionDown();
+ secondWindow->assertNoEvents();
+ wallpaper1->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ wallpaper2->assertNoEvents();
+
+ // Transfer touch focus to the second window
+ TransferFunction f = GetParam();
+ bool success = f(mDispatcher, firstWindow->getToken(), secondWindow->getToken());
+ ASSERT_TRUE(success);
+
+ // The first window gets cancel and the second gets down
+ firstWindow->consumeMotionCancel();
+ secondWindow->consumeMotionDown();
+ wallpaper1->consumeMotionCancel(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+ wallpaper2->consumeMotionDown(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+
+ // Send up event to the second window
+ 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();
+ wallpaper1->assertNoEvents();
+ wallpaper2->consumeMotionUp(ADISPLAY_ID_DEFAULT, expectedWallpaperFlags);
+}
+
// For the cases of single pointer touch and two pointers non-split touch, the api's
// 'transferTouch' and 'transferTouchFocus' are equivalent in behaviour. They only differ
// for the case where there are multiple pointers split across several windows.
@@ -2785,40 +5351,41 @@ INSTANTIATE_TEST_SUITE_P(TransferFunctionTests, TransferTouchFixture,
[&](const std::unique_ptr<InputDispatcher>& dispatcher,
sp<IBinder> from, sp<IBinder> to) {
return dispatcher->transferTouchFocus(from, to,
- false /*isDragAndDrop*/);
+ /*isDragAndDrop=*/false);
}));
TEST_F(InputDispatcherTest, TransferTouchFocus_TwoPointersSplitTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
firstWindow->setFrame(Rect(0, 0, 600, 400));
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
secondWindow->setFrame(Rect(0, 400, 600, 800));
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
PointF pointInFirst = {300, 200};
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();
@@ -2830,19 +5397,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();
@@ -2856,33 +5420,34 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
firstWindow->setFrame(Rect(0, 0, 600, 400));
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
secondWindow->setFrame(Rect(0, 400, 600, 800));
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
PointF pointInFirst = {300, 200};
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();
@@ -2897,19 +5462,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();
@@ -2921,32 +5483,32 @@ TEST_F(InputDispatcherTest, TransferTouch_TwoPointersSplitTouch) {
TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> firstWindowInPrimary =
- new FakeWindowHandle(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
sp<FakeWindowHandle> secondWindowInPrimary =
- new FakeWindowHandle(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
- sp<FakeWindowHandle> mirrorWindowInPrimary =
- firstWindowInPrimary->clone(application, mDispatcher, ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
- sp<FakeWindowHandle> firstWindowInSecondary =
- firstWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
firstWindowInSecondary->setFrame(Rect(0, 0, 100, 100));
- sp<FakeWindowHandle> secondWindowInSecondary =
- secondWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> secondWindowInSecondary = secondWindowInPrimary->clone(SECOND_DISPLAY_ID);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
// Update window info, let it find window handle of second display first.
- mDispatcher->setInputWindows(
- {{SECOND_DISPLAY_ID, {firstWindowInSecondary, secondWindowInSecondary}},
- {ADISPLAY_ID_DEFAULT,
- {mirrorWindowInPrimary, firstWindowInPrimary, secondWindowInPrimary}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindowInSecondary->getInfo(), *secondWindowInSecondary->getInfo(),
+ *mirrorWindowInPrimary->getInfo(), *firstWindowInPrimary->getInfo(),
+ *secondWindowInPrimary->getInfo()},
+ {},
+ 0,
+ 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -2961,14 +5523,14 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) {
secondWindowInPrimary->consumeMotionDown();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
firstWindowInPrimary->assertNoEvents();
secondWindowInPrimary->consumeMotionMove();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
firstWindowInPrimary->assertNoEvents();
@@ -2980,33 +5542,34 @@ TEST_F(InputDispatcherTest, TransferTouchFocus_CloneSurface) {
TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> firstWindowInPrimary =
- new FakeWindowHandle(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W1", ADISPLAY_ID_DEFAULT);
firstWindowInPrimary->setFrame(Rect(0, 0, 100, 100));
sp<FakeWindowHandle> secondWindowInPrimary =
- new FakeWindowHandle(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
- sp<FakeWindowHandle> mirrorWindowInPrimary =
- firstWindowInPrimary->clone(application, mDispatcher, ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> mirrorWindowInPrimary = firstWindowInPrimary->clone(ADISPLAY_ID_DEFAULT);
mirrorWindowInPrimary->setFrame(Rect(0, 100, 100, 200));
- sp<FakeWindowHandle> firstWindowInSecondary =
- firstWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> firstWindowInSecondary = firstWindowInPrimary->clone(SECOND_DISPLAY_ID);
firstWindowInSecondary->setFrame(Rect(0, 0, 100, 100));
- sp<FakeWindowHandle> secondWindowInSecondary =
- secondWindowInPrimary->clone(application, mDispatcher, SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle> secondWindowInSecondary = secondWindowInPrimary->clone(SECOND_DISPLAY_ID);
secondWindowInPrimary->setFrame(Rect(100, 0, 200, 100));
// Update window info, let it find window handle of second display first.
- mDispatcher->setInputWindows(
- {{SECOND_DISPLAY_ID, {firstWindowInSecondary, secondWindowInSecondary}},
- {ADISPLAY_ID_DEFAULT,
- {mirrorWindowInPrimary, firstWindowInPrimary, secondWindowInPrimary}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindowInSecondary->getInfo(), *secondWindowInSecondary->getInfo(),
+ *mirrorWindowInPrimary->getInfo(), *firstWindowInPrimary->getInfo(),
+ *secondWindowInPrimary->getInfo()},
+ {},
+ 0,
+ 0});
// Touch on second display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {50, 50}))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID,
+ {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
// Window should receive motion event.
@@ -3020,14 +5583,14 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
secondWindowInPrimary->consumeMotionDown(SECOND_DISPLAY_ID);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
SECOND_DISPLAY_ID, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
firstWindowInPrimary->assertNoEvents();
secondWindowInPrimary->consumeMotionMove(SECOND_DISPLAY_ID);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50}))
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
firstWindowInPrimary->assertNoEvents();
secondWindowInPrimary->consumeMotionUp(SECOND_DISPLAY_ID);
@@ -3035,31 +5598,139 @@ TEST_F(InputDispatcherTest, TransferTouch_CloneSurface) {
TEST_F(InputDispatcherTest, FocusedWindow_ReceivesFocusEventAndKeyEvent) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
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
+ mDispatcher->waitForIdle();
+ mFakePolicy->assertUserActivityPoked();
+}
+
+TEST_F(InputDispatcherTest, FocusedWindow_DisableUserActivity) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+
+ window->setDisableUserActivity(true);
+ window->setFocusable(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ 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
+ mDispatcher->waitForIdle();
+ mFakePolicy->assertUserActivityNotPoked();
+}
+
+TEST_F(InputDispatcherTest, FocusedWindow_DoesNotReceiveSystemShortcut) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+
+ window->setFocusable(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+
+ window->setFocusable(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+
+ window->setDisableUserActivity(true);
+ window->setFocusable(true);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ 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, InjectedTouchesPokeUserActivity) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT, {100, 100}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ window->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDisplayId(ADISPLAY_ID_DEFAULT)));
+
+ // Should have poked user activity
+ mDispatcher->waitForIdle();
+ mFakePolicy->assertUserActivityPoked();
}
TEST_F(InputDispatcherTest, UnfocusedWindow_DoesNotReceiveFocusEventOrKeyEvent) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
- 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();
@@ -3068,19 +5739,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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
// 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);
@@ -3091,33 +5759,34 @@ TEST_F(InputDispatcherTest, PointerCancel_SendCancelWhenSplitTouch) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> firstWindow =
- new FakeWindowHandle(application, mDispatcher, "First Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "First Window",
+ ADISPLAY_ID_DEFAULT);
firstWindow->setFrame(Rect(0, 0, 600, 400));
sp<FakeWindowHandle> secondWindow =
- new FakeWindowHandle(application, mDispatcher, "Second Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second Window",
+ ADISPLAY_ID_DEFAULT);
secondWindow->setFrame(Rect(0, 400, 600, 800));
// Add the windows to the dispatcher
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {firstWindow, secondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*firstWindow->getInfo(), *secondWindow->getInfo()}, {}, 0, 0});
PointF pointInFirst = {300, 200};
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();
@@ -3127,16 +5796,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();
@@ -3146,83 +5813,17 @@ TEST_F(InputDispatcherTest, SendTimeline_DoesNotCrashDispatcher) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
std::array<nsecs_t, GraphicsTimeline::SIZE> 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();
}
-class FakeMonitorReceiver {
-public:
- FakeMonitorReceiver(const std::unique_ptr<InputDispatcher>& dispatcher, const std::string name,
- int32_t displayId) {
- base::Result<std::unique_ptr<InputChannel>> channel =
- dispatcher->createInputMonitor(displayId, name, MONITOR_PID);
- mInputReceiver = std::make_unique<FakeInputReceiver>(std::move(*channel), name);
- }
-
- sp<IBinder> 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);
- }
-
- std::optional<int32_t> receiveEvent() { return mInputReceiver->receiveEvent(); }
-
- 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,
- expectedDisplayId, expectedFlags);
- }
-
- void consumeMotionMove(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
- mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_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,
- expectedDisplayId, expectedFlags);
- }
-
- void consumeMotionCancel(int32_t expectedDisplayId, int32_t expectedFlags = 0) {
- mInputReceiver->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_CANCEL,
- expectedDisplayId, expectedFlags);
- }
-
- 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*/);
- }
-
- MotionEvent* consumeMotion() {
- InputEvent* event = mInputReceiver->consume();
- if (!event) {
- 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";
- return nullptr;
- }
- return static_cast<MotionEvent*>(event);
- }
-
- void assertNoEvents() { mInputReceiver->assertNoEvents(); }
-
-private:
- std::unique_ptr<FakeInputReceiver> mInputReceiver;
-};
-
using InputDispatcherMonitorTest = InputDispatcherTest;
/**
@@ -3236,13 +5837,13 @@ using InputDispatcherMonitorTest = InputDispatcherTest;
TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDisappears) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Foreground", ADISPLAY_ID_DEFAULT);
- FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{100, 200}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -3251,7 +5852,7 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDis
monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {110, 200}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -3259,14 +5860,14 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDis
monitor.consumeMotionMove(ADISPLAY_ID_DEFAULT);
// Now the foreground window goes away
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+ mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
window->consumeMotionCancel();
monitor.assertNoEvents(); // Global monitor does not get a cancel yet
// If more events come in, there will be no more foreground window to send them to. This will
// cause a cancel for the monitor, as well.
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {120, 200}))
<< "Injection should fail because the window was removed";
window->assertNoEvents();
@@ -3276,29 +5877,29 @@ TEST_F(InputDispatcherMonitorTest, MonitorTouchIsCanceledWhenForegroundWindowDis
TEST_F(InputDispatcherMonitorTest, ReceivesMotionEvents) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
- FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
}
TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
- FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
monitor.consumeMotionDown(ADISPLAY_ID_DEFAULT);
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -3308,7 +5909,7 @@ TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
EXPECT_NE(OK, mDispatcher->pilferPointers(monitor.getToken()));
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -3318,16 +5919,16 @@ TEST_F(InputDispatcherMonitorTest, MonitorCannotPilferPointers) {
TEST_F(InputDispatcherMonitorTest, NoWindowTransform) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
window->setWindowOffset(20, 40);
window->setWindowTransform(0, 1, -1, 0);
- FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
MotionEvent* event = monitor.consumeMotion();
@@ -3337,26 +5938,26 @@ TEST_F(InputDispatcherMonitorTest, NoWindowTransform) {
TEST_F(InputDispatcherMonitorTest, InjectionFailsWithNoWindow) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Injection should fail if there is a monitor, but no touchable window";
monitor.assertNoEvents();
}
TEST_F(InputDispatcherTest, TestMoveEvent) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Fake Window", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
NotifyMotionArgs motionArgs =
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);
@@ -3366,9 +5967,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);
}
/**
@@ -3378,8 +5979,8 @@ TEST_F(InputDispatcherTest, TestMoveEvent) {
*/
TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Test window", ADISPLAY_ID_DEFAULT);
const WindowInfo& windowInfo = *window->getInfo();
// Set focused application.
@@ -3387,58 +5988,58 @@ TEST_F(InputDispatcherTest, TouchModeState_IsSentToApps) {
window->setFocusable(true);
SCOPED_TRACE("Check default value of touch mode");
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
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*/);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ 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}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
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*/);
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ 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}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
setFocusedWindow(window);
- window->consumeFocusEvent(true /*hasFocus*/, true /*inTouchMode*/);
+ window->consumeFocusEvent(/*hasFocus=*/true, /*inTouchMode=*/true);
window->assertNoEvents();
}
TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Test window", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
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();
+ KeyEvent* event = window->consumeKey();
ASSERT_NE(event, nullptr);
std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
@@ -3463,8 +6064,8 @@ TEST_F(InputDispatcherTest, VerifyInputEvent_KeyEvent) {
TEST_F(InputDispatcherTest, VerifyInputEvent_MotionEvent) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Test window", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
@@ -3475,14 +6076,14 @@ 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();
+ MotionEvent* event = window->consumeMotion();
ASSERT_NE(event, nullptr);
std::unique_ptr<VerifiedInputEvent> verified = mDispatcher->verifyInputEvent(*event);
@@ -3568,19 +6169,20 @@ TEST_F(InputDispatcherTest, GeneratedHmac_ChangesWhenFieldsChange) {
TEST_F(InputDispatcherTest, SetFocusedWindow) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowTop =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
sp<FakeWindowHandle> windowSecond =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
// Top window is also focusable but is not granted focus.
windowTop->setFocusable(true);
windowSecond->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
setFocusedWindow(windowSecond);
windowSecond->consumeFocusEvent(true);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
// Focused window should receive event.
@@ -3591,18 +6193,17 @@ TEST_F(InputDispatcherTest, SetFocusedWindow) {
TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
window->setFocusable(true);
// Release channel for window is no longer valid.
window->releaseChannel();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
setFocusedWindow(window);
// Test inject a key down, should timeout.
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
- << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+ ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
// window channel is invalid, so it should not receive any input event.
window->assertNoEvents();
@@ -3611,16 +6212,15 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestInvalidChannel) {
TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
window->setFocusable(false);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
setFocusedWindow(window);
// Test inject a key down, should timeout.
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
- << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+ ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
// window is not focusable, so it should not receive any input event.
window->assertNoEvents();
@@ -3629,62 +6229,69 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestNoFocusableWindow) {
TEST_F(InputDispatcherTest, SetFocusedWindow_CheckFocusedToken) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowTop =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
sp<FakeWindowHandle> windowSecond =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
windowTop->setFocusable(true);
windowSecond->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
setFocusedWindow(windowTop);
windowTop->consumeFocusEvent(true);
- setFocusedWindow(windowSecond, windowTop);
+ windowTop->editInfo()->focusTransferTarget = windowSecond->getToken();
+ mDispatcher->onWindowInfosChanged(
+ {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
windowSecond->consumeFocusEvent(true);
windowTop->consumeFocusEvent(false);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
// Focused window should receive event.
windowSecond->consumeKeyDown(ADISPLAY_ID_NONE);
}
-TEST_F(InputDispatcherTest, SetFocusedWindow_DropRequestFocusTokenNotFocused) {
+TEST_F(InputDispatcherTest, SetFocusedWindow_TransferFocusTokenNotFocusable) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> windowTop =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
sp<FakeWindowHandle> windowSecond =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
windowTop->setFocusable(true);
- windowSecond->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowTop, windowSecond}}});
- setFocusedWindow(windowSecond, windowTop);
+ windowSecond->setFocusable(false);
+ windowTop->editInfo()->focusTransferTarget = windowSecond->getToken();
+ mDispatcher->onWindowInfosChanged(
+ {{*windowTop->getInfo(), *windowSecond->getInfo()}, {}, 0, 0});
+ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
sp<FakeWindowHandle> previousFocusedWindow =
- new FakeWindowHandle(application, mDispatcher, "previousFocusedWindow",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "previousFocusedWindow",
+ ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
window->setFocusable(true);
previousFocusedWindow->setFocusable(true);
window->setVisible(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window, previousFocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*window->getInfo(), *previousFocusedWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(previousFocusedWindow);
previousFocusedWindow->consumeFocusEvent(true);
@@ -3694,7 +6301,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) {
// Injected key goes to pending queue.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
+ injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
// Window does not get focus event or key down.
@@ -3702,7 +6309,7 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) {
// Window becomes visible.
window->setVisible(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
// Window receives focus event.
window->consumeFocusEvent(true);
@@ -3713,12 +6320,12 @@ TEST_F(InputDispatcherTest, SetFocusedWindow_DeferInvisibleWindow) {
TEST_F(InputDispatcherTest, DisplayRemoved) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "window", ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
// window is granted focus.
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
setFocusedWindow(window);
window->consumeFocusEvent(true);
@@ -3751,14 +6358,14 @@ TEST_F(InputDispatcherTest, DisplayRemoved) {
* FLAG_WINDOW_IS_PARTIALLY_OBSCURED.
*/
TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) {
- constexpr int32_t SLIPPERY_PID = WINDOW_PID + 1;
- constexpr int32_t SLIPPERY_UID = WINDOW_UID + 1;
+ constexpr gui::Pid SLIPPERY_PID{WINDOW_PID.val() + 1};
+ constexpr gui::Uid SLIPPERY_UID{WINDOW_UID.val() + 1};
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
sp<FakeWindowHandle> slipperyExitWindow =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::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));
@@ -3767,24 +6374,24 @@ TEST_F(InputDispatcherTest, SlipperyWindow_SetsFlagPartiallyObscured) {
slipperyExitWindow->setOwnerInfo(SLIPPERY_PID, SLIPPERY_UID);
sp<FakeWindowHandle> slipperyEnterWindow =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
slipperyExitWindow->setFrame(Rect(0, 0, 100, 100));
- mDispatcher->setInputWindows(
- {{ADISPLAY_ID_DEFAULT, {slipperyExitWindow, slipperyEnterWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0});
// 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}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*slipperyExitWindow->getInfo(), *slipperyEnterWindow->getInfo()}, {}, 0, 0});
- 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();
@@ -3792,19 +6399,466 @@ 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<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> leftSlipperyWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftSlipperyWindow->setSlippery(true);
+ leftSlipperyWindow->setFrame(Rect(0, 0, 100, 100));
+
+ sp<FakeWindowHandle> rightDropTouchesWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightDropTouchesWindow->setFrame(Rect(100, 0, 200, 100));
+ rightDropTouchesWindow->setDropInput(true);
+
+ mDispatcher->onWindowInfosChanged(
+ {{*leftSlipperyWindow->getInfo(), *rightDropTouchesWindow->getInfo()}, {}, 0, 0});
+
+ // 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();
+}
+
+/**
+ * A single window is on screen first. Touch is injected into that window. Next, a second window
+ * appears. Since the first window is slippery, touch will move from the first window to the second.
+ */
+TEST_F(InputDispatcherTest, InjectedTouchSlips) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ sp<FakeWindowHandle> originalWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Original", ADISPLAY_ID_DEFAULT);
+ originalWindow->setFrame(Rect(0, 0, 200, 200));
+ originalWindow->setSlippery(true);
+
+ sp<FakeWindowHandle> appearingWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Appearing", ADISPLAY_ID_DEFAULT);
+ appearingWindow->setFrame(Rect(0, 0, 200, 200));
+
+ mDispatcher->onWindowInfosChanged({{*originalWindow->getInfo()}, {}, 0, 0});
+
+ // Touch down on the original window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(100).y(100))
+ .build()));
+ originalWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+
+ // Now, a new window appears. This could be, for example, a notification shade that appears
+ // after user starts to drag down on the launcher window.
+ mDispatcher->onWindowInfosChanged(
+ {{*appearingWindow->getInfo(), *originalWindow->getInfo()}, {}, 0, 0});
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(110).y(110))
+ .build()));
+ originalWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ appearingWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(120).y(120))
+ .build()));
+ appearingWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+ originalWindow->assertNoEvents();
+ appearingWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithMotions) {
+ using Uid = gui::Uid;
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> leftWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Left", ADISPLAY_ID_DEFAULT);
+ leftWindow->setFrame(Rect(0, 0, 100, 100));
+ leftWindow->setOwnerInfo(gui::Pid{1}, Uid{101});
+
+ sp<FakeWindowHandle> rightSpy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right spy", ADISPLAY_ID_DEFAULT);
+ rightSpy->setFrame(Rect(100, 0, 200, 100));
+ rightSpy->setOwnerInfo(gui::Pid{2}, Uid{102});
+ rightSpy->setSpy(true);
+ rightSpy->setTrustedOverlay(true);
+
+ sp<FakeWindowHandle> rightWindow =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Right", ADISPLAY_ID_DEFAULT);
+ rightWindow->setFrame(Rect(100, 0, 200, 100));
+ rightWindow->setOwnerInfo(gui::Pid{3}, Uid{103});
+
+ mDispatcher->onWindowInfosChanged(
+ {{*rightSpy->getInfo(), *rightWindow->getInfo(), *leftWindow->getInfo()}, {}, 0, 0});
+
+ // Touch in the left window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionDown());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(
+ mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {Uid{101}}));
+
+ // Touch another finger over the right windows
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionDown());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionDown());
+ ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionMove());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(
+ mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID,
+ {Uid{101}, Uid{102}, Uid{103}}));
+
+ // Release finger over left window. The UP actions are not treated as device interaction.
+ // The windows that did not receive the UP pointer will receive MOVE events, but since this
+ // is part of the UP action, we do not treat this as device interaction.
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_0_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(leftWindow->consumeMotionUp());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionMove());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionMove());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
+
+ // Move remaining finger
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionMove());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionMove());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(
+ mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {Uid{102}, Uid{103}}));
+
+ // Release all fingers
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(150).y(50))
+ .build());
+ ASSERT_NO_FATAL_FAILURE(rightSpy->consumeMotionUp());
+ ASSERT_NO_FATAL_FAILURE(rightWindow->consumeMotionUp());
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
+}
+
+TEST_F(InputDispatcherTest, NotifiesDeviceInteractionsWithKeys) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ window->setFrame(Rect(0, 0, 100, 100));
+ window->setOwnerInfo(gui::Pid{1}, gui::Uid{101});
+
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
+ setFocusedWindow(window);
+ ASSERT_NO_FATAL_FAILURE(window->consumeFocusEvent(true));
+
+ mDispatcher->notifyKey(KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build());
+ ASSERT_NO_FATAL_FAILURE(window->consumeKeyDown(ADISPLAY_ID_DEFAULT));
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(
+ mFakePolicy->assertNotifyDeviceInteractionWasCalled(DEVICE_ID, {gui::Uid{101}}));
+
+ // The UP actions are not treated as device interaction.
+ mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build());
+ ASSERT_NO_FATAL_FAILURE(window->consumeKeyUp(ADISPLAY_ID_DEFAULT));
+ mDispatcher->waitForIdle();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertNotifyDeviceInteractionWasNotCalled());
+}
+
+TEST_F(InputDispatcherTest, HoverEnterExitSynthesisUsesNewEventId) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+ sp<FakeWindowHandle> spy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+ spy->setFrame(Rect(0, 0, 200, 100));
+ spy->setTrustedOverlay(true);
+ spy->setSpy(true);
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ // Send hover move to the left window, and ensure hover enter is synthesized with a new eventId.
+ NotifyMotionArgs notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS,
+ ADISPLAY_ID_DEFAULT, {PointF{50, 50}});
+ mDispatcher->notifyMotion(notifyArgs);
+
+ const MotionEvent& leftEnter = left->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_ENTER), Not(WithEventId(notifyArgs.id)),
+ WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+ spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ Not(WithEventId(notifyArgs.id)),
+ Not(WithEventId(leftEnter.getId())),
+ WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+ // Send move to the right window, and ensure hover exit and enter are synthesized with new ids.
+ notifyArgs = generateMotionArgs(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS, ADISPLAY_ID_DEFAULT,
+ {PointF{150, 50}});
+ mDispatcher->notifyMotion(notifyArgs);
+
+ const MotionEvent& leftExit = left->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_EXIT), Not(WithEventId(notifyArgs.id)),
+ WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+ right->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER),
+ Not(WithEventId(notifyArgs.id)),
+ Not(WithEventId(leftExit.getId())),
+ WithEventIdSource(IdGenerator::Source::INPUT_DISPATCHER)));
+
+ spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithEventId(notifyArgs.id)));
+}
+
+class InputDispatcherFallbackKeyTest : public InputDispatcherTest {
+protected:
+ std::shared_ptr<FakeApplicationHandle> mApp;
+ sp<FakeWindowHandle> mWindow;
+
+ virtual void SetUp() override {
+ InputDispatcherTest::SetUp();
+
+ mApp = std::make_shared<FakeApplicationHandle>();
+
+ mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Window", ADISPLAY_ID_DEFAULT);
+ mWindow->setFrame(Rect(0, 0, 100, 100));
+
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
+ setFocusedWindow(mWindow);
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeFocusEvent(/*hasFocus=*/true));
+ }
+
+ void setFallback(int32_t keycode) {
+ mFakePolicy->setUnhandledKeyHandler([keycode](const KeyEvent& event) {
+ return KeyEventBuilder(event).keyCode(keycode).build();
+ });
+ }
+
+ void consumeKey(bool handled, const ::testing::Matcher<KeyEvent>& matcher) {
+ KeyEvent* event = mWindow->consumeKey(handled);
+ ASSERT_NE(event, nullptr) << "Did not receive key event";
+ ASSERT_THAT(*event, matcher);
+ }
+};
+
+TEST_F(InputDispatcherFallbackKeyTest, PolicyNotNotifiedForHandledKey) {
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+ consumeKey(/*handled=*/true, AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, PolicyNotifiedForUnhandledKey) {
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+ consumeKey(/*handled=*/false, AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, NoFallbackRequestedByPolicy) {
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+ // Do not handle this key event.
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+
+ // Since the policy did not request any fallback to be generated, ensure there are no events.
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, FallbackDispatchForUnhandledKey) {
+ setFallback(AKEYCODE_B);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+ // Do not handle this key event.
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+
+ // Since the key was not handled, ensure the fallback event was dispatched instead.
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ // Release the original key, and ensure the fallback key is also released.
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+ mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, AppHandlesPreviouslyUnhandledKey) {
+ setFallback(AKEYCODE_B);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+ // Do not handle this key event, but handle the fallback.
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ // Release the original key, and ensure the fallback key is also released.
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+ // But this time, the app handles the original key.
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ // Ensure the fallback key is canceled.
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+ mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, AppDoesNotHandleFallback) {
+ setFallback(AKEYCODE_B);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+ // Do not handle this key event.
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ // App does not handle the fallback either, so ensure another fallback is not generated.
+ setFallback(AKEYCODE_C);
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ // Release the original key, and ensure the fallback key is also released.
+ setFallback(AKEYCODE_B);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+ mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, InconsistentPolicyCancelsFallback) {
+ setFallback(AKEYCODE_B);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+ // Do not handle this key event, so fallback is generated.
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ // Release the original key, but assume the policy is misbehaving and it
+ // generates an inconsistent fallback to the one from the DOWN event.
+ setFallback(AKEYCODE_C);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ // Ensure the fallback key reported before as DOWN is canceled due to the inconsistency.
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+ mWindow->assertNoEvents();
+}
+
+TEST_F(InputDispatcherFallbackKeyTest, CanceledKeyCancelsFallback) {
+ setFallback(AKEYCODE_B);
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).keyCode(AKEYCODE_A).build());
+
+ // Do not handle this key event, so fallback is generated.
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_A), WithFlags(0)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_DOWN), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK)));
+
+ // The original key is canceled.
+ mDispatcher->notifyKey(KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+ .keyCode(AKEYCODE_A)
+ .addFlag(AKEY_EVENT_FLAG_CANCELED)
+ .build());
+ consumeKey(/*handled=*/false,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_A),
+ WithFlags(AKEY_EVENT_FLAG_CANCELED)));
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyReported(AKEYCODE_A));
+ // Ensure the fallback key is also canceled due to the original key being canceled.
+ consumeKey(/*handled=*/true,
+ AllOf(WithKeyAction(ACTION_UP), WithKeyCode(AKEYCODE_B),
+ WithFlags(AKEY_EVENT_FLAG_FALLBACK | AKEY_EVENT_FLAG_CANCELED)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertUnhandledKeyNotReported());
+ mWindow->assertNoEvents();
+}
+
class InputDispatcherKeyRepeatTest : public InputDispatcherTest {
protected:
- static constexpr nsecs_t KEY_REPEAT_TIMEOUT = 40 * 1000000; // 40 ms
- static constexpr nsecs_t KEY_REPEAT_DELAY = 40 * 1000000; // 40 ms
+ static constexpr std::chrono::nanoseconds KEY_REPEAT_TIMEOUT = 40ms;
+ static constexpr std::chrono::nanoseconds KEY_REPEAT_DELAY = 40ms;
std::shared_ptr<FakeApplicationHandle> mApp;
sp<FakeWindowHandle> mWindow;
virtual void SetUp() override {
- mFakePolicy = new FakeInputDispatcherPolicy();
- mFakePolicy->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
- mDispatcher = std::make_unique<InputDispatcher>(mFakePolicy);
- mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
+ mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>();
+ mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy);
+ mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+ mDispatcher->setKeyRepeatConfiguration(KEY_REPEAT_TIMEOUT, KEY_REPEAT_DELAY);
ASSERT_EQ(OK, mDispatcher->start());
setUpWindow();
@@ -3812,10 +6866,10 @@ protected:
void setUpWindow() {
mApp = std::make_shared<FakeApplicationHandle>();
- mWindow = new FakeWindowHandle(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
mWindow->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mWindow);
mWindow->consumeFocusEvent(true);
}
@@ -3824,7 +6878,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);
@@ -3832,43 +6886,35 @@ protected:
void expectKeyRepeatOnce(int32_t repeatCount) {
SCOPED_TRACE(StringPrintf("Checking event with repeat count %" PRId32, repeatCount));
- InputEvent* repeatEvent = mWindow->consume();
- ASSERT_NE(nullptr, repeatEvent);
-
- uint32_t eventType = repeatEvent->getType();
- ASSERT_EQ(AINPUT_EVENT_TYPE_KEY, eventType);
-
- KeyEvent* repeatKeyEvent = static_cast<KeyEvent*>(repeatEvent);
- uint32_t eventAction = repeatKeyEvent->getAction();
- EXPECT_EQ(AKEY_EVENT_ACTION_DOWN, eventAction);
- EXPECT_EQ(repeatCount, repeatKeyEvent->getRepeatCount());
+ mWindow->consumeKeyEvent(
+ AllOf(WithKeyAction(AKEY_EVENT_ACTION_DOWN), WithRepeatCount(repeatCount)));
}
void sendAndConsumeKeyUp(int32_t deviceId) {
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);
@@ -3876,52 +6922,52 @@ 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();
+ KeyEvent* repeatEvent = mWindow->consumeKey();
ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
EXPECT_EQ(IdGenerator::Source::INPUT_DISPATCHER,
IdGenerator::getSource(repeatEvent->getId()));
@@ -3929,11 +6975,12 @@ 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<int32_t> idSet;
for (int32_t repeatCount = 1; repeatCount <= 10; ++repeatCount) {
- InputEvent* repeatEvent = mWindow->consume();
+ KeyEvent* repeatEvent = mWindow->consumeKey();
ASSERT_NE(nullptr, repeatEvent) << "Didn't receive event with repeat count " << repeatCount;
int32_t id = repeatEvent->getId();
EXPECT_EQ(idSet.end(), idSet.find(id));
@@ -3949,25 +6996,27 @@ public:
application1 = std::make_shared<FakeApplicationHandle>();
windowInPrimary =
- new FakeWindowHandle(application1, mDispatcher, "D_1", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::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);
windowInPrimary->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowInPrimary}}});
+ mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
+
setFocusedWindow(windowInPrimary);
windowInPrimary->consumeFocusEvent(true);
application2 = std::make_shared<FakeApplicationHandle>();
windowInSecondary =
- new FakeWindowHandle(application2, mDispatcher, "D_2", SECOND_DISPLAY_ID);
+ sp<FakeWindowHandle>::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);
// Set focus window for second display.
mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2);
windowInSecondary->setFocusable(true);
- mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowInPrimary->getInfo(), *windowInSecondary->getInfo()}, {}, 0, 0});
setFocusedWindow(windowInSecondary);
windowInSecondary->consumeFocusEvent(true);
}
@@ -3991,14 +7040,14 @@ protected:
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch) {
// Test touch down on primary display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
windowInSecondary->assertNoEvents();
// Test touch down on second display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->assertNoEvents();
windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
@@ -4007,27 +7056,26 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayTouch)
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus) {
// Test inject a key down with display id specified.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKeyDownNoRepeat(mDispatcher, ADISPLAY_ID_DEFAULT))
+ injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->consumeKeyDown(ADISPLAY_ID_DEFAULT);
windowInSecondary->assertNoEvents();
// Test inject a key down without display id specified.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->assertNoEvents();
windowInSecondary->consumeKeyDown(ADISPLAY_ID_NONE);
// Remove all windows in secondary display.
- mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {}}});
+ mDispatcher->onWindowInfosChanged({{*windowInPrimary->getInfo()}, {}, 0, 0});
// 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.
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDownNoRepeat(mDispatcher))
- << "Inject key event should return InputEventInjectionResult::TIMED_OUT";
+ ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
windowInPrimary->assertNoEvents();
windowInSecondary->consumeFocusEvent(false);
windowInSecondary->assertNoEvents();
@@ -4036,13 +7084,13 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, SetInputWindow_MultiDisplayFocus)
// Test per-display input monitors for motion event.
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
FakeMonitorReceiver monitorInPrimary =
- FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
FakeMonitorReceiver monitorInSecondary =
- FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+ FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
// Test touch down on primary display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -4051,18 +7099,25 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
// Test touch down on second display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->assertNoEvents();
monitorInPrimary.assertNoEvents();
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.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_NONE))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->assertNoEvents();
monitorInPrimary.assertNoEvents();
@@ -4074,12 +7129,12 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorMotionEvent_MultiDisplay) {
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
// Input monitor per display.
FakeMonitorReceiver monitorInPrimary =
- FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
FakeMonitorReceiver monitorInSecondary =
- FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+ FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
// Test inject a key down.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->assertNoEvents();
monitorInPrimary.assertNoEvents();
@@ -4089,15 +7144,21 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, MonitorKeyEvent_MultiDisplay) {
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay) {
sp<FakeWindowHandle> secondWindowInPrimary =
- new FakeWindowHandle(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application1, mDispatcher, "D_1_W2", ADISPLAY_ID_DEFAULT);
secondWindowInPrimary->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {windowInPrimary, secondWindowInPrimary}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*windowInPrimary->getInfo(), *secondWindowInPrimary->getInfo(),
+ *windowInSecondary->getInfo()},
+ {},
+ 0,
+ 0});
setFocusedWindow(secondWindowInPrimary);
windowInPrimary->consumeFocusEvent(false);
secondWindowInPrimary->consumeFocusEvent(true);
// Test inject a key down.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->assertNoEvents();
windowInSecondary->assertNoEvents();
@@ -4106,20 +7167,20 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CanFocusWindowOnUnfocusedDisplay)
TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) {
FakeMonitorReceiver monitorInPrimary =
- FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
FakeMonitorReceiver monitorInSecondary =
- FakeMonitorReceiver(mDispatcher, "M_2", SECOND_DISPLAY_ID);
+ FakeMonitorReceiver(*mDispatcher, "M_2", SECOND_DISPLAY_ID);
// Test touch down on primary display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInPrimary->consumeMotionDown(ADISPLAY_ID_DEFAULT);
monitorInPrimary.consumeMotionDown(ADISPLAY_ID_DEFAULT);
// Test touch down on second display.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, SECOND_DISPLAY_ID))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
windowInSecondary->consumeMotionDown(SECOND_DISPLAY_ID);
monitorInSecondary.consumeMotionDown(SECOND_DISPLAY_ID);
@@ -4133,14 +7194,14 @@ TEST_F(InputDispatcherFocusOnTwoDisplaysTest, CancelTouch_MultiDisplay) {
// Test inject a move motion event, no window/monitor should receive the event.
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {110, 200}))
<< "Inject motion event should return InputEventInjectionResult::FAILED";
windowInPrimary->assertNoEvents();
monitorInPrimary.assertNoEvents();
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
SECOND_DISPLAY_ID, {110, 200}))
<< "Inject motion event should return InputEventInjectionResult::FAILED";
windowInSecondary->assertNoEvents();
@@ -4155,13 +7216,13 @@ 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());
+ const auto xy = transform.transform(motionArgs.pointerCoords[0].getXYValue());
mFakePolicy->assertFilterInputEventWasCalled(motionArgs, xy);
} else {
mFakePolicy->assertFilterInputEventWasNotCalled();
@@ -4172,9 +7233,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) {
@@ -4188,36 +7249,36 @@ protected:
// Test InputFilter for MotionEvent
TEST_F(InputFilterTest, MotionEvent_InputFilter) {
// Since the InputFilter is disabled by default, check if touch events aren't filtered.
- testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ false);
- testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ false);
+ testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+ testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
// Enable InputFilter
mDispatcher->setInputFilterEnabled(true);
// Test touch on both primary and second display, and check if both events are filtered.
- testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ true);
- testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ true);
+ testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true);
+ testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true);
// Disable InputFilter
mDispatcher->setInputFilterEnabled(false);
// Test touch on both primary and second display, and check if both events aren't filtered.
- testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ false);
- testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ false);
+ testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/false);
+ testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/false);
}
// Test InputFilter for KeyEvent
TEST_F(InputFilterTest, KeyEvent_InputFilter) {
// Since the InputFilter is disabled by default, check if key event aren't filtered.
- testNotifyKey(/*expectToBeFiltered*/ false);
+ testNotifyKey(/*expectToBeFiltered=*/false);
// Enable InputFilter
mDispatcher->setInputFilterEnabled(true);
// Send a key event, and check if it is filtered.
- testNotifyKey(/*expectToBeFiltered*/ true);
+ testNotifyKey(/*expectToBeFiltered=*/true);
// Disable InputFilter
mDispatcher->setInputFilterEnabled(false);
// Send a key event, and check if it isn't filtered.
- testNotifyKey(/*expectToBeFiltered*/ false);
+ testNotifyKey(/*expectToBeFiltered=*/false);
}
// Ensure that MotionEvents sent to the InputFilter through InputListener are converted to the
@@ -4234,14 +7295,14 @@ 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);
// Ensure the correct transforms are used for the displays.
- testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered*/ true, firstDisplayTransform);
- testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered*/ true, secondDisplayTransform);
+ testNotifyMotion(ADISPLAY_ID_DEFAULT, /*expectToBeFiltered=*/true, firstDisplayTransform);
+ testNotifyMotion(SECOND_DISPLAY_ID, /*expectToBeFiltered=*/true, secondDisplayTransform);
}
class InputFilterInjectionPolicyTest : public InputDispatcherTest {
@@ -4258,12 +7319,12 @@ protected:
std::shared_ptr<InputApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
- mWindow =
- new FakeWindowHandle(application, mDispatcher, "Test Window", ADISPLAY_ID_DEFAULT);
+ mWindow = sp<FakeWindowHandle>::make(application, mDispatcher, "Test Window",
+ ADISPLAY_ID_DEFAULT);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
mWindow->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mWindow);
mWindow->consumeFocusEvent(true);
}
@@ -4275,20 +7336,15 @@ 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*/,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
+ InputEventInjectionSync::WAIT_FOR_RESULT, 100ms,
policyFlags | additionalPolicyFlags));
- InputEvent* received = mWindow->consume();
- ASSERT_NE(nullptr, received);
- ASSERT_EQ(resolvedDeviceId, received->getDeviceId());
- ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_KEY);
- KeyEvent& keyEvent = static_cast<KeyEvent&>(*received);
- ASSERT_EQ(flags, keyEvent.getFlags());
+ mWindow->consumeKeyEvent(AllOf(WithDeviceId(resolvedDeviceId), WithFlags(flags)));
}
void testInjectedMotion(int32_t policyFlags, int32_t injectedDeviceId, int32_t resolvedDeviceId,
@@ -4310,20 +7366,15 @@ protected:
identityTransform, 0, 0, AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION, identityTransform, eventTime,
eventTime,
- /*pointerCount*/ 1, pointerProperties, pointerCoords);
+ /*pointerCount=*/1, pointerProperties, pointerCoords);
const int32_t additionalPolicyFlags = POLICY_FLAG_PASS_TO_USER;
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- mDispatcher->injectInputEvent(&event, {} /*targetUid*/,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms,
+ mDispatcher->injectInputEvent(&event, /*targetUid=*/{},
+ InputEventInjectionSync::WAIT_FOR_RESULT, 100ms,
policyFlags | additionalPolicyFlags));
- InputEvent* received = mWindow->consume();
- ASSERT_NE(nullptr, received);
- ASSERT_EQ(resolvedDeviceId, received->getDeviceId());
- ASSERT_EQ(received->getType(), AINPUT_EVENT_TYPE_MOTION);
- MotionEvent& motionEvent = static_cast<MotionEvent&>(*received);
- ASSERT_EQ(flags, motionEvent.getFlags());
+ mWindow->consumeMotionEvent(AllOf(WithFlags(flags), WithDeviceId(resolvedDeviceId)));
}
private:
@@ -4334,26 +7385,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 {
@@ -4363,11 +7414,11 @@ class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
std::shared_ptr<FakeApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
mUnfocusedWindow =
- new FakeWindowHandle(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Top", ADISPLAY_ID_DEFAULT);
mUnfocusedWindow->setFrame(Rect(0, 0, 30, 30));
mFocusedWindow =
- new FakeWindowHandle(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Second", ADISPLAY_ID_DEFAULT);
mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
// Set focused application.
@@ -4375,7 +7426,8 @@ class InputDispatcherOnPointerDownOutsideFocus : public InputDispatcherTest {
mFocusedWindow->setFocusable(true);
// Expect one focus window exist in display.
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mFocusedWindow);
mFocusedWindow->consumeFocusEvent(true);
}
@@ -4398,7 +7450,7 @@ protected:
// the onPointerDownOutsideFocus callback.
TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Success) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{20, 20}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mUnfocusedWindow->consumeMotionDown();
@@ -4412,7 +7464,8 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_Succe
// onPointerDownOutsideFocus callback.
TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPointerSource) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT, {20, 20}))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TRACKBALL, ADISPLAY_ID_DEFAULT,
+ {20, 20}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mFocusedWindow->consumeMotionDown();
@@ -4424,7 +7477,7 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonPo
// have focus. Ensure no window received the onPointerDownOutsideFocus callback.
TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMotionFailure) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKeyDownNoRepeat(mDispatcher, ADISPLAY_ID_DEFAULT))
+ injectKeyDownNoRepeat(*mDispatcher, ADISPLAY_ID_DEFAULT))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mFocusedWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
@@ -4437,7 +7490,7 @@ TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_NonMo
// onPointerDownOutsideFocus callback.
TEST_F(InputDispatcherOnPointerDownOutsideFocus, OnPointerDownOutsideFocus_OnAlreadyFocusedWindow) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
FOCUSED_WINDOW_TOUCH_POINT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mFocusedWindow->consumeMotionDown();
@@ -4452,10 +7505,10 @@ 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))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(*mDispatcher, event))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
@@ -4473,15 +7526,15 @@ class InputDispatcherMultiWindowSameTokenTests : public InputDispatcherTest {
std::shared_ptr<FakeApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
- mWindow1 = new FakeWindowHandle(application, mDispatcher, "Fake Window 1",
- ADISPLAY_ID_DEFAULT);
+ mWindow1 = sp<FakeWindowHandle>::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<FakeWindowHandle>::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}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
}
protected:
@@ -4497,36 +7550,31 @@ protected:
void consumeMotionEvent(const sp<FakeWindowHandle>& window, int32_t expectedAction,
const std::vector<PointF>& points) {
const std::string name = window->getName();
- InputEvent* event = window->consume();
-
- ASSERT_NE(nullptr, event) << name.c_str()
- << ": consumer should have returned non-NULL event.";
+ MotionEvent* motionEvent = window->consumeMotion();
- ASSERT_EQ(AINPUT_EVENT_TYPE_MOTION, event->getType())
- << name.c_str() << "expected " << inputEventTypeToString(AINPUT_EVENT_TYPE_MOTION)
- << " event, got " << inputEventTypeToString(event->getType()) << " event";
+ ASSERT_NE(nullptr, motionEvent)
+ << name.c_str() << ": consumer should have returned non-NULL event.";
- const MotionEvent& motionEvent = static_cast<const MotionEvent&>(*event);
- assertMotionAction(expectedAction, motionEvent.getAction());
+ ASSERT_THAT(*motionEvent, WithMotionAction(expectedAction));
+ ASSERT_EQ(points.size(), motionEvent->getPointerCount());
for (size_t i = 0; i < points.size(); i++) {
float expectedX = points[i].x;
float expectedY = points[i].y;
- EXPECT_EQ(expectedX, motionEvent.getX(i))
+ EXPECT_EQ(expectedX, motionEvent->getX(i))
<< "expected " << expectedX << " for x[" << i << "] coord of " << name.c_str()
- << ", got " << motionEvent.getX(i);
- EXPECT_EQ(expectedY, motionEvent.getY(i))
+ << ", got " << motionEvent->getX(i);
+ EXPECT_EQ(expectedY, motionEvent->getY(i))
<< "expected " << expectedY << " for y[" << i << "] coord of " << name.c_str()
- << ", got " << motionEvent.getY(i);
+ << ", got " << motionEvent->getY(i);
}
}
void touchAndAssertPositions(int32_t action, const std::vector<PointF>& touchedPoints,
std::vector<PointF> 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);
@@ -4551,6 +7599,7 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchSameScale) {
TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentTransform) {
// Set scale value for window2
mWindow2->setWindowScale(0.5f, 0.5f);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
// Touch Window 1
PointF touchedPoint = {10, 10};
@@ -4567,12 +7616,14 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, SingleTouchDifferentTransform)
// Update the transform so rotation is set
mWindow2->setWindowTransform(0, -1, 1, 0);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
expectedPoint = getPointInWindow(mWindow2->getInfo(), touchedPoint);
touchAndAssertPositions(AMOTION_EVENT_ACTION_DOWN, {touchedPoint}, {expectedPoint});
}
TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform) {
mWindow2->setWindowScale(0.5f, 0.5f);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
// Touch Window 1
std::vector<PointF> touchedPoints = {PointF{10, 10}};
@@ -4590,12 +7641,14 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchDifferentTransform
// Update the transform so rotation is set for Window 2
mWindow2->setWindowTransform(0, -1, 1, 0);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
}
TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTransform) {
mWindow2->setWindowScale(0.5f, 0.5f);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
// Touch Window 1
std::vector<PointF> touchedPoints = {PointF{10, 10}};
@@ -4621,6 +7674,7 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTrans
// Touch Window 2
mWindow2->setWindowTransform(0, -1, 1, 0);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
expectedPoints.push_back(getPointInWindow(mWindow2->getInfo(), touchedPoints[1]));
touchAndAssertPositions(POINTER_1_DOWN, touchedPoints, expectedPoints);
@@ -4634,6 +7688,7 @@ TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleTouchMoveDifferentTrans
TEST_F(InputDispatcherMultiWindowSameTokenTests, MultipleWindowsFirstTouchWithScale) {
mWindow1->setWindowScale(0.5f, 0.5f);
+ mDispatcher->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
// Touch Window 1
std::vector<PointF> touchedPoints = {PointF{10, 10}};
@@ -4654,22 +7709,67 @@ 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->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
+
+ // 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->onWindowInfosChanged({{*mWindow1->getInfo(), *mWindow2->getInfo()}, {}, 0, 0});
+
+ // Start hover in window 1
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(50).y(50))
+ .build());
+ consumeMotionEvent(mWindow1, ACTION_HOVER_ENTER,
+ {getPointInWindow(mWindow1->getInfo(), PointF{50, 50})});
+ // Move hover to window 2.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(150))
+ .build());
+ 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<FakeApplicationHandle>();
- mApplication->setDispatchingTimeout(20ms);
- mWindow =
- new FakeWindowHandle(mApplication, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mApplication->setDispatchingTimeout(100ms);
+ mWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "TestWindow",
+ ADISPLAY_ID_DEFAULT);
mWindow->setFrame(Rect(0, 0, 30, 30));
- mWindow->setDispatchingTimeout(30ms);
+ mWindow->setDispatchingTimeout(100ms);
mWindow->setFocusable(true);
// Set focused application.
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mWindow);
mWindow->consumeFocusEvent(true);
}
@@ -4680,27 +7780,31 @@ class InputDispatcherSingleWindowAnr : public InputDispatcherTest {
}
protected:
+ static constexpr std::chrono::duration SPY_TIMEOUT = 200ms;
std::shared_ptr<FakeApplicationHandle> mApplication;
sp<FakeWindowHandle> mWindow;
static constexpr PointF WINDOW_LOCATION = {20, 20};
void tapOnWindow() {
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- WINDOW_LOCATION));
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- WINDOW_LOCATION));
+ const auto touchingPointer = PointerBuilder(/*id=*/0, ToolType::FINGER)
+ .x(WINDOW_LOCATION.x)
+ .y(WINDOW_LOCATION.y);
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(touchingPointer)
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(touchingPointer)
+ .build());
}
sp<FakeWindowHandle> addSpyWindow() {
sp<FakeWindowHandle> spy =
- new FakeWindowHandle(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Spy", ADISPLAY_ID_DEFAULT);
spy->setTrustedOverlay(true);
spy->setFocusable(false);
spy->setSpy(true);
- spy->setDispatchingTimeout(30ms);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, mWindow}}});
+ spy->setDispatchingTimeout(SPY_TIMEOUT);
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *mWindow->getInfo()}, {}, 0, 0});
return spy;
}
};
@@ -4716,7 +7820,7 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenTouchIsConsumed_NoAnr) {
// Send a regular key and respond, which should not cause an ANR.
TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) {
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(mDispatcher));
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
ASSERT_TRUE(mDispatcher->waitForIdle());
mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -4724,13 +7828,13 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenKeyIsConsumed_NoAnr) {
TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) {
mWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
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, CONSUME_TIMEOUT_EVENT_EXPECTED,
+ /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
// Key will not go to window because we have no focused window.
// The 'no focused window' ANR timer should start instead.
@@ -4748,7 +7852,7 @@ TEST_F(InputDispatcherSingleWindowAnr, WhenFocusedApplicationChanges_NoAnr) {
// So InputDispatcher will enqueue ACTION_CANCEL event as well.
TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
WINDOW_LOCATION));
std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
@@ -4757,8 +7861,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());
}
@@ -4766,7 +7870,7 @@ TEST_F(InputDispatcherSingleWindowAnr, OnPointerDown_BasicAnr) {
// Send a key to the app and have the app not respond right away.
TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) {
// Inject a key, and don't respond - expect that ANR is called.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(mDispatcher));
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDownNoRepeat(*mDispatcher));
std::optional<uint32_t> sequenceNum = mWindow->receiveEvent();
ASSERT_TRUE(sequenceNum);
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -4777,12 +7881,12 @@ TEST_F(InputDispatcherSingleWindowAnr, OnKeyDown_BasicAnr) {
// We have a focused application, but no focused window
TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) {
mWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
mWindow->consumeFocusEvent(false);
// taps on the window work as normal
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
WINDOW_LOCATION));
ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
mDispatcher->waitForIdle();
@@ -4792,8 +7896,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, 50ms, /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
@@ -4806,22 +7910,24 @@ TEST_F(InputDispatcherSingleWindowAnr, FocusedApplication_NoFocusedWindow) {
*/
TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) {
mWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
mWindow->consumeFocusEvent(false);
KeyEvent event;
+ static constexpr std::chrono::duration STALE_EVENT_TIMEOUT = 1000ms;
+ mFakePolicy->setStaleEventTimeout(STALE_EVENT_TIMEOUT);
const nsecs_t eventTime = systemTime(SYSTEM_TIME_MONOTONIC) -
std::chrono::nanoseconds(STALE_EVENT_TIMEOUT).count();
// Define a valid key down event that is stale (too old).
event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE,
- INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /* flags */ 0, AKEYCODE_A, KEY_A,
- AMETA_NONE, 1 /*repeatCount*/, eventTime, eventTime);
+ INVALID_HMAC, AKEY_EVENT_ACTION_DOWN, /*flags=*/0, AKEYCODE_A, KEY_A,
+ 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)
@@ -4835,20 +7941,29 @@ TEST_F(InputDispatcherSingleWindowAnr, StaleKeyEventDoesNotAnr) {
// We have a focused application, but no focused window
// Make sure that we don't notify policy twice about the same ANR.
TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr) {
+ const std::chrono::duration appTimeout = 400ms;
+ mApplication->setDispatchingTimeout(appTimeout);
+ mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApplication);
+
mWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
mWindow->consumeFocusEvent(false);
// Once a focused event arrives, we get an ANR for this application
// We specify the injection timeout to be smaller than the application timeout, to ensure that
// injection times out (instead of failing).
+ const std::chrono::duration eventInjectionTimeout = 100ms;
+ ASSERT_LT(eventInjectionTimeout, appTimeout);
const InputEventInjectionResult result =
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */, ADISPLAY_ID_DEFAULT,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms, false /* allowKeyRepeat */);
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
- const std::chrono::duration appTimeout =
- mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
- mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(appTimeout, mApplication);
+ injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::WAIT_FOR_RESULT, eventInjectionTimeout,
+ /*allowKeyRepeat=*/false);
+ ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result)
+ << "result=" << ftl::enum_string(result);
+ // We already waited for 'eventInjectionTimeout`, because the countdown started when the event
+ // was first injected. So now we have (appTimeout - eventInjectionTimeout) left to wait.
+ std::chrono::duration remainingWaitTime = appTimeout - eventInjectionTimeout;
+ mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(remainingWaitTime, mApplication);
std::this_thread::sleep_for(appTimeout);
// ANR should not be raised again. It is up to policy to do that if it desires.
@@ -4862,20 +7977,17 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DoesNotSendDuplicateAnr)
// We have a focused application, but no focused window
TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) {
mWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
mWindow->consumeFocusEvent(false);
// 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,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, result);
+ ASSERT_NO_FATAL_FAILURE(assertInjectedKeyTimesOut(*mDispatcher));
const std::chrono::duration timeout = mApplication->getDispatchingTimeout(DISPATCHING_TIMEOUT);
mFakePolicy->assertNotifyNoFocusedWindowAnrWasCalled(timeout, mApplication);
// Future focused events get dropped right away
- ASSERT_EQ(InputEventInjectionResult::FAILED, injectKeyDown(mDispatcher));
+ ASSERT_EQ(InputEventInjectionResult::FAILED, injectKeyDown(*mDispatcher));
ASSERT_TRUE(mDispatcher->waitForIdle());
mWindow->assertNoEvents();
}
@@ -4891,14 +8003,14 @@ TEST_F(InputDispatcherSingleWindowAnr, NoFocusedWindow_DropsFocusedEvents) {
*/
TEST_F(InputDispatcherSingleWindowAnr, Anr_HandlesEventsWithIdenticalTimestamps) {
nsecs_t currentTime = systemTime(SYSTEM_TIME_MONOTONIC);
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
{AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION},
500ms, InputEventInjectionSync::WAIT_FOR_RESULT, currentTime);
// Now send ACTION_UP, with identical timestamp
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, WINDOW_LOCATION,
{AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION},
@@ -4915,7 +8027,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowAnr) {
sp<FakeWindowHandle> spy = addSpyWindow();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
WINDOW_LOCATION));
mWindow->consumeMotionDown();
@@ -4925,8 +8037,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());
}
@@ -4937,9 +8049,9 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnKey)
sp<FakeWindowHandle> spy = addSpyWindow();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT));
+ injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT));
mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher, ADISPLAY_ID_DEFAULT));
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher, ADISPLAY_ID_DEFAULT));
// Stuck on the ACTION_UP
const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -4984,19 +8096,20 @@ TEST_F(InputDispatcherSingleWindowAnr, SpyWindowReceivesEventsDuringAppAnrOnMoti
}
TEST_F(InputDispatcherSingleWindowAnr, UnresponsiveMonitorAnr) {
- mDispatcher->setMonitorDispatchingTimeoutForTest(30ms);
+ mDispatcher->setMonitorDispatchingTimeoutForTest(SPY_TIMEOUT);
- FakeMonitorReceiver monitor = FakeMonitorReceiver(mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
+ FakeMonitorReceiver monitor = FakeMonitorReceiver(*mDispatcher, "M_1", ADISPLAY_ID_DEFAULT);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
WINDOW_LOCATION));
mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
const std::optional<uint32_t> consumeSeq = monitor.receiveEvent();
ASSERT_TRUE(consumeSeq);
- mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(30ms, monitor.getToken(), MONITOR_PID);
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(SPY_TIMEOUT, monitor.getToken(),
+ MONITOR_PID);
monitor.finishEvent(*consumeSeq);
monitor.consumeMotionCancel(ADISPLAY_ID_DEFAULT);
@@ -5038,7 +8151,7 @@ TEST_F(InputDispatcherSingleWindowAnr, SameWindow_CanReceiveAnrTwice) {
// it.
TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
WINDOW_LOCATION));
const std::chrono::duration windowTimeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
@@ -5048,8 +8161,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());
@@ -5058,19 +8171,19 @@ TEST_F(InputDispatcherSingleWindowAnr, Policy_DoesNotGetDuplicateAnr) {
/**
* If a window is processing a motion event, and then a key event comes in, the key event should
- * not to to the focused window until the motion is processed.
+ * not get delivered to the focused window until the motion is processed.
*
* Warning!!!
* This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
* and the injection timeout that we specify when injecting the key.
- * We must have the injection timeout (10ms) be smaller than
+ * We must have the injection timeout (100ms) be smaller than
* KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
*
* If that value changes, this test should also change.
*/
TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) {
mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
tapOnWindow();
std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
@@ -5083,13 +8196,15 @@ 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,
- InputEventInjectionSync::WAIT_FOR_RESULT, 10ms);
+ injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0, ADISPLAY_ID_DEFAULT,
+ InputEventInjectionSync::WAIT_FOR_RESULT, 100ms);
ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, 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
- std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
- ASSERT_FALSE(keySequenceNum);
+ // Make sure that `assertNoEvents` doesn't wait too long, because it could cause an ANR.
+ // Rely here on the fact that it uses CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
+ static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
+ mWindow->assertNoEvents();
std::this_thread::sleep_for(500ms);
// if we wait long enough though, dispatcher will give up, and still send the key
@@ -5108,7 +8223,7 @@ TEST_F(InputDispatcherSingleWindowAnr, Key_StaysPendingWhileMotionIsProcessed) {
TEST_F(InputDispatcherSingleWindowAnr,
PendingKey_IsDroppedWhileMotionIsProcessedAndNewTouchComesIn) {
mWindow->setDispatchingTimeout(2s); // Set a long ANR timeout to prevent it from triggering
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
tapOnWindow();
std::optional<uint32_t> downSequenceNum = mWindow->receiveEvent();
@@ -5116,40 +8231,91 @@ TEST_F(InputDispatcherSingleWindowAnr,
std::optional<uint32_t> upSequenceNum = mWindow->receiveEvent();
ASSERT_TRUE(upSequenceNum);
// 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));
+ mDispatcher->notifyKey(
+ KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+ .policyFlags(DEFAULT_POLICY_FLAGS | POLICY_FLAG_DISABLE_KEY_REPEAT)
+ .build());
// At this point, key is still pending, and should not be sent to the application yet.
- std::optional<uint32_t> keySequenceNum = mWindow->receiveEvent();
- ASSERT_FALSE(keySequenceNum);
+ // Make sure the `assertNoEvents` check doesn't take too long. It uses
+ // CONSUME_TIMEOUT_NO_EVENT_EXPECTED under the hood.
+ static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
+ mWindow->assertNoEvents();
// Now tap down again. It should cause the pending key to go to the focused window right away.
tapOnWindow();
- mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT); // it doesn't matter that we haven't ack'd
- // the other events yet. We can finish events in any order.
+ mWindow->consumeKeyEvent(WithKeyAction(AKEY_EVENT_ACTION_DOWN)); // it doesn't matter that we
+ // haven't ack'd the other events yet. We can finish events in any order.
mWindow->finishEvent(*downSequenceNum); // first tap's ACTION_DOWN
mWindow->finishEvent(*upSequenceNum); // first tap's ACTION_UP
- mWindow->consumeMotionDown();
- mWindow->consumeMotionUp();
+ mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ mWindow->consumeMotionEvent(WithMotionAction(ACTION_UP));
mWindow->assertNoEvents();
}
+/**
+ * Send an event to the app and have the app not respond right away.
+ * When ANR is raised, policy will tell the dispatcher to cancel the events for that window.
+ * So InputDispatcher will enqueue ACTION_CANCEL event as well.
+ * At some point, the window becomes responsive again.
+ * Ensure that subsequent events get dropped, and the next gesture is delivered.
+ */
+TEST_F(InputDispatcherSingleWindowAnr, TwoGesturesWithAnr) {
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(10).y(10))
+ .build());
+
+ std::optional<uint32_t> sequenceNum = mWindow->receiveEvent(); // ACTION_DOWN
+ ASSERT_TRUE(sequenceNum);
+ const std::chrono::duration timeout = mWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
+ mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mWindow);
+
+ mWindow->finishEvent(*sequenceNum);
+ mWindow->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ mFakePolicy->assertNotifyWindowResponsiveWasCalled(mWindow->getToken(), mWindow->getPid());
+
+ // Now that the window is responsive, let's continue the gesture.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(3).y(3))
+ .build());
+
+ mDispatcher->notifyMotion(MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .pointer(PointerBuilder(1, ToolType::FINGER).x(3).y(3))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(11).y(11))
+ .build());
+ // We already canceled this pointer, so the window shouldn't get any new events.
+ mWindow->assertNoEvents();
+
+ // Start another one.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(15).y(15))
+ .build());
+ mWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+}
+
class InputDispatcherMultiWindowAnr : public InputDispatcherTest {
virtual void SetUp() override {
InputDispatcherTest::SetUp();
mApplication = std::make_shared<FakeApplicationHandle>();
- mApplication->setDispatchingTimeout(10ms);
- mUnfocusedWindow =
- new FakeWindowHandle(mApplication, mDispatcher, "Unfocused", ADISPLAY_ID_DEFAULT);
+ mApplication->setDispatchingTimeout(100ms);
+ mUnfocusedWindow = sp<FakeWindowHandle>::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->setDispatchingTimeout(30ms);
+ mFocusedWindow = sp<FakeWindowHandle>::make(mApplication, mDispatcher, "Focused",
+ ADISPLAY_ID_DEFAULT);
+ mFocusedWindow->setDispatchingTimeout(100ms);
mFocusedWindow->setFrame(Rect(50, 50, 100, 100));
// Set focused application.
@@ -5157,7 +8323,8 @@ class InputDispatcherMultiWindowAnr : public InputDispatcherTest {
mFocusedWindow->setFocusable(true);
// Expect one focus window exist in display.
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mFocusedWindow);
mFocusedWindow->consumeFocusEvent(true);
}
@@ -5184,10 +8351,10 @@ protected:
private:
void tap(const PointF& location) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
location));
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
location));
}
};
@@ -5196,28 +8363,44 @@ private:
// should be ANR'd first.
TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) {
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- FOCUSED_WINDOW_LOCATION))
- << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER)
+ .x(FOCUSED_WINDOW_LOCATION.x)
+ .y(FOCUSED_WINDOW_LOCATION.y))
+ .build()));
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_UP,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER)
+ .x(FOCUSED_WINDOW_LOCATION.x)
+ .y(FOCUSED_WINDOW_LOCATION.y))
+ .build()));
mFocusedWindow->consumeMotionDown();
- mUnfocusedWindow->consumeEvent(AINPUT_EVENT_TYPE_MOTION, AMOTION_EVENT_ACTION_OUTSIDE,
- ADISPLAY_ID_DEFAULT, 0 /*flags*/);
+ mFocusedWindow->consumeMotionUp();
+ 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();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- FOCUSED_WINDOW_LOCATION));
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(0, ToolType::FINGER)
+ .x(FOCUSED_WINDOW_LOCATION.x)
+ .y(FOCUSED_WINDOW_LOCATION.y))
+ .build()));
std::optional<uint32_t> unfocusedSequenceNum = mUnfocusedWindow->receiveEvent();
ASSERT_TRUE(unfocusedSequenceNum);
const std::chrono::duration timeout =
mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT);
mFakePolicy->assertNotifyWindowUnresponsiveWasCalled(timeout, mFocusedWindow);
- // Because we injected two DOWN events in a row, CANCEL is enqueued for the first event
- // sequence to make it consistent
- mFocusedWindow->consumeMotionCancel();
+
mUnfocusedWindow->finishEvent(*unfocusedSequenceNum);
mFocusedWindow->consumeMotionDown();
// This cancel is generated because the connection was unresponsive
@@ -5235,20 +8418,22 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsive) {
// But we should receive ANR for both.
TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout) {
// Set the timeout for unfocused window to match the focused window
- mUnfocusedWindow->setDispatchingTimeout(10ms);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+ mUnfocusedWindow->setDispatchingTimeout(
+ mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
+ mDispatcher->onWindowInfosChanged(
+ {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
tapOnFocusedWindow();
// we should have ACTION_DOWN/ACTION_UP on focused window and ACTION_OUTSIDE on unfocused window
- sp<IBinder> anrConnectionToken1, anrConnectionToken2;
- ASSERT_NO_FATAL_FAILURE(anrConnectionToken1 = mFakePolicy->getUnresponsiveWindowToken(10ms));
- ASSERT_NO_FATAL_FAILURE(anrConnectionToken2 = mFakePolicy->getUnresponsiveWindowToken(0ms));
-
// We don't know which window will ANR first. But both of them should happen eventually.
- ASSERT_TRUE(mFocusedWindow->getToken() == anrConnectionToken1 ||
- mFocusedWindow->getToken() == anrConnectionToken2);
- ASSERT_TRUE(mUnfocusedWindow->getToken() == anrConnectionToken1 ||
- mUnfocusedWindow->getToken() == anrConnectionToken2);
+ std::array<sp<IBinder>, 2> anrConnectionTokens = {mFakePolicy->getUnresponsiveWindowToken(
+ mFocusedWindow->getDispatchingTimeout(
+ DISPATCHING_TIMEOUT)),
+ mFakePolicy->getUnresponsiveWindowToken(0ms)};
+
+ ASSERT_THAT(anrConnectionTokens,
+ ::testing::UnorderedElementsAre(testing::Eq(mFocusedWindow->getToken()),
+ testing::Eq(mUnfocusedWindow->getToken())));
ASSERT_TRUE(mDispatcher->waitForIdle());
mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -5257,15 +8442,13 @@ TEST_F(InputDispatcherMultiWindowAnr, TwoWindows_BothUnresponsiveWithSameTimeout
mFocusedWindow->consumeMotionUp();
mUnfocusedWindow->consumeMotionOutside();
- sp<IBinder> responsiveToken1, responsiveToken2;
- ASSERT_NO_FATAL_FAILURE(responsiveToken1 = mFakePolicy->getResponsiveWindowToken());
- ASSERT_NO_FATAL_FAILURE(responsiveToken2 = mFakePolicy->getResponsiveWindowToken());
+ std::array<sp<IBinder>, 2> responsiveTokens = {mFakePolicy->getResponsiveWindowToken(),
+ mFakePolicy->getResponsiveWindowToken()};
// Both applications should be marked as responsive, in any order
- ASSERT_TRUE(mFocusedWindow->getToken() == responsiveToken1 ||
- mFocusedWindow->getToken() == responsiveToken2);
- ASSERT_TRUE(mUnfocusedWindow->getToken() == responsiveToken1 ||
- mUnfocusedWindow->getToken() == responsiveToken2);
+ ASSERT_THAT(responsiveTokens,
+ ::testing::UnorderedElementsAre(testing::Eq(mFocusedWindow->getToken()),
+ testing::Eq(mUnfocusedWindow->getToken())));
mFakePolicy->assertNotifyAnrWasNotCalled();
}
@@ -5274,8 +8457,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<uint32_t> downEventSequenceNum = mFocusedWindow->receiveEvent(); // ACTION_DOWN
ASSERT_TRUE(downEventSequenceNum);
@@ -5288,10 +8471,10 @@ TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
// Tap once again
// We cannot use "tapOnFocusedWindow" because it asserts the injection result to be success
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
FOCUSED_WINDOW_LOCATION));
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
FOCUSED_WINDOW_LOCATION));
// Unfocused window does not receive ACTION_OUTSIDE because the tapped window is not a
// valid touch target
@@ -5312,7 +8495,7 @@ TEST_F(InputDispatcherMultiWindowAnr, DuringAnr_SecondTapIsIgnored) {
// If you tap outside of all windows, there will not be ANR
TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) {
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
LOCATION_OUTSIDE_ALL_WINDOWS));
ASSERT_TRUE(mDispatcher->waitForIdle());
mFakePolicy->assertNotifyAnrWasNotCalled();
@@ -5321,10 +8504,11 @@ TEST_F(InputDispatcherMultiWindowAnr, TapOutsideAllWindows_DoesNotAnr) {
// Since the focused window is paused, tapping on it should not produce any events
TEST_F(InputDispatcherMultiWindowAnr, Window_CanBePaused) {
mFocusedWindow->setPaused(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mUnfocusedWindow, mFocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mUnfocusedWindow->getInfo(), *mFocusedWindow->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::FAILED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
FOCUSED_WINDOW_LOCATION));
std::this_thread::sleep_for(mFocusedWindow->getDispatchingTimeout(DISPATCHING_TIMEOUT));
@@ -5338,13 +8522,13 @@ TEST_F(InputDispatcherMultiWindowAnr, Window_CanBePaused) {
/**
* If a window is processing a motion event, and then a key event comes in, the key event should
- * not to to the focused window until the motion is processed.
+ * not get delivered to the focused window until the motion is processed.
* If a different window becomes focused at this time, the key should go to that window instead.
*
* Warning!!!
* This test depends on the value of android::inputdispatcher::KEY_WAITING_FOR_MOTION_TIMEOUT
* and the injection timeout that we specify when injecting the key.
- * We must have the injection timeout (10ms) be smaller than
+ * We must have the injection timeout (100ms) be smaller than
* KEY_WAITING_FOR_MOTION_TIMEOUT (currently 500ms).
*
* If that value changes, this test should also change.
@@ -5352,7 +8536,8 @@ TEST_F(InputDispatcherMultiWindowAnr, Window_CanBePaused) {
TEST_F(InputDispatcherMultiWindowAnr, PendingKey_GoesToNewlyFocusedWindow) {
// Set a long ANR timeout to prevent it from triggering
mFocusedWindow->setDispatchingTimeout(2s);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
tapOnUnfocusedWindow();
std::optional<uint32_t> downSequenceNum = mUnfocusedWindow->receiveEvent();
@@ -5364,18 +8549,21 @@ 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=*/100ms);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
// Key will not be sent to the window, yet, because the window is still processing events
- // and the key remains pending, waiting for the touch events to be processed
- std::optional<uint32_t> keySequenceNum = mFocusedWindow->receiveEvent();
- ASSERT_FALSE(keySequenceNum);
+ // and the key remains pending, waiting for the touch events to be processed.
+ // Make sure `assertNoEvents` doesn't take too long. It uses CONSUME_TIMEOUT_NO_EVENT_EXPECTED
+ // under the hood.
+ static_assert(CONSUME_TIMEOUT_NO_EVENT_EXPECTED < 100ms);
+ mFocusedWindow->assertNoEvents();
// Switch the focus to the "unfocused" window that we tapped. Expect the key to go there
mFocusedWindow->setFocusable(false);
mUnfocusedWindow->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mUnfocusedWindow);
// Focus events should precede the key events
@@ -5399,17 +8587,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);
@@ -5424,7 +8611,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<MotionEvent&>(*event);
if (motionEvent.getAction() == AMOTION_EVENT_ACTION_MOVE) {
mFocusedWindow->consumeMotionCancel();
@@ -5454,21 +8641,22 @@ TEST_F(InputDispatcherMultiWindowAnr, SplitTouch_SingleWindowAnr) {
TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_NoAnr) {
std::shared_ptr<FakeApplicationHandle> focusedApplication =
std::make_shared<FakeApplicationHandle>();
- focusedApplication->setDispatchingTimeout(60ms);
+ focusedApplication->setDispatchingTimeout(300ms);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, focusedApplication);
// The application that owns 'mFocusedWindow' and 'mUnfocusedWindow' is not focused.
mFocusedWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
mFocusedWindow->consumeFocusEvent(false);
// Send a key. The ANR timer should start because there is no focused window.
// '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=*/100ms,
+ /*allowKeyRepeat=*/false);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, result);
// Wait until dispatcher starts the "no focused window" timer. If we don't wait here,
@@ -5478,19 +8666,19 @@ TEST_F(InputDispatcherMultiWindowAnr, FocusedWindowWithoutSetFocusedApplication_
// simply be added to the queue without 'shouldPruneInboundQueueLocked' returning 'true'.
// For this test, it means that the key would get delivered to the window once it becomes
// focused.
- std::this_thread::sleep_for(10ms);
+ std::this_thread::sleep_for(100ms);
// 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.
// Set the focused window first.
mFocusedWindow->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mFocusedWindow, mUnfocusedWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mFocusedWindow->getInfo(), *mUnfocusedWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mFocusedWindow);
mFocusedWindow->consumeFocusEvent(true);
// We do not call "setFocusedApplication" here, even though the newly focused window belongs
@@ -5515,19 +8703,20 @@ class InputDispatcherMultiWindowOcclusionTests : public InputDispatcherTest {
InputDispatcherTest::SetUp();
mApplication = std::make_shared<FakeApplicationHandle>();
- mNoInputWindow = new FakeWindowHandle(mApplication, mDispatcher,
- "Window without input channel", ADISPLAY_ID_DEFAULT,
- std::make_optional<sp<IBinder>>(nullptr) /*token*/);
-
+ mNoInputWindow =
+ sp<FakeWindowHandle>::make(mApplication, mDispatcher,
+ "Window without input channel", ADISPLAY_ID_DEFAULT,
+ /*token=*/std::make_optional<sp<IBinder>>(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<FakeWindowHandle>::make(mApplication, mDispatcher, "Bottom window",
+ ADISPLAY_ID_DEFAULT);
mBottomWindow->setFrame(Rect(0, 0, 100, 100));
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mNoInputWindow->getInfo(), *mBottomWindow->getInfo()}, {}, 0, 0});
}
protected:
@@ -5539,10 +8728,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
@@ -5557,20 +8745,20 @@ 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<FakeWindowHandle>::make(mApplication, mDispatcher,
+ "Window with input channel and NO_INPUT_CHANNEL",
+ ADISPLAY_ID_DEFAULT);
mNoInputWindow->setNoInputChannel(true);
mNoInputWindow->setFrame(Rect(0, 0, 100, 100));
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mNoInputWindow, mBottomWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mNoInputWindow->getInfo(), *mBottomWindow->getInfo()}, {}, 0, 0});
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();
@@ -5585,13 +8773,13 @@ protected:
virtual void SetUp() override {
InputDispatcherTest::SetUp();
mApp = std::make_shared<FakeApplicationHandle>();
- mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
- mMirror = new FakeWindowHandle(mApp, mDispatcher, "TestWindowMirror", ADISPLAY_ID_DEFAULT,
- mWindow->getToken());
+ mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mMirror = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindowMirror",
+ ADISPLAY_ID_DEFAULT, mWindow->getToken());
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
mWindow->setFocusable(true);
mMirror->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
}
};
@@ -5601,7 +8789,7 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, CanGetFocus) {
// window gets focused
mWindow->consumeFocusEvent(true);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
}
@@ -5613,20 +8801,20 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAllWindowsFocusable) {
// window gets focused
mWindow->consumeFocusEvent(true);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
mMirror->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
// window loses focus since one of the windows associated with the token in not focusable
mWindow->consumeFocusEvent(false);
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::TIMED_OUT";
mWindow->assertNoEvents();
}
@@ -5638,30 +8826,30 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedIfAnyWindowVisible) {
// window gets focused
mWindow->consumeFocusEvent(true);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
mMirror->setVisible(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
mWindow->setVisible(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
// window loses focus only after all windows associated with the token become invisible.
mWindow->consumeFocusEvent(false);
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::TIMED_OUT";
mWindow->assertNoEvents();
}
@@ -5672,28 +8860,28 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, FocusedWhileWindowsAlive) {
// window gets focused
mWindow->consumeFocusEvent(true);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
// single window is removed but the window token remains focused
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mMirror->getInfo()}, {}, 0, 0});
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_NONE);
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyUp(ADISPLAY_ID_NONE);
// Both windows are removed
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {}}});
+ mDispatcher->onWindowInfosChanged({{}, {}, 0, 0});
mWindow->consumeFocusEvent(false);
- ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(mDispatcher))
+ ASSERT_EQ(InputEventInjectionResult::TIMED_OUT, injectKeyDown(*mDispatcher))
<< "Inject key event should return InputEventInjectionResult::TIMED_OUT";
mWindow->assertNoEvents();
}
@@ -5703,16 +8891,16 @@ TEST_F(InputDispatcherMirrorWindowFocusTests, DeferFocusWhenInvisible) {
// Request focus on an invisible mirror.
mWindow->setVisible(false);
mMirror->setVisible(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
setFocusedWindow(mMirror);
// Injected key goes to pending queue.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectKey(mDispatcher, AKEY_EVENT_ACTION_DOWN, 0 /* repeatCount */,
+ injectKey(*mDispatcher, AKEY_EVENT_ACTION_DOWN, /*repeatCount=*/0,
ADISPLAY_ID_DEFAULT, InputEventInjectionSync::NONE));
mMirror->setVisible(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mMirror}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mMirror->getInfo()}, {}, 0, 0});
// window gets focused
mWindow->consumeFocusEvent(true);
@@ -5729,21 +8917,22 @@ protected:
void SetUp() override {
InputDispatcherTest::SetUp();
mApp = std::make_shared<FakeApplicationHandle>();
- mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
mWindow->setFocusable(true);
- mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+ mSecondWindow =
+ sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
mSecondWindow->setFocusable(true);
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, mApp);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow, mSecondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
setFocusedWindow(mWindow);
mWindow->consumeFocusEvent(true);
}
void notifyPointerCaptureChanged(const PointerCaptureRequest& request) {
- const NotifyPointerCaptureChangedArgs args = generatePointerCaptureChangedArgs(request);
- mDispatcher->notifyPointerCaptureChanged(&args);
+ mDispatcher->notifyPointerCaptureChanged(generatePointerCaptureChangedArgs(request));
}
PointerCaptureRequest requestAndVerifyPointerCapture(const sp<FakeWindowHandle>& window,
@@ -5867,6 +9056,47 @@ TEST_F(InputDispatcherPointerCaptureTests, RapidToggleRequests) {
mWindow->assertNoEvents();
}
+/**
+ * One window. Hover mouse in the window, and then start capture. Make sure that the relative
+ * mouse movements don't affect the previous mouse hovering state.
+ * When pointer capture is enabled, the incoming events are always ACTION_MOVE (there are no
+ * HOVER_MOVE events).
+ */
+TEST_F(InputDispatcherPointerCaptureTests, MouseHoverAndPointerCapture) {
+ // Mouse hover on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+ .build());
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(100).y(110))
+ .build());
+
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_ENTER)));
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_HOVER_MOVE)));
+
+ // Start pointer capture
+ requestAndVerifyPointerCapture(mWindow, true);
+
+ // Send some relative mouse movements and receive them in the window.
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(10).y(11))
+ .build());
+ mWindow->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithCoords(10, 11),
+ WithSource(AINPUT_SOURCE_MOUSE_RELATIVE)));
+
+ // Stop pointer capture
+ requestAndVerifyPointerCapture(mWindow, false);
+
+ // Continue hovering on the window
+ mDispatcher->notifyMotion(MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(0, ToolType::MOUSE).x(105).y(115))
+ .build());
+ mWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_HOVER_MOVE), WithSource(AINPUT_SOURCE_MOUSE)));
+
+ mWindow->assertNoEvents();
+}
+
class InputDispatcherUntrustedTouchesTest : public InputDispatcherTest {
protected:
constexpr static const float MAXIMUM_OBSCURING_OPACITY = 0.8;
@@ -5883,16 +9113,15 @@ protected:
static_assert(1 - (1 - OPACITY_FAR_BELOW_THRESHOLD) * (1 - OPACITY_FAR_BELOW_THRESHOLD) <
MAXIMUM_OBSCURING_OPACITY);
- static const int32_t TOUCHED_APP_UID = 10001;
- static const int32_t APP_B_UID = 10002;
- static const int32_t APP_C_UID = 10003;
+ static constexpr gui::Uid TOUCHED_APP_UID{10001};
+ static constexpr gui::Uid APP_B_UID{10002};
+ static constexpr gui::Uid APP_C_UID{10003};
sp<FakeWindowHandle> mTouchWindow;
virtual void SetUp() override {
InputDispatcherTest::SetUp();
mTouchWindow = getWindow(TOUCHED_APP_UID, "Touched");
- mDispatcher->setBlockUntrustedTouchesMode(android::os::BlockUntrustedTouchesMode::BLOCK);
mDispatcher->setMaximumObscuringOpacityForTouch(MAXIMUM_OBSCURING_OPACITY);
}
@@ -5901,7 +9130,7 @@ protected:
mTouchWindow.clear();
}
- sp<FakeWindowHandle> getOccludingWindow(int32_t uid, std::string name, TouchOcclusionMode mode,
+ sp<FakeWindowHandle> getOccludingWindow(gui::Uid uid, std::string name, TouchOcclusionMode mode,
float alpha = 1.0f) {
sp<FakeWindowHandle> window = getWindow(uid, name);
window->setTouchable(false);
@@ -5910,27 +9139,26 @@ protected:
return window;
}
- sp<FakeWindowHandle> getWindow(int32_t uid, std::string name) {
+ sp<FakeWindowHandle> getWindow(gui::Uid uid, std::string name) {
std::shared_ptr<FakeApplicationHandle> app = std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(app, mDispatcher, name, ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(app, mDispatcher, name, ADISPLAY_ID_DEFAULT);
// Generate an arbitrary PID based on the UID
- window->setOwnerInfo(1777 + (uid % 10000), uid);
+ window->setOwnerInfo(gui::Pid{static_cast<pid_t>(1777 + (uid.val() % 10000))}, uid);
return window;
}
void touch(const std::vector<PointF>& 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));
}
};
TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithBlockUntrustedOcclusionMode_BlocksTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -5941,7 +9169,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
WindowWithBlockUntrustedOcclusionModeWithOpacityBelowThreshold_BlocksTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.7f);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -5952,7 +9180,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
WindowWithBlockUntrustedOcclusionMode_DoesNotReceiveTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -5961,7 +9189,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithAllowOcclusionMode_AllowsTouch) {
const sp<FakeWindowHandle>& w = getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::ALLOW);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -5972,7 +9200,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, TouchOutsideOccludingWindow_AllowsTo
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
w->setFrame(Rect(0, 0, 50, 50));
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch({PointF{100, 100}});
@@ -5982,7 +9210,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, TouchOutsideOccludingWindow_AllowsTo
TEST_F(InputDispatcherUntrustedTouchesTest, WindowFromSameUid_AllowsTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(TOUCHED_APP_UID, "A", TouchOcclusionMode::BLOCK_UNTRUSTED);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -5992,7 +9220,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowFromSameUid_AllowsTouch) {
TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_AllowsTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6002,7 +9230,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_AllowsTouch) {
TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithZeroOpacity_DoesNotReceiveTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6019,7 +9247,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
w->setWatchOutsideTouch(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6030,7 +9258,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, OutsideEvent_HasZeroCoordinates) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED, 0.0f);
w->setWatchOutsideTouch(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6041,7 +9269,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityBelowThreshold_Allo
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
OPACITY_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6052,7 +9280,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityAtThreshold_AllowsT
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
MAXIMUM_OBSCURING_OPACITY);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6063,7 +9291,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowWithOpacityAboveThreshold_Bloc
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
OPACITY_ABOVE_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6078,7 +9306,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowsWithCombinedOpacityAboveThres
const sp<FakeWindowHandle>& w2 =
getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY,
OPACITY_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*w1->getInfo(), *w2->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6093,7 +9322,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowsWithCombinedOpacityBelowThres
const sp<FakeWindowHandle>& w2 =
getOccludingWindow(APP_B_UID, "B2", TouchOcclusionMode::USE_OPACITY,
OPACITY_FAR_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*w1->getInfo(), *w2->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6108,7 +9338,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& wC =
getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
OPACITY_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*wB->getInfo(), *wC->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6122,7 +9353,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest, WindowsFromDifferentAppsOneAboveThre
const sp<FakeWindowHandle>& wC =
getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
OPACITY_ABOVE_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*wB->getInfo(), *wC->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6137,7 +9369,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& wB =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
OPACITY_ABOVE_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wA, wB, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*wA->getInfo(), *wB->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6152,7 +9385,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& wB =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
OPACITY_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wA, wB, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*wA->getInfo(), *wB->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6163,7 +9397,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, SelfWindowWithOpacityAboveThreshold_
const sp<FakeWindowHandle>& w =
getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::USE_OPACITY,
OPACITY_ABOVE_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6173,7 +9407,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, SelfWindowWithOpacityAboveThreshold_
TEST_F(InputDispatcherUntrustedTouchesTest, SelfWindowWithBlockUntrustedMode_AllowsTouch) {
const sp<FakeWindowHandle>& w =
getOccludingWindow(TOUCHED_APP_UID, "T", TouchOcclusionMode::BLOCK_UNTRUSTED);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6185,7 +9419,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
mDispatcher->setMaximumObscuringOpacityForTouch(0.0f);
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.1f);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6196,7 +9430,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest, OpacityThresholdIs0AndWindowAtThresh
mDispatcher->setMaximumObscuringOpacityForTouch(0.0f);
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY, 0.0f);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6209,7 +9443,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
OPACITY_ABOVE_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6224,7 +9458,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& w2 =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::USE_OPACITY,
OPACITY_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w1, w2, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*w1->getInfo(), *w2->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6244,7 +9479,8 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& wC =
getOccludingWindow(APP_C_UID, "C", TouchOcclusionMode::USE_OPACITY,
OPACITY_BELOW_THRESHOLD);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {wB, wC, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*wB->getInfo(), *wC->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6260,7 +9496,7 @@ TEST_F(InputDispatcherUntrustedTouchesTest,
const sp<FakeWindowHandle>& w =
getOccludingWindow(APP_B_UID, "B", TouchOcclusionMode::BLOCK_UNTRUSTED);
w->setApplicationToken(mTouchWindow->getApplicationToken());
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {w, mTouchWindow}}});
+ mDispatcher->onWindowInfosChanged({{*w->getInfo(), *mTouchWindow->getInfo()}, {}, 0, 0});
touch();
@@ -6273,53 +9509,65 @@ protected:
sp<FakeWindowHandle> mWindow;
sp<FakeWindowHandle> mSecondWindow;
sp<FakeWindowHandle> mDragWindow;
+ sp<FakeWindowHandle> 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<FakeApplicationHandle>();
- mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mWindow = sp<FakeWindowHandle>::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<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
mSecondWindow->setFrame(Rect(100, 0, 200, 100));
+ mSpyWindow =
+ sp<FakeWindowHandle>::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->onWindowInfosChanged(
+ {{*mSpyWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()},
+ {},
+ 0,
+ 0});
}
void injectDown(int fromSource = AINPUT_SOURCE_TOUCHSCREEN) {
switch (fromSource) {
case AINPUT_SOURCE_TOUCHSCREEN:
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
break;
case AINPUT_SOURCE_STYLUS:
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(
- mDispatcher,
- MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
- AINPUT_SOURCE_STYLUS)
- .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
- .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
- .x(50)
- .y(50))
- .build()));
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_STYLUS)
+ .buttonState(
+ AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+ .pointer(PointerBuilder(0, ToolType::STYLUS)
+ .x(50)
+ .y(50))
+ .build()));
break;
case AINPUT_SOURCE_MOUSE:
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(
- mDispatcher,
- MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
- .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
- .pointer(PointerBuilder(MOUSE_POINTER_ID,
- AMOTION_EVENT_TOOL_TYPE_MOUSE)
- .x(50)
- .y(50))
- .build()));
+ injectMotionEvent(*mDispatcher,
+ MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_MOUSE)
+ .buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID,
+ ToolType::MOUSE)
+ .x(50)
+ .y(50))
+ .build()));
break;
default:
FAIL() << "Source " << fromSource << " doesn't support drag and drop";
@@ -6327,6 +9575,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.
@@ -6338,14 +9588,19 @@ protected:
}
// The drag window covers the entire display
- mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
- mDispatcher->setInputWindows(
- {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});
+ mDragWindow =
+ sp<FakeWindowHandle>::make(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
+ mDragWindow->setTouchableRegion(Region{{0, 0, 0, 0}});
+ mDispatcher->onWindowInfosChanged({{*mDragWindow->getInfo(), *mSpyWindow->getInfo(),
+ *mWindow->getInfo(), *mSecondWindow->getInfo()},
+ {},
+ 0,
+ 0});
// 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();
@@ -6359,7 +9614,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
// Move on window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6368,7 +9623,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
// Move to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6377,7 +9632,7 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
// Move back to original window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6385,19 +9640,44 @@ TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
mSecondWindow->consumeDragEvent(true, -50, 50);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, {50, 50}))
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
mWindow->assertNoEvents();
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();
// Move on window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6406,7 +9686,7 @@ TEST_F(InputDispatcherDragTests, DragAndDrop) {
// Move to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6415,26 +9695,68 @@ TEST_F(InputDispatcherDragTests, DragAndDrop) {
// drop to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- mFakePolicy->assertDropTargetEquals(mSecondWindow->getToken());
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
}
+TEST_F(InputDispatcherDragTests, DragAndDropNotCancelledIfSomeOtherPointerIsPilfered) {
+ 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->consumeMotionEvent(WithMotionAction(ACTION_CANCEL));
+ mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_DOWN));
+ mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+
+ mSpyWindow->assertNoEvents();
+
+ // Spy window calls pilfer pointers
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(mSpyWindow->getToken()));
+ mDragWindow->assertNoEvents();
+
+ const MotionEvent firstFingerMoveEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(60).y(60))
+ .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";
+
+ // Drag window should still receive the new event
+ mDragWindow->consumeMotionEvent(WithMotionAction(ACTION_MOVE));
+ mDragWindow->assertNoEvents();
+}
+
TEST_F(InputDispatcherDragTests, StylusDragAndDrop) {
startDrag(true, AINPUT_SOURCE_STYLUS);
// Move on window and keep button pressed.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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);
@@ -6443,27 +9765,23 @@ TEST_F(InputDispatcherDragTests, StylusDragAndDrop) {
// Move to another window and release button, expect to drop item.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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);
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
- mFakePolicy->assertDropTargetEquals(mSecondWindow->getToken());
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
// nothing to the window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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);
@@ -6476,11 +9794,12 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) {
// Set second window invisible.
mSecondWindow->setVisible(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mDragWindow->getInfo(), *mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
// Move on window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6489,7 +9808,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) {
// Move to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6498,11 +9817,11 @@ TEST_F(InputDispatcherDragTests, DragAndDropOnInvalidWindow) {
// drop to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- mFakePolicy->assertDropTargetEquals(nullptr);
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, nullptr);
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
}
@@ -6512,7 +9831,7 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) {
mWindow->setPreventSplitting(true);
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -6521,14 +9840,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,
+ 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));
@@ -6537,7 +9856,7 @@ TEST_F(InputDispatcherDragTests, NoDragAndDropWhenMultiFingers) {
TEST_F(InputDispatcherDragTests, DragAndDropWhenSplitTouch) {
// First down on second window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
@@ -6548,12 +9867,11 @@ 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,
+ injectMotionEvent(*mDispatcher, secondFingerDownEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -6565,12 +9883,11 @@ 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,
+ injectMotionEvent(*mDispatcher, secondFingerMoveEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT));
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
mWindow->consumeDragEvent(false, 50, 50);
@@ -6580,15 +9897,14 @@ 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,
+ injectMotionEvent(*mDispatcher, secondFingerUpEvent, INJECT_EVENT_TIMEOUT,
InputEventInjectionSync::WAIT_FOR_RESULT));
mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- mFakePolicy->assertDropTargetEquals(mWindow->getToken());
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, mWindow->getToken());
mWindow->assertNoEvents();
mSecondWindow->consumeMotionMove();
}
@@ -6598,27 +9914,35 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) {
// Update window of second display.
sp<FakeWindowHandle> windowInSecondary =
- new FakeWindowHandle(mApp, mDispatcher, "D_2", SECOND_DISPLAY_ID);
- mDispatcher->setInputWindows({{SECOND_DISPLAY_ID, {windowInSecondary}}});
+ sp<FakeWindowHandle>::make(mApp, mDispatcher, "D_2", SECOND_DISPLAY_ID);
+ mDispatcher->onWindowInfosChanged(
+ {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
+ *mSecondWindow->getInfo(), *windowInSecondary->getInfo()},
+ {},
+ 0,
+ 0});
// Let second display has a touch state.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ 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))
+ .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}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*mDragWindow->getInfo(), *mSpyWindow->getInfo(), *mWindow->getInfo(),
+ *mSecondWindow->getInfo(), *windowInSecondary->getInfo()},
+ {},
+ 0,
+ 0});
// Move on window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {50, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6627,7 +9951,7 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) {
// Move to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
@@ -6636,11 +9960,11 @@ TEST_F(InputDispatcherDragTests, DragAndDropWhenMultiDisplays) {
// drop to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{150, 50}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- mFakePolicy->assertDropTargetEquals(mSecondWindow->getToken());
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
}
@@ -6649,11 +9973,10 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) {
startDrag(true, AINPUT_SOURCE_MOUSE);
// Move on window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
.buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
- .pointer(PointerBuilder(MOUSE_POINTER_ID,
- AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
.x(50)
.y(50))
.build()))
@@ -6664,11 +9987,10 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) {
// Move to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
.buttonState(AMOTION_EVENT_BUTTON_PRIMARY)
- .pointer(PointerBuilder(MOUSE_POINTER_ID,
- AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
.x(150)
.y(50))
.build()))
@@ -6679,56 +10001,136 @@ TEST_F(InputDispatcherDragTests, MouseDragAndDrop) {
// drop to another window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher,
+ injectMotionEvent(*mDispatcher,
MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_MOUSE)
.buttonState(0)
- .pointer(PointerBuilder(MOUSE_POINTER_ID,
- AMOTION_EVENT_TOOL_TYPE_MOUSE)
+ .pointer(PointerBuilder(MOUSE_POINTER_ID, ToolType::MOUSE)
.x(150)
.y(50))
.build()))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
- mFakePolicy->assertDropTargetEquals(mSecondWindow->getToken());
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, mSecondWindow->getToken());
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
}
+/**
+ * Start drag and drop with a pointer whose id is not 0, cancel the current touch, and ensure drag
+ * and drop is also canceled. Then inject a simple gesture, and ensure dispatcher does not crash.
+ */
+TEST_F(InputDispatcherDragTests, DragAndDropFinishedWhenCancelCurrentTouch) {
+ // Down on second window
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {150, 50}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+
+ ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionDown());
+ ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionDown());
+
+ // Down on first window
+ const MotionEvent secondFingerDownEvent =
+ MotionEventBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .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,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
+ ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionMove());
+ ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionPointerDown(1));
+
+ // Start drag on first window
+ ASSERT_TRUE(startDrag(/*sendDown=*/false, AINPUT_SOURCE_TOUCHSCREEN));
+
+ // Trigger cancel
+ mDispatcher->cancelCurrentTouch();
+ ASSERT_NO_FATAL_FAILURE(mSecondWindow->consumeMotionCancel());
+ ASSERT_NO_FATAL_FAILURE(mDragWindow->consumeMotionCancel());
+ ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionCancel());
+
+ ASSERT_TRUE(mDispatcher->waitForIdle());
+ // The D&D finished with nullptr
+ mFakePolicy->assertDropTargetEquals(*mDispatcher, nullptr);
+
+ // Remove drag window
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo(), *mSecondWindow->getInfo()}, {}, 0, 0});
+
+ // Inject a simple gesture, ensure dispatcher not crashed
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ PointF{50, 50}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionDown());
+
+ const MotionEvent moveEvent =
+ MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .displayId(ADISPLAY_ID_DEFAULT)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .build();
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher, moveEvent, INJECT_EVENT_TIMEOUT,
+ InputEventInjectionSync::WAIT_FOR_RESULT))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionMove());
+
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {50, 50}))
+ << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionUp());
+}
+
+TEST_F(InputDispatcherDragTests, NoDragAndDropWithHoveringPointer) {
+ // Start hovering over the window.
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectMotionEvent(*mDispatcher, ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE,
+ ADISPLAY_ID_DEFAULT, {50, 50}));
+
+ ASSERT_NO_FATAL_FAILURE(mWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
+ ASSERT_NO_FATAL_FAILURE(mSpyWindow->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER)));
+
+ ASSERT_FALSE(startDrag(/*sendDown=*/false))
+ << "Drag and drop should not work with a hovering pointer";
+}
+
class InputDispatcherDropInputFeatureTest : public InputDispatcherTest {};
TEST_F(InputDispatcherDropInputFeatureTest, WindowDropsInput) {
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::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}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
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));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN,
+ ADISPLAY_ID_DEFAULT));
+ mDispatcher->waitForIdle();
window->assertNoEvents();
// With the flag cleared, the window should get input
window->setDropInput(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
- 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();
}
@@ -6737,44 +10139,43 @@ TEST_F(InputDispatcherDropInputFeatureTest, ObscuredWindowDropsInput) {
std::shared_ptr<FakeApplicationHandle> obscuringApplication =
std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> obscuringWindow =
- new FakeWindowHandle(obscuringApplication, mDispatcher, "obscuringWindow",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(obscuringApplication, mDispatcher, "obscuringWindow",
+ ADISPLAY_ID_DEFAULT);
obscuringWindow->setFrame(Rect(0, 0, 50, 50));
- obscuringWindow->setOwnerInfo(111, 111);
+ obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
obscuringWindow->setTouchable(false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Test window", ADISPLAY_ID_DEFAULT);
window->setDropInputIfObscured(true);
- window->setOwnerInfo(222, 222);
+ window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
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));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, 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}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
- 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();
}
@@ -6783,43 +10184,42 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) {
std::shared_ptr<FakeApplicationHandle> obscuringApplication =
std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> obscuringWindow =
- new FakeWindowHandle(obscuringApplication, mDispatcher, "obscuringWindow",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(obscuringApplication, mDispatcher, "obscuringWindow",
+ ADISPLAY_ID_DEFAULT);
obscuringWindow->setFrame(Rect(0, 0, 50, 50));
- obscuringWindow->setOwnerInfo(111, 111);
+ obscuringWindow->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
obscuringWindow->setTouchable(false);
std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Test window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Test window", ADISPLAY_ID_DEFAULT);
window->setDropInputIfObscured(true);
- window->setOwnerInfo(222, 222);
+ window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
window->setFocusable(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {obscuringWindow, window}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*obscuringWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
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));
+ mDispatcher->notifyMotion(generateMotionArgs(AMOTION_EVENT_ACTION_UP, 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}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*window->getInfo(), *obscuringWindow->getInfo()}, {}, 0, 0});
- 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();
}
@@ -6827,88 +10227,127 @@ TEST_F(InputDispatcherDropInputFeatureTest, UnobscuredWindowGetsInput) {
class InputDispatcherTouchModeChangedTests : public InputDispatcherTest {
protected:
std::shared_ptr<FakeApplicationHandle> mApp;
+ std::shared_ptr<FakeApplicationHandle> mSecondaryApp;
sp<FakeWindowHandle> mWindow;
sp<FakeWindowHandle> mSecondWindow;
+ sp<FakeWindowHandle> mThirdWindow;
void SetUp() override {
InputDispatcherTest::SetUp();
mApp = std::make_shared<FakeApplicationHandle>();
- mWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
+ mSecondaryApp = std::make_shared<FakeApplicationHandle>();
+ mWindow = sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow", ADISPLAY_ID_DEFAULT);
mWindow->setFocusable(true);
setFocusedWindow(mWindow);
- mSecondWindow = new FakeWindowHandle(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
+ mSecondWindow =
+ sp<FakeWindowHandle>::make(mApp, mDispatcher, "TestWindow2", ADISPLAY_ID_DEFAULT);
mSecondWindow->setFocusable(true);
+ mThirdWindow =
+ sp<FakeWindowHandle>::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->onWindowInfosChanged(
+ {{*mWindow->getInfo(), *mSecondWindow->getInfo(), *mThirdWindow->getInfo()},
+ {},
+ 0,
+ 0});
+ 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, gui::Pid pid, gui::Uid 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) {
const WindowInfo& windowInfo = *mWindow->getInfo();
- int32_t ownerPid = windowInfo.ownerPid;
- int32_t ownerUid = windowInfo.ownerUid;
- mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
+ gui::Pid ownerPid = windowInfo.ownerPid;
+ gui::Uid ownerUid = windowInfo.ownerUid;
+ mWindow->setOwnerInfo(gui::Pid::INVALID, gui::Uid::INVALID);
ASSERT_FALSE(mDispatcher->setInTouchMode(InputDispatcher::kDefaultInTouchMode, ownerPid,
- ownerUid, /* hasPermission */ false));
+ ownerUid, /*hasPermission=*/false,
+ ADISPLAY_ID_DEFAULT));
mWindow->assertNoEvents();
mSecondWindow->assertNoEvents();
}
TEST_F(InputDispatcherTouchModeChangedTests, NonWindowOwnerMayChangeTouchModeOnPermissionGranted) {
const WindowInfo& windowInfo = *mWindow->getInfo();
- int32_t ownerPid = windowInfo.ownerPid;
- int32_t ownerUid = windowInfo.ownerUid;
- mWindow->setOwnerInfo(/* pid */ -1, /* uid */ -1);
- changeAndVerifyTouchMode(!InputDispatcher::kDefaultInTouchMode, ownerPid, ownerUid,
- /* hasPermission */ true);
+ gui::Pid ownerPid = windowInfo.ownerPid;
+ gui::Uid ownerUid = windowInfo.ownerUid;
+ mWindow->setOwnerInfo(gui::Pid::INVALID, gui::Uid::INVALID);
+ 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) {
// Interact with the window first.
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher, ADISPLAY_ID_DEFAULT))
+ ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+ injectKeyDown(*mDispatcher, ADISPLAY_ID_DEFAULT))
<< "Inject key event should return InputEventInjectionResult::SUCCEEDED";
mWindow->consumeKeyDown(ADISPLAY_ID_DEFAULT);
// Then remove focus.
mWindow->setFocusable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {mWindow}}});
+ mDispatcher->onWindowInfosChanged({{*mWindow->getInfo()}, {}, 0, 0});
// Assert that caller can switch touch mode by owning one of the last interacted window.
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 {
@@ -6918,8 +10357,8 @@ public:
std::make_shared<FakeApplicationHandle>();
std::string name = "Fake Spy ";
name += std::to_string(mSpyCount++);
- sp<FakeWindowHandle> spy =
- new FakeWindowHandle(application, mDispatcher, name.c_str(), ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> spy = sp<FakeWindowHandle>::make(application, mDispatcher,
+ name.c_str(), ADISPLAY_ID_DEFAULT);
spy->setSpy(true);
spy->setTrustedOverlay(true);
return spy;
@@ -6929,7 +10368,8 @@ public:
std::shared_ptr<FakeApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Fake Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Fake Window",
+ ADISPLAY_ID_DEFAULT);
window->setFocusable(true);
return window;
}
@@ -6943,11 +10383,12 @@ using InputDispatcherSpyWindowDeathTest = InputDispatcherSpyWindowTest;
* Adding a spy window that is not a trusted overlay causes Dispatcher to abort.
*/
TEST_F(InputDispatcherSpyWindowDeathTest, UntrustedSpy_AbortsDispatcher) {
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
ScopedSilentDeath _silentDeath;
auto spy = createSpy();
spy->setTrustedOverlay(false);
- ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}}),
+ ASSERT_DEATH(mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0}),
".* not a trusted overlay");
}
@@ -6956,10 +10397,10 @@ TEST_F(InputDispatcherSpyWindowDeathTest, UntrustedSpy_AbortsDispatcher) {
*/
TEST_F(InputDispatcherSpyWindowTest, NoForegroundWindow) {
auto spy = createSpy();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
}
@@ -6981,7 +10422,8 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) {
auto spy1 = createSpy();
auto spy2 = createSpy();
auto spy3 = createSpy();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window, spy3}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo(), *spy3->getInfo()}, {}, 0, 0});
const std::vector<sp<FakeWindowHandle>> channels{spy1, spy2, window, spy3};
const size_t numChannels = channels.size();
@@ -6998,7 +10440,7 @@ TEST_F(InputDispatcherSpyWindowTest, ReceivesInputInOrder) {
}
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
std::vector<size_t> eventOrder;
@@ -7013,8 +10455,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<uint32_t>(EPOLLIN), events[i].events);
+ eventOrder.push_back(static_cast<size_t>(events[i].data.u64));
channels[i]->consumeMotionDown();
}
}
@@ -7033,10 +10475,10 @@ TEST_F(InputDispatcherSpyWindowTest, NotTouchable) {
auto window = createForeground();
auto spy = createSpy();
spy->setTouchable(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
spy->assertNoEvents();
@@ -7051,23 +10493,23 @@ TEST_F(InputDispatcherSpyWindowTest, TouchableRegion) {
auto window = createForeground();
auto spy = createSpy();
spy->setTouchableRegion(Region{{0, 0, 20, 20}});
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
// Inject an event outside the spy window's touchable region.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown();
spy->assertNoEvents();
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionUp();
spy->assertNoEvents();
// Inject an event inside the spy window's touchable region.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{5, 10}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown();
@@ -7080,16 +10522,16 @@ TEST_F(InputDispatcherSpyWindowTest, TouchableRegion) {
*/
TEST_F(InputDispatcherSpyWindowTest, WatchOutsideTouches) {
auto window = createForeground();
- window->setOwnerInfo(12, 34);
+ window->setOwnerInfo(gui::Pid{12}, gui::Uid{34});
auto spy = createSpy();
spy->setWatchOutsideTouch(true);
- spy->setOwnerInfo(56, 78);
+ spy->setOwnerInfo(gui::Pid{56}, gui::Uid{78});
spy->setFrame(Rect{0, 0, 20, 20});
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
// Inject an event outside the spy window's frame and touchable region.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{100, 200}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown();
@@ -7097,17 +10539,151 @@ 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->onWindowInfosChanged(
+ {{*spy->getInfo(), *windowLeft->getInfo(), *windowRight->getInfo()}, {}, 0, 0});
+
+ 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, PilferPointers) {
+TEST_F(InputDispatcherSpyWindowTest, ReceivesSecondPointerAsDown) {
+ auto window = createForeground();
+ window->setFrame({0, 0, 200, 200});
+ auto spyRight = createSpy();
+ spyRight->setFrame({100, 0, 200, 200});
+ mDispatcher->onWindowInfosChanged({{*spyRight->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ 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->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ // 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->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ 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(InputDispatcherPilferPointersTest, PilferPointers) {
auto window = createForeground();
auto spy1 = createSpy();
auto spy2 = createSpy();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy1, spy2, window}}});
+ mDispatcher->onWindowInfosChanged(
+ {{*spy1->getInfo(), *spy2->getInfo(), *window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown();
spy1->consumeMotionDown();
@@ -7121,7 +10697,7 @@ TEST_F(InputDispatcherSpyWindowTest, PilferPointers) {
// The rest of the gesture should only be sent to the second spy window.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
spy2->consumeMotionMove();
@@ -7133,13 +10709,13 @@ 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}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
window->consumeMotionDown(ADISPLAY_ID_DEFAULT);
spy->consumeMotionDown(ADISPLAY_ID_DEFAULT);
@@ -7149,7 +10725,7 @@ TEST_F(InputDispatcherSpyWindowTest, CanPilferAfterWindowIsRemovedMidStream) {
EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionUp(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
+ injectMotionUp(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
spy->consumeMotionUp(ADISPLAY_ID_DEFAULT);
}
@@ -7158,15 +10734,15 @@ 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();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
// First finger down on the window and the spy.
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
{100, 200}))
<< "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
spy->consumeMotionDown();
@@ -7181,170 +10757,263 @@ 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,
+ 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,
+ 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->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ // First finger down on the window only
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {50, 50}))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {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,
+ 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->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+ // First finger down on both spy and window
ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionDown(mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
- {50, 50}))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {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,
+ 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}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
- // 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}))
+ injectMotionDown(*mDispatcher, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ {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,
+ 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 */);
+ // TODO(b/232530217): do not send the unnecessary MOVE event and delete the next line
+ spy->consumeMotionMove();
+ spy->assertNoEvents();
}
/**
- * A spy window will usually be implemented as an un-focusable window. Verify that these windows
- * do not receive key events.
+ * A window on the left and a window on the right. Also, a spy window that's above all of the
+ * windows, and spanning both left and right windows.
+ * Send simultaneous motion streams from two different devices, one to the left window, and another
+ * to the right window.
+ * Pilfer from spy window.
+ * Check that the pilfering only affects the pointers that are actually being received by the spy.
*/
-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);
+TEST_F(InputDispatcherPilferPointersTest, MultiDevicePilfer) {
+ sp<FakeWindowHandle> spy = createSpy();
+ spy->setFrame(Rect(0, 0, 200, 200));
+ sp<FakeWindowHandle> leftWindow = createForeground();
+ leftWindow->setFrame(Rect(0, 0, 100, 100));
+
+ sp<FakeWindowHandle> rightWindow = createForeground();
+ rightWindow->setFrame(Rect(100, 0, 200, 100));
+
+ constexpr int32_t stylusDeviceId = 1;
+ constexpr int32_t touchDeviceId = 2;
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *leftWindow->getInfo(), *rightWindow->getInfo()}, {}, 0, 0});
+
+ // Stylus down on left window and spy
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(50).y(50))
+ .build());
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+ spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(stylusDeviceId)));
+
+ // Finger down on right window and spy - but spy already has stylus
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(150).y(50))
+ .build());
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_DOWN), WithDeviceId(touchDeviceId)));
+ spy->assertNoEvents();
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher))
- << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeKeyDown(ADISPLAY_ID_NONE);
+ // Act: pilfer from spy. Spy is currently receiving touch events.
+ EXPECT_EQ(OK, mDispatcher->pilferPointers(spy->getToken()));
+ leftWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(stylusDeviceId)));
+ rightWindow->consumeMotionEvent(
+ AllOf(WithMotionAction(ACTION_CANCEL), WithDeviceId(touchDeviceId)));
+
+ // Continue movements from both stylus and touch. Touch will be delivered to spy, but not stylus
+ mDispatcher->notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+ .deviceId(stylusDeviceId)
+ .pointer(PointerBuilder(0, ToolType::STYLUS).x(51).y(52))
+ .build());
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN)
+ .deviceId(touchDeviceId)
+ .pointer(PointerBuilder(0, ToolType::FINGER).x(151).y(52))
+ .build());
+ spy->consumeMotionEvent(AllOf(WithMotionAction(ACTION_MOVE), WithDeviceId(stylusDeviceId)));
- ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyUp(mDispatcher))
- << "Inject key event should return InputEventInjectionResult::SUCCEEDED";
- window->consumeKeyUp(ADISPLAY_ID_NONE);
+ spy->assertNoEvents();
+ leftWindow->assertNoEvents();
+ rightWindow->assertNoEvents();
+}
+TEST_F(InputDispatcherPilferPointersTest, NoPilferingWithHoveringPointers) {
+ auto window = createForeground();
+ auto spy = createSpy();
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
+
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .deviceId(1)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(100).y(200))
+ .build());
+ window->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ // Pilfer pointers from the spy window should fail.
+ EXPECT_NE(OK, mDispatcher->pilferPointers(spy->getToken()));
spy->assertNoEvents();
+ window->assertNoEvents();
}
class InputDispatcherStylusInterceptorTest : public InputDispatcherTest {
@@ -7353,10 +11022,10 @@ public:
std::shared_ptr<FakeApplicationHandle> overlayApplication =
std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> overlay =
- new FakeWindowHandle(overlayApplication, mDispatcher, "Stylus interceptor window",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
+ "Stylus interceptor window", ADISPLAY_ID_DEFAULT);
overlay->setFocusable(false);
- overlay->setOwnerInfo(111, 111);
+ overlay->setOwnerInfo(gui::Pid{111}, gui::Uid{111});
overlay->setTouchable(false);
overlay->setInterceptsStylus(true);
overlay->setTrustedOverlay(true);
@@ -7364,49 +11033,50 @@ public:
std::shared_ptr<FakeApplicationHandle> application =
std::make_shared<FakeApplicationHandle>();
sp<FakeWindowHandle> window =
- new FakeWindowHandle(application, mDispatcher, "Application window",
- ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Application window",
+ ADISPLAY_ID_DEFAULT);
window->setFocusable(true);
- window->setOwnerInfo(222, 222);
+ window->setOwnerInfo(gui::Pid{222}, gui::Uid{222});
mDispatcher->setFocusedApplication(ADISPLAY_ID_DEFAULT, application);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+ mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
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);
}
};
using InputDispatcherStylusInterceptorDeathTest = InputDispatcherStylusInterceptorTest;
TEST_F(InputDispatcherStylusInterceptorDeathTest, UntrustedOverlay_AbortsDispatcher) {
+ testing::GTEST_FLAG(death_test_style) = "threadsafe";
ScopedSilentDeath _silentDeath;
auto [overlay, window] = setupStylusOverlayScenario();
overlay->setTrustedOverlay(false);
// Configuring an untrusted overlay as a stylus interceptor should cause Dispatcher to abort.
- ASSERT_DEATH(mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}}),
+ ASSERT_DEATH(mDispatcher->onWindowInfosChanged(
+ {{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0}),
".* not a trusted overlay");
}
TEST_F(InputDispatcherStylusInterceptorTest, ConsmesOnlyStylusEvents) {
auto [overlay, window] = setupStylusOverlayScenario();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+ mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
overlay->consumeMotionDown();
@@ -7425,7 +11095,7 @@ TEST_F(InputDispatcherStylusInterceptorTest, ConsmesOnlyStylusEvents) {
TEST_F(InputDispatcherStylusInterceptorTest, SpyWindowStylusInterceptor) {
auto [overlay, window] = setupStylusOverlayScenario();
overlay->setSpy(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+ mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
overlay->consumeMotionDown();
@@ -7454,7 +11124,7 @@ TEST_F(InputDispatcherStylusInterceptorTest, SpyWindowStylusInterceptor) {
TEST_F(InputDispatcherStylusInterceptorTest, StylusHandwritingScenario) {
auto [overlay, window] = setupStylusOverlayScenario();
overlay->setSpy(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+ mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
sendStylusEvent(AMOTION_EVENT_ACTION_DOWN);
overlay->consumeMotionDown();
@@ -7466,7 +11136,7 @@ TEST_F(InputDispatcherStylusInterceptorTest, StylusHandwritingScenario) {
// The interceptor configures itself so that it is no longer a spy.
overlay->setSpy(false);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {overlay, window}}});
+ mDispatcher->onWindowInfosChanged({{*overlay->getInfo(), *window->getInfo()}, {}, 0, 0});
// It continues to receive the rest of the stylus gesture.
sendStylusEvent(AMOTION_EVENT_ACTION_MOVE);
@@ -7478,16 +11148,16 @@ TEST_F(InputDispatcherStylusInterceptorTest, StylusHandwritingScenario) {
}
struct User {
- int32_t mPid;
- int32_t mUid;
+ gui::Pid mPid;
+ gui::Uid mUid;
uint32_t mPolicyFlags{DEFAULT_POLICY_FLAGS};
std::unique_ptr<InputDispatcher>& mDispatcher;
- User(std::unique_ptr<InputDispatcher>& dispatcher, int32_t pid, int32_t uid)
+ User(std::unique_ptr<InputDispatcher>& dispatcher, gui::Pid pid, gui::Uid uid)
: mPid(pid), mUid(uid), mDispatcher(dispatcher) {}
InputEventInjectionResult injectTargetedMotion(int32_t action) const {
- return injectMotionEvent(mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
+ return injectMotionEvent(*mDispatcher, action, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, {100, 200},
{AMOTION_EVENT_INVALID_CURSOR_POSITION,
AMOTION_EVENT_INVALID_CURSOR_POSITION},
@@ -7496,17 +11166,17 @@ 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<FakeWindowHandle> createWindow() const {
+ sp<FakeWindowHandle> createWindow(const char* name) const {
std::shared_ptr<FakeApplicationHandle> overlayApplication =
std::make_shared<FakeApplicationHandle>();
- sp<FakeWindowHandle> window = new FakeWindowHandle(overlayApplication, mDispatcher,
- "Owned Window", ADISPLAY_ID_DEFAULT);
+ sp<FakeWindowHandle> window = sp<FakeWindowHandle>::make(overlayApplication, mDispatcher,
+ name, ADISPLAY_ID_DEFAULT);
window->setOwnerInfo(mPid, mUid);
return window;
}
@@ -7515,9 +11185,9 @@ struct User {
using InputDispatcherTargetedInjectionTest = InputDispatcherTest;
TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) {
- auto owner = User(mDispatcher, 10, 11);
- auto window = owner.createWindow();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
+ auto window = owner.createWindow("Owned window");
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
@@ -7532,11 +11202,11 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedWindow) {
}
TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) {
- auto owner = User(mDispatcher, 10, 11);
- auto window = owner.createWindow();
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {window}}});
+ auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
+ auto window = owner.createWindow("Owned window");
+ mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0});
- auto rando = User(mDispatcher, 20, 21);
+ auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
EXPECT_EQ(InputEventInjectionResult::TARGET_MISMATCH,
rando.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
@@ -7549,12 +11219,12 @@ TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedWindow) {
}
TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedSpyWindow) {
- auto owner = User(mDispatcher, 10, 11);
- auto window = owner.createWindow();
- auto spy = owner.createWindow();
+ auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
+ auto window = owner.createWindow("Owned window");
+ auto spy = owner.createWindow("Owned spy");
spy->setSpy(true);
spy->setTrustedOverlay(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {spy, window}}});
+ mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0});
EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
@@ -7563,14 +11233,14 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoOwnedSpyWindow) {
}
TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedSpyWindow) {
- auto owner = User(mDispatcher, 10, 11);
- auto window = owner.createWindow();
+ auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
+ auto window = owner.createWindow("Owned window");
- auto rando = User(mDispatcher, 20, 21);
- auto randosSpy = rando.createWindow();
+ auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
+ auto randosSpy = rando.createWindow("Rando's spy");
randosSpy->setSpy(true);
randosSpy->setTrustedOverlay(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}});
+ mDispatcher->onWindowInfosChanged({{*randosSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
// The event is targeted at owner's window, so injection should succeed, but the spy should
// not receive the event.
@@ -7581,18 +11251,18 @@ TEST_F(InputDispatcherTargetedInjectionTest, CannotInjectIntoUnownedSpyWindow) {
}
TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTargeting) {
- auto owner = User(mDispatcher, 10, 11);
- auto window = owner.createWindow();
+ auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
+ auto window = owner.createWindow("Owned window");
- auto rando = User(mDispatcher, 20, 21);
- auto randosSpy = rando.createWindow();
+ auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
+ auto randosSpy = rando.createWindow("Rando's spy");
randosSpy->setSpy(true);
randosSpy->setTrustedOverlay(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosSpy, window}}});
+ mDispatcher->onWindowInfosChanged({{*randosSpy->getInfo(), *window->getInfo()}, {}, 0, 0});
// A user that has injection permission can inject into any window.
EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
- injectMotionEvent(mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
+ injectMotionEvent(*mDispatcher, AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT));
randosSpy->consumeMotionDown();
window->consumeMotionDown();
@@ -7600,26 +11270,265 @@ TEST_F(InputDispatcherTargetedInjectionTest, CanInjectIntoAnyWindowWhenNotTarget
setFocusedWindow(randosSpy);
randosSpy->consumeFocusEvent(true);
- EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(mDispatcher));
+ EXPECT_EQ(InputEventInjectionResult::SUCCEEDED, injectKeyDown(*mDispatcher));
randosSpy->consumeKeyDown(ADISPLAY_ID_NONE);
window->assertNoEvents();
}
-TEST_F(InputDispatcherTargetedInjectionTest, CanGenerateActionOutsideToOtherUids) {
- auto owner = User(mDispatcher, 10, 11);
- auto window = owner.createWindow();
+TEST_F(InputDispatcherTargetedInjectionTest, CannotGenerateActionOutsideToOtherUids) {
+ auto owner = User(mDispatcher, gui::Pid{10}, gui::Uid{11});
+ auto window = owner.createWindow("Owned window");
- auto rando = User(mDispatcher, 20, 21);
- auto randosWindow = rando.createWindow();
+ auto rando = User(mDispatcher, gui::Pid{20}, gui::Uid{21});
+ auto randosWindow = rando.createWindow("Rando's window");
randosWindow->setFrame(Rect{-10, -10, -5, -5});
randosWindow->setWatchOutsideTouch(true);
- mDispatcher->setInputWindows({{ADISPLAY_ID_DEFAULT, {randosWindow, window}}});
+ mDispatcher->onWindowInfosChanged({{*randosWindow->getInfo(), *window->getInfo()}, {}, 0, 0});
- // We allow generation of ACTION_OUTSIDE events into windows owned by different uids.
+ // Do not allow generation of ACTION_OUTSIDE events into windows owned by different uids.
EXPECT_EQ(InputEventInjectionResult::SUCCEEDED,
owner.injectTargetedMotion(AMOTION_EVENT_ACTION_DOWN));
window->consumeMotionDown();
- randosWindow->consumeMotionOutside();
+ randosWindow->assertNoEvents();
+}
+
+using InputDispatcherPointerInWindowTest = InputDispatcherTest;
+
+TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWhenHovering) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+ sp<FakeWindowHandle> spy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+ spy->setFrame(Rect(0, 0, 200, 100));
+ spy->setTrustedOverlay(true);
+ spy->setSpy(true);
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ // Hover into the left window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(50).y(50))
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Hover move to the right window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(50))
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_MOVE));
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Stop hovering.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(50))
+ .build());
+
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ spy->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherPointerInWindowTest, PointerInWindowWithSplitTouch) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+ sp<FakeWindowHandle> spy =
+ sp<FakeWindowHandle>::make(application, mDispatcher, "Spy Window", ADISPLAY_ID_DEFAULT);
+ spy->setFrame(Rect(0, 0, 200, 100));
+ spy->setTrustedOverlay(true);
+ spy->setSpy(true);
+
+ mDispatcher->onWindowInfosChanged(
+ {{*spy->getInfo(), *left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ // First pointer down on left window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .build());
+
+ left->consumeMotionDown();
+ spy->consumeMotionDown();
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Second pointer down on right window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(POINTER_1_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
+ .build());
+
+ left->consumeMotionMove();
+ right->consumeMotionDown();
+ spy->consumeMotionEvent(WithMotionAction(POINTER_1_DOWN));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+
+ // Second pointer up.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(POINTER_1_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(150).y(50))
+ .build());
+
+ left->consumeMotionMove();
+ right->consumeMotionUp();
+ spy->consumeMotionEvent(WithMotionAction(POINTER_1_UP));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/1));
+
+ // First pointer up.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(50).y(50))
+ .build());
+
+ left->consumeMotionUp();
+ spy->consumeMotionUp();
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(spy->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+}
+
+TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+
+ sp<FakeWindowHandle> left = sp<FakeWindowHandle>::make(application, mDispatcher, "Left Window",
+ ADISPLAY_ID_DEFAULT);
+ left->setFrame(Rect(0, 0, 100, 100));
+ sp<FakeWindowHandle> right = sp<FakeWindowHandle>::make(application, mDispatcher,
+ "Right Window", ADISPLAY_ID_DEFAULT);
+ right->setFrame(Rect(100, 0, 200, 100));
+
+ mDispatcher->onWindowInfosChanged({{*left->getInfo(), *right->getInfo()}, {}, 0, 0});
+
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(right->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Hover move into the window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(50).y(50))
+ .rawXCursorPosition(50)
+ .rawYCursorPosition(50)
+ .deviceId(DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Move the mouse with another device. This cancels the hovering pointer from the first device.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(51).y(50))
+ .rawXCursorPosition(51)
+ .rawYCursorPosition(50)
+ .deviceId(SECOND_DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+
+ // TODO(b/313689709): InputDispatcher's touch state is not updated, even though the window gets
+ // a HOVER_EXIT from the first device.
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+ SECOND_DEVICE_ID,
+ /*pointerId=*/0));
+
+ // Move the mouse outside the window. Document the current behavior, where the window does not
+ // receive HOVER_EXIT even though the mouse left the window.
+ mDispatcher->notifyMotion(
+ MotionArgsBuilder(ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE).x(150).y(50))
+ .rawXCursorPosition(150)
+ .rawYCursorPosition(50)
+ .deviceId(SECOND_DEVICE_ID)
+ .build());
+
+ left->consumeMotionEvent(WithMotionAction(ACTION_HOVER_EXIT));
+ right->consumeMotionEvent(WithMotionAction(ACTION_HOVER_ENTER));
+ ASSERT_TRUE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT, DEVICE_ID,
+ /*pointerId=*/0));
+ ASSERT_FALSE(mDispatcher->isPointerInWindow(left->getToken(), ADISPLAY_ID_DEFAULT,
+ SECOND_DEVICE_ID,
+ /*pointerId=*/0));
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InputFlingerService_test.cpp b/services/inputflinger/tests/InputFlingerService_test.cpp
deleted file mode 100644
index 454e531af6..0000000000
--- 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 <BnInputFlingerQuery.h>
-#include <IInputFlingerQuery.h>
-
-#include <android/os/BnInputFlinger.h>
-#include <android/os/IInputFlinger.h>
-
-#include <binder/Binder.h>
-#include <binder/IPCThreadState.h>
-#include <binder/IServiceManager.h>
-#include <binder/Parcel.h>
-#include <binder/ProcessState.h>
-
-#include <input/Input.h>
-#include <input/InputTransport.h>
-
-#include <gtest/gtest.h>
-#include <inttypes.h>
-#include <linux/uinput.h>
-#include <log/log.h>
-#include <ui/Rect.h>
-#include <ui/Region.h>
-#include <chrono>
-#include <thread>
-#include <unordered_map>
-
-#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<IInputFlinger> mService;
- sp<IInputFlingerQuery> mQuery;
-
-private:
- std::unique_ptr<InputChannel> 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<String16>& args) override;
-
- binder::Status createInputChannel(const std::string& name, InputChannel* outChannel) override;
- binder::Status removeInputChannel(const sp<IBinder>& connectionToken) override;
- binder::Status setFocusedWindow(const FocusRequest&) override;
-
- void reset();
-
-private:
- mutable Mutex mLock;
- std::vector<std::shared_ptr<InputChannel>> mInputChannels;
-};
-
-class TestInputQuery : public BnInputFlingerQuery {
-public:
- TestInputQuery(sp<android::TestInputManager> manager) : mManager(manager){};
- binder::Status getInputChannels(std::vector<::android::InputChannel>* channels) override;
- binder::Status resetInputManager() override;
-
-private:
- sp<android::TestInputManager> 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<InputChannel> serverChannel;
- std::unique_ptr<InputChannel> 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<IBinder>& connectionToken) {
- AutoMutex _l(mLock);
-
- auto it = std::find_if(mInputChannels.begin(), mInputChannels.end(),
- [&](std::shared_ptr<InputChannel>& c) {
- return c->getConnectionToken() == connectionToken;
- });
- if (it != mInputChannels.end()) {
- mInputChannels.erase(it);
- }
-
- return binder::Status::ok();
-}
-
-status_t TestInputManager::dump(int fd, const Vector<String16>& 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<InputChannel>& 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<IBinder> input(defaultServiceManager()->waitForService(kTestServiceName));
- ASSERT_TRUE(input != nullptr);
- mService = interface_cast<IInputFlinger>(input);
-
- input = defaultServiceManager()->waitForService(kQueryServiceName);
- ASSERT_TRUE(input != nullptr);
- mQuery = interface_cast<IInputFlingerQuery>(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<android::TestInputManager> manager = new android::TestInputManager();
- android::sp<android::TestInputQuery> 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 0000000000..36be684f96
--- /dev/null
+++ b/services/inputflinger/tests/InputMapperTest.cpp
@@ -0,0 +1,247 @@
+/*
+ * 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 <InputReaderBase.h>
+#include <gtest/gtest.h>
+#include <ui/Rotation.h>
+
+namespace android {
+
+using testing::Return;
+
+void InputMapperUnitTest::SetUp() {
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(INITIAL_CURSOR_X, INITIAL_CURSOR_Y);
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+
+ EXPECT_CALL(mMockInputReaderContext, getPointerController(DEVICE_ID))
+ .WillRepeatedly(Return(mFakePointerController));
+
+ EXPECT_CALL(mMockInputReaderContext, getPolicy()).WillRepeatedly(Return(mFakePolicy.get()));
+
+ 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<InputDevice>(&mMockInputReaderContext, DEVICE_ID,
+ /*generation=*/2, identifier);
+ mDeviceContext = std::make_unique<InputDeviceContext>(*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<int> scanCodes) {
+ for (const auto& scanCode : scanCodes) {
+ EXPECT_CALL(mMockEventHub, hasScanCode(EVENTHUB_ID, scanCode))
+ .WillRepeatedly(testing::Return(present));
+ }
+}
+
+void InputMapperUnitTest::setScanCodeState(KeyState state, std::set<int> scanCodes) {
+ for (const auto& scanCode : scanCodes) {
+ EXPECT_CALL(mMockEventHub, getScanCodeState(EVENTHUB_ID, scanCode))
+ .WillRepeatedly(testing::Return(static_cast<int>(state)));
+ }
+}
+
+void InputMapperUnitTest::setKeyCodeState(KeyState state, std::set<int> keyCodes) {
+ for (const auto& keyCode : keyCodes) {
+ EXPECT_CALL(mMockEventHub, getKeyCodeState(EVENTHUB_ID, keyCode))
+ .WillRepeatedly(testing::Return(static_cast<int>(state)));
+ }
+}
+
+std::list<NotifyArgs> InputMapperUnitTest::process(int32_t type, int32_t code, int32_t value) {
+ nsecs_t when = systemTime(SYSTEM_TIME_MONOTONIC);
+ return process(when, type, code, value);
+}
+
+std::list<NotifyArgs> InputMapperUnitTest::process(nsecs_t when, int32_t type, int32_t code,
+ int32_t value) {
+ RawEvent event;
+ event.when = when;
+ event.readTime = 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<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
+ ftl::Flags<InputDeviceClass>(0); // not needed for current tests
+
+void InputMapperTest::SetUp(ftl::Flags<InputDeviceClass> classes, int bus) {
+ mFakeEventHub = std::make_unique<FakeEventHub>();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+ mFakeListener = std::make_unique<TestInputListener>();
+ mReader = std::make_unique<InstrumentedInputReader>(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<NotifyArgs> 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<NotifyArgs> 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<InputDevice> InputMapperTest::newDevice(int32_t deviceId, const std::string& name,
+ const std::string& location,
+ int32_t eventHubId,
+ ftl::Flags<InputDeviceClass> classes,
+ int bus) {
+ InputDeviceIdentifier identifier;
+ identifier.name = name;
+ identifier.location = location;
+ identifier.bus = bus;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(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<uint8_t> 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<NotifyArgs> 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<NotifyArgs> 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<NotifyArgs> InputMapperTest::handleTimeout(InputMapper& mapper, nsecs_t when) {
+ std::list<NotifyArgs> 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 0000000000..05b0e97ff6
--- /dev/null
+++ b/services/inputflinger/tests/InputMapperTest.h
@@ -0,0 +1,142 @@
+/*
+ * 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 <list>
+#include <memory>
+
+#include <InputDevice.h>
+#include <InputMapper.h>
+#include <NotifyArgs.h>
+#include <ftl/flags.h>
+#include <gmock/gmock.h>
+#include <utils/StrongPointer.h>
+
+#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;
+ static constexpr float INITIAL_CURSOR_X = 400;
+ static constexpr float INITIAL_CURSOR_Y = 240;
+ 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<int> scanCodes);
+
+ void setScanCodeState(KeyState state, std::set<int> scanCodes);
+
+ void setKeyCodeState(KeyState state, std::set<int> keyCodes);
+
+ std::list<NotifyArgs> process(int32_t type, int32_t code, int32_t value);
+ std::list<NotifyArgs> process(nsecs_t when, int32_t type, int32_t code, int32_t value);
+
+ MockEventHubInterface mMockEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ std::shared_ptr<FakePointerController> mFakePointerController;
+ MockInputReaderContext mMockInputReaderContext;
+ std::unique_ptr<InputDevice> mDevice;
+
+ std::unique_ptr<InputDeviceContext> mDeviceContext;
+ InputReaderConfiguration mReaderConfiguration;
+ // The mapper should be created by the subclasses.
+ std::unique_ptr<InputMapper> 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<InputDeviceClass> DEVICE_CLASSES;
+ static constexpr int32_t EVENTHUB_ID = 1;
+
+ std::shared_ptr<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ std::unique_ptr<TestInputListener> mFakeListener;
+ std::unique_ptr<InstrumentedInputReader> mReader;
+ std::shared_ptr<InputDevice> mDevice;
+
+ virtual void SetUp(ftl::Flags<InputDeviceClass> classes, int bus = 0);
+ void SetUp() override;
+ void TearDown() override;
+
+ void addConfigurationProperty(const char* key, const char* value);
+ std::list<NotifyArgs> configureDevice(ConfigurationChanges changes);
+ std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
+ const std::string& location, int32_t eventHubId,
+ ftl::Flags<InputDeviceClass> classes, int bus = 0);
+ template <class T, typename... Args>
+ T& addMapperAndConfigure(Args... args) {
+ T& mapper =
+ mDevice->addMapper<T>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(), args...);
+ configureDevice(/*changes=*/{});
+ std::list<NotifyArgs> 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 <class T, typename... Args>
+ 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<T>(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<uint8_t> physicalPort,
+ ViewportType viewportType);
+ void clearViewports();
+ std::list<NotifyArgs> 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<NotifyArgs> 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
index 81ef9b95f0..4b42f4b141 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<uint64_t>(coords.bits),
- static_cast<uint64_t>(axis));
+ static_cast<uint64_t>(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
index 3a7712727a..3b7cbfac5c 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 <gtest/gtest.h>
#include <gui/constants.h>
@@ -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<InputClassifierInterface> mClassifier;
+ std::unique_ptr<InputProcessorInterface> mProcessor;
- void SetUp() override { mClassifier = std::make_unique<InputClassifier>(mTestListener); }
+ void SetUp() override { mProcessor = std::make_unique<InputProcessor>(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<int16_t> 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<int16_t> videoData1 = {1, 2, 3, 4};
- timeval timestamp1 = { 1, 1};
+ timeval timestamp1 = {1, 1};
TouchVideoFrame frame1(2, 2, std::move(videoData1), timestamp1);
std::vector<int16_t> 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 3afa52cf2c..e0a3e94aad 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -16,6 +16,7 @@
#include <cinttypes>
#include <memory>
+#include <optional>
#include <CursorInputMapper.h>
#include <InputDevice.h>
@@ -30,30 +31,34 @@
#include <SensorInputMapper.h>
#include <SingleTouchInputMapper.h>
#include <SwitchInputMapper.h>
+#include <TestEventMatchers.h>
#include <TestInputListener.h>
#include <TouchInputMapper.h>
#include <UinputDevice.h>
#include <VibratorInputMapper.h>
#include <android-base/thread_annotations.h>
+#include <com_android_input_flags.h>
+#include <ftl/enum.h>
#include <gtest/gtest.h>
#include <gui/constants.h>
-
+#include <ui/Rotation.h>
+
+#include <thread>
+#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 +79,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,26 +92,15 @@ 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;
-}
+static constexpr uint32_t STYLUS_FUSION_SOURCE =
+ AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_BLUETOOTH_STYLUS;
-MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
- return arg.displayId == displayId;
-}
+// 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);
-MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") {
- return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY();
-}
+namespace input_flags = com::android::input::flags;
template<typename T>
static inline T min(T a, T b) {
@@ -125,12 +116,12 @@ const std::unordered_map<std::string, LightColor> 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;
}
@@ -138,7 +129,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);
@@ -147,928 +138,22 @@ 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;
- }
-
- void setButtonState(int32_t buttonState) override { mButtonState = buttonState; }
-
- int32_t getButtonState() const override { return mButtonState; }
-
- void getPosition(float* outX, float* outY) const override {
- *outX = mX;
- *outY = mY;
- }
-
- int32_t getDisplayId() const override { return mDisplayId; }
-
- void setDisplayViewport(const DisplayViewport& viewport) override {
- mDisplayId = viewport.displayId;
- }
-
- const std::map<int32_t, std::vector<int32_t>>& getSpots() {
- return mSpotsByDisplay;
- }
-
-private:
- bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
- *outMinX = mMinX;
- *outMinY = mMinY;
- *outMaxX = mMaxX;
- *outMaxY = mMaxY;
- return mHaveBounds;
- }
-
- 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 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<int32_t> 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 clearSpots() override { mSpotsByDisplay.clear(); }
-
- std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
-};
-
-
-// --- FakeInputReaderPolicy ---
-
-class FakeInputReaderPolicy : public InputReaderPolicyInterface {
- std::mutex mLock;
- std::condition_variable mDevicesChangedCondition;
-
- InputReaderConfiguration mConfig;
- std::shared_ptr<FakePointerController> mPointerController;
- std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
- bool mInputDevicesChanged GUARDED_BY(mLock){false};
- std::vector<DisplayViewport> mViewports;
- TouchAffineTransformation transform;
-
-protected:
- virtual ~FakeInputReaderPolicy() {}
-
-public:
- FakeInputReaderPolicy() {
- }
-
- void assertInputDevicesChanged() {
- waitForInputDevices([](bool devicesChanged) {
- if (!devicesChanged) {
- FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
- }
- });
- }
-
- void assertInputDevicesNotChanged() {
- waitForInputDevices([](bool devicesChanged) {
- if (devicesChanged) {
- FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
- }
- });
- }
-
- virtual void clearViewports() {
- mViewports.clear();
- mConfig.setDisplayViewports(mViewports);
- }
-
- std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const {
- return mConfig.getDisplayViewportByUniqueId(uniqueId);
- }
- std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const {
- return mConfig.getDisplayViewportByType(type);
- }
-
- std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const {
- return mConfig.getDisplayViewportByPort(displayPort);
- }
-
- void addDisplayViewport(DisplayViewport viewport) {
- mViewports.push_back(std::move(viewport));
- mConfig.setDisplayViewports(mViewports);
- }
-
- void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
- bool isActive, const std::string& uniqueId,
- std::optional<uint8_t> 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);
- }
-
- 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;
- }
- }
- // 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<FakePointerController> controller) {
- mPointerController = std::move(controller);
- }
-
- const InputReaderConfiguration* getReaderConfiguration() const {
- return &mConfig;
- }
-
- const std::vector<InputDeviceInfo>& getInputDevices() const {
- return mInputDevices;
- }
-
- TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
- int32_t surfaceRotation) {
- return transform;
- }
-
- void setTouchAffineTransformation(const TouchAffineTransformation t) {
- transform = t;
- }
-
- PointerCaptureRequest setPointerCapture(bool enabled) {
- mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
- return mConfig.pointerCaptureRequest;
- }
-
- void setShowTouches(bool enabled) {
- mConfig.showTouches = enabled;
- }
-
- void setDefaultPointerDisplayId(int32_t pointerDisplayId) {
- mConfig.defaultPointerDisplayId = pointerDisplayId;
- }
-
- float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
-
- void setVelocityControlParams(const VelocityControlParameters& params) {
- mConfig.pointerVelocityControlParameters = params;
- mConfig.wheelVelocityControlParameters = params;
- }
-
-private:
- uint32_t mNextPointerCaptureSequenceNumber = 0;
-
- void getReaderConfiguration(InputReaderConfiguration* outConfig) override {
- *outConfig = mConfig;
+[[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));
}
-
- std::shared_ptr<PointerControllerInterface> obtainPointerController(
- int32_t /*deviceId*/) override {
- return mPointerController;
- }
-
- void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {
- std::scoped_lock<std::mutex> lock(mLock);
- mInputDevices = inputDevices;
- mInputDevicesChanged = true;
- mDevicesChangedCondition.notify_all();
- }
-
- std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
- const InputDeviceIdentifier&) override {
- return nullptr;
- }
-
- std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; }
-
- void waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
- std::unique_lock<std::mutex> 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;
- }
-};
-
-// --- FakeEventHub ---
-
-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<InputDeviceClass> classes;
- PropertyMap configuration;
- KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
- KeyedVector<int, bool> relativeAxes;
- KeyedVector<int32_t, int32_t> keyCodeStates;
- KeyedVector<int32_t, int32_t> scanCodeStates;
- KeyedVector<int32_t, int32_t> switchStates;
- KeyedVector<int32_t, int32_t> absoluteAxisValue;
- KeyedVector<int32_t, KeyInfo> keysByScanCode;
- KeyedVector<int32_t, KeyInfo> keysByUsageCode;
- KeyedVector<int32_t, bool> leds;
- // fake mapping which would normally come from keyCharacterMap
- std::unordered_map<int32_t, int32_t> keyCodeMapping;
- std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
- BitArray<MSC_MAX> mscBitmask;
- std::vector<VirtualKeyDefinition> virtualKeys;
- bool enabled;
-
- status_t enable() {
- enabled = true;
- return OK;
- }
-
- status_t disable() {
- enabled = false;
- return OK;
- }
-
- explicit Device(ftl::Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
- };
-
- std::mutex mLock;
- std::condition_variable mEventsCondition;
-
- KeyedVector<int32_t, Device*> mDevices;
- std::vector<std::string> mExcludedDevices;
- std::vector<RawEvent> mEvents GUARDED_BY(mLock);
- std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
- std::vector<int32_t> mVibrators = {0, 1};
- std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
- // Simulates a device light brightness, from light id to light brightness.
- std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
- // Simulates a device light intensities, from light id to light intensities map.
- std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
- mLightIntensities;
-
-public:
- virtual ~FakeEventHub() {
- for (size_t i = 0; i < mDevices.size(); i++) {
- delete mDevices.valueAt(i);
- }
- }
-
- FakeEventHub() { }
-
- void addDevice(int32_t deviceId, const std::string& name,
- ftl::Flags<InputDeviceClass> 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);
- }
-
- void removeDevice(int32_t deviceId) {
- delete mDevices.valueFor(deviceId);
- mDevices.removeItem(deviceId);
-
- enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
- }
-
- 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;
- }
-
- 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();
- }
-
- void finishDeviceScan() {
- enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
- }
-
- void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
- Device* device = getDevice(deviceId);
- device->configuration.addProperty(key, value);
- }
-
- void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
- Device* device = getDevice(deviceId);
- device->configuration.addAll(configuration);
- }
-
- 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);
- }
-
- void addRelativeAxis(int32_t deviceId, int32_t axis) {
- Device* device = getDevice(deviceId);
- device->relativeAxes.add(axis, true);
- }
-
- void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
- Device* device = getDevice(deviceId);
- device->keyCodeStates.replaceValueFor(keyCode, state);
- }
-
- void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
- Device* device = getDevice(deviceId);
- device->scanCodeStates.replaceValueFor(scanCode, state);
- }
-
- void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
- Device* device = getDevice(deviceId);
- device->switchStates.replaceValueFor(switchCode, state);
- }
-
- void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) {
- Device* device = getDevice(deviceId);
- device->absoluteAxisValue.replaceValueFor(axis, value);
- }
-
- 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);
- }
- }
-
- void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) {
- Device* device = getDevice(deviceId);
- device->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
- }
-
- void addLed(int32_t deviceId, int32_t led, bool initialState) {
- Device* device = getDevice(deviceId);
- device->leds.add(led, initialState);
- }
-
- 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);
- }
-
- void setMscEvent(int32_t deviceId, int32_t mscEvent) {
- Device* device = getDevice(deviceId);
- typename BitArray<MSC_MAX>::Buffer buffer;
- buffer[mscEvent / 32] = 1 << mscEvent % 32;
- device->mscBitmask.loadFromBuffer(buffer);
- }
-
- void addRawLightInfo(int32_t rawId, RawLightInfo&& info) {
- mRawLightInfos.emplace(rawId, std::move(info));
- }
-
- void fakeLightBrightness(int32_t rawId, int32_t brightness) {
- mLightBrightness.emplace(rawId, brightness);
- }
-
- void fakeLightIntensities(int32_t rawId,
- const std::unordered_map<LightColor, int32_t> intensities) {
- mLightIntensities.emplace(rawId, std::move(intensities));
- }
-
- bool getLedState(int32_t deviceId, int32_t led) {
- Device* device = getDevice(deviceId);
- return device->leds.valueFor(led);
- }
-
- std::vector<std::string>& getExcludedDevices() {
- return mExcludedDevices;
- }
-
- void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) {
- Device* device = getDevice(deviceId);
- device->virtualKeys.push_back(definition);
- }
-
- void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code,
- int32_t value) {
- std::scoped_lock<std::mutex> 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 setVideoFrames(std::unordered_map<int32_t /*deviceId*/,
- std::vector<TouchVideoFrame>> videoFrames) {
- mVideoFrames = std::move(videoFrames);
- }
-
- void assertQueueIsEmpty() {
- std::unique_lock<std::mutex> 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.";
- }
- }
-
-private:
- Device* getDevice(int32_t deviceId) const {
- ssize_t index = mDevices.indexOfKey(deviceId);
- return index >= 0 ? mDevices.valueAt(index) : nullptr;
- }
-
- ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
- Device* device = getDevice(deviceId);
- return device ? device->classes : ftl::Flags<InputDeviceClass>(0);
- }
-
- InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override {
- Device* device = getDevice(deviceId);
- return device ? device->identifier : InputDeviceIdentifier();
- }
-
- int32_t getDeviceControllerNumber(int32_t) const override { return 0; }
-
- void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override {
- Device* device = getDevice(deviceId);
- if (device) {
- *outConfiguration = device->configuration;
- }
- }
-
- 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;
- }
-
- bool hasRelativeAxis(int32_t deviceId, int axis) const override {
- Device* device = getDevice(deviceId);
- if (device) {
- return device->relativeAxes.indexOfKey(axis) >= 0;
- }
- return false;
- }
-
- bool hasInputProperty(int32_t, int) const override { return false; }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; }
-
- base::Result<std::pair<InputDeviceSensorType, int32_t>> 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);
- }
-
- void setExcludedDevices(const std::vector<std::string>& devices) override {
- mExcludedDevices = devices;
- }
-
- size_t getEvents(int, RawEvent* buffer, size_t bufferSize) override {
- std::scoped_lock lock(mLock);
-
- const size_t filledSize = std::min(mEvents.size(), bufferSize);
- std::copy(mEvents.begin(), mEvents.begin() + filledSize, buffer);
-
- mEvents.erase(mEvents.begin(), mEvents.begin() + filledSize);
- mEventsCondition.notify_all();
- return filledSize;
- }
-
- std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override {
- auto it = mVideoFrames.find(deviceId);
- if (it != mVideoFrames.end()) {
- std::vector<TouchVideoFrame> frames = std::move(it->second);
- mVideoFrames.erase(deviceId);
- return frames;
- }
- return {};
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- // 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;
- }
-
- 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;
- }
-
- 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;
- }
-
- bool hasLed(int32_t deviceId, int32_t led) const override {
- Device* device = getDevice(deviceId);
- return device && device->leds.indexOfKey(led) >= 0;
- }
-
- 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;
- }
- }
- }
-
- void getVirtualKeyDefinitions(
- int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const override {
- outVirtualKeys.clear();
-
- Device* device = getDevice(deviceId);
- if (device) {
- outVirtualKeys = device->virtualKeys;
- }
- }
-
- const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override {
- return nullptr;
- }
-
- bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override {
- return false;
- }
-
- void vibrate(int32_t, const VibrationElement&) override {}
-
- void cancelVibrate(int32_t) override {}
-
- std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; };
-
- std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override {
- return BATTERY_CAPACITY;
- }
-
- std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override {
- return BATTERY_STATUS;
- }
-
- const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) override {
- return {DEFAULT_BATTERY};
- }
-
- std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId, int32_t batteryId) {
- return std::nullopt;
- }
-
- const std::vector<int32_t> getRawLightIds(int32_t deviceId) override {
- std::vector<int32_t> ids;
- for (const auto& [rawId, info] : mRawLightInfos) {
- ids.push_back(rawId);
- }
- return ids;
- }
-
- std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override {
- auto it = mRawLightInfos.find(lightId);
- if (it == mRawLightInfos.end()) {
- return std::nullopt;
- }
- return it->second;
- }
-
- void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override {
- mLightBrightness.emplace(lightId, brightness);
- }
-
- void setLightIntensities(int32_t deviceId, int32_t lightId,
- std::unordered_map<LightColor, int32_t> intensities) override {
- mLightIntensities.emplace(lightId, intensities);
- };
-
- std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override {
- auto lightIt = mLightBrightness.find(lightId);
- if (lightIt == mLightBrightness.end()) {
- return std::nullopt;
- }
- return lightIt->second;
- }
-
- std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
- int32_t deviceId, int32_t lightId) override {
- auto lightIt = mLightIntensities.find(lightId);
- if (lightIt == mLightIntensities.end()) {
- return std::nullopt;
- }
- return lightIt->second;
- };
-
- virtual bool isExternal(int32_t) const {
- return false;
- }
-
- void dump(std::string&) override {}
-
- void monitor() override {}
-
- void requestReopenDevices() override {}
-
- void wake() override {}
-};
+}
// --- FakeInputMapper ---
@@ -1082,6 +167,7 @@ class FakeInputMapper : public InputMapper {
// fake mapping which would normally come from keyCharacterMap
std::unordered_map<int32_t, int32_t> mKeyCodeMapping;
std::vector<int32_t> mSupportedKeyCodes;
+ std::list<NotifyArgs> mProcessResult;
std::mutex mLock;
std::condition_variable mStateChangedCondition;
@@ -1092,8 +178,9 @@ class FakeInputMapper : public InputMapper {
std::optional<DisplayViewport> mViewport;
public:
- FakeInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
- : InputMapper(deviceContext),
+ FakeInputMapper(InputDeviceContext& deviceContext, const InputReaderConfiguration& readerConfig,
+ uint32_t sources)
+ : InputMapper(deviceContext, readerConfig),
mSources(sources),
mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
mMetaState(0),
@@ -1111,6 +198,14 @@ public:
mMetaState = metaState;
}
+ // Sets the return value for the `process` call.
+ void setProcessResult(std::list<NotifyArgs> notifyArgs) {
+ mProcessResult.clear();
+ for (auto notifyArg : notifyArgs) {
+ mProcessResult.push_back(notifyArg);
+ }
+ }
+
void assertConfigureWasCalled() {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
@@ -1137,6 +232,11 @@ public:
mResetWasCalled = false;
}
+ void assertResetWasNotCalled() {
+ std::scoped_lock lock(mLock);
+ ASSERT_FALSE(mResetWasCalled) << "Expected reset to not have been called.";
+ }
+
void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
@@ -1153,6 +253,11 @@ public:
mProcessWasCalled = false;
}
+ void assertProcessWasNotCalled() {
+ std::scoped_lock lock(mLock);
+ ASSERT_FALSE(mProcessWasCalled) << "Expected process to not have been called.";
+ }
+
void setKeyCodeState(int32_t keyCode, int32_t state) {
mKeyCodeStates.replaceValueFor(keyCode, state);
}
@@ -1176,38 +281,42 @@ public:
private:
uint32_t getSources() const override { return mSources; }
- void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {
+ void populateDeviceInfo(InputDeviceInfo& deviceInfo) override {
InputMapper::populateDeviceInfo(deviceInfo);
if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
- deviceInfo->setKeyboardType(mKeyboardType);
+ deviceInfo.setKeyboardType(mKeyboardType);
}
}
- void configure(nsecs_t, const InputReaderConfiguration* config, uint32_t changes) override {
+ std::list<NotifyArgs> reconfigure(nsecs_t, const InputReaderConfiguration& config,
+ ConfigurationChanges changes) override {
std::scoped_lock<std::mutex> lock(mLock);
mConfigureWasCalled = true;
// Find the associated viewport if exist.
const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
- if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
- mViewport = config->getDisplayViewportByPort(*displayPort);
+ if (displayPort && changes.test(InputReaderConfiguration::Change::DISPLAY_INFO)) {
+ mViewport = config.getDisplayViewportByPort(*displayPort);
}
mStateChangedCondition.notify_all();
+ return {};
}
- void reset(nsecs_t) override {
+ std::list<NotifyArgs> reset(nsecs_t) override {
std::scoped_lock<std::mutex> lock(mLock);
mResetWasCalled = true;
mStateChangedCondition.notify_all();
+ return {};
}
- void process(const RawEvent* rawEvent) override {
+ std::list<NotifyArgs> process(const RawEvent* rawEvent) override {
std::scoped_lock<std::mutex> lock(mLock);
mLastEvent = *rawEvent;
mProcessWasCalled = true;
mStateChangedCondition.notify_all();
+ return mProcessResult;
}
int32_t getKeyCodeState(uint32_t, int32_t keyCode) override {
@@ -1231,9 +340,9 @@ private:
}
// Return true if the device has non-empty key layout.
- bool markSupportedKeyCodes(uint32_t, size_t numCodes, const int32_t* keyCodes,
+ bool markSupportedKeyCodes(uint32_t, const std::vector<int32_t>& keyCodes,
uint8_t* outFlags) override {
- for (size_t i = 0; i < numCodes; i++) {
+ 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;
@@ -1259,98 +368,12 @@ private:
}
};
-
-// --- InstrumentedInputReader ---
-
-class InstrumentedInputReader : public InputReader {
- std::queue<std::shared_ptr<InputDevice>> mNextDevices;
-
-public:
- InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
- const sp<InputReaderPolicyInterface>& policy,
- InputListenerInterface& listener)
- : InputReader(eventHub, policy, listener), mFakeContext(this) {}
-
- virtual ~InstrumentedInputReader() {}
-
- void pushNextDevice(std::shared_ptr<InputDevice> device) { mNextDevices.push(device); }
-
- std::shared_ptr<InputDevice> 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<InputDevice>(&mFakeContext, deviceId, generation, identifier);
- }
-
- // Make the protected loopOnce method accessible to tests.
- using InputReader::loopOnce;
-
-protected:
- virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId,
- const InputDeviceIdentifier& identifier)
- REQUIRES(mLock) {
- if (!mNextDevices.empty()) {
- std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
- mNextDevices.pop();
- return device;
- }
- return InputReader::createDeviceLocked(eventHubId, identifier);
- }
-
- // --- FakeInputReaderContext ---
- class FakeInputReaderContext : public ContextImpl {
- int32_t mGlobalMetaState;
- bool mUpdateGlobalMetaStateWasCalled;
- int32_t mGeneration;
-
- 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;
- }
- } mFakeContext;
-
- friend class InputReaderTest;
-
-public:
- FakeInputReaderContext* getContext() { return &mFakeContext; }
-};
-
// --- InputReaderPolicyTest ---
class InputReaderPolicyTest : public testing::Test {
protected:
sp<FakeInputReaderPolicy> mFakePolicy;
- void SetUp() override { mFakePolicy = new FakeInputReaderPolicy(); }
+ void SetUp() override { mFakePolicy = sp<FakeInputReaderPolicy>::make(); }
void TearDown() override { mFakePolicy.clear(); }
};
@@ -1370,9 +393,8 @@ TEST_F(InputReaderPolicyTest, Viewports_GetCleared) {
ASSERT_FALSE(internalViewport);
// 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);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, uniqueId, NO_PORT, ViewportType::INTERNAL);
// Check matching by uniqueId
internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
@@ -1401,21 +423,21 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByType) {
constexpr int32_t virtualDisplayId2 = 3;
// Add an internal viewport
- mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, true /*isActive*/, internalUniqueId,
- NO_PORT, ViewportType::INTERNAL);
+ 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,
- DISPLAY_ORIENTATION_0, true /*isActive*/, externalUniqueId,
- NO_PORT, ViewportType::EXTERNAL);
+ 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,
- DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId1,
- NO_PORT, ViewportType::VIRTUAL);
+ ui::ROTATION_0, /*isActive=*/true, 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);
+ ui::ROTATION_0, /*isActive=*/true, virtualUniqueId2, NO_PORT,
+ ViewportType::VIRTUAL);
// Check matching by type for internal
std::optional<DisplayViewport> internalViewport =
@@ -1463,13 +485,11 @@ TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) {
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);
+ 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,
- DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2,
- NO_PORT, type);
+ mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, uniqueId2, NO_PORT, type);
// Check that correct display viewport was returned by comparing the display IDs.
std::optional<DisplayViewport> viewport1 =
@@ -1509,10 +529,10 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) {
// 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,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
std::optional<DisplayViewport> viewport =
@@ -1524,10 +544,10 @@ TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) {
// 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,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
+ ui::ROTATION_0, /*isActive=*/true, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
@@ -1551,13 +571,11 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByPort) {
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);
+ 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,
- DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, hdmi1,
- type);
+ mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, uniqueId2, hdmi1, type);
// Check that correct display viewport was returned by comparing the display ports.
std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
@@ -1588,7 +606,7 @@ protected:
void SetUp() override {
mFakeEventHub = std::make_unique<FakeEventHub>();
- mFakePolicy = new FakeInputReaderPolicy();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
mFakeListener = std::make_unique<TestInputListener>();
mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
@@ -1611,17 +629,18 @@ protected:
mReader->loopOnce();
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyInputDevicesChangedWasCalled());
ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
}
void disableDevice(int32_t deviceId) {
mFakePolicy->addDisabledDevice(deviceId);
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
+ mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE);
}
void enableDevice(int32_t deviceId) {
mFakePolicy->removeDisabledDevice(deviceId);
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
+ mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::ENABLED_STATE);
}
FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId,
@@ -1630,7 +649,9 @@ protected:
uint32_t sources,
const PropertyMap* configuration) {
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, name);
- FakeInputMapper& mapper = device->addMapper<FakeInputMapper>(eventHubId, sources);
+ FakeInputMapper& mapper =
+ device->addMapper<FakeInputMapper>(eventHubId,
+ mFakePolicy->getReaderConfiguration(), sources);
mReader->pushNextDevice(device);
addDevice(eventHubId, name, classes, configuration);
return mapper;
@@ -1652,14 +673,40 @@ TEST_F(InputReaderTest, PolicyGetInputDevices) {
ASSERT_EQ(0U, inputDevices[0].getMotionRanges().size());
}
+TEST_F(InputReaderTest, InputDeviceRecreatedOnSysfsNodeChanged) {
+ ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr));
+ mFakeEventHub->setSysfsRootPath(1, "xyz");
+
+ // 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();
+
+ // 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());
+}
+
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<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
- device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
- device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[0], mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[1], mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
// Push same device instance for next device to be added, so they'll have same identifier.
mReader->pushNextDevice(device);
@@ -1679,8 +726,10 @@ TEST_F(InputReaderTest, GetMergedInputDevicesEnabled) {
// Add two subdevices to device
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
- device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
- device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[0], mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[1], mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
// Push same device instance for next device to be added, so they'll have same identifier.
mReader->pushNextDevice(device);
@@ -1705,7 +754,8 @@ TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) {
constexpr int32_t eventHubId = 1;
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
- device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
@@ -1877,34 +927,37 @@ TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
mapper.addSupportedKeyCode(AKEYCODE_A);
mapper.addSupportedKeyCode(AKEYCODE_B);
- const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+ const std::vector<int32_t> keyCodes{AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2};
uint8_t flags[4] = { 0, 0, 0, 1 };
- ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags))
+ 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]);
flags[3] = 1;
- ASSERT_FALSE(mReader->hasKeys(deviceId, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
+ 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]);
flags[3] = 1;
- ASSERT_TRUE(mReader->hasKeys(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4,
+ 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]);
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_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]);
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(
+ 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]);
}
@@ -1948,7 +1001,8 @@ TEST_F(InputReaderTest, DeviceReset_RandomId) {
constexpr int32_t eventHubId = 1;
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
- device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
@@ -1981,7 +1035,8 @@ TEST_F(InputReaderTest, DeviceReset_GenerateIdWithInputReaderSource) {
constexpr int32_t eventHubId = 1;
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
- device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr));
@@ -1997,7 +1052,8 @@ TEST_F(InputReaderTest, Device_CanDispatchToDisplay) {
const char* DEVICE_LOCATION = "USB1";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakeInputMapper& mapper =
- device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_TOUCHSCREEN);
+ device->addMapper<FakeInputMapper>(eventHubId, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_TOUCHSCREEN);
mReader->pushNextDevice(device);
const uint8_t hdmi1 = 1;
@@ -2007,13 +1063,12 @@ TEST_F(InputReaderTest, Device_CanDispatchToDisplay) {
// 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(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,
- DISPLAY_ORIENTATION_0, true /*isActive*/, "local:1", hdmi1,
+ ui::ROTATION_0, /*isActive=*/true, "local:1", hdmi1,
ViewportType::EXTERNAL);
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO);
mReader->loopOnce();
// Add the device, and make sure all of the callbacks are triggered.
@@ -2041,8 +1096,10 @@ TEST_F(InputReaderTest, WhenEnabledChanges_AllSubdevicesAreUpdated) {
constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
- device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
- device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[0], mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[1], mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
@@ -2083,9 +1140,13 @@ TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) {
// Add two subdevices to device
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
FakeInputMapper& mapperDevice1 =
- device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[0],
+ mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
FakeInputMapper& mapperDevice2 =
- device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeInputMapper>(eventHubIds[1],
+ mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
@@ -2106,29 +1167,30 @@ TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) {
NotifyPointerCaptureChangedArgs args;
auto request = mFakePolicy->setPointerCapture(true);
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ 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.";
mFakePolicy->setPointerCapture(false);
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasCalled(&args);
ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
// Verify that the Pointer Capture state is not updated when the configuration value
// does not change.
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasNotCalled();
}
class FakeVibratorInputMapper : public FakeInputMapper {
public:
- FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
- : FakeInputMapper(deviceContext, sources) {}
+ FakeVibratorInputMapper(InputDeviceContext& deviceContext,
+ const InputReaderConfiguration& readerConfig, uint32_t sources)
+ : FakeInputMapper(deviceContext, readerConfig, sources) {}
std::vector<int32_t> getVibratorIds() override { return getDeviceContext().getVibratorIds(); }
};
@@ -2141,7 +1203,9 @@ TEST_F(InputReaderTest, VibratorGetVibratorIds) {
const char* DEVICE_LOCATION = "BLUETOOTH";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakeVibratorInputMapper& mapper =
- device->addMapper<FakeVibratorInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+ device->addMapper<FakeVibratorInputMapper>(eventHubId,
+ mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
@@ -2210,8 +1274,9 @@ TEST_F(InputReaderTest, BatteryGetCapacity) {
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
- ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY), BATTERY_CAPACITY);
- ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY);
+ ASSERT_EQ(controller.getBatteryCapacity(FakeEventHub::DEFAULT_BATTERY),
+ FakeEventHub::BATTERY_CAPACITY);
+ ASSERT_EQ(mReader->getBatteryCapacity(deviceId), FakeEventHub::BATTERY_CAPACITY);
}
TEST_F(InputReaderTest, BatteryGetStatus) {
@@ -2227,8 +1292,24 @@ TEST_F(InputReaderTest, BatteryGetStatus) {
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
- ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY), BATTERY_STATUS);
- ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS);
+ ASSERT_EQ(controller.getBatteryStatus(FakeEventHub::DEFAULT_BATTERY),
+ FakeEventHub::BATTERY_STATUS);
+ ASSERT_EQ(mReader->getBatteryStatus(deviceId), FakeEventHub::BATTERY_STATUS);
+}
+
+TEST_F(InputReaderTest, BatteryGetDevicePath) {
+ constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+ ftl::Flags<InputDeviceClass> deviceClass =
+ InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
+ constexpr int32_t eventHubId = 1;
+ const char* DEVICE_LOCATION = "BLUETOOTH";
+ std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+ device->addController<FakePeripheralController>(eventHubId);
+ mReader->pushNextDevice(device);
+
+ ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+
+ ASSERT_EQ(mReader->getBatteryDevicePath(deviceId), FakeEventHub::BATTERY_DEVPATH);
}
TEST_F(InputReaderTest, LightGetColor) {
@@ -2245,15 +1326,15 @@ TEST_F(InputReaderTest, LightGetColor) {
.maxBrightness = 255,
.flags = InputLightClass::BRIGHTNESS,
.path = ""};
- mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info));
- mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
+ mFakeEventHub->addRawLightInfo(/*rawId=*/1, std::move(info));
+ mFakeEventHub->fakeLightBrightness(/*rawId=*/1, 0x55);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
- 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_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 ---
@@ -2272,11 +1353,38 @@ protected:
std::shared_ptr<FakePointerController> mFakePointerController;
void SetUp() override {
- mFakePolicy = new FakeInputReaderPolicy();
+#if !defined(__ANDROID__)
+ GTEST_SKIP();
+#endif
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
mFakePointerController = std::make_shared<FakePointerController>();
mFakePolicy->setPointerController(mFakePointerController);
- mTestListener = std::make_unique<TestInputListener>(2000ms /*eventHappenedTimeout*/,
- 30ms /*eventDidNotHappenTimeout*/);
+
+ setupInputReader();
+ }
+
+ void TearDown() override {
+#if !defined(__ANDROID__)
+ return;
+#endif
+ ASSERT_EQ(mReader->stop(), OK);
+ mReader.reset();
+ mTestListener.reset();
+ mFakePolicy.clear();
+ }
+
+ std::optional<InputDeviceInfo> findDeviceByName(const std::string& name) {
+ const std::vector<InputDeviceInfo> 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;
+ }
+
+ void setupInputReader() {
+ mTestListener = std::make_unique<TestInputListener>(/*eventHappenedTimeout=*/2000ms,
+ /*eventDidNotHappenTimeout=*/30ms);
mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
*mTestListener);
@@ -2286,22 +1394,16 @@ protected:
// 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 {
- ASSERT_EQ(mReader->stop(), OK);
- mReader.reset();
- mTestListener.reset();
- mFakePolicy.clear();
- }
};
TEST_F(InputReaderIntegrationTest, TestInvalidDevice) {
// An invalid input device that is only used for this test.
class InvalidUinputDevice : public UinputDevice {
public:
- InvalidUinputDevice() : UinputDevice("Invalid Device") {}
+ InvalidUinputDevice() : UinputDevice("Invalid Device", /*productId=*/99) {}
private:
void configureDevice(int fd, uinput_user_dev* device) override {}
@@ -2330,18 +1432,11 @@ TEST_F(InputReaderIntegrationTest, AddNewDevice) {
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size());
- // Find the test device by its name.
- const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
- const auto& it =
- std::find_if(inputDevices.begin(), inputDevices.end(),
- [&keyboard](const InputDeviceInfo& info) {
- return info.getIdentifier().name == keyboard->getName();
- });
-
- 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());
+ 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());
keyboard.reset();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
@@ -2376,6 +1471,81 @@ TEST_F(InputReaderIntegrationTest, SendsEventsToInputListener) {
ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
}
+TEST_F(InputReaderIntegrationTest, ExternalStylusesButtons) {
+ std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+
+ const auto device = findDeviceByName(stylus->getName());
+ ASSERT_TRUE(device.has_value());
+
+ // 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());
+
+ 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));
+
+ 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))));
+}
+
+TEST_F(InputReaderIntegrationTest, KeyboardWithStylusButtons) {
+ std::unique_ptr<UinputKeyboard> keyboard =
+ createUinputDevice<UinputKeyboard>("KeyboardWithStylusButtons", /*productId=*/99,
+ std::initializer_list<int>{KEY_Q, KEY_W, KEY_E,
+ KEY_R, KEY_T, KEY_Y,
+ BTN_STYLUS, BTN_STYLUS2,
+ BTN_STYLUS3});
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+
+ const auto device = findDeviceByName(keyboard->getName());
+ ASSERT_TRUE(device.has_value());
+
+ // An alphabetical keyboard that reports stylus buttons should not be recognized as a stylus.
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources())
+ << "Unexpected source " << inputEventSourceToString(device->getSources()).c_str();
+ ASSERT_EQ(AINPUT_KEYBOARD_TYPE_ALPHABETIC, device->getKeyboardType());
+}
+
+TEST_F(InputReaderIntegrationTest, HidUsageKeyboardIsNotAStylus) {
+ // Create a Uinput keyboard that simulates a keyboard that can report HID usage codes. The
+ // hid-input driver reports HID usage codes using the value for EV_MSC MSC_SCAN event.
+ std::unique_ptr<UinputKeyboardWithHidUsage> keyboard =
+ createUinputDevice<UinputKeyboardWithHidUsage>(
+ std::initializer_list<int>{KEY_VOLUMEUP, KEY_VOLUMEDOWN});
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+
+ const auto device = findDeviceByName(keyboard->getName());
+ ASSERT_TRUE(device.has_value());
+
+ ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, device->getSources())
+ << "Unexpected source " << inputEventSourceToString(device->getSources()).c_str();
+
+ // If a device supports reporting HID usage codes, it shouldn't automatically support
+ // stylus keys.
+ const std::vector<int> keycodes{AKEYCODE_STYLUS_BUTTON_PRIMARY};
+ uint8_t outFlags[] = {0};
+ ASSERT_TRUE(mReader->hasKeys(device->getId(), AINPUT_SOURCE_KEYBOARD, keycodes, outFlags));
+ ASSERT_EQ(0, outFlags[0]) << "Keyboard should not have stylus button";
+}
+
/**
* 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
@@ -2398,47 +1568,110 @@ TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) {
ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode);
}
-// --- TouchProcessTest ---
-class TouchIntegrationTest : public InputReaderIntegrationTest {
+// --- TouchIntegrationTest ---
+
+class BaseTouchIntegrationTest : public InputReaderIntegrationTest {
protected:
const std::string UNIQUE_ID = "local:0";
void SetUp() override {
+#if !defined(__ANDROID__)
+ GTEST_SKIP();
+#endif
InputReaderIntegrationTest::SetUp();
// At least add an internal display.
- setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, UNIQUE_ID, NO_PORT,
- ViewportType::INTERNAL);
+ setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto info = findDeviceByName(mDevice->getName());
+ ASSERT_TRUE(info);
+ mDeviceInfo = *info;
}
void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
- int32_t orientation, const std::string& uniqueId,
+ ui::Rotation orientation, const std::string& uniqueId,
std::optional<uint8_t> physicalPort,
ViewportType viewportType) {
- mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
+ mFakePolicy->addDisplayViewport(displayId, width, height, orientation, /*isActive=*/true,
uniqueId, physicalPort, viewportType);
- mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ mReader->requestRefreshConfiguration(InputReaderConfiguration::Change::DISPLAY_INFO);
}
void assertReceivedMotion(int32_t action, const std::vector<Point>& 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++) {
+ ASSERT_EQ(points.size(), args.getPointerCount());
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
EXPECT_EQ(points[i].x, args.pointerCoords[i].getX());
EXPECT_EQ(points[i].y, args.pointerCoords[i].getY());
}
}
std::unique_ptr<UinputTouchScreen> mDevice;
+ InputDeviceInfo mDeviceInfo;
+};
+
+enum class TouchIntegrationTestDisplays { DISPLAY_INTERNAL, DISPLAY_INPUT_PORT, DISPLAY_UNIQUE_ID };
+
+class TouchIntegrationTest : public BaseTouchIntegrationTest,
+ public testing::WithParamInterface<TouchIntegrationTestDisplays> {
+protected:
+ static constexpr std::optional<uint8_t> DISPLAY_PORT = 0;
+ const std::string INPUT_PORT = "uinput_touch/input0";
+
+ void SetUp() override {
+#if !defined(__ANDROID__)
+ GTEST_SKIP();
+#endif
+ if (GetParam() == TouchIntegrationTestDisplays::DISPLAY_INTERNAL) {
+ BaseTouchIntegrationTest::SetUp();
+ return;
+ }
+
+ // setup policy with a input-port or UniqueId association to the display
+ bool isInputPortAssociation =
+ GetParam() == TouchIntegrationTestDisplays::DISPLAY_INPUT_PORT;
+
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+ if (isInputPortAssociation) {
+ mFakePolicy->addInputPortAssociation(INPUT_PORT, DISPLAY_PORT.value());
+ } else {
+ mFakePolicy->addInputUniqueIdAssociation(INPUT_PORT, UNIQUE_ID);
+ }
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(mFakePointerController);
+
+ InputReaderIntegrationTest::setupInputReader();
+
+ mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT),
+ INPUT_PORT);
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+
+ // Add a display linked to a physical port or UniqueId.
+ setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ UNIQUE_ID, isInputPortAssociation ? DISPLAY_PORT : NO_PORT,
+ ViewportType::INTERNAL);
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto info = findDeviceByName(mDevice->getName());
+ ASSERT_TRUE(info);
+ mDeviceInfo = *info;
+ }
};
-TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
+TEST_P(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());
+}
+
+TEST_P(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
@@ -2462,7 +1695,7 @@ TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
-TEST_F(TouchIntegrationTest, InputEvent_ProcessMultiTouch) {
+TEST_P(TouchIntegrationTest, InputEvent_ProcessMultiTouch) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
@@ -2518,7 +1751,7 @@ TEST_F(TouchIntegrationTest, InputEvent_ProcessMultiTouch) {
* 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) {
+TEST_P(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerUp) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
@@ -2561,7 +1794,7 @@ TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerUp) {
* 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) {
+TEST_P(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerMoveAndUp) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
@@ -2601,7 +1834,7 @@ TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerMoveAndUp) {
assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)});
}
-TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) {
+TEST_P(TouchIntegrationTest, InputEvent_ProcessPalm) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
@@ -2652,6 +1885,616 @@ TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) {
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
+/**
+ * Some drivers historically have reported axis values outside of the range specified in the
+ * evdev axis info. Ensure we don't crash when this happens. For example, a driver may report a
+ * pressure value greater than the reported maximum, since it unclear what specific meaning the
+ * maximum value for pressure has (beyond the maximum value that can be produced by a sensor),
+ * and no units for pressure (resolution) is specified by the evdev documentation.
+ */
+TEST_P(TouchIntegrationTest, AcceptsAxisValuesOutsideReportedRange) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Down with pressure outside the reported range
+ mDevice->sendSlot(FIRST_SLOT);
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendPressure(UinputTouchScreen::RAW_PRESSURE_MAX + 2);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Move to a point outside the reported range
+ mDevice->sendMove(Point(DISPLAY_WIDTH, DISPLAY_HEIGHT) + Point(1, 1));
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+ // Up
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+}
+
+TEST_P(TouchIntegrationTest, NotifiesPolicyWhenStylusGestureStarted) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // 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_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+
+ // Release the stylus touch.
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+ // 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))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotNotified());
+
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ // 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_NO_FATAL_FAILURE(mFakePolicy->assertStylusGestureNotified(mDeviceInfo.getId()));
+}
+
+TEST_P(TouchIntegrationTest, ExternalStylusConnectedDuringTouchGesture) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Down
+ mDevice->sendSlot(FIRST_SLOT);
+ mDevice->sendTrackingId(FIRST_TRACKING_ID);
+ mDevice->sendDown(centerPoint);
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Move
+ mDevice->sendMove(centerPoint + Point(1, 1));
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+ // Connecting an external stylus mid-gesture should not interrupt the ongoing gesture stream.
+ auto externalStylus = createUinputDevice<UinputExternalStylus>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto stylusInfo = findDeviceByName(externalStylus->getName());
+ ASSERT_TRUE(stylusInfo);
+
+ // Move
+ mDevice->sendMove(centerPoint + Point(2, 2));
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+ // Disconnecting an external stylus mid-gesture should not interrupt the ongoing gesture stream.
+ externalStylus.reset();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+
+ // Up
+ mDevice->sendUp();
+ mDevice->sendSync();
+ ASSERT_NO_FATAL_FAILURE(
+ mTestListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+}
+
+INSTANTIATE_TEST_SUITE_P(TouchIntegrationTestDisplayVariants, TouchIntegrationTest,
+ testing::Values(TouchIntegrationTestDisplays::DISPLAY_INTERNAL,
+ TouchIntegrationTestDisplays::DISPLAY_INPUT_PORT,
+ TouchIntegrationTestDisplays::DISPLAY_UNIQUE_ID));
+
+// --- StylusButtonIntegrationTest ---
+
+// 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 <typename UinputStylusDevice>
+class StylusButtonIntegrationTest : public BaseTouchIntegrationTest {
+protected:
+ void SetUp() override {
+#if !defined(__ANDROID__)
+ GTEST_SKIP();
+#endif
+ BaseTouchIntegrationTest::SetUp();
+ mTouchscreen = mDevice.get();
+ mTouchscreenInfo = mDeviceInfo;
+
+ setUpStylusDevice();
+ }
+
+ UinputStylusDevice* mStylus{nullptr};
+ InputDeviceInfo mStylusInfo{};
+
+ UinputTouchScreen* mTouchscreen{nullptr};
+ InputDeviceInfo mTouchscreenInfo{};
+
+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 <typename T = UinputStylusDevice>
+ std::enable_if_t<std::is_same_v<UinputTouchScreen, 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 <typename T = UinputStylusDevice>
+ std::enable_if_t<!std::is_same_v<UinputTouchScreen, T>, void> setUpStylusDevice() {
+ mStylusDeviceLifecycleTracker = createUinputDevice<T>();
+ 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;
+ }
+
+ std::unique_ptr<UinputStylusDevice> mStylusDeviceLifecycleTracker{};
+
+ // Hide the base class's device to expose it with a different name for readability.
+ using BaseTouchIntegrationTest::mDevice;
+ using BaseTouchIntegrationTest::mDeviceInfo;
+};
+
+using StylusButtonIntegrationTestTypes =
+ ::testing::Types<UinputTouchScreen, UinputExternalStylus, UinputExternalStylusWithPressure>;
+TYPED_TEST_SUITE(StylusButtonIntegrationTest, StylusButtonIntegrationTestTypes);
+
+TYPED_TEST(StylusButtonIntegrationTest, 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, 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, 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, StylusButtonsWithinTouchGesture) {
+ const Point centerPoint = TestFixture::mTouchscreen->getCenterPoint();
+ const auto touchscreenId = TestFixture::mTouchscreenInfo.getId();
+ const auto stylusId = TestFixture::mStylusInfo.getId();
+
+ // 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, 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 = BaseTouchIntegrationTest;
+
+TEST_F(ExternalStylusIntegrationTest, ExternalStylusConnectionChangesTouchscreenSource) {
+ // Create an external stylus capable of reporting pressure data that
+ // should be fused with a touch pointer.
+ std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+ createUinputDevice<UinputExternalStylusWithPressure>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto stylusInfo = findDeviceByName(stylus->getName());
+ ASSERT_TRUE(stylusInfo);
+
+ // Connecting an external stylus changes the source of the touchscreen.
+ const auto deviceInfo = findDeviceByName(mDevice->getName());
+ ASSERT_TRUE(deviceInfo);
+ ASSERT_TRUE(isFromSource(deviceInfo->getSources(), STYLUS_FUSION_SOURCE));
+}
+
+TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureReported) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Create an external stylus capable of reporting pressure data that
+ // should be fused with a touch pointer.
+ std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+ createUinputDevice<UinputExternalStylusWithPressure>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto stylusInfo = findDeviceByName(stylus->getName());
+ ASSERT_TRUE(stylusInfo);
+
+ ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
+
+ const auto touchscreenId = mDeviceInfo.getId();
+
+ // 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());
+
+ // 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), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+ WithPressure(100.f / RAW_PRESSURE_MAX))));
+
+ // 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), WithSource(STYLUS_FUSION_SOURCE), WithDeviceId(touchscreenId),
+ WithPressure(200.f / RAW_PRESSURE_MAX))));
+
+ // The external stylus did not generate any events.
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(ExternalStylusIntegrationTest, FusedExternalStylusPressureNotReported) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Create an external stylus capable of reporting pressure data that
+ // should be fused with a touch pointer.
+ std::unique_ptr<UinputExternalStylusWithPressure> stylus =
+ createUinputDevice<UinputExternalStylusWithPressure>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto stylusInfo = findDeviceByName(stylus->getName());
+ ASSERT_TRUE(stylusInfo);
+
+ ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
+
+ const auto touchscreenId = mDeviceInfo.getId();
+
+ // 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());
+
+ // 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),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+ 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),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS),
+ WithToolType(ToolType::FINGER))));
+
+ // 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), WithSource(STYLUS_FUSION_SOURCE),
+ WithToolType(ToolType::STYLUS), WithButtonState(0), WithDeviceId(touchscreenId),
+ WithPressure(200.f / RAW_PRESSURE_MAX))));
+
+ // The external stylus did not generate any events.
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
+}
+
+TEST_F(ExternalStylusIntegrationTest, UnfusedExternalStylus) {
+ const Point centerPoint = mDevice->getCenterPoint();
+
+ // Create an external stylus device that does not support pressure. It should not affect any
+ // touch pointers.
+ std::unique_ptr<UinputExternalStylus> stylus = createUinputDevice<UinputExternalStylus>();
+ ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
+ const auto stylusInfo = findDeviceByName(stylus->getName());
+ ASSERT_TRUE(stylusInfo);
+
+ ASSERT_EQ(AINPUT_SOURCE_STYLUS | AINPUT_SOURCE_KEYBOARD, stylusInfo->getSources());
+
+ const auto touchscreenId = mDeviceInfo.getId();
+
+ // 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),
+ WithSource(AINPUT_SOURCE_TOUCHSCREEN |
+ AINPUT_SOURCE_STYLUS),
+ WithButtonState(0),
+ WithDeviceId(touchscreenId),
+ WithPressure(1.f)),
+ waitUntil));
+
+ // The external stylus did not generate any events.
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasNotCalled());
+ ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasNotCalled());
+}
+
// --- InputDeviceTest ---
class InputDeviceTest : public testing::Test {
protected:
@@ -2662,6 +2505,7 @@ protected:
static const int32_t DEVICE_CONTROLLER_NUMBER;
static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
static const int32_t EVENTHUB_ID;
+ static const std::string DEVICE_BLUETOOTH_ADDRESS;
std::shared_ptr<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
@@ -2671,13 +2515,14 @@ protected:
void SetUp() override {
mFakeEventHub = std::make_unique<FakeEventHub>();
- mFakePolicy = new FakeInputReaderPolicy();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
mFakeListener = std::make_unique<TestInputListener>();
mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
*mFakeListener);
InputDeviceIdentifier identifier;
identifier.name = DEVICE_NAME;
identifier.location = DEVICE_LOCATION;
+ identifier.bluetoothAddress = DEVICE_BLUETOOTH_ADDRESS;
mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION,
identifier);
mReader->pushNextDevice(mDevice);
@@ -2699,6 +2544,7 @@ const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0;
const ftl::Flags<InputDeviceClass> 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";
TEST_F(InputDeviceTest, ImmutableProperties) {
ASSERT_EQ(DEVICE_ID, mDevice->getId());
@@ -2713,10 +2559,10 @@ TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) {
TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
// Configuration.
InputReaderConfiguration config;
- mDevice->configure(ARBITRARY_TIME, &config, 0);
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
// Reset.
- mDevice->reset(ARBITRARY_TIME);
+ unused += mDevice->reset(ARBITRARY_TIME);
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -2743,9 +2589,9 @@ TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
ASSERT_EQ(AKEY_STATE_UNKNOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 0))
<< "Ignored device should return unknown switch state.";
- const int32_t keyCodes[2] = { AKEYCODE_A, AKEYCODE_B };
+ const std::vector<int32_t> keyCodes{AKEYCODE_A, AKEYCODE_B};
uint8_t flags[2] = { 0, 1 };
- ASSERT_FALSE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 2, keyCodes, flags))
+ 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.";
@@ -2753,10 +2599,11 @@ TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRequestsToMappers) {
// Configuration.
- mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, String8("key"), String8("value"));
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "key", "value");
FakeInputMapper& mapper1 =
- mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
mapper1.setKeyboardType(AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mapper1.setMetaState(AMETA_ALT_ON);
mapper1.addSupportedKeyCode(AKEYCODE_A);
@@ -2768,22 +2615,23 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe
mapper1.setSwitchState(4, AKEY_STATE_DOWN);
FakeInputMapper& mapper2 =
- mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN);
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_TOUCHSCREEN);
mapper2.setMetaState(AMETA_SHIFT_ON);
InputReaderConfiguration config;
- mDevice->configure(ARBITRARY_TIME, &config, 0);
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
- String8 propertyValue;
- ASSERT_TRUE(mDevice->getConfiguration().tryGetProperty(String8("key"), propertyValue))
+ std::optional<std::string> propertyValue = mDevice->getConfiguration().getString("key");
+ ASSERT_TRUE(propertyValue.has_value())
<< "Device should have read configuration during configuration phase.";
- ASSERT_STREQ("value", propertyValue.string());
+ ASSERT_EQ("value", *propertyValue);
ASSERT_NO_FATAL_FAILURE(mapper1.assertConfigureWasCalled());
ASSERT_NO_FATAL_FAILURE(mapper2.assertConfigureWasCalled());
// Reset
- mDevice->reset(ARBITRARY_TIME);
+ unused += mDevice->reset(ARBITRARY_TIME);
ASSERT_NO_FATAL_FAILURE(mapper1.assertResetWasCalled());
ASSERT_NO_FATAL_FAILURE(mapper2.assertResetWasCalled());
@@ -2820,16 +2668,16 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe
ASSERT_EQ(AKEY_STATE_DOWN, mDevice->getSwitchState(AINPUT_SOURCE_KEYBOARD, 4))
<< "Should query mapper when source is supported.";
- const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
+ const std::vector<int32_t> keyCodes{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))
+ 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.";
- ASSERT_TRUE(mDevice->markSupportedKeyCodes(AINPUT_SOURCE_KEYBOARD, 4, keyCodes, flags))
+ 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.";
@@ -2839,20 +2687,90 @@ TEST_F(InputDeviceTest, WhenMappersAreRegistered_DeviceIsNotIgnoredAndForwardsRe
// Event handling.
RawEvent event;
event.deviceId = EVENTHUB_ID;
- mDevice->process(&event, 1);
+ unused += mDevice->process(&event, 1);
ASSERT_NO_FATAL_FAILURE(mapper1.assertProcessWasCalled());
ASSERT_NO_FATAL_FAILURE(mapper2.assertProcessWasCalled());
}
+TEST_F(InputDeviceTest, WakeDevice_AddsWakeFlagToProcessNotifyArgs) {
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "1");
+ FakeInputMapper& mapper =
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ NotifyMotionArgs args1;
+ NotifySwitchArgs args2;
+ NotifyKeyArgs args3;
+ mapper.setProcessResult({args1, args2, args3});
+
+ InputReaderConfiguration config;
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+ RawEvent event;
+ event.deviceId = EVENTHUB_ID;
+ std::list<NotifyArgs> notifyArgs = mDevice->process(&event, 1);
+
+ for (auto& arg : notifyArgs) {
+ if (const auto notifyMotionArgs = std::get_if<NotifyMotionArgs>(&arg)) {
+ ASSERT_EQ(POLICY_FLAG_WAKE, notifyMotionArgs->policyFlags);
+ } else if (const auto notifySwitchArgs = std::get_if<NotifySwitchArgs>(&arg)) {
+ ASSERT_EQ(POLICY_FLAG_WAKE, notifySwitchArgs->policyFlags);
+ } else if (const auto notifyKeyArgs = std::get_if<NotifyKeyArgs>(&arg)) {
+ ASSERT_EQ(POLICY_FLAG_WAKE, notifyKeyArgs->policyFlags);
+ }
+ }
+}
+
+TEST_F(InputDeviceTest, NotWakeDevice_DoesNotAddWakeFlagToProcessNotifyArgs) {
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "0");
+ FakeInputMapper& mapper =
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ NotifyMotionArgs args;
+ mapper.setProcessResult({args});
+
+ InputReaderConfiguration config;
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+ RawEvent event;
+ event.deviceId = EVENTHUB_ID;
+ std::list<NotifyArgs> notifyArgs = mDevice->process(&event, 1);
+
+ // POLICY_FLAG_WAKE is not added to the NotifyArgs.
+ ASSERT_EQ(0u, std::get<NotifyMotionArgs>(notifyArgs.front()).policyFlags);
+}
+
+TEST_F(InputDeviceTest, NotWakeDevice_DoesNotRemoveExistingWakeFlagFromProcessNotifyArgs) {
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "device.wake", "0");
+ FakeInputMapper& mapper =
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ NotifyMotionArgs args;
+ args.policyFlags = POLICY_FLAG_WAKE;
+ mapper.setProcessResult({args});
+
+ InputReaderConfiguration config;
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+ RawEvent event;
+ event.deviceId = EVENTHUB_ID;
+ std::list<NotifyArgs> notifyArgs = mDevice->process(&event, 1);
+
+ // The POLICY_FLAG_WAKE is preserved, despite the device being a non-wake device.
+ ASSERT_EQ(POLICY_FLAG_WAKE, std::get<NotifyMotionArgs>(notifyArgs.front()).policyFlags);
+}
+
// 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
+// 2. Device is disabled when configure API is called
TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) {
- mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_TOUCHSCREEN);
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_TOUCHSCREEN);
// First Configuration.
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
// Device should be enabled by default.
ASSERT_TRUE(mDevice->isEnabled());
@@ -2862,77 +2780,83 @@ TEST_F(InputDeviceTest, Configure_AssignsDisplayPort) {
const std::string UNIQUE_ID = "local:1";
mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi);
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ 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());
// Prepare displays.
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, true /*isActive*/, UNIQUE_ID, hdmi,
+ ui::ROTATION_0, /*isActive=*/true, UNIQUE_ID, hdmi,
ViewportType::INTERNAL);
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
ASSERT_TRUE(mDevice->isEnabled());
// Device should be disabled after set disable.
mFakePolicy->addDisabledDevice(mDevice->getId());
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_ENABLED_STATE);
+ 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.
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
ASSERT_FALSE(mDevice->isEnabled());
}
TEST_F(InputDeviceTest, Configure_AssignsDisplayUniqueId) {
// Device should be enabled by default.
mFakePolicy->clearViewports();
- mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
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);
+ unused += 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,
+ ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
NO_PORT, ViewportType::INTERNAL);
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
ASSERT_TRUE(mDevice->isEnabled());
// Device should be disabled after set disable.
mFakePolicy->addDisabledDevice(mDevice->getId());
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_ENABLED_STATE);
+ 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.
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
ASSERT_FALSE(mDevice->isEnabled());
}
TEST_F(InputDeviceTest, Configure_UniqueId_CorrectlyMatches) {
mFakePolicy->clearViewports();
- mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD);
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0);
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, DISPLAY_UNIQUE_ID);
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
+ ui::ROTATION_0, /* isActive= */ true, DISPLAY_UNIQUE_ID,
NO_PORT, ViewportType::INTERNAL);
- mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
ASSERT_EQ(DISPLAY_UNIQUE_ID, mDevice->getAssociatedDisplayUniqueId());
}
@@ -2944,168 +2868,73 @@ 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*/);
+ InputDevice device(mReader->getContext(), /*id=*/1, /*generation=*/2, /*identifier=*/{});
+ auto _ = device.addEventHubDevice(ARBITRARY_TIME, TEST_EVENTHUB_ID,
+ mFakePolicy->getReaderConfiguration());
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<InputDeviceClass> DEVICE_CLASSES;
- static const int32_t EVENTHUB_ID;
-
- std::shared_ptr<FakeEventHub> mFakeEventHub;
- sp<FakeInputReaderPolicy> mFakePolicy;
- std::unique_ptr<TestInputListener> mFakeListener;
- std::unique_ptr<InstrumentedInputReader> mReader;
- std::shared_ptr<InputDevice> mDevice;
-
- virtual void SetUp(ftl::Flags<InputDeviceClass> classes) {
- mFakeEventHub = std::make_unique<FakeEventHub>();
- mFakePolicy = new FakeInputReaderPolicy();
- mFakeListener = std::make_unique<TestInputListener>();
- mReader = std::make_unique<InstrumentedInputReader>(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();
- }
-
- void SetUp() override {
- SetUp(DEVICE_CLASSES);
- }
-
- void TearDown() override {
- mFakeListener.reset();
- mFakePolicy.clear();
- }
-
- void addConfigurationProperty(const char* key, const char* value) {
- mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, String8(key), String8(value));
- }
-
- 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();
- }
-
- std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
- const std::string& location, int32_t eventHubId,
- ftl::Flags<InputDeviceClass> classes) {
- InputDeviceIdentifier identifier;
- identifier.name = name;
- identifier.location = location;
- std::shared_ptr<InputDevice> device =
- std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION,
- identifier);
- mReader->pushNextDevice(device);
- mFakeEventHub->addDevice(eventHubId, name, classes);
- mReader->loopOnce();
- return device;
- }
-
- template <class T, typename... Args>
- T& addMapperAndConfigure(Args... args) {
- T& mapper = mDevice->addMapper<T>(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;
- }
-
- void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
- int32_t orientation, const std::string& uniqueId,
- std::optional<uint8_t> physicalPort, ViewportType viewportType) {
- mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
- uniqueId, physicalPort, viewportType);
- configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
- }
-
- void clearViewports() {
- mFakePolicy->clearViewports();
- }
-
- 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();
- }
-
- 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;
- }
-
- 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);
- }
-
- 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);
- }
-};
+TEST_F(InputDeviceTest, GetBluetoothAddress) {
+ const auto& address = mReader->getBluetoothAddress(DEVICE_ID);
+ ASSERT_TRUE(address);
+ ASSERT_EQ(DEVICE_BLUETOOTH_ADDRESS, *address);
+}
-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<InputDeviceClass> InputMapperTest::DEVICE_CLASSES =
- ftl::Flags<InputDeviceClass>(0); // not needed for current tests
-const int32_t InputMapperTest::EVENTHUB_ID = 1;
+TEST_F(InputDeviceTest, KernelBufferOverflowResetsMappers) {
+ mFakePolicy->clearViewports();
+ FakeInputMapper& mapper =
+ mDevice->addMapper<FakeInputMapper>(EVENTHUB_ID, mFakePolicy->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD);
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+
+ mapper.assertConfigureWasCalled();
+ mapper.assertResetWasNotCalled();
+
+ RawEvent event{.deviceId = EVENTHUB_ID,
+ .readTime = ARBITRARY_TIME,
+ .when = ARBITRARY_TIME,
+ .type = EV_SYN,
+ .code = SYN_REPORT,
+ .value = 0};
+
+ // Events are processed normally.
+ unused = mDevice->process(&event, /*count=*/1);
+ mapper.assertProcessWasCalled();
+
+ // Simulate a kernel buffer overflow, which generates a SYN_DROPPED event.
+ // This should reset the mapper.
+ event.type = EV_SYN;
+ event.code = SYN_DROPPED;
+ event.value = 0;
+ unused = mDevice->process(&event, /*count=*/1);
+ mapper.assertProcessWasNotCalled();
+ mapper.assertResetWasCalled();
+
+ // All events until the next SYN_REPORT should be dropped.
+ event.type = EV_KEY;
+ event.code = KEY_A;
+ event.value = 1;
+ unused = mDevice->process(&event, /*count=*/1);
+ mapper.assertProcessWasNotCalled();
+
+ // We get the SYN_REPORT event now, which is not forwarded to mappers.
+ event.type = EV_SYN;
+ event.code = SYN_REPORT;
+ event.value = 0;
+ unused = mDevice->process(&event, /*count=*/1);
+ mapper.assertProcessWasNotCalled();
+
+ // The mapper receives events normally now.
+ event.type = EV_KEY;
+ event.code = KEY_B;
+ event.value = 1;
+ unused = mDevice->process(&event, /*count=*/1);
+ mapper.assertProcessWasCalled();
+}
// --- SwitchInputMapperTest ---
@@ -3114,13 +2943,13 @@ protected:
};
TEST_F(SwitchInputMapperTest, GetSources) {
- SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+ SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
ASSERT_EQ(uint32_t(AINPUT_SOURCE_SWITCH), mapper.getSources());
}
TEST_F(SwitchInputMapperTest, GetSwitchState) {
- SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
+ SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
mFakeEventHub->setSwitchState(EVENTHUB_ID, SW_LID, 1);
ASSERT_EQ(1, mapper.getSwitchState(AINPUT_SOURCE_ANY, SW_LID));
@@ -3130,15 +2959,18 @@ TEST_F(SwitchInputMapperTest, GetSwitchState) {
}
TEST_F(SwitchInputMapperTest, Process) {
- SwitchInputMapper& mapper = addMapperAndConfigure<SwitchInputMapper>();
-
- 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);
-
- NotifySwitchArgs args;
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifySwitchWasCalled(&args));
+ SwitchInputMapper& mapper = constructAndAddMapper<SwitchInputMapper>();
+ std::list<NotifyArgs> 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<NotifySwitchArgs>(*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),
@@ -3153,13 +2985,13 @@ protected:
};
TEST_F(VibratorInputMapperTest, GetSources) {
- VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+ VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
}
TEST_F(VibratorInputMapperTest, GetVibratorIds) {
- VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+ VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
}
@@ -3167,17 +2999,17 @@ TEST_F(VibratorInputMapperTest, GetVibratorIds) {
TEST_F(VibratorInputMapperTest, Vibrate) {
constexpr uint8_t DEFAULT_AMPLITUDE = 192;
constexpr int32_t VIBRATION_TOKEN = 100;
- VibratorInputMapper& mapper = addMapperAndConfigure<VibratorInputMapper>();
+ VibratorInputMapper& mapper = constructAndAddMapper<VibratorInputMapper>();
VibrationElement pattern(2);
VibrationSequence sequence(2);
pattern.duration = std::chrono::milliseconds(200);
- pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 2},
- {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+ pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 2},
+ {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
sequence.addElement(pattern);
pattern.duration = std::chrono::milliseconds(500);
- pattern.channels = {{0 /* vibratorId */, DEFAULT_AMPLITUDE / 4},
- {1 /* vibratorId */, DEFAULT_AMPLITUDE}};
+ pattern.channels = {{/*vibratorId=*/0, DEFAULT_AMPLITUDE / 4},
+ {/*vibratorId=*/1, DEFAULT_AMPLITUDE}};
sequence.addElement(pattern);
std::vector<int64_t> timings = {0, 1};
@@ -3185,22 +3017,23 @@ TEST_F(VibratorInputMapperTest, Vibrate) {
ASSERT_FALSE(mapper.isVibrating());
// Start vibrating
- mapper.vibrate(sequence, -1 /* repeat */, VIBRATION_TOKEN);
+ std::list<NotifyArgs> out = mapper.vibrate(sequence, /*repeat=*/-1, 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);
+ ASSERT_EQ(1u, out.size());
+ const NotifyVibratorStateArgs& vibrateArgs = std::get<NotifyVibratorStateArgs>(*out.begin());
+ ASSERT_EQ(DEVICE_ID, vibrateArgs.deviceId);
+ ASSERT_TRUE(vibrateArgs.isOn);
// Stop vibrating
- mapper.cancelVibrate(VIBRATION_TOKEN);
+ out = 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);
+ ASSERT_EQ(1u, out.size());
+ const NotifyVibratorStateArgs& cancelArgs = std::get<NotifyVibratorStateArgs>(*out.begin());
+ ASSERT_EQ(DEVICE_ID, cancelArgs.deviceId);
+ ASSERT_FALSE(cancelArgs.isOn);
}
// --- SensorInputMapperTest ---
@@ -3291,7 +3124,7 @@ void SensorInputMapperTest::setGyroProperties() {
}
TEST_F(SensorInputMapperTest, GetSources) {
- SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+ SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
ASSERT_EQ(static_cast<uint32_t>(AINPUT_SOURCE_SENSOR), mapper.getSources());
}
@@ -3299,7 +3132,7 @@ TEST_F(SensorInputMapperTest, GetSources) {
TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
setAccelProperties();
prepareAccelAxes();
- SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+ SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::ACCELEROMETER,
std::chrono::microseconds(10000),
@@ -3329,7 +3162,7 @@ TEST_F(SensorInputMapperTest, ProcessAccelerometerSensor) {
TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
setGyroProperties();
prepareGyroAxes();
- SensorInputMapper& mapper = addMapperAndConfigure<SensorInputMapper>();
+ SensorInputMapper& mapper = constructAndAddMapper<SensorInputMapper>();
ASSERT_TRUE(mapper.enableSensor(InputDeviceSensorType::GYROSCOPE,
std::chrono::microseconds(10000),
@@ -3361,8 +3194,8 @@ TEST_F(SensorInputMapperTest, ProcessGyroscopeSensor) {
class KeyboardInputMapperTest : public InputMapperTest {
protected:
const std::string UNIQUE_ID = "local:0";
-
- void prepareDisplay(int32_t orientation);
+ const KeyboardLayoutInfo DEVICE_KEYBOARD_LAYOUT_INFO = KeyboardLayoutInfo("en-US", "qwerty");
+ void prepareDisplay(ui::Rotation orientation);
void testDPadKeyRotation(KeyboardInputMapper& mapper, int32_t originalScanCode,
int32_t originalKeyCode, int32_t rotatedKeyCode,
@@ -3372,7 +3205,7 @@ protected:
/* Similar to setDisplayInfoAndReconfigure, but pre-populates all parameters except for the
* orientation.
*/
-void KeyboardInputMapperTest::prepareDisplay(int32_t orientation) {
+void KeyboardInputMapperTest::prepareDisplay(ui::Rotation orientation) {
setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID,
NO_PORT, ViewportType::INTERNAL);
}
@@ -3399,7 +3232,7 @@ void KeyboardInputMapperTest::testDPadKeyRotation(KeyboardInputMapper& mapper,
TEST_F(KeyboardInputMapperTest, GetSources) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, mapper.getSources());
@@ -3415,7 +3248,7 @@ TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3510,6 +3343,27 @@ TEST_F(KeyboardInputMapperTest, Process_SimpleKeyPress) {
ASSERT_EQ(ARBITRARY_TIME, args.downTime);
}
+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);
+
+ KeyboardInputMapper& mapper =
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+
+ // 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);
+
+ // 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);
+}
+
/**
* Ensure that the readTime is set to the time when the EV_KEY is received.
*/
@@ -3517,17 +3371,17 @@ TEST_F(KeyboardInputMapperTest, Process_SendsReadTime) {
mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(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, /*readTime=*/12, EV_KEY, KEY_HOME, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(12, args.readTime);
// Key up
- process(mapper, ARBITRARY_TIME, 15 /*readTime*/, EV_KEY, KEY_HOME, 1);
+ process(mapper, ARBITRARY_TIME, /*readTime=*/15, EV_KEY, KEY_HOME, 1);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(15, args.readTime);
}
@@ -3540,7 +3394,7 @@ TEST_F(KeyboardInputMapperTest, Process_ShouldUpdateMetaState) {
mFakeEventHub->addKey(EVENTHUB_ID, 0, KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
@@ -3581,10 +3435,10 @@ TEST_F(KeyboardInputMapperTest, Process_WhenNotOrientationAware_ShouldNotRotateD
mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ prepareDisplay(ui::ROTATION_90);
ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
KEY_UP, AKEYCODE_DPAD_UP, AKEYCODE_DPAD_UP));
ASSERT_NO_FATAL_FAILURE(testDPadKeyRotation(mapper,
@@ -3603,10 +3457,10 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
addConfigurationProperty("keyboard.orientationAware", "1");
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ 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,
@@ -3617,7 +3471,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
AKEYCODE_DPAD_LEFT, DISPLAY_ID));
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ 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,
@@ -3628,7 +3482,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
AKEYCODE_DPAD_DOWN, DISPLAY_ID));
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_180);
+ 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,
@@ -3639,7 +3493,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
AKEYCODE_DPAD_RIGHT, DISPLAY_ID));
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_270);
+ 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,
@@ -3653,7 +3507,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
// in the key up as we did in the key down.
NotifyKeyArgs args;
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_270);
+ 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);
@@ -3661,7 +3515,7 @@ TEST_F(KeyboardInputMapperTest, Process_WhenOrientationAware_ShouldRotateDPad) {
ASSERT_EQ(AKEYCODE_DPAD_RIGHT, args.keyCode);
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_180);
+ 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);
@@ -3675,7 +3529,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware
mFakeEventHub->addKey(EVENTHUB_ID, KEY_UP, 0, AKEYCODE_DPAD_UP, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
NotifyKeyArgs args;
@@ -3686,7 +3540,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_NotOrientationAware
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
ASSERT_EQ(ADISPLAY_ID_NONE, args.displayId);
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ 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);
@@ -3701,14 +3555,14 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) {
addConfigurationProperty("keyboard.orientationAware", "1");
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
NotifyKeyArgs args;
// Display id should be ADISPLAY_ID_NONE without any display configuration.
// ^--- already checked by the previous test
- setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+ 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));
@@ -3718,7 +3572,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) {
constexpr int32_t newDisplayId = 2;
clearViewports();
- setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+ 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));
@@ -3729,7 +3583,7 @@ TEST_F(KeyboardInputMapperTest, DisplayIdConfigurationChange_OrientationAware) {
TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->setKeyCodeState(EVENTHUB_ID, AKEYCODE_A, 1);
@@ -3741,7 +3595,7 @@ TEST_F(KeyboardInputMapperTest, GetKeyCodeState) {
TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->addKeyCodeMapping(EVENTHUB_ID, AKEYCODE_Y, AKEYCODE_Z);
@@ -3754,7 +3608,7 @@ TEST_F(KeyboardInputMapperTest, GetKeyCodeForKeyLocation) {
TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->setScanCodeState(EVENTHUB_ID, KEY_A, 1);
@@ -3766,14 +3620,13 @@ TEST_F(KeyboardInputMapperTest, GetScanCodeState) {
TEST_F(KeyboardInputMapperTest, MarkSupportedKeyCodes) {
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
mFakeEventHub->addKey(EVENTHUB_ID, KEY_A, 0, AKEYCODE_A, 0);
- 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(mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_A, AKEYCODE_B}, flags));
ASSERT_TRUE(flags[0]);
ASSERT_FALSE(flags[1]);
}
@@ -3787,7 +3640,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleMetaStateAndLeds)
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -3853,11 +3706,11 @@ TEST_F(KeyboardInputMapperTest, NoMetaStateWhenMetaKeysNotPresent) {
mFakeEventHub->addKey(EVENTHUB_ID, BTN_Y, 0, AKEYCODE_BUTTON_Y, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
// Meta state should be AMETA_NONE after reset
- mapper.reset(ARBITRARY_TIME);
+ std::list<NotifyArgs> 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);
@@ -3903,14 +3756,20 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) {
mFakeEventHub->addKey(SECOND_EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ device2->addEmptyEventHubDevice(SECOND_EVENTHUB_ID);
KeyboardInputMapper& mapper2 =
- device2->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
- device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/);
- device2->reset(ARBITRARY_TIME);
+ device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+ mFakePolicy
+ ->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ std::list<NotifyArgs> unused =
+ device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+ unused += device2->reset(ARBITRARY_TIME);
// Prepared displays and associated info.
constexpr uint8_t hdmi1 = 0;
@@ -3921,19 +3780,19 @@ TEST_F(KeyboardInputMapperTest, Configure_AssignsDisplayPort) {
mFakePolicy->addInputPortAssociation(USB2, hdmi2);
// No associated display viewport found, should disable the device.
- device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
ASSERT_FALSE(device2->isEnabled());
// Prepare second display.
constexpr int32_t newDisplayId = 2;
- setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+ setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
UNIQUE_ID, hdmi1, ViewportType::INTERNAL);
- setDisplayInfoAndReconfigure(newDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_ORIENTATION_0,
+ 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.
- device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
- InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ unused += device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DISPLAY_INFO);
// Device should be enabled after the associated display is found.
ASSERT_TRUE(mDevice->isEnabled());
@@ -3968,7 +3827,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) {
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -4014,11 +3873,17 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleAfterReattach) {
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->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
- device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/);
- device2->reset(ARBITRARY_TIME);
+ device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+ mFakePolicy
+ ->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ std::list<NotifyArgs> 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));
@@ -4033,10 +3898,10 @@ TEST_F(KeyboardInputMapperTest, Process_toggleCapsLockState) {
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
// Suppose we have two mappers. (DPAD + KEYBOARD)
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_DPAD,
AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper.getMetaState());
@@ -4055,7 +3920,7 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
mFakeEventHub->addKey(EVENTHUB_ID, KEY_SCROLLLOCK, 0, AKEYCODE_SCROLL_LOCK, 0);
KeyboardInputMapper& mapper1 =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
// keyboard 2.
@@ -4073,11 +3938,17 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
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->addMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID, AINPUT_SOURCE_KEYBOARD,
- AINPUT_KEYBOARD_TYPE_ALPHABETIC);
- device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), 0 /*changes*/);
- device2->reset(ARBITRARY_TIME);
+ device2->constructAndAddMapper<KeyboardInputMapper>(SECOND_EVENTHUB_ID,
+ mFakePolicy
+ ->getReaderConfiguration(),
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ std::list<NotifyArgs> unused =
+ device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+ unused += device2->reset(ARBITRARY_TIME);
// Initial metastate is AMETA_NONE.
ASSERT_EQ(AMETA_NONE, mapper1.getMetaState());
@@ -4123,6 +3994,92 @@ TEST_F(KeyboardInputMapperTest, Process_LockedKeysShouldToggleInMultiDevices) {
ASSERT_EQ(AMETA_NONE, mapper2.getMetaState());
}
+TEST_F(KeyboardInputMapperTest, Process_DisabledDevice) {
+ const int32_t USAGE_A = 0x070004;
+ mFakeEventHub->addKey(EVENTHUB_ID, KEY_HOME, 0, AKEYCODE_HOME, POLICY_FLAG_WAKE);
+ mFakeEventHub->addKey(EVENTHUB_ID, 0, USAGE_A, AKEYCODE_A, POLICY_FLAG_WAKE);
+
+ KeyboardInputMapper& mapper =
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ // 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(AKEY_EVENT_FLAG_FROM_SYSTEM, args.flags);
+
+ // 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(AKEY_EVENT_ACTION_UP, args.action);
+ ASSERT_EQ(AKEYCODE_HOME, args.keyCode);
+ ASSERT_EQ(KEY_HOME, args.scanCode);
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_CANCELED, args.flags);
+}
+
+TEST_F(KeyboardInputMapperTest, Configure_AssignKeyboardLayoutInfo) {
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ std::list<NotifyArgs> 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<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ InputReaderConfiguration config;
+ std::list<NotifyArgs> unused = mDevice->configure(ARBITRARY_TIME, config, /*changes=*/{});
+
+ ASSERT_EQ("en", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->languageTag);
+ ASSERT_EQ("extended", mDevice->getDeviceInfo().getKeyboardLayoutInfo()->layoutType);
+}
+
+TEST_F(KeyboardInputMapperTest, Process_GesureEventToSetFlagKeepTouchMode) {
+ mFakeEventHub->addKey(EVENTHUB_ID, KEY_LEFT, 0, AKEYCODE_DPAD_LEFT, POLICY_FLAG_GESTURE);
+ KeyboardInputMapper& mapper =
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ NotifyKeyArgs args;
+
+ // Key down
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_LEFT, 1);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
+ ASSERT_EQ(AKEY_EVENT_FLAG_FROM_SYSTEM | AKEY_EVENT_FLAG_KEEP_TOUCH_MODE, args.flags);
+}
+
// --- KeyboardInputMapperTest_ExternalDevice ---
class KeyboardInputMapperTest_ExternalDevice : public InputMapperTest {
@@ -4131,8 +4088,8 @@ protected:
};
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.
+ // 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);
@@ -4140,7 +4097,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) {
POLICY_FLAG_WAKE);
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
@@ -4154,7 +4111,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, WakeBehavior) {
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_EQ(POLICY_FLAG_WAKE, args.policyFlags);
process(mapper, ARBITRARY_TIME + 1, READ_TIME, EV_KEY, KEY_PLAY, 0);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&args));
@@ -4178,7 +4135,7 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) {
addConfigurationProperty("keyboard.doNotWakeByDefault", "1");
KeyboardInputMapper& mapper =
- addMapperAndConfigure<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
+ constructAndAddMapper<KeyboardInputMapper>(AINPUT_SOURCE_KEYBOARD,
AINPUT_KEYBOARD_TYPE_ALPHABETIC);
process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, KEY_HOME, 1);
@@ -4207,9 +4164,9 @@ TEST_F(KeyboardInputMapperTest_ExternalDevice, DoNotWakeByDefaultBehavior) {
ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
}
-// --- CursorInputMapperTest ---
+// --- CursorInputMapperTestBase ---
-class CursorInputMapperTest : public InputMapperTest {
+class CursorInputMapperTestBase : public InputMapperTest {
protected:
static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
@@ -4225,14 +4182,14 @@ protected:
void testMotionRotation(CursorInputMapper& mapper, int32_t originalX, int32_t originalY,
int32_t rotatedX, int32_t rotatedY);
- void prepareDisplay(int32_t orientation) {
+ void prepareDisplay(ui::Rotation orientation) {
setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation,
DISPLAY_UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
}
void prepareSecondaryDisplay() {
setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
+ ui::ROTATION_0, SECONDARY_DISPLAY_UNIQUE_ID, NO_PORT,
ViewportType::EXTERNAL);
}
@@ -4243,11 +4200,11 @@ protected:
}
};
-const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6;
-void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
- int32_t originalY, int32_t rotatedX,
- int32_t rotatedY) {
+void CursorInputMapperTestBase::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);
@@ -4261,26 +4218,36 @@ void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_
float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
}
+// --- CursorInputMapperTest ---
+
+class CursorInputMapperTest : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(false);
+ CursorInputMapperTestBase::SetUp();
+ }
+};
+
TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
addConfigurationProperty("cursor.mode", "pointer");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
}
TEST_F(CursorInputMapperTest, WhenModeIsNavigation_GetSources_ReturnsTrackball) {
addConfigurationProperty("cursor.mode", "navigation");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
ASSERT_EQ(AINPUT_SOURCE_TRACKBALL, mapper.getSources());
}
TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeFromPointerController) {
addConfigurationProperty("cursor.mode", "pointer");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
InputDeviceInfo info;
- mapper.populateDeviceInfo(&info);
+ mapper.populateDeviceInfo(info);
// Initially there may not be a valid motion range.
ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
@@ -4290,9 +4257,10 @@ TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeF
// When the bounds are set, then there should be a valid motion range.
mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
InputDeviceInfo info2;
- mapper.populateDeviceInfo(&info2);
+ mapper.populateDeviceInfo(info2);
ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2,
AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE,
@@ -4307,10 +4275,10 @@ TEST_F(CursorInputMapperTest, WhenModeIsPointer_PopulateDeviceInfo_ReturnsRangeF
TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsScaledRange) {
addConfigurationProperty("cursor.mode", "navigation");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
InputDeviceInfo info;
- mapper.populateDeviceInfo(&info);
+ mapper.populateDeviceInfo(info);
ASSERT_NO_FATAL_FAILURE(assertMotionRange(info,
AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_TRACKBALL,
@@ -4325,7 +4293,7 @@ TEST_F(CursorInputMapperTest, WhenModeIsNavigation_PopulateDeviceInfo_ReturnsSca
TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaState) {
addConfigurationProperty("cursor.mode", "navigation");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -4345,9 +4313,9 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat
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(uint32_t(1), args.getPointerCount());
ASSERT_EQ(0, args.pointerProperties[0].id);
- ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+ 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);
@@ -4363,9 +4331,9 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat
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(uint32_t(1), args.getPointerCount());
ASSERT_EQ(0, args.pointerProperties[0].id);
- ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+ 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);
@@ -4384,9 +4352,9 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat
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(uint32_t(1), args.getPointerCount());
ASSERT_EQ(0, args.pointerProperties[0].id);
- ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+ 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);
@@ -4402,9 +4370,9 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat
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(uint32_t(1), args.getPointerCount());
ASSERT_EQ(0, args.pointerProperties[0].id);
- ASSERT_EQ(AMOTION_EVENT_TOOL_TYPE_MOUSE, args.pointerProperties[0].toolType);
+ 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);
@@ -4413,7 +4381,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldSetAllFieldsAndIncludeGlobalMetaStat
TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
addConfigurationProperty("cursor.mode", "navigation");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
NotifyMotionArgs args;
@@ -4437,7 +4405,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentXYUpdates) {
TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
addConfigurationProperty("cursor.mode", "navigation");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
NotifyMotionArgs args;
@@ -4466,7 +4434,7 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleIndependentButtonUpdates) {
TEST_F(CursorInputMapperTest, Process_ShouldHandleCombinedXYAndButtonUpdates) {
addConfigurationProperty("cursor.mode", "navigation");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
NotifyMotionArgs args;
@@ -4515,9 +4483,9 @@ TEST_F(CursorInputMapperTest, Process_WhenOrientationAware_ShouldNotRotateMotion
// 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<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ 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));
@@ -4533,10 +4501,10 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion
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<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ 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));
@@ -4547,7 +4515,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion
ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, 1));
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ 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));
@@ -4558,7 +4526,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion
ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, -1, -1));
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_180);
+ 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));
@@ -4569,7 +4537,7 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion
ASSERT_NO_FATAL_FAILURE(testMotionRotation(mapper, -1, 1, 1, -1));
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_270);
+ 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));
@@ -4582,11 +4550,10 @@ TEST_F(CursorInputMapperTest, Process_WhenNotOrientationAware_ShouldRotateMotion
TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
addConfigurationProperty("cursor.mode", "pointer");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
mFakePointerController->setPosition(100, 200);
- mFakePointerController->setButtonState(0);
NotifyMotionArgs motionArgs;
NotifyKeyArgs keyArgs;
@@ -4597,14 +4564,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4613,21 +4578,18 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4638,26 +4600,20 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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());
+ motionArgs.buttonState);
ASSERT_NO_FATAL_FAILURE(
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_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));
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());
+ motionArgs.buttonState);
ASSERT_NO_FATAL_FAILURE(
assertCursorPointerCoords(motionArgs.pointerCoords[0], 100.0f, 200.0f, 1.0f));
@@ -4666,14 +4622,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4682,7 +4636,6 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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);
@@ -4690,14 +4643,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4712,14 +4663,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4728,14 +4677,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
@@ -4753,14 +4700,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4769,14 +4714,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
@@ -4794,14 +4737,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4810,14 +4751,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
@@ -4835,14 +4774,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
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));
@@ -4851,14 +4788,12 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
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));
@@ -4869,11 +4804,10 @@ TEST_F(CursorInputMapperTest, Process_ShouldHandleAllButtons) {
TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerAround) {
addConfigurationProperty("cursor.mode", "pointer");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
mFakePointerController->setPosition(100, 200);
- mFakePointerController->setButtonState(0);
NotifyMotionArgs args;
@@ -4885,94 +4819,7 @@ TEST_F(CursorInputMapperTest, Process_WhenModeIsPointer_ShouldMoveThePointerArou
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));
-}
-
-TEST_F(CursorInputMapperTest, Process_PointerCapture) {
- addConfigurationProperty("cursor.mode", "pointer");
- mFakePolicy->setPointerCapture(true);
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
-
- NotifyDeviceResetArgs resetArgs;
- ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
- ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
- mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
- mFakePointerController->setPosition(100, 200);
- mFakePointerController->setButtonState(0);
-
- NotifyMotionArgs args;
-
- // 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));
-
- // 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));
-
- // 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));
-
- // 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));
-
- // 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_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
- ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
-
- 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));
+ ASSERT_NO_FATAL_FAILURE(mFakePointerController->assertPosition(110.0f, 220.0f));
}
/**
@@ -4981,10 +4828,10 @@ TEST_F(CursorInputMapperTest, Process_PointerCapture) {
*/
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*/);
+ const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
+ /*high threshold=*/100.f, /*acceleration=*/10.f);
mFakePolicy->setVelocityControlParams(testParams);
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -5007,7 +4854,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
// Enable Pointer Capture
mFakePolicy->setPointerCapture(true);
- configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
NotifyPointerCaptureChangedArgs captureArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
ASSERT_TRUE(captureArgs.request.enable);
@@ -5025,7 +4872,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesVelocityProcessing) {
TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
addConfigurationProperty("cursor.mode", "pointer");
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
@@ -5033,7 +4880,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
// Ensure the display is rotated.
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ prepareDisplay(ui::ROTATION_90);
NotifyMotionArgs args;
@@ -5049,7 +4896,7 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
// Enable Pointer Capture.
mFakePolicy->setPointerCapture(true);
- configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
NotifyPointerCaptureChangedArgs captureArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
ASSERT_TRUE(captureArgs.request.enable);
@@ -5066,68 +4913,68 @@ TEST_F(CursorInputMapperTest, PointerCaptureDisablesOrientationChanges) {
}
TEST_F(CursorInputMapperTest, ConfigureDisplayId_NoAssociatedViewport) {
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
// Set up the default display.
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ prepareDisplay(ui::ROTATION_90);
// 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);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
mFakePointerController->setPosition(100, 200);
- mFakePointerController->setButtonState(0);
// 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));
+ 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, ConfigureDisplayId_WithAssociatedViewport) {
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
// Set up the default display.
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ prepareDisplay(ui::ROTATION_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);
+ 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));
+ 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, ConfigureDisplayId_IgnoresEventsForMismatchedPointerDisplay) {
- CursorInputMapper& mapper = addMapperAndConfigure<CursorInputMapper>();
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
// Set up the default display as the display on which the pointer should be shown.
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ prepareDisplay(ui::ROTATION_90);
mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
// Associate the InputDevice with the secondary display.
prepareSecondaryDisplay();
mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
- configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
// The mapper should not generate any events because it is associated with a display that is
// different from the pointer display.
@@ -5137,6 +4984,672 @@ TEST_F(CursorInputMapperTest, ConfigureDisplayId_IgnoresEventsForMismatchedPoint
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}
+// --- CursorInputMapperTestWithChoreographer ---
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+// logic can be removed.
+class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(true);
+ CursorInputMapperTestBase::SetUp();
+ }
+};
+
+TEST_F(CursorInputMapperTestWithChoreographer, PopulateDeviceInfoReturnsRangeFromPolicy) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(info);
+
+ // 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));
+
+ // When the viewport and the default pointer display ID is set, then there should be a valid
+ // motion range.
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ InputDeviceInfo info2;
+ mapper.populateDeviceInfo(info2);
+
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
+ DISPLAY_WIDTH - 1, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
+ DISPLAY_HEIGHT - 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(CursorInputMapperTestWithChoreographer, ProcessShouldHandleAllButtonsWithZeroCoords) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ prepareDisplay(ui::ROTATION_0);
+
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ NotifyMotionArgs motionArgs;
+ NotifyKeyArgs keyArgs;
+
+ // 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(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.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(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.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_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ // 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], 0.0f, 0.0f, 1.0f));
+
+ 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], 0.0f, 0.0f, 1.0f));
+
+ 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], 0.0f, 0.0f, 1.0f));
+
+ 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], 0.0f, 0.0f, 1.0f));
+
+ 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], 0.0f, 0.0f, 1.0f));
+
+ 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], 0.0f, 0.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);
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ // 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);
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.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_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+ 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
+ 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);
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.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_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.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_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // 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);
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.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_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ 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
+ 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);
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.0f, 0.0f));
+
+ 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], 0.0f, 0.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_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.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(CursorInputMapperTestWithChoreographer, ProcessWhenModeIsPointerShouldKeepZeroCoords) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ prepareDisplay(ui::ROTATION_0);
+
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ NotifyMotionArgs args;
+
+ 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], 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ const VelocityControlParameters testParams(/*scale=*/5.f, /*lowThreshold=*/0.f,
+ /*highThreshold=*/100.f, /*acceleration=*/10.f);
+ mFakePolicy->setVelocityControlParams(testParams);
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ prepareDisplay(ui::ROTATION_0);
+
+ NotifyDeviceResetArgs resetArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
+ ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
+
+ NotifyMotionArgs 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);
+
+ // 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 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);
+ const float relX2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float relY2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ ASSERT_EQ(10, relX2);
+ ASSERT_EQ(20, relY2);
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdNoAssociatedViewport) {
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_90);
+
+ // 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);
+
+ mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ // Ensure input events are generated without display ID and coords,
+ // because they will be decided later by PointerChoreographer.
+ 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(ADISPLAY_ID_NONE),
+ WithCoords(0.0f, 0.0f))));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayIdWithAssociatedViewport) {
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_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);
+
+ // Ensure input events are generated with associated display ID but not with coords,
+ // because the coords will be decided later by PointerChoreographer.
+ 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(0.0f, 0.0f))));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer,
+ ConfigureDisplayIdShouldGenerateEventWithMismatchedPointerDisplay) {
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display as the display on which the pointer should be shown.
+ prepareDisplay(ui::ROTATION_90);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+
+ // Associate the InputDevice with the secondary display.
+ prepareSecondaryDisplay();
+ mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ // With PointerChoreographer enabled, there could be a PointerController for the associated
+ // display even if it is different from the pointer display. So the mapper should generate an
+ // event.
+ 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(0.0f, 0.0f))));
+}
+
+// --- BluetoothCursorInputMapperTest ---
+
+class BluetoothCursorInputMapperTest : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(false);
+ InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
+
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(mFakePointerController);
+ }
+};
+
+TEST_F(BluetoothCursorInputMapperTest, TimestampSmoothening) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ 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))));
+
+ // 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;
+
+ 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(BluetoothCursorInputMapperTest, TimestampSmootheningIsCapped) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ nsecs_t expectedEventTime = ARBITRARY_TIME;
+ 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))));
+
+ // 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;
+
+ 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))));
+ }
+
+ // 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(BluetoothCursorInputMapperTest, TimestampSmootheningNotUsed) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ 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))));
+}
+
+// --- BluetoothCursorInputMapperTestWithChoreographer ---
+
+class BluetoothCursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(true);
+ InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
+
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(mFakePointerController);
+ }
+};
+
+TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmoothening) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_0);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ 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))));
+
+ // 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;
+
+ 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(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningIsCapped) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_0);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ nsecs_t expectedEventTime = ARBITRARY_TIME;
+ 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))));
+
+ // 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;
+
+ 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))));
+ }
+
+ // 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(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningNotUsed) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_0);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ 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))));
+}
+
// --- TouchInputMapperTest ---
class TouchInputMapperTest : public InputMapperTest {
@@ -5188,9 +5701,9 @@ protected:
TOOL_TYPE = 1 << 10,
};
- void prepareDisplay(int32_t orientation, std::optional<uint8_t> port = NO_PORT);
+ void prepareDisplay(ui::Rotation orientation, std::optional<uint8_t> port = NO_PORT);
void prepareSecondaryDisplay(ViewportType type, std::optional<uint8_t> port = NO_PORT);
- void prepareVirtualDisplay(int32_t orientation);
+ void prepareVirtualDisplay(ui::Rotation orientation);
void prepareVirtualKeys();
void prepareLocationCalibration();
int32_t toRawX(float displayX);
@@ -5244,17 +5757,17 @@ const VirtualKeyDefinition TouchInputMapperTest::VIRTUAL_KEYS[2] = {
{ KEY_MENU, DISPLAY_HEIGHT - 60, DISPLAY_WIDTH + 15, 20, 20 },
};
-void TouchInputMapperTest::prepareDisplay(int32_t orientation, std::optional<uint8_t> port) {
+void TouchInputMapperTest::prepareDisplay(ui::Rotation orientation, std::optional<uint8_t> port) {
setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, orientation, UNIQUE_ID,
port, ViewportType::INTERNAL);
}
void TouchInputMapperTest::prepareSecondaryDisplay(ViewportType type, std::optional<uint8_t> port) {
setDisplayInfoAndReconfigure(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
- DISPLAY_ORIENTATION_0, SECONDARY_UNIQUE_ID, port, type);
+ ui::ROTATION_0, SECONDARY_UNIQUE_ID, port, type);
}
-void TouchInputMapperTest::prepareVirtualDisplay(int32_t orientation) {
+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);
@@ -5405,46 +5918,27 @@ void SingleTouchInputMapperTest::processSync(SingleTouchInputMapper& mapper) {
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndNotACursor_ReturnsPointer) {
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
}
-TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsNotSpecifiedAndIsACursor_ReturnsTouchPad) {
- mFakeEventHub->addRelativeAxis(EVENTHUB_ID, REL_X);
- mFakeEventHub->addRelativeAxis(EVENTHUB_ID, REL_Y);
- prepareButtons();
- prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
-
- ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
-}
-
-TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchPad_ReturnsTouchPad) {
- prepareButtons();
- prepareAxes(POSITION);
- addConfigurationProperty("touch.deviceType", "touchPad");
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
-
- ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
-}
-
TEST_F(SingleTouchInputMapperTest, GetSources_WhenDeviceTypeIsTouchScreen_ReturnsTouchScreen) {
prepareButtons();
prepareAxes(POSITION);
addConfigurationProperty("touch.deviceType", "touchScreen");
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
}
TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
// Unknown key.
ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getKeyCodeState(AINPUT_SOURCE_ANY, AKEYCODE_A));
@@ -5468,11 +5962,11 @@ TEST_F(SingleTouchInputMapperTest, GetKeyCodeState) {
TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
// Unknown key.
ASSERT_EQ(AKEY_STATE_UNKNOWN, mapper.getScanCodeState(AINPUT_SOURCE_ANY, KEY_A));
@@ -5496,26 +5990,26 @@ TEST_F(SingleTouchInputMapperTest, GetScanCodeState) {
TEST_F(SingleTouchInputMapperTest, MarkSupportedKeyCodes) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
- 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(
+ mapper.markSupportedKeyCodes(AINPUT_SOURCE_ANY, {AKEYCODE_HOME, AKEYCODE_A}, flags));
ASSERT_TRUE(flags[0]);
ASSERT_FALSE(flags[1]);
}
TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNormally_SendsKeyDownAndKeyUp) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -5561,11 +6055,11 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndReleasedNor
TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfBounds_SendsKeyDownAndKeyCancel) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -5619,9 +6113,9 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5643,9 +6137,9 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5666,9 +6160,9 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5682,11 +6176,11 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenVirtualKeyIsPressedAndMovedOutOfB
TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMovesIn_SendsDownAsTouchEntersDisplay) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -5716,9 +6210,9 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMoves
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5739,9 +6233,9 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenTouchStartsOutsideDisplayAndMoves
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5757,11 +6251,11 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl
addConfigurationProperty("touch.deviceType", "touchScreen");
addConfigurationProperty("touch.displayId", VIRTUAL_DISPLAY_UNIQUE_ID);
- prepareVirtualDisplay(DISPLAY_ORIENTATION_0);
+ prepareVirtualDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -5784,9 +6278,9 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl
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(size_t(1), motionArgs.getPointerCount());
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(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT),
1, 0, 0, 0, 0, 0, 0, 0));
@@ -5811,9 +6305,9 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl
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(size_t(1), motionArgs.getPointerCount());
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(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT),
1, 0, 0, 0, 0, 0, 0, 0));
@@ -5836,9 +6330,9 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl
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(size_t(1), motionArgs.getPointerCount());
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(x, VIRTUAL_DISPLAY_WIDTH), toDisplayY(y, VIRTUAL_DISPLAY_HEIGHT),
1, 0, 0, 0, 0, 0, 0, 0));
@@ -5853,11 +6347,11 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture_VirtualDispl
TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
prepareVirtualKeys();
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -5879,9 +6373,9 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5904,9 +6398,9 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5927,9 +6421,9 @@ TEST_F(SingleTouchInputMapperTest, Process_NormalSingleTouchGesture) {
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(size_t(1), motionArgs.getPointerCount());
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(x), toDisplayY(y), 1, 0, 0, 0, 0, 0, 0, 0));
ASSERT_NEAR(X_PRECISION, motionArgs.xPrecision, EPSILON);
@@ -5947,12 +6441,12 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationAware_DoesNotRotateMot
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<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Rotation 90.
- prepareDisplay(DISPLAY_ORIENTATION_90);
+ prepareDisplay(ui::ROTATION_90);
processDown(mapper, toRawX(50), toRawY(75));
processSync(mapper);
@@ -5972,13 +6466,13 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion
// 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>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Rotation 0.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
processDown(mapper, toRawX(50), toRawY(75));
processSync(mapper);
@@ -5992,8 +6486,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion
// Rotation 90.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_90);
- processDown(mapper, toRawX(75), RAW_Y_MAX - toRawY(50) + RAW_Y_MIN);
+ prepareDisplay(ui::ROTATION_90);
+ processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -6006,7 +6500,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion
// Rotation 180.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_180);
+ prepareDisplay(ui::ROTATION_180);
processDown(mapper, RAW_X_MAX - toRawX(50) + RAW_X_MIN, RAW_Y_MAX - toRawY(75) + RAW_Y_MIN);
processSync(mapper);
@@ -6020,8 +6514,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenNotOrientationAware_RotatesMotion
// Rotation 270.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_270);
- processDown(mapper, RAW_X_MAX - toRawX(75) + RAW_X_MIN, toRawY(50));
+ 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));
@@ -6040,8 +6534,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation0_RotatesMotions) {
addConfigurationProperty("touch.orientationAware", "1");
addConfigurationProperty("touch.orientation", "ORIENTATION_0");
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
- auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Orientation 0.
@@ -6064,8 +6558,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation90_RotatesMotions) {
addConfigurationProperty("touch.orientationAware", "1");
addConfigurationProperty("touch.orientation", "ORIENTATION_90");
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
- auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Orientation 90.
@@ -6088,8 +6582,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation180_RotatesMotions) {
addConfigurationProperty("touch.orientationAware", "1");
addConfigurationProperty("touch.orientation", "ORIENTATION_180");
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
- auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Orientation 180.
@@ -6112,8 +6606,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientation270_RotatesMotions) {
addConfigurationProperty("touch.orientationAware", "1");
addConfigurationProperty("touch.orientation", "ORIENTATION_270");
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
- auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Orientation 270.
@@ -6137,13 +6631,13 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio
// orientation-aware are affected by display rotation.
addConfigurationProperty("touch.orientationAware", "0");
addConfigurationProperty("touch.orientation", "ORIENTATION_90");
- auto& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs args;
// Orientation 90, Rotation 0.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
processDown(mapper, RAW_X_MAX - toRotatedRawX(75) + RAW_X_MIN, toRotatedRawY(50));
processSync(mapper);
@@ -6157,8 +6651,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio
// Orientation 90, Rotation 90.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_90);
- processDown(mapper, toRotatedRawX(50), toRotatedRawY(75));
+ prepareDisplay(ui::ROTATION_90);
+ processDown(mapper, toRawX(50), toRawY(75));
processSync(mapper);
EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
@@ -6171,7 +6665,7 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio
// Orientation 90, Rotation 180.
clearViewports();
- prepareDisplay(DISPLAY_ORIENTATION_180);
+ prepareDisplay(ui::ROTATION_180);
processDown(mapper, toRotatedRawX(75), RAW_Y_MAX - toRotatedRawY(50) + RAW_Y_MIN);
processSync(mapper);
@@ -6185,9 +6679,8 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio
// 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);
+ 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));
@@ -6199,12 +6692,101 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenOrientationSpecified_RotatesMotio
EXPECT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled());
}
+TEST_F(SingleTouchInputMapperTest, Process_IgnoresTouchesOutsidePhysicalFrame) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareButtons();
+ prepareAxes(POSITION);
+ addConfigurationProperty("touch.orientationAware", "1");
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // 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);
+
+ // 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 be ignored.
+ const std::array<Point, 6> 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());
+ }
+
+ // Move the touch into the physical frame.
+ processMove(mapper, toRawX(25), toRawY(605));
+ processSync(mapper);
+ 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);
+
+ // 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, Process_DoesntCheckPhysicalFrameForTouchpads) {
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(fakePointerController);
+
+ addConfigurationProperty("touch.deviceType", "pointer");
+ prepareAxes(POSITION);
+ prepareDisplay(ui::ROTATION_0);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // 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);
+
+ // 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<Point, 6> 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_AllAxes_DefaultCalibration) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION | PRESSURE | TOOL | DISTANCE | TILT);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
// These calculations are based on the input device calibration documentation.
int32_t rawX = 100;
@@ -6245,11 +6827,11 @@ TEST_F(SingleTouchInputMapperTest, Process_AllAxes_DefaultCalibration) {
TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareLocationCalibration();
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
int32_t rawX = 100;
int32_t rawY = 200;
@@ -6268,10 +6850,10 @@ TEST_F(SingleTouchInputMapperTest, Process_XYAxes_AffineCalibration) {
TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
NotifyKeyArgs keyArgs;
@@ -6511,10 +7093,10 @@ TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllButtons) {
TEST_F(SingleTouchInputMapperTest, Process_ShouldHandleAllToolTypes) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -6523,14 +7105,14 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6538,7 +7120,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6546,7 +7128,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6554,7 +7136,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6562,7 +7144,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6570,7 +7152,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6578,7 +7160,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6586,7 +7168,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6594,7 +7176,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6602,7 +7184,7 @@ TEST_F(SingleTouchInputMapperTest, 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);
@@ -6610,28 +7192,28 @@ TEST_F(SingleTouchInputMapperTest, 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);
// back to default tool type
processKey(mapper, BTN_TOOL_MOUSE, 0);
@@ -6641,16 +7223,16 @@ TEST_F(SingleTouchInputMapperTest, 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(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueIsZero) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_FINGER, 0, AKEYCODE_UNKNOWN, 0);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -6718,10 +7300,10 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenBtnTouchPresent_HoversIfItsValueI
TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsValueIsZero) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION | PRESSURE);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -6787,19 +7369,66 @@ TEST_F(SingleTouchInputMapperTest, Process_WhenAbsPressureIsPresent_HoversIfItsV
toDisplayX(150), toDisplayY(250), 0, 0, 0, 0, 0, 0, 0, 0));
}
+TEST_F(SingleTouchInputMapperTest, Reset_CancelsOngoingGesture) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION | PRESSURE);
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // Touch down.
+ processDown(mapper, 100, 200);
+ processPressure(mapper, 1);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Reset the mapper. This should cancel the ongoing gesture.
+ resetMapper(mapper, ARBITRARY_TIME);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, Reset_RecreatesTouchState) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION | PRESSURE);
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // 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);
+
+ // 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);
+
+ // 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))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
TEST_F(SingleTouchInputMapperTest,
Process_WhenViewportDisplayIdChanged_TouchIsCanceledAndDeviceIsReset) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
// Down.
- int32_t x = 100;
- int32_t y = 200;
- processDown(mapper, x, y);
+ processDown(mapper, 100, 200);
processSync(mapper);
// We should receive a down event
@@ -6820,10 +7449,10 @@ TEST_F(SingleTouchInputMapperTest,
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>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
NotifyMotionArgs motionArgs;
@@ -6837,7 +7466,7 @@ TEST_F(SingleTouchInputMapperTest,
auto viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
viewport->isActive = false;
mFakePolicy->updateViewport(*viewport);
- configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
// We should receive a cancel event for the ongoing gesture.
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
@@ -6848,30 +7477,242 @@ TEST_F(SingleTouchInputMapperTest,
// No events are generated while the viewport is inactive.
processMove(mapper, 101, 201);
processSync(mapper);
- processDown(mapper, 102, 202);
+ processUp(mapper);
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+ // 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);
+
// Make the viewport active again. The device should resume processing events.
viewport->isActive = true;
mFakePolicy->updateViewport(*viewport);
- configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
// 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());
- // Start a new gesture.
- processDown(mapper, 100, 200);
+ // 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);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
// No more events.
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
}
+TEST_F(SingleTouchInputMapperTest, ButtonIsReleasedOnTouchUp) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+ // Press a stylus button.
+ processKey(mapper, BTN_STYLUS, 1);
+ processSync(mapper);
+
+ // Start a touch gesture and ensure the BUTTON_PRESS event is generated.
+ processDown(mapper, 100, 200);
+ processSync(mapper);
+ 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))));
+}
+
+TEST_F(SingleTouchInputMapperTest, StylusButtonMotionEventsDisabled) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+
+ mFakePolicy->setStylusButtonMotionEventsEnabled(false);
+
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+ // Press a stylus button.
+ processKey(mapper, BTN_STYLUS, 1);
+ processSync(mapper);
+
+ // 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))));
+
+ // Release and press the stylus button again.
+ processKey(mapper, BTN_STYLUS, 0);
+ processSync(mapper);
+ 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))));
+
+ // 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->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsSetToTouchNavigation_setsCorrectType) {
+ mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+ ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mapper.getSources());
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenConfigEnabled_ShouldShowDirectStylusPointer) {
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ 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<SingleTouchInputMapper>();
+
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processMove(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(ToolType::STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_TRUE(fakePointerController->isPointerShown());
+ ASSERT_NO_FATAL_FAILURE(
+ fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
+}
+
+TEST_F(SingleTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ 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<SingleTouchInputMapper>();
+
+ processKey(mapper, BTN_TOOL_PEN, 1);
+ processMove(mapper, 100, 200);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(ToolType::STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_FALSE(fakePointerController->isPointerShown());
+}
+
+TEST_F(SingleTouchInputMapperTest, WhenDeviceTypeIsChangedToTouchNavigation_updatesDeviceType) {
+ // Initialize the device without setting device source to touch navigation.
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // Ensure that the device is created as a touchscreen, not touch navigation.
+ ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
+
+ // Add device type association after the device was created.
+ mFakePolicy->addDeviceTypeAssociation(DEVICE_LOCATION, "touchNavigation");
+
+ // Send update to the mapper.
+ std::list<NotifyArgs> unused2 =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ InputReaderConfiguration::Change::DEVICE_TYPE /*changes*/);
+
+ // Check whether device type update was successful.
+ ASSERT_EQ(AINPUT_SOURCE_TOUCH_NAVIGATION, mDevice->getSources());
+}
+
+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);
+
+ // 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);
+
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // 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)));
+
+ // 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(
+ WithMotionAction(AMOTION_EVENT_ACTION_HOVER_EXIT)));
+
+ // 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());
+
+ // Re-entering the physical frame produces events.
+ processMove(mapper, RAW_X_MIN, RAW_Y_MIN);
+ 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)));
+}
+
// --- TouchDisplayProjectionTest ---
class TouchDisplayProjectionTest : public SingleTouchInputMapperTest {
@@ -6879,30 +7720,30 @@ 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) {
+ void configurePhysicalDisplay(ui::Rotation orientation, Rect naturalPhysicalDisplay,
+ int32_t naturalDisplayWidth = DISPLAY_WIDTH,
+ int32_t naturalDisplayHeight = DISPLAY_HEIGHT) {
uint32_t inverseRotationFlags;
- auto width = DISPLAY_WIDTH;
- auto height = DISPLAY_HEIGHT;
+ auto rotatedWidth = naturalDisplayWidth;
+ auto rotatedHeight = naturalDisplayHeight;
switch (orientation) {
- case DISPLAY_ORIENTATION_90:
+ case ui::ROTATION_90:
inverseRotationFlags = ui::Transform::ROT_270;
- std::swap(width, height);
+ std::swap(rotatedWidth, rotatedHeight);
break;
- case DISPLAY_ORIENTATION_180:
+ case ui::ROTATION_180:
inverseRotationFlags = ui::Transform::ROT_180;
break;
- case DISPLAY_ORIENTATION_270:
+ case ui::ROTATION_270:
inverseRotationFlags = ui::Transform::ROT_90;
- std::swap(width, height);
+ std::swap(rotatedWidth, rotatedHeight);
break;
- case DISPLAY_ORIENTATION_0:
+ case ui::ROTATION_0:
inverseRotationFlags = ui::Transform::ROT_0;
break;
- default:
- FAIL() << "Invalid orientation: " << orientation;
}
- const ui::Transform rotation(inverseRotationFlags, width, height);
+ const ui::Transform rotation(inverseRotationFlags, rotatedWidth, rotatedHeight);
const Rect rotatedPhysicalDisplay = rotation.transform(naturalPhysicalDisplay);
std::optional<DisplayViewport> internalViewport =
@@ -6921,21 +7762,21 @@ public:
v.physicalRight = rotatedPhysicalDisplay.right;
v.physicalBottom = rotatedPhysicalDisplay.bottom;
- v.deviceWidth = width;
- v.deviceHeight = height;
+ v.deviceWidth = rotatedWidth;
+ v.deviceHeight = rotatedHeight;
v.isActive = true;
v.uniqueId = UNIQUE_ID;
v.type = ViewportType::INTERNAL;
mFakePolicy->updateViewport(v);
- configureDevice(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
}
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_EQ(size_t(1), motionArgs.getPointerCount());
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(motionArgs.pointerCoords[0], point.x, point.y,
1, 0, 0, 0, 0, 0, 0, 0));
}
@@ -6943,11 +7784,11 @@ public:
TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -6958,8 +7799,7 @@ TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) {
static const std::array<Point, 6> kPointsOutsidePhysicalDisplay{
{{-10, -10}, {0, 0}, {5, 100}, {50, 15}, {75, 100}, {50, 165}}};
- for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
- DISPLAY_ORIENTATION_270}) {
+ for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) {
configurePhysicalDisplay(orientation, kPhysicalDisplay);
// Touches outside the physical display should be ignored, and should not generate any
@@ -6979,11 +7819,11 @@ TEST_F(TouchDisplayProjectionTest, IgnoresTouchesOutsidePhysicalDisplay) {
TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ prepareDisplay(ui::ROTATION_0);
prepareButtons();
prepareAxes(POSITION);
- SingleTouchInputMapper& mapper = addMapperAndConfigure<SingleTouchInputMapper>();
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -6992,8 +7832,7 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
// 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};
- for (auto orientation : {DISPLAY_ORIENTATION_0, DISPLAY_ORIENTATION_90, DISPLAY_ORIENTATION_180,
- DISPLAY_ORIENTATION_270}) {
+ for (auto orientation : {ui::ROTATION_0, ui::ROTATION_90, ui::ROTATION_180, ui::ROTATION_270}) {
configurePhysicalDisplay(orientation, kPhysicalDisplay);
// Touches that start outside the physical display should be ignored until it enters the
@@ -7009,7 +7848,7 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
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_EQ(size_t(1), motionArgs.getPointerCount());
ASSERT_NO_FATAL_FAILURE(
assertPointerCoords(motionArgs.pointerCoords[0], 11, 21, 1, 0, 0, 0, 0, 0, 0, 0));
@@ -7038,6 +7877,594 @@ TEST_F(TouchDisplayProjectionTest, EmitsTouchDownAfterEnteringPhysicalDisplay) {
}
}
+// --- TouchscreenPrecisionTests ---
+
+// 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<ui::Rotation> {
+public:
+ void SetUp() override {
+ SingleTouchInputMapperTest::SetUp();
+
+ // 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);
+ }
+
+ 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;
+
+ static const int32_t PRECISION_RAW_X_RES = 50; // units per millimeter
+ static const int32_t PRECISION_RAW_Y_RES = 100; // units per millimeter
+
+ static const int32_t PRECISION_RAW_X_FLAT = 16;
+ static const int32_t PRECISION_RAW_Y_FLAT = 32;
+
+ static const int32_t PRECISION_RAW_X_FUZZ = 4;
+ static const int32_t PRECISION_RAW_Y_FUZZ = 8;
+
+ static const std::array<Point, 4> kRawCorners;
+};
+
+const std::array<Point, 4> 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<Orientation, std::array<vec2, 4> /*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<Orientation>(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<SingleTouchInputMapper>();
+
+ // 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_P(TouchscreenPrecisionTestsFixture, RotationPrecisionWhenOrientationAware) {
+ static const std::map<ui::Rotation /*rotation*/, std::array<vec2, 4> /*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(displayRotation);
+
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ const auto& expectedPoints = kMappedCorners.at(displayRotation);
+
+ // 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))));
+ }
+}
+
+TEST_P(TouchscreenPrecisionTestsFixture, RotationPrecisionOrientationAwareInOri270) {
+ static const std::map<ui::Rotation /*orientation*/, std::array<vec2, 4> /*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}}}},
+ };
+
+ const ui::Rotation displayRotation = GetParam();
+
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ addConfigurationProperty("touch.orientation", "ORIENTATION_270");
+
+ SingleTouchInputMapper& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ // 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);
+
+ const auto& expectedPoints = kMappedCorners.at(displayRotation);
+
+ // 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_P(TouchscreenPrecisionTestsFixture, MotionRangesAreOrientedInRotatedDisplay) {
+ const ui::Rotation displayRotation = GetParam();
+
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(displayRotation);
+
+ __attribute__((unused)) SingleTouchInputMapper& mapper =
+ constructAndAddMapper<SingleTouchInputMapper>();
+
+ 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);
+
+ // The MotionRanges should be oriented in the rotated display's coordinate space
+ const bool displayRotated =
+ displayRotation == ui::ROTATION_90 || displayRotation == ui::ROTATION_270;
+
+ 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
+}
+
+// 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<ui::Rotation>& testParamInfo) {
+ return ftl::enum_string(testParamInfo.param);
+ });
+
+// --- ExternalStylusFusionTest ---
+
+class ExternalStylusFusionTest : public SingleTouchInputMapperTest {
+public:
+ SingleTouchInputMapper& initializeInputMapperWithExternalStylus() {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareButtons();
+ prepareAxes(POSITION);
+ auto& mapper = constructAndAddMapper<SingleTouchInputMapper>();
+
+ 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;
+ }
+
+ std::list<NotifyArgs> processExternalStylusState(InputMapper& mapper) {
+ std::list<NotifyArgs> 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;
+ }
+
+protected:
+ StylusState mStylusState{};
+
+ void testStartFusedStylusGesture(SingleTouchInputMapper& mapper) {
+ auto toolTypeSource =
+ AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
+
+ // 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))));
+
+ ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+ }
+
+ void testSuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+ ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
+
+ // Releasing the touch pointer ends the gesture.
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
+ WithToolType(ToolType::STYLUS))));
+
+ mStylusState.pressure = 0.f;
+ processExternalStylusState(mapper);
+ ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+ }
+
+ void testUnsuccessfulFusionGesture(SingleTouchInputMapper& mapper) {
+ // When stylus fusion is not successful, events should be reported with the original source.
+ // In this case, it is from a touchscreen.
+ auto toolTypeSource =
+ AllOf(WithSource(AINPUT_SOURCE_TOUCHSCREEN), WithToolType(ToolType::FINGER));
+
+ // 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));
+
+ // 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));
+
+ // 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))));
+
+ // 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))));
+
+ ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+ }
+
+private:
+ InputDeviceInfo mExternalStylusDeviceInfo{};
+};
+
+TEST_F(ExternalStylusFusionTest, UsesBluetoothStylusSource) {
+ SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+ ASSERT_EQ(STYLUS_FUSION_SOURCE, mapper.getSources());
+}
+
+TEST_F(ExternalStylusFusionTest, UnsuccessfulFusion) {
+ SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+ ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+}
+
+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(STYLUS_FUSION_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))));
+
+ ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(ExternalStylusFusionTest, FusionIsRepeatedForEachNewGesture) {
+ SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+
+ ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+ ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+
+ ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+ ASSERT_NO_FATAL_FAILURE(testSuccessfulFusionGesture(mapper));
+ ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+ ASSERT_NO_FATAL_FAILURE(testUnsuccessfulFusionGesture(mapper));
+}
+
+TEST_F(ExternalStylusFusionTest, FusedPointerReportsPressureChanges) {
+ SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+ auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
+
+ 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());
+
+ // 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));
+
+ // 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());
+
+ // 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));
+
+ // 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))));
+
+ // 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))));
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithSource(STYLUS_FUSION_SOURCE),
+ WithToolType(ToolType::STYLUS))));
+
+ ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(ExternalStylusFusionTest, FusedPointerReportsToolTypeChanges) {
+ SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+ auto source = WithSource(STYLUS_FUSION_SOURCE);
+
+ 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());
+
+ // 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));
+
+ // 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());
+
+ // 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));
+
+ // 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))));
+
+ processUp(mapper);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(source, WithMotionAction(AMOTION_EVENT_ACTION_UP),
+ WithToolType(ToolType::FINGER))));
+
+ ASSERT_NO_FATAL_FAILURE(mReader->getContext()->assertTimeoutWasNotRequested());
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(ExternalStylusFusionTest, FusedPointerReportsButtons) {
+ SingleTouchInputMapper& mapper = initializeInputMapperWithExternalStylus();
+ auto toolTypeSource = AllOf(WithSource(STYLUS_FUSION_SOURCE), WithToolType(ToolType::STYLUS));
+
+ ASSERT_NO_FATAL_FAILURE(testStartFusedStylusGesture(mapper));
+
+ // 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 ---
class MultiTouchInputMapperTest : public TouchInputMapperTest {
@@ -7056,8 +8483,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) {
@@ -7160,20 +8589,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -7197,9 +8633,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(size_t(1), motionArgs.getPointerCount());
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);
@@ -7216,11 +8652,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7247,11 +8683,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7276,11 +8712,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7299,9 +8735,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(size_t(1), motionArgs.getPointerCount());
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);
@@ -7324,9 +8760,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(size_t(1), motionArgs.getPointerCount());
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);
@@ -7351,11 +8787,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7380,11 +8816,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7403,9 +8839,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(size_t(1), motionArgs.getPointerCount());
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);
@@ -7426,9 +8862,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithoutTrackin
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(size_t(1), motionArgs.getPointerCount());
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);
@@ -7442,7 +8878,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);
@@ -7457,7 +8893,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// X and Y axes
assertAxisResolution(mapper, AMOTION_EVENT_AXIS_X, 10 / X_PRECISION);
@@ -7472,7 +8908,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);
@@ -7481,7 +8917,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// Touch major and minor
assertAxisNotPresent(mapper, AMOTION_EVENT_AXIS_TOUCH_MAJOR);
@@ -7493,10 +8929,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -7514,19 +8950,19 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7544,11 +8980,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7563,11 +8999,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7575,9 +9011,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7590,9 +9026,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7608,11 +9044,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7627,11 +9063,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7639,9 +9075,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7651,9 +9087,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithTrackingId
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7664,10 +9100,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
mReader->getContext()->setGlobalMetaState(AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_ON);
@@ -7684,19 +9120,19 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7712,11 +9148,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7732,11 +9168,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_0_UP, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7744,9 +9180,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7757,9 +9193,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7773,11 +9209,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_0_DOWN, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7793,11 +9229,11 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
- ASSERT_EQ(size_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(2), motionArgs.getPointerCount());
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],
@@ -7805,9 +9241,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7817,9 +9253,9 @@ TEST_F(MultiTouchInputMapperTest, Process_NormalMultiTouchGesture_WithSlots) {
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
- ASSERT_EQ(size_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(size_t(1), motionArgs.getPointerCount());
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));
@@ -7830,9 +9266,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// These calculations are based on the input device calibration documentation.
int32_t rawX = 100;
@@ -7879,10 +9315,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// These calculations are based on the input device calibration documentation.
int32_t rawX = 100;
@@ -7916,13 +9352,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// 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
@@ -7958,7 +9394,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchAndToolAxes_SummedLinearCalibrati
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
- ASSERT_EQ(size_t(2), args.pointerCount);
+ ASSERT_EQ(size_t(2), args.getPointerCount());
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0],
x, y, 1.0f, size, touch, touch, tool, tool, 0, 0));
ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[1],
@@ -7967,12 +9403,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// These calculations are based on the input device calibration documentation.
int32_t rawX = 100;
@@ -8000,14 +9436,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
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));
@@ -8034,9 +9470,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
NotifyKeyArgs keyArgs;
@@ -8275,11 +9711,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<MultiTouchInputMapper>();
+
+ 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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -8289,14 +9782,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);
@@ -8304,7 +9797,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);
@@ -8312,7 +9805,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);
@@ -8320,7 +9813,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);
@@ -8328,7 +9821,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);
@@ -8336,7 +9829,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);
@@ -8344,7 +9837,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);
@@ -8352,7 +9845,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);
@@ -8360,7 +9853,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);
@@ -8368,7 +9861,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);
@@ -8376,42 +9869,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
@@ -8422,15 +9915,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -8498,9 +9991,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -8581,7 +10074,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareAxes(POSITION);
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
mFakePolicy->addInputPortAssociation(usb2, hdmi2);
@@ -8598,7 +10091,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);
@@ -8611,12 +10104,12 @@ TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayPort) {
TEST_F(MultiTouchInputMapperTest, Configure_AssignsDisplayUniqueId) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareAxes(POSITION);
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
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);
@@ -8633,15 +10126,14 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
std::make_shared<FakePointerController>();
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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// Check source is mouse that would obtain the PointerController.
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
@@ -8661,21 +10153,21 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShouldHandleDisplayId) {
TEST_F(MultiTouchInputMapperTest, Process_SendsReadTime) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareAxes(POSITION);
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
- 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);
@@ -8687,12 +10179,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
processPosition(mapper, 100, 100);
@@ -8701,19 +10193,45 @@ 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<MultiTouchInputMapper>();
+
+ NotifyMotionArgs motionArgs;
+ processPosition(mapper, 100, 100);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+}
+
+/**
+ * When the viewport is deactivated (isActive transitions from true to false),
+ * and touch.enableForInactiveViewport is false, touches prior to the transition
+ * should be cancelled.
+ */
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<DisplayViewport> 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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// Finger down
int32_t x = 100, y = 100;
@@ -8727,7 +10245,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));
@@ -8742,7 +10260,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;
@@ -8752,11 +10270,65 @@ TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_AbortTouches) {
EXPECT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
}
+/**
+ * When the viewport is deactivated (isActive transitions from true to false),
+ * and touch.enableForInactiveViewport is true, touches prior to the transition
+ * should not be cancelled.
+ */
+TEST_F(MultiTouchInputMapperTest, Process_DeactivateViewport_TouchesNotAborted) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ addConfigurationProperty("touch.enableForInactiveViewport", "1");
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, UNIQUE_ID, NO_PORT, ViewportType::INTERNAL);
+ std::optional<DisplayViewport> optionalDisplayViewport =
+ mFakePolicy->getDisplayViewportByUniqueId(UNIQUE_ID);
+ ASSERT_TRUE(optionalDisplayViewport.has_value());
+ DisplayViewport displayViewport = *optionalDisplayViewport;
+
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+ prepareAxes(POSITION);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+ // Finger down
+ int32_t x = 100, y = 100;
+ processPosition(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Deactivate display viewport
+ displayViewport.isActive = false;
+ ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ // The ongoing touch should not be canceled
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+ // Finger move is not ignored
+ x += 10, y += 10;
+ processPosition(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+ // Reactivate display viewport
+ displayViewport.isActive = true;
+ ASSERT_TRUE(mFakePolicy->updateViewport(displayViewport));
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ // Finger move continues and does not start new gesture
+ x += 10, y += 10;
+ processPosition(mapper, x, y);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+}
+
TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
// Setup the first touch screen device.
prepareAxes(POSITION | ID | SLOT);
addConfigurationProperty("touch.deviceType", "touchScreen");
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// Create the second touch screen device, and enable multi fingers.
const std::string USB2 = "USB2";
@@ -8768,21 +10340,25 @@ TEST_F(MultiTouchInputMapperTest, Process_Pointer_ShowTouches) {
ftl::Flags<InputDeviceClass>(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<MultiTouchInputMapper>(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<NotifyArgs> unused =
+ device2->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+ unused += device2->reset(ARBITRARY_TIME);
// Setup PointerController.
std::shared_ptr<FakePointerController> fakePointerController =
@@ -8797,13 +10373,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;
@@ -8833,8 +10409,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());
}
@@ -8842,8 +10418,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<MultiTouchInputMapper>();
+ prepareDisplay(ui::ROTATION_0);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
// Unrotated video frame
@@ -8867,14 +10443,13 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_ReceivedByListener) {
TEST_F(MultiTouchInputMapperTest, VideoFrames_AreNotRotated) {
prepareAxes(POSITION);
addConfigurationProperty("touch.deviceType", "touchScreen");
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// 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<ui::Rotation>()) {
SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
clearViewports();
prepareDisplay(orientation);
@@ -8893,14 +10468,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// 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<ui::Rotation>()) {
SCOPED_TRACE("Orientation " + StringPrintf("%i", orientation));
clearViewports();
prepareDisplay(orientation);
@@ -8925,7 +10499,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_AreRotated
TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) {
prepareAxes(POSITION);
addConfigurationProperty("touch.deviceType", "touchScreen");
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// 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});
@@ -8934,7 +10508,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_MultipleFramesAreNotRotated) {
std::vector<TouchVideoFrame> 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);
@@ -8948,7 +10522,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// 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});
@@ -8957,7 +10531,7 @@ TEST_F(MultiTouchInputMapperTest, VideoFrames_WhenNotOrientationAware_MultipleFr
std::vector<TouchVideoFrame> 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);
@@ -8967,7 +10541,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);
}
@@ -8985,7 +10559,7 @@ TEST_F(MultiTouchInputMapperTest, Configure_EnabledForAssociatedDisplay) {
addConfigurationProperty("touch.deviceType", "touchScreen");
prepareAxes(POSITION);
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
ASSERT_EQ(mDevice->isEnabled(), false);
@@ -9004,9 +10578,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -9017,7 +10591,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);
@@ -9025,14 +10599,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);
@@ -9040,7 +10614,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);
}
/**
@@ -9049,9 +10623,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -9062,7 +10636,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);
@@ -9088,7 +10662,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);
}
/**
@@ -9097,9 +10671,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -9110,7 +10684,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);
@@ -9119,7 +10693,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.
@@ -9138,7 +10712,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers)
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// First finger up. It used to be in palm mode, and we already generated ACTION_POINTER_UP for
// it. Second finger receive move.
@@ -9147,7 +10721,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers)
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// Second finger keeps moving.
processSlot(mapper, SECOND_SLOT);
@@ -9156,7 +10730,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_TwoPointers)
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// Second finger up.
processId(mapper, INVALID_TRACKING_ID);
@@ -9172,9 +10746,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -9185,7 +10759,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);
@@ -9194,7 +10768,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.
@@ -9229,8 +10803,8 @@ 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(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(ToolType::FINGER, motionArgs.pointerProperties[0].toolType);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// third finger move
processId(mapper, THIRD_TRACKING_ID);
@@ -9245,7 +10819,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// second finger up, third finger receive move.
processSlot(mapper, SECOND_SLOT);
@@ -9253,7 +10827,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_ShouldCancelW
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// third finger up.
processSlot(mapper, THIRD_SLOT);
@@ -9270,9 +10844,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -9283,7 +10857,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);
@@ -9292,7 +10866,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.
@@ -9310,7 +10884,7 @@ TEST_F(MultiTouchInputMapperTest, Process_ShouldHandlePalmToolType_KeepFirstPoin
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// second finger up.
processSlot(mapper, SECOND_SLOT);
@@ -9342,9 +10916,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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
NotifyMotionArgs motionArgs;
@@ -9356,7 +10930,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// First finger move.
processId(mapper, FIRST_TRACKING_ID);
@@ -9365,7 +10939,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
// Second finger down.
processSlot(mapper, SECOND_SLOT);
@@ -9375,7 +10949,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_1_DOWN, motionArgs.action);
- ASSERT_EQ(uint32_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(2), motionArgs.getPointerCount());
// second finger up with some unexpected data.
processSlot(mapper, SECOND_SLOT);
@@ -9384,7 +10958,7 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(ACTION_POINTER_1_UP, motionArgs.action);
- ASSERT_EQ(uint32_t(2), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(2), motionArgs.getPointerCount());
// first finger up with some unexpected data.
processSlot(mapper, FIRST_SLOT);
@@ -9394,7 +10968,247 @@ TEST_F(MultiTouchInputMapperTest, Process_MultiTouch_WithInvalidTrackingId) {
processSync(mapper);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
- ASSERT_EQ(uint32_t(1), motionArgs.pointerCount);
+ ASSERT_EQ(uint32_t(1), motionArgs.getPointerCount());
+}
+
+TEST_F(MultiTouchInputMapperTest, ResetClearsTouchState) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareAxes(POSITION | ID | SLOT | PRESSURE);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+ // 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(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Second finger down.
+ processSlot(mapper, SECOND_SLOT);
+ processId(mapper, SECOND_TRACKING_ID);
+ processPosition(mapper, 300, 400);
+ processPressure(mapper, RAW_PRESSURE_MAX);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(
+ mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(ACTION_POINTER_1_DOWN)));
+
+ // Reset the mapper. When the mapper is reset, the touch state is also cleared.
+ resetMapper(mapper, ARBITRARY_TIME);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
+
+ // Move the second slot pointer, and ensure there are no events, because the touch state was
+ // cleared and no slots should be in use.
+ processPosition(mapper, 301, 302);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+ // Release both fingers.
+ processId(mapper, INVALID_TRACKING_ID);
+ processSlot(mapper, FIRST_SLOT);
+ processId(mapper, INVALID_TRACKING_ID);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+ // Start a new gesture, and ensure we get a DOWN event for it.
+ processId(mapper, FIRST_TRACKING_ID);
+ processPosition(mapper, 200, 300);
+ processPressure(mapper, RAW_PRESSURE_MAX);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPressure(1.f))));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+}
+
+TEST_F(MultiTouchInputMapperTest, ResetClearsTouchStateWithNoPointersDown) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareAxes(POSITION | ID | SLOT | PRESSURE);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+ // 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<MultiTouchInputMapper>();
+ 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> fakePointerController =
+ std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(fakePointerController);
+ mFakePolicy->setStylusPointerIconEnabled(true);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+ processId(mapper, FIRST_TRACKING_ID);
+ processPressure(mapper, RAW_PRESSURE_MIN);
+ processPosition(mapper, 100, 200);
+ processToolType(mapper, MT_TOOL_PEN);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(ToolType::STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_TRUE(fakePointerController->isPointerShown());
+ ASSERT_NO_FATAL_FAILURE(
+ fakePointerController->assertPosition(toDisplayX(100), toDisplayY(200)));
+}
+
+TEST_F(MultiTouchInputMapperTest, Process_WhenConfigDisabled_ShouldNotShowDirectStylusPointer) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareAxes(POSITION | ID | SLOT | TOOL_TYPE | PRESSURE);
+ // Add BTN_TOOL_PEN to statically show stylus support, since using ABS_MT_TOOL_TYPE can only
+ // indicate stylus presence dynamically.
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ std::shared_ptr<FakePointerController> fakePointerController =
+ std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(fakePointerController);
+ mFakePolicy->setStylusPointerIconEnabled(false);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+ processId(mapper, FIRST_TRACKING_ID);
+ processPressure(mapper, RAW_PRESSURE_MIN);
+ processPosition(mapper, 100, 200);
+ processToolType(mapper, MT_TOOL_PEN);
+ processSync(mapper);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_ENTER),
+ WithToolType(ToolType::STYLUS),
+ WithPointerCoords(0, toDisplayX(100), toDisplayY(200)))));
+ ASSERT_FALSE(fakePointerController->isPointerShown());
+}
+
+TEST_F(MultiTouchInputMapperTest, SimulateKernelBufferOverflow) {
+ addConfigurationProperty("touch.deviceType", "touchScreen");
+ prepareDisplay(ui::ROTATION_0);
+ prepareAxes(POSITION | ID | SLOT | PRESSURE);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+
+ // 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(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // Assume the kernel buffer overflows, and we get a SYN_DROPPED event.
+ // This will reset the mapper, and thus also reset the touch state.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_DROPPED, 0);
+ resetMapper(mapper, ARBITRARY_TIME);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_CANCEL)));
+
+ // Since the touch state was reset, it doesn't know which slots are active, so any movements
+ // are ignored.
+ processPosition(mapper, 101, 201);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
+
+ // Second finger goes down. This is the first active finger, so we get a DOWN event.
+ processSlot(mapper, SECOND_SLOT);
+ processId(mapper, SECOND_TRACKING_ID);
+ processPosition(mapper, 400, 500);
+ processPressure(mapper, RAW_PRESSURE_MAX);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_DOWN)));
+
+ // First slot is still ignored, only the second one is active.
+ processSlot(mapper, FIRST_SLOT);
+ processPosition(mapper, 102, 202);
+ processSlot(mapper, SECOND_SLOT);
+ processPosition(mapper, 401, 501);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ WithMotionAction(AMOTION_EVENT_ACTION_MOVE)));
+
+ // Both slots up, so we get the UP event for the active pointer.
+ processSlot(mapper, FIRST_SLOT);
+ processId(mapper, INVALID_TRACKING_ID);
+ processSlot(mapper, SECOND_SLOT);
+ processId(mapper, INVALID_TRACKING_ID);
+ processSync(mapper);
+
+ ASSERT_NO_FATAL_FAILURE(
+ mFakeListener->assertNotifyMotionWasCalled(WithMotionAction(AMOTION_EVENT_ACTION_UP)));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}
// --- MultiTouchInputMapperTest_ExternalDevice ---
@@ -9410,8 +11224,8 @@ protected:
TEST_F(MultiTouchInputMapperTest_ExternalDevice, Viewports_Fallback) {
prepareAxes(POSITION);
addConfigurationProperty("touch.deviceType", "touchScreen");
- prepareDisplay(DISPLAY_ORIENTATION_0);
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ prepareDisplay(ui::ROTATION_0);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
ASSERT_EQ(AINPUT_SOURCE_TOUCHSCREEN, mapper.getSources());
@@ -9438,16 +11252,15 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
std::make_shared<FakePointerController>();
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>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// captured touchpad should be a touchpad source
NotifyDeviceResetArgs resetArgs;
@@ -9481,7 +11294,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
NotifyMotionArgs args;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
- ASSERT_EQ(1U, args.pointerCount);
+ ASSERT_EQ(1U, args.getPointerCount());
ASSERT_EQ(0, args.pointerProperties[0].id);
ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, args.source);
ASSERT_NO_FATAL_FAILURE(
@@ -9496,7 +11309,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
// expect coord[0] to contain previous location, coord[1] to contain new touch 1 location
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
- ASSERT_EQ(2U, args.pointerCount);
+ ASSERT_EQ(2U, args.getPointerCount());
ASSERT_EQ(0, args.pointerProperties[0].id);
ASSERT_EQ(1, args.pointerProperties[1].id);
ASSERT_NO_FATAL_FAILURE(
@@ -9564,7 +11377,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
// 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(1U, args.getPointerCount());
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));
@@ -9578,7 +11391,7 @@ TEST_F(MultiTouchInputMapperTest, Process_TouchpadCapture) {
// non captured touchpad should be a mouse source
mFakePolicy->setPointerCapture(false);
- configureDevice(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
+ configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(AINPUT_SOURCE_MOUSE, mapper.getSources());
}
@@ -9588,15 +11401,14 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
std::make_shared<FakePointerController>();
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->setPointerController(fakePointerController);
- MultiTouchInputMapper& mapper = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// run uncaptured pointer tests - pushes out generic events
// FINGER 0 DOWN
processId(mapper, 3);
@@ -9626,28 +11438,443 @@ TEST_F(MultiTouchInputMapperTest, Process_UnCapturedTouchpadPointer) {
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> fakePointerController =
std::make_shared<FakePointerController>();
- prepareDisplay(DISPLAY_ORIENTATION_0);
+ 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 = addMapperAndConfigure<MultiTouchInputMapper>();
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
// 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);
+ 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<MultiTouchInputMapper>();
+
+ 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> fakePointerController =
+ std::make_shared<FakePointerController>();
+ 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<MultiTouchInputMapper>();
+ 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.getPointerCount());
+ 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.getPointerCount());
+ 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<MultiTouchInputMapper>();
+ 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.getPointerCount());
+ 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.getPointerCount());
+ 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<MultiTouchInputMapper>();
+
+ NotifyMotionArgs motionArgs;
+
+ // 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;
+
+ 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.getPointerCount());
+ 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));
+
+ int32_t movingDistance = 200;
+
+ // 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);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ // The previous PRESS gesture is cancelled, because it is transformed to freeform
+ ASSERT_EQ(1U, motionArgs.getPointerCount());
+ 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.getPointerCount());
+ 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.getPointerCount());
+ 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;
+
+ 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(2U, motionArgs.getPointerCount());
+ 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<MultiTouchInputMapper>();
+ NotifyMotionArgs motionArgs;
+
+ // Place two fingers down.
+ 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.getPointerCount());
+ 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));
+
+ // Move the two fingers down and to the left.
+ int32_t movingDistance = 200;
+ x1 -= movingDistance;
+ y1 += movingDistance;
+ x2 -= 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.getPointerCount());
+ 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(MultiTouchPointerModeTest, WhenViewportActiveStatusChanged_PointerGestureIsReset) {
+ preparePointerMode(/*xResolution=*/25, /*yResolution=*/25);
+ mFakeEventHub->addKey(EVENTHUB_ID, BTN_TOOL_PEN, 0, AKEYCODE_UNKNOWN, 0);
+ MultiTouchInputMapper& mapper = constructAndAddMapper<MultiTouchInputMapper>();
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
+
+ // 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());
+
+ // 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 ---
class JoystickInputMapperTest : public InputMapperTest {
@@ -9673,7 +11900,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);
@@ -9687,11 +11914,11 @@ const int32_t JoystickInputMapperTest::RAW_Y_MAX = 32767;
TEST_F(JoystickInputMapperTest, Configure_AssignsDisplayUniqueId) {
prepareAxes();
- JoystickInputMapper& mapper = addMapperAndConfigure<JoystickInputMapper>();
+ JoystickInputMapper& mapper = constructAndAddMapper<JoystickInputMapper>();
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);
@@ -9729,7 +11956,7 @@ protected:
virtual void SetUp(ftl::Flags<InputDeviceClass> classes) {
mFakeEventHub = std::make_unique<FakeEventHub>();
- mFakePolicy = new FakeInputReaderPolicy();
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
mFakeListener = std::make_unique<TestInputListener>();
mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
*mFakeListener);
@@ -9743,14 +11970,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<InputDevice> newDevice(int32_t deviceId, const std::string& name,
const std::string& location, int32_t eventHubId,
ftl::Flags<InputDeviceClass> classes) {
@@ -9794,15 +12013,17 @@ protected:
TEST_F(BatteryControllerTest, GetBatteryCapacity) {
PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
- 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<PeripheralController>();
- 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 ---
@@ -9815,7 +12036,7 @@ protected:
TEST_F(LightControllerTest, MonoLight) {
RawLightInfo infoMono = {.id = 1,
- .name = "Mono",
+ .name = "mono_light",
.maxBrightness = 255,
.flags = InputLightClass::BRIGHTNESS,
.path = ""};
@@ -9826,12 +12047,129 @@ TEST_F(LightControllerTest, MonoLight) {
controller.populateDeviceInfo(&info);
std::vector<InputDeviceLightInfo> 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<PeripheralController>();
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> 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);
}
+TEST_F(LightControllerTest, Ignore_MonoLight_WithPreferredBacklightLevels) {
+ RawLightInfo infoMono = {.id = 1,
+ .name = "mono_light",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono));
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "keyboard.backlight.brightnessLevels",
+ "0,100,200");
+
+ PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> lights = info.getLights();
+ ASSERT_EQ(1U, lights.size());
+ ASSERT_EQ(0U, lights[0].preferredBrightnessLevels.size());
+}
+
+TEST_F(LightControllerTest, KeyboardBacklight_WithNoPreferredBacklightLevels) {
+ 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<PeripheralController>();
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> lights = info.getLights();
+ ASSERT_EQ(1U, lights.size());
+ ASSERT_EQ(0U, lights[0].preferredBrightnessLevels.size());
+}
+
+TEST_F(LightControllerTest, KeyboardBacklight_WithPreferredBacklightLevels) {
+ RawLightInfo infoMono = {.id = 1,
+ .name = "mono_keyboard_backlight",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS |
+ InputLightClass::KEYBOARD_BACKLIGHT,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono));
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "keyboard.backlight.brightnessLevels",
+ "0,100,200");
+
+ PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> lights = info.getLights();
+ ASSERT_EQ(1U, lights.size());
+ ASSERT_EQ(3U, lights[0].preferredBrightnessLevels.size());
+ std::set<BrightnessLevel>::iterator it = lights[0].preferredBrightnessLevels.begin();
+ ASSERT_EQ(BrightnessLevel(0), *it);
+ std::advance(it, 1);
+ ASSERT_EQ(BrightnessLevel(100), *it);
+ std::advance(it, 1);
+ ASSERT_EQ(BrightnessLevel(200), *it);
+}
+
+TEST_F(LightControllerTest, KeyboardBacklight_WithWrongPreferredBacklightLevels) {
+ RawLightInfo infoMono = {.id = 1,
+ .name = "mono_keyboard_backlight",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS |
+ InputLightClass::KEYBOARD_BACKLIGHT,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(infoMono.id, std::move(infoMono));
+ mFakeEventHub->addConfigurationProperty(EVENTHUB_ID, "keyboard.backlight.brightnessLevels",
+ "0,100,200,300,400,500");
+
+ PeripheralController& controller = addControllerAndConfigure<PeripheralController>();
+ std::list<NotifyArgs> unused =
+ mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(),
+ /*changes=*/{});
+
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> lights = info.getLights();
+ ASSERT_EQ(1U, lights.size());
+ ASSERT_EQ(0U, lights[0].preferredBrightnessLevels.size());
+}
+
TEST_F(LightControllerTest, RGBLight) {
RawLightInfo infoRed = {.id = 1,
.name = "red",
@@ -9857,7 +12195,85 @@ TEST_F(LightControllerTest, RGBLight) {
controller.populateDeviceInfo(&info);
std::vector<InputDeviceLightInfo> 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<PeripheralController>();
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> 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<PeripheralController>();
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> 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);
@@ -9865,7 +12281,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 |
@@ -9879,7 +12295,34 @@ TEST_F(LightControllerTest, MultiColorRGBLight) {
controller.populateDeviceInfo(&info);
std::vector<InputDeviceLightInfo> 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<PeripheralController>();
+ InputDeviceInfo info;
+ controller.populateDeviceInfo(&info);
+ std::vector<InputDeviceLightInfo> 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);
@@ -9917,6 +12360,8 @@ TEST_F(LightControllerTest, PlayerIdLight) {
std::vector<InputDeviceLightInfo> 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 0000000000..110ca5fab0
--- /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<EventHubInterface> eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ InputListenerInterface& listener)
+ : InputReader(eventHub, policy, listener), mFakeContext(this) {}
+
+void InstrumentedInputReader::pushNextDevice(std::shared_ptr<InputDevice> device) {
+ mNextDevices.push(device);
+}
+
+std::shared_ptr<InputDevice> 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<InputDevice>(&mFakeContext, deviceId, generation, identifier);
+}
+
+std::shared_ptr<InputDevice> InstrumentedInputReader::createDeviceLocked(
+ nsecs_t when, int32_t eventHubId, const InputDeviceIdentifier& identifier) REQUIRES(mLock) {
+ if (!mNextDevices.empty()) {
+ std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
+ mNextDevices.pop();
+ return device;
+ }
+ return InputReader::createDeviceLocked(when, eventHubId, identifier);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/InstrumentedInputReader.h b/services/inputflinger/tests/InstrumentedInputReader.h
new file mode 100644
index 0000000000..e9c7bb44e8
--- /dev/null
+++ b/services/inputflinger/tests/InstrumentedInputReader.h
@@ -0,0 +1,131 @@
+/*
+ * 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 <memory>
+#include <queue>
+#include <string>
+
+#include <InputDevice.h>
+#include <InputReader.h>
+#include <gtest/gtest.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+class InstrumentedInputReader : public InputReader {
+public:
+ InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
+ const sp<InputReaderPolicyInterface>& policy,
+ InputListenerInterface& listener);
+ virtual ~InstrumentedInputReader() {}
+
+ void pushNextDevice(std::shared_ptr<InputDevice> device);
+
+ std::shared_ptr<InputDevice> 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<InputDevice> createDeviceLocked(
+ nsecs_t when, 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<InputDeviceInfo>& outDevices) override {
+ outDevices = mExternalStylusDevices;
+ }
+
+ void setExternalStylusDevices(std::vector<InputDeviceInfo>&& devices) {
+ mExternalStylusDevices = devices;
+ }
+
+ void setPreventingTouchpadTaps(bool prevent) override { mPreventingTouchpadTaps = prevent; }
+ bool isPreventingTouchpadTaps() override { return mPreventingTouchpadTaps; }
+
+ void setLastKeyDownTimestamp(nsecs_t when) override { mLastKeyDownTimestamp = when; };
+ nsecs_t getLastKeyDownTimestamp() override { return mLastKeyDownTimestamp; };
+
+ private:
+ int32_t mGlobalMetaState;
+ bool mUpdateGlobalMetaStateWasCalled;
+ int32_t mGeneration;
+ std::optional<nsecs_t> mRequestedTimeout;
+ std::vector<InputDeviceInfo> mExternalStylusDevices;
+ bool mPreventingTouchpadTaps{false};
+ nsecs_t mLastKeyDownTimestamp;
+ } mFakeContext;
+
+ friend class InputReaderTest;
+
+public:
+ FakeInputReaderContext* getContext() { return &mFakeContext; }
+
+private:
+ std::queue<std::shared_ptr<InputDevice>> mNextDevices;
+};
+
+} // namespace android
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
new file mode 100644
index 0000000000..73949136f4
--- /dev/null
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -0,0 +1,179 @@
+/*
+ * 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 <cstdint>
+#include <list>
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+
+#include <EventHub.h>
+#include <InputReaderBase.h>
+#include <NotifyArgs.h>
+#include <PointerControllerInterface.h>
+#include <StylusState.h>
+#include <VibrationElement.h>
+#include <android-base/logging.h>
+#include <android-base/result.h>
+#include <gmock/gmock.h>
+#include <input/InputDevice.h>
+#include <input/KeyCharacterMap.h>
+#include <input/KeyLayoutMap.h>
+#include <input/PropertyMap.h>
+#include <input/TouchVideoFrame.h>
+#include <input/VirtualKeyMap.h>
+#include <utils/Errors.h>
+#include <utils/Timers.h>
+
+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<PointerControllerInterface>, getPointerController,
+ (int32_t deviceId), (override));
+
+ MOCK_METHOD(void, requestTimeoutAtTime, (nsecs_t when), (override));
+ int32_t bumpGeneration() override { return ++mGeneration; }
+
+ MOCK_METHOD(void, getExternalStylusDevices, (std::vector<InputDeviceInfo> & outDevices),
+ (override));
+ MOCK_METHOD(std::list<NotifyArgs>, 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));
+
+ MOCK_METHOD(void, setPreventingTouchpadTaps, (bool prevent), (override));
+ MOCK_METHOD(bool, isPreventingTouchpadTaps, (), (override));
+
+ MOCK_METHOD(void, setLastKeyDownTimestamp, (nsecs_t when));
+ MOCK_METHOD(nsecs_t, getLastKeyDownTimestamp, ());
+
+private:
+ int32_t mGeneration = 0;
+};
+
+class MockEventHubInterface : public EventHubInterface {
+public:
+ MOCK_METHOD(ftl::Flags<InputDeviceClass>, 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<PropertyMap>, 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<std::string>& devices));
+ MOCK_METHOD(std::vector<RawEvent>, getEvents, (int timeoutMillis));
+ MOCK_METHOD(std::vector<TouchVideoFrame>, getVideoFrames, (int32_t deviceId));
+ MOCK_METHOD((base::Result<std::pair<InputDeviceSensorType, int32_t>>), mapSensor,
+ (int32_t deviceId, int32_t absCode), (const, override));
+ MOCK_METHOD(std::vector<int32_t>, getRawBatteryIds, (int32_t deviceId), (const, override));
+ MOCK_METHOD(std::optional<RawBatteryInfo>, getRawBatteryInfo,
+ (int32_t deviceId, int32_t BatteryId), (const, override));
+ MOCK_METHOD(std::vector<int32_t>, getRawLightIds, (int32_t deviceId), (const, override));
+ MOCK_METHOD(std::optional<RawLightInfo>, getRawLightInfo, (int32_t deviceId, int32_t lightId),
+ (const, override));
+ MOCK_METHOD(std::optional<int32_t>, 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<std::unordered_map<LightColor, int32_t>>), getLightIntensities,
+ (int32_t deviceId, int32_t lightId), (const, override));
+ MOCK_METHOD(void, setLightIntensities,
+ (int32_t deviceId, int32_t lightId,
+ (std::unordered_map<LightColor, int32_t>)intensities),
+ (override));
+
+ MOCK_METHOD(std::optional<RawLayoutInfo>, 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<int32_t>& 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<VirtualKeyDefinition>& outVirtualKeys),
+ (const, override));
+
+ MOCK_METHOD(const std::shared_ptr<KeyCharacterMap>, getKeyCharacterMap, (int32_t deviceId),
+ (const, override));
+
+ MOCK_METHOD(bool, setKeyboardLayoutOverlay,
+ (int32_t deviceId, std::shared_ptr<KeyCharacterMap> 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<int32_t>, getVibratorIds, (int32_t deviceId), (const, override));
+ MOCK_METHOD(std::optional<int32_t>, getBatteryCapacity, (int32_t deviceId, int32_t batteryId),
+ (const, override));
+
+ MOCK_METHOD(std::optional<int32_t>, 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/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
new file mode 100644
index 0000000000..2ef79999a2
--- /dev/null
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -0,0 +1,176 @@
+/*
+ * 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 "KeyboardInputMapper.h"
+
+#include <gtest/gtest.h>
+
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+
+#define TAG "KeyboardInputMapper_test"
+
+namespace android {
+
+using testing::_;
+using testing::Args;
+using testing::DoAll;
+using testing::Return;
+using testing::SetArgPointee;
+
+/**
+ * Unit tests for KeyboardInputMapper.
+ */
+class KeyboardInputMapperUnitTest : public InputMapperUnitTest {
+protected:
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ const std::unordered_map<int32_t, int32_t> mKeyCodeMap{{KEY_0, AKEYCODE_0},
+ {KEY_A, AKEYCODE_A},
+ {KEY_LEFTCTRL, AKEYCODE_CTRL_LEFT},
+ {KEY_LEFTALT, AKEYCODE_ALT_LEFT},
+ {KEY_RIGHTALT, AKEYCODE_ALT_RIGHT},
+ {KEY_LEFTSHIFT, AKEYCODE_SHIFT_LEFT},
+ {KEY_RIGHTSHIFT, AKEYCODE_SHIFT_RIGHT},
+ {KEY_FN, AKEYCODE_FUNCTION},
+ {KEY_LEFTCTRL, AKEYCODE_CTRL_LEFT},
+ {KEY_RIGHTCTRL, AKEYCODE_CTRL_RIGHT},
+ {KEY_LEFTMETA, AKEYCODE_META_LEFT},
+ {KEY_RIGHTMETA, AKEYCODE_META_RIGHT},
+ {KEY_CAPSLOCK, AKEYCODE_CAPS_LOCK},
+ {KEY_NUMLOCK, AKEYCODE_NUM_LOCK},
+ {KEY_SCROLLLOCK, AKEYCODE_SCROLL_LOCK}};
+
+ void SetUp() override {
+ InputMapperUnitTest::SetUp();
+
+ // set key-codes expected in tests
+ for (const auto& [scanCode, outKeycode] : mKeyCodeMap) {
+ EXPECT_CALL(mMockEventHub, mapKey(EVENTHUB_ID, scanCode, _, _, _, _, _))
+ .WillRepeatedly(DoAll(SetArgPointee<4>(outKeycode), Return(NO_ERROR)));
+ }
+
+ mFakePolicy = sp<FakeInputReaderPolicy>::make();
+ EXPECT_CALL(mMockInputReaderContext, getPolicy).WillRepeatedly(Return(mFakePolicy.get()));
+
+ mMapper = createInputMapper<KeyboardInputMapper>(*mDeviceContext, mReaderConfiguration,
+ AINPUT_SOURCE_KEYBOARD,
+ AINPUT_KEYBOARD_TYPE_ALPHABETIC);
+ }
+
+ void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
+ EXPECT_CALL(mMockInputReaderContext, fadePointer)
+ .Times(expectVisible ? 0 : keyCodes.size());
+ for (int32_t keyCode : keyCodes) {
+ process(EV_KEY, keyCode, 1);
+ process(EV_SYN, SYN_REPORT, 0);
+ process(EV_KEY, keyCode, 0);
+ process(EV_SYN, SYN_REPORT, 0);
+ }
+ }
+
+ void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
+ const bool expectPrevent) {
+ EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).Times(keyCodes.size());
+ if (expectPrevent) {
+ EXPECT_CALL(mMockInputReaderContext, setPreventingTouchpadTaps(true))
+ .Times(keyCodes.size());
+ }
+ for (int32_t keyCode : keyCodes) {
+ process(EV_KEY, keyCode, 1);
+ process(EV_SYN, SYN_REPORT, 0);
+ process(EV_KEY, keyCode, 0);
+ process(EV_SYN, SYN_REPORT, 0);
+ }
+ }
+};
+
+/**
+ * Pointer visibility should remain unaffected if there is no active Input Method Connection
+ */
+TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDoesNotHidePointer) {
+ testPointerVisibilityForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectVisible= */ true);
+}
+
+/**
+ * Pointer should hide if there is a active Input Method Connection
+ */
+TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionHidePointer) {
+ mFakePolicy->setIsInputMethodConnectionActive(true);
+ testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
+}
+
+/**
+ * Pointer should still hide if touchpad taps are already disabled
+ */
+TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
+ mFakePolicy->setIsInputMethodConnectionActive(true);
+ EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
+ testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
+}
+
+/**
+ * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
+ * active
+ */
+TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDoesNotHidePointer) {
+ mFakePolicy->setIsInputMethodConnectionActive(true);
+ std::vector<int32_t> metaKeys{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
+ KEY_FN, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA,
+ KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK, KEY_SCROLLLOCK};
+ testPointerVisibilityForKeys(metaKeys, /* expectVisible= */ true);
+}
+
+/**
+ * Touchpad tap should not be disabled if there is no active Input Method Connection
+ */
+TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDontDisableTouchpadTap) {
+ testTouchpadTapStateForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectPrevent= */ false);
+}
+
+/**
+ * Touchpad tap should be disabled if there is a active Input Method Connection
+ */
+TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionDisableTouchpadTap) {
+ mFakePolicy->setIsInputMethodConnectionActive(true);
+ testTouchpadTapStateForKeys({KEY_0, KEY_A}, /* expectPrevent= */ true);
+}
+
+/**
+ * Touchpad tap should not be disabled by meta keys even if Input Method Connection is active
+ */
+TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDontDisableTouchpadTap) {
+ mFakePolicy->setIsInputMethodConnectionActive(true);
+ std::vector<int32_t> metaKeys{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
+ KEY_FN, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA,
+ KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK, KEY_SCROLLLOCK};
+ testTouchpadTapStateForKeys(metaKeys, /* expectPrevent= */ false);
+}
+
+TEST_F(KeyboardInputMapperUnitTest, KeyPressTimestampRecorded) {
+ nsecs_t when = ARBITRARY_TIME;
+ std::vector<int32_t> keyCodes{KEY_0, KEY_A, KEY_LEFTCTRL, KEY_RIGHTALT, KEY_LEFTSHIFT};
+ EXPECT_CALL(mMockInputReaderContext, setLastKeyDownTimestamp)
+ .With(Args<0>(when))
+ .Times(keyCodes.size());
+ for (int32_t keyCode : keyCodes) {
+ process(when, EV_KEY, keyCode, 1);
+ process(when, EV_SYN, SYN_REPORT, 0);
+ process(when, EV_KEY, keyCode, 0);
+ process(when, EV_SYN, SYN_REPORT, 0);
+ }
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
index 89c0741dfd..6606de896b 100644
--- a/services/inputflinger/tests/LatencyTracker_test.cpp
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -15,11 +15,14 @@
*/
#include "../dispatcher/LatencyTracker.h"
+#include "../InputDeviceMetricsSource.h"
#include <android-base/properties.h>
#include <binder/Binder.h>
#include <gtest/gtest.h>
+#include <gui/constants.h>
#include <inttypes.h>
+#include <linux/input.h>
#include <log/log.h>
#define TAG "LatencyTracker_test"
@@ -30,21 +33,47 @@ using android::inputdispatcher::LatencyTracker;
namespace android::inputdispatcher {
+namespace {
+
+constexpr DeviceId DEVICE_ID = 100;
+
+static InputDeviceInfo generateTestDeviceInfo(uint16_t vendorId, uint16_t productId,
+ DeviceId deviceId) {
+ InputDeviceIdentifier identifier;
+ identifier.vendor = vendorId;
+ identifier.product = productId;
+ auto info = InputDeviceInfo();
+ info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "Test Device",
+ /*isExternal=*/false, /*hasMic=*/false, ADISPLAY_ID_NONE);
+ return info;
+}
+
+void setDefaultInputDeviceInfo(LatencyTracker& tracker) {
+ InputDeviceInfo deviceInfo = generateTestDeviceInfo(
+ /*vendorId=*/0, /*productId=*/0, DEVICE_ID);
+ tracker.setInputDevices({deviceInfo});
+}
+
+} // namespace
+
const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds(
android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
HwTimeoutMultiplier());
InputEventTimeline getTestTimeline() {
InputEventTimeline t(
- /*isDown*/ true,
- /*eventTime*/ 2,
- /*readTime*/ 3);
- ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8);
+ /*isDown=*/true,
+ /*eventTime=*/2,
+ /*readTime=*/3,
+ /*vendorId=*/0,
+ /*productId=*/0,
+ /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+ ConnectionTimeline expectedCT(/*deliveryTime=*/6, /*consumeTime=*/7, /*finishTime=*/8);
std::array<nsecs_t, GraphicsTimeline::SIZE> 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<BBinder>::make(), std::move(expectedCT));
return t;
}
@@ -56,10 +85,11 @@ protected:
sp<IBinder> connection2;
void SetUp() override {
- connection1 = new BBinder();
- connection2 = new BBinder();
+ connection1 = sp<BBinder>::make();
+ connection2 = sp<BBinder>::make();
mTracker = std::make_unique<LatencyTracker>(this);
+ setDefaultInputDeviceInfo(*mTracker);
}
void TearDown() override {}
@@ -87,7 +117,9 @@ 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, DEVICE_ID,
+ /*sources=*/{InputDeviceUsageSource::UNKNOWN});
}
void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
@@ -136,18 +168,21 @@ void LatencyTrackerTest::assertReceivedTimelines(const std::vector<InputEventTim
* any additional ConnectionTimeline's.
*/
TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
- mTracker->trackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/);
- triggerEventReporting(2 /*eventTime*/);
- assertReceivedTimeline(InputEventTimeline{false, 2, 3});
+ mTracker->trackListener(/*inputEventId=*/1, /*isDown=*/false, /*eventTime=*/2,
+ /*readTime=*/3, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
+ triggerEventReporting(/*eventTime=*/2);
+ assertReceivedTimeline(InputEventTimeline{/*isDown=*/false, /*eventTime=*/2,
+ /*readTime=*/3, /*vendorId=*/0, /*productID=*/0,
+ /*sources=*/{InputDeviceUsageSource::UNKNOWN}});
}
/**
* 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 +193,8 @@ TEST_F(LatencyTrackerTest, TrackGraphicsLatency_DoesNotTriggerReporting) {
std::array<nsecs_t, GraphicsTimeline::SIZE> 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({});
}
@@ -169,7 +204,8 @@ TEST_F(LatencyTrackerTest, TrackAllParameters_ReportsFullTimeline) {
const auto& [connectionToken, expectedCT] = *expected.connectionTimelines.begin();
- mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+ mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime,
+ DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
mTracker->trackFinishedEvent(inputEventId, connectionToken, expectedCT.deliveryTime,
expectedCT.consumeTime, expectedCT.finishTime);
mTracker->trackGraphicsLatency(inputEventId, connectionToken, expectedCT.graphicsTimeline);
@@ -189,10 +225,12 @@ 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, DEVICE_ID,
+ {InputDeviceUsageSource::UNKNOWN});
+ mTracker->trackListener(inputEventId, isDown, /*eventTime=*/2, readTime, DEVICE_ID,
+ {InputDeviceUsageSource::UNKNOWN});
- 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({});
@@ -203,7 +241,10 @@ TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
InputEventTimeline timeline1(
/*isDown*/ true,
/*eventTime*/ 2,
- /*readTime*/ 3);
+ /*readTime*/ 3,
+ /*vendorId=*/0,
+ /*productId=*/0,
+ /*sources=*/{InputDeviceUsageSource::UNKNOWN});
timeline1.connectionTimelines.emplace(connection1,
ConnectionTimeline(/*deliveryTime*/ 6, /*consumeTime*/ 7,
/*finishTime*/ 8));
@@ -215,13 +256,16 @@ TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
constexpr int32_t inputEventId2 = 10;
InputEventTimeline timeline2(
- /*isDown*/ false,
- /*eventTime*/ 20,
- /*readTime*/ 30);
+ /*isDown=*/false,
+ /*eventTime=*/20,
+ /*readTime=*/30,
+ /*vendorId=*/0,
+ /*productId=*/0,
+ /*sources=*/{InputDeviceUsageSource::UNKNOWN});
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<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline2;
graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90;
@@ -230,10 +274,10 @@ TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
// Start processing first event
mTracker->trackListener(inputEventId1, timeline1.isDown, timeline1.eventTime,
- timeline1.readTime);
+ timeline1.readTime, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
// Start processing second event
mTracker->trackListener(inputEventId2, timeline2.isDown, timeline2.eventTime,
- timeline2.readTime);
+ timeline2.readTime, DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
mTracker->trackFinishedEvent(inputEventId1, connection1, connectionTimeline1.deliveryTime,
connectionTimeline1.consumeTime, connectionTimeline1.finishTime);
@@ -258,15 +302,17 @@ TEST_F(LatencyTrackerTest, IncompleteEvents_AreHandledConsistently) {
const sp<IBinder>& token = timeline.connectionTimelines.begin()->first;
for (size_t i = 1; i <= 100; i++) {
- mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime,
- timeline.readTime);
- expectedTimelines.push_back(
- InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime});
+ mTracker->trackListener(/*inputEventId=*/i, timeline.isDown, timeline.eventTime,
+ timeline.readTime, /*deviceId=*/DEVICE_ID,
+ /*sources=*/{InputDeviceUsageSource::UNKNOWN});
+ expectedTimelines.push_back(InputEventTimeline{timeline.isDown, timeline.eventTime,
+ timeline.readTime, timeline.vendorId,
+ timeline.productId, timeline.sources});
}
// 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);
@@ -287,10 +333,38 @@ TEST_F(LatencyTrackerTest, EventsAreTracked_WhenTrackListenerIsCalledFirst) {
expectedCT.consumeTime, expectedCT.finishTime);
mTracker->trackGraphicsLatency(inputEventId, connection1, expectedCT.graphicsTimeline);
- mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+ mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime,
+ DEVICE_ID, {InputDeviceUsageSource::UNKNOWN});
triggerEventReporting(expected.eventTime);
- assertReceivedTimeline(
- InputEventTimeline{expected.isDown, expected.eventTime, expected.readTime});
+ assertReceivedTimeline(InputEventTimeline{expected.isDown, expected.eventTime,
+ expected.readTime, expected.vendorId,
+ expected.productId, expected.sources});
+}
+
+/**
+ * Check that LatencyTracker has the received timeline that contains the correctly
+ * resolved product ID, vendor ID and source for a particular device ID from
+ * among a list of devices.
+ */
+TEST_F(LatencyTrackerTest, TrackListenerCheck_DeviceInfoFieldsInputEventTimeline) {
+ constexpr int32_t inputEventId = 1;
+ InputEventTimeline timeline(
+ /*isDown*/ true, /*eventTime*/ 2, /*readTime*/ 3,
+ /*vendorId=*/50, /*productId=*/60,
+ /*sources=*/
+ {InputDeviceUsageSource::TOUCHSCREEN, InputDeviceUsageSource::STYLUS_DIRECT});
+ InputDeviceInfo deviceInfo1 = generateTestDeviceInfo(
+ /*vendorId=*/5, /*productId=*/6, /*deviceId=*/DEVICE_ID + 1);
+ InputDeviceInfo deviceInfo2 = generateTestDeviceInfo(
+ /*vendorId=*/50, /*productId=*/60, /*deviceId=*/DEVICE_ID);
+
+ mTracker->setInputDevices({deviceInfo1, deviceInfo2});
+ mTracker->trackListener(inputEventId, timeline.isDown, timeline.eventTime, timeline.readTime,
+ DEVICE_ID,
+ {InputDeviceUsageSource::TOUCHSCREEN,
+ InputDeviceUsageSource::STYLUS_DIRECT});
+ triggerEventReporting(timeline.eventTime);
+ assertReceivedTimeline(timeline);
}
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
new file mode 100644
index 0000000000..9fa6cdd298
--- /dev/null
+++ b/services/inputflinger/tests/MultiTouchMotionAccumulator_test.cpp
@@ -0,0 +1,87 @@
+/*
+ * 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 "MultiTouchMotionAccumulator.h"
+#include "InputMapperTest.h"
+
+namespace android {
+
+class MultiTouchMotionAccumulatorTest : public InputMapperUnitTest {
+protected:
+ static constexpr size_t SLOT_COUNT = 8;
+
+ MultiTouchMotionAccumulator mMotionAccumulator;
+
+ void processMotionEvent(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;
+ mMotionAccumulator.process(&event);
+ }
+};
+
+TEST_F(MultiTouchMotionAccumulatorTest, ActiveSlotCountUsingSlotsProtocol) {
+ mMotionAccumulator.configure(*mDeviceContext, SLOT_COUNT, /*usingSlotsProtocol=*/true);
+ // We expect active slot count to match the touches being tracked
+ // first touch
+ processMotionEvent(EV_ABS, ABS_MT_SLOT, 0);
+ processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, 123);
+ processMotionEvent(EV_SYN, SYN_REPORT, 0);
+ ASSERT_EQ(1u, mMotionAccumulator.getActiveSlotsCount());
+
+ // second touch
+ processMotionEvent(EV_ABS, ABS_MT_SLOT, 1);
+ processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, 456);
+ processMotionEvent(EV_SYN, SYN_REPORT, 0);
+ ASSERT_EQ(2u, mMotionAccumulator.getActiveSlotsCount());
+
+ // second lifted
+ processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ processMotionEvent(EV_SYN, SYN_REPORT, 0);
+ ASSERT_EQ(1u, mMotionAccumulator.getActiveSlotsCount());
+
+ // first lifted
+ processMotionEvent(EV_ABS, ABS_MT_SLOT, 0);
+ processMotionEvent(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ processMotionEvent(EV_SYN, SYN_REPORT, 0);
+ ASSERT_EQ(0u, mMotionAccumulator.getActiveSlotsCount());
+}
+
+TEST_F(MultiTouchMotionAccumulatorTest, ActiveSlotCountNotUsingSlotsProtocol) {
+ mMotionAccumulator.configure(*mDeviceContext, SLOT_COUNT, /*usingSlotsProtocol=*/false);
+
+ // first touch
+ processMotionEvent(EV_ABS, ABS_MT_POSITION_X, 0);
+ processMotionEvent(EV_ABS, ABS_MT_POSITION_Y, 0);
+ processMotionEvent(EV_SYN, SYN_MT_REPORT, 0);
+ ASSERT_EQ(1u, mMotionAccumulator.getActiveSlotsCount());
+
+ // second touch
+ processMotionEvent(EV_ABS, ABS_MT_POSITION_X, 50);
+ processMotionEvent(EV_ABS, ABS_MT_POSITION_Y, 50);
+ processMotionEvent(EV_SYN, SYN_MT_REPORT, 0);
+ ASSERT_EQ(2u, mMotionAccumulator.getActiveSlotsCount());
+
+ // reset
+ mMotionAccumulator.finishSync();
+ ASSERT_EQ(0u, mMotionAccumulator.getActiveSlotsCount());
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/NotifyArgs_test.cpp b/services/inputflinger/tests/NotifyArgs_test.cpp
new file mode 100644
index 0000000000..15367568ab
--- /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 <NotifyArgs.h>
+#include <utils/Timers.h>
+
+#include <gtest/gtest.h>
+#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<int16_t> videoData = {1, 2, 3, 4};
+ timeval timestamp = {5, 6};
+ TouchVideoFrame frame(2, 2, std::move(videoData), timestamp);
+ std::vector<TouchVideoFrame> 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/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
new file mode 100644
index 0000000000..193b84d313
--- /dev/null
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -0,0 +1,1548 @@
+/*
+ * 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 "../PointerChoreographer.h"
+
+#include <gtest/gtest.h>
+#include <deque>
+#include <vector>
+
+#include "FakePointerController.h"
+#include "NotifyArgsBuilders.h"
+#include "TestEventMatchers.h"
+#include "TestInputListener.h"
+
+namespace android {
+
+using ControllerType = PointerControllerInterface::ControllerType;
+using testing::AllOf;
+
+namespace {
+
+// Helpers to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {
+ using V::operator()...;
+};
+template <typename... V>
+Visitor(V...) -> Visitor<V...>;
+
+constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t SECOND_DEVICE_ID = DEVICE_ID + 1;
+constexpr int32_t DISPLAY_ID = 5;
+constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+
+const auto MOUSE_POINTER = PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
+const auto FIRST_TOUCH_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER).x(100).y(200);
+const auto SECOND_TOUCH_POINTER = PointerBuilder(/*id=*/1, ToolType::FINGER).x(200).y(300);
+const auto STYLUS_POINTER = PointerBuilder(/*id=*/0, ToolType::STYLUS).x(100).y(200);
+const auto TOUCHPAD_POINTER = PointerBuilder(/*id=*/0, ToolType::FINGER)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20);
+
+static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
+ int32_t associatedDisplayId) {
+ InputDeviceIdentifier identifier;
+
+ auto info = InputDeviceInfo();
+ info.initialize(deviceId, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias",
+ /*isExternal=*/false, /*hasMic=*/false, associatedDisplayId);
+ info.addSource(source);
+ return info;
+}
+
+static std::vector<DisplayViewport> createViewports(std::vector<int32_t> displayIds) {
+ std::vector<DisplayViewport> viewports;
+ for (auto displayId : displayIds) {
+ DisplayViewport viewport;
+ viewport.displayId = displayId;
+ viewport.logicalRight = DISPLAY_WIDTH;
+ viewport.logicalBottom = DISPLAY_HEIGHT;
+ viewports.push_back(viewport);
+ }
+ return viewports;
+}
+
+} // namespace
+
+// --- PointerChoreographerTest ---
+
+class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface {
+protected:
+ TestInputListener mTestListener;
+ PointerChoreographer mChoreographer{mTestListener, *this};
+
+ std::shared_ptr<FakePointerController> assertPointerControllerCreated(
+ ControllerType expectedType) {
+ EXPECT_FALSE(mCreatedControllers.empty()) << "No PointerController was created";
+ auto [type, controller] = std::move(mCreatedControllers.front());
+ EXPECT_EQ(expectedType, type);
+ mCreatedControllers.pop_front();
+ return controller;
+ }
+
+ void assertPointerControllerNotCreated() { ASSERT_TRUE(mCreatedControllers.empty()); }
+
+ void assertPointerControllerRemoved(const std::shared_ptr<FakePointerController>& pc) {
+ // Ensure that the code under test is not holding onto this PointerController.
+ // While the policy initially creates the PointerControllers, the PointerChoreographer is
+ // expected to manage their lifecycles. Although we may not want to strictly enforce how
+ // the object is managed, in this case, we need to have a way of ensuring that the
+ // corresponding graphical resources have been released by the PointerController, and the
+ // simplest way of checking for that is to just make sure that the PointerControllers
+ // themselves are released by Choreographer when no longer in use. This check is ensuring
+ // that the reference retained by the test is the last one.
+ ASSERT_EQ(1, pc.use_count()) << "Expected PointerChoreographer to release all references "
+ "to this PointerController";
+ }
+
+ void assertPointerControllerNotRemoved(const std::shared_ptr<FakePointerController>& pc) {
+ // See assertPointerControllerRemoved above.
+ ASSERT_GT(pc.use_count(), 1) << "Expected PointerChoreographer to hold at least one "
+ "reference to this PointerController";
+ }
+
+ void assertPointerDisplayIdNotified(int32_t displayId) {
+ ASSERT_EQ(displayId, mPointerDisplayIdNotified);
+ mPointerDisplayIdNotified.reset();
+ }
+
+ void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); }
+
+private:
+ std::deque<std::pair<ControllerType, std::shared_ptr<FakePointerController>>>
+ mCreatedControllers;
+ std::optional<int32_t> mPointerDisplayIdNotified;
+
+ std::shared_ptr<PointerControllerInterface> createPointerController(
+ ControllerType type) override {
+ std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+ EXPECT_FALSE(pc->isPointerShown());
+ mCreatedControllers.emplace_back(type, pc);
+ return pc;
+ }
+
+ void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override {
+ mPointerDisplayIdNotified = displayId;
+ }
+};
+
+TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
+ const std::vector<NotifyArgs> allArgs{NotifyInputDevicesChangedArgs{},
+ NotifyConfigurationChangedArgs{},
+ NotifyKeyArgs{},
+ NotifyMotionArgs{},
+ NotifySensorArgs{},
+ NotifySwitchArgs{},
+ NotifyDeviceResetArgs{},
+ NotifyPointerCaptureChangedArgs{},
+ NotifyVibratorStateArgs{}};
+
+ for (auto notifyArgs : allArgs) {
+ mChoreographer.notify(notifyArgs);
+ EXPECT_NO_FATAL_FAILURE(
+ std::visit(Visitor{
+ [&](const NotifyInputDevicesChangedArgs& args) {
+ mTestListener.assertNotifyInputDevicesChangedWasCalled();
+ },
+ [&](const NotifyConfigurationChangedArgs& args) {
+ mTestListener.assertNotifyConfigurationChangedWasCalled();
+ },
+ [&](const NotifyKeyArgs& args) {
+ mTestListener.assertNotifyKeyWasCalled();
+ },
+ [&](const NotifyMotionArgs& args) {
+ mTestListener.assertNotifyMotionWasCalled();
+ },
+ [&](const NotifySensorArgs& args) {
+ mTestListener.assertNotifySensorWasCalled();
+ },
+ [&](const NotifySwitchArgs& args) {
+ mTestListener.assertNotifySwitchWasCalled();
+ },
+ [&](const NotifyDeviceResetArgs& args) {
+ mTestListener.assertNotifyDeviceResetWasCalled();
+ },
+ [&](const NotifyPointerCaptureChangedArgs& args) {
+ mTestListener.assertNotifyCaptureWasCalled();
+ },
+ [&](const NotifyVibratorStateArgs& args) {
+ mTestListener.assertNotifyVibratorStateWasCalled();
+ },
+ },
+ notifyArgs));
+ }
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsAddedCreatesPointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsRemovedRemovesPointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+ // Remove the mouse.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenKeyboardIsAddedDoesNotCreatePointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}});
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForAssociatedMouse) {
+ // Just adding a viewport or device should create a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertViewportSet(DISPLAY_ID);
+ ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportSetLaterSetsViewportForAssociatedMouse) {
+ // Without viewport information, PointerController will be created but viewport won't be set.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertViewportNotSet();
+
+ // After Choreographer gets viewport, PointerController should also have viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // For a mouse event without a target display, default viewport should be set for
+ // the PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertViewportSet(DISPLAY_ID);
+ ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenDefaultMouseDisplayChangesSetsDefaultMouseViewportForPointerController) {
+ // Set one display as a default mouse display and emit mouse event to create PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+ firstDisplayPc->assertViewportSet(DISPLAY_ID);
+ ASSERT_TRUE(firstDisplayPc->isPointerShown());
+
+ // Change default mouse display. Existing PointerController should be removed and a new one
+ // should be created.
+ mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+ assertPointerControllerRemoved(firstDisplayPc);
+
+ auto secondDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+ secondDisplayPc->assertViewportSet(ANOTHER_DISPLAY_ID);
+ ASSERT_TRUE(secondDisplayPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ assertPointerControllerCreated(ControllerType::MOUSE);
+
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterCallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotNotified();
+
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsRemovedCallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenDefaultMouseDisplayChangesCallsNotifyPointerDisplayIdChanged) {
+ // Add two viewports.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+ // Set one viewport as a default mouse display ID.
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+
+ // Set another viewport as a default mouse display ID. The mouse is moved to the other display.
+ mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+ assertPointerControllerRemoved(firstDisplayPc);
+
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, MouseMovesPointerAndReturnsNewArgs) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Make NotifyMotionArgs and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that the PointerController updated the position and the pointer is shown.
+ pc->assertPosition(110, 220);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Check that x-y coordinates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest,
+ AssociatedMouseMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+ // Add two displays and set one to default.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // Add two devices, one unassociated and the other associated with non-default mouse display.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+ auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+ auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+ // Set initial position for PointerControllers.
+ unassociatedMousePc->setPosition(100, 200);
+ associatedMousePc->setPosition(300, 400);
+
+ // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // Check the status of the PointerControllers.
+ unassociatedMousePc->assertPosition(100, 200);
+ ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+ associatedMousePc->assertPosition(310, 420);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+ ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+ // Check that x-y coordinates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+ WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForMouseRelativeSource) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Assume that pointer capture is enabled.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/1,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE_RELATIVE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyPointerCaptureChanged(
+ NotifyPointerCaptureChangedArgs(/*id=*/2, systemTime(SYSTEM_TIME_MONOTONIC),
+ PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+ // Notify motion as if pointer capture is enabled.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE_RELATIVE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .x(10)
+ .y(20)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that there's no update on the PointerController.
+ pc->assertPosition(100, 200);
+ ASSERT_FALSE(pc->isPointerShown());
+
+ // Check x-y coordinates, displayId and cursor position are not changed.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(10, 20), WithRelativeMotion(10, 20), WithDisplayId(ADISPLAY_ID_NONE),
+ WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledHidesPointer) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Enable pointer capture and check if the PointerController hid the pointer.
+ mChoreographer.notifyPointerCaptureChanged(
+ NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+ PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, MultipleMiceConnectionAndRemoval) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // A mouse is connected, and the pointer is shown.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ pc->fade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ // Add a second mouse is added, the pointer is shown again.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // One of the mice is removed, and it does not cause the mouse pointer to fade, because
+ // we have one more mouse connected.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ assertPointerControllerNotRemoved(pc);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // The final mouse is removed. The pointer is removed.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/0, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, UnrelatedChangeDoesNotUnfadePointer) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ pc->fade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ // Adding a touchscreen device does not unfade the mouse pointer.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+ DISPLAY_ID)}});
+
+ ASSERT_FALSE(pc->isPointerShown());
+
+ // Show touches setting change does not unfade the mouse pointer.
+ mChoreographer.setShowTouchesEnabled(true);
+
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesEnabledAndDisabledDoesNotCreatePointerController) {
+ // Disable show touches and add a touch device.
+ mChoreographer.setShowTouchesEnabled(false);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ assertPointerControllerNotCreated();
+
+ // Enable show touches. PointerController still should not be created.
+ mChoreographer.setShowTouchesEnabled(true);
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchEventOccursCreatesPointerController) {
+ // Add a touch device and enable show touches.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+
+ // Emit touch event. Now PointerController should be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCreated(ControllerType::TOUCH);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenShowTouchesDisabledAndTouchEventOccursDoesNotCreatePointerController) {
+ // Add a touch device and disable show touches.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(false);
+ assertPointerControllerNotCreated();
+
+ // Emit touch event. Still, PointerController should not be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsRemovedRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+ // Remove the device.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenShowTouchesDisabledRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+ // Disable show touches.
+ mChoreographer.setShowTouchesEnabled(false);
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpots) {
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+
+ // Emit first pointer down.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+ pc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Emit second pointer down.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .pointer(SECOND_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertSpotCount(DISPLAY_ID, 2);
+
+ // Emit second pointer up.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_UP |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .pointer(SECOND_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Emit first pointer up.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertSpotCount(DISPLAY_ID, 0);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForStylusEvent) {
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS,
+ DISPLAY_ID)}});
+
+ // Emit down event with stylus properties.
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN,
+ AINPUT_SOURCE_TOUCHSCREEN | AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+ pc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, TouchSetsSpotsForTwoDisplays) {
+ mChoreographer.setShowTouchesEnabled(true);
+ // Add two touch devices associated to different displays.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ ANOTHER_DISPLAY_ID)}});
+
+ // Emit touch event with first device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+ firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Emit touch events with second device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .pointer(SECOND_TOUCH_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // There should be another PointerController created.
+ auto secondDisplayPc = assertPointerControllerCreated(ControllerType::TOUCH);
+
+ // Check if the spots are set for the second device.
+ secondDisplayPc->assertSpotCount(ANOTHER_DISPLAY_ID, 2);
+
+ // Check if there's no change on the spot of the first device.
+ firstDisplayPc->assertSpotCount(DISPLAY_ID, 1);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchDeviceIsResetClearsSpots) {
+ // Make sure the PointerController is created and there is a spot.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, DISPLAY_ID)}});
+ mChoreographer.setShowTouchesEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::TOUCH);
+ pc->assertSpotCount(DISPLAY_ID, 1);
+
+ // Reset the device and ensure the touch pointer controller was removed.
+ mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenStylusPointerIconEnabledAndDisabledDoesNotCreatePointerController) {
+ // Disable stylus pointer icon and add a stylus device.
+ mChoreographer.setStylusPointerIconEnabled(false);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ assertPointerControllerNotCreated();
+
+ // Enable stylus pointer icon. PointerController still should not be created.
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusHoverEventOccursCreatesPointerController) {
+ // Add a stylus device and enable stylus pointer icon.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ assertPointerControllerNotCreated();
+
+ // Emit hover event. Now PointerController should be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCreated(ControllerType::STYLUS);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenStylusPointerIconDisabledAndHoverEventOccursDoesNotCreatePointerController) {
+ // Add a stylus device and disable stylus pointer icon.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(false);
+ assertPointerControllerNotCreated();
+
+ // Emit hover event. Still, PointerController should not be created.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsRemovedRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Remove the device.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusPointerIconDisabledRemovesPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Disable stylus pointer icon.
+ mChoreographer.setStylusPointerIconEnabled(false);
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForStylusPointerController) {
+ // Set viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Check that viewport is set for the PointerController.
+ pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterSetsViewportForStylusPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Check that viewport is unset.
+ pc->assertViewportNotSet();
+
+ // Set viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Check that the viewport is set for the PointerController.
+ pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenViewportDoesNotMatchDoesNotSetViewportForStylusPointerController) {
+ // Make sure the PointerController is created.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Check that viewport is unset.
+ pc->assertViewportNotSet();
+
+ // Set viewport which does not match the associated display of the stylus.
+ mChoreographer.setDisplayViewports(createViewports({ANOTHER_DISPLAY_ID}));
+
+ // Check that viewport is still unset.
+ pc->assertViewportNotSet();
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointer) {
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Emit hover enter event. This is for creating PointerController.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Emit hover move event. After bounds are set, PointerController will update the position.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ pc->assertPosition(150, 250);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Emit hover exit event.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_EXIT, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ // Check that the pointer is gone.
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, StylusHoverManipulatesPointerForTwoDisplays) {
+ mChoreographer.setStylusPointerIconEnabled(true);
+ // Add two stylus devices associated to different displays.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, ANOTHER_DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+ // Emit hover event with first device. This is for creating PointerController.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Emit hover event with second device. This is for creating PointerController.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // There should be another PointerController created.
+ auto secondDisplayPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Emit hover event with first device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(150).y(250))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+
+ // Check the pointer of the first device.
+ firstDisplayPc->assertPosition(150, 250);
+ ASSERT_TRUE(firstDisplayPc->isPointerShown());
+
+ // Emit hover event with second device.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_STYLUS)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::STYLUS).x(250).y(350))
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // Check the pointer of the second device.
+ secondDisplayPc->assertPosition(250, 350);
+ ASSERT_TRUE(secondDisplayPc->isPointerShown());
+
+ // Check that there's no change on the pointer of the first device.
+ firstDisplayPc->assertPosition(150, 250);
+ ASSERT_TRUE(firstDisplayPc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, WhenStylusDeviceIsResetRemovesPointer) {
+ // Make sure the PointerController is created and there is a pointer.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Reset the device and see the pointer controller was removed.
+ mChoreographer.notifyDeviceReset(NotifyDeviceResetArgs(/*id=*/1, /*eventTime=*/0, DEVICE_ID));
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsAddedCreatesPointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ assertPointerControllerCreated(ControllerType::MOUSE);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedRemovesPointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+
+ // Remove the touchpad.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForAssociatedTouchpad) {
+ // Just adding a viewport or device should not create a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportSetLaterSetsViewportForAssociatedTouchpad) {
+ // Without viewport information, PointerController will be created by a touchpad event
+ // but viewport won't be set.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertViewportNotSet();
+
+ // After Choreographer gets viewport, PointerController should also have viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, SetsDefaultTouchpadViewportForPointerController) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // For a touchpad event without a target display, default viewport should be set for
+ // the PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertViewportSet(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenDefaultTouchpadDisplayChangesSetsDefaultTouchpadViewportForPointerController) {
+ // Set one display as a default touchpad display and create PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+ firstDisplayPc->assertViewportSet(DISPLAY_ID);
+
+ // Change default mouse display. Existing PointerController should be removed.
+ mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+ assertPointerControllerRemoved(firstDisplayPc);
+
+ auto secondDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+ secondDisplayPc->assertViewportSet(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, TouchpadCallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ assertPointerControllerCreated(ControllerType::MOUSE);
+
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLaterTouchpadCallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotNotified();
+
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenTouchpadIsRemovedCallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+ assertPointerControllerRemoved(pc);
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenDefaultMouseDisplayChangesTouchpadCallsNotifyPointerDisplayIdChanged) {
+ // Add two viewports.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+ // Set one viewport as a default mouse display ID.
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto firstDisplayPc = assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+
+ // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified
+ // before a touchpad event.
+ mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+ assertPointerControllerRemoved(firstDisplayPc);
+
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, TouchpadMovesPointerAndReturnsNewArgs) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Make NotifyMotionArgs and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(TOUCHPAD_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that the PointerController updated the position and the pointer is shown.
+ pc->assertPosition(110, 220);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Check that x-y coordinates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(110, 220), WithDisplayId(DISPLAY_ID), WithCursorPosition(110, 220)));
+}
+
+TEST_F(PointerChoreographerTest, TouchpadAddsPointerPositionToTheCoords) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set initial position of the PointerController.
+ pc->setPosition(100, 200);
+
+ // Notify motion with fake fingers, as if it is multi-finger swipe.
+ // Check if the position of the PointerController is added to the fake finger coords.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+ .classification(MotionClassification::MULTI_FINGER_SWIPE)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithCoords(0, 200), WithCursorPosition(100, 200)));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
+ .classification(MotionClassification::MULTI_FINGER_SWIPE)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCoords(0, 0, 200), WithPointerCoords(1, 100, 200),
+ WithCursorPosition(100, 200)));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-100).y(0))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(0).y(0))
+ .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(100).y(0))
+ .classification(MotionClassification::MULTI_FINGER_SWIPE)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ (2 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT)),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCoords(0, 0, 200), WithPointerCoords(1, 100, 200),
+ WithPointerCoords(2, 200, 200), WithCursorPosition(100, 200)));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::FINGER).x(-90).y(10))
+ .pointer(PointerBuilder(/*id=*/1, ToolType::FINGER).x(10).y(10))
+ .pointer(PointerBuilder(/*id=*/2, ToolType::FINGER).x(110).y(10))
+ .classification(MotionClassification::MULTI_FINGER_SWIPE)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE),
+ WithMotionClassification(MotionClassification::MULTI_FINGER_SWIPE),
+ WithPointerCoords(0, 10, 210), WithPointerCoords(1, 110, 210),
+ WithPointerCoords(2, 210, 210), WithCursorPosition(100, 200)));
+}
+
+TEST_F(PointerChoreographerTest,
+ AssociatedTouchpadMovesPointerOnAssociatedDisplayAndDoesNotMovePointerOnDefaultDisplay) {
+ // Add two displays and set one to default.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // Add two devices, one unassociated and the other associated with non-default mouse display.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ANOTHER_DISPLAY_ID)}});
+ auto unassociatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+ auto associatedMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+
+ // Set initial positions for PointerControllers.
+ unassociatedMousePc->setPosition(100, 200);
+ associatedMousePc->setPosition(300, 400);
+
+ // Make NotifyMotionArgs from the associated mouse and notify Choreographer.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(TOUCHPAD_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(ANOTHER_DISPLAY_ID)
+ .build());
+
+ // Check the status of the PointerControllers.
+ unassociatedMousePc->assertPosition(100, 200);
+ ASSERT_EQ(DISPLAY_ID, unassociatedMousePc->getDisplayId());
+ associatedMousePc->assertPosition(310, 420);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, associatedMousePc->getDisplayId());
+ ASSERT_TRUE(associatedMousePc->isPointerShown());
+
+ // Check that x-y coordinates, displayId and cursor position are correctly updated.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(310, 420), WithDeviceId(SECOND_DEVICE_ID),
+ WithDisplayId(ANOTHER_DISPLAY_ID), WithCursorPosition(310, 420)));
+}
+
+TEST_F(PointerChoreographerTest, DoesNotMovePointerForTouchpadSource) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+
+ // Set initial position of the PointerController.
+ pc->setPosition(200, 300);
+
+ // Assume that pointer capture is enabled.
+ mChoreographer.notifyPointerCaptureChanged(
+ NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+ PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+
+ // Notify motion as if pointer capture is enabled.
+ mChoreographer.notifyMotion(MotionArgsBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_TOUCHPAD)
+ .pointer(FIRST_TOUCH_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ // Check that there's no update on the PointerController.
+ pc->assertPosition(200, 300);
+ ASSERT_FALSE(pc->isPointerShown());
+
+ // Check x-y coordinates, displayId and cursor position are not changed.
+ mTestListener.assertNotifyMotionWasCalled(
+ AllOf(WithCoords(100, 200), WithDisplayId(ADISPLAY_ID_NONE),
+ WithCursorPosition(AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION)));
+}
+
+TEST_F(PointerChoreographerTest, WhenPointerCaptureEnabledTouchpadHidesPointer) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE | AINPUT_SOURCE_TOUCHPAD,
+ ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, pc->getDisplayId());
+ ASSERT_TRUE(pc->isPointerShown());
+
+ // Enable pointer capture and check if the PointerController hid the pointer.
+ mChoreographer.notifyPointerCaptureChanged(
+ NotifyPointerCaptureChangedArgs(/*id=*/1, systemTime(SYSTEM_TIME_MONOTONIC),
+ PointerCaptureRequest(/*enable=*/true, /*seq=*/0)));
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouse) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetMousePointerIconForWrongDisplayId) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for wrong display id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, DoesNotSetPointerIconForWrongDeviceId) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsCustomPointerIconForMouse) {
+ // Make sure there is a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ pc->assertCustomPointerIconNotSet();
+
+ // Set custom pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, DEVICE_ID));
+ pc->assertCustomPointerIconSet(PointerIconStyle::TYPE_CUSTOM);
+
+ // Set custom pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, SECOND_DEVICE_ID));
+ pc->assertCustomPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouseOnTwoDisplays) {
+ // Make sure there are two PointerControllers on different displays.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+ auto firstMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, firstMousePc->getDisplayId());
+ auto secondMousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, secondMousePc->getDisplayId());
+
+ // Set pointer icon for one mouse.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ firstMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ secondMousePc->assertPointerIconNotSet();
+
+ // Set pointer icon for another mouse.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, ANOTHER_DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ secondMousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ firstMousePc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForStylus) {
+ // Make sure there is a PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ pc->assertPointerIconNotSet();
+
+ // Set pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ pc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+
+ // Set pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ pc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsCustomPointerIconForStylus) {
+ // Make sure there is a PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto pc = assertPointerControllerCreated(ControllerType::STYLUS);
+ pc->assertCustomPointerIconNotSet();
+
+ // Set custom pointer icon for the device.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, DEVICE_ID));
+ pc->assertCustomPointerIconSet(PointerIconStyle::TYPE_CUSTOM);
+
+ // Set custom pointer icon for wrong device id. This should be ignored.
+ ASSERT_FALSE(mChoreographer.setPointerIcon(std::make_unique<SpriteIcon>(
+ PointerIconStyle::TYPE_CUSTOM),
+ DISPLAY_ID, SECOND_DEVICE_ID));
+ pc->assertCustomPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForTwoStyluses) {
+ // Make sure there are two StylusPointerControllers. They can be on a same display.
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto firstStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto secondStylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Set pointer icon for one stylus.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ firstStylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ secondStylusPc->assertPointerIconNotSet();
+
+ // Set pointer icon for another stylus.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ secondStylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ firstStylusPc->assertPointerIconNotSet();
+}
+
+TEST_F(PointerChoreographerTest, SetsPointerIconForMouseAndStylus) {
+ // Make sure there are PointerControllers for a mouse and a stylus.
+ mChoreographer.setStylusPointerIconEnabled(true);
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_STYLUS, DISPLAY_ID)}});
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ auto mousePc = assertPointerControllerCreated(ControllerType::MOUSE);
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_STYLUS)
+ .pointer(STYLUS_POINTER)
+ .deviceId(SECOND_DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ auto stylusPc = assertPointerControllerCreated(ControllerType::STYLUS);
+
+ // Set pointer icon for the mouse.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID, DEVICE_ID));
+ mousePc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ stylusPc->assertPointerIconNotSet();
+
+ // Set pointer icon for the stylus.
+ ASSERT_TRUE(mChoreographer.setPointerIcon(PointerIconStyle::TYPE_TEXT, DISPLAY_ID,
+ SECOND_DEVICE_ID));
+ stylusPc->assertPointerIconSet(PointerIconStyle::TYPE_TEXT);
+ mousePc->assertPointerIconNotSet();
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
index 8e2ab88e80..9818176cb0 100644
--- a/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
+++ b/services/inputflinger/tests/PreferStylusOverTouch_test.cpp
@@ -15,6 +15,7 @@
*/
#include <gtest/gtest.h>
+#include <gui/constants.h>
#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<PointerData>& points,
- uint32_t source) {
+ const std::vector<Point>& 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 0000000000..8a40e78b89
--- /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 <gestures/PropertyProvider.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#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<int*>(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<SetterData*>(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<GesturesPropBool*>(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<SetterData*>(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<double*>(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<SetterData*>(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<GetterData*>(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/SlopController_test.cpp b/services/inputflinger/tests/SlopController_test.cpp
new file mode 100644
index 0000000000..f524acd025
--- /dev/null
+++ b/services/inputflinger/tests/SlopController_test.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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 "../reader/mapper/SlopController.h"
+
+#include <gtest/gtest.h>
+
+namespace android {
+
+// --- SlopControllerTest ---
+
+TEST(SlopControllerTest, PositiveValues) {
+ SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(0, controller.consumeEvent(1000, 1));
+ ASSERT_EQ(0, controller.consumeEvent(1003, 3));
+ ASSERT_EQ(2, controller.consumeEvent(1005, 3));
+ ASSERT_EQ(4, controller.consumeEvent(1009, 4));
+
+ SlopController controller2 = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(0, controller2.consumeEvent(1000, 5));
+ ASSERT_EQ(3, controller2.consumeEvent(1003, 3));
+ ASSERT_EQ(4, controller2.consumeEvent(1005, 4));
+}
+
+TEST(SlopControllerTest, NegativeValues) {
+ SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(0, controller.consumeEvent(1000, -1));
+ ASSERT_EQ(0, controller.consumeEvent(1003, -3));
+ ASSERT_EQ(-2, controller.consumeEvent(1005, -3));
+ ASSERT_EQ(-4, controller.consumeEvent(1009, -4));
+
+ SlopController controller2 = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(0, controller2.consumeEvent(1000, -5));
+ ASSERT_EQ(-3, controller2.consumeEvent(1003, -3));
+ ASSERT_EQ(-4, controller2.consumeEvent(1005, -4));
+}
+
+TEST(SlopControllerTest, ZeroDoesNotResetSlop) {
+ SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(1, controller.consumeEvent(1005, 6));
+ ASSERT_EQ(0, controller.consumeEvent(1006, 0));
+ ASSERT_EQ(2, controller.consumeEvent(1008, 2));
+}
+
+TEST(SlopControllerTest, SignChange_ResetsSlop) {
+ SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(0, controller.consumeEvent(1000, 2));
+ ASSERT_EQ(0, controller.consumeEvent(1001, -4));
+ ASSERT_EQ(0, controller.consumeEvent(1002, 3));
+ ASSERT_EQ(0, controller.consumeEvent(1003, -2));
+
+ ASSERT_EQ(1, controller.consumeEvent(1005, 6));
+ ASSERT_EQ(0, controller.consumeEvent(1006, 0));
+ ASSERT_EQ(2, controller.consumeEvent(1008, 2));
+
+ ASSERT_EQ(0, controller.consumeEvent(1010, -4));
+ ASSERT_EQ(-1, controller.consumeEvent(1011, -2));
+
+ ASSERT_EQ(0, controller.consumeEvent(1015, 5));
+ ASSERT_EQ(2, controller.consumeEvent(1016, 2));
+
+ ASSERT_EQ(0, controller.consumeEvent(1017, -5));
+ ASSERT_EQ(-2, controller.consumeEvent(1018, -2));
+}
+
+TEST(SlopControllerTest, OldAge_ResetsSlop) {
+ SlopController controller = SlopController(/*slopThreshold=*/5, /*slopDurationNanos=*/100);
+
+ ASSERT_EQ(1, controller.consumeEvent(1005, 6));
+ ASSERT_EQ(0, controller.consumeEvent(1108, 2)); // age exceeds slop duration
+
+ ASSERT_EQ(1, controller.consumeEvent(1110, 4));
+ ASSERT_EQ(0, controller.consumeEvent(1210, 2)); // age equals slop duration
+
+ ASSERT_EQ(0, controller.consumeEvent(1215, -3));
+ ASSERT_EQ(-2, controller.consumeEvent(1216, -4));
+ ASSERT_EQ(-5, controller.consumeEvent(1315, -5));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/SyncQueue_test.cpp b/services/inputflinger/tests/SyncQueue_test.cpp
new file mode 100644
index 0000000000..b57ccc24be
--- /dev/null
+++ b/services/inputflinger/tests/SyncQueue_test.cpp
@@ -0,0 +1,92 @@
+/*
+ * 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 "../SyncQueue.h"
+
+#include <gtest/gtest.h>
+#include <thread>
+
+namespace android {
+
+// --- SyncQueueTest ---
+
+// Validate basic pop and push operation.
+TEST(SyncQueueTest, AddAndRemove) {
+ SyncQueue<int> queue;
+
+ queue.push(1);
+ ASSERT_EQ(queue.pop(), 1);
+
+ queue.push(3);
+ ASSERT_EQ(queue.pop(), 3);
+
+ ASSERT_EQ(std::nullopt, queue.pop());
+}
+
+// Make sure the queue maintains FIFO order.
+// Add elements and remove them, and check the order.
+TEST(SyncQueueTest, isFIFO) {
+ SyncQueue<int> queue;
+
+ constexpr int numItems = 10;
+ for (int i = 0; i < numItems; i++) {
+ queue.push(static_cast<int>(i));
+ }
+ for (int i = 0; i < numItems; i++) {
+ ASSERT_EQ(queue.pop(), static_cast<int>(i));
+ }
+}
+
+// Make sure the queue has strict capacity limits.
+TEST(SyncQueueTest, QueueReachesCapacity) {
+ constexpr size_t capacity = 3;
+ SyncQueue<int> queue(capacity);
+
+ // First 3 elements should be added successfully
+ ASSERT_TRUE(queue.push(1));
+ ASSERT_TRUE(queue.push(2));
+ ASSERT_TRUE(queue.push(3));
+ ASSERT_FALSE(queue.push(4)) << "Queue should reach capacity at size " << capacity;
+}
+
+TEST(SyncQueueTest, AllowsMultipleThreads) {
+ SyncQueue<int> queue;
+
+ // Test with a large number of items to increase likelihood that threads overlap
+ constexpr int numItems = 100;
+
+ // Fill queue from a different thread
+ std::thread fillQueue([&queue]() {
+ for (int i = 0; i < numItems; i++) {
+ queue.push(static_cast<int>(i));
+ }
+ });
+
+ // Make sure all elements are received in correct order
+ for (int i = 0; i < numItems; i++) {
+ // Since popping races with the thread that's filling the queue,
+ // keep popping until we get something back
+ std::optional<int> popped;
+ do {
+ popped = queue.pop();
+ } while (!popped);
+ ASSERT_EQ(popped, static_cast<int>(i));
+ }
+
+ fillQueue.join();
+}
+
+} // namespace android
diff --git a/libs/renderengine/include/renderengine/mock/Framebuffer.h b/services/inputflinger/tests/TestConstants.h
index dfb6a4e41e..ad48b0fbe0 100644
--- a/libs/renderengine/include/renderengine/mock/Framebuffer.h
+++ b/services/inputflinger/tests/TestConstants.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 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.
@@ -16,21 +16,22 @@
#pragma once
-#include <gmock/gmock.h>
-#include <renderengine/Framebuffer.h>
+#include <chrono>
+
+#include <utils/Timers.h>
namespace android {
-namespace renderengine {
-namespace mock {
-class Framebuffer : public renderengine::Framebuffer {
-public:
- Framebuffer();
- ~Framebuffer() override;
+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;
- MOCK_METHOD3(setNativeWindowBuffer, bool(ANativeWindowBuffer*, bool, const bool));
-};
+// Error tolerance for floating point assertions.
+static const float EPSILON = 0.001f;
-} // namespace mock
-} // namespace renderengine
} // namespace android
diff --git a/services/inputflinger/tests/TestEventMatchers.h b/services/inputflinger/tests/TestEventMatchers.h
new file mode 100644
index 0000000000..66fdaa4520
--- /dev/null
+++ b/services/inputflinger/tests/TestEventMatchers.h
@@ -0,0 +1,699 @@
+/*
+ * 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 <cmath>
+#include <compare>
+
+#include <android-base/stringprintf.h>
+#include <android/input.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <input/Input.h>
+#include <input/PrintTools.h>
+
+#include "NotifyArgs.h"
+#include "TestConstants.h"
+
+namespace android {
+
+struct PointF {
+ float x;
+ float y;
+ auto operator<=>(const PointF&) const = default;
+};
+
+inline std::string pointFToString(const PointF& p) {
+ return std::string("(") + std::to_string(p.x) + ", " + std::to_string(p.y) + ")";
+}
+
+/// Source
+class WithSourceMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithSourceMatcher(uint32_t source) : mSource(source) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mSource == args.source;
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mSource == args.source;
+ }
+
+ bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+ return mSource == event.getSource();
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with source " << inputEventSourceToString(mSource);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong source"; }
+
+private:
+ const uint32_t mSource;
+};
+
+inline WithSourceMatcher WithSource(uint32_t source) {
+ return WithSourceMatcher(source);
+}
+
+/// Key action
+class WithKeyActionMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithKeyActionMatcher(int32_t action) : mAction(action) {}
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mAction == args.action;
+ }
+
+ bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+ return mAction == event.getAction();
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with key action " << KeyEvent::actionToString(mAction);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong action"; }
+
+private:
+ const int32_t mAction;
+};
+
+inline WithKeyActionMatcher WithKeyAction(int32_t action) {
+ return WithKeyActionMatcher(action);
+}
+
+/// Motion action
+class WithMotionActionMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithMotionActionMatcher(int32_t action) : mAction(action) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ bool matches = mAction == args.action;
+ if (args.action == AMOTION_EVENT_ACTION_CANCEL) {
+ matches &= (args.flags & AMOTION_EVENT_FLAG_CANCELED) != 0;
+ }
+ return matches;
+ }
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+ bool matches = mAction == event.getAction();
+ if (event.getAction() == AMOTION_EVENT_ACTION_CANCEL) {
+ matches &= (event.getFlags() & AMOTION_EVENT_FLAG_CANCELED) != 0;
+ }
+ return matches;
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with motion action " << MotionEvent::actionToString(mAction);
+ if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
+ *os << " and FLAG_CANCELED";
+ }
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong action"; }
+
+private:
+ const int32_t mAction;
+};
+
+inline WithMotionActionMatcher WithMotionAction(int32_t action) {
+ return WithMotionActionMatcher(action);
+}
+
+/// Display Id
+class WithDisplayIdMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithDisplayIdMatcher(int32_t displayId) : mDisplayId(displayId) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mDisplayId == args.displayId;
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mDisplayId == args.displayId;
+ }
+
+ bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+ return mDisplayId == event.getDisplayId();
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "with display id " << mDisplayId; }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong display id"; }
+
+private:
+ const int32_t mDisplayId;
+};
+
+inline WithDisplayIdMatcher WithDisplayId(int32_t displayId) {
+ return WithDisplayIdMatcher(displayId);
+}
+
+/// Device Id
+class WithDeviceIdMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithDeviceIdMatcher(int32_t deviceId) : mDeviceId(deviceId) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mDeviceId == args.deviceId;
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mDeviceId == args.deviceId;
+ }
+
+ bool MatchAndExplain(const NotifyDeviceResetArgs& args, std::ostream*) const {
+ return mDeviceId == args.deviceId;
+ }
+
+ bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+ return mDeviceId == event.getDeviceId();
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "with device id " << mDeviceId; }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong device id"; }
+
+private:
+ const int32_t mDeviceId;
+};
+
+inline WithDeviceIdMatcher WithDeviceId(int32_t deviceId) {
+ return WithDeviceIdMatcher(deviceId);
+}
+
+/// Flags
+class WithFlagsMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithFlagsMatcher(int32_t flags) : mFlags(flags) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mFlags == args.flags;
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mFlags == args.flags;
+ }
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+ return mFlags == event.getFlags();
+ }
+
+ bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+ return mFlags == event.getFlags();
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with flags " << base::StringPrintf("0x%x", mFlags);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong flags"; }
+
+private:
+ const int32_t mFlags;
+};
+
+inline WithFlagsMatcher WithFlags(int32_t flags) {
+ return WithFlagsMatcher(flags);
+}
+
+/// DownTime
+class WithDownTimeMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithDownTimeMatcher(nsecs_t downTime) : mDownTime(downTime) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mDownTime == args.downTime;
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mDownTime == args.downTime;
+ }
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream*) const {
+ return mDownTime == event.getDownTime();
+ }
+
+ bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+ return mDownTime == event.getDownTime();
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "with down time " << mDownTime; }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong down time"; }
+
+private:
+ const nsecs_t mDownTime;
+};
+
+inline WithDownTimeMatcher WithDownTime(nsecs_t downTime) {
+ return WithDownTimeMatcher(downTime);
+}
+
+/// Coordinate matcher
+class WithCoordsMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithCoordsMatcher(size_t pointerIndex, float x, float y)
+ : mPointerIndex(pointerIndex), mX(x), mY(y) {}
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+ if (mPointerIndex >= event.getPointerCount()) {
+ *os << "Pointer index " << mPointerIndex << " is out of bounds";
+ return false;
+ }
+
+ bool matches = mX == event.getX(mPointerIndex) && mY == event.getY(mPointerIndex);
+ if (!matches) {
+ *os << "expected coords (" << mX << ", " << mY << ") at pointer index " << mPointerIndex
+ << ", but got (" << event.getX(mPointerIndex) << ", " << event.getY(mPointerIndex)
+ << ")";
+ }
+ return matches;
+ }
+
+ bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+ if (mPointerIndex >= event.pointerCoords.size()) {
+ *os << "Pointer index " << mPointerIndex << " is out of bounds";
+ return false;
+ }
+
+ bool matches = mX == event.pointerCoords[mPointerIndex].getX() &&
+ mY == event.pointerCoords[mPointerIndex].getY();
+ if (!matches) {
+ *os << "expected coords (" << mX << ", " << mY << ") at pointer index " << mPointerIndex
+ << ", but got (" << event.pointerCoords[mPointerIndex].getX() << ", "
+ << event.pointerCoords[mPointerIndex].getY() << ")";
+ }
+ return matches;
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with coords (" << mX << ", " << mY << ") at pointer index " << mPointerIndex;
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong coords"; }
+
+private:
+ const size_t mPointerIndex;
+ const float mX;
+ const float mY;
+};
+
+inline WithCoordsMatcher WithCoords(float x, float y) {
+ return WithCoordsMatcher(0, x, y);
+}
+
+inline WithCoordsMatcher WithPointerCoords(size_t pointerIndex, float x, float y) {
+ return WithCoordsMatcher(pointerIndex, x, y);
+}
+
+/// Raw coordinate matcher
+class WithRawCoordsMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithRawCoordsMatcher(size_t pointerIndex, float rawX, float rawY)
+ : mPointerIndex(pointerIndex), mRawX(rawX), mRawY(rawY) {}
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+ if (mPointerIndex >= event.getPointerCount()) {
+ *os << "Pointer index " << mPointerIndex << " is out of bounds";
+ return false;
+ }
+
+ bool matches =
+ mRawX == event.getRawX(mPointerIndex) && mRawY == event.getRawY(mPointerIndex);
+ if (!matches) {
+ *os << "expected raw coords (" << mRawX << ", " << mRawY << ") at pointer index "
+ << mPointerIndex << ", but got (" << event.getRawX(mPointerIndex) << ", "
+ << event.getRawY(mPointerIndex) << ")";
+ }
+ return matches;
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with raw coords (" << mRawX << ", " << mRawY << ") at pointer index "
+ << mPointerIndex;
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong raw coords"; }
+
+private:
+ const size_t mPointerIndex;
+ const float mRawX;
+ const float mRawY;
+};
+
+inline WithRawCoordsMatcher WithRawCoords(float rawX, float rawY) {
+ return WithRawCoordsMatcher(0, rawX, rawY);
+}
+
+inline WithRawCoordsMatcher WithPointerRawCoords(size_t pointerIndex, float rawX, float rawY) {
+ return WithRawCoordsMatcher(pointerIndex, rawX, rawY);
+}
+
+/// Pointer count
+class WithPointerCountMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithPointerCountMatcher(size_t pointerCount) : mPointerCount(pointerCount) {}
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+ if (event.getPointerCount() != mPointerCount) {
+ *os << "expected pointer count " << mPointerCount << ", but got "
+ << event.getPointerCount();
+ return false;
+ }
+ return true;
+ }
+
+ bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+ if (event.pointerCoords.size() != mPointerCount) {
+ *os << "expected pointer count " << mPointerCount << ", but got "
+ << event.pointerCoords.size();
+ return false;
+ }
+ return true;
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "with pointer count " << mPointerCount; }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointer count"; }
+
+private:
+ const size_t mPointerCount;
+};
+
+inline WithPointerCountMatcher WithPointerCount(size_t pointerCount) {
+ return WithPointerCountMatcher(pointerCount);
+}
+
+/// Pointers matcher
+class WithPointersMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithPointersMatcher(std::map<int32_t, PointF> pointers) : mPointers(pointers) {}
+
+ bool MatchAndExplain(const MotionEvent& event, std::ostream* os) const {
+ std::map<int32_t, PointF> actualPointers;
+ for (size_t pointerIndex = 0; pointerIndex < event.getPointerCount(); pointerIndex++) {
+ const int32_t pointerId = event.getPointerId(pointerIndex);
+ actualPointers[pointerId] = {event.getX(pointerIndex), event.getY(pointerIndex)};
+ }
+
+ if (mPointers != actualPointers) {
+ *os << "expected pointers " << dumpMap(mPointers, constToString, pointFToString)
+ << ", but got " << dumpMap(actualPointers, constToString, pointFToString);
+ return false;
+ }
+ return true;
+ }
+
+ bool MatchAndExplain(const NotifyMotionArgs& event, std::ostream* os) const {
+ std::map<int32_t, PointF> actualPointers;
+ for (size_t pointerIndex = 0; pointerIndex < event.pointerCoords.size(); pointerIndex++) {
+ const int32_t pointerId = event.pointerProperties[pointerIndex].id;
+ actualPointers[pointerId] = {event.pointerCoords[pointerIndex].getX(),
+ event.pointerCoords[pointerIndex].getY()};
+ }
+
+ if (mPointers != actualPointers) {
+ *os << "expected pointers " << dumpMap(mPointers, constToString, pointFToString)
+ << ", but got " << dumpMap(actualPointers, constToString, pointFToString);
+ return false;
+ }
+ return true;
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with pointers " << dumpMap(mPointers, constToString, pointFToString);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong pointers"; }
+
+private:
+ const std::map<int32_t, PointF> mPointers;
+};
+
+inline WithPointersMatcher WithPointers(
+ const std::map<int32_t /*id*/, PointF /*coords*/>& pointers) {
+ return WithPointersMatcher(pointers);
+}
+
+/// Key code
+class WithKeyCodeMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithKeyCodeMatcher(int32_t keyCode) : mKeyCode(keyCode) {}
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mKeyCode == args.keyCode;
+ }
+
+ bool MatchAndExplain(const KeyEvent& event, std::ostream*) const {
+ return mKeyCode == event.getKeyCode();
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with key code " << KeyEvent::getLabel(mKeyCode);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong key code"; }
+
+private:
+ const int32_t mKeyCode;
+};
+
+inline WithKeyCodeMatcher WithKeyCode(int32_t keyCode) {
+ return WithKeyCodeMatcher(keyCode);
+}
+
+/// EventId
+class WithEventIdMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithEventIdMatcher(int32_t eventId) : mEventId(eventId) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mEventId == args.id;
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mEventId == args.id;
+ }
+
+ bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+ return mEventId == event.getId();
+ }
+
+ void DescribeTo(std::ostream* os) const { *os << "with eventId 0x" << std::hex << mEventId; }
+
+ void DescribeNegationTo(std::ostream* os) const {
+ *os << "with eventId not equal to 0x" << std::hex << mEventId;
+ }
+
+private:
+ const int32_t mEventId;
+};
+
+inline WithEventIdMatcher WithEventId(int32_t eventId) {
+ return WithEventIdMatcher(eventId);
+}
+
+/// EventIdSource
+class WithEventIdSourceMatcher {
+public:
+ using is_gtest_matcher = void;
+ explicit WithEventIdSourceMatcher(IdGenerator::Source eventIdSource)
+ : mEventIdSource(eventIdSource) {}
+
+ bool MatchAndExplain(const NotifyMotionArgs& args, std::ostream*) const {
+ return mEventIdSource == IdGenerator::getSource(args.id);
+ }
+
+ bool MatchAndExplain(const NotifyKeyArgs& args, std::ostream*) const {
+ return mEventIdSource == IdGenerator::getSource(args.id);
+ }
+
+ bool MatchAndExplain(const InputEvent& event, std::ostream*) const {
+ return mEventIdSource == IdGenerator::getSource(event.getId());
+ }
+
+ void DescribeTo(std::ostream* os) const {
+ *os << "with eventId from source 0x" << std::hex << ftl::to_underlying(mEventIdSource);
+ }
+
+ void DescribeNegationTo(std::ostream* os) const { *os << "wrong event from source"; }
+
+private:
+ const IdGenerator::Source mEventIdSource;
+};
+
+inline WithEventIdSourceMatcher WithEventIdSource(IdGenerator::Source eventIdSource) {
+ return WithEventIdSourceMatcher(eventIdSource);
+}
+
+MATCHER_P(WithRepeatCount, repeatCount, "KeyEvent with specified repeat count") {
+ return arg.getRepeatCount() == repeatCount;
+}
+
+MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") {
+ const auto argPointerId = arg.pointerProperties[index].id;
+ *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId;
+ return argPointerId == id;
+}
+
+MATCHER_P2(WithCursorPosition, x, y, "InputEvent with specified cursor position") {
+ const auto argX = arg.xCursorPosition;
+ const auto argY = arg.yCursorPosition;
+ *result_listener << "expected cursor position (" << x << ", " << y << "), but got (" << argX
+ << ", " << argY << ")";
+ return (isnan(x) ? isnan(argX) : x == argX) && (isnan(y) ? isnan(argY) : y == argY);
+}
+
+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(WithGestureSwipeFingerCount, count,
+ "InputEvent with specified touchpad swipe finger count") {
+ const auto argFingerCount =
+ arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_GESTURE_SWIPE_FINGER_COUNT);
+ *result_listener << "expected gesture swipe finger count " << count << " but got "
+ << argFingerCount;
+ return fabs(argFingerCount - count) <= 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(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/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp
index 57b382c718..41e250f789 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<NotifyInputDevicesChangedArgs>(outEventArgs,
+ "Expected notifyInputDevicesChanged() "
+ "to have been called."));
+}
+
void TestInputListener::assertNotifyConfigurationChangedWasCalled(
NotifyConfigurationChangedArgs* outEventArgs) {
ASSERT_NO_FATAL_FAILURE(
@@ -49,6 +57,18 @@ void TestInputListener::assertNotifyDeviceResetWasCalled(NotifyDeviceResetArgs*
"Expected notifyDeviceReset() to have been called."));
}
+void TestInputListener::clearNotifyDeviceResetCalls() {
+ std::scoped_lock<std::mutex> lock(mLock);
+ std::get<std::vector<NotifyDeviceResetArgs>>(mQueues).clear();
+}
+
+void TestInputListener::assertNotifyDeviceResetWasCalled(
+ const ::testing::Matcher<NotifyDeviceResetArgs>& matcher) {
+ NotifyDeviceResetArgs outEventArgs;
+ ASSERT_NO_FATAL_FAILURE(assertNotifyDeviceResetWasCalled(&outEventArgs));
+ ASSERT_THAT(outEventArgs, matcher);
+}
+
void TestInputListener::assertNotifyDeviceResetWasNotCalled() {
ASSERT_NO_FATAL_FAILURE(
assertNotCalled<NotifyDeviceResetArgs>("notifyDeviceReset() should not be called."));
@@ -59,26 +79,34 @@ void TestInputListener::assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs) {
assertCalled<NotifyKeyArgs>(outEventArgs, "Expected notifyKey() to have been called."));
}
+void TestInputListener::assertNotifyKeyWasCalled(const ::testing::Matcher<NotifyKeyArgs>& matcher) {
+ NotifyKeyArgs outEventArgs;
+ ASSERT_NO_FATAL_FAILURE(assertNotifyKeyWasCalled(&outEventArgs));
+ ASSERT_THAT(outEventArgs, matcher);
+}
+
void TestInputListener::assertNotifyKeyWasNotCalled() {
ASSERT_NO_FATAL_FAILURE(assertNotCalled<NotifyKeyArgs>("notifyKey() should not be called."));
}
-void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs) {
+void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs,
+ std::optional<TimePoint> waitUntil) {
ASSERT_NO_FATAL_FAILURE(
assertCalled<NotifyMotionArgs>(outEventArgs,
- "Expected notifyMotion() to have been called."));
+ "Expected notifyMotion() to have been called.",
+ waitUntil));
}
void TestInputListener::assertNotifyMotionWasCalled(
- const ::testing::Matcher<NotifyMotionArgs>& matcher) {
+ const ::testing::Matcher<NotifyMotionArgs>& matcher, std::optional<TimePoint> 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<TimePoint> waitUntil) {
ASSERT_NO_FATAL_FAILURE(
- assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called."));
+ assertNotCalled<NotifyMotionArgs>("notifyMotion() should not be called.", waitUntil));
}
void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) {
@@ -113,15 +141,18 @@ void TestInputListener::assertNotifyCaptureWasNotCalled() {
}
template <class NotifyArgsType>
-void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message) {
+void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string message,
+ std::optional<TimePoint> waitUntil) {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(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 +164,64 @@ void TestInputListener::assertCalled(NotifyArgsType* outEventArgs, std::string m
}
template <class NotifyArgsType>
-void TestInputListener::assertNotCalled(std::string message) {
+void TestInputListener::assertNotCalled(std::string message, std::optional<TimePoint> waitUntil) {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(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 <class NotifyArgsType>
-void TestInputListener::notify(const NotifyArgsType* args) {
+void TestInputListener::addToQueue(const NotifyArgsType& args) {
std::scoped_lock<std::mutex> lock(mLock);
std::vector<NotifyArgsType>& queue = std::get<std::vector<NotifyArgsType>>(mQueues);
- queue.push_back(*args);
+ queue.push_back(args);
mCondition.notify_all();
}
-void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) {
- notify<NotifyConfigurationChangedArgs>(args);
+void TestInputListener::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ addToQueue<NotifyInputDevicesChangedArgs>(args);
+}
+
+void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) {
+ addToQueue<NotifyConfigurationChangedArgs>(args);
}
-void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) {
- notify<NotifyDeviceResetArgs>(args);
+void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
+ addToQueue<NotifyDeviceResetArgs>(args);
}
-void TestInputListener::notifyKey(const NotifyKeyArgs* args) {
- notify<NotifyKeyArgs>(args);
+void TestInputListener::notifyKey(const NotifyKeyArgs& args) {
+ addToQueue<NotifyKeyArgs>(args);
}
-void TestInputListener::notifyMotion(const NotifyMotionArgs* args) {
- notify<NotifyMotionArgs>(args);
+void TestInputListener::notifyMotion(const NotifyMotionArgs& args) {
+ addToQueue<NotifyMotionArgs>(args);
}
-void TestInputListener::notifySwitch(const NotifySwitchArgs* args) {
- notify<NotifySwitchArgs>(args);
+void TestInputListener::notifySwitch(const NotifySwitchArgs& args) {
+ addToQueue<NotifySwitchArgs>(args);
}
-void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs* args) {
- notify<NotifyPointerCaptureChangedArgs>(args);
+void TestInputListener::notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs& args) {
+ addToQueue<NotifyPointerCaptureChangedArgs>(args);
}
-void TestInputListener::notifySensor(const NotifySensorArgs* args) {
- notify<NotifySensorArgs>(args);
+void TestInputListener::notifySensor(const NotifySensorArgs& args) {
+ addToQueue<NotifySensorArgs>(args);
}
-void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs* args) {
- notify<NotifyVibratorStateArgs>(args);
+void TestInputListener::notifyVibratorState(const NotifyVibratorStateArgs& args) {
+ addToQueue<NotifyVibratorStateArgs>(args);
}
} // namespace android
diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h
index 0bdfc6b9fb..3c5e0146d6 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 <android-base/thread_annotations.h>
#include <gmock/gmock.h>
@@ -34,24 +33,37 @@ public:
std::chrono::milliseconds eventDidNotHappenTimeout = 0ms);
virtual ~TestInputListener();
+ using TimePoint = std::chrono::time_point<std::chrono::system_clock>;
+
+ void assertNotifyInputDevicesChangedWasCalled(
+ NotifyInputDevicesChangedArgs* outEventArgs = nullptr);
+
void assertNotifyConfigurationChangedWasCalled(
NotifyConfigurationChangedArgs* outEventArgs = nullptr);
void assertNotifyConfigurationChangedWasNotCalled();
+ void clearNotifyDeviceResetCalls();
+
+ void assertNotifyDeviceResetWasCalled(const ::testing::Matcher<NotifyDeviceResetArgs>& matcher);
+
void assertNotifyDeviceResetWasCalled(NotifyDeviceResetArgs* outEventArgs = nullptr);
void assertNotifyDeviceResetWasNotCalled();
void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = nullptr);
+ void assertNotifyKeyWasCalled(const ::testing::Matcher<NotifyKeyArgs>& matcher);
+
void assertNotifyKeyWasNotCalled();
- void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr);
+ void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr,
+ std::optional<TimePoint> waitUntil = {});
- void assertNotifyMotionWasCalled(const ::testing::Matcher<NotifyMotionArgs>& matcher);
+ void assertNotifyMotionWasCalled(const ::testing::Matcher<NotifyMotionArgs>& matcher,
+ std::optional<TimePoint> waitUntil = {});
- void assertNotifyMotionWasNotCalled();
+ void assertNotifyMotionWasNotCalled(std::optional<TimePoint> waitUntil = {});
void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr);
@@ -62,36 +74,40 @@ public:
private:
template <class NotifyArgsType>
- void assertCalled(NotifyArgsType* outEventArgs, std::string message);
+ void assertCalled(NotifyArgsType* outEventArgs, std::string message,
+ std::optional<TimePoint> waitUntil = {});
template <class NotifyArgsType>
- void assertNotCalled(std::string message);
+ void assertNotCalled(std::string message, std::optional<TimePoint> timeout = {});
template <class NotifyArgsType>
- 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::vector<NotifyConfigurationChangedArgs>, //
+ std::tuple<std::vector<NotifyInputDevicesChangedArgs>, //
+ std::vector<NotifyConfigurationChangedArgs>, //
std::vector<NotifyDeviceResetArgs>, //
std::vector<NotifyKeyArgs>, //
std::vector<NotifyMotionArgs>, //
@@ -103,4 +119,3 @@ private:
};
} // namespace android
-#endif
diff --git a/services/inputflinger/tests/TimerProvider_test.cpp b/services/inputflinger/tests/TimerProvider_test.cpp
new file mode 100644
index 0000000000..cb28823679
--- /dev/null
+++ b/services/inputflinger/tests/TimerProvider_test.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 "gestures/TimerProvider.h"
+
+#include <vector>
+
+#include <gtest/gtest.h>
+
+#include "InterfaceMocks.h"
+#include "TestConstants.h"
+#include "include/gestures.h"
+
+namespace android {
+
+namespace {
+
+class TestTimerProvider : public TimerProvider {
+public:
+ TestTimerProvider(InputReaderContext& context) : TimerProvider(context) {}
+
+ void setCurrentTime(nsecs_t time) { mCurrentTime = time; }
+
+protected:
+ nsecs_t getCurrentTime() override { return mCurrentTime; }
+
+private:
+ nsecs_t mCurrentTime = 0;
+};
+
+stime_t pushTimeOntoVector(stime_t triggerTime, void* data) {
+ std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(data);
+ times->push_back(triggerTime);
+ return NO_DEADLINE;
+}
+
+stime_t copyTimeToVariable(stime_t triggerTime, void* data) {
+ stime_t* time = static_cast<stime_t*>(data);
+ *time = triggerTime;
+ return NO_DEADLINE;
+}
+
+stime_t incrementInt(stime_t triggerTime, void* data) {
+ int* count = static_cast<int*>(data);
+ *count += 1;
+ return NO_DEADLINE;
+}
+
+} // namespace
+
+using testing::AtLeast;
+
+class TimerProviderTest : public testing::Test {
+public:
+ TimerProviderTest() : mProvider(mMockContext) {}
+
+protected:
+ void triggerCallbacksWithFakeTime(nsecs_t time) {
+ mProvider.setCurrentTime(time);
+ mProvider.triggerCallbacks(time);
+ }
+
+ MockInputReaderContext mMockContext;
+ TestTimerProvider mProvider;
+};
+
+TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsExactlyOnTime) {
+ GesturesTimer* timer = mProvider.createTimer();
+ std::vector<stime_t> callTimes;
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(3);
+
+ // Call through kGestureTimerProvider in this test case, so that we cover the stime_t to nsecs_t
+ // conversion code. This is why the delay is 1.0 rather than 1'000'000'000 here.
+ kGestureTimerProvider.set_fn(&mProvider, timer, 1.0, &pushTimeOntoVector, &callTimes);
+
+ triggerCallbacksWithFakeTime(900'000'000);
+ triggerCallbacksWithFakeTime(999'999'999);
+ EXPECT_EQ(0u, callTimes.size());
+ triggerCallbacksWithFakeTime(1'000'000'000);
+ ASSERT_EQ(1u, callTimes.size());
+ EXPECT_NEAR(1.0, callTimes[0], EPSILON);
+
+ // Now that the timer has triggered, it shouldn't trigger again if we get another timeout from
+ // InputReader.
+ triggerCallbacksWithFakeTime(1'300'000'000);
+ EXPECT_EQ(1u, callTimes.size());
+}
+
+TEST_F(TimerProviderTest, SingleDeadlineTriggersWhenTimeoutIsLate) {
+ GesturesTimer* timer = mProvider.createTimer();
+ stime_t callTime = -1.0;
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+ mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime);
+
+ triggerCallbacksWithFakeTime(1'010'000'000);
+ EXPECT_NEAR(1.01, callTime, EPSILON);
+}
+
+TEST_F(TimerProviderTest, SingleRescheduledDeadlineTriggers) {
+ GesturesTimer* timer = mProvider.createTimer();
+ std::vector<stime_t> callTimes;
+ auto callback = [](stime_t triggerTime, void* callbackData) {
+ std::vector<stime_t>* times = static_cast<std::vector<stime_t>*>(callbackData);
+ times->push_back(triggerTime);
+ if (times->size() < 2) {
+ return 1.0;
+ } else {
+ return NO_DEADLINE;
+ }
+ };
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+ // The deadline should be rescheduled for 2.01s, since the first triggerCallbacks call is 0.01s
+ // late.
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(2'010'000'000)).Times(1);
+
+ mProvider.setDeadline(timer, 1'000'000'000, callback, &callTimes);
+
+ triggerCallbacksWithFakeTime(1'010'000'000);
+ ASSERT_EQ(1u, callTimes.size());
+ EXPECT_NEAR(1.01, callTimes[0], EPSILON);
+
+ triggerCallbacksWithFakeTime(2'020'000'000);
+ ASSERT_EQ(2u, callTimes.size());
+ EXPECT_NEAR(1.01, callTimes[0], EPSILON);
+ EXPECT_NEAR(2.02, callTimes[1], EPSILON);
+
+ triggerCallbacksWithFakeTime(3'000'000'000);
+ EXPECT_EQ(2u, callTimes.size());
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithMultipleTimeouts) {
+ GesturesTimer* timer = mProvider.createTimer();
+ std::vector<stime_t> callTimes1;
+ std::vector<stime_t> callTimes2;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1);
+
+ mProvider.setDeadline(timer, 1'000'000'000, &pushTimeOntoVector, &callTimes1);
+ mProvider.setDeadline(timer, 1'500'000'000, &pushTimeOntoVector, &callTimes2);
+
+ EXPECT_EQ(0u, callTimes1.size());
+ EXPECT_EQ(0u, callTimes2.size());
+
+ triggerCallbacksWithFakeTime(1'010'000'000);
+ ASSERT_EQ(1u, callTimes1.size());
+ EXPECT_NEAR(1.01, callTimes1[0], EPSILON);
+ EXPECT_EQ(0u, callTimes2.size());
+
+ triggerCallbacksWithFakeTime(1'500'000'000);
+ EXPECT_EQ(1u, callTimes1.size());
+ ASSERT_EQ(1u, callTimes2.size());
+ EXPECT_NEAR(1.5, callTimes2[0], EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesTriggerWithOneLateTimeout) {
+ GesturesTimer* timer = mProvider.createTimer();
+ stime_t callTime1 = -1.0;
+ stime_t callTime2 = -1.0;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+
+ mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime1);
+ mProvider.setDeadline(timer, 1'500'000'000, &copyTimeToVariable, &callTime2);
+
+ triggerCallbacksWithFakeTime(1'510'000'000);
+ EXPECT_NEAR(1.51, callTime1, EPSILON);
+ EXPECT_NEAR(1.51, callTime2, EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleDeadlinesAtSameTimeTriggerTogether) {
+ GesturesTimer* timer = mProvider.createTimer();
+ stime_t callTime1 = -1.0;
+ stime_t callTime2 = -1.0;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(AtLeast(1));
+
+ mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime1);
+ mProvider.setDeadline(timer, 1'000'000'000, &copyTimeToVariable, &callTime2);
+
+ triggerCallbacksWithFakeTime(1'000'000'000);
+ EXPECT_NEAR(1.0, callTime1, EPSILON);
+ EXPECT_NEAR(1.0, callTime2, EPSILON);
+}
+
+TEST_F(TimerProviderTest, MultipleTimersTriggerCorrectly) {
+ GesturesTimer* timer1 = mProvider.createTimer();
+ GesturesTimer* timer2 = mProvider.createTimer();
+ std::vector<stime_t> callTimes1;
+ std::vector<stime_t> callTimes2;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'250'000'000)).Times(1);
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'500'000'000)).Times(1);
+
+ mProvider.setDeadline(timer1, 500'000'000, &pushTimeOntoVector, &callTimes1);
+ mProvider.setDeadline(timer1, 1'250'000'000, &pushTimeOntoVector, &callTimes1);
+ mProvider.setDeadline(timer1, 1'500'000'000, &pushTimeOntoVector, &callTimes1);
+ mProvider.setDeadline(timer2, 750'000'000, &pushTimeOntoVector, &callTimes2);
+ mProvider.setDeadline(timer2, 1'250'000'000, &pushTimeOntoVector, &callTimes2);
+
+ triggerCallbacksWithFakeTime(800'000'000);
+ ASSERT_EQ(1u, callTimes1.size());
+ EXPECT_NEAR(0.8, callTimes1[0], EPSILON);
+ ASSERT_EQ(1u, callTimes2.size());
+ EXPECT_NEAR(0.8, callTimes2[0], EPSILON);
+
+ triggerCallbacksWithFakeTime(1'250'000'000);
+ ASSERT_EQ(2u, callTimes1.size());
+ EXPECT_NEAR(1.25, callTimes1[1], EPSILON);
+ ASSERT_EQ(2u, callTimes2.size());
+ EXPECT_NEAR(1.25, callTimes2[1], EPSILON);
+
+ triggerCallbacksWithFakeTime(1'501'000'000);
+ ASSERT_EQ(3u, callTimes1.size());
+ EXPECT_NEAR(1.501, callTimes1[2], EPSILON);
+ EXPECT_EQ(2u, callTimes2.size());
+}
+
+TEST_F(TimerProviderTest, CancelledTimerDoesntTrigger) {
+ GesturesTimer* timer = mProvider.createTimer();
+ int numCalls = 0;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+ mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCalls);
+ mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls);
+ mProvider.cancelTimer(timer);
+
+ triggerCallbacksWithFakeTime(1'100'000'000);
+ EXPECT_EQ(0, numCalls);
+}
+
+TEST_F(TimerProviderTest, CancellingOneTimerDoesntAffectOthers) {
+ GesturesTimer* timer1 = mProvider.createTimer();
+ GesturesTimer* timer2 = mProvider.createTimer();
+ int numCalls1 = 0;
+ int numCalls2 = 0;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+
+ mProvider.setDeadline(timer1, 500'000'000, &incrementInt, &numCalls1);
+ mProvider.setDeadline(timer2, 500'000'000, &incrementInt, &numCalls2);
+ mProvider.setDeadline(timer2, 1'000'000'000, &incrementInt, &numCalls2);
+ mProvider.cancelTimer(timer1);
+
+ triggerCallbacksWithFakeTime(501'000'000);
+ EXPECT_EQ(0, numCalls1);
+ EXPECT_EQ(1, numCalls2);
+
+ triggerCallbacksWithFakeTime(1'000'000'000);
+ EXPECT_EQ(0, numCalls1);
+ EXPECT_EQ(2, numCalls2);
+}
+
+TEST_F(TimerProviderTest, CancellingOneTimerCausesNewTimeoutRequestForAnother) {
+ GesturesTimer* timer1 = mProvider.createTimer();
+ GesturesTimer* timer2 = mProvider.createTimer();
+ auto callback = [](stime_t, void*) { return NO_DEADLINE; };
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(AtLeast(1));
+
+ mProvider.setDeadline(timer1, 500'000'000, callback, nullptr);
+ mProvider.setDeadline(timer2, 1'000'000'000, callback, nullptr);
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+ mProvider.cancelTimer(timer1);
+}
+
+TEST_F(TimerProviderTest, CancelledTimerCanBeReused) {
+ GesturesTimer* timer = mProvider.createTimer();
+ int numCallsBeforeCancellation = 0;
+ int numCallsAfterCancellation = 0;
+
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(500'000'000)).Times(1);
+ EXPECT_CALL(mMockContext, requestTimeoutAtTime(1'000'000'000)).Times(1);
+
+ mProvider.setDeadline(timer, 500'000'000, &incrementInt, &numCallsBeforeCancellation);
+ mProvider.cancelTimer(timer);
+ mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCallsAfterCancellation);
+
+ triggerCallbacksWithFakeTime(1'000'000'000);
+ EXPECT_EQ(0, numCallsBeforeCancellation);
+ EXPECT_EQ(1, numCallsAfterCancellation);
+}
+
+TEST_F(TimerProviderTest, FreeingTimerCancelsFirst) {
+ GesturesTimer* timer = mProvider.createTimer();
+ int numCalls = 0;
+
+ mProvider.setDeadline(timer, 1'000'000'000, &incrementInt, &numCalls);
+ mProvider.freeTimer(timer);
+
+ triggerCallbacksWithFakeTime(1'000'000'000);
+ EXPECT_EQ(0, numCalls);
+}
+
+} // namespace android \ No newline at end of file
diff --git a/services/inputflinger/tests/TouchpadInputMapper_test.cpp b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
new file mode 100644
index 0000000000..8cf738cf4f
--- /dev/null
+++ b/services/inputflinger/tests/TouchpadInputMapper_test.cpp
@@ -0,0 +1,238 @@
+/*
+ * 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 <android-base/logging.h>
+#include <gtest/gtest.h>
+
+#include <com_android_input_flags.h>
+#include <thread>
+#include "FakePointerController.h"
+#include "InputMapperTest.h"
+#include "InterfaceMocks.h"
+#include "TestEventMatchers.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;
+constexpr int32_t DISPLAY_ID = 0;
+constexpr int32_t DISPLAY_WIDTH = 480;
+constexpr int32_t DISPLAY_HEIGHT = 800;
+constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
+
+namespace input_flags = com::android::input::flags;
+
+/**
+ * Unit tests for TouchpadInputMapper.
+ */
+class TouchpadInputMapperTestBase : 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<TouchpadInputMapper>(*mDeviceContext, mReaderConfiguration);
+ }
+};
+
+class TouchpadInputMapperTest : public TouchpadInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(false);
+ TouchpadInputMapperTestBase::SetUp();
+ }
+};
+
+/**
+ * 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<NotifyArgs> 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<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+ // Liftoff
+ args.clear();
+ args += process(EV_ABS, ABS_MT_PRESSURE, 0);
+ args += process(EV_ABS, ABS_MT_TRACKING_ID, -1);
+ args += process(EV_KEY, BTN_TOUCH, 0);
+ setScanCodeState(KeyState::UP, {BTN_TOOL_FINGER});
+ args += process(EV_KEY, BTN_TOOL_FINGER, 0);
+ args += process(EV_SYN, SYN_REPORT, 0);
+ ASSERT_THAT(args, testing::IsEmpty());
+}
+
+class TouchpadInputMapperTestWithChoreographer : public TouchpadInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(true);
+ TouchpadInputMapperTestBase::SetUp();
+ }
+};
+
+// TODO(b/311416205): De-duplicate the test cases after the refactoring is complete and the flagging
+// logic can be removed.
+/**
+ * Start moving the finger and then click the left touchpad button. Check whether HOVER_EXIT is
+ * generated when hovering stops. Currently, it is not.
+ * In the current implementation, HOVER_MOVE and ACTION_DOWN events are not sent out right away,
+ * but only after the button is released.
+ */
+TEST_F(TouchpadInputMapperTestWithChoreographer, HoverAndLeftButtonPress) {
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+
+ std::list<NotifyArgs> args;
+
+ args += mMapper->reconfigure(systemTime(SYSTEM_TIME_MONOTONIC), mReaderConfiguration,
+ InputReaderConfiguration::Change::DISPLAY_INFO);
+ ASSERT_THAT(args, testing::IsEmpty());
+
+ 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<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_DOWN)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_PRESS)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(BUTTON_RELEASE)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(ACTION_UP)),
+ VariantWith<NotifyMotionArgs>(WithMotionAction(HOVER_MOVE))));
+
+ // 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 9c939198f3..acc7023fc8 100644
--- a/services/inputflinger/tests/UinputDevice.cpp
+++ b/services/inputflinger/tests/UinputDevice.cpp
@@ -17,13 +17,15 @@
#include "UinputDevice.h"
#include <android-base/stringprintf.h>
+#include <cutils/memory.h>
#include <fcntl.h>
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<int> keys)
- : UinputDevice(UinputKeyboard::KEYBOARD_NAME), mKeys(keys.begin(), keys.end()) {}
+UinputKeyboard::UinputKeyboard(const char* name, int16_t productId, std::initializer_list<int> 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,65 @@ 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);
+}
+
+// --- UinputKeyboardWithHidUsage ---
+
+UinputKeyboardWithHidUsage::UinputKeyboardWithHidUsage(std::initializer_list<int> keys)
+ : UinputKeyboard(DEVICE_NAME, PRODUCT_ID, keys) {}
+
+void UinputKeyboardWithHidUsage::configureDevice(int fd, uinput_user_dev* device) {
+ UinputKeyboard::configureDevice(fd, device);
+
+ ioctl(fd, UI_SET_EVBIT, EV_MSC);
+ ioctl(fd, UI_SET_MSCBIT, MSC_SCAN);
+}
// --- UinputTouchScreen ---
-UinputTouchScreen::UinputTouchScreen(const Rect* size)
- : UinputDevice(UinputTouchScreen::DEVICE_NAME), mSize(*size) {}
+
+UinputTouchScreen::UinputTouchScreen(const Rect& size, const std::string& physicalPort)
+ : UinputKeyboard(DEVICE_NAME, PRODUCT_ID,
+ {BTN_TOUCH, BTN_TOOL_PEN, BTN_STYLUS, BTN_STYLUS2, BTN_STYLUS3}),
+ mSize(size),
+ mPhysicalPort(physicalPort) {}
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);
@@ -144,8 +189,11 @@ void UinputTouchScreen::configureDevice(int fd, uinput_user_dev* device) {
ioctl(fd, UI_SET_ABSBIT, ABS_MT_POSITION_Y);
ioctl(fd, UI_SET_ABSBIT, ABS_MT_TRACKING_ID);
ioctl(fd, UI_SET_ABSBIT, ABS_MT_TOOL_TYPE);
+ ioctl(fd, UI_SET_ABSBIT, ABS_MT_PRESSURE);
ioctl(fd, UI_SET_PROPBIT, INPUT_PROP_DIRECT);
- ioctl(fd, UI_SET_KEYBIT, BTN_TOUCH);
+ if (!mPhysicalPort.empty()) {
+ ioctl(fd, UI_SET_PHYS, mPhysicalPort.c_str());
+ }
device->absmin[ABS_MT_SLOT] = RAW_SLOT_MIN;
device->absmax[ABS_MT_SLOT] = RAW_SLOT_MAX;
@@ -157,6 +205,10 @@ 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;
+ device->absmin[ABS_MT_PRESSURE] = RAW_PRESSURE_MIN;
+ device->absmax[ABS_MT_PRESSURE] = RAW_PRESSURE_MAX;
}
void UinputTouchScreen::sendSlot(int32_t slot) {
@@ -169,6 +221,7 @@ void UinputTouchScreen::sendTrackingId(int32_t trackingId) {
void UinputTouchScreen::sendDown(const Point& point) {
injectEvent(EV_KEY, BTN_TOUCH, 1);
+ injectEvent(EV_ABS, ABS_MT_PRESSURE, RAW_PRESSURE_MAX);
injectEvent(EV_ABS, ABS_MT_POSITION_X, point.x);
injectEvent(EV_ABS, ABS_MT_POSITION_Y, point.y);
}
@@ -178,6 +231,10 @@ void UinputTouchScreen::sendMove(const Point& point) {
injectEvent(EV_ABS, ABS_MT_POSITION_Y, point.y);
}
+void UinputTouchScreen::sendPressure(int32_t pressure) {
+ injectEvent(EV_ABS, ABS_MT_PRESSURE, pressure);
+}
+
void UinputTouchScreen::sendPointerUp() {
sendTrackingId(0xffffffff);
}
diff --git a/services/inputflinger/tests/UinputDevice.h b/services/inputflinger/tests/UinputDevice.h
index a37fc2b790..d4b4e77e36 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 <android-base/unique_fd.h>
#include <gtest/gtest.h>
@@ -33,7 +32,7 @@ namespace android {
template <class D, class... Ts>
std::unique_ptr<D> createUinputDevice(Ts... args) {
// Using `new` to access non-public constructors.
- std::unique_ptr<D> dev(new D(&args...));
+ std::unique_ptr<D> 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<D> createUinputDevice(Ts... args);
protected:
- UinputKeyboard(std::initializer_list<int> keys = {});
+ explicit UinputKeyboard(const char* name, int16_t productId = PRODUCT_ID,
+ std::initializer_list<int> keys = {});
-private:
void configureDevice(int fd, uinput_user_dev* device) override;
+private:
std::set<int> 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,86 @@ public:
friend std::unique_ptr<D> 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 <class D, class... Ts>
friend std::unique_ptr<D> createUinputDevice(Ts... args);
private:
- UinputSteamController();
+ 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 <class D, class... Ts>
+ friend std::unique_ptr<D> 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 <class D, class... Ts>
+ friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+private:
+ void configureDevice(int fd, uinput_user_dev* device) override;
+
+ explicit UinputExternalStylusWithPressure();
+};
+
+// --- UinputKeyboardWithUsage ---
+// A keyboard that supports EV_MSC MSC_SCAN through which it can report HID usage codes.
+
+class UinputKeyboardWithHidUsage : public UinputKeyboard {
+public:
+ static constexpr const char* DEVICE_NAME = "Test Uinput Keyboard With Usage";
+ static constexpr int16_t PRODUCT_ID = 47;
+
+ template <class D, class... Ts>
+ friend std::unique_ptr<D> createUinputDevice(Ts... args);
+
+protected:
+ explicit UinputKeyboardWithHidUsage(std::initializer_list<int> keys);
+
+ void configureDevice(int fd, uinput_user_dev* device) override;
};
// --- 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 = 48;
+
static const int32_t RAW_TOUCH_MIN = 0;
static const int32_t RAW_TOUCH_MAX = 31;
static const int32_t RAW_ID_MIN = 0;
@@ -139,6 +206,7 @@ public:
void sendTrackingId(int32_t trackingId);
void sendDown(const Point& point);
void sendMove(const Point& point);
+ void sendPressure(int32_t pressure);
void sendPointerUp();
void sendUp();
void sendToolType(int32_t toolType);
@@ -147,13 +215,12 @@ public:
const Point getCenterPoint();
protected:
- UinputTouchScreen(const Rect* size);
+ explicit UinputTouchScreen(const Rect& size, const std::string& physicalPort = "");
private:
void configureDevice(int fd, uinput_user_dev* device) override;
const Rect mSize;
+ const std::string mPhysicalPort;
};
} // 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 29fa001185..78f7291243 100644
--- a/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
+++ b/services/inputflinger/tests/UnwantedInteractionBlocker_test.cpp
@@ -23,6 +23,7 @@
#include <thread>
#include "ui/events/ozone/evdev/touch_filter/neural_stylus_palm_detection_filter.h"
+#include "TestEventMatchers.h"
#include "TestInputListener.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,14 +88,13 @@ 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,
- action, /* actionButton */ 0,
- /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
- MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
- pointerProperties, pointerCoords, /* xPrecision */ 0, /* yPrecision */ 0,
+ 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, 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;
}
@@ -118,15 +103,15 @@ static InputDeviceInfo generateTestDeviceInfo() {
InputDeviceIdentifier identifier;
auto info = InputDeviceInfo();
- info.initialize(DEVICE_ID, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
- /*isExternal*/ false, /*hasMic*/ false);
+ info.initialize(DEVICE_ID, /*generation=*/1, /*controllerNumber=*/1, identifier, "alias",
+ /*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);
- info.addMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN, 0, 2559, /*flat*/ 0,
- /*fuzz*/ 0, Y_RESOLUTION);
+ info.addMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHSCREEN, 0, 1599, /*flat=*/0,
+ /*fuzz=*/0, X_RESOLUTION);
+ info.addMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHSCREEN, 0, 2559, /*flat=*/0,
+ /*fuzz=*/0, Y_RESOLUTION);
info.addMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHSCREEN, 0, 255,
- /*flat*/ 0, /*fuzz*/ 0, MAJOR_RESOLUTION);
+ /*flat=*/0, /*fuzz=*/0, MAJOR_RESOLUTION);
return info;
}
@@ -152,9 +137,10 @@ TEST(DeviceInfoConversionTest, TabletDeviceTest) {
static void assertArgs(const NotifyMotionArgs& args, int32_t action,
const std::vector<std::pair<int32_t /*pointerId*/, PointerData>>& pointers) {
- ASSERT_EQ(action, args.action);
- ASSERT_EQ(pointers.size(), args.pointerCount);
- for (size_t i = 0; i < args.pointerCount; i++) {
+ ASSERT_EQ(action, args.action)
+ << "Expected " << MotionEvent::actionToString(action) << " but got " << args.action;
+ ASSERT_EQ(pointers.size(), args.getPointerCount());
+ for (size_t i = 0; i < args.getPointerCount(); i++) {
const auto& [pointerId, pointerData] = pointers[i];
ASSERT_EQ(pointerId, args.pointerProperties[i].id);
ASSERT_EQ(pointerData.x, args.pointerCoords[i].getX());
@@ -165,7 +151,7 @@ static void assertArgs(const NotifyMotionArgs& args, int32_t action,
}
TEST(RemovePointerIdsTest, RemoveOnePointer) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0,
AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
NotifyMotionArgs pointer1Only = removePointerIds(args, {0});
@@ -180,7 +166,7 @@ TEST(RemovePointerIdsTest, RemoveOnePointer) {
*/
TEST(RemovePointerIdsTest, RemoveTwoPointers) {
NotifyMotionArgs args =
- generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, AMOTION_EVENT_ACTION_MOVE,
+ generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, AMOTION_EVENT_ACTION_MOVE,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
NotifyMotionArgs pointer1Only = removePointerIds(args, {0, 2});
@@ -192,7 +178,7 @@ TEST(RemovePointerIdsTest, RemoveTwoPointers) {
* pointer during a POINTER_DOWN event.
*/
TEST(RemovePointerIdsTest, ActionPointerDown) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_DOWN,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
NotifyMotionArgs pointers0And2 = removePointerIds(args, {1});
@@ -206,11 +192,11 @@ TEST(RemovePointerIdsTest, ActionPointerDown) {
* Remove all pointers during a MOVE event.
*/
TEST(RemovePointerIdsTest, RemoveAllPointersDuringMove) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0,
AMOTION_EVENT_ACTION_MOVE, {{1, 2, 3}, {4, 5, 6}});
NotifyMotionArgs noPointers = removePointerIds(args, {0, 1});
- ASSERT_EQ(0u, noPointers.pointerCount);
+ ASSERT_EQ(0u, noPointers.getPointerCount());
}
/**
@@ -218,7 +204,7 @@ TEST(RemovePointerIdsTest, RemoveAllPointersDuringMove) {
* then we should just have ACTION_DOWN. Likewise, a POINTER_UP event should become an UP event.
*/
TEST(RemovePointerIdsTest, PointerDownBecomesDown) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_DOWN,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
NotifyMotionArgs pointer1 = removePointerIds(args, {0, 2});
@@ -233,11 +219,11 @@ TEST(RemovePointerIdsTest, PointerDownBecomesDown) {
* If a pointer that is now going down is canceled, then we can just drop the POINTER_DOWN event.
*/
TEST(CancelSuppressedPointersTest, CanceledPointerDownIsDropped) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_DOWN,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_DOWN,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
- /*newSuppressedPointerIds*/ {1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+ /*newSuppressedPointerIds=*/{1});
ASSERT_TRUE(result.empty());
}
@@ -245,11 +231,11 @@ TEST(CancelSuppressedPointersTest, CanceledPointerDownIsDropped) {
* If a pointer is already suppressed, the POINTER_UP event for this pointer should be dropped
*/
TEST(CancelSuppressedPointersTest, SuppressedPointerUpIsDropped) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_UP,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
- /*newSuppressedPointerIds*/ {1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{1},
+ /*newSuppressedPointerIds=*/{1});
ASSERT_TRUE(result.empty());
}
@@ -257,11 +243,11 @@ TEST(CancelSuppressedPointersTest, SuppressedPointerUpIsDropped) {
* If a pointer is already suppressed, it should be removed from a MOVE event.
*/
TEST(CancelSuppressedPointersTest, SuppressedPointerIsRemovedDuringMove) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
- /*newSuppressedPointerIds*/ {1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{1},
+ /*newSuppressedPointerIds=*/{1});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], MOVE, {{0, {1, 2, 3}}, {2, {7, 8, 9}}});
}
@@ -272,11 +258,11 @@ TEST(CancelSuppressedPointersTest, SuppressedPointerIsRemovedDuringMove) {
* 2) A MOVE event without this pointer
*/
TEST(CancelSuppressedPointersTest, NewlySuppressedPointerIsCanceled) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
- /*newSuppressedPointerIds*/ {1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+ /*newSuppressedPointerIds=*/{1});
ASSERT_EQ(2u, result.size());
assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -288,10 +274,10 @@ TEST(CancelSuppressedPointersTest, NewlySuppressedPointerIsCanceled) {
* should be canceled with ACTION_CANCEL.
*/
TEST(CancelSuppressedPointersTest, SingleSuppressedPointerIsCanceled) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}});
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE, {{1, 2, 3}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
- /*newSuppressedPointerIds*/ {0});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+ /*newSuppressedPointerIds=*/{0});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -302,11 +288,11 @@ TEST(CancelSuppressedPointersTest, SingleSuppressedPointerIsCanceled) {
* but this event should also have FLAG_CANCELED to indicate that this pointer was unintentional.
*/
TEST(CancelSuppressedPointersTest, SuppressedPointer1GoingUpIsCanceled) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_UP,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
- /*newSuppressedPointerIds*/ {1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+ /*newSuppressedPointerIds=*/{1});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], POINTER_1_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}, {2, {7, 8, 9}}});
ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -317,11 +303,11 @@ TEST(CancelSuppressedPointersTest, SuppressedPointer1GoingUpIsCanceled) {
* errors with handling pointer index inside the action.
*/
TEST(CancelSuppressedPointersTest, SuppressedPointer0GoingUpIsCanceled) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_0_UP,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_0_UP,
{{1, 2, 3}, {4, 5, 6}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
- /*newSuppressedPointerIds*/ {0});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+ /*newSuppressedPointerIds=*/{0});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], POINTER_0_UP, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -333,10 +319,10 @@ TEST(CancelSuppressedPointersTest, SuppressedPointer0GoingUpIsCanceled) {
*/
TEST(CancelSuppressedPointersTest, TwoNewlySuppressedPointersAreBothCanceled) {
NotifyMotionArgs args =
- generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, MOVE, {{1, 2, 3}, {4, 5, 6}});
+ generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, MOVE, {{1, 2, 3}, {4, 5, 6}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {},
- /*newSuppressedPointerIds*/ {0, 1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{},
+ /*newSuppressedPointerIds=*/{0, 1});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}, {1, {4, 5, 6}}});
ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -348,11 +334,11 @@ TEST(CancelSuppressedPointersTest, TwoNewlySuppressedPointersAreBothCanceled) {
* would undo the entire gesture.
*/
TEST(CancelSuppressedPointersTest, TwoPointersAreCanceledDuringPointerUp) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_1_UP,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_1_UP,
{{1, 2, 3}, {4, 5, 6}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {1},
- /*newSuppressedPointerIds*/ {0, 1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{1},
+ /*newSuppressedPointerIds=*/{0, 1});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], CANCEL, {{0, {1, 2, 3}}});
ASSERT_EQ(FLAG_CANCELED, result[0].flags);
@@ -363,11 +349,11 @@ TEST(CancelSuppressedPointersTest, TwoPointersAreCanceledDuringPointerUp) {
* this should become a regular DOWN event because it's the only pointer that will be valid now.
*/
TEST(CancelSuppressedPointersTest, NewPointerDownBecomesDown) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, POINTER_2_DOWN,
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, POINTER_2_DOWN,
{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}});
std::vector<NotifyMotionArgs> result =
- cancelSuppressedPointers(args, /*oldSuppressedPointerIds*/ {0, 1},
- /*newSuppressedPointerIds*/ {0, 1});
+ cancelSuppressedPointers(args, /*oldSuppressedPointerIds=*/{0, 1},
+ /*newSuppressedPointerIds=*/{0, 1});
ASSERT_EQ(1u, result.size());
assertArgs(result[0], DOWN, {{2, {7, 8, 9}}});
ASSERT_EQ(0, result[0].flags);
@@ -378,7 +364,7 @@ TEST(CancelSuppressedPointersTest, NewPointerDownBecomesDown) {
* struct is populated as expected.
*/
TEST(GetTouchesTest, ConvertDownEvent) {
- NotifyMotionArgs args = generateMotionArgs(/*downTime*/ 0, /*eventTime*/ 0, DOWN, {{1, 2, 3}});
+ NotifyMotionArgs args = generateMotionArgs(/*downTime=*/0, /*eventTime=*/0, DOWN, {{1, 2, 3}});
AndroidPalmFilterDeviceInfo deviceInfo = generatePalmFilterDeviceInfo();
SlotState slotState;
SlotState oldSlotState = slotState;
@@ -423,19 +409,19 @@ protected:
void SetUp() override {
mBlocker = std::make_unique<UnwantedInteractionBlocker>(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");
}
@@ -805,7 +771,7 @@ TEST_F(PalmRejectorTest, TwoPointersAreCanceled) {
ASSERT_EQ(POINTER_0_UP, argsList[0].action);
ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
ASSERT_EQ(MOVE, argsList[1].action);
- ASSERT_EQ(1u, argsList[1].pointerCount);
+ ASSERT_EQ(1u, argsList[1].getPointerCount());
ASSERT_EQ(0, argsList[1].flags);
mPalmRejector->processMotion(
@@ -992,7 +958,7 @@ TEST_F(PalmRejectorFakeFilterTest, OneOfTwoPointersIsCanceled) {
{{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}}));
ASSERT_EQ(1u, argsList.size());
ASSERT_EQ(MOVE, argsList[0].action);
- ASSERT_EQ(1u, argsList[0].pointerCount);
+ ASSERT_EQ(1u, argsList[0].getPointerCount());
ASSERT_EQ(1433, argsList[0].pointerCoords[0].getX());
ASSERT_EQ(751, argsList[0].pointerCoords[0].getY());
}
@@ -1020,7 +986,7 @@ TEST_F(PalmRejectorFakeFilterTest, NewDownEventAfterCancel) {
ASSERT_EQ(1u, argsList.size());
// Cancel all
ASSERT_EQ(CANCEL, argsList[0].action);
- ASSERT_EQ(2u, argsList[0].pointerCount);
+ ASSERT_EQ(2u, argsList[0].getPointerCount());
ASSERT_EQ(FLAG_CANCELED, argsList[0].flags);
// Future move events are ignored
@@ -1035,7 +1001,7 @@ TEST_F(PalmRejectorFakeFilterTest, NewDownEventAfterCancel) {
{{1433.0, 751.0, 43.0}, {1072.0, 766.0, 13.0}, {1000, 700, 10}}));
ASSERT_EQ(1u, argsList.size());
ASSERT_EQ(DOWN, argsList[0].action);
- ASSERT_EQ(1u, argsList[0].pointerCount);
+ ASSERT_EQ(1u, argsList[0].getPointerCount());
ASSERT_EQ(2, argsList[0].pointerProperties[0].id);
}
@@ -1057,7 +1023,7 @@ TEST_F(PalmRejectorFakeFilterTest, TwoPointersCanceledWhenOnePointerGoesUp) {
mPalmRejector->processMotion(
generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
mPalmRejector->processMotion(
- generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+ generateMotionArgs(downTime, /*eventTime=*/1, POINTER_1_DOWN,
{{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
// Suppress both pointers!!
suppressPointerAtPosition(1414, 702);
@@ -1091,13 +1057,13 @@ TEST_F(PalmRejectorFakeFilterTest, CancelTwoPointers) {
mPalmRejector->processMotion(
generateMotionArgs(downTime, downTime, DOWN, {{1342.0, 613.0, 79.0}}));
mPalmRejector->processMotion(
- generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_DOWN,
+ generateMotionArgs(downTime, /*eventTime=*/1, POINTER_1_DOWN,
{{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
// Suppress second pointer (pointer 1)
suppressPointerAtPosition(1060, 700);
argsList = mPalmRejector->processMotion(
- generateMotionArgs(downTime, /*eventTime*/ 1, MOVE,
+ generateMotionArgs(downTime, /*eventTime=*/1, MOVE,
{{1417.0, 685.0, 41.0}, {1060, 700, 10.0}}));
ASSERT_EQ(2u, argsList.size());
ASSERT_EQ(POINTER_1_UP, argsList[0].action);
@@ -1109,20 +1075,20 @@ TEST_F(PalmRejectorFakeFilterTest, CancelTwoPointers) {
// A new pointer goes down and gets suppressed right away. It should just be dropped
suppressPointerAtPosition(1001, 601);
argsList = mPalmRejector->processMotion(
- generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_DOWN,
+ generateMotionArgs(downTime, /*eventTime=*/1, POINTER_2_DOWN,
{{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
ASSERT_EQ(0u, argsList.size());
// Likewise, pointer that's already canceled should be ignored
argsList = mPalmRejector->processMotion(
- generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_2_UP,
+ generateMotionArgs(downTime, /*eventTime=*/1, POINTER_2_UP,
{{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}, {1001, 601, 5}}));
ASSERT_EQ(0u, argsList.size());
// Cancel all pointers when pointer 1 goes up. Pointer 1 was already canceled earlier.
suppressPointerAtPosition(1417, 685);
argsList = mPalmRejector->processMotion(
- generateMotionArgs(downTime, /*eventTime*/ 1, POINTER_1_UP,
+ generateMotionArgs(downTime, /*eventTime=*/1, POINTER_1_UP,
{{1417.0, 685.0, 41.0}, {1062.0, 697.0, 10.0}}));
ASSERT_EQ(1u, argsList.size());
ASSERT_EQ(CANCEL, argsList[0].action);
diff --git a/services/inputflinger/tests/fuzzers/Android.bp b/services/inputflinger/tests/fuzzers/Android.bp
index 455a1e2133..81c335326a 100644
--- a/services/inputflinger/tests/fuzzers/Android.bp
+++ b/services/inputflinger/tests/fuzzers/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_input_framework",
// 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"
@@ -21,28 +22,163 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-
-cc_fuzz {
- name: "inputflinger_latencytracker_fuzzer",
+cc_defaults {
+ name: "inputflinger_fuzz_defaults",
defaults: [
"inputflinger_defaults",
+ "libinputflinger_defaults",
],
+ host_supported: true,
include_dirs: [
"frameworks/native/services/inputflinger",
],
shared_libs: [
- "libbase",
- "libbinder",
- "liblog",
- "libui",
- "libutils",
- "libinput",
- "libinputflinger",
+ "libinputreader",
+ "libinputflinger_base",
],
- srcs: [
- "LatencyTrackerFuzzer.cpp",
+ sanitize: {
+ hwaddress: true,
+ undefined: true,
+ all_undefined: true,
+ diag: {
+ undefined: true,
+ },
+ },
+ target: {
+ host: {
+ sanitize: {
+ address: true,
+ },
+ },
+ },
+ header_libs: [
+ "libbatteryservice_headers",
+ "libinputreader_headers",
],
fuzz_config: {
- cc: ["android-framework-input@google.com"],
+ cc: ["android-framework-input@google.com"],
+ componentid: 155276,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libinputflinger library",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
},
}
+
+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_touchpad_input_fuzzer",
+ defaults: [
+ "inputflinger_fuzz_defaults",
+ ],
+ srcs: [
+ "TouchpadInputFuzzer.cpp",
+ ],
+ static_libs: [
+ "libchrome-gestures",
+ ],
+ header_libs: [
+ "libchrome-gestures_headers",
+ ],
+}
+
+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",
+ ],
+}
+
+cc_fuzz {
+ name: "inputflinger_latencytracker_fuzzer",
+ defaults: [
+ "inputflinger_fuzz_defaults",
+ "libinputdispatcher_defaults",
+ ],
+ shared_libs: [
+ "libinputreporter",
+ ],
+ srcs: [
+ "LatencyTrackerFuzzer.cpp",
+ ],
+}
+
+cc_fuzz {
+ name: "inputflinger_input_dispatcher_fuzzer",
+ defaults: [
+ "inputflinger_fuzz_defaults",
+ "libinputdispatcher_defaults",
+ ],
+ shared_libs: [
+ "libinputreporter",
+ ],
+ srcs: [
+ "InputDispatcherFuzzer.cpp",
+ ],
+}
diff --git a/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
new file mode 100644
index 0000000000..863d0a165e
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/BlockingQueueFuzzer.cpp
@@ -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.
+ */
+
+#include <fuzzer/FuzzedDataProvider.h>
+#include <input/BlockingQueue.h>
+#include <thread>
+
+// 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<size_t>(1, MAX_CAPACITY);
+ size_t filled = 0;
+ BlockingQueue<int32_t> queue(capacity);
+
+ while (fdp.remaining_bytes() > 0) {
+ fdp.PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ size_t numPushes = fdp.ConsumeIntegralInRange<size_t>(0, capacity + 1);
+ for (size_t i = 0; i < numPushes; i++) {
+ queue.push(fdp.ConsumeIntegral<int32_t>());
+ }
+ 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<size_t>(0, filled);
+ for (size_t i = 0; i < numPops; i++) {
+ queue.pop();
+ }
+ filled > numPops ? filled -= numPops : filled = 0;
+ },
+ [&]() -> void {
+ // Pops blocks if it is empty, so only pop up to num elements inserted.
+ size_t numPops = fdp.ConsumeIntegralInRange<size_t>(0, filled);
+ for (size_t i = 0; i < numPops; i++) {
+ // Provide a random timeout up to 1 second
+ queue.popWithTimeout(std::chrono::nanoseconds(
+ fdp.ConsumeIntegralInRange<int64_t>(0, 1E9)));
+ }
+ filled > numPops ? filled -= numPops : filled = 0;
+ },
+ [&]() -> void {
+ queue.clear();
+ filled = 0;
+ },
+ [&]() -> void {
+ int32_t eraseElement = fdp.ConsumeIntegral<int32_t>();
+ queue.erase_if([&](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 0000000000..af20a271b8
--- /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 <CursorInputMapper.h>
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
+
+namespace android {
+
+static void addProperty(FuzzEventHub& eventHub, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
+ // Pick a random property to set for the mapper to have set.
+ fdp->PickValueInArray<std::function<void()>>(
+ {[&]() -> void { eventHub.addProperty("cursor.mode", "pointer"); },
+ [&]() -> void { eventHub.addProperty("cursor.mode", "navigation"); },
+ [&]() -> void {
+ eventHub.addProperty("cursor.mode", fdp->ConsumeRandomLengthString(100).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("cursor.orientationAware",
+ fdp->ConsumeRandomLengthString(100).data());
+ }})();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+ // Create mocked objects to support the fuzzed input mapper.
+ std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+ FuzzInputReaderContext context(eventHub, fdp);
+ InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+ InputReaderConfiguration policyConfig;
+ CursorInputMapper& mapper =
+ getMapperForDevice<ThreadSafeFuzzedDataProvider, CursorInputMapper>(*fdp.get(), device,
+ policyConfig);
+
+ // Loop through mapper operations until randomness is exhausted.
+ while (fdp->remaining_bytes() > 0) {
+ fdp->PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ addProperty(*eventHub.get(), fdp);
+ configureAndResetDevice(*fdp, device);
+ },
+ [&]() -> void {
+ std::string dump;
+ mapper.dump(dump);
+ },
+ [&]() -> void { mapper.getSources(); },
+ [&]() -> void {
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+ InputReaderConfiguration::Change(
+ fdp->ConsumeIntegral<int32_t>()));
+ },
+ [&]() -> void {
+ // Need to reconfigure with 0 or you risk a NPE.
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+ InputReaderConfiguration::Change(0));
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(info);
+ },
+ [&]() -> void {
+ // Need to reconfigure with 0 or you risk a NPE.
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+ InputReaderConfiguration::Change(0));
+ RawEvent rawEvent = getFuzzedRawEvent(*fdp);
+ unused += mapper.process(&rawEvent);
+ },
+ [&]() -> void {
+ std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
+ },
+ [&]() -> void {
+ mapper.getScanCodeState(fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ // Need to reconfigure with 0 or you risk a NPE.
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+ InputReaderConfiguration::Change(0));
+ mapper.getAssociatedDisplayId();
+ },
+ })();
+ }
+
+ return 0;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/FuzzedInputStream.h b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
new file mode 100644
index 0000000000..885820fafb
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/FuzzedInputStream.h
@@ -0,0 +1,204 @@
+/*
+ * 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 <fuzzer/FuzzedDataProvider.h>
+
+namespace android {
+
+namespace {
+static constexpr int32_t MAX_RANDOM_POINTERS = 4;
+static constexpr int32_t MAX_RANDOM_DEVICES = 4;
+} // namespace
+
+int getFuzzedMotionAction(FuzzedDataProvider& fdp) {
+ int actionMasked = fdp.PickValueInArray<int>({
+ AMOTION_EVENT_ACTION_DOWN, AMOTION_EVENT_ACTION_UP, AMOTION_EVENT_ACTION_MOVE,
+ AMOTION_EVENT_ACTION_HOVER_ENTER, AMOTION_EVENT_ACTION_HOVER_MOVE,
+ AMOTION_EVENT_ACTION_HOVER_EXIT, AMOTION_EVENT_ACTION_CANCEL,
+ // do not inject AMOTION_EVENT_ACTION_OUTSIDE,
+ AMOTION_EVENT_ACTION_SCROLL, AMOTION_EVENT_ACTION_POINTER_DOWN,
+ AMOTION_EVENT_ACTION_POINTER_UP,
+ // do not send buttons until verifier supports them
+ // AMOTION_EVENT_ACTION_BUTTON_PRESS,
+ // AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+ });
+ switch (actionMasked) {
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ const int32_t index = fdp.ConsumeIntegralInRange(0, MAX_RANDOM_POINTERS - 1);
+ const int32_t action =
+ actionMasked | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+ return action;
+ }
+ default:
+ return actionMasked;
+ }
+}
+
+/**
+ * For now, focus on the 3 main sources.
+ */
+int getFuzzedSource(FuzzedDataProvider& fdp) {
+ return fdp.PickValueInArray<int>({
+ // AINPUT_SOURCE_UNKNOWN,
+ // AINPUT_SOURCE_KEYBOARD,
+ // AINPUT_SOURCE_DPAD,
+ // AINPUT_SOURCE_GAMEPAD,
+ AINPUT_SOURCE_TOUCHSCREEN, AINPUT_SOURCE_MOUSE, AINPUT_SOURCE_STYLUS,
+ // AINPUT_SOURCE_BLUETOOTH_STYLUS,
+ // AINPUT_SOURCE_TRACKBALL,
+ // AINPUT_SOURCE_MOUSE_RELATIVE,
+ // AINPUT_SOURCE_TOUCHPAD,
+ // AINPUT_SOURCE_TOUCH_NAVIGATION,
+ // AINPUT_SOURCE_JOYSTICK,
+ // AINPUT_SOURCE_HDMI,
+ // AINPUT_SOURCE_SENSOR,
+ // AINPUT_SOURCE_ROTARY_ENCODER,
+ // AINPUT_SOURCE_ANY,
+ });
+}
+
+int getFuzzedButtonState(FuzzedDataProvider& fdp) {
+ return fdp.PickValueInArray<int>({
+ 0,
+ // AMOTION_EVENT_BUTTON_PRIMARY,
+ // AMOTION_EVENT_BUTTON_SECONDARY,
+ // AMOTION_EVENT_BUTTON_TERTIARY,
+ // AMOTION_EVENT_BUTTON_BACK,
+ // AMOTION_EVENT_BUTTON_FORWARD,
+ // AMOTION_EVENT_BUTTON_STYLUS_PRIMARY,
+ // AMOTION_EVENT_BUTTON_STYLUS_SECONDARY,
+ });
+}
+
+int32_t getFuzzedFlags(FuzzedDataProvider& fdp, int32_t action) {
+ constexpr std::array<int32_t, 4> FLAGS{
+ AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED,
+ AMOTION_EVENT_FLAG_WINDOW_IS_PARTIALLY_OBSCURED,
+ AMOTION_EVENT_FLAG_IS_ACCESSIBILITY_EVENT,
+ AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE,
+ };
+
+ int32_t flags = 0;
+ for (size_t i = 0; i < fdp.ConsumeIntegralInRange(size_t(0), FLAGS.size()); i++) {
+ flags |= fdp.PickValueInArray<int32_t>(FLAGS);
+ }
+ if (action == AMOTION_EVENT_ACTION_CANCEL) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ if (MotionEvent::getActionMasked(action) == AMOTION_EVENT_ACTION_POINTER_UP) {
+ if (fdp.ConsumeBool()) {
+ flags |= AMOTION_EVENT_FLAG_CANCELED;
+ }
+ }
+ return flags;
+}
+
+int32_t getFuzzedPointerCount(FuzzedDataProvider& fdp, int32_t action) {
+ switch (MotionEvent::getActionMasked(action)) {
+ case AMOTION_EVENT_ACTION_DOWN:
+ case AMOTION_EVENT_ACTION_UP: {
+ return 1;
+ }
+ case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_MOVE:
+ return fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS);
+ case AMOTION_EVENT_ACTION_HOVER_ENTER:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_EXIT:
+ return 1;
+ case AMOTION_EVENT_ACTION_SCROLL:
+ return 1;
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
+ case AMOTION_EVENT_ACTION_POINTER_UP: {
+ const uint8_t actionIndex = MotionEvent::getActionIndex(action);
+ const int32_t count =
+ std::max(actionIndex + 1,
+ fdp.ConsumeIntegralInRange<int32_t>(1, MAX_RANDOM_POINTERS));
+ // Need to have at least 2 pointers
+ return std::max(2, count);
+ }
+ case AMOTION_EVENT_ACTION_BUTTON_PRESS:
+ case AMOTION_EVENT_ACTION_BUTTON_RELEASE: {
+ return 1;
+ }
+ }
+ return 1;
+}
+
+ToolType getToolType(int32_t source) {
+ switch (source) {
+ case AINPUT_SOURCE_TOUCHSCREEN:
+ return ToolType::FINGER;
+ case AINPUT_SOURCE_MOUSE:
+ return ToolType::MOUSE;
+ case AINPUT_SOURCE_STYLUS:
+ return ToolType::STYLUS;
+ }
+ return ToolType::UNKNOWN;
+}
+
+inline nsecs_t now() {
+ return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+NotifyMotionArgs generateFuzzedMotionArgs(IdGenerator& idGenerator, FuzzedDataProvider& fdp,
+ int32_t maxDisplays) {
+ // Create a basic motion event for testing
+ const int32_t source = getFuzzedSource(fdp);
+ const ToolType toolType = getToolType(source);
+ const int32_t action = getFuzzedMotionAction(fdp);
+ const int32_t pointerCount = getFuzzedPointerCount(fdp, action);
+ std::vector<PointerProperties> pointerProperties;
+ std::vector<PointerCoords> pointerCoords;
+ for (int i = 0; i < pointerCount; i++) {
+ PointerProperties properties{};
+ properties.id = i;
+ properties.toolType = toolType;
+ pointerProperties.push_back(properties);
+
+ PointerCoords coords{};
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, fdp.ConsumeIntegralInRange<int>(-1000, 1000));
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, 1);
+ pointerCoords.push_back(coords);
+ }
+
+ const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, maxDisplays - 1);
+ const int32_t deviceId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DEVICES - 1);
+
+ // Current time +- 5 seconds
+ const nsecs_t currentTime = now();
+ const nsecs_t downTime =
+ fdp.ConsumeIntegralInRange<nsecs_t>(currentTime - 5E9, currentTime + 5E9);
+ const nsecs_t readTime = downTime;
+ const nsecs_t eventTime = fdp.ConsumeIntegralInRange<nsecs_t>(downTime, downTime + 1E9);
+
+ const float cursorX = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
+ const float cursorY = fdp.ConsumeIntegralInRange<int>(-10000, 10000);
+ return NotifyMotionArgs(idGenerator.nextId(), eventTime, readTime, deviceId, source, displayId,
+ POLICY_FLAG_PASS_TO_USER, action,
+ /*actionButton=*/fdp.ConsumeIntegral<int32_t>(),
+ getFuzzedFlags(fdp, action), AMETA_NONE, getFuzzedButtonState(fdp),
+ MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, pointerCount,
+ pointerProperties.data(), pointerCoords.data(),
+ /*xPrecision=*/0,
+ /*yPrecision=*/0, cursorX, cursorY, downTime,
+ /*videoFrames=*/{});
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.cpp
new file mode 100644
index 0000000000..deb811d1ca
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/InputClassifierFuzzer.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 <MapperHelpers.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "FuzzedInputStream.h"
+#include "InputCommonConverter.h"
+#include "InputProcessor.h"
+
+namespace android {
+
+namespace {
+
+constexpr int32_t MAX_RANDOM_DISPLAYS = 4;
+
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t *data, size_t size) {
+ FuzzedDataProvider fdp(data, size);
+
+ std::unique_ptr<FuzzInputListener> mFuzzListener = std::make_unique<FuzzInputListener>();
+ std::unique_ptr<InputProcessorInterface> mClassifier =
+ std::make_unique<InputProcessor>(*mFuzzListener);
+ IdGenerator idGenerator(IdGenerator::Source::OTHER);
+
+ while (fdp.remaining_bytes() > 0) {
+ fdp.PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ // SendToNextStage_NotifyConfigurationChangedArgs
+ mClassifier->notifyConfigurationChanged(
+ {/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+ /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>()});
+ },
+ [&]() -> void {
+ // SendToNextStage_NotifyKeyArgs
+ const nsecs_t eventTime =
+ fdp.ConsumeIntegralInRange<nsecs_t>(0,
+ systemTime(SYSTEM_TIME_MONOTONIC));
+ const nsecs_t readTime = fdp.ConsumeIntegralInRange<
+ nsecs_t>(eventTime, std::numeric_limits<nsecs_t>::max());
+ mClassifier->notifyKey({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+ eventTime, readTime,
+ /*deviceId=*/fdp.ConsumeIntegral<int32_t>(),
+ AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_DEFAULT,
+ /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
+ AKEY_EVENT_ACTION_DOWN,
+ /*flags=*/fdp.ConsumeIntegral<int32_t>(), AKEYCODE_HOME,
+ /*scanCode=*/fdp.ConsumeIntegral<int32_t>(), AMETA_NONE,
+ /*downTime=*/fdp.ConsumeIntegral<nsecs_t>()});
+ },
+ [&]() -> void {
+ // SendToNextStage_NotifyMotionArgs
+ mClassifier->notifyMotion(
+ generateFuzzedMotionArgs(idGenerator, fdp, MAX_RANDOM_DISPLAYS));
+ },
+ [&]() -> void {
+ // SendToNextStage_NotifySwitchArgs
+ mClassifier->notifySwitch({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+ /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+ /*policyFlags=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*switchValues=*/fdp.ConsumeIntegral<uint32_t>(),
+ /*switchMask=*/fdp.ConsumeIntegral<uint32_t>()});
+ },
+ [&]() -> void {
+ // SendToNextStage_NotifyDeviceResetArgs
+ mClassifier->notifyDeviceReset({/*sequenceNum=*/fdp.ConsumeIntegral<int32_t>(),
+ /*eventTime=*/fdp.ConsumeIntegral<nsecs_t>(),
+ /*deviceId=*/fdp.ConsumeIntegral<int32_t>()});
+ },
+ [&]() -> void {
+ // InputClassifierConverterTest
+ const NotifyMotionArgs motionArgs =
+ generateFuzzedMotionArgs(idGenerator, fdp, MAX_RANDOM_DISPLAYS);
+ aidl::android::hardware::input::common::MotionEvent motionEvent =
+ notifyMotionArgsToHalMotionEvent(motionArgs);
+ },
+ })();
+ }
+ return 0;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
new file mode 100644
index 0000000000..dc5a2130e7
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/InputDispatcherFuzzer.cpp
@@ -0,0 +1,197 @@
+/*
+ * 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 <android-base/stringprintf.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include "../FakeApplicationHandle.h"
+#include "../FakeInputDispatcherPolicy.h"
+#include "../FakeWindowHandle.h"
+#include "FuzzedInputStream.h"
+#include "dispatcher/InputDispatcher.h"
+#include "input/InputVerifier.h"
+
+namespace android {
+
+using android::base::Result;
+using android::gui::WindowInfo;
+
+namespace inputdispatcher {
+
+namespace {
+
+static constexpr int32_t MAX_RANDOM_DISPLAYS = 4;
+static constexpr int32_t MAX_RANDOM_WINDOWS = 4;
+
+/**
+ * Provide a valid motion stream, to make the fuzzer more effective.
+ */
+class NotifyStreamProvider {
+public:
+ NotifyStreamProvider(FuzzedDataProvider& fdp)
+ : mFdp(fdp), mIdGenerator(IdGenerator::Source::OTHER) {}
+
+ std::optional<NotifyMotionArgs> nextMotion() {
+ NotifyMotionArgs args = generateFuzzedMotionArgs(mIdGenerator, mFdp, MAX_RANDOM_DISPLAYS);
+ auto [it, _] = mVerifiers.emplace(args.displayId, "Fuzz Verifier");
+ InputVerifier& verifier = it->second;
+ const Result<void> result =
+ verifier.processMovement(args.deviceId, args.source, args.action,
+ args.getPointerCount(), args.pointerProperties.data(),
+ args.pointerCoords.data(), args.flags);
+ if (result.ok()) {
+ return args;
+ }
+ return {};
+ }
+
+private:
+ FuzzedDataProvider& mFdp;
+
+ IdGenerator mIdGenerator;
+
+ std::map<int32_t /*displayId*/, InputVerifier> mVerifiers;
+};
+
+void scrambleWindow(FuzzedDataProvider& fdp, FakeWindowHandle& window) {
+ const int32_t left = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+ const int32_t top = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+ const int32_t width = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+ const int32_t height = fdp.ConsumeIntegralInRange<int32_t>(0, 100);
+
+ window.setFrame(Rect(left, top, left + width, top + height));
+ window.setSlippery(fdp.ConsumeBool());
+ window.setDupTouchToWallpaper(fdp.ConsumeBool());
+ window.setIsWallpaper(fdp.ConsumeBool());
+ window.setVisible(fdp.ConsumeBool());
+ window.setPreventSplitting(fdp.ConsumeBool());
+ const bool isTrustedOverlay = fdp.ConsumeBool();
+ window.setTrustedOverlay(isTrustedOverlay);
+ if (isTrustedOverlay) {
+ window.setSpy(fdp.ConsumeBool());
+ } else {
+ window.setSpy(false);
+ }
+}
+
+} // namespace
+
+sp<FakeWindowHandle> generateFuzzedWindow(FuzzedDataProvider& fdp, InputDispatcher& dispatcher,
+ int32_t displayId) {
+ static size_t windowNumber = 0;
+ std::shared_ptr<FakeApplicationHandle> application = std::make_shared<FakeApplicationHandle>();
+ std::string windowName = android::base::StringPrintf("Win") + std::to_string(windowNumber++);
+ sp<FakeWindowHandle> window =
+ sp<FakeWindowHandle>::make(application, dispatcher, windowName, displayId);
+
+ scrambleWindow(fdp, *window);
+ return window;
+}
+
+void randomizeWindows(
+ std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>>& windowsPerDisplay,
+ FuzzedDataProvider& fdp, InputDispatcher& dispatcher) {
+ const int32_t displayId = fdp.ConsumeIntegralInRange<int32_t>(0, MAX_RANDOM_DISPLAYS - 1);
+ std::vector<sp<FakeWindowHandle>>& windows = windowsPerDisplay[displayId];
+
+ fdp.PickValueInArray<std::function<void()>>({
+ // Add a new window
+ [&]() -> void {
+ if (windows.size() < MAX_RANDOM_WINDOWS) {
+ windows.push_back(generateFuzzedWindow(fdp, dispatcher, displayId));
+ }
+ },
+ // Remove a window
+ [&]() -> void {
+ if (windows.empty()) {
+ return;
+ }
+ const int32_t erasedPosition =
+ fdp.ConsumeIntegralInRange<int32_t>(0, windows.size() - 1);
+
+ windows.erase(windows.begin() + erasedPosition);
+ if (windows.empty()) {
+ windowsPerDisplay.erase(displayId);
+ }
+ },
+ // Change flags or move some of the existing windows
+ [&]() -> void {
+ for (auto& window : windows) {
+ if (fdp.ConsumeBool()) {
+ scrambleWindow(fdp, *window);
+ }
+ }
+ },
+ })();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ FuzzedDataProvider fdp(data, size);
+ NotifyStreamProvider streamProvider(fdp);
+
+ FakeInputDispatcherPolicy fakePolicy;
+ InputDispatcher dispatcher(fakePolicy);
+ dispatcher.setInputDispatchMode(/*enabled=*/true, /*frozen=*/false);
+ // Start InputDispatcher thread
+ dispatcher.start();
+
+ std::unordered_map<int32_t, std::vector<sp<FakeWindowHandle>>> windowsPerDisplay;
+
+ // Randomly invoke InputDispatcher api's until randomness is exhausted.
+ while (fdp.remaining_bytes() > 0) {
+ fdp.PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ std::optional<NotifyMotionArgs> motion = streamProvider.nextMotion();
+ if (motion) {
+ dispatcher.notifyMotion(*motion);
+ }
+ },
+ [&]() -> void {
+ // Scramble the windows we currently have
+ randomizeWindows(/*byref*/ windowsPerDisplay, fdp, dispatcher);
+
+ std::vector<WindowInfo> windowInfos;
+ for (const auto& [displayId, windows] : windowsPerDisplay) {
+ for (const sp<FakeWindowHandle>& window : windows) {
+ windowInfos.emplace_back(*window->getInfo());
+ }
+ }
+
+ dispatcher.onWindowInfosChanged(
+ {windowInfos, {}, /*vsyncId=*/0, /*timestamp=*/0});
+ },
+ // Consume on all the windows
+ [&]() -> void {
+ for (const auto& [_, windows] : windowsPerDisplay) {
+ for (const sp<FakeWindowHandle>& window : windows) {
+ // To speed up the fuzzing, don't wait for consumption. If there's an
+ // event pending, this can be consumed on the next call instead.
+ // We also don't care about whether consumption succeeds here, or what
+ // kind of event is returned.
+ window->consume(0ms);
+ }
+ }
+ },
+ })();
+ }
+
+ dispatcher.stop();
+
+ return 0;
+}
+
+} // namespace inputdispatcher
+
+} // namespace android \ No newline at end of file
diff --git a/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp b/services/inputflinger/tests/fuzzers/InputReaderFuzzer.cpp
new file mode 100644
index 0000000000..9223287114
--- /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 <InputReader.h>
+#include <MapperHelpers.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <input/InputDevice.h>
+#include <chrono>
+#include <thread>
+
+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<EventHubInterface> fuzzEventHub,
+ const sp<InputReaderPolicyInterface>& fuzzPolicy,
+ InputListenerInterface& fuzzListener) {
+ reader = std::make_unique<InputReader>(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<InputDeviceInfo> 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<int32_t>& 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<int32_t> getVibratorIds(int32_t deviceId) {
+ return reader->getVibratorIds(deviceId);
+ }
+
+ std::optional<int32_t> getBatteryCapacity(int32_t deviceId) {
+ return reader->getBatteryCapacity(deviceId);
+ }
+
+ std::optional<int32_t> getBatteryStatus(int32_t deviceId) {
+ return reader->getBatteryStatus(deviceId);
+ }
+
+ std::optional<std::string> getBatteryDevicePath(int32_t deviceId) {
+ return reader->getBatteryDevicePath(deviceId);
+ }
+
+ std::vector<InputDeviceLightInfo> getLights(int32_t deviceId) {
+ return reader->getLights(deviceId);
+ }
+
+ std::vector<InputDeviceSensorInfo> 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<int32_t> getLightColor(int32_t deviceId, int32_t lightId) {
+ return reader->getLightColor(deviceId, lightId);
+ }
+
+ std::optional<int32_t> 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<std::string> getBluetoothAddress(int32_t deviceId) const {
+ return reader->getBluetoothAddress(deviceId);
+ }
+
+ void sysfsNodeChanged(const std::string& sysfsNodePath) {
+ reader->sysfsNodeChanged(sysfsNodePath);
+ }
+
+private:
+ std::unique_ptr<InputReaderInterface> reader;
+};
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+ FuzzInputListener fuzzListener;
+ sp<FuzzInputReaderPolicy> fuzzPolicy = sp<FuzzInputReaderPolicy>::make(fdp);
+ std::shared_ptr<FuzzEventHub> fuzzEventHub = std::make_shared<FuzzEventHub>(fdp);
+ std::unique_ptr<FuzzInputReader> reader =
+ std::make_unique<FuzzInputReader>(fuzzEventHub, fuzzPolicy, fuzzListener);
+ size_t patternCount = fdp->ConsumeIntegralInRange<size_t>(1, 260);
+ VibrationSequence pattern(patternCount);
+ for (size_t i = 0; i < patternCount; ++i) {
+ VibrationElement element(i);
+ element.addChannel(/*vibratorId=*/fdp->ConsumeIntegral<int32_t>(),
+ /*amplitude=*/fdp->ConsumeIntegral<uint8_t>());
+ pattern.addElement(element);
+ }
+ reader->vibrate(fdp->ConsumeIntegral<int32_t>(), pattern,
+ /*repeat=*/fdp->ConsumeIntegral<ssize_t>(),
+ /*token=*/fdp->ConsumeIntegral<int32_t>());
+ reader->start();
+
+ // Loop through mapper operations until randomness is exhausted.
+ while (fdp->remaining_bytes() > 0) {
+ fdp->PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ std::string dump;
+ reader->dump(dump);
+ },
+ [&]() -> void { reader->monitor(); },
+ [&]() -> void { reader->getInputDevices(); },
+ [&]() -> void { reader->isInputDeviceEnabled(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void {
+ reader->getScanCodeState(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->getKeyCodeState(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->getSwitchState(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void { reader->toggleCapsLockState(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void {
+ size_t count = fdp->ConsumeIntegralInRange<size_t>(1, 1024);
+ std::vector<uint8_t> outFlags(count);
+ std::vector<int32_t> keyCodes;
+ for (size_t i = 0; i < count; ++i) {
+ keyCodes.push_back(fdp->ConsumeIntegral<int32_t>());
+ }
+ reader->hasKeys(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<uint32_t>(), keyCodes, outFlags.data());
+ },
+ [&]() -> void {
+ reader->requestRefreshConfiguration(
+ InputReaderConfiguration::Change(fdp->ConsumeIntegral<uint32_t>()));
+ },
+ [&]() -> void {
+ reader->cancelVibrate(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->canDispatchToDisplay(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->getKeyCodeForKeyLocation(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void { reader->getBatteryCapacity(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void { reader->getBatteryStatus(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void { reader->getBatteryDevicePath(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void { reader->getLights(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void { reader->getSensors(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void {
+ reader->getLightPlayerId(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->getLightColor(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->setLightPlayerId(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->setLightColor(fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ reader->flushSensor(fdp->ConsumeIntegral<int32_t>(),
+ fdp->PickValueInArray<InputDeviceSensorType>(
+ kInputDeviceSensorType));
+ },
+ [&]() -> void {
+ reader->disableSensor(fdp->ConsumeIntegral<int32_t>(),
+ fdp->PickValueInArray<InputDeviceSensorType>(
+ kInputDeviceSensorType));
+ },
+ [&]() -> void {
+ reader->enableSensor(fdp->ConsumeIntegral<int32_t>(),
+ fdp->PickValueInArray<InputDeviceSensorType>(
+ kInputDeviceSensorType),
+ std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()),
+ std::chrono::microseconds(fdp->ConsumeIntegral<size_t>()));
+ },
+ [&]() -> void { reader->getBluetoothAddress(fdp->ConsumeIntegral<int32_t>()); },
+ })();
+ }
+
+ 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 0000000000..922cbdfb87
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/KeyboardInputFuzzer.cpp
@@ -0,0 +1,111 @@
+/*
+ * 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 <InputDevice.h>
+#include <InputReaderBase.h>
+#include <KeyboardInputMapper.h>
+#include <MapperHelpers.h>
+
+namespace android {
+
+const int32_t kMaxKeycodes = 100;
+
+static void addProperty(FuzzEventHub& eventHub, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
+ // Pick a random property to set for the mapper to have set.
+ fdp->PickValueInArray<std::function<void()>>(
+ {[&]() -> void { eventHub.addProperty("keyboard.orientationAware", "1"); },
+ [&]() -> void {
+ eventHub.addProperty("keyboard.orientationAware",
+ fdp->ConsumeRandomLengthString(100).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("keyboard.doNotWakeByDefault",
+ fdp->ConsumeRandomLengthString(100).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("keyboard.handlesKeyRepeat",
+ fdp->ConsumeRandomLengthString(100).data());
+ }})();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+ // Create mocked objects to support the fuzzed input mapper.
+ std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+ FuzzInputReaderContext context(eventHub, fdp);
+ InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+ KeyboardInputMapper& mapper = getMapperForDevice<
+ ThreadSafeFuzzedDataProvider,
+ KeyboardInputMapper>(*fdp.get(), device, InputReaderConfiguration{},
+ /*source=*/fdp->ConsumeIntegral<uint32_t>(),
+ /*keyboardType=*/fdp->ConsumeIntegral<int32_t>());
+
+ // Loop through mapper operations until randomness is exhausted.
+ while (fdp->remaining_bytes() > 0) {
+ fdp->PickValueInArray<std::function<void()>>({
+ [&]() -> void { addProperty(*eventHub.get(), fdp); },
+ [&]() -> void {
+ std::string dump;
+ mapper.dump(dump);
+ },
+ [&]() -> void {
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(info);
+ },
+ [&]() -> void { mapper.getSources(); },
+ [&]() -> void {
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), /*readerConfig=*/{},
+ InputReaderConfiguration::Change(
+ fdp->ConsumeIntegral<uint32_t>()));
+ },
+ [&]() -> void {
+ std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
+ },
+ [&]() -> void {
+ RawEvent rawEvent = getFuzzedRawEvent(*fdp);
+ std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+ },
+ [&]() -> void {
+ mapper.getKeyCodeState(fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ mapper.getScanCodeState(fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ std::vector<int32_t> keyCodes;
+ int32_t numBytes = fdp->ConsumeIntegralInRange<int32_t>(0, kMaxKeycodes);
+ for (int32_t i = 0; i < numBytes; ++i) {
+ keyCodes.push_back(fdp->ConsumeIntegral<int32_t>());
+ }
+ mapper.markSupportedKeyCodes(fdp->ConsumeIntegral<uint32_t>(), keyCodes,
+ nullptr);
+ },
+ [&]() -> void { mapper.getMetaState(); },
+ [&]() -> void { mapper.updateMetaState(fdp->ConsumeIntegral<int32_t>()); },
+ [&]() -> void { mapper.getAssociatedDisplayId(); },
+ })();
+ }
+
+ return 0;
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
index 4f066ad513..6daeaafbb3 100644
--- a/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
+++ b/services/inputflinger/tests/fuzzers/LatencyTrackerFuzzer.cpp
@@ -15,6 +15,9 @@
*/
#include <fuzzer/FuzzedDataProvider.h>
+#include <linux/input.h>
+
+#include "../../InputDeviceMetricsSource.h"
#include "dispatcher/LatencyTracker.h"
namespace android {
@@ -42,7 +45,7 @@ static sp<IBinder> getConnectionToken(FuzzedDataProvider& fdp,
if (useExistingToken) {
return tokens[fdp.ConsumeIntegralInRange<size_t>(0ul, tokens.size() - 1)];
}
- return new BBinder();
+ return sp<BBinder>::make();
}
extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
@@ -54,7 +57,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<sp<IBinder> /*token*/, 10> predefinedTokens;
for (size_t i = 0; i < predefinedTokens.size(); i++) {
- predefinedTokens[i] = new BBinder();
+ predefinedTokens[i] = sp<BBinder>::make();
}
// Randomly invoke LatencyTracker api's until randomness is exhausted.
@@ -65,7 +68,11 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
int32_t isDown = fdp.ConsumeBool();
nsecs_t eventTime = fdp.ConsumeIntegral<nsecs_t>();
nsecs_t readTime = fdp.ConsumeIntegral<nsecs_t>();
- tracker.trackListener(inputEventId, isDown, eventTime, readTime);
+ const DeviceId deviceId = fdp.ConsumeIntegral<int32_t>();
+ std::set<InputDeviceUsageSource> sources = {
+ fdp.ConsumeEnum<InputDeviceUsageSource>()};
+ tracker.trackListener(inputEventId, isDown, eventTime, readTime, deviceId,
+ sources);
},
[&]() -> void {
int32_t inputEventId = fdp.ConsumeIntegral<int32_t>();
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
new file mode 100644
index 0000000000..81c570d77b
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -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.
+ */
+#pragma once
+
+#include <map>
+#include <memory>
+
+#include <EventHub.h>
+#include <InputDevice.h>
+#include <InputMapper.h>
+#include <InputReader.h>
+#include <ThreadSafeFuzzedDataProvider.h>
+
+constexpr size_t kValidTypes[] = {EV_SW,
+ EV_SYN,
+ 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,
+ 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<class Fdp>
+ToolType getFuzzedToolType(Fdp& fdp) {
+ const int32_t toolType = fdp.template ConsumeIntegralInRange<int32_t>(
+ static_cast<int32_t>(ToolType::ftl_first),
+ static_cast<int32_t>(ToolType::ftl_last));
+ return static_cast<ToolType>(toolType);
+}
+
+template <class Fdp>
+RawEvent getFuzzedRawEvent(Fdp& fdp) {
+ const int32_t type = fdp.ConsumeBool() ? fdp.PickValueInArray(kValidTypes)
+ : fdp.template ConsumeIntegral<int32_t>();
+ const int32_t code = fdp.ConsumeBool() ? fdp.PickValueInArray(kValidCodes)
+ : fdp.template ConsumeIntegral<int32_t>();
+ return RawEvent{
+ .when = fdp.template ConsumeIntegral<nsecs_t>(),
+ .readTime = fdp.template ConsumeIntegral<nsecs_t>(),
+ .deviceId = fdp.template ConsumeIntegral<int32_t>(),
+ .type = type,
+ .code = code,
+ .value = fdp.template ConsumeIntegral<int32_t>(),
+ };
+}
+
+class FuzzEventHub : public EventHubInterface {
+ InputDeviceIdentifier mIdentifier;
+ std::vector<TouchVideoFrame> mVideoFrames;
+ PropertyMap mFuzzConfig;
+ std::map<int32_t /* deviceId */, std::map<int /* axis */, RawAbsoluteAxisInfo>> mAxes;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
+
+public:
+ FuzzEventHub(std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) : mFdp(std::move(fdp)) {}
+ ~FuzzEventHub() {}
+ void addProperty(std::string key, std::string value) { mFuzzConfig.addProperty(key, value); }
+
+ ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
+ return ftl::Flags<InputDeviceClass>(mFdp->ConsumeIntegral<uint32_t>());
+ }
+ InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override {
+ return mIdentifier;
+ }
+ int32_t getDeviceControllerNumber(int32_t deviceId) const override {
+ return mFdp->ConsumeIntegral<int32_t>();
+ }
+ std::optional<PropertyMap> getConfiguration(int32_t deviceId) const override {
+ return mFuzzConfig;
+ }
+ void setAbsoluteAxisInfo(int32_t deviceId, int axis, const RawAbsoluteAxisInfo& axisInfo) {
+ mAxes[deviceId][axis] = axisInfo;
+ }
+ status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
+ RawAbsoluteAxisInfo* outAxisInfo) const override {
+ if (auto deviceAxesIt = mAxes.find(deviceId); deviceAxesIt != mAxes.end()) {
+ const std::map<int, RawAbsoluteAxisInfo>& deviceAxes = deviceAxesIt->second;
+ if (auto axisInfoIt = deviceAxes.find(axis); axisInfoIt != deviceAxes.end()) {
+ *outAxisInfo = axisInfoIt->second;
+ return OK;
+ }
+ }
+ return mFdp->ConsumeIntegral<status_t>();
+ }
+ 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>();
+ }
+ status_t mapAxis(int32_t deviceId, int32_t scanCode, AxisInfo* outAxisInfo) const override {
+ return mFdp->ConsumeIntegral<status_t>();
+ }
+ void setExcludedDevices(const std::vector<std::string>& devices) override {}
+ std::vector<RawEvent> getEvents(int timeoutMillis) override {
+ std::vector<RawEvent> events;
+ const size_t count = mFdp->ConsumeIntegralInRange<size_t>(0, kMaxSize);
+ for (size_t i = 0; i < count; ++i) {
+ events.push_back(getFuzzedRawEvent(*mFdp));
+ }
+ return events;
+ }
+ std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override { return mVideoFrames; }
+
+ base::Result<std::pair<InputDeviceSensorType, int32_t>> 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<int32_t> getRawBatteryIds(int32_t deviceId) const override { return {}; }
+ std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
+ int32_t BatteryId) const override {
+ return std::nullopt;
+ };
+
+ std::vector<int32_t> getRawLightIds(int32_t deviceId) const override { return {}; };
+ std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) const override {
+ return std::nullopt;
+ };
+ std::optional<int32_t> 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<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+ int32_t deviceId, int32_t lightId) const override {
+ return std::nullopt;
+ };
+ void setLightIntensities(int32_t deviceId, int32_t lightId,
+ std::unordered_map<LightColor, int32_t> intensities) override{};
+
+ std::optional<RawLayoutInfo> 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>();
+ }
+ int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override {
+ return mFdp->ConsumeIntegral<int32_t>();
+ }
+ int32_t getSwitchState(int32_t deviceId, int32_t sw) const override {
+ return mFdp->ConsumeIntegral<int32_t>();
+ }
+ 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<int32_t>();
+ }
+ status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
+ int32_t* outValue) const override {
+ return mFdp->ConsumeIntegral<status_t>();
+ }
+ bool markSupportedKeyCodes(int32_t deviceId, const std::vector<int32_t>& 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<VirtualKeyDefinition>& outVirtualKeys) const override {}
+ const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t deviceId) const override {
+ return nullptr;
+ }
+ bool setKeyboardLayoutOverlay(int32_t deviceId, std::shared_ptr<KeyCharacterMap> map) override {
+ return mFdp->ConsumeBool();
+ }
+ void vibrate(int32_t deviceId, const VibrationElement& effect) override {}
+ void cancelVibrate(int32_t deviceId) override {}
+
+ std::vector<int32_t> getVibratorIds(int32_t deviceId) const override { return {}; };
+
+ /* Query battery level. */
+ std::optional<int32_t> getBatteryCapacity(int32_t deviceId, int32_t batteryId) const override {
+ return std::nullopt;
+ };
+
+ /* Query battery status. */
+ std::optional<int32_t> 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>(); }
+ status_t disableDevice(int32_t deviceId) override { return mFdp->ConsumeIntegral<status_t>(); }
+ void sysfsNodeChanged(const std::string& sysfsNodePath) override {}
+};
+
+class FuzzPointerController : public PointerControllerInterface {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
+
+public:
+ FuzzPointerController(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {}
+ ~FuzzPointerController() {}
+ std::optional<FloatRect> getBounds() const override {
+ if (mFdp->ConsumeBool()) {
+ return {};
+ } else {
+ return FloatRect{mFdp->ConsumeFloatingPoint<float>(),
+ mFdp->ConsumeFloatingPoint<float>(),
+ mFdp->ConsumeFloatingPoint<float>(),
+ mFdp->ConsumeFloatingPoint<float>()};
+ }
+ }
+ void move(float deltaX, float deltaY) override {}
+ void setPosition(float x, float y) override {}
+ FloatPoint getPosition() const override {
+ return {mFdp->ConsumeFloatingPoint<float>(), mFdp->ConsumeFloatingPoint<float>()};
+ }
+ 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<int32_t>(); }
+ void setDisplayViewport(const DisplayViewport& displayViewport) override {}
+ void updatePointerIcon(PointerIconStyle iconId) override {}
+ void setCustomPointerIcon(const SpriteIcon& icon) override {}
+ std::string dump() override { return ""; }
+};
+
+class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
+ TouchAffineTransformation mTransform;
+ std::shared_ptr<FuzzPointerController> mPointerController;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
+
+protected:
+ ~FuzzInputReaderPolicy() {}
+
+public:
+ FuzzInputReaderPolicy(std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp) : mFdp(mFdp) {
+ mPointerController = std::make_shared<FuzzPointerController>(mFdp);
+ }
+ void getReaderConfiguration(InputReaderConfiguration* outConfig) override {}
+ std::shared_ptr<PointerControllerInterface> obtainPointerController(int32_t deviceId) override {
+ return mPointerController;
+ }
+ void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {}
+ std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
+ const InputDeviceIdentifier& identifier,
+ const std::optional<KeyboardLayoutInfo> layoutInfo) 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) {}
+ bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); }
+ std::optional<DisplayViewport> getPointerViewportForAssociatedDisplay(
+ int32_t associatedDisplayId) override {
+ return {};
+ }
+};
+
+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<EventHubInterface> mEventHub;
+ sp<InputReaderPolicyInterface> mPolicy;
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> mFdp;
+
+public:
+ FuzzInputReaderContext(std::shared_ptr<EventHubInterface> eventHub,
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp)
+ : mEventHub(eventHub), mPolicy(sp<FuzzInputReaderPolicy>::make(fdp)), mFdp(fdp) {}
+ ~FuzzInputReaderContext() {}
+ void updateGlobalMetaState() override {}
+ int32_t getGlobalMetaState() { return mFdp->ConsumeIntegral<int32_t>(); }
+ 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<PointerControllerInterface> getPointerController(int32_t deviceId) override {
+ return mPolicy->obtainPointerController(0);
+ }
+ void requestTimeoutAtTime(nsecs_t when) override {}
+ int32_t bumpGeneration() override { return mFdp->ConsumeIntegral<int32_t>(); }
+ void getExternalStylusDevices(std::vector<InputDeviceInfo>& outDevices) override {}
+ std::list<NotifyArgs> 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<int32_t>(); }
+
+ void updateLedMetaState(int32_t metaState) override{};
+ int32_t getLedMetaState() override { return mFdp->ConsumeIntegral<int32_t>(); };
+ void notifyStylusGestureStarted(int32_t, nsecs_t) {}
+
+ void setPreventingTouchpadTaps(bool prevent) {}
+ bool isPreventingTouchpadTaps() { return mFdp->ConsumeBool(); };
+
+ void setLastKeyDownTimestamp(nsecs_t when) { mLastKeyDownTimestamp = when; };
+ nsecs_t getLastKeyDownTimestamp() { return mLastKeyDownTimestamp; };
+
+private:
+ nsecs_t mLastKeyDownTimestamp;
+};
+
+template <class Fdp>
+InputDevice getFuzzedInputDevice(Fdp& fdp, FuzzInputReaderContext* context) {
+ InputDeviceIdentifier identifier;
+ identifier.name = fdp.ConsumeRandomLengthString(16);
+ identifier.location = fdp.ConsumeRandomLengthString(12);
+ int32_t deviceID = fdp.ConsumeIntegralInRange(0, 5);
+ int32_t deviceGeneration = fdp.ConsumeIntegralInRange(0, 5);
+ return InputDevice(context, deviceID, deviceGeneration, identifier);
+}
+
+template <class Fdp>
+void configureAndResetDevice(Fdp& fdp, InputDevice& device) {
+ nsecs_t arbitraryTime = fdp.template ConsumeIntegral<nsecs_t>();
+ std::list<NotifyArgs> out;
+ out += device.configure(arbitraryTime, /*readerConfig=*/{}, /*changes=*/{});
+ out += device.reset(arbitraryTime);
+}
+
+template <class Fdp, class T, typename... Args>
+T& getMapperForDevice(Fdp& fdp, InputDevice& device, Args... args) {
+ int32_t eventhubId = fdp.template ConsumeIntegral<int32_t>();
+ // ensure a device entry exists for this eventHubId
+ device.addEmptyEventHubDevice(eventhubId);
+ configureAndResetDevice(fdp, device);
+
+ return device.template constructAndAddMapper<T>(eventhubId, args...);
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
new file mode 100644
index 0000000000..d3f66900da
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/MultiTouchInputFuzzer.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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 <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
+#include <MultiTouchInputMapper.h>
+
+namespace android {
+
+const int32_t kMaxKeycodes = 100;
+
+static void addProperty(FuzzEventHub& eventHub, std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp) {
+ // Pick a random property to set for the mapper to have set.
+ fdp->PickValueInArray<std::function<void()>>(
+ {[&]() -> void { eventHub.addProperty("touch.deviceType", "touchScreen"); },
+ [&]() -> void {
+ eventHub.addProperty("touch.deviceType", fdp->ConsumeRandomLengthString(8).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.size.scale", fdp->ConsumeRandomLengthString(8).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.size.bias", fdp->ConsumeRandomLengthString(8).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.size.isSummed",
+ fdp->ConsumeRandomLengthString(8).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.size.calibration",
+ fdp->ConsumeRandomLengthString(8).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.pressure.scale",
+ fdp->ConsumeRandomLengthString(8).data());
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.size.calibration",
+ fdp->ConsumeBool() ? "diameter" : "area");
+ },
+ [&]() -> void {
+ eventHub.addProperty("touch.pressure.calibration",
+ fdp->ConsumeRandomLengthString(8).data());
+ }})();
+}
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+ // Create mocked objects to support the fuzzed input mapper.
+ std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+ FuzzInputReaderContext context(eventHub, fdp);
+ InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+ InputReaderConfiguration policyConfig;
+ MultiTouchInputMapper& mapper =
+ getMapperForDevice<ThreadSafeFuzzedDataProvider, MultiTouchInputMapper>(*fdp.get(),
+ device,
+ policyConfig);
+
+ // Loop through mapper operations until randomness is exhausted.
+ while (fdp->remaining_bytes() > 0) {
+ fdp->PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ addProperty(*eventHub.get(), fdp);
+ configureAndResetDevice(*fdp, device);
+ },
+ [&]() -> void {
+ std::string dump;
+ mapper.dump(dump);
+ },
+ [&]() -> void {
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(info);
+ },
+ [&]() -> void { mapper.getSources(); },
+ [&]() -> void {
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+ InputReaderConfiguration::Change(
+ fdp->ConsumeIntegral<uint32_t>()));
+ },
+ [&]() -> void {
+ std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
+ },
+ [&]() -> void {
+ RawEvent rawEvent = getFuzzedRawEvent(*fdp);
+ std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+ },
+ [&]() -> void {
+ mapper.getKeyCodeState(fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ mapper.getScanCodeState(fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ [&]() -> void {
+ std::vector<int32_t> keyCodes;
+ int32_t numBytes = fdp->ConsumeIntegralInRange<int32_t>(0, kMaxKeycodes);
+ for (int32_t i = 0; i < numBytes; ++i) {
+ keyCodes.push_back(fdp->ConsumeIntegral<int32_t>());
+ }
+ mapper.markSupportedKeyCodes(fdp->ConsumeIntegral<uint32_t>(), keyCodes,
+ nullptr);
+ },
+ [&]() -> void {
+ std::list<NotifyArgs> unused =
+ mapper.cancelTouch(fdp->ConsumeIntegral<nsecs_t>(),
+ fdp->ConsumeIntegral<nsecs_t>());
+ },
+ [&]() -> void {
+ std::list<NotifyArgs> unused =
+ mapper.timeoutExpired(fdp->ConsumeIntegral<nsecs_t>());
+ },
+ [&]() -> void {
+ StylusState state{fdp->ConsumeIntegral<nsecs_t>(),
+ fdp->ConsumeFloatingPoint<float>(),
+ fdp->ConsumeIntegral<uint32_t>(), getFuzzedToolType(*fdp)};
+ std::list<NotifyArgs> 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 0000000000..ac2030afd3
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/SwitchInputFuzzer.cpp
@@ -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.
+ */
+
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
+#include <SwitchInputMapper.h>
+
+namespace android {
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+ // Create mocked objects to support the fuzzed input mapper.
+ std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+ FuzzInputReaderContext context(eventHub, fdp);
+ InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+ SwitchInputMapper& mapper =
+ getMapperForDevice<ThreadSafeFuzzedDataProvider,
+ SwitchInputMapper>(*fdp.get(), device, InputReaderConfiguration{});
+
+ // Loop through mapper operations until randomness is exhausted.
+ while (fdp->remaining_bytes() > 0) {
+ fdp->PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ std::string dump;
+ mapper.dump(dump);
+ },
+ [&]() -> void { mapper.getSources(); },
+ [&]() -> void {
+ RawEvent rawEvent = getFuzzedRawEvent(*fdp);
+ std::list<NotifyArgs> unused = mapper.process(&rawEvent);
+ },
+ [&]() -> void {
+ mapper.getSwitchState(fdp->ConsumeIntegral<uint32_t>(),
+ fdp->ConsumeIntegral<int32_t>());
+ },
+ })();
+ }
+
+ 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 0000000000..2f76f181cb
--- /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 <fuzzer/FuzzedDataProvider.h>
+
+/**
+ * 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 <typename T>
+ std::vector<T> ConsumeBytes(size_t num_bytes) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBytes<T>(num_bytes);
+ }
+
+ template <typename T>
+ std::vector<T> ConsumeBytesWithTerminator(size_t num_bytes, T terminator) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBytesWithTerminator<T>(num_bytes, terminator);
+ }
+
+ template <typename T>
+ std::vector<T> ConsumeRemainingBytes() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeRemainingBytes<T>();
+ }
+
+ 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 <typename T>
+ T ConsumeIntegral() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeIntegral<T>();
+ }
+
+ template <typename T>
+ T ConsumeIntegralInRange(T min, T max) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeIntegralInRange<T>(min, max);
+ }
+
+ template <typename T>
+ T ConsumeFloatingPoint() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeFloatingPoint<T>();
+ }
+
+ template <typename T>
+ T ConsumeFloatingPointInRange(T min, T max) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeFloatingPointInRange<T>(min, max);
+ }
+
+ template <typename T>
+ T ConsumeProbability() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeProbability<T>();
+ }
+
+ bool ConsumeBool() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeBool();
+ }
+
+ template <typename T>
+ T ConsumeEnum() {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::ConsumeEnum<T>();
+ }
+
+ template <typename T, size_t size>
+ T PickValueInArray(const T (&array)[size]) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::PickValueInArray(array);
+ }
+
+ template <typename T, size_t size>
+ T PickValueInArray(const std::array<T, size>& array) {
+ std::scoped_lock _l(mLock);
+ return FuzzedDataProvider::PickValueInArray(array);
+ }
+
+ template <typename T>
+ T PickValueInArray(std::initializer_list<const T> 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/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
new file mode 100644
index 0000000000..be765cc364
--- /dev/null
+++ b/services/inputflinger/tests/fuzzers/TouchpadInputFuzzer.cpp
@@ -0,0 +1,185 @@
+/*
+ * 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 <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include <linux/input-event-codes.h>
+
+#include <InputDevice.h>
+#include <InputReaderBase.h>
+#include <MapperHelpers.h>
+#include <TouchpadInputMapper.h>
+
+namespace android {
+
+namespace {
+
+void setAxisInfo(ThreadSafeFuzzedDataProvider& fdp, FuzzEventHub& eventHub, int32_t id, int axis) {
+ if (fdp.ConsumeBool()) {
+ eventHub.setAbsoluteAxisInfo(id, axis,
+ RawAbsoluteAxisInfo{
+ .valid = fdp.ConsumeBool(),
+ .minValue = fdp.ConsumeIntegral<int32_t>(),
+ .maxValue = fdp.ConsumeIntegral<int32_t>(),
+ .flat = fdp.ConsumeIntegral<int32_t>(),
+ .fuzz = fdp.ConsumeIntegral<int32_t>(),
+ .resolution = fdp.ConsumeIntegral<int32_t>(),
+ });
+ }
+}
+
+void setAxisInfos(ThreadSafeFuzzedDataProvider& fdp, FuzzEventHub& eventHub, int32_t id) {
+ setAxisInfo(fdp, eventHub, id, ABS_MT_SLOT);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_POSITION_X);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_POSITION_Y);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_PRESSURE);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_ORIENTATION);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_TOUCH_MAJOR);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_TOUCH_MINOR);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_WIDTH_MAJOR);
+ setAxisInfo(fdp, eventHub, id, ABS_MT_WIDTH_MINOR);
+}
+
+const std::vector<std::string> boolPropertiesToFuzz = {
+ "gestureProp.Compute_Surface_Area_from_Pressure",
+ "gestureProp.Drumroll_Suppression_Enable",
+ "gestureProp.Fling_Buffer_Suppress_Zero_Length_Scrolls",
+ "gestureProp.Stationary_Wiggle_Filter_Enabled",
+};
+const std::vector<std::string> doublePropertiesToFuzz = {
+ "gestureProp.Fake_Timestamp_Delta",
+ "gestureProp.Finger_Moving_Energy",
+ "gestureProp.Finger_Moving_Hysteresis",
+ "gestureProp.IIR_a1",
+ "gestureProp.IIR_a2",
+ "gestureProp.IIR_b0",
+ "gestureProp.IIR_b1",
+ "gestureProp.IIR_b2",
+ "gestureProp.IIR_b3",
+ "gestureProp.Max_Allowed_Pressure_Change_Per_Sec",
+ "gestureProp.Max_Hysteresis_Pressure_Per_Sec",
+ "gestureProp.Max_Stationary_Move_Speed",
+ "gestureProp.Max_Stationary_Move_Speed_Hysteresis",
+ "gestureProp.Max_Stationary_Move_Suppress_Distance",
+ "gestureProp.Multiple_Palm_Width",
+ "gestureProp.Palm_Edge_Zone_Width",
+ "gestureProp.Palm_Eval_Timeout",
+ "gestureProp.Palm_Pressure",
+ "gestureProp.Palm_Width",
+ "gestureProp.Pressure_Calibration_Offset",
+ "gestureProp.Pressure_Calibration_Slope",
+ "gestureProp.Tap_Exclusion_Border_Width",
+ "gestureProp.Touchpad_Device_Output_Bias_on_X-Axis",
+ "gestureProp.Touchpad_Device_Output_Bias_on_Y-Axis",
+ "gestureProp.Two_Finger_Vertical_Close_Distance_Thresh",
+};
+
+void setDeviceSpecificConfig(ThreadSafeFuzzedDataProvider& fdp, FuzzEventHub& eventHub) {
+ // There are a great many gesture properties offered by the Gestures library, all of which could
+ // potentially be set in Input Device Configuration files. Maintaining a complete list is
+ // impractical, so instead we only fuzz properties which are used in at least one IDC file, or
+ // which are likely to be used in future (e.g. ones for controlling palm rejection).
+
+ if (fdp.ConsumeBool()) {
+ eventHub.addProperty("gestureProp.Touchpad_Stack_Version",
+ std::to_string(fdp.ConsumeIntegral<int>()));
+ }
+
+ for (auto& propertyName : boolPropertiesToFuzz) {
+ if (fdp.ConsumeBool()) {
+ eventHub.addProperty(propertyName, fdp.ConsumeBool() ? "1" : "0");
+ }
+ }
+
+ for (auto& propertyName : doublePropertiesToFuzz) {
+ if (fdp.ConsumeBool()) {
+ eventHub.addProperty(propertyName, std::to_string(fdp.ConsumeFloatingPoint<double>()));
+ }
+ }
+
+ if (fdp.ConsumeBool()) {
+ eventHub.addProperty("gestureProp." + fdp.ConsumeRandomLengthString(),
+ std::to_string(fdp.ConsumeIntegral<int>()));
+ }
+}
+
+void setTouchpadSettings(ThreadSafeFuzzedDataProvider& fdp, InputReaderConfiguration& config) {
+ config.touchpadPointerSpeed = fdp.ConsumeIntegralInRange(-7, 7);
+ config.touchpadNaturalScrollingEnabled = fdp.ConsumeBool();
+ config.touchpadTapToClickEnabled = fdp.ConsumeBool();
+ config.touchpadRightClickZoneEnabled = fdp.ConsumeBool();
+}
+
+} // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(uint8_t* data, size_t size) {
+ std::shared_ptr<ThreadSafeFuzzedDataProvider> fdp =
+ std::make_shared<ThreadSafeFuzzedDataProvider>(data, size);
+
+ // Create mocked objects to support the fuzzed input mapper.
+ std::shared_ptr<FuzzEventHub> eventHub = std::make_shared<FuzzEventHub>(fdp);
+ FuzzInputReaderContext context(eventHub, fdp);
+ InputDevice device = getFuzzedInputDevice(*fdp, &context);
+
+ setAxisInfos(*fdp, *eventHub.get(), device.getId());
+ setDeviceSpecificConfig(*fdp, *eventHub.get());
+
+ InputReaderConfiguration policyConfig;
+ // Some settings are fuzzed here, as well as in the main loop, to provide randomized data to the
+ // TouchpadInputMapper constructor.
+ setTouchpadSettings(*fdp, policyConfig);
+ policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
+ TouchpadInputMapper& mapper =
+ getMapperForDevice<ThreadSafeFuzzedDataProvider, TouchpadInputMapper>(*fdp, device,
+ policyConfig);
+
+ // Loop through mapper operations until randomness is exhausted.
+ while (fdp->remaining_bytes() > 0) {
+ fdp->PickValueInArray<std::function<void()>>({
+ [&]() -> void {
+ std::string dump;
+ mapper.dump(dump);
+ },
+ [&]() -> void {
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(info);
+ },
+ [&]() -> void { mapper.getSources(); },
+ [&]() -> void {
+ setTouchpadSettings(*fdp, policyConfig);
+ policyConfig.pointerCaptureRequest.enable = fdp->ConsumeBool();
+ std::list<NotifyArgs> unused =
+ mapper.reconfigure(fdp->ConsumeIntegral<nsecs_t>(), policyConfig,
+ InputReaderConfiguration::Change(
+ fdp->ConsumeIntegral<uint32_t>()));
+ },
+ [&]() -> void {
+ std::list<NotifyArgs> unused = mapper.reset(fdp->ConsumeIntegral<nsecs_t>());
+ },
+ [&]() -> void {
+ RawEvent event = getFuzzedRawEvent(*fdp);
+ std::list<NotifyArgs> unused = mapper.process(&event);
+ },
+ })();
+ }
+
+ return 0;
+}
+
+} // namespace android
diff --git a/services/memtrackproxy/MemtrackProxy.cpp b/services/memtrackproxy/MemtrackProxy.cpp
index 467616724a..9e41a9333e 100644
--- a/services/memtrackproxy/MemtrackProxy.cpp
+++ b/services/memtrackproxy/MemtrackProxy.cpp
@@ -97,9 +97,14 @@ bool MemtrackProxy::CheckPid(pid_t calling_pid, pid_t request_pid) {
return calling_pid == request_pid;
}
-MemtrackProxy::MemtrackProxy()
- : memtrack_hidl_instance_(MemtrackProxy::MemtrackHidlInstance()),
- memtrack_aidl_instance_(MemtrackProxy::MemtrackAidlInstance()) {}
+MemtrackProxy::MemtrackProxy() {
+ memtrack_aidl_instance_ = MemtrackProxy::MemtrackAidlInstance();
+
+ // Only check for a HIDL implementation if we failed to get the AIDL service
+ if (!memtrack_aidl_instance_) {
+ memtrack_hidl_instance_ = MemtrackProxy::MemtrackHidlInstance();
+ }
+}
ndk::ScopedAStatus MemtrackProxy::getMemory(int pid, MemtrackType type,
std::vector<MemtrackRecord>* _aidl_return) {
diff --git a/services/powermanager/Android.bp b/services/powermanager/Android.bp
index 6fbba3f568..1f72e8ba2c 100644
--- a/services/powermanager/Android.bp
+++ b/services/powermanager/Android.bp
@@ -9,7 +9,7 @@ package {
cc_library_shared {
name: "libpowermanager",
-
+ defaults: ["android.hardware.power-ndk_export_shared"],
srcs: [
"BatterySaverPolicyConfig.cpp",
"CoolingDevice.cpp",
@@ -19,6 +19,7 @@ cc_library_shared {
"PowerHalWrapper.cpp",
"PowerSaveState.cpp",
"Temperature.cpp",
+ "WorkDuration.cpp",
"WorkSource.cpp",
":libpowermanager_aidl",
],
@@ -33,12 +34,21 @@ cc_library_shared {
shared_libs: [
"libbinder",
+ "libbinder_ndk",
"libhidlbase",
"liblog",
"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",
+ ],
+
+ export_shared_lib_headers: [
+ "android.hardware.power@1.0",
+ "android.hardware.power@1.1",
+ "android.hardware.power@1.2",
+ "android.hardware.power@1.3",
],
cflags: [
diff --git a/services/powermanager/PowerHalController.cpp b/services/powermanager/PowerHalController.cpp
index 8c225d5d02..9a23c848c9 100644
--- a/services/powermanager/PowerHalController.cpp
+++ b/services/powermanager/PowerHalController.cpp
@@ -15,11 +15,11 @@
*/
#define LOG_TAG "PowerHalController"
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
#include <powermanager/PowerHalController.h>
#include <powermanager/PowerHalLoader.h>
#include <utils/Log.h>
@@ -33,16 +33,21 @@ namespace power {
// -------------------------------------------------------------------------------------------------
std::unique_ptr<HalWrapper> HalConnector::connect() {
- sp<IPower> halAidl = PowerHalLoader::loadAidl();
- if (halAidl) {
+ if (std::shared_ptr<aidl::android::hardware::power::IPower> halAidl =
+ PowerHalLoader::loadAidl()) {
return std::make_unique<AidlHalWrapper>(halAidl);
}
- sp<V1_0::IPower> halHidlV1_0 = PowerHalLoader::loadHidlV1_0();
- sp<V1_1::IPower> halHidlV1_1 = PowerHalLoader::loadHidlV1_1();
- if (halHidlV1_1) {
- return std::make_unique<HidlHalWrapperV1_1>(halHidlV1_0, halHidlV1_1);
- }
- if (halHidlV1_0) {
+ // If V1_0 isn't defined, none of them are
+ if (sp<V1_0::IPower> halHidlV1_0 = PowerHalLoader::loadHidlV1_0()) {
+ if (sp<V1_3::IPower> halHidlV1_3 = PowerHalLoader::loadHidlV1_3()) {
+ return std::make_unique<HidlHalWrapperV1_3>(halHidlV1_3);
+ }
+ if (sp<V1_2::IPower> halHidlV1_2 = PowerHalLoader::loadHidlV1_2()) {
+ return std::make_unique<HidlHalWrapperV1_2>(halHidlV1_2);
+ }
+ if (sp<V1_1::IPower> halHidlV1_1 = PowerHalLoader::loadHidlV1_1()) {
+ return std::make_unique<HidlHalWrapperV1_1>(halHidlV1_1);
+ }
return std::make_unique<HidlHalWrapperV1_0>(halHidlV1_0);
}
return nullptr;
@@ -86,20 +91,24 @@ HalResult<T> PowerHalController::processHalResult(HalResult<T> result, const cha
return result;
}
-HalResult<void> PowerHalController::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> PowerHalController::setBoost(aidl::android::hardware::power::Boost boost,
+ int32_t durationMs) {
std::shared_ptr<HalWrapper> handle = initHal();
auto result = handle->setBoost(boost, durationMs);
return processHalResult(result, "setBoost");
}
-HalResult<void> PowerHalController::setMode(Mode mode, bool enabled) {
+HalResult<void> PowerHalController::setMode(aidl::android::hardware::power::Mode mode,
+ bool enabled) {
std::shared_ptr<HalWrapper> handle = initHal();
auto result = handle->setMode(mode, enabled);
return processHalResult(result, "setMode");
}
-HalResult<sp<IPowerHintSession>> PowerHalController::createHintSession(
- int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
+HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>
+PowerHalController::createHintSession(int32_t tgid, int32_t uid,
+ const std::vector<int32_t>& threadIds,
+ int64_t durationNanos) {
std::shared_ptr<HalWrapper> handle = initHal();
auto result = handle->createHintSession(tgid, uid, threadIds, durationNanos);
return processHalResult(result, "createHintSession");
diff --git a/services/powermanager/PowerHalLoader.cpp b/services/powermanager/PowerHalLoader.cpp
index 1f1b43a607..22144615da 100644
--- a/services/powermanager/PowerHalLoader.cpp
+++ b/services/powermanager/PowerHalLoader.cpp
@@ -16,8 +16,11 @@
#define LOG_TAG "PowerHalLoader"
+#include <aidl/android/hardware/power/IPower.h>
+#include <android/binder_manager.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/IPower.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <android/hardware/power/1.3/IPower.h>
#include <binder/IServiceManager.h>
#include <hardware/power.h>
#include <hardware_legacy/power.h>
@@ -52,22 +55,45 @@ sp<T> loadHal(bool& exists, sp<T>& hal, F& loadFn, const char* halName) {
// -------------------------------------------------------------------------------------------------
std::mutex PowerHalLoader::gHalMutex;
-sp<IPower> PowerHalLoader::gHalAidl = nullptr;
+std::shared_ptr<aidl::android::hardware::power::IPower> PowerHalLoader::gHalAidl = nullptr;
sp<V1_0::IPower> PowerHalLoader::gHalHidlV1_0 = nullptr;
sp<V1_1::IPower> PowerHalLoader::gHalHidlV1_1 = nullptr;
+sp<V1_2::IPower> PowerHalLoader::gHalHidlV1_2 = nullptr;
+sp<V1_3::IPower> PowerHalLoader::gHalHidlV1_3 = nullptr;
void PowerHalLoader::unloadAll() {
std::lock_guard<std::mutex> lock(gHalMutex);
gHalAidl = nullptr;
gHalHidlV1_0 = nullptr;
gHalHidlV1_1 = nullptr;
+ gHalHidlV1_2 = nullptr;
+ gHalHidlV1_3 = nullptr;
}
-sp<IPower> PowerHalLoader::loadAidl() {
+std::shared_ptr<aidl::android::hardware::power::IPower> PowerHalLoader::loadAidl() {
std::lock_guard<std::mutex> lock(gHalMutex);
static bool gHalExists = true;
- static auto loadFn = []() { return waitForVintfService<IPower>(); };
- return loadHal<IPower>(gHalExists, gHalAidl, loadFn, "AIDL");
+ if (!gHalExists) {
+ return nullptr;
+ }
+ if (gHalAidl) {
+ return gHalAidl;
+ }
+ auto aidlServiceName =
+ std::string(aidl::android::hardware::power::IPower::descriptor) + "/default";
+ if (!AServiceManager_isDeclared(aidlServiceName.c_str())) {
+ gHalExists = false;
+ return nullptr;
+ }
+ gHalAidl = aidl::android::hardware::power::IPower::fromBinder(
+ ndk::SpAIBinder(AServiceManager_waitForService(aidlServiceName.c_str())));
+ if (gHalAidl) {
+ ALOGI("Successfully connected to Power HAL AIDL service.");
+ } else {
+ ALOGI("Power HAL AIDL service not available.");
+ gHalExists = false;
+ }
+ return gHalAidl;
}
sp<V1_0::IPower> PowerHalLoader::loadHidlV1_0() {
@@ -82,6 +108,20 @@ sp<V1_1::IPower> PowerHalLoader::loadHidlV1_1() {
return loadHal<V1_1::IPower>(gHalExists, gHalHidlV1_1, loadFn, "HIDL v1.1");
}
+sp<V1_2::IPower> PowerHalLoader::loadHidlV1_2() {
+ std::lock_guard<std::mutex> lock(gHalMutex);
+ static bool gHalExists = true;
+ static auto loadFn = []() { return V1_2::IPower::castFrom(loadHidlV1_0Locked()); };
+ return loadHal<V1_2::IPower>(gHalExists, gHalHidlV1_2, loadFn, "HIDL v1.2");
+}
+
+sp<V1_3::IPower> PowerHalLoader::loadHidlV1_3() {
+ std::lock_guard<std::mutex> lock(gHalMutex);
+ static bool gHalExists = true;
+ static auto loadFn = []() { return V1_3::IPower::castFrom(loadHidlV1_0Locked()); };
+ return loadHal<V1_3::IPower>(gHalExists, gHalHidlV1_3, loadFn, "HIDL v1.3");
+}
+
sp<V1_0::IPower> 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 d74bd23a8d..76afbfc646 100644
--- a/services/powermanager/PowerHalWrapper.cpp
+++ b/services/powermanager/PowerHalWrapper.cpp
@@ -15,88 +15,49 @@
*/
#define LOG_TAG "HalWrapper"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <powermanager/PowerHalWrapper.h>
#include <utils/Log.h>
#include <cinttypes>
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 Aidl = aidl::android::hardware::power;
namespace android {
namespace power {
// -------------------------------------------------------------------------------------------------
-
-inline HalResult<void> toHalResult(const binder::Status& result) {
+inline HalResult<void> toHalResult(const ndk::ScopedAStatus& result) {
if (result.isOk()) {
return HalResult<void>::ok();
}
- ALOGE("Power HAL request failed: %s", result.toString8().c_str());
- return HalResult<void>::fromStatus(result);
-}
-
-template <typename T>
-template <typename R>
-HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, T data) {
- return ret.isOk() ? HalResult<T>::ok(data) : HalResult<T>::failed(ret.description());
-}
-
-template <typename T>
-template <typename R>
-HalResult<T> HalResult<T>::fromReturn(hardware::Return<R>& ret, V1_0::Status status, T data) {
- return ret.isOk() ? HalResult<T>::fromStatus(status, data)
- : HalResult<T>::failed(ret.description());
+ ALOGE("Power HAL request failed: %s", result.getDescription().c_str());
+ return HalResult<void>::failed(result.getDescription());
}
// -------------------------------------------------------------------------------------------------
-HalResult<void> HalResult<void>::fromStatus(status_t status) {
- if (status == android::OK) {
- return HalResult<void>::ok();
- }
- return HalResult<void>::failed(statusToString(status));
-}
-
-HalResult<void> HalResult<void>::fromStatus(binder::Status status) {
- if (status.exceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
- return HalResult<void>::unsupported();
- }
- if (status.isOk()) {
- return HalResult<void>::ok();
- }
- return HalResult<void>::failed(std::string(status.toString8().c_str()));
-}
-
-template <typename R>
-HalResult<void> HalResult<void>::fromReturn(hardware::Return<R>& ret) {
- return ret.isOk() ? HalResult<void>::ok() : HalResult<void>::failed(ret.description());
-}
-// -------------------------------------------------------------------------------------------------
-
-HalResult<void> EmptyHalWrapper::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> EmptyHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
ALOGV("Skipped setBoost %s with duration %dms because Power HAL not available",
toString(boost).c_str(), durationMs);
return HalResult<void>::unsupported();
}
-HalResult<void> EmptyHalWrapper::setMode(Mode mode, bool enabled) {
+HalResult<void> EmptyHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
ALOGV("Skipped setMode %s to %s because Power HAL not available", toString(mode).c_str(),
enabled ? "true" : "false");
return HalResult<void>::unsupported();
}
-HalResult<sp<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> EmptyHalWrapper::createHintSession(
int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
threadIds.size());
- return HalResult<sp<Aidl::IPowerHintSession>>::unsupported();
+ return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
}
HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
@@ -106,29 +67,29 @@ HalResult<int64_t> EmptyHalWrapper::getHintSessionPreferredRate() {
// -------------------------------------------------------------------------------------------------
-HalResult<void> HidlHalWrapperV1_0::setBoost(Boost boost, int32_t durationMs) {
- if (boost == Boost::INTERACTION) {
- return sendPowerHint(V1_0::PowerHint::INTERACTION, durationMs);
+HalResult<void> HidlHalWrapperV1_0::setBoost(Aidl::Boost boost, int32_t durationMs) {
+ if (boost == Aidl::Boost::INTERACTION) {
+ return sendPowerHint(V1_3::PowerHint::INTERACTION, durationMs);
} else {
ALOGV("Skipped setBoost %s because Power HAL AIDL not available", toString(boost).c_str());
return HalResult<void>::unsupported();
}
}
-HalResult<void> HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) {
+HalResult<void> HidlHalWrapperV1_0::setMode(Aidl::Mode mode, bool enabled) {
uint32_t data = enabled ? 1 : 0;
switch (mode) {
- case Mode::LAUNCH:
- return sendPowerHint(V1_0::PowerHint::LAUNCH, data);
- case Mode::LOW_POWER:
- return sendPowerHint(V1_0::PowerHint::LOW_POWER, data);
- case Mode::SUSTAINED_PERFORMANCE:
- return sendPowerHint(V1_0::PowerHint::SUSTAINED_PERFORMANCE, data);
- case Mode::VR:
- return sendPowerHint(V1_0::PowerHint::VR_MODE, data);
- case Mode::INTERACTIVE:
+ case Aidl::Mode::LAUNCH:
+ return sendPowerHint(V1_3::PowerHint::LAUNCH, data);
+ case Aidl::Mode::LOW_POWER:
+ return sendPowerHint(V1_3::PowerHint::LOW_POWER, data);
+ case Aidl::Mode::SUSTAINED_PERFORMANCE:
+ return sendPowerHint(V1_3::PowerHint::SUSTAINED_PERFORMANCE, data);
+ case Aidl::Mode::VR:
+ return sendPowerHint(V1_3::PowerHint::VR_MODE, data);
+ case Aidl::Mode::INTERACTIVE:
return setInteractive(enabled);
- case Mode::DOUBLE_TAP_TO_WAKE:
+ case Aidl::Mode::DOUBLE_TAP_TO_WAKE:
return setFeature(V1_0::Feature::POWER_FEATURE_DOUBLE_TAP_TO_WAKE, enabled);
default:
ALOGV("Skipped setMode %s because Power HAL AIDL not available",
@@ -137,8 +98,8 @@ HalResult<void> HidlHalWrapperV1_0::setMode(Mode mode, bool enabled) {
}
}
-HalResult<void> HidlHalWrapperV1_0::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) {
- auto ret = mHandleV1_0->powerHint(hintId, data);
+HalResult<void> HidlHalWrapperV1_0::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+ auto ret = mHandleV1_0->powerHint(static_cast<V1_0::PowerHint>(hintId), data);
return HalResult<void>::fromReturn(ret);
}
@@ -152,11 +113,11 @@ HalResult<void> HidlHalWrapperV1_0::setFeature(V1_0::Feature feature, bool enabl
return HalResult<void>::fromReturn(ret);
}
-HalResult<sp<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> HidlHalWrapperV1_0::createHintSession(
int32_t, int32_t, const std::vector<int32_t>& threadIds, int64_t) {
ALOGV("Skipped createHintSession(task num=%zu) because Power HAL not available",
threadIds.size());
- return HalResult<sp<Aidl::IPowerHintSession>>::unsupported();
+ return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::unsupported();
}
HalResult<int64_t> HidlHalWrapperV1_0::getHintSessionPreferredRate() {
@@ -166,14 +127,65 @@ HalResult<int64_t> HidlHalWrapperV1_0::getHintSessionPreferredRate() {
// -------------------------------------------------------------------------------------------------
-HalResult<void> HidlHalWrapperV1_1::sendPowerHint(V1_0::PowerHint hintId, uint32_t data) {
- auto ret = mHandleV1_1->powerHintAsync(hintId, data);
+HalResult<void> HidlHalWrapperV1_1::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+ auto handle = static_cast<V1_1::IPower*>(mHandleV1_0.get());
+ auto ret = handle->powerHintAsync(static_cast<V1_0::PowerHint>(hintId), data);
+ return HalResult<void>::fromReturn(ret);
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HidlHalWrapperV1_2::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+ auto handle = static_cast<V1_2::IPower*>(mHandleV1_0.get());
+ auto ret = handle->powerHintAsync_1_2(static_cast<V1_2::PowerHint>(hintId), data);
+ return HalResult<void>::fromReturn(ret);
+}
+
+HalResult<void> HidlHalWrapperV1_2::setBoost(Aidl::Boost boost, int32_t durationMs) {
+ switch (boost) {
+ case Aidl::Boost::CAMERA_SHOT:
+ return sendPowerHint(V1_3::PowerHint::CAMERA_SHOT, durationMs);
+ case Aidl::Boost::CAMERA_LAUNCH:
+ return sendPowerHint(V1_3::PowerHint::CAMERA_LAUNCH, durationMs);
+ default:
+ return HidlHalWrapperV1_1::setBoost(boost, durationMs);
+ }
+}
+
+HalResult<void> HidlHalWrapperV1_2::setMode(Aidl::Mode mode, bool enabled) {
+ uint32_t data = enabled ? 1 : 0;
+ switch (mode) {
+ case Aidl::Mode::CAMERA_STREAMING_SECURE:
+ case Aidl::Mode::CAMERA_STREAMING_LOW:
+ case Aidl::Mode::CAMERA_STREAMING_MID:
+ case Aidl::Mode::CAMERA_STREAMING_HIGH:
+ return sendPowerHint(V1_3::PowerHint::CAMERA_STREAMING, data);
+ case Aidl::Mode::AUDIO_STREAMING_LOW_LATENCY:
+ return sendPowerHint(V1_3::PowerHint::AUDIO_LOW_LATENCY, data);
+ default:
+ return HidlHalWrapperV1_1::setMode(mode, enabled);
+ }
+}
+
+// -------------------------------------------------------------------------------------------------
+
+HalResult<void> HidlHalWrapperV1_3::setMode(Aidl::Mode mode, bool enabled) {
+ uint32_t data = enabled ? 1 : 0;
+ if (mode == Aidl::Mode::EXPENSIVE_RENDERING) {
+ return sendPowerHint(V1_3::PowerHint::EXPENSIVE_RENDERING, data);
+ }
+ return HidlHalWrapperV1_2::setMode(mode, enabled);
+}
+
+HalResult<void> HidlHalWrapperV1_3::sendPowerHint(V1_3::PowerHint hintId, uint32_t data) {
+ auto handle = static_cast<V1_3::IPower*>(mHandleV1_0.get());
+ auto ret = handle->powerHintAsync_1_3(hintId, data);
return HalResult<void>::fromReturn(ret);
}
// -------------------------------------------------------------------------------------------------
-HalResult<void> AidlHalWrapper::setBoost(Boost boost, int32_t durationMs) {
+HalResult<void> AidlHalWrapper::setBoost(Aidl::Boost boost, int32_t durationMs) {
std::unique_lock<std::mutex> lock(mBoostMutex);
size_t idx = static_cast<size_t>(boost);
@@ -188,7 +200,7 @@ HalResult<void> AidlHalWrapper::setBoost(Boost boost, int32_t durationMs) {
auto isSupportedRet = mHandle->isBoostSupported(boost, &isSupported);
if (!isSupportedRet.isOk()) {
ALOGE("Skipped setBoost %s because check support failed with: %s",
- toString(boost).c_str(), isSupportedRet.toString8().c_str());
+ toString(boost).c_str(), isSupportedRet.getDescription().c_str());
// return HalResult::FAILED;
return HalResult<void>::fromStatus(isSupportedRet);
}
@@ -205,7 +217,7 @@ HalResult<void> AidlHalWrapper::setBoost(Boost boost, int32_t durationMs) {
return toHalResult(mHandle->setBoost(boost, durationMs));
}
-HalResult<void> AidlHalWrapper::setMode(Mode mode, bool enabled) {
+HalResult<void> AidlHalWrapper::setMode(Aidl::Mode mode, bool enabled) {
std::unique_lock<std::mutex> lock(mModeMutex);
size_t idx = static_cast<size_t>(mode);
@@ -219,7 +231,7 @@ HalResult<void> AidlHalWrapper::setMode(Mode mode, bool enabled) {
bool isSupported = false;
auto isSupportedRet = mHandle->isModeSupported(mode, &isSupported);
if (!isSupportedRet.isOk()) {
- return HalResult<void>::failed(isSupportedRet.toString8().c_str());
+ return HalResult<void>::failed(isSupportedRet.getDescription());
}
mModeSupportedArray[idx] = isSupported ? HalSupport::ON : HalSupport::OFF;
@@ -234,10 +246,10 @@ HalResult<void> AidlHalWrapper::setMode(Mode mode, bool enabled) {
return toHalResult(mHandle->setMode(mode, enabled));
}
-HalResult<sp<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
+HalResult<std::shared_ptr<Aidl::IPowerHintSession>> AidlHalWrapper::createHintSession(
int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds, int64_t durationNanos) {
- sp<IPowerHintSession> appSession;
- return HalResult<sp<Aidl::IPowerHintSession>>::
+ std::shared_ptr<Aidl::IPowerHintSession> appSession;
+ return HalResult<std::shared_ptr<Aidl::IPowerHintSession>>::
fromStatus(mHandle->createHintSession(tgid, uid, threadIds, durationNanos, &appSession),
appSession);
}
diff --git a/services/powermanager/WorkDuration.cpp b/services/powermanager/WorkDuration.cpp
new file mode 100644
index 0000000000..ef723c229c
--- /dev/null
+++ b/services/powermanager/WorkDuration.cpp
@@ -0,0 +1,51 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "WorkDuration"
+
+#include <android/WorkDuration.h>
+#include <android/performance_hint.h>
+#include <binder/Parcel.h>
+#include <utils/Log.h>
+
+namespace android::os {
+
+WorkDuration::WorkDuration(int64_t startTimestampNanos, int64_t totalDurationNanos,
+ int64_t cpuDurationNanos, int64_t gpuDurationNanos)
+ : workPeriodStartTimestampNanos(startTimestampNanos),
+ actualTotalDurationNanos(totalDurationNanos),
+ actualCpuDurationNanos(cpuDurationNanos),
+ actualGpuDurationNanos(gpuDurationNanos) {}
+
+status_t WorkDuration::writeToParcel(Parcel* parcel) const {
+ if (parcel == nullptr) {
+ ALOGE("%s: Null parcel", __func__);
+ return BAD_VALUE;
+ }
+
+ parcel->writeInt64(workPeriodStartTimestampNanos);
+ parcel->writeInt64(actualTotalDurationNanos);
+ parcel->writeInt64(actualCpuDurationNanos);
+ parcel->writeInt64(actualGpuDurationNanos);
+ parcel->writeInt64(timestampNanos);
+ return OK;
+}
+
+status_t WorkDuration::readFromParcel(const Parcel*) {
+ return INVALID_OPERATION;
+}
+
+} // namespace android::os
diff --git a/services/powermanager/benchmarks/Android.bp b/services/powermanager/benchmarks/Android.bp
index fcb012fc75..2b5ddb1460 100644
--- a/services/powermanager/benchmarks/Android.bp
+++ b/services/powermanager/benchmarks/Android.bp
@@ -23,6 +23,7 @@ package {
cc_benchmark {
name: "libpowermanager_benchmarks",
+ defaults: ["android.hardware.power-ndk_shared"],
srcs: [
"main.cpp",
"PowerHalAidlBenchmarks.cpp",
@@ -32,13 +33,15 @@ cc_benchmark {
shared_libs: [
"libbase",
"libbinder",
+ "libbinder_ndk",
"libhidlbase",
"liblog",
"libpowermanager",
"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",
],
static_libs: [
"libtestUtil",
diff --git a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
index 6e5e14de28..759485ffce 100644
--- a/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalAidlBenchmarks.cpp
@@ -16,21 +16,24 @@
#define LOG_TAG "PowerHalAidlBenchmarks"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
-#include <android/hardware/power/WorkDuration.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/WorkDuration.h>
#include <benchmark/benchmark.h>
#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <powermanager/PowerHalLoader.h>
#include <testUtil.h>
#include <chrono>
-using android::hardware::power::Boost;
-using android::hardware::power::IPower;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
-using android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPower;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::WorkDuration;
+using android::power::PowerHalLoader;
using std::chrono::microseconds;
using namespace android;
@@ -63,15 +66,15 @@ static constexpr microseconds ONEWAY_API_DELAY = 100us;
template <class R, class... Args0, class... Args1>
static void runBenchmark(benchmark::State& state, microseconds delay, R (IPower::*fn)(Args0...),
Args1&&... args1) {
- sp<IPower> hal = waitForVintfService<IPower>();
+ std::shared_ptr<IPower> hal = PowerHalLoader::loadAidl();
if (hal == nullptr) {
ALOGV("Power HAL not available, skipping test...");
return;
}
- binder::Status ret = (*hal.*fn)(std::forward<Args1>(args1)...);
- if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
+ ndk::ScopedAStatus ret = (*hal.*fn)(std::forward<Args1>(args1)...);
+ if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
ALOGV("Power HAL does not support this operation, skipping test...");
return;
}
@@ -79,7 +82,7 @@ static void runBenchmark(benchmark::State& state, microseconds delay, R (IPower:
while (state.KeepRunning()) {
ret = (*hal.*fn)(std::forward<Args1>(args1)...);
state.PauseTiming();
- if (!ret.isOk()) state.SkipWithError(ret.toString8().c_str());
+ if (!ret.isOk()) state.SkipWithError(ret.getDescription().c_str());
if (delay > 0us) {
testDelaySpin(std::chrono::duration_cast<std::chrono::duration<float>>(delay).count());
}
@@ -90,9 +93,9 @@ static void runBenchmark(benchmark::State& state, microseconds delay, R (IPower:
template <class R, class... Args0, class... Args1>
static void runSessionBenchmark(benchmark::State& state, R (IPowerHintSession::*fn)(Args0...),
Args1&&... args1) {
- sp<IPower> pwHal = waitForVintfService<IPower>();
+ std::shared_ptr<IPower> hal = PowerHalLoader::loadAidl();
- if (pwHal == nullptr) {
+ if (hal == nullptr) {
ALOGV("Power HAL not available, skipping test...");
return;
}
@@ -100,32 +103,32 @@ static void runSessionBenchmark(benchmark::State& state, R (IPowerHintSession::*
// do not use tid from the benchmark process, use 1 for init
std::vector<int32_t> threadIds{1};
int64_t durationNanos = 16666666L;
- sp<IPowerHintSession> hal;
+ std::shared_ptr<IPowerHintSession> session;
- auto status = pwHal->createHintSession(1, 0, threadIds, durationNanos, &hal);
+ auto status = hal->createHintSession(1, 0, threadIds, durationNanos, &session);
- if (hal == nullptr) {
+ if (session == nullptr) {
ALOGV("Power HAL doesn't support session, skipping test...");
return;
}
- binder::Status ret = (*hal.*fn)(std::forward<Args1>(args1)...);
- if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
+ ndk::ScopedAStatus ret = (*session.*fn)(std::forward<Args1>(args1)...);
+ if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
ALOGV("Power HAL does not support this operation, skipping test...");
return;
}
while (state.KeepRunning()) {
- ret = (*hal.*fn)(std::forward<Args1>(args1)...);
+ ret = (*session.*fn)(std::forward<Args1>(args1)...);
state.PauseTiming();
- if (!ret.isOk()) state.SkipWithError(ret.toString8().c_str());
+ if (!ret.isOk()) state.SkipWithError(ret.getDescription().c_str());
if (ONEWAY_API_DELAY > 0us) {
testDelaySpin(std::chrono::duration_cast<std::chrono::duration<float>>(ONEWAY_API_DELAY)
.count());
}
state.ResumeTiming();
}
- hal->close();
+ session->close();
}
static void BM_PowerHalAidlBenchmarks_isBoostSupported(benchmark::State& state) {
@@ -155,16 +158,17 @@ static void BM_PowerHalAidlBenchmarks_createHintSession(benchmark::State& state)
int64_t durationNanos = 16666666L;
int32_t tgid = 999;
int32_t uid = 1001;
- sp<IPowerHintSession> appSession;
- sp<IPower> hal = waitForVintfService<IPower>();
+ std::shared_ptr<IPowerHintSession> appSession;
+ std::shared_ptr<IPower> hal = PowerHalLoader::loadAidl();
if (hal == nullptr) {
ALOGV("Power HAL not available, skipping test...");
return;
}
- binder::Status ret = hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
- if (ret.exceptionCode() == binder::Status::Exception::EX_UNSUPPORTED_OPERATION) {
+ ndk::ScopedAStatus ret =
+ hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
+ if (ret.getExceptionCode() == binder::Status::EX_UNSUPPORTED_OPERATION) {
ALOGV("Power HAL does not support this operation, skipping test...");
return;
}
@@ -172,7 +176,7 @@ static void BM_PowerHalAidlBenchmarks_createHintSession(benchmark::State& state)
while (state.KeepRunning()) {
ret = hal->createHintSession(tgid, uid, threadIds, durationNanos, &appSession);
state.PauseTiming();
- if (!ret.isOk()) state.SkipWithError(ret.toString8().c_str());
+ if (!ret.isOk()) state.SkipWithError(ret.getDescription().c_str());
appSession->close();
state.ResumeTiming();
}
diff --git a/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp
index f8abc7aba6..effddda6a4 100644
--- a/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalControllerBenchmarks.cpp
@@ -16,15 +16,15 @@
#define LOG_TAG "PowerHalControllerBenchmarks"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <benchmark/benchmark.h>
#include <powermanager/PowerHalController.h>
#include <testUtil.h>
#include <chrono>
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
using android::power::HalResult;
using android::power::PowerHalController;
diff --git a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
index 167f3a64af..111b5d70bc 100644
--- a/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
+++ b/services/powermanager/benchmarks/PowerHalHidlBenchmarks.cpp
@@ -16,10 +16,10 @@
#define LOG_TAG "PowerHalHidlBenchmarks"
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
#include <benchmark/benchmark.h>
#include <hardware/power.h>
#include <hardware_legacy/power.h>
@@ -27,8 +27,6 @@
#include <chrono>
using android::hardware::Return;
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
using android::hardware::power::V1_0::Feature;
using android::hardware::power::V1_0::PowerHint;
using std::chrono::microseconds;
diff --git a/services/powermanager/include/android/WorkDuration.h b/services/powermanager/include/android/WorkDuration.h
new file mode 100644
index 0000000000..99b5b8b1b4
--- /dev/null
+++ b/services/powermanager/include/android/WorkDuration.h
@@ -0,0 +1,71 @@
+/**
+ * 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 <binder/Parcelable.h>
+#include <math.h>
+
+struct AWorkDuration {};
+
+namespace android::os {
+
+/**
+ * C++ Parcelable version of {@link PerformanceHintManager.WorkDuration} that can be used in
+ * binder calls.
+ * This file needs to be kept in sync with the WorkDuration in
+ * frameworks/base/core/java/android/os/WorkDuration.java
+ */
+struct WorkDuration : AWorkDuration, android::Parcelable {
+ WorkDuration() = default;
+ ~WorkDuration() = default;
+
+ WorkDuration(int64_t workPeriodStartTimestampNanos, int64_t actualTotalDurationNanos,
+ int64_t actualCpuDurationNanos, int64_t actualGpuDurationNanos);
+ status_t writeToParcel(Parcel* parcel) const override;
+ status_t readFromParcel(const Parcel* parcel) override;
+
+ inline bool equalsWithoutTimestamp(const WorkDuration& other) const {
+ return workPeriodStartTimestampNanos == other.workPeriodStartTimestampNanos &&
+ actualTotalDurationNanos == other.actualTotalDurationNanos &&
+ actualCpuDurationNanos == other.actualCpuDurationNanos &&
+ actualGpuDurationNanos == other.actualGpuDurationNanos;
+ }
+
+ bool operator==(const WorkDuration& other) const {
+ return timestampNanos == other.timestampNanos && equalsWithoutTimestamp(other);
+ }
+
+ bool operator!=(const WorkDuration& other) const { return !(*this == other); }
+
+ friend std::ostream& operator<<(std::ostream& os, const WorkDuration& workDuration) {
+ os << "{"
+ << "workPeriodStartTimestampNanos: " << workDuration.workPeriodStartTimestampNanos
+ << ", actualTotalDurationNanos: " << workDuration.actualTotalDurationNanos
+ << ", actualCpuDurationNanos: " << workDuration.actualCpuDurationNanos
+ << ", actualGpuDurationNanos: " << workDuration.actualGpuDurationNanos
+ << ", timestampNanos: " << workDuration.timestampNanos << "}";
+ return os;
+ }
+
+ int64_t workPeriodStartTimestampNanos;
+ int64_t actualTotalDurationNanos;
+ int64_t actualCpuDurationNanos;
+ int64_t actualGpuDurationNanos;
+ int64_t timestampNanos;
+};
+
+} // namespace android::os
diff --git a/services/powermanager/tests/Android.bp b/services/powermanager/tests/Android.bp
index 962784cbae..6fc96c0959 100644
--- a/services/powermanager/tests/Android.bp
+++ b/services/powermanager/tests/Android.bp
@@ -23,6 +23,10 @@ package {
cc_test {
name: "libpowermanager_test",
+ defaults: [
+ "android.hardware.power-ndk_shared",
+ "android.hardware.power-ndk_shared",
+ ],
test_suites: ["device-tests"],
srcs: [
"IThermalManagerTest.cpp",
@@ -31,6 +35,8 @@ cc_test {
"PowerHalWrapperAidlTest.cpp",
"PowerHalWrapperHidlV1_0Test.cpp",
"PowerHalWrapperHidlV1_1Test.cpp",
+ "PowerHalWrapperHidlV1_2Test.cpp",
+ "PowerHalWrapperHidlV1_3Test.cpp",
"WorkSourceTest.cpp",
],
cflags: [
@@ -41,15 +47,18 @@ cc_test {
shared_libs: [
"libbase",
"libbinder",
+ "libbinder_ndk",
"libhidlbase",
"liblog",
"libpowermanager",
"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",
],
static_libs: [
"libgmock",
],
+ require_root: true,
}
diff --git a/services/powermanager/tests/PowerHalControllerTest.cpp b/services/powermanager/tests/PowerHalControllerTest.cpp
index 6cc7a6f3da..01270ce599 100644
--- a/services/powermanager/tests/PowerHalControllerTest.cpp
+++ b/services/powermanager/tests/PowerHalControllerTest.cpp
@@ -16,9 +16,9 @@
#define LOG_TAG "PowerHalControllerTest"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <powermanager/PowerHalController.h>
@@ -26,8 +26,8 @@
#include <thread>
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
using android::hardware::power::V1_0::Feature;
using android::hardware::power::V1_0::IPower;
using android::hardware::power::V1_0::PowerHint;
diff --git a/services/powermanager/tests/PowerHalLoaderTest.cpp b/services/powermanager/tests/PowerHalLoaderTest.cpp
index 058e1b5ca8..7d9735421b 100644
--- a/services/powermanager/tests/PowerHalLoaderTest.cpp
+++ b/services/powermanager/tests/PowerHalLoaderTest.cpp
@@ -16,9 +16,8 @@
#define LOG_TAG "PowerHalLoaderTest"
-#include <android-base/logging.h>
+#include <aidl/android/hardware/power/IPower.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/IPower.h>
#include <gtest/gtest.h>
#include <powermanager/PowerHalLoader.h>
@@ -26,7 +25,9 @@
using IPowerV1_0 = android::hardware::power::V1_0::IPower;
using IPowerV1_1 = android::hardware::power::V1_1::IPower;
-using IPowerAidl = android::hardware::power::IPower;
+using IPowerV1_2 = android::hardware::power::V1_2::IPower;
+using IPowerV1_3 = android::hardware::power::V1_3::IPower;
+using IPowerAidl = aidl::android::hardware::power::IPower;
using namespace android;
using namespace android::power;
@@ -35,68 +36,79 @@ using namespace testing;
// -------------------------------------------------------------------------------------------------
template <typename T>
-sp<T> loadHal();
+T loadHal();
template <>
-sp<IPowerAidl> loadHal<IPowerAidl>() {
+std::shared_ptr<IPowerAidl> loadHal<std::shared_ptr<IPowerAidl>>() {
return PowerHalLoader::loadAidl();
}
template <>
-sp<IPowerV1_0> loadHal<IPowerV1_0>() {
+sp<IPowerV1_0> loadHal<sp<IPowerV1_0>>() {
return PowerHalLoader::loadHidlV1_0();
}
template <>
-sp<IPowerV1_1> loadHal<IPowerV1_1>() {
+sp<IPowerV1_1> loadHal<sp<IPowerV1_1>>() {
return PowerHalLoader::loadHidlV1_1();
}
+template <>
+sp<IPowerV1_2> loadHal<sp<IPowerV1_2>>() {
+ return PowerHalLoader::loadHidlV1_2();
+}
+
+template <>
+sp<IPowerV1_3> loadHal<sp<IPowerV1_3>>() {
+ return PowerHalLoader::loadHidlV1_3();
+}
+
// -------------------------------------------------------------------------------------------------
template <typename T>
class PowerHalLoaderTest : public Test {
public:
- sp<T> load() { return ::loadHal<T>(); }
+ T load() { return ::loadHal<T>(); }
void unload() { PowerHalLoader::unloadAll(); }
};
// -------------------------------------------------------------------------------------------------
-
-typedef ::testing::Types<IPowerAidl, IPowerV1_0, IPowerV1_1> PowerHalTypes;
+typedef ::testing::Types<std::shared_ptr<IPowerAidl>, sp<IPowerV1_0>, sp<IPowerV1_1>,
+ sp<IPowerV1_2>, sp<IPowerV1_3>>
+ PowerHalTypes;
TYPED_TEST_SUITE(PowerHalLoaderTest, PowerHalTypes);
TYPED_TEST(PowerHalLoaderTest, TestLoadsOnlyOnce) {
- sp<TypeParam> firstHal = this->load();
+ TypeParam firstHal = this->load();
if (firstHal == nullptr) {
ALOGE("Power HAL not available. Skipping test.");
return;
}
- sp<TypeParam> secondHal = this->load();
+ TypeParam secondHal = this->load();
ASSERT_EQ(firstHal, secondHal);
}
TYPED_TEST(PowerHalLoaderTest, TestUnload) {
- sp<TypeParam> firstHal = this->load();
+ TypeParam firstHal = this->load();
if (firstHal == nullptr) {
ALOGE("Power HAL not available. Skipping test.");
return;
}
this->unload();
- sp<TypeParam> secondHal = this->load();
+ TypeParam secondHal = this->load();
ASSERT_NE(secondHal, nullptr);
ASSERT_NE(firstHal, secondHal);
}
TYPED_TEST(PowerHalLoaderTest, TestLoadMultiThreadLoadsOnlyOnce) {
- std::vector<std::future<sp<TypeParam>>> futures;
+ std::vector<std::future<TypeParam>> futures;
for (int i = 0; i < 10; i++) {
futures.push_back(
std::async(std::launch::async, &PowerHalLoaderTest<TypeParam>::load, this));
}
futures[0].wait();
- sp<TypeParam> firstHal = futures[0].get();
+ TypeParam firstHal = futures[0].get();
if (firstHal == nullptr) {
ALOGE("Power HAL not available. Skipping test.");
return;
@@ -104,7 +116,7 @@ TYPED_TEST(PowerHalLoaderTest, TestLoadMultiThreadLoadsOnlyOnce) {
for (int i = 1; i < 10; i++) {
futures[i].wait();
- sp<TypeParam> currentHal = futures[i].get();
+ TypeParam currentHal = futures[i].get();
ASSERT_EQ(firstHal, currentHal);
}
}
diff --git a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
index cb1a77a45f..641ba9f44b 100644
--- a/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
+++ b/services/powermanager/tests/PowerHalWrapperAidlTest.cpp
@@ -16,9 +16,9 @@
#define LOG_TAG "PowerHalWrapperAidlTest"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <binder/IServiceManager.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -28,11 +28,11 @@
#include <unistd.h>
#include <thread>
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPower;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
using android::binder::Status;
-using android::hardware::power::Boost;
-using android::hardware::power::IPower;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
using namespace android;
using namespace android::power;
@@ -43,18 +43,21 @@ using namespace testing;
class MockIPower : public IPower {
public:
- MOCK_METHOD(Status, isBoostSupported, (Boost boost, bool* ret), (override));
- MOCK_METHOD(Status, setBoost, (Boost boost, int32_t durationMs), (override));
- MOCK_METHOD(Status, isModeSupported, (Mode mode, bool* ret), (override));
- MOCK_METHOD(Status, setMode, (Mode mode, bool enabled), (override));
- MOCK_METHOD(Status, createHintSession,
+ MockIPower() = default;
+
+ MOCK_METHOD(ndk::ScopedAStatus, isBoostSupported, (Boost boost, bool* ret), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, setBoost, (Boost boost, int32_t durationMs), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, isModeSupported, (Mode mode, bool* ret), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, setMode, (Mode mode, bool enabled), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, createHintSession,
(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
- int64_t durationNanos, sp<IPowerHintSession>* session),
+ int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
(override));
- MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * rate), (override));
- MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
- MOCK_METHOD(std::string, getInterfaceHash, (), (override));
- MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
+ MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+ MOCK_METHOD(bool, isRemote, (), (override));
};
// -------------------------------------------------------------------------------------------------
@@ -65,13 +68,13 @@ public:
protected:
std::unique_ptr<HalWrapper> mWrapper = nullptr;
- sp<StrictMock<MockIPower>> mMockHal = nullptr;
+ std::shared_ptr<StrictMock<MockIPower>> mMockHal = nullptr;
};
// -------------------------------------------------------------------------------------------------
void PowerHalWrapperAidlTest::SetUp() {
- mMockHal = new StrictMock<MockIPower>();
+ mMockHal = ndk::SharedRefBase::make<StrictMock<MockIPower>>();
mWrapper = std::make_unique<AidlHalWrapper>(mMockHal);
ASSERT_NE(nullptr, mWrapper);
}
@@ -83,9 +86,11 @@ TEST_F(PowerHalWrapperAidlTest, TestSetBoostSuccessful) {
InSequence seq;
EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::DISPLAY_UPDATE_IMMINENT), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(true),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::DISPLAY_UPDATE_IMMINENT), Eq(100)))
- .Times(Exactly(1));
+ .Times(Exactly(1))
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
}
auto result = mWrapper->setBoost(Boost::DISPLAY_UPDATE_IMMINENT, 100);
@@ -97,13 +102,14 @@ TEST_F(PowerHalWrapperAidlTest, TestSetBoostFailed) {
InSequence seq;
EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(true),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::INTERACTION), Eq(100)))
.Times(Exactly(1))
- .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::DISPLAY_UPDATE_IMMINENT), _))
.Times(Exactly(1))
- .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
}
auto result = mWrapper->setBoost(Boost::INTERACTION, 100);
@@ -115,7 +121,8 @@ TEST_F(PowerHalWrapperAidlTest, TestSetBoostFailed) {
TEST_F(PowerHalWrapperAidlTest, TestSetBoostUnsupported) {
EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(false),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
auto result = mWrapper->setBoost(Boost::INTERACTION, 1000);
ASSERT_TRUE(result.isUnsupported());
@@ -128,8 +135,13 @@ TEST_F(PowerHalWrapperAidlTest, TestSetBoostMultiThreadCheckSupportedOnlyOnce) {
InSequence seq;
EXPECT_CALL(*mMockHal.get(), isBoostSupported(Eq(Boost::INTERACTION), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
- EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::INTERACTION), Eq(100))).Times(Exactly(10));
+ .WillOnce(DoAll(SetArgPointee<1>(true),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+ auto& exp = EXPECT_CALL(*mMockHal.get(), setBoost(Eq(Boost::INTERACTION), Eq(100)))
+ .Times(Exactly(10));
+ for (int i = 0; i < 10; i++) {
+ exp.WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+ }
}
std::vector<std::thread> threads;
@@ -147,9 +159,11 @@ TEST_F(PowerHalWrapperAidlTest, TestSetModeSuccessful) {
InSequence seq;
EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::DISPLAY_INACTIVE), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(true),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::DISPLAY_INACTIVE), Eq(false)))
- .Times(Exactly(1));
+ .Times(Exactly(1))
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
}
auto result = mWrapper->setMode(Mode::DISPLAY_INACTIVE, false);
@@ -161,13 +175,14 @@ TEST_F(PowerHalWrapperAidlTest, TestSetModeFailed) {
InSequence seq;
EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::LAUNCH), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(true),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::LAUNCH), Eq(true)))
.Times(Exactly(1))
- .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::DISPLAY_INACTIVE), _))
.Times(Exactly(1))
- .WillRepeatedly(Return(Status::fromExceptionCode(-1)));
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::fromExceptionCode(-1))));
}
auto result = mWrapper->setMode(Mode::LAUNCH, true);
@@ -179,14 +194,16 @@ TEST_F(PowerHalWrapperAidlTest, TestSetModeFailed) {
TEST_F(PowerHalWrapperAidlTest, TestSetModeUnsupported) {
EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::LAUNCH), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(false),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
auto result = mWrapper->setMode(Mode::LAUNCH, true);
ASSERT_TRUE(result.isUnsupported());
EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::CAMERA_STREAMING_HIGH), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(false), Return(Status())));
+ .WillOnce(DoAll(SetArgPointee<1>(false),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
result = mWrapper->setMode(Mode::CAMERA_STREAMING_HIGH, true);
ASSERT_TRUE(result.isUnsupported());
}
@@ -196,8 +213,13 @@ TEST_F(PowerHalWrapperAidlTest, TestSetModeMultiThreadCheckSupportedOnlyOnce) {
InSequence seq;
EXPECT_CALL(*mMockHal.get(), isModeSupported(Eq(Mode::LAUNCH), _))
.Times(Exactly(1))
- .WillRepeatedly(DoAll(SetArgPointee<1>(true), Return(Status())));
- EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::LAUNCH), Eq(false))).Times(Exactly(10));
+ .WillOnce(DoAll(SetArgPointee<1>(true),
+ Return(testing::ByMove(ndk::ScopedAStatus::ok()))));
+ auto& exp = EXPECT_CALL(*mMockHal.get(), setMode(Eq(Mode::LAUNCH), Eq(false)))
+ .Times(Exactly(10));
+ for (int i = 0; i < 10; i++) {
+ exp.WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+ }
}
std::vector<std::thread> threads;
@@ -217,7 +239,8 @@ TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionSuccessful) {
int64_t durationNanos = 16666666L;
EXPECT_CALL(*mMockHal.get(),
createHintSession(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), _))
- .Times(Exactly(1));
+ .Times(Exactly(1))
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
auto result = mWrapper->createHintSession(tgid, uid, threadIds, durationNanos);
ASSERT_TRUE(result.isOk());
}
@@ -230,13 +253,16 @@ TEST_F(PowerHalWrapperAidlTest, TestCreateHintSessionFailed) {
EXPECT_CALL(*mMockHal.get(),
createHintSession(Eq(tgid), Eq(uid), Eq(threadIds), Eq(durationNanos), _))
.Times(Exactly(1))
- .WillRepeatedly(Return(Status::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT)));
+ .WillOnce(Return(testing::ByMove(
+ ndk::ScopedAStatus::fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT))));
auto result = mWrapper->createHintSession(tgid, uid, threadIds, durationNanos);
ASSERT_TRUE(result.isFailed());
}
TEST_F(PowerHalWrapperAidlTest, TestGetHintSessionPreferredRate) {
- EXPECT_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).Times(Exactly(1));
+ EXPECT_CALL(*mMockHal.get(), getHintSessionPreferredRate(_))
+ .Times(Exactly(1))
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
auto result = mWrapper->getHintSessionPreferredRate();
ASSERT_TRUE(result.isOk());
int64_t rate = result.value();
diff --git a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
index b54762cf6d..461143bfc6 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_0Test.cpp
@@ -16,17 +16,17 @@
#define LOG_TAG "PowerHalWrapperHidlV1_0Test"
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <binder/IServiceManager.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <powermanager/PowerHalWrapper.h>
#include <utils/Log.h>
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
using android::hardware::power::V1_0::Feature;
using android::hardware::power::V1_0::IPower;
using android::hardware::power::V1_0::PowerHint;
@@ -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 d30e8d2c49..79dd996098 100644
--- a/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
+++ b/services/powermanager/tests/PowerHalWrapperHidlV1_1Test.cpp
@@ -16,22 +16,21 @@
#define LOG_TAG "PowerHalWrapperHidlV1_1Test"
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
#include <binder/IServiceManager.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <powermanager/PowerHalWrapper.h>
#include <utils/Log.h>
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
+using aidl::android::hardware::power::Boost;
+using aidl::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<void>, setInteractive, (bool interactive), (override));
- MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHint hint, int32_t data), (override));
- MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override));
- MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats,
- (getPlatformLowPowerStats_cb _hidl_cb), (override));
-};
-
class MockIPowerV1_1 : public IPowerV1_1 {
public:
MOCK_METHOD(hardware::Return<void>, setInteractive, (bool interactive), (override));
@@ -69,23 +59,22 @@ public:
protected:
std::unique_ptr<HalWrapper> mWrapper = nullptr;
- sp<StrictMock<MockIPowerV1_0>> mMockHalV1_0 = nullptr;
- sp<StrictMock<MockIPowerV1_1>> mMockHalV1_1 = nullptr;
+ sp<StrictMock<MockIPowerV1_1>> mMockHal = nullptr;
};
// -------------------------------------------------------------------------------------------------
void PowerHalWrapperHidlV1_1Test::SetUp() {
- mMockHalV1_0 = new StrictMock<MockIPowerV1_0>();
- mMockHalV1_1 = new StrictMock<MockIPowerV1_1>();
- mWrapper = std::make_unique<HidlHalWrapperV1_1>(mMockHalV1_0, mMockHalV1_1);
+ mMockHal = new StrictMock<MockIPowerV1_1>();
+ mWrapper = std::make_unique<HidlHalWrapperV1_1>(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<void>(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<void>(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 0000000000..aa6d6c7a3d
--- /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 <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <android/hardware/power/1.2/IPower.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <powermanager/PowerHalWrapper.h>
+#include <utils/Log.h>
+
+using aidl::android::hardware::power::Boost;
+using aidl::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<void>, setInteractive, (bool interactive), (override));
+ MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHintV1_0 hint, int32_t data), (override));
+ MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override));
+ MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats,
+ (getPlatformLowPowerStats_cb _hidl_cb), (override));
+ MOCK_METHOD(hardware::Return<void>, powerHintAsync, (PowerHintV1_0 hint, int32_t data),
+ (override));
+ MOCK_METHOD(hardware::Return<void>, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data),
+ (override));
+ MOCK_METHOD(hardware::Return<void>, getSubsystemLowPowerStats,
+ (getSubsystemLowPowerStats_cb _hidl_cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class PowerHalWrapperHidlV1_2Test : public Test {
+public:
+ void SetUp() override;
+
+protected:
+ std::unique_ptr<HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIPowerV1_2>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+void PowerHalWrapperHidlV1_2Test::SetUp() {
+ mMockHal = new StrictMock<MockIPowerV1_2>();
+ mWrapper = std::make_unique<HidlHalWrapperV1_2>(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<void>(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<void>(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 0000000000..a995afd750
--- /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 <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/Mode.h>
+#include <android/hardware/power/1.3/IPower.h>
+#include <binder/IServiceManager.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <powermanager/PowerHalWrapper.h>
+#include <utils/Log.h>
+
+using aidl::android::hardware::power::Boost;
+using aidl::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<void>, setInteractive, (bool interactive), (override));
+ MOCK_METHOD(hardware::Return<void>, powerHint, (PowerHintV1_0 hint, int32_t data), (override));
+ MOCK_METHOD(hardware::Return<void>, setFeature, (Feature feature, bool activate), (override));
+ MOCK_METHOD(hardware::Return<void>, getPlatformLowPowerStats,
+ (getPlatformLowPowerStats_cb _hidl_cb), (override));
+ MOCK_METHOD(hardware::Return<void>, powerHintAsync, (PowerHintV1_0 hint, int32_t data),
+ (override));
+ MOCK_METHOD(hardware::Return<void>, powerHintAsync_1_2, (PowerHintV1_2 hint, int32_t data),
+ (override));
+ MOCK_METHOD(hardware::Return<void>, powerHintAsync_1_3, (PowerHintV1_3 hint, int32_t data),
+ (override));
+
+ MOCK_METHOD(hardware::Return<void>, getSubsystemLowPowerStats,
+ (getSubsystemLowPowerStats_cb _hidl_cb), (override));
+};
+
+// -------------------------------------------------------------------------------------------------
+
+class PowerHalWrapperHidlV1_3Test : public Test {
+public:
+ void SetUp() override;
+
+protected:
+ std::unique_ptr<HalWrapper> mWrapper = nullptr;
+ sp<StrictMock<MockIPowerV1_3>> mMockHal = nullptr;
+};
+
+// -------------------------------------------------------------------------------------------------
+
+void PowerHalWrapperHidlV1_3Test::SetUp() {
+ mMockHal = new StrictMock<MockIPowerV1_3>();
+ mWrapper = std::make_unique<HidlHalWrapperV1_3>(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<void>(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<void>(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/AidlSensorHalWrapper.cpp b/services/sensorservice/AidlSensorHalWrapper.cpp
index f5b360f3b6..e60db93431 100644
--- a/services/sensorservice/AidlSensorHalWrapper.cpp
+++ b/services/sensorservice/AidlSensorHalWrapper.cpp
@@ -308,8 +308,12 @@ status_t AidlSensorHalWrapper::configureDirectChannel(int32_t sensorHandle, int3
}
int32_t token;
- mSensors->configDirectReport(sensorHandle, channelHandle, rate, &token);
- return token;
+ status_t status = convertToStatus(
+ mSensors->configDirectReport(sensorHandle, channelHandle, rate, &token));
+ if (status == OK && rate != ISensors::RateLevel::STOP) {
+ status = static_cast<status_t>(token);
+ }
+ return status;
}
void AidlSensorHalWrapper::writeWakeLockHandled(uint32_t count) {
diff --git a/services/sensorservice/Android.bp b/services/sensorservice/Android.bp
index a2042d6451..11c56a8c56 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 94de55c124..0ea548c958 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<IServiceManager> sm(defaultServiceManager());
diff --git a/services/sensorservice/BatteryService.h b/services/sensorservice/BatteryService.h
index 13fc58aadb..60ef03f685 100644
--- a/services/sensorservice/BatteryService.h
+++ b/services/sensorservice/BatteryService.h
@@ -19,11 +19,14 @@
#include <batterystats/IBatteryStats.h>
#include <utils/Singleton.h>
+#include <utils/SortedVector.h>
+#include <utils/SystemClock.h>
namespace android {
// ---------------------------------------------------------------------------
class BatteryService : public Singleton<BatteryService> {
+ static constexpr int64_t WAKEUP_SENSOR_EVENT_DEBOUNCE_MS = 1000;
friend class Singleton<BatteryService>;
sp<IBatteryStats> mBatteryStatService;
@@ -32,6 +35,7 @@ class BatteryService : public Singleton<BatteryService> {
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<BatteryService> {
}
};
+ int64_t mLastWakeupSensorEventReportedMs;
Mutex mActivationsLock;
SortedVector<Info> 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/RecentEventLogger.cpp b/services/sensorservice/RecentEventLogger.cpp
index d7ca6e1dbf..47fa8b39ac 100644
--- a/services/sensorservice/RecentEventLogger.cpp
+++ b/services/sensorservice/RecentEventLogger.cpp
@@ -83,7 +83,7 @@ std::string RecentEventLogger::dump() const {
}
buffer.append("\n");
}
- return std::string(buffer.string());
+ return std::string(buffer.c_str());
}
/**
diff --git a/services/sensorservice/SensorDevice.cpp b/services/sensorservice/SensorDevice.cpp
index de050e02d0..dd83fdefc3 100644
--- a/services/sensorservice/SensorDevice.cpp
+++ b/services/sensorservice/SensorDevice.cpp
@@ -37,9 +37,10 @@
#include <cinttypes>
#include <cstddef>
#include <thread>
+#include <mutex>
+#include <condition_variable>
using namespace android::hardware::sensors;
-using android::hardware::Return;
using android::util::ProtoOutputStream;
namespace android {
@@ -66,7 +67,7 @@ enum DevicePrivateBase : int32_t {
} // anonymous namespace
-SensorDevice::SensorDevice() {
+SensorDevice::SensorDevice() : mInHalBypassMode(false) {
if (!connectHalService()) {
return;
}
@@ -299,7 +300,7 @@ std::string SensorDevice::dump() const {
result.appendFormat("}, selected = %.2f ms\n", info.bestBatchParams.mTBatch / 1e6f);
}
- return result.string();
+ return result.c_str();
}
/**
@@ -353,13 +354,17 @@ ssize_t SensorDevice::poll(sensors_event_t* buffer, size_t count) {
if (mHalWrapper == nullptr) return NO_INIT;
ssize_t eventsRead = 0;
- if (mHalWrapper->supportsMessageQueues()) {
- eventsRead = mHalWrapper->pollFmq(buffer, count);
- } else if (mHalWrapper->supportsPolling()) {
- eventsRead = mHalWrapper->poll(buffer, count);
+ if (mInHalBypassMode) [[unlikely]] {
+ eventsRead = getHalBypassInjectedEvents(buffer, count);
} else {
- ALOGE("Must support polling or FMQ");
- eventsRead = -1;
+ if (mHalWrapper->supportsMessageQueues()) {
+ eventsRead = mHalWrapper->pollFmq(buffer, count);
+ } else if (mHalWrapper->supportsPolling()) {
+ eventsRead = mHalWrapper->poll(buffer, count);
+ } else {
+ ALOGE("Must support polling or FMQ");
+ eventsRead = -1;
+ }
}
if (eventsRead > 0) {
@@ -763,11 +768,38 @@ status_t SensorDevice::injectSensorData(const sensors_event_t* injected_sensor_e
injected_sensor_event->data[2], injected_sensor_event->data[3],
injected_sensor_event->data[4], injected_sensor_event->data[5]);
+ if (mInHalBypassMode) {
+ std::lock_guard _l(mHalBypassLock);
+ mHalBypassInjectedEventQueue.push(*injected_sensor_event);
+ mHalBypassCV.notify_one();
+ return OK;
+ }
return mHalWrapper->injectSensorData(injected_sensor_event);
}
status_t SensorDevice::setMode(uint32_t mode) {
if (mHalWrapper == nullptr) return NO_INIT;
+ if (mode == SensorService::Mode::HAL_BYPASS_REPLAY_DATA_INJECTION) {
+ if (!mInHalBypassMode) {
+ std::lock_guard _l(mHalBypassLock);
+ while (!mHalBypassInjectedEventQueue.empty()) {
+ // flush any stale events from the injected event queue
+ mHalBypassInjectedEventQueue.pop();
+ }
+ mInHalBypassMode = true;
+ }
+ return OK;
+ } else {
+ if (mInHalBypassMode) {
+ // We are transitioning out of HAL Bypass mode. We need to notify the reader thread
+ // (specifically getHalBypassInjectedEvents()) of this change in state so that it is not
+ // stuck waiting on more injected events to come and therefore preventing events coming
+ // from the HAL from being read.
+ std::lock_guard _l(mHalBypassLock);
+ mInHalBypassMode = false;
+ mHalBypassCV.notify_one();
+ }
+ }
return mHalWrapper->setOperationMode(static_cast<SensorService::Mode>(mode));
}
@@ -873,5 +905,24 @@ float SensorDevice::getResolutionForSensor(int sensorHandle) {
return 0;
}
+ssize_t SensorDevice::getHalBypassInjectedEvents(sensors_event_t* buffer,
+ size_t maxNumEventsToRead) {
+ std::unique_lock _l(mHalBypassLock);
+ if (mHalBypassInjectedEventQueue.empty()) {
+ // if the injected event queue is empty, block and wait till there are events to process
+ // or if we are no longer in HAL Bypass mode so that this method is not called in a tight
+ // loop. Otherwise, continue copying the injected events into the supplied buffer.
+ mHalBypassCV.wait(_l, [this] {
+ return (!mHalBypassInjectedEventQueue.empty() || !mInHalBypassMode);
+ });
+ }
+ size_t eventsToRead = std::min(mHalBypassInjectedEventQueue.size(), maxNumEventsToRead);
+ for (size_t i = 0; i < eventsToRead; i++) {
+ buffer[i] = mHalBypassInjectedEventQueue.front();
+ mHalBypassInjectedEventQueue.pop();
+ }
+ return eventsToRead;
+}
+
// ---------------------------------------------------------------------------
}; // namespace android
diff --git a/services/sensorservice/SensorDevice.h b/services/sensorservice/SensorDevice.h
index 747a6b0926..f127c0f8cd 100644
--- a/services/sensorservice/SensorDevice.h
+++ b/services/sensorservice/SensorDevice.h
@@ -35,6 +35,9 @@
#include <utils/Timers.h>
#include <algorithm> //std::max std::min
+#include <condition_variable>
+#include <mutex>
+#include <queue>
#include <string>
#include <unordered_map>
#include <vector>
@@ -225,6 +228,12 @@ private:
float getResolutionForSensor(int sensorHandle);
bool mIsDirectReportSupported;
+
+ std::mutex mHalBypassLock;
+ std::condition_variable mHalBypassCV;
+ std::queue<sensors_event_t> mHalBypassInjectedEventQueue;
+ ssize_t getHalBypassInjectedEvents(sensors_event_t* buffer, size_t count);
+ bool mInHalBypassMode;
};
// ---------------------------------------------------------------------------
diff --git a/services/sensorservice/SensorDirectConnection.cpp b/services/sensorservice/SensorDirectConnection.cpp
index 2dd12e9446..555b80aed3 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 <android/util/ProtoOutputStream.h>
#include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
#include <hardware/sensors.h>
+#include "SensorDevice.h"
#define UNUSED(x) (void)(x)
@@ -28,10 +28,10 @@ using util::ProtoOutputStream;
SensorService::SensorDirectConnection::SensorDirectConnection(const sp<SensorService>& 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<struct native_handle*>(mMem.handle));
}
mDestroyed = true;
@@ -63,7 +63,7 @@ void SensorService::SensorDirectConnection::onFirstRef() {
void SensorService::SensorDirectConnection::dump(String8& result) const {
Mutex::Autolock _l(mConnectionLock);
result.appendFormat("\tPackage %s, HAL channel handle %d, total sensor activated %zu\n",
- String8(mOpPackageName).string(), getHalChannelHandle(), mActivated.size());
+ String8(mOpPackageName).c_str(), getHalChannelHandle(), mActivated.size());
for (auto &i : mActivated) {
result.appendFormat("\t\tSensor %#08x, rate %d\n", i.first, i.second);
}
@@ -79,7 +79,7 @@ void SensorService::SensorDirectConnection::dump(String8& result) const {
void SensorService::SensorDirectConnection::dump(ProtoOutputStream* proto) const {
using namespace service::SensorDirectConnectionProto;
Mutex::Autolock _l(mConnectionLock);
- proto->write(PACKAGE_NAME, std::string(String8(mOpPackageName).string()));
+ proto->write(PACKAGE_NAME, std::string(String8(mOpPackageName).c_str()));
proto->write(HAL_CHANNEL_HANDLE, getHalChannelHandle());
proto->write(NUM_SENSOR_ACTIVATED, int(mActivated.size()));
for (auto &i : mActivated) {
@@ -151,7 +151,7 @@ int32_t SensorService::SensorDirectConnection::configureChannel(int handle, int
return PERMISSION_DENIED;
}
- sp<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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<int, int>& existingConnections =
(!temporarilyStopped) ? mActivated : mActivatedBackup;
- SensorDevice& dev(SensorDevice::getInstance());
for (auto &i : existingConnections) {
int handle = i.first;
int rateLevel = i.second;
- sp<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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 d39a073f98..bfaf811330 100644
--- a/services/sensorservice/SensorDirectConnection.h
+++ b/services/sensorservice/SensorDirectConnection.h
@@ -39,7 +39,7 @@ class SensorService::SensorDirectConnection: public BnSensorEventConnection {
public:
SensorDirectConnection(const sp<SensorService>& 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<int, int> mActivated;
diff --git a/services/sensorservice/SensorEventConnection.cpp b/services/sensorservice/SensorEventConnection.cpp
index f06f9472bd..dc8657759c 100644
--- a/services/sensorservice/SensorEventConnection.cpp
+++ b/services/sensorservice/SensorEventConnection.cpp
@@ -23,6 +23,7 @@
#include <sensor/SensorEventQueue.h>
#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");
@@ -90,12 +90,12 @@ void SensorService::SensorEventConnection::dump(String8& result) {
result.append("NORMAL\n");
}
result.appendFormat("\t %s | WakeLockRefCount %d | uid %d | cache size %d | "
- "max cache size %d\n", mPackageName.string(), mWakeLockRefCount, mUid, mCacheSize,
+ "max cache size %d\n", mPackageName.c_str(), mWakeLockRefCount, mUid, mCacheSize,
mMaxCacheSize);
for (auto& it : mSensorInfo) {
const FlushInfo& flushInfo = it.second;
result.appendFormat("\t %s 0x%08x | status: %s | pending flush events %d \n",
- mService->getSensorName(it.first).string(),
+ mService->getSensorName(it.first).c_str(),
it.first,
flushInfo.mFirstFlushPending ? "First flush pending" :
"active",
@@ -124,14 +124,14 @@ 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);
} else {
proto->write(OPERATING_MODE, OP_MODE_NORMAL);
}
- proto->write(PACKAGE_NAME, std::string(mPackageName.string()));
+ proto->write(PACKAGE_NAME, std::string(mPackageName.c_str()));
proto->write(WAKE_LOCK_REF_COUNT, int32_t(mWakeLockRefCount));
proto->write(UID, int32_t(mUid));
proto->write(CACHE_SIZE, int32_t(mCacheSize));
@@ -160,7 +160,7 @@ void SensorService::SensorEventConnection::dump(util::ProtoOutputStream* proto)
bool SensorService::SensorEventConnection::addSensor(int32_t handle) {
Mutex::Autolock _l(mConnectionLock);
- sp<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> si = mService->getSensorInterfaceFromHandle(handle);
if (si != nullptr) {
const Sensor& s = si->getSensor();
if (mService->isSensorInCappedSet(s.getType())) {
@@ -846,13 +848,18 @@ int SensorService::SensorEventConnection::handleEvent(int fd, int events, void*
if (numBytesRead == sizeof(sensors_event_t)) {
if (!mDataInjectionMode) {
ALOGE("Data injected in normal mode, dropping event"
- "package=%s uid=%d", mPackageName.string(), mUid);
+ "package=%s uid=%d", mPackageName.c_str(), mUid);
// 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<SensorInterface> si =
+ std::shared_ptr<SensorInterface> 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<SensorInterface> si = mService->getSensorInterfaceFromHandle(it.first);
+ std::shared_ptr<SensorInterface> si = mService->getSensorInterfaceFromHandle(it.first);
if (si == nullptr) {
continue;
}
diff --git a/services/sensorservice/SensorFusion.cpp b/services/sensorservice/SensorFusion.cpp
index e27b52b23e..16088ca7ea 100644
--- a/services/sensorservice/SensorFusion.cpp
+++ b/services/sensorservice/SensorFusion.cpp
@@ -19,6 +19,7 @@
#include "SensorService.h"
#include <android/util/ProtoOutputStream.h>
+#include <cutils/properties.h>
#include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
namespace android {
@@ -41,17 +42,20 @@ SensorFusion::SensorFusion()
if (count > 0) {
for (size_t i=0 ; i<size_t(count) ; i++) {
- if (list[i].type == SENSOR_TYPE_ACCELEROMETER) {
- mAcc = Sensor(list + i);
- }
- if (list[i].type == SENSOR_TYPE_MAGNETIC_FIELD) {
- mMag = Sensor(list + i);
- }
- if (list[i].type == SENSOR_TYPE_GYROSCOPE) {
- mGyro = Sensor(list + i);
- }
- if (list[i].type == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
- uncalibratedGyro = Sensor(list + i);
+ // Only use non-wakeup sensors
+ if ((list[i].flags & SENSOR_FLAG_WAKE_UP) == 0) {
+ if (list[i].type == SENSOR_TYPE_ACCELEROMETER) {
+ mAcc = Sensor(list + i);
+ }
+ if (list[i].type == SENSOR_TYPE_MAGNETIC_FIELD) {
+ mMag = Sensor(list + i);
+ }
+ if (list[i].type == SENSOR_TYPE_GYROSCOPE) {
+ mGyro = Sensor(list + i);
+ }
+ if (list[i].type == SENSOR_TYPE_GYROSCOPE_UNCALIBRATED) {
+ uncalibratedGyro = Sensor(list + i);
+ }
}
}
@@ -60,10 +64,12 @@ SensorFusion::SensorFusion()
mGyro = uncalibratedGyro;
}
- // 200 Hz for gyro events is a good compromise between precision
- // and power/cpu usage.
- mEstimatedGyroRate = 200;
- mTargetDelayNs = 1000000000LL/mEstimatedGyroRate;
+ // Wearable devices will want to tune this parameter
+ // to 100 (Hz) in device.mk to save some power.
+ int32_t value = property_get_int32(
+ "sensors.aosp_low_power_sensor_fusion.maximum_rate", 200);
+ mEstimatedGyroRate = static_cast<float>(value);
+ mTargetDelayNs = 1000000000LL / mEstimatedGyroRate;
for (int i = 0; i<NUM_FUSION_MODE; ++i) {
mFusions[i].init(i);
diff --git a/services/sensorservice/SensorInterface.cpp b/services/sensorservice/SensorInterface.cpp
index 46f00e8329..e9c8335963 100644
--- a/services/sensorservice/SensorInterface.cpp
+++ b/services/sensorservice/SensorInterface.cpp
@@ -87,6 +87,45 @@ VirtualSensor::VirtualSensor() :
// ---------------------------------------------------------------------------
+RuntimeSensor::RuntimeSensor(const sensor_t& sensor, sp<SensorCallback> 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 57043592c5..c446d61f89 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<SensorCallback> 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<SensorCallback> 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 85ce0f0018..7e302060c5 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<SensorInterface> si, bool isForDebug,
+ bool isVirtual, int deviceId) {
std::lock_guard<std::mutex> 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<SensorInterface> SensorList::getInterface(int handle) const {
- return getOne<sp<SensorInterface>>(
- handle, [] (const Entry& e) -> sp<SensorInterface> {return e.si;}, nullptr);
+std::shared_ptr<SensorInterface> SensorList::getInterface(int handle) const {
+ return getOne<std::shared_ptr<SensorInterface>>(
+ handle, [] (const Entry& e) -> std::shared_ptr<SensorInterface> {return e.si;},
+ nullptr);
}
-
bool SensorList::isNewHandle(int handle) const {
std::lock_guard<std::mutex> lk(mLock);
return mUsedHandle.find(handle) == mUsedHandle.end();
@@ -79,7 +79,8 @@ const Vector<Sensor> SensorList::getUserSensors() const {
Vector<Sensor> 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<Sensor> SensorList::getUserDebugSensors() const {
Vector<Sensor> 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<Sensor> SensorList::getDynamicSensors() const {
Vector<Sensor> 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<Sensor> SensorList::getVirtualSensors() const {
Vector<Sensor> 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<Sensor> SensorList::getRuntimeSensors(int deviceId) const {
+ // lock in forEachEntry
+ Vector<Sensor> sensors;
+ forEachEntry(
+ [&sensors, deviceId] (const Entry& e) -> bool {
+ if (!e.isForDebug && e.deviceId == deviceId) {
sensors.add(e.si->getSensor());
}
return true;
@@ -134,12 +150,12 @@ std::string SensorList::dump() const {
"%#010x) %-25s | %-15s | ver: %" PRId32 " | type: %20s(%" PRId32
") | perm: %s | flags: 0x%08x\n",
s.getHandle(),
- s.getName().string(),
- s.getVendor().string(),
+ s.getName().c_str(),
+ s.getVendor().c_str(),
s.getVersion(),
- s.getStringType().string(),
+ s.getStringType().c_str(),
s.getType(),
- s.getRequiredPermission().size() ? s.getRequiredPermission().string() : "n/a",
+ s.getRequiredPermission().size() ? s.getRequiredPermission().c_str() : "n/a",
static_cast<int>(s.getFlags()));
result.append("\t");
@@ -208,7 +224,7 @@ std::string SensorList::dump() const {
}
return true;
});
- return std::string(result.string());
+ return std::string(result.c_str());
}
/**
@@ -225,13 +241,13 @@ void SensorList::dump(util::ProtoOutputStream* proto) const {
forEachSensor([&proto] (const Sensor& s) -> bool {
const uint64_t token = proto->start(SENSORS);
proto->write(HANDLE, s.getHandle());
- proto->write(NAME, std::string(s.getName().string()));
- proto->write(VENDOR, std::string(s.getVendor().string()));
+ proto->write(NAME, std::string(s.getName().c_str()));
+ proto->write(VENDOR, std::string(s.getVendor().c_str()));
proto->write(VERSION, s.getVersion());
- proto->write(STRING_TYPE, std::string(s.getStringType().string()));
+ proto->write(STRING_TYPE, std::string(s.getStringType().c_str()));
proto->write(TYPE, s.getType());
proto->write(REQUIRED_PERMISSION, std::string(s.getRequiredPermission().size() ?
- s.getRequiredPermission().string() : ""));
+ s.getRequiredPermission().c_str() : ""));
proto->write(FLAGS, int(s.getFlags()));
switch (s.getReportingMode()) {
case AREPORTING_MODE_CONTINUOUS:
diff --git a/services/sensorservice/SensorList.h b/services/sensorservice/SensorList.h
index 049ae7c855..ad5b21f370 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<SensorInterface> si;
+ std::shared_ptr<SensorInterface> 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<SensorInterface> 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<SensorInterface> 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<Sensor> getUserDebugSensors() const;
const Vector<Sensor> getDynamicSensors() const;
const Vector<Sensor> getVirtualSensors() const;
+ const Vector<Sensor> getRuntimeSensors(int deviceId) const;
String8 getName(int handle) const;
String8 getStringType(int handle) const;
- sp<SensorInterface> getInterface(int handle) const;
+ std::shared_ptr<SensorInterface> 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/SensorRegistrationInfo.h b/services/sensorservice/SensorRegistrationInfo.h
index a34a65bbdd..dc9e8215e7 100644
--- a/services/sensorservice/SensorRegistrationInfo.h
+++ b/services/sensorservice/SensorRegistrationInfo.h
@@ -93,7 +93,7 @@ public:
using namespace service::SensorRegistrationInfoProto;
proto->write(TIMESTAMP_SEC, int64_t(mRealtimeSec));
proto->write(SENSOR_HANDLE, mSensorHandle);
- proto->write(PACKAGE_NAME, std::string(mPackageName.string()));
+ proto->write(PACKAGE_NAME, std::string(mPackageName.c_str()));
proto->write(PID, int32_t(mPid));
proto->write(UID, int32_t(mUid));
proto->write(SAMPLING_RATE_US, mSamplingRateUs);
diff --git a/services/sensorservice/SensorService.cpp b/services/sensorservice/SensorService.cpp
index e0a4f034cb..85043c9038 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 <aidl/android/hardware/sensors/ISensors.h>
#include <android-base/strings.h>
#include <android/content/pm/IPackageManagerNative.h>
#include <android/util/ProtoOutputStream.h>
-#include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
#include <binder/ActivityManager.h>
#include <binder/BinderService.h>
#include <binder/IServiceManager.h>
@@ -25,6 +25,7 @@
#include <cutils/ashmem.h>
#include <cutils/misc.h>
#include <cutils/properties.h>
+#include <frameworks/base/core/proto/android/service/sensor_service.proto.h>
#include <hardware/sensors.h>
#include <hardware_legacy/power.h>
#include <log/log.h>
@@ -51,6 +52,7 @@
#include "SensorEventConnection.h"
#include "SensorRecord.h"
#include "SensorRegistrationInfo.h"
+#include "SensorServiceUtils.h"
#include <inttypes.h>
#include <math.h>
@@ -61,8 +63,11 @@
#include <sys/types.h>
#include <unistd.h>
+#include <condition_variable>
#include <ctime>
#include <future>
+#include <mutex>
+#include <string>
#include <private/android_filesystem_config.h>
@@ -86,7 +91,7 @@ bool SensorService::sHmacGlobalKeyIsValid = false;
std::map<String16, int> SensorService::sPackageTargetVersion;
Mutex SensorService::sPackageTargetVersionLock;
String16 SensorService::sSensorInterfaceDescriptorPrefix =
- String16("android.frameworks.sensorservice@");
+ String16("android.frameworks.sensorservice");
AppOpsManager SensorService::sAppOpsManager;
std::atomic_uint64_t SensorService::curProxCallbackSeq(0);
std::atomic_uint64_t SensorService::completedCallbackSeq(0);
@@ -102,6 +107,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<SensorService::RuntimeSensorCallback> 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<SensorService::RuntimeSensorCallback> mCallback;
+};
+
+} // namespace
+
static bool isAutomotive() {
sp<IServiceManager> serviceManager = defaultServiceManager();
if (serviceManager.get() == nullptr) {
@@ -137,6 +168,88 @@ SensorService::SensorService()
mMicSensorPrivacyPolicy = new MicrophonePrivacyPolicy(this);
}
+int SensorService::registerRuntimeSensor(
+ const sensor_t& sensor, int deviceId, sp<RuntimeSensorCallback> 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<RuntimeSensor::SensorCallback> runtimeSensorCallback(
+ new RuntimeSensorCallbackProxy(callback));
+ sensor_t runtimeSensor = sensor;
+ // force the handle to be consistent
+ runtimeSensor.handle = handle;
+ auto si = std::make_shared<RuntimeSensor>(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);
+ }
+
+ if (mRuntimeSensorHandler == nullptr) {
+ mRuntimeSensorEventBuffer =
+ new sensors_event_t[SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT];
+ mRuntimeSensorHandler = new RuntimeSensorHandler(this);
+ // Use PRIORITY_URGENT_DISPLAY as the injected sensor events should be dispatched as soon as
+ // possible, and also for consistency within the SensorService.
+ mRuntimeSensorHandler->run("RuntimeSensorHandler", PRIORITY_URGENT_DISPLAY);
+ }
+
+ 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<SensorEventConnection>& 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) {
+ std::unique_lock<std::mutex> lock(mRutimeSensorThreadMutex);
+ mRuntimeSensorEventQueue.push(event);
+ mRuntimeSensorsCv.notify_all();
+ return OK;
+}
+
bool SensorService::initializeHmacKey() {
int fd = open(SENSOR_SERVICE_HMAC_KEY_FILE, O_RDONLY|O_CLOEXEC);
if (fd != -1) {
@@ -238,11 +351,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<ProximitySensor>(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<HardwareSensor>(list[i]));
}
}
}
@@ -257,56 +372,63 @@ void SensorService::onFirstRef() {
// available in the HAL
bool needRotationVector =
(virtualSensorsNeeds & (1<<SENSOR_TYPE_ROTATION_VECTOR)) != 0;
-
- registerSensor(new RotationVectorSensor(), !needRotationVector, true);
- registerSensor(new OrientationSensor(), !needRotationVector, true);
+ registerVirtualSensor(std::make_shared<RotationVectorSensor>(),
+ /* isDebug= */ !needRotationVector);
+ registerVirtualSensor(std::make_shared<OrientationSensor>(),
+ /* 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<CorrectedGyroSensor>(list, count),
+ /* isDebug= */ true);
+ registerVirtualSensor(std::make_shared<GyroDriftSensor>(), /* isDebug= */ true);
}
if (hasAccel && (hasGyro || hasGyroUncalibrated)) {
bool needGravitySensor = (virtualSensorsNeeds & (1<<SENSOR_TYPE_GRAVITY)) != 0;
- registerSensor(new GravitySensor(list, count), !needGravitySensor, true);
+ registerVirtualSensor(std::make_shared<GravitySensor>(list, count),
+ /* isDebug= */ !needGravitySensor);
bool needLinearAcceleration =
(virtualSensorsNeeds & (1<<SENSOR_TYPE_LINEAR_ACCELERATION)) != 0;
- registerSensor(new LinearAccelerationSensor(list, count),
- !needLinearAcceleration, true);
+ registerVirtualSensor(std::make_shared<LinearAccelerationSensor>(list, count),
+ /* isDebug= */ !needLinearAcceleration);
bool needGameRotationVector =
(virtualSensorsNeeds & (1<<SENSOR_TYPE_GAME_ROTATION_VECTOR)) != 0;
- registerSensor(new GameRotationVectorSensor(), !needGameRotationVector, true);
+ registerVirtualSensor(std::make_shared<GameRotationVectorSensor>(),
+ /* isDebug= */ !needGameRotationVector);
}
if (hasAccel && hasMag) {
bool needGeoMagRotationVector =
(virtualSensorsNeeds & (1<<SENSOR_TYPE_GEOMAGNETIC_ROTATION_VECTOR)) != 0;
- registerSensor(new GeoMagRotationVectorSensor(), !needGeoMagRotationVector, true);
+ registerVirtualSensor(std::make_shared<GeoMagRotationVectorSensor>(),
+ /* isDebug= */ !needGeoMagRotationVector);
}
if (isAutomotive()) {
if (hasAccel) {
- registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_ACCELEROMETER),
- /*isDebug=*/false, /*isVirtual=*/true);
+ registerVirtualSensor(
+ std::make_shared<LimitedAxesImuSensor>(
+ list, count, SENSOR_TYPE_ACCELEROMETER));
}
if (hasGyro) {
- registerSensor(new LimitedAxesImuSensor(list, count, SENSOR_TYPE_GYROSCOPE),
- /*isDebug=*/false, /*isVirtual=*/true);
+ registerVirtualSensor(
+ std::make_shared<LimitedAxesImuSensor>(
+ list, count, SENSOR_TYPE_GYROSCOPE));
}
if (hasAccelUncalibrated) {
- registerSensor(new LimitedAxesImuSensor(list, count,
- SENSOR_TYPE_ACCELEROMETER_UNCALIBRATED),
- /*isDebug=*/false, /*isVirtual=*/true);
+ registerVirtualSensor(
+ std::make_shared<LimitedAxesImuSensor>(
+ 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<LimitedAxesImuSensor>(
+ list, count, SENSOR_TYPE_GYROSCOPE_UNCALIBRATED));
}
}
@@ -349,6 +471,7 @@ void SensorService::onFirstRef() {
const size_t minBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;
mSensorEventBuffer = new sensors_event_t[minBufferSize];
mSensorEventScratch = new sensors_event_t[minBufferSize];
+ mRuntimeSensorEventBuffer = nullptr;
mMapFlushEventsToConnections = new wp<const SensorEventConnection> [minBufferSize];
mCurrentOperatingMode = NORMAL;
@@ -407,19 +530,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<SensorInterface> 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<SensorInterface> s, bool isDebug) {
+ return registerSensor(std::move(s), isDebug);
}
bool SensorService::unregisterDynamicSensorLocked(int handle) {
@@ -433,8 +558,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<SensorInterface> s, bool isDebug) {
+ return registerSensor(std::move(s), isDebug, true);
}
SensorService::~SensorService() {
@@ -457,55 +582,22 @@ status_t SensorService::dump(int fd, const Vector<String16>& 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.setTo(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.setTo(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,15 +621,15 @@ status_t SensorService::dump(int fd, const Vector<String16>& args) {
result.append("Recent Sensor events:\n");
for (auto&& i : mRecentEvent) {
- sp<SensorInterface> s = mSensors.getInterface(i.first);
- if (!i.second->isEmpty()) {
- if (privileged || s->getSensor().getRequiredPermission().isEmpty()) {
+ std::shared_ptr<SensorInterface> s = getSensorInterfaceFromHandle(i.first);
+ if (!i.second->isEmpty() && s != nullptr) {
+ if (privileged || s->getSensor().getRequiredPermission().empty()) {
i.second->setFormat("normal");
} else {
i.second->setFormat("mask_data");
}
// if there is events and sensor does not need special permission.
- result.appendFormat("%s: ", s->getSensor().getName().string());
+ result.appendFormat("%s: ", s->getSensor().getName().c_str());
result.append(i.second->dump().c_str());
}
}
@@ -548,7 +640,7 @@ status_t SensorService::dump(int fd, const Vector<String16>& args) {
int handle = mActiveSensors.keyAt(i);
if (dev.isSensorActive(handle)) {
result.appendFormat("%s (handle=0x%08x, connections=%zu)\n",
- getSensorName(handle).string(),
+ getSensorName(handle).c_str(),
handle,
mActiveSensors.valueAt(i)->getNumConnections());
}
@@ -564,10 +656,22 @@ status_t SensorService::dump(int fd, const Vector<String16>& args) {
result.appendFormat(" NORMAL\n");
break;
case RESTRICTED:
- result.appendFormat(" RESTRICTED : %s\n", mWhiteListedPackage.string());
+ result.appendFormat(" RESTRICTED : %s\n", mAllowListedPackage.c_str());
break;
case DATA_INJECTION:
- result.appendFormat(" DATA_INJECTION : %s\n", mWhiteListedPackage.string());
+ 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;
+ case HAL_BYPASS_REPLAY_DATA_INJECTION:
+ result.appendFormat(" HAL_BYPASS_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");
@@ -605,7 +709,7 @@ status_t SensorService::dump(int fd, const Vector<String16>& args) {
} while(startIndex != currentIndex);
}
}
- write(fd, result.string(), result.size());
+ write(fd, result.c_str(), result.size());
return NO_ERROR;
}
@@ -647,13 +751,13 @@ status_t SensorService::dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock
// Write SensorEventsProto
token = proto.start(SENSOR_EVENTS);
for (auto&& i : mRecentEvent) {
- sp<SensorInterface> s = mSensors.getInterface(i.first);
- if (!i.second->isEmpty()) {
- i.second->setFormat(privileged || s->getSensor().getRequiredPermission().isEmpty() ?
+ std::shared_ptr<SensorInterface> 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);
proto.write(service::SensorEventsProto::RecentEventsLog::NAME,
- std::string(s->getSensor().getName().string()));
+ std::string(s->getSensor().getName().c_str()));
i.second->dump(&proto);
proto.end(mToken);
}
@@ -667,7 +771,7 @@ status_t SensorService::dumpProtoLocked(int fd, ConnectionSafeAutolock* connLock
if (dev.isSensorActive(handle)) {
token = proto.start(ACTIVE_SENSORS);
proto.write(service::ActiveSensorProto::NAME,
- std::string(getSensorName(handle).string()));
+ std::string(getSensorName(handle).c_str()));
proto.write(service::ActiveSensorProto::HANDLE, handle);
proto.write(service::ActiveSensorProto::NUM_CONNECTIONS,
int(mActiveSensors.valueAt(i)->getNumConnections()));
@@ -685,11 +789,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.string()));
+ 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.string()));
+ proto.write(WHITELISTED_PACKAGE, std::string(mAllowListedPackage.c_str()));
break;
default:
proto.write(OPERATING_MODE, OP_MODE_UNKNOWN);
@@ -832,8 +936,8 @@ static status_t getUidForPackage(String16 packageName, int userId, /*inout*/uid_
PermissionController pc;
uid = pc.getPackageUid(packageName, 0);
if (uid <= 0) {
- ALOGE("Unknown package: '%s'", String8(packageName).string());
- dprintf(err, "Unknown package: '%s'\n", String8(packageName).string());
+ ALOGE("Unknown package: '%s'", String8(packageName).c_str());
+ dprintf(err, "Unknown package: '%s'\n", String8(packageName).c_str());
return BAD_VALUE;
}
@@ -858,7 +962,7 @@ status_t SensorService::handleSetUidState(Vector<String16>& args, int err) {
if (args[2] == String16("active")) {
active = true;
} else if ((args[2] != String16("idle"))) {
- ALOGE("Expected active or idle but got: '%s'", String8(args[2]).string());
+ ALOGE("Expected active or idle but got: '%s'", String8(args[2]).c_str());
return BAD_VALUE;
}
@@ -938,7 +1042,7 @@ void SensorService::cleanupAutoDisabledSensorLocked(const sp<SensorEventConnecti
handle = buffer[i].meta_data.sensor;
}
if (connection->hasSensor(handle)) {
- sp<SensorInterface> si = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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 &&
@@ -1022,7 +1126,7 @@ bool SensorService::threadLoop() {
break;
}
sensors_event_t out;
- sp<SensorInterface> si = mSensors.getInterface(handle);
+ std::shared_ptr<SensorInterface> si = getSensorInterfaceFromHandle(handle);
if (si == nullptr) {
ALOGE("handle %d is not an valid virtual sensor", handle);
continue;
@@ -1038,7 +1142,6 @@ bool SensorService::threadLoop() {
// record the last synthesized values
recordLastValueLocked(&mSensorEventBuffer[count], k);
count += k;
- // sort the buffer by time-stamps
sortEventBuffer(mSensorEventBuffer, count);
}
}
@@ -1092,12 +1195,12 @@ bool SensorService::threadLoop() {
// force the handle to be consistent
s.handle = handle;
- SensorInterface *si = new HardwareSensor(s, uuid);
+ auto si = std::make_shared<HardwareSensor>(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);
}
@@ -1122,7 +1225,7 @@ bool SensorService::threadLoop() {
bool needsWakeLock = false;
for (const sp<SensorEventConnection>& connection : activeConnections) {
connection->sendEvents(mSensorEventBuffer, count, mSensorEventScratch,
- mMapFlushEventsToConnections);
+ mMapFlushEventsToConnections);
needsWakeLock |= connection->needsWakeLock();
// If the connection has one-shot sensors, it may be cleaned up after first trigger.
// Early check for one-shot sensors.
@@ -1141,6 +1244,46 @@ bool SensorService::threadLoop() {
return false;
}
+void SensorService::processRuntimeSensorEvents() {
+ size_t count = 0;
+ const size_t maxBufferSize = SensorEventQueue::MAX_RECEIVE_BUFFER_EVENT_COUNT;
+
+ {
+ std::unique_lock<std::mutex> lock(mRutimeSensorThreadMutex);
+
+ if (mRuntimeSensorEventQueue.empty()) {
+ mRuntimeSensorsCv.wait(lock, [this] { return !mRuntimeSensorEventQueue.empty(); });
+ }
+
+ // Pop the events from the queue into the buffer until it's empty or the buffer is full.
+ while (!mRuntimeSensorEventQueue.empty()) {
+ if (count >= maxBufferSize) {
+ ALOGE("buffer too small to hold all events: count=%zd, size=%zu", count,
+ maxBufferSize);
+ break;
+ }
+ mRuntimeSensorEventBuffer[count] = mRuntimeSensorEventQueue.front();
+ mRuntimeSensorEventQueue.pop();
+ count++;
+ }
+ }
+
+ if (count) {
+ ConnectionSafeAutolock connLock = mConnectionHolder.lock(mLock);
+
+ recordLastValueLocked(mRuntimeSensorEventBuffer, count);
+ sortEventBuffer(mRuntimeSensorEventBuffer, count);
+
+ for (const sp<SensorEventConnection>& connection : connLock.getActiveConnections()) {
+ connection->sendEvents(mRuntimeSensorEventBuffer, count, /* scratch= */ nullptr,
+ /* mapFlushEventsToConnections= */ nullptr);
+ if (connection->hasOneShotSensors()) {
+ cleanupAutoDisabledSensorLocked(connection, mRuntimeSensorEventBuffer, count);
+ }
+ }
+ }
+}
+
sp<Looper> SensorService::getLooper() const {
return mLooper;
}
@@ -1188,6 +1331,14 @@ bool SensorService::SensorEventAckReceiver::threadLoop() {
return false;
}
+bool SensorService::RuntimeSensorHandler::threadLoop() {
+ ALOGD("new thread RuntimeSensorHandler");
+ do {
+ mService->processRuntimeSensorEvents();
+ } while (!Thread::exitPending());
+ return false;
+}
+
void SensorService::recordLastValueLocked(
const sensors_event_t* buffer, size_t count) {
for (size_t i = 0; i < count; i++) {
@@ -1224,7 +1375,7 @@ String8 SensorService::getSensorStringType(int handle) const {
}
bool SensorService::isVirtualSensor(int handle) const {
- sp<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
return sensor != nullptr && sensor->isVirtual();
}
@@ -1233,7 +1384,7 @@ bool SensorService::isWakeUpSensorEvent(const sensors_event_t& event) const {
if (event.type == SENSOR_TYPE_META_DATA) {
handle = event.meta_data.sensor;
}
- sp<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
return sensor != nullptr && sensor->getSensor().isWakeUpSensor();
}
@@ -1342,19 +1493,37 @@ Vector<Sensor> SensorService::getSensorList(const String16& opPackageName) {
return accessibleSensorList;
}
+void SensorService::addSensorIfAccessible(const String16& opPackageName, const Sensor& sensor,
+ Vector<Sensor>& 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<Sensor> SensorService::getDynamicSensorList(const String16& opPackageName) {
Vector<Sensor> 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().string(),
- sensor.getRequiredPermission().string(),
- sensor.getRequiredAppOp());
- }
+ addSensorIfAccessible(opPackageName, sensor, accessibleSensorList);
+ }
+ return true;
+ });
+ makeUuidsIntoIdsForSensorList(accessibleSensorList);
+ return accessibleSensorList;
+}
+
+Vector<Sensor> SensorService::getRuntimeSensorList(const String16& opPackageName, int deviceId) {
+ Vector<Sensor> 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 +1533,9 @@ Vector<Sensor> SensorService::getDynamicSensorList(const String16& opPackageName
sp<ISensorEventConnection> 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 4 modes supported for a SensorEventConnection ... NORMAL, DATA_INJECTION,
+ // REPLAY_DATA_INJECTION and HAL_BYPASS_REPLAY_DATA_INJECTION
+ if (requestedMode != NORMAL && !isInjectionMode(requestedMode)) {
return nullptr;
}
resetTargetSdkVersionCache(opPackageName);
@@ -1375,7 +1545,7 @@ sp<ISensorEventConnection> 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 +1556,9 @@ sp<ISensorEventConnection> SensorService::createSensorEventConnection(const Stri
String16 connOpPackageName =
(opPackageName == String16("")) ? String16(connPackageName) : opPackageName;
sp<SensorEventConnection> result(new SensorEventConnection(this, uid, connPackageName,
- requestedMode == DATA_INJECTION, connOpPackageName, attributionTag));
- if (requestedMode == DATA_INJECTION) {
+ isInjectionMode(requestedMode),
+ connOpPackageName, attributionTag));
+ if (isInjectionMode(requestedMode)) {
mConnectionHolder.addEventConnectionIfNotPresent(result);
// Add the associated file descriptor to the Looper for polling whenever there is data to
// be injected.
@@ -1398,11 +1569,26 @@ sp<ISensorEventConnection> SensorService::createSensorEventConnection(const Stri
int SensorService::isDataInjectionEnabled() {
Mutex::Autolock _l(mLock);
- return (mCurrentOperatingMode == DATA_INJECTION);
+ return mCurrentOperatingMode == DATA_INJECTION;
+}
+
+int SensorService::isReplayDataInjectionEnabled() {
+ Mutex::Autolock _l(mLock);
+ return mCurrentOperatingMode == REPLAY_DATA_INJECTION;
+}
+
+int SensorService::isHalBypassReplayDataInjectionEnabled() {
+ Mutex::Autolock _l(mLock);
+ return mCurrentOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION;
+}
+
+bool SensorService::isInjectionMode(int mode) {
+ return (mode == DATA_INJECTION || mode == REPLAY_DATA_INJECTION ||
+ mode == HAL_BYPASS_REPLAY_DATA_INJECTION);
}
sp<ISensorEventConnection> 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 +1661,32 @@ sp<ISensorEventConnection> SensorService::createSensorDirectConnection(
if (!clone) {
return nullptr;
}
+ native_handle_set_fdsan_tag(clone);
sp<SensorDirectConnection> 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 +1696,24 @@ sp<ISensorEventConnection> 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<float> &floats, const Vector<int32_t> &ints) {
@@ -1614,7 +1830,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<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
if (sensor != nullptr) {
sensor->activate(c, false);
} else {
@@ -1653,8 +1869,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 +1954,38 @@ status_t SensorService::removeProximityActiveListener(
return NAME_NOT_FOUND;
}
-sp<SensorInterface> SensorService::getSensorInterfaceFromHandle(int handle) const {
+std::shared_ptr<SensorInterface> 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<SensorEventConnection>& connection,
int handle, nsecs_t samplingPeriodNs, nsecs_t maxBatchReportLatencyNs, int reservedFlags,
const String16& opPackageName) {
if (mInitCheck != NO_ERROR)
return mInitCheck;
- sp<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> 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 +2123,7 @@ status_t SensorService::disable(const sp<SensorEventConnection>& connection, int
Mutex::Autolock _l(mLock);
status_t err = cleanupWithoutDisableLocked(connection, handle);
if (err == NO_ERROR) {
- sp<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
err = sensor != nullptr ? sensor->activate(connection.get(), false) : status_t(BAD_VALUE);
}
@@ -1930,7 +2169,7 @@ status_t SensorService::setEventRate(const sp<SensorEventConnection>& connection
if (mInitCheck != NO_ERROR)
return mInitCheck;
- sp<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
if (sensor == nullptr ||
!canAccessSensor(sensor->getSensor(), "Tried configuring", opPackageName)) {
return BAD_VALUE;
@@ -1956,7 +2195,7 @@ status_t SensorService::flushSensor(const sp<SensorEventConnection>& 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<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
+ std::shared_ptr<SensorInterface> sensor = getSensorInterfaceFromHandle(handle);
if (sensor == nullptr) {
continue;
}
@@ -1996,10 +2235,10 @@ bool SensorService::canAccessSensor(const Sensor& sensor, const char* operation,
!isAudioServerOrSystemServerUid(IPCThreadState::self()->getCallingUid())) {
if (!mHtRestricted) {
ALOGI("Permitting access to HT sensor type outside system (%s)",
- String8(opPackageName).string());
+ String8(opPackageName).c_str());
} else {
- ALOGW("%s %s a sensor (%s) as a non-system client", String8(opPackageName).string(),
- operation, sensor.getName().string());
+ ALOGW("%s %s a sensor (%s) as a non-system client", String8(opPackageName).c_str(),
+ operation, sensor.getName().c_str());
return false;
}
}
@@ -2032,8 +2271,8 @@ bool SensorService::canAccessSensor(const Sensor& sensor, const char* operation,
}
if (!canAccess) {
- ALOGE("%s %s a sensor (%s) without holding %s", String8(opPackageName).string(),
- operation, sensor.getName().string(), sensor.getRequiredPermission().string());
+ ALOGE("%s %s a sensor (%s) without holding %s", String8(opPackageName).c_str(),
+ operation, sensor.getName().c_str(), sensor.getRequiredPermission().c_str());
}
return canAccess;
@@ -2056,10 +2295,12 @@ bool SensorService::hasPermissionForSensor(const Sensor& sensor) {
}
int SensorService::getTargetSdkVersion(const String16& opPackageName) {
- // Don't query the SDK version for the ISensorManager descriptor as it doesn't have one. This
- // descriptor tends to be used for VNDK clients, but can technically be set by anyone so don't
- // give it elevated privileges.
- if (opPackageName.startsWith(sSensorInterfaceDescriptorPrefix)) {
+ // Don't query the SDK version for the ISensorManager descriptor as it
+ // doesn't have one. This descriptor tends to be used for VNDK clients, but
+ // can technically be set by anyone so don't give it elevated privileges.
+ bool isVNDK = opPackageName.startsWith(sSensorInterfaceDescriptorPrefix) &&
+ opPackageName.contains(String16("@"));
+ if (isVNDK) {
return -1;
}
@@ -2094,6 +2335,109 @@ 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;
+ }
+ if (inputString == std::string("hal_bypass_replay_data_injection")) {
+ *targetModeOut = HAL_BYPASS_REPLAY_DATA_INJECTION;
+ return true;
+ }
+ return false;
+}
+
+status_t SensorService::changeOperatingMode(const Vector<String16>& 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 ||
+ mCurrentOperatingMode == HAL_BYPASS_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 HAL_BYPASS_REPLAY_DATA_INJECTION:
+ FALLTHROUGH_INTENDED;
+ case REPLAY_DATA_INJECTION:
+ if (SensorServiceUtil::isUserBuild()) {
+ return INVALID_OPERATION;
+ }
+ FALLTHROUGH_INTENDED;
+ case DATA_INJECTION:
+ if (mCurrentOperatingMode == NORMAL) {
+ dev.disableAllSensors();
+ status_t err = NO_ERROR;
+ if (targetOperatingMode == HAL_BYPASS_REPLAY_DATA_INJECTION) {
+ // Set SensorDevice to HAL_BYPASS_REPLAY_DATA_INJECTION_MODE. This value is not
+ // injected into the HAL, nor will any events be injected into the HAL
+ err = dev.setMode(HAL_BYPASS_REPLAY_DATA_INJECTION);
+ } else {
+ // Otherwise 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.
+ 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 +2467,14 @@ void SensorService::sendEventsFromCache(const sp<SensorEventConnection>& connect
}
}
-bool SensorService::isWhiteListedPackage(const String8& packageName) {
- return (packageName.contains(mWhiteListedPackage.string()));
+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 4ba3c51985..b643f6b382 100644
--- a/services/sensorservice/SensorService.h
+++ b/services/sensorservice/SensorService.h
@@ -42,6 +42,9 @@
#include <stdint.h>
#include <sys/types.h>
+#include <condition_variable>
+#include <mutex>
+#include <queue>
#include <unordered_map>
#include <unordered_set>
#include <vector>
@@ -60,7 +63,7 @@
// For older HALs which don't support batching, use a smaller socket buffer size.
#define SOCKET_BUFFER_SIZE_NON_BATCHED (4 * 1024)
-#define SENSOR_REGISTRATIONS_BUF_SIZE 200
+#define SENSOR_REGISTRATIONS_BUF_SIZE 500
// Apps that targets S+ and do not have HIGH_SAMPLING_RATE_SENSORS permission will be capped
// at 200 Hz. The cap also applies to all requests when the mic toggle is flipped to on, regardless
@@ -101,8 +104,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 +117,19 @@ 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,
+ // Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+ // buffer in SensorDevice and played back to SensorService when SensorDevice::poll() is
+ // called. This is useful for playing back sensor data on the platform without relying on
+ // the HAL to support data injection.
+ HAL_BYPASS_REPLAY_DATA_INJECTION = 4,
// State Transitions supported.
- // RESTRICTED <--- NORMAL ---> DATA_INJECTION
+ // RESTRICTED <--- NORMAL ---> DATA_INJECTION/REPLAY_DATA_INJECTION
// ---> <---
// Shell commands to switch modes in SensorService.
@@ -143,6 +154,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 +193,14 @@ public:
status_t addProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
status_t removeProximityActiveListener(const sp<ProximityActiveListener>& callback) ANDROID_API;
+ int registerRuntimeSensor(const sensor_t& sensor, int deviceId,
+ sp<RuntimeSensorCallback> 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);
@@ -183,6 +215,7 @@ private:
class SensorEventAckReceiver;
class SensorRecord;
class SensorRegistrationInfo;
+ class RuntimeSensorHandler;
// Promoting a SensorEventConnection or SensorDirectConnection from wp to sp must be done with
// mLock held, but destroying that sp must be done unlocked to avoid a race condition that
@@ -239,6 +272,14 @@ private:
SortedVector< wp<SensorDirectConnection> > mDirectConnections;
};
+ class RuntimeSensorHandler : public Thread {
+ sp<SensorService> const mService;
+ public:
+ virtual bool threadLoop();
+ explicit RuntimeSensorHandler(const sp<SensorService>& service) : mService(service) {
+ }
+ };
+
// If accessing a sensor we need to make sure the UID has access to it. If
// the app UID is idle then it cannot access sensors and gets no trigger
// events, no on-change events, flush event behavior does not change, and
@@ -263,7 +304,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);
@@ -343,15 +384,21 @@ private:
// Thread interface
virtual bool threadLoop();
+ void processRuntimeSensorEvents();
+
// ISensorServer interface
virtual Vector<Sensor> getSensorList(const String16& opPackageName);
virtual Vector<Sensor> getDynamicSensorList(const String16& opPackageName);
+ virtual Vector<Sensor> getRuntimeSensorList(const String16& opPackageName, int deviceId);
virtual sp<ISensorEventConnection> createSensorEventConnection(
const String8& packageName,
int requestedMode, const String16& opPackageName, const String16& attributionTag);
virtual int isDataInjectionEnabled();
+ virtual int isReplayDataInjectionEnabled();
+ virtual int isHalBypassReplayDataInjectionEnabled();
virtual sp<ISensorEventConnection> 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<float> &floats, const Vector<int32_t> &ints);
virtual status_t dump(int fd, const Vector<String16>& args);
@@ -360,14 +407,15 @@ private:
String8 getSensorName(int handle) const;
String8 getSensorStringType(int handle) const;
bool isVirtualSensor(int handle) const;
- sp<SensorInterface> getSensorInterfaceFromHandle(int handle) const;
+ std::shared_ptr<SensorInterface> 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<SensorInterface> sensor, bool isDebug = false,
+ bool isVirtual = false, int deviceId = RuntimeSensor::DEFAULT_DEVICE_ID);
+ bool registerVirtualSensor(std::shared_ptr<SensorInterface> sensor, bool isDebug = false);
+ bool registerDynamicSensorLocked(std::shared_ptr<SensorInterface> sensor, bool isDebug = false);
bool unregisterDynamicSensorLocked(int handle);
status_t cleanupWithoutDisable(const sp<SensorEventConnection>& connection, int handle);
status_t cleanupWithoutDisableLocked(const sp<SensorEventConnection>& connection, int handle);
@@ -375,9 +423,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<Sensor>& 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<String16>& 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 +456,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
@@ -461,6 +514,8 @@ private:
// Removes the capped rate on active direct connections (when the mic toggle is flipped to off)
void uncapRates();
+ bool isInjectionMode(int mode);
+
static inline bool isAudioServerOrSystemServerUid(uid_t uid) {
return multiuser_get_app_id(uid) == AID_SYSTEM || uid == AID_AUDIOSERVER;
}
@@ -479,6 +534,10 @@ private:
uint32_t mSocketBufferSize;
sp<Looper> mLooper;
sp<SensorEventAckReceiver> mAckReceiver;
+ sp<RuntimeSensorHandler> mRuntimeSensorHandler;
+ // Mutex and CV used to notify the mRuntimeSensorHandler thread that there are new events.
+ std::mutex mRutimeSensorThreadMutex;
+ std::condition_variable mRuntimeSensorsCv;
// protected by mLock
mutable Mutex mLock;
@@ -486,12 +545,14 @@ private:
std::unordered_set<int> mActiveVirtualSensors;
SensorConnectionHolder mConnectionHolder;
bool mWakeLockAcquired;
- sensors_event_t *mSensorEventBuffer, *mSensorEventScratch;
+ sensors_event_t *mSensorEventBuffer, *mSensorEventScratch, *mRuntimeSensorEventBuffer;
// WARNING: these SensorEventConnection instances must not be promoted to sp, except via
// modification to add support for them in ConnectionSafeAutolock
wp<const SensorEventConnection> * mMapFlushEventsToConnections;
std::unordered_map<int, SensorServiceUtil::RecentEventLogger*> mRecentEvent;
Mode mCurrentOperatingMode;
+ std::queue<sensors_event_t> mRuntimeSensorEventQueue;
+ std::unordered_map</*deviceId*/int, sp<RuntimeSensorCallback>> 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 +562,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<SensorRegistrationInfo> mLastNSensorRegistrations;
diff --git a/services/sensorservice/SensorServiceUtils.cpp b/services/sensorservice/SensorServiceUtils.cpp
index 6bad962521..46b4b5bff6 100644
--- a/services/sensorservice/SensorServiceUtils.cpp
+++ b/services/sensorservice/SensorServiceUtils.cpp
@@ -16,6 +16,7 @@
#include "SensorServiceUtils.h"
+#include <android-base/properties.h>
#include <hardware/sensors.h>
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 49457cf287..a6e0d6b401 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 34d1de72f9..542fcaee8d 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 cab53c17f5..9ef4ca8d08 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<int>(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 9b0334443b..b6acc8a572 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,18 +188,27 @@ ndk::ScopedAStatus SensorManagerAidl::getSensorList(std::vector<SensorInfo>* _ai
}
::android::SensorManager& SensorManagerAidl::getInternalManager() {
- std::lock_guard<std::mutex> lock(mInternalManagerMutex);
- if (mInternalManager == nullptr) {
- mInternalManager = &::android::SensorManager::getInstanceForPackage(
- String16(ISensorManager::descriptor));
+ int32_t version;
+ ndk::ScopedAStatus status = getInterfaceVersion(&version);
+ if (!status.isOk()) {
+ LOG(ERROR) << "Failed to get interface version with error: "
+ << status.getDescription();
+ version = -1;
}
- return *mInternalManager;
+ String16 packageName = String16(ISensorManager::descriptor);
+ packageName += String16("@") + String16(std::to_string(version).c_str());
+ return ::android::SensorManager::getInstanceForPackage(packageName);
}
/* One global looper for all event queues created from this SensorManager. */
sp<Looper> SensorManagerAidl::getLooper() {
std::lock_guard<std::mutex> lock(mThreadMutex);
+ if (!mJavaVm) {
+ LOG(ERROR) << "No Java VM. This must be running in a test or fuzzer.";
+ return mLooper;
+ }
+
if (!mPollThread.joinable()) {
// if thread not initialized, start thread
mStopThread = false;
diff --git a/services/sensorservice/aidl/fuzzer/Android.bp b/services/sensorservice/aidl/fuzzer/Android.bp
index 0d6e476e70..f6f104ee88 100644
--- a/services/sensorservice/aidl/fuzzer/Android.bp
+++ b/services/sensorservice/aidl/fuzzer/Android.bp
@@ -11,14 +11,16 @@ cc_fuzz {
name: "libsensorserviceaidl_fuzzer",
defaults: [
"service_fuzzer_defaults",
+ "fuzzer_disable_leaks",
],
host_supported: true,
static_libs: [
"libsensorserviceaidl",
"libpermission",
+ "android.companion.virtual.virtualdevice_aidl-cpp",
"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",
@@ -45,7 +47,6 @@ cc_fuzz {
"unsigned-integer-overflow",
],
},
- address: true,
integer_overflow: true,
},
diff --git a/services/sensorservice/aidl/fuzzer/fuzzer.cpp b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
index 1b63d76953..ee8ceb354c 100644
--- a/services/sensorservice/aidl/fuzzer/fuzzer.cpp
+++ b/services/sensorservice/aidl/fuzzer/fuzzer.cpp
@@ -16,7 +16,7 @@
#include <fuzzbinder/libbinder_ndk_driver.h>
#include <fuzzer/FuzzedDataProvider.h>
-#include <ServiceManager.h>
+#include <fakeservicemanager/FakeServiceManager.h>
#include <android-base/logging.h>
#include <android/binder_interface_utils.h>
#include <fuzzbinder/random_binder.h>
@@ -29,7 +29,7 @@ using ndk::SharedRefBase;
[[clang::no_destroy]] static std::once_flag gSmOnce;
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- static android::sp<android::ServiceManager> fakeServiceManager = new android::ServiceManager();
+ static android::sp<android::FakeServiceManager> fakeServiceManager = new android::FakeServiceManager();
std::call_once(gSmOnce, [&] { setDefaultServiceManager(fakeServiceManager); });
fakeServiceManager->clear();
diff --git a/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h b/services/sensorservice/aidl/include/sensorserviceaidl/SensorManagerAidl.h
index c77ee880dd..83496f6012 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<Looper> getLooper();
- std::mutex mInternalManagerMutex;
- ::android::SensorManager* mInternalManager = nullptr; // does not own
sp<Looper> mLooper;
volatile bool mStopThread;
diff --git a/services/sensorservice/hidl/SensorManager.cpp b/services/sensorservice/hidl/SensorManager.cpp
index 938060063f..3d148e103a 100644
--- a/services/sensorservice/hidl/SensorManager.cpp
+++ b/services/sensorservice/hidl/SensorManager.cpp
@@ -60,6 +60,9 @@ SensorManager::~SensorManager() {
if (mPollThread.joinable()) {
mPollThread.join();
}
+
+ ::android::SensorManager::removeInstanceForPackage(
+ String16(ISensorManager::descriptor));
}
// Methods from ::android::frameworks::sensorservice::V1_0::ISensorManager follow.
@@ -193,12 +196,8 @@ sp<Looper> SensorManager::getLooper() {
}
::android::SensorManager& SensorManager::getInternalManager() {
- std::lock_guard<std::mutex> lock(mInternalManagerMutex);
- if (mInternalManager == nullptr) {
- mInternalManager = &::android::SensorManager::getInstanceForPackage(
- String16(ISensorManager::descriptor));
- }
- return *mInternalManager;
+ return ::android::SensorManager::getInstanceForPackage(
+ String16(ISensorManager::descriptor));
}
Return<void> SensorManager::createEventQueue(
diff --git a/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h b/services/sensorservice/hidl/include/sensorservicehidl/SensorManager.h
index 8d7a05b5e2..1b085acfca 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<Looper> getLooper();
- std::mutex mInternalManagerMutex;
- ::android::SensorManager* mInternalManager = nullptr; // does not own
sp<Looper> mLooper;
volatile bool mStopThread;
diff --git a/services/sensorservice/hidl/utils.cpp b/services/sensorservice/hidl/utils.cpp
index 5fa594d01d..d338d02675 100644
--- a/services/sensorservice/hidl/utils.cpp
+++ b/services/sensorservice/hidl/utils.cpp
@@ -32,8 +32,8 @@ SensorInfo convertSensor(const Sensor& src) {
SensorInfo dst;
const String8& name = src.getName();
const String8& vendor = src.getVendor();
- dst.name = hidl_string{name.string(), name.size()};
- dst.vendor = hidl_string{vendor.string(), vendor.size()};
+ dst.name = hidl_string{name.c_str(), name.size()};
+ dst.vendor = hidl_string{vendor.c_str(), vendor.size()};
dst.version = src.getVersion();
dst.sensorHandle = src.getHandle();
dst.type = static_cast<::android::hardware::sensors::V1_0::SensorType>(
diff --git a/services/sensorservice/tests/sensorservicetest.cpp b/services/sensorservice/tests/sensorservicetest.cpp
index caf7f03361..92956d6726 100644
--- a/services/sensorservice/tests/sensorservicetest.cpp
+++ b/services/sensorservice/tests/sensorservicetest.cpp
@@ -87,8 +87,19 @@ void testInvalidSharedMem_NoCrash(SensorManager &mgr) {
int ret = mgr.createDirectChannel(
kMemSize, ASENSOR_DIRECT_CHANNEL_TYPE_SHARED_MEMORY, resourceHandle);
- // Should print -22 (BAD_VALUE) and the device runtime shouldn't restart
+ // Should not succeed (ret != OK) 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() {
@@ -105,7 +116,7 @@ int main() {
Sensor const* accelerometer = mgr.getDefaultSensor(Sensor::TYPE_ACCELEROMETER);
printf("accelerometer=%p (%s)\n",
- accelerometer, accelerometer->getName().string());
+ accelerometer, accelerometer->getName().c_str());
sStartTime = systemTime();
@@ -130,7 +141,7 @@ int main() {
printf("ALOOPER_POLL_TIMEOUT\n");
break;
case ALOOPER_POLL_ERROR:
- printf("ALOOPER_POLL_TIMEOUT\n");
+ printf("ALOOPER_POLL_ERROR\n");
break;
default:
printf("ugh? poll returned %d\n", ret);
diff --git a/services/stats/Android.bp b/services/stats/Android.bp
index 7d358e1bd2..6b99627f5a 100644
--- a/services/stats/Android.bp
+++ b/services/stats/Android.bp
@@ -21,6 +21,7 @@ cc_library_shared {
"android.frameworks.stats@1.0",
"android.frameworks.stats-V2-ndk",
"libbinder_ndk",
+ "libexpresslog",
"libhidlbase",
"liblog",
"libstatslog",
diff --git a/services/stats/StatsAidl.cpp b/services/stats/StatsAidl.cpp
index 0f01507c9f..b22f903654 100644
--- a/services/stats/StatsAidl.cpp
+++ b/services/stats/StatsAidl.cpp
@@ -22,6 +22,7 @@
#include "StatsAidl.h"
+#include <Counter.h>
#include <log/log.h>
#include <stats_annotations.h>
#include <stats_event.h>
@@ -29,11 +30,18 @@
#include <unordered_map>
+namespace {
+ static const char* g_AtomErrorMetricName =
+ "statsd_errors.value_report_vendor_atom_errors_count";
+}
+
namespace aidl {
namespace android {
namespace frameworks {
namespace stats {
+using ::android::expresslog::Counter;
+
template <typename E>
constexpr typename std::underlying_type<E>::type to_underlying(E e) noexcept {
return static_cast<typename std::underlying_type<E>::type>(e);
@@ -86,12 +94,14 @@ bool write_field_annotations(AStatsEvent* event, const std::vector<Annotation>&
ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
if (vendorAtom.atomId < 100000 || vendorAtom.atomId >= 200000) {
ALOGE("Atom ID %ld is not a valid vendor atom ID", (long)vendorAtom.atomId);
+ Counter::logIncrement(g_AtomErrorMetricName);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-1, "Not a valid vendor atom ID");
}
if (vendorAtom.reverseDomainName.length() > 50) {
ALOGE("Vendor atom reverse domain name %s is too long.",
vendorAtom.reverseDomainName.c_str());
+ Counter::logIncrement(g_AtomErrorMetricName);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-1, "Vendor atom reverse domain name is too long");
}
@@ -100,8 +110,9 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
if (vendorAtom.atomAnnotations) {
if (!write_atom_annotations(event, *vendorAtom.atomAnnotations)) {
- ALOGE("Atom ID %ld has incompatible atom level annotation", (long)vendorAtom.atomId);
AStatsEvent_release(event);
+ ALOGE("Atom ID %ld has incompatible atom level annotation", (long)vendorAtom.atomId);
+ Counter::logIncrement(g_AtomErrorMetricName);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-1, "invalid atom annotation");
}
@@ -222,6 +233,7 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
default: {
AStatsEvent_release(event);
ALOGE("Atom ID %ld has invalid atomValue.getTag", (long)vendorAtom.atomId);
+ Counter::logIncrement(g_AtomErrorMetricName);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-1, "invalid atomValue.getTag");
break;
@@ -235,9 +247,10 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
VLOG("Atom ID %ld has %ld annotations for field #%ld", (long)vendorAtom.atomId,
(long)fieldAnnotations.size(), (long)atomValueIdx + 2);
if (!write_field_annotations(event, fieldAnnotations)) {
+ AStatsEvent_release(event);
ALOGE("Atom ID %ld has incompatible field level annotation for field #%ld",
(long)vendorAtom.atomId, (long)atomValueIdx + 2);
- AStatsEvent_release(event);
+ Counter::logIncrement(g_AtomErrorMetricName);
return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(
-1, "invalid atom field annotation");
}
@@ -249,6 +262,7 @@ ndk::ScopedAStatus StatsHal::reportVendorAtom(const VendorAtom& vendorAtom) {
AStatsEvent_release(event);
if (ret <= 0) {
ALOGE("Error writing Atom ID %ld. Result: %d", (long)vendorAtom.atomId, ret);
+ Counter::logIncrement(g_AtomErrorMetricName);
}
return ret <= 0 ? ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(ret,
"report atom failed")
diff --git a/services/surfaceflinger/Android.bp b/services/surfaceflinger/Android.bp
index cbb95f963c..dcef9a3775 100644
--- a/services/surfaceflinger/Android.bp
+++ b/services/surfaceflinger/Android.bp
@@ -7,6 +7,18 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
+aconfig_declarations {
+ name: "surfaceflinger_flags",
+ package: "com.android.graphics.surfaceflinger.flags",
+ container: "system",
+ srcs: ["surfaceflinger_flags.aconfig"],
+}
+
+cc_aconfig_library {
+ name: "libsurfaceflingerflags",
+ aconfig_declarations: "surfaceflinger_flags",
+}
+
cc_defaults {
name: "surfaceflinger_defaults",
cflags: [
@@ -18,14 +30,18 @@ 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",
+ "android.hardware.power-ndk_shared",
+ "librenderengine_deps",
+ "libtimestats_deps",
"surfaceflinger_defaults",
- "skia_renderengine_deps",
],
cflags: [
"-DLOG_TAG=\"SurfaceFlinger\"",
@@ -45,10 +61,6 @@ 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",
"libbase",
"libbinder",
"libbinder_ndk",
@@ -59,13 +71,12 @@ cc_defaults {
"libGLESv2",
"libgui",
"libhidlbase",
- "liblayers_proto",
"liblog",
"libnativewindow",
+ "libpowermanager",
"libprocessgroup",
"libprotobuf-cpp-lite",
"libsync",
- "libtimestats",
"libui",
"libinput",
"libutils",
@@ -77,13 +88,16 @@ cc_defaults {
"libcompositionengine",
"libframetimeline",
"libgui_aidl_static",
+ "liblayers_proto",
"libperfetto_client_experimental",
"librenderengine",
"libscheduler",
"libserviceutils",
"libshaders",
+ "libsurfaceflinger_common",
+ "libtimestats",
"libtonemap",
- "libtrace_proto",
+ "libsurfaceflingerflags",
],
header_libs: [
"android.hardware.graphics.composer@2.1-command-buffer",
@@ -96,6 +110,7 @@ cc_defaults {
"libcompositionengine",
"librenderengine",
"libserviceutils",
+ "libtimestats",
],
export_shared_lib_headers: [
"android.hardware.graphics.allocator@2.0",
@@ -105,10 +120,8 @@ 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",
],
// TODO (marissaw): this library is not used by surfaceflinger. This is here so
// the library compiled in a way that is accessible to system partition when running
@@ -141,64 +154,69 @@ 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",
- "FlagManager.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",
"FpsReporter.cpp",
"FrameTracer/FrameTracer.cpp",
"FrameTracker.cpp",
"HdrLayerInfoReporter.cpp",
+ "HdrSdrRatioOverlay.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/SmallAreaDetectionAllowMappings.cpp",
"Scheduler/VSyncDispatchTimerQueue.cpp",
"Scheduler/VSyncPredictor.cpp",
"Scheduler/VSyncReactor.cpp",
"Scheduler/VsyncConfiguration.cpp",
"Scheduler/VsyncModulator.cpp",
"Scheduler/VsyncSchedule.cpp",
+ "ScreenCaptureOutput.cpp",
"StartPropertySetThread.cpp",
"SurfaceFlinger.cpp",
"SurfaceFlingerDefaultFactory.cpp",
- "SurfaceInterceptor.cpp",
+ "Tracing/LayerDataSource.cpp",
"Tracing/LayerTracing.cpp",
+ "Tracing/TransactionDataSource.cpp",
"Tracing/TransactionTracing.cpp",
"Tracing/TransactionProtoParser.cpp",
+ "Tracing/tools/LayerTraceGenerator.cpp",
"TransactionCallbackInvoker.cpp",
"TunnelModeEnabledReporter.cpp",
],
@@ -214,14 +232,12 @@ cc_defaults {
"-DLOG_TAG=\"SurfaceFlinger\"",
],
shared_libs: [
- "android.frameworks.displayservice@1.0",
"android.hardware.configstore-utils",
"android.hardware.configstore@1.0",
"android.hardware.graphics.allocator@2.0",
"android.hardware.graphics.allocator@3.0",
"libbinder",
"libcutils",
- "libdisplayservicehidl",
"libhidlbase",
"liblog",
"libprocessgroup",
@@ -229,8 +245,10 @@ cc_defaults {
"libutils",
],
static_libs: [
+ "android.frameworks.displayservice@1.0",
+ "libc++fs",
+ "libdisplayservicehidl",
"libserviceutils",
- "libtrace_proto",
],
}
diff --git a/services/surfaceflinger/BackgroundExecutor.cpp b/services/surfaceflinger/BackgroundExecutor.cpp
index a15de2b183..5a1ec6f501 100644
--- a/services/surfaceflinger/BackgroundExecutor.cpp
+++ b/services/surfaceflinger/BackgroundExecutor.cpp
@@ -20,6 +20,7 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <utils/Log.h>
+#include <mutex>
#include "BackgroundExecutor.h"
@@ -28,29 +29,19 @@ namespace android {
ANDROID_SINGLETON_STATIC_INSTANCE(BackgroundExecutor);
BackgroundExecutor::BackgroundExecutor() : Singleton<BackgroundExecutor>() {
+ // 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<Work*, 10> 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<std::mutex> lock{mutex};
+ cv.wait(lock, [&]() { return flushComplete; });
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/BackgroundExecutor.h b/services/surfaceflinger/BackgroundExecutor.h
index eeaf3bdd2e..66b7d7a1fc 100644
--- a/services/surfaceflinger/BackgroundExecutor.h
+++ b/services/surfaceflinger/BackgroundExecutor.h
@@ -16,15 +16,13 @@
#pragma once
-#include <Tracing/LocklessStack.h>
-#include <android-base/thread_annotations.h>
#include <ftl/small_vector.h>
#include <semaphore.h>
#include <utils/Singleton.h>
-#include <mutex>
-#include <queue>
#include <thread>
+#include "LocklessQueue.h"
+
namespace android {
// Executes tasks off the main thread.
@@ -34,24 +32,15 @@ public:
~BackgroundExecutor();
using Callbacks = ftl::SmallVector<std::function<void()>, 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<Work> mWorks;
+ LocklessQueue<Callbacks> mCallbacksQueue;
std::thread mThread;
};
diff --git a/services/surfaceflinger/BufferLayer.cpp b/services/surfaceflinger/BufferLayer.cpp
deleted file mode 100644
index d9c89cd821..0000000000
--- 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 <compositionengine/CompositionEngine.h>
-#include <compositionengine/LayerFECompositionState.h>
-#include <compositionengine/OutputLayer.h>
-#include <compositionengine/impl/OutputLayerCompositionState.h>
-#include <cutils/compiler.h>
-#include <cutils/native_handle.h>
-#include <cutils/properties.h>
-#include <gui/BufferItem.h>
-#include <gui/BufferQueue.h>
-#include <gui/GLConsumer.h>
-#include <gui/LayerDebugInfo.h>
-#include <gui/Surface.h>
-#include <renderengine/RenderEngine.h>
-#include <ui/DebugUtils.h>
-#include <utils/Errors.h>
-#include <utils/Log.h>
-#include <utils/NativeHandle.h>
-#include <utils/StopWatch.h>
-#include <utils/Trace.h>
-
-#include <cmath>
-#include <cstdlib>
-#include <mutex>
-#include <sstream>
-
-#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<compositionengine::LayerFE::LayerSettings> BufferLayer::prepareClientComposition(
- compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
- ATRACE_CALL();
-
- std::optional<compositionengine::LayerFE::LayerSettings> 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<Layer> 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<const float*>(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<const float*>(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<compositionengine::LayerFE> 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<FenceTime>& glDoneFence,
- const std::shared_ptr<FenceTime>& 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<FenceTime> 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<Fps> 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<FenceTime>(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<GraphicBuffer> 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<Layer>& clonedFrom) {
- Layer::setInitialValuesForClone(clonedFrom);
-
- sp<BufferLayer> bufferClonedFrom = static_cast<BufferLayer*>(clonedFrom.get());
- mPremultipliedAlpha = bufferClonedFrom->mPremultipliedAlpha;
- mPotentialCursor = bufferClonedFrom->mPotentialCursor;
- mProtectedByApp = bufferClonedFrom->mProtectedByApp;
-
- updateCloneBufferInfo();
-}
-
-void BufferLayer::updateCloneBufferInfo() {
- if (!isClone() || !isClonedFromAlive()) {
- return;
- }
-
- sp<BufferLayer> clonedFrom = static_cast<BufferLayer*>(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<Layer> tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf;
- SortedVector<wp<Layer>> tmpZOrderRelatives = mDrawingState.zOrderRelatives;
- wp<Layer> 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<renderengine::ExternalTexture>& 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 4c70eb58bf..0000000000
--- 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 <sys/types.h>
-#include <cstdint>
-#include <list>
-
-#include <gui/ISurfaceComposerClient.h>
-#include <gui/LayerState.h>
-#include <renderengine/Image.h>
-#include <renderengine/Mesh.h>
-#include <renderengine/Texture.h>
-#include <system/window.h> // For NATIVE_WINDOW_SCALING_MODE_FREEZE
-#include <ui/FrameStats.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/PixelFormat.h>
-#include <ui/Region.h>
-#include <utils/RefBase.h>
-#include <utils/String8.h>
-#include <utils/Timers.h>
-
-#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<compositionengine::LayerFE> 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<FenceTime>& glDoneFence,
- const std::shared_ptr<FenceTime>& 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<GraphicBuffer> getBuffer() const override;
- const std::shared_ptr<renderengine::ExternalTexture>& 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<FenceTime> mFenceTime;
- sp<Fence> 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<renderengine::ExternalTexture> mBuffer;
- uint64_t mFrameNumber;
- int mBufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
-
- bool mFrameLatencyNeeded{false};
- };
-
- BufferInfo mBufferInfo;
- virtual void gatherBufferInfo() = 0;
-
- std::optional<compositionengine::LayerFE::LayerSettings> 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<Layer>& 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<bool> 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<compositionengine::LayerFECompositionState> 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 7361a4fdef..0000000000
--- 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 <inttypes.h>
-
-#include <cutils/compiler.h>
-
-#include <hardware/hardware.h>
-
-#include <math/mat4.h>
-
-#include <gui/BufferItem.h>
-#include <gui/GLConsumer.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <private/gui/ComposerService.h>
-#include <renderengine/RenderEngine.h>
-#include <renderengine/impl/ExternalTexture.h>
-#include <utils/Log.h>
-#include <utils/String8.h>
-#include <utils/Trace.h>
-
-namespace android {
-
-// Macros for including the BufferLayerConsumer name in log messages
-#define BLC_LOGV(x, ...) ALOGV("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define BLC_LOGD(x, ...) ALOGD("[%s] " x, mName.string(), ##__VA_ARGS__)
-//#define BLC_LOGI(x, ...) ALOGI("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define BLC_LOGW(x, ...) ALOGW("[%s] " x, mName.string(), ##__VA_ARGS__)
-#define BLC_LOGE(x, ...) ALOGE("[%s] " x, mName.string(), ##__VA_ARGS__)
-
-static const mat4 mtxIdentity;
-
-BufferLayerConsumer::BufferLayerConsumer(const sp<IGraphicBufferConsumer>& 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<ContentsChangedListener>& 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>& 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<Fence> 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<std::mutex> 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<renderengine::ExternalTexture> nextTextureBuffer;
- {
- std::lock_guard<std::mutex> 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<ui::Dataspace>(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<renderengine::ExternalTexture> BufferLayerConsumer::getCurrentBuffer(
- int* outSlot, sp<Fence>* 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<Fence> BufferLayerConsumer::getCurrentFence() const {
- Mutex::Autolock lock(mMutex);
- return mCurrentFence;
-}
-
-std::shared_ptr<FenceTime> BufferLayerConsumer::getCurrentFenceTime() const {
- Mutex::Autolock lock(mMutex);
- return mCurrentFenceTime;
-}
-
-void BufferLayerConsumer::freeBufferLocked(int slotIndex) {
- BLC_LOGV("freeBufferLocked: slotIndex=%d", slotIndex);
- std::lock_guard<std::mutex> 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<ContentsChangedListener> 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<std::mutex> lock(mImagesMutex);
- const std::shared_ptr<renderengine::ExternalTexture>& 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<std::mutex> 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 23ad2a3f91..0000000000
--- 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 <android-base/thread_annotations.h>
-#include <gui/BufferQueueDefs.h>
-#include <gui/ConsumerBase.h>
-#include <gui/HdrMetadata.h>
-#include <renderengine/ExternalTexture.h>
-#include <ui/FenceTime.h>
-#include <ui/GraphicBuffer.h>
-#include <ui/GraphicTypes.h>
-#include <ui/Region.h>
-#include <utils/String8.h>
-#include <utils/Vector.h>
-#include <utils/threads.h>
-
-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<GraphicBuffer>& 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<IGraphicBufferConsumer>& 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<ContentsChangedListener>& 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>& fence);
-
- bool releasePendingBuffer();
-
- sp<Fence> 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<renderengine::ExternalTexture> getCurrentBuffer(
- int* outSlot = nullptr, sp<Fence>* 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<Fence> getCurrentFence() const;
-
- // getCurrentFence returns the FenceTime indicating when the current
- // buffer is ready to be read from.
- std::shared_ptr<FenceTime> 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> 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>& graphicBuffer, renderengine::RenderEngine& engine);
- virtual ~Image();
- const sp<GraphicBuffer>& graphicBuffer() { return mGraphicBuffer; }
-
- private:
- // mGraphicBuffer is the buffer that was used to create this image.
- sp<GraphicBuffer> 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<renderengine::ExternalTexture> 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<Fence> mCurrentFence;
-
- // The FenceTime wrapper around mCurrentFence.
- std::shared_ptr<FenceTime> 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<ContentsChangedListener> 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<renderengine::ExternalTexture>
- 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 bee4de32a6..0000000000
--- 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 <compositionengine/LayerFECompositionState.h>
-#include <gui/BufferQueueConsumer.h>
-#include <system/window.h>
-
-#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<FenceResult> futureFenceResult) {
- const sp<Fence> 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<FenceTime>(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<FenceTime>(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<FenceTime>(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<IGraphicBufferProducer> producer;
- sp<IGraphicBufferConsumer> 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<IGraphicBufferProducer> 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<uint32_t>(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<Layer> BufferQueueLayer::createClone() {
- LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
- args.textureName = mTextureName;
- sp<BufferQueueLayer> 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 e1c80d581d..0000000000
--- 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 <utils/String8.h>
-
-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<FenceResult>) 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<IGraphicBufferProducer> 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<Layer> 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<BufferLayerConsumer> mConsumer;
- sp<IGraphicBufferProducer> 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<frametimeline::SurfaceFrame> surfaceFrame)
- : item(item), surfaceFrame(surfaceFrame) {}
- BufferItem item;
- std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame;
- };
- std::vector<BufferData> mQueueItems;
- std::atomic<uint64_t> mLastFrameNumberReceived{0};
-
- // thread-safe
- std::atomic<int32_t> mQueuedFrames{0};
-
- sp<ContentsChangedListener> 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 3875f151cb..0000000000
--- a/services/surfaceflinger/BufferStateLayer.cpp
+++ /dev/null
@@ -1,1085 +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 <limits>
-
-#include <FrameTimeline/FrameTimeline.h>
-#include <compositionengine/LayerFECompositionState.h>
-#include <gui/BufferQueue.h>
-#include <private/gui/SyncFeatures.h>
-#include <renderengine/Image.h>
-#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<ITransactionCompletedListener>& listener,
- const sp<GraphicBuffer>& buffer, uint64_t framenumber,
- const sp<Fence>& 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<FenceResult> 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<CallbackHandle> 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<frametimeline::SurfaceFrame>& 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;
- jankData.reserve(mPendingJankClassifications.size());
- while (!mPendingJankClassifications.empty()
- && mPendingJankClassifications.front()->getJankType()) {
- std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame =
- mPendingJankClassifications.front();
- mPendingJankClassifications.pop_front();
- jankData.emplace_back(
- JankData(surfaceFrame->getToken(), surfaceFrame->getJankType().value()));
- }
-
- mFlinger->getTransactionCallbackInvoker().addCallbackHandles(
- mDrawingState.callbackHandles, jankData);
-
- sp<Fence> 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<FenceTime>& 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<float>(bufferWidth);
- float sy = destH / static_cast<float>(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<renderengine::ExternalTexture>& buffer,
- const BufferData& bufferData, nsecs_t postTime,
- nsecs_t desiredPresentTime, bool isAutoTimestamp,
- std::optional<nsecs_t> 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<FenceTime>(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<nsecs_t>(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) {
- 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<NativeHandle>& 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<sp<CallbackHandle>>& 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<int32_t>(bufWidth), static_cast<int32_t>(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<FenceTime>(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<sp<CallbackHandle>> 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<std::mutex> 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<ErasedRecipient>(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<FenceTime>(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<Layer> BufferStateLayer::createClone() {
- LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0, LayerMetadata());
- args.textureName = mTextureName;
- sp<BufferStateLayer> 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<int32_t>(s.buffer->getWidth());
- int32_t bufferHeight = static_cast<int32_t>(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<Layer> 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<int>(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 3f0dbe4039..0000000000
--- 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 <renderengine/Image.h>
-#include <renderengine/RenderEngine.h>
-#include <system/window.h>
-#include <utils/String8.h>
-
-#include <stack>
-
-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<FenceResult>) override;
-
- void releasePendingBuffer(nsecs_t dequeueReadyTime) override;
-
- void finalizeFrameEventHistory(const std::shared_ptr<FenceTime>& 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<renderengine::ExternalTexture>& /* buffer */,
- const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
- bool isAutoTimestamp, std::optional<nsecs_t> 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<NativeHandle>& sidebandStream) override;
- bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& 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<int32_t>* 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<frametimeline::SurfaceFrame>& 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<Fence>& 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<Layer> 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<nsecs_t, sp<Fence>> mCallbackHandleAcquireTimeOrFence = -1;
-
- std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> 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<int32_t> 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<uint64_t>{}(clientCacheId.id);
- }
- };
-
- std::mutex mMutex;
-
- std::unordered_map<client_cache_t, std::pair<int /*HwcCacheSlot*/, uint64_t /*counter*/>,
- CachedBufferHash>
- mCachedBuffers GUARDED_BY(mMutex);
- std::stack<int /*HwcCacheSlot*/> 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<HwcSlotGenerator> mHwcSlotGenerator;
-};
-
-} // namespace android
diff --git a/services/surfaceflinger/Client.cpp b/services/surfaceflinger/Client.cpp
index 6d7b732b36..6b4215e2f4 100644
--- a/services/surfaceflinger/Client.cpp
+++ b/services/surfaceflinger/Client.cpp
@@ -21,12 +21,19 @@
#include <private/android_filesystem_config.h>
+#include <gui/AidlStatusUtil.h>
+#include <gui/SchedulingPolicy.h>
+
#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 +49,77 @@ status_t Client::initCheck() const {
return NO_ERROR;
}
-void Client::attachLayer(const sp<IBinder>& handle, const sp<Layer>& layer)
-{
- Mutex::Autolock _l(mLock);
- mLayers.add(handle, layer);
+binder::Status Client::createSurface(const std::string& name, int32_t flags,
+ const sp<IBinder>& parent, const gui::LayerMetadata& metadata,
+ gui::CreateSurfaceResult* outResult) {
+ // We rely on createLayer to check permissions.
+ sp<IBinder> handle;
+ LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this), name.c_str(),
+ static_cast<uint32_t>(flags), std::move(metadata));
+ args.parentHandle = parent;
+ const status_t status = mFlinger->createLayer(args, *outResult);
+ return binderStatusFromStatusT(status);
}
-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<count ; i++) {
- if (mLayers.valueAt(i) == layer) {
- mLayers.removeItemsAt(i, 1);
- break;
- }
- }
-}
-sp<Layer> Client::getLayerUser(const sp<IBinder>& handle) const
-{
- Mutex::Autolock _l(mLock);
- sp<Layer> lbc;
- wp<Layer> layer(mLayers.valueFor(handle));
- if (layer != 0) {
- lbc = layer.promote();
- ALOGE_IF(lbc==0, "getLayerUser(name=%p) is dead", handle.get());
+binder::Status Client::clearLayerFrameStats(const sp<IBinder>& handle) {
+ status_t status;
+ sp<Layer> layer = LayerHandle::getLayer(handle);
+ if (layer == nullptr) {
+ status = NAME_NOT_FOUND;
+ } else {
+ layer->clearFrameStats();
+ status = NO_ERROR;
}
- return lbc;
-}
-
-status_t Client::createSurface(const String8& name, uint32_t /* w */, uint32_t /* h */,
- PixelFormat /* format */, uint32_t flags,
- const sp<IBinder>& parentHandle, LayerMetadata metadata,
- sp<IBinder>* outHandle, sp<IGraphicBufferProducer>* /* gbp */,
- int32_t* outLayerId, uint32_t* outTransformHint) {
- // 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);
+ return binderStatusFromStatusT(status);
}
-status_t Client::createWithSurfaceParent(const String8& /* name */, uint32_t /* w */,
- uint32_t /* h */, PixelFormat /* format */,
- uint32_t /* flags */,
- const sp<IGraphicBufferProducer>& /* parent */,
- LayerMetadata /* metadata */, sp<IBinder>* /* handle */,
- sp<IGraphicBufferProducer>* /* 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;
+binder::Status Client::getLayerFrameStats(const sp<IBinder>& handle, gui::FrameStats* outStats) {
+ status_t status;
+ sp<Layer> layer = LayerHandle::getLayer(handle);
+ if (layer == nullptr) {
+ 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;
+ }
+ return binderStatusFromStatusT(status);
}
-status_t Client::mirrorSurface(const sp<IBinder>& mirrorFromHandle, sp<IBinder>* outHandle,
- int32_t* outLayerId) {
- LayerCreationArgs args(mFlinger.get(), this, "MirrorRoot", 0 /* flags */, LayerMetadata());
- return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId);
+binder::Status Client::mirrorSurface(const sp<IBinder>& mirrorFromHandle,
+ gui::CreateSurfaceResult* outResult) {
+ sp<IBinder> handle;
+ LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this), "MirrorRoot",
+ 0 /* flags */, gui::LayerMetadata());
+ status_t status = mFlinger->mirrorLayer(args, mirrorFromHandle, *outResult);
+ return binderStatusFromStatusT(status);
}
-status_t Client::clearLayerFrameStats(const sp<IBinder>& handle) const {
- sp<Layer> layer = getLayerUser(handle);
- if (layer == nullptr) {
- return NAME_NOT_FOUND;
- }
- layer->clearFrameStats();
- return NO_ERROR;
+binder::Status Client::mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) {
+ sp<IBinder> handle;
+ LayerCreationArgs args(mFlinger.get(), sp<Client>::fromExisting(this),
+ "MirrorRoot-" + std::to_string(displayId), 0 /* flags */,
+ gui::LayerMetadata());
+ std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
+ status_t status = mFlinger->mirrorDisplay(*id, args, *outResult);
+ return binderStatusFromStatusT(status);
}
-status_t Client::getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const {
- sp<Layer> layer = getLayerUser(handle);
- if (layer == nullptr) {
- return NAME_NOT_FOUND;
- }
- layer->getFrameStats(outStats);
- return NO_ERROR;
+binder::Status Client::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ return gui::getSchedulingPolicy(outPolicy);
}
// ---------------------------------------------------------------------------
diff --git a/services/surfaceflinger/Client.h b/services/surfaceflinger/Client.h
index 15cd763822..77916e0a78 100644
--- a/services/surfaceflinger/Client.h
+++ b/services/surfaceflinger/Client.h
@@ -24,55 +24,42 @@
#include <utils/KeyedVector.h>
#include <utils/Mutex.h>
-#include <gui/ISurfaceComposerClient.h>
+#include <android/gui/BnSurfaceComposerClient.h>
namespace android {
class Layer;
class SurfaceFlinger;
-class Client : public BnSurfaceComposerClient
-{
+class Client : public gui::BnSurfaceComposerClient {
public:
explicit Client(const sp<SurfaceFlinger>& flinger);
~Client() = default;
status_t initCheck() const;
- // protected by SurfaceFlinger::mStateLock
- void attachLayer(const sp<IBinder>& handle, const sp<Layer>& layer);
- void detachLayer(const Layer* layer);
-
- sp<Layer> getLayerUser(const sp<IBinder>& 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<IBinder>& parent,
- LayerMetadata metadata, sp<IBinder>* handle,
- sp<IGraphicBufferProducer>* 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<IGraphicBufferProducer>& parent,
- LayerMetadata metadata, sp<IBinder>* handle,
- sp<IGraphicBufferProducer>* gbp, int32_t* outLayerId,
- uint32_t* outTransformHint = nullptr);
+ binder::Status createSurface(const std::string& name, int32_t flags, const sp<IBinder>& parent,
+ const gui::LayerMetadata& metadata,
+ gui::CreateSurfaceResult* outResult) override;
+
+ binder::Status clearLayerFrameStats(const sp<IBinder>& handle) override;
- status_t mirrorSurface(const sp<IBinder>& mirrorFromHandle, sp<IBinder>* handle,
- int32_t* outLayerId);
+ binder::Status getLayerFrameStats(const sp<IBinder>& handle,
+ gui::FrameStats* outStats) override;
- virtual status_t clearLayerFrameStats(const sp<IBinder>& handle) const;
+ binder::Status mirrorSurface(const sp<IBinder>& mirrorFromHandle,
+ gui::CreateSurfaceResult* outResult) override;
- virtual status_t getLayerFrameStats(const sp<IBinder>& handle, FrameStats* outStats) const;
+ binder::Status mirrorDisplay(int64_t displayId, gui::CreateSurfaceResult* outResult) override;
+
+ binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
// constant
sp<SurfaceFlinger> mFlinger;
- // protected by mLock
- DefaultKeyedVector< wp<IBinder>, wp<Layer> > mLayers;
-
// thread-safe
mutable Mutex mLock;
};
diff --git a/services/surfaceflinger/ClientCache.cpp b/services/surfaceflinger/ClientCache.cpp
index 3c7b9d93aa..09e41ffede 100644
--- a/services/surfaceflinger/ClientCache.cpp
+++ b/services/surfaceflinger/ClientCache.cpp
@@ -22,6 +22,7 @@
#include <cinttypes>
#include <android-base/stringprintf.h>
+#include <gui/TraceUtils.h>
#include <renderengine/impl/ExternalTexture.h>
#include "ClientCache.h"
@@ -30,18 +31,18 @@ namespace android {
ANDROID_SINGLETON_STATIC_INSTANCE(ClientCache);
-ClientCache::ClientCache() : mDeathRecipient(new CacheDeathRecipient) {}
+ClientCache::ClientCache() : mDeathRecipient(sp<CacheDeathRecipient>::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<GraphicBuffer>& buffer) {
+base::expected<std::shared_ptr<renderengine::ExternalTexture>, ClientCache::AddError>
+ClientCache::add(const client_cache_t& cacheId, const sp<GraphicBuffer>& 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<GraphicBuffer>& 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<GraphicBuffer>& 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<GraphicBuffer> ClientCache::erase(const client_cache_t& cacheId) {
+ sp<GraphicBuffer> buffer;
auto& [processToken, id] = cacheId;
std::vector<sp<ErasedRecipient>> 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> 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<renderengine::ExternalTexture> ClientCache::get(const client_cache_t& cacheId) {
diff --git a/services/surfaceflinger/ClientCache.h b/services/surfaceflinger/ClientCache.h
index a9b8177d70..fefc04054d 100644
--- a/services/surfaceflinger/ClientCache.h
+++ b/services/surfaceflinger/ClientCache.h
@@ -29,16 +29,33 @@
#include <set>
#include <unordered_map>
-#define BUFFER_CACHE_MAX_SIZE 64
+// 4096 is based on 64 buffers * 64 layers. Once this limit is reached, the least recently used
+// buffer is uncached before the new buffer is cached.
+#define BUFFER_CACHE_MAX_SIZE 4096
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<ClientCache> {
public:
ClientCache();
- bool add(const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer);
- void erase(const client_cache_t& cacheId);
+ enum class AddError { CacheFull, Unspecified };
+
+ base::expected<std::shared_ptr<renderengine::ExternalTexture>, AddError> add(
+ const client_cache_t& cacheId, const sp<GraphicBuffer>& buffer);
+
+ sp<GraphicBuffer> erase(const client_cache_t& cacheId);
std::shared_ptr<renderengine::ExternalTexture> get(const client_cache_t& cacheId);
diff --git a/services/surfaceflinger/CompositionEngine/Android.bp b/services/surfaceflinger/CompositionEngine/Android.bp
index 11a9e19db8..0a70d4299d 100644
--- a/services/surfaceflinger/CompositionEngine/Android.bp
+++ b/services/surfaceflinger/CompositionEngine/Android.bp
@@ -9,7 +9,14 @@ package {
cc_defaults {
name: "libcompositionengine_defaults",
- defaults: ["surfaceflinger_defaults"],
+ defaults: [
+ "aconfig_lib_cc_static_link.defaults",
+ "android.hardware.graphics.composer3-ndk_shared",
+ "android.hardware.power-ndk_shared",
+ "librenderengine_deps",
+ "libtimestats_deps",
+ "surfaceflinger_defaults",
+ ],
cflags: [
"-DLOG_TAG=\"CompositionEngine\"",
"-DATRACE_TAG=ATRACE_TAG_GRAPHICS",
@@ -20,27 +27,25 @@ 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",
"libbase",
"libcutils",
"libgui",
- "liblayers_proto",
"liblog",
"libnativewindow",
"libprotobuf-cpp-lite",
"libSurfaceFlingerProp",
- "libtimestats",
"libui",
"libutils",
+ "server_configurable_flags",
],
static_libs: [
+ "liblayers_proto",
"libmath",
"librenderengine",
+ "libtimestats",
"libtonemap",
- "libtrace_proto",
"libaidlcommonsupport",
"libprocessgroup",
"libcgrouprc",
@@ -57,9 +62,8 @@ cc_defaults {
],
}
-cc_library {
- name: "libcompositionengine",
- defaults: ["libcompositionengine_defaults"],
+filegroup {
+ name: "libcompositionengine_sources",
srcs: [
"src/planner/CachedSet.cpp",
"src/planner/Flattener.cpp",
@@ -82,8 +86,23 @@ cc_library {
"src/OutputLayerCompositionState.cpp",
"src/RenderSurface.cpp",
],
+}
+
+cc_library {
+ name: "libcompositionengine",
+ defaults: ["libcompositionengine_defaults"],
+ static_libs: [
+ "libsurfaceflinger_common",
+ "libsurfaceflingerflags",
+ ],
+ srcs: [
+ ":libcompositionengine_sources",
+ ],
local_include_dirs: ["include"],
export_include_dirs: ["include"],
+ shared_libs: [
+ "server_configurable_flags",
+ ],
}
cc_library {
@@ -104,6 +123,11 @@ cc_library {
"libgtest",
"libgmock",
"libcompositionengine",
+ "libsurfaceflinger_common_test",
+ "libsurfaceflingerflags_test",
+ ],
+ shared_libs: [
+ "server_configurable_flags",
],
local_include_dirs: ["include"],
export_include_dirs: ["include"],
@@ -112,8 +136,13 @@ cc_library {
cc_test {
name: "libcompositionengine_test",
test_suites: ["device-tests"],
+ include_dirs: [
+ "frameworks/native/services/surfaceflinger/common/include",
+ "frameworks/native/services/surfaceflinger/tests/unittests",
+ ],
defaults: ["libcompositionengine_defaults"],
srcs: [
+ ":libcompositionengine_sources",
"tests/planner/CachedSetTest.cpp",
"tests/planner/FlattenerTest.cpp",
"tests/planner/LayerStateTest.cpp",
@@ -132,26 +161,21 @@ cc_test {
"tests/RenderSurfaceTest.cpp",
],
static_libs: [
- "libcompositionengine",
"libcompositionengine_mocks",
"libgui_mocks",
"librenderengine_mocks",
"libgmock",
"libgtest",
+ "libsurfaceflinger_common_test",
+ "libsurfaceflingerflags_test",
+ ],
+ shared_libs: [
+ // For some reason, libvulkan isn't picked up from librenderengine
+ // Probably ASAN related?
+ "libvulkan",
+ "server_configurable_flags",
],
sanitize: {
- // By using the address sanitizer, we not only uncover any issues
- // with the test, but also any issues with the code under test.
- //
- // Note: If you get an runtime link error like:
- //
- // CANNOT LINK EXECUTABLE "/data/local/tmp/libcompositionengine_test": library "libclang_rt.asan-aarch64-android.so" not found
- //
- // it is because the address sanitizer shared objects are not installed
- // by default in the system image.
- //
- // You can either "make dist tests" before flashing, or set this
- // option to false temporarily.
- address: true,
+ hwaddress: true,
},
}
diff --git a/services/surfaceflinger/CompositionEngine/AndroidTest.xml b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
new file mode 100644
index 0000000000..94e92f0143
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/AndroidTest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Config for libcompositionengine_test">
+ <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+ <option name="cleanup" value="true" />
+ <option name="push"
+ value="libcompositionengine_test->/data/local/tmp/libcompositionengine_test" />
+ </target_preparer>
+
+ <!--
+ Disable SELinux so that crashes in the test suite produces symbolized stack traces.
+ -->
+ <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+ <option name="test-suite-tag" value="apct" />
+ <test class="com.android.tradefed.testtype.GTest" >
+ <option name="native-test-device-path" value="/data/local/tmp" />
+ <option name="module-name" value="libcompositionengine_test" />
+ </test>
+</configuration>
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
index 3faa068c03..7c10fa57ec 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionEngine.h
@@ -18,9 +18,10 @@
#include <TimeStats/TimeStats.h>
#include <utils/Timers.h>
-
#include <memory>
+#include "Feature.h"
+
namespace android {
class HWComposer;
@@ -55,9 +56,9 @@ public:
virtual void setHwComposer(std::unique_ptr<HWComposer>) = 0;
virtual renderengine::RenderEngine& getRenderEngine() const = 0;
- virtual void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>) = 0;
+ virtual void setRenderEngine(renderengine::RenderEngine*) = 0;
- virtual TimeStats& getTimeStats() const = 0;
+ virtual TimeStats* getTimeStats() const = 0;
virtual void setTimeStats(const std::shared_ptr<TimeStats>&) = 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 f201751d59..1a235e9401 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/CompositionRefreshArgs.h
@@ -32,6 +32,20 @@ namespace android::compositionengine {
using Layers = std::vector<sp<compositionengine::LayerFE>>;
using Outputs = std::vector<std::shared_ptr<compositionengine::Output>>;
+struct BorderRenderInfo {
+ float width = 0;
+ half4 color;
+ std::vector<int32_t> layerIds;
+};
+
+// Interface of composition engine power hint callback.
+struct ICEPowerCallback {
+ virtual void notifyCpuLoadUp() = 0;
+
+protected:
+ ~ICEPowerCallback() = default;
+};
+
/**
* A parameter object for refreshing a set of outputs
*/
@@ -47,21 +61,18 @@ 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<uint64_t> bufferIdsToUncache;
+
// Controls how the color mode is chosen for an output
OutputColorSetting outputColorSetting{OutputColorSetting::kEnhanced};
- // If not Dataspace::UNKNOWN, overrides the dataspace on each output
- ui::Dataspace colorSpaceAgnosticDataspace{ui::Dataspace::UNKNOWN};
-
// Forces a color mode on the outputs being refreshed
ui::ColorMode forceOutputColorMode{ui::ColorMode::NATIVE};
// 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 +89,24 @@ struct CompositionRefreshArgs {
// If set, causes the dirty regions to flash with the delay
std::optional<std::chrono::microseconds> 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<FenceTime> previousPresentFence;
+ // Optional.
+ // The earliest time to send the present command to the HAL.
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime;
// The expected time for the next present
nsecs_t expectedPresentTime{0};
+ // The frameInterval for the next present
+ Fps frameInterval{};
+
// If set, a frame has been scheduled for that time.
std::optional<std::chrono::steady_clock::time_point> scheduledFrameTime;
+
+ std::vector<BorderRenderInfo> borderInfoList;
+
+ bool hasTrustedPresentationListener = false;
+
+ ICEPowerCallback* powerCallback = nullptr;
};
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
index df44e75625..a74c5fe207 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayColorProfile.h
@@ -36,7 +36,7 @@ class HdrCapabilities;
namespace compositionengine {
/**
- * Encapsulates all the state and functionality for how colors should be
+ * Encapsulates all the states and functionality for how colors should be
* transformed for a display
*/
class DisplayColorProfile {
@@ -87,10 +87,6 @@ public:
// Returns true if HWC for this profile supports the dataspace
virtual bool isDataspaceSupported(ui::Dataspace) const = 0;
- // Returns the target dataspace for picked color mode and dataspace
- virtual ui::Dataspace getTargetDataspace(ui::ColorMode, ui::Dataspace,
- ui::Dataspace colorSpaceAgnosticDataspace) const = 0;
-
// Debugging
virtual void dump(std::string&) const = 0;
};
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
index 98c4af487e..6e60839dd9 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplayCreationArgs.h
@@ -42,6 +42,10 @@ struct DisplayCreationArgs {
// True if this display should be considered secure
bool isSecure = false;
+ // True if this display should be considered protected, as in this display should render DRM
+ // content.
+ bool isProtected = false;
+
// Optional pointer to the power advisor interface, if one is needed for
// this display.
Hwc2::PowerAdvisor* powerAdvisor = nullptr;
@@ -73,6 +77,11 @@ public:
return *this;
}
+ DisplayCreationArgsBuilder& setIsProtected(bool isProtected) {
+ mArgs.isProtected = isProtected;
+ return *this;
+ }
+
DisplayCreationArgsBuilder& setPowerAdvisor(Hwc2::PowerAdvisor* powerAdvisor) {
mArgs.powerAdvisor = powerAdvisor;
return *this;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
index ca86f4c604..643b458067 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/DisplaySurface.h
@@ -60,7 +60,7 @@ public:
//
// advanceFrame must be followed by a call to onFrameCommitted before
// advanceFrame may be called again.
- virtual status_t advanceFrame() = 0;
+ virtual status_t advanceFrame(float hdrSdrRatio) = 0;
// onFrameCommitted is called after the frame has been committed to the
// hardware composer. The surface collects the release fence for this
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h
new file mode 100644
index 0000000000..ee8000ae18
--- /dev/null
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Feature.h
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include <ftl/flags.h>
+#include <cstdint>
+
+namespace android::compositionengine {
+
+enum class Feature : int32_t {
+ kSnapshotLayerMetadata = 1 << 0,
+};
+
+using FeatureFlags = ftl::Flags<Feature>;
+
+} // namespace android::compositionengine \ No newline at end of file
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h
deleted file mode 100644
index 0ce263b930..0000000000
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/FenceResult.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <android-base/expected.h>
-#include <utils/Errors.h>
-#include <utils/StrongPointer.h>
-
-// TODO(b/232535621): Pull this file to <ui/FenceResult.h> so that RenderEngine::drawLayers returns
-// FenceResult rather than RenderEngineResult.
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <renderengine/RenderEngine.h>
-#pragma clang diagnostic pop
-
-namespace android {
-
-class Fence;
-
-using FenceResult = base::expected<sp<Fence>, 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<Fence>::make(std::move(fence));
- } else {
- return base::unexpected(status);
- }
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
index ec610c1b1d..a1d61323a8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFE.h
@@ -19,8 +19,7 @@
#include <optional>
#include <ostream>
#include <unordered_set>
-
-#include <compositionengine/FenceResult.h>
+#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 <ftl/future.h>
+#include <ui/FenceResult.h>
#include <utils/RefBase.h>
#include <utils/Timers.h>
@@ -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 {
@@ -116,7 +97,7 @@ public:
const bool isSecure;
// If set to true, the target buffer has protected content support.
- const bool supportsProtectedContent;
+ const bool isProtected;
// Viewport of the target being rendered to. This is used to determine
// the shadow light position.
@@ -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<LayerSettings> 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<LayerSettings> prepareClientComposition(
+ ClientCompositionTargetSettings&) const = 0;
// Called after the layer is displayed to update the presentation fence
- virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>) = 0;
+ virtual void onLayerDisplayed(ftl::SharedFuture<FenceResult>, 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<Fence>&) {}
+ virtual const gui::LayerMetadata* getMetadata() const = 0;
+ virtual const gui::LayerMetadata* getRelativeMetadata() const = 0;
};
// TODO(b/121291683): Specialize std::hash<> for sp<T> so these and others can
@@ -181,8 +167,7 @@ using LayerFESet = std::unordered_set<sp<LayerFE>, LayerFESpHash>;
static inline bool operator==(const LayerFE::ClientCompositionTargetSettings& lhs,
const LayerFE::ClientCompositionTargetSettings& rhs) {
return lhs.clip.hasSameRects(rhs.clip) && lhs.needsFiltering == rhs.needsFiltering &&
- lhs.isSecure == rhs.isSecure &&
- lhs.supportsProtectedContent == rhs.supportsProtectedContent &&
+ lhs.isSecure == rhs.isSecure && lhs.isProtected == rhs.isProtected &&
lhs.viewport == rhs.viewport && lhs.dataspace == rhs.dataspace &&
lhs.realContentIsVisible == rhs.realContentIsVisible &&
lhs.clearContent == rhs.clearContent;
@@ -203,7 +188,7 @@ static inline void PrintTo(const LayerFE::ClientCompositionTargetSettings& setti
PrintTo(settings.clip, os);
*os << "\n .needsFiltering = " << settings.needsFiltering;
*os << "\n .isSecure = " << settings.isSecure;
- *os << "\n .supportsProtectedContent = " << settings.supportsProtectedContent;
+ *os << "\n .isProtected = " << settings.isProtected;
*os << "\n .viewport = ";
PrintTo(settings.viewport, os);
*os << "\n .dataspace = ";
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
index 974f7c6134..11759b855f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/LayerFECompositionState.h
@@ -18,6 +18,7 @@
#include <cstdint>
+#include <android/gui/CachingHint.h>
#include <gui/HdrMetadata.h>
#include <math/mat4.h>
#include <ui/BlurRegion.h>
@@ -25,6 +26,7 @@
#include <ui/LayerStack.h>
#include <ui/Rect.h>
#include <ui/Region.h>
+#include <ui/ShadowSettings.h>
#include <ui/Transform.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
@@ -131,8 +133,7 @@ struct LayerFECompositionState {
// The bounds of the layer in layer local coordinates
FloatRect geomLayerBounds;
- // length of the shadow in screen space
- float shadowRadius{0.f};
+ ShadowSettings shadowSettings;
// List of regions that require blur
std::vector<BlurRegion> blurRegions;
@@ -163,7 +164,6 @@ struct LayerFECompositionState {
// The buffer and related state
sp<GraphicBuffer> buffer;
- int bufferSlot{BufferQueue::INVALID_BUFFER_SLOT};
sp<Fence> 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 2203639b1a..f1d6f52eb8 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/Output.h
@@ -23,8 +23,10 @@
#include <type_traits>
#include <unordered_map>
#include <utility>
+#include <vector>
#include <compositionengine/LayerFE.h>
+#include <ftl/future.h>
#include <renderengine/LayerSettings.h>
#include <ui/Fence.h>
#include <ui/FenceTime.h>
@@ -60,7 +62,7 @@ struct GpuCompositionResult;
} // namespace impl
/**
- * Encapsulates all the state involved with composing layers for an output
+ * Encapsulates all the states involved with composing layers for an output
*/
class Output {
public:
@@ -135,7 +137,6 @@ public:
ui::ColorMode mode{ui::ColorMode::NATIVE};
ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
ui::RenderIntent renderIntent{ui::RenderIntent::COLORIMETRIC};
- ui::Dataspace colorSpaceAgnosticDataspace{ui::Dataspace::UNKNOWN};
};
// Use internally to incrementally compute visibility/coverage
@@ -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<Region> aboveCoveredLayersExcludingOverlays;
};
virtual ~Output();
@@ -259,11 +264,15 @@ public:
// Prepare the output, updating the OutputLayers used in the output
virtual void prepare(const CompositionRefreshArgs&, LayerFESet&) = 0;
- // Presents the output, finalizing all composition details
- virtual void present(const CompositionRefreshArgs&) = 0;
+ // Presents the output, finalizing all composition details. This may happen
+ // asynchronously, in which case the returned future must be waited upon.
+ virtual ftl::Future<std::monostate> present(const CompositionRefreshArgs&) = 0;
- // Latches the front-end layer state for each output layer
- virtual void updateLayerStateFromFE(const CompositionRefreshArgs&) const = 0;
+ // Whether this output can be presented from another thread.
+ virtual bool supportsOffloadPresent() const = 0;
+
+ // Make the next call to `present` run asynchronously.
+ virtual void offloadPresentNextFrame() = 0;
// Enables predicting composition strategy to run client composition earlier
virtual void setPredictCompositionStrategy(bool) = 0;
@@ -275,6 +284,7 @@ protected:
virtual void setDisplayColorProfile(std::unique_ptr<DisplayColorProfile>) = 0;
virtual void setRenderSurface(std::unique_ptr<RenderSurface>) = 0;
+ virtual void uncacheBuffers(const std::vector<uint64_t>&) = 0;
virtual void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) = 0;
virtual void collectVisibleLayers(const CompositionRefreshArgs&, CoverageState&) = 0;
virtual void ensureOutputLayerIfVisible(sp<LayerFE>&, CoverageState&) = 0;
@@ -291,20 +301,19 @@ 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<base::unique_fd> composeSurfaces(
- const Region&, const compositionengine::CompositionRefreshArgs&,
- std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&) = 0;
- virtual void postFramebuffer() = 0;
+ const Region&, std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&) = 0;
+ virtual void presentFrameAndReleaseLayers() = 0;
virtual void renderCachedSets(const CompositionRefreshArgs&) = 0;
virtual bool chooseCompositionStrategy(
std::optional<android::HWComposer::DeviceRequestedChanges>*) = 0;
virtual void applyCompositionStrategy(
const std::optional<android::HWComposer::DeviceRequestedChanges>& changes) = 0;
virtual bool getSkipColorTransform() const = 0;
- virtual FrameFences presentAndGetFrameFences() = 0;
+ virtual FrameFences presentFrame() = 0;
virtual std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
bool supportsProtectedContent, ui::Dataspace outputDataspace,
std::vector<LayerFE*> &outLayerRef) = 0;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
index bf5184e997..4dbf8d2fce 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/OutputLayer.h
@@ -19,6 +19,7 @@
#include <cstdint>
#include <optional>
#include <string>
+#include <vector>
#include <ui/Transform.h>
#include <utils/StrongPointer.h>
@@ -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<uint64_t>& 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<LayerFE::LayerSettings> getOverrideCompositionList() const = 0;
+ virtual std::optional<LayerFE::LayerSettings> 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 9ee779cca1..02cea0d1ce 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/RenderSurface.h
@@ -86,21 +86,14 @@ public:
// Queues the drawn buffer for consumption by HWC. readyFence is the fence
// which will fire when the buffer is ready for consumption.
- virtual void queueBuffer(base::unique_fd readyFence) = 0;
+ virtual void queueBuffer(base::unique_fd readyFence, float hdrSdrRatio) = 0;
// 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 386808d714..c6995576a1 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<HWComposer>) override;
renderengine::RenderEngine& getRenderEngine() const override;
- void setRenderEngine(std::unique_ptr<renderengine::RenderEngine>) override;
+ void setRenderEngine(renderengine::RenderEngine*) override;
- TimeStats& getTimeStats() const override;
+ TimeStats* getTimeStats() const override;
void setTimeStats(const std::shared_ptr<TimeStats>&) 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<HWComposer> mHwComposer;
- std::unique_ptr<renderengine::RenderEngine> mRenderEngine;
+ renderengine::RenderEngine* mRenderEngine;
std::shared_ptr<TimeStats> 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 33a10a36a7..eac5d97df3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Display.h
@@ -59,9 +59,10 @@ public:
std::optional<android::HWComposer::DeviceRequestedChanges>*) override;
void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override;
bool getSkipColorTransform() const override;
- compositionengine::Output::FrameFences presentAndGetFrameFences() override;
+ compositionengine::Output::FrameFences presentFrame() override;
void setExpensiveRenderingExpected(bool) override;
- void finishFrame(const CompositionRefreshArgs&, GpuCompositionResult&&) override;
+ void finishFrame(GpuCompositionResult&&) override;
+ bool supportsOffloadPresent() const override;
// compositionengine::Display overrides
DisplayId getId() const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h
index 9bc0e681c7..3aad04c1a3 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/DisplayColorProfile.h
@@ -55,7 +55,6 @@ public:
const HdrCapabilities& getHdrCapabilities() const override;
bool isDataspaceSupported(ui::Dataspace) const override;
- ui::Dataspace getTargetDataspace(ui::ColorMode, ui::Dataspace, ui::Dataspace) const override;
void dump(std::string&) const override;
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/HwcBufferCache.h
index fd22aa3a2a..b6a4240b4b 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 <cstdint>
-#include <vector>
+#include <stack>
+#include <unordered_map>
// 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<GraphicBuffer> 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<GraphicBuffer>& buffer, uint32_t* outSlot,
- sp<GraphicBuffer>* outBuffer);
+ HwcSlotAndBuffer getHwcSlotAndBuffer(const sp<GraphicBuffer>& 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<GraphicBuffer>& 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<GraphicBuffer> mBuffers[kMaxLayerBufferCount];
+ uint32_t cache(const sp<GraphicBuffer>& buffer);
+ uint32_t getLeastRecentlyUsedSlot();
+
+ struct Cache {
+ sp<GraphicBuffer> 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<uint64_t, Cache> mCacheByBufferId;
+ sp<GraphicBuffer> mLastOverrideBuffer;
+ std::stack<uint32_t> 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 df721cdc89..911d67b5ed 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/Output.h
@@ -80,8 +80,11 @@ public:
void setReleasedLayers(ReleasedLayers&&) override;
void prepare(const CompositionRefreshArgs&, LayerFESet&) override;
- void present(const CompositionRefreshArgs&) override;
+ ftl::Future<std::monostate> present(const CompositionRefreshArgs&) override;
+ bool supportsOffloadPresent() const override { return false; }
+ void offloadPresentNextFrame() override;
+ void uncacheBuffers(const std::vector<uint64_t>& bufferIdsToUncache) override;
void rebuildLayerStacks(const CompositionRefreshArgs&, LayerFESet&) override;
void collectVisibleLayers(const CompositionRefreshArgs&,
compositionengine::Output::CoverageState&) override;
@@ -89,21 +92,19 @@ 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<base::unique_fd> composeSurfaces(const Region&,
- const compositionengine::CompositionRefreshArgs&,
std::shared_ptr<renderengine::ExternalTexture>,
base::unique_fd&) override;
- void postFramebuffer() override;
+ void presentFrameAndReleaseLayers() override;
void renderCachedSets(const CompositionRefreshArgs&) override;
void cacheClientCompositionRequests(uint32_t) override;
bool canPredictCompositionStrategy(const CompositionRefreshArgs&) override;
@@ -122,6 +123,7 @@ public:
virtual std::future<bool> chooseCompositionStrategyAsync(
std::optional<android::HWComposer::DeviceRequestedChanges>*);
virtual void resetCompositionStrategy();
+ virtual ftl::Future<std::monostate> presentFrameAndReleaseLayersAsync();
protected:
std::unique_ptr<compositionengine::OutputLayer> createOutputLayer(const sp<LayerFE>&) const;
@@ -134,10 +136,12 @@ protected:
};
void applyCompositionStrategy(const std::optional<DeviceRequestedChanges>&) override{};
bool getSkipColorTransform() const override;
- compositionengine::Output::FrameFences presentAndGetFrameFences() override;
+ compositionengine::Output::FrameFences presentFrame() override;
+ virtual renderengine::DisplaySettings generateClientCompositionDisplaySettings(
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer) const;
std::vector<LayerFE::LayerSettings> generateClientCompositionRequests(
- bool supportsProtectedContent, ui::Dataspace outputDataspace,
- std::vector<LayerFE*> &outLayerFEs) override;
+ bool supportsProtectedContent, ui::Dataspace outputDataspace,
+ std::vector<LayerFE*>& outLayerFEs) override;
void appendRegionFlashRequests(const Region&, std::vector<LayerFE::LayerSettings>&) override;
void setExpensiveRenderingExpected(bool enabled) override;
void setHintSessionGpuFence(std::unique_ptr<FenceTime>&& gpuFence) override;
@@ -152,15 +156,23 @@ protected:
virtual const compositionengine::CompositionEngine& getCompositionEngine() const = 0;
virtual void dumpState(std::string& out) const = 0;
+ 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;
compositionengine::Output::ColorProfile pickColorProfile(
const compositionengine::CompositionRefreshArgs&) const;
+ void updateHwcAsyncWorker();
+ float getHdrSdrRatio(const std::shared_ptr<renderengine::ExternalTexture>& buffer) const;
std::string mName;
+ std::string mNamePlusId;
std::unique_ptr<compositionengine::DisplayColorProfile> mDisplayColorProfile;
std::unique_ptr<compositionengine::RenderSurface> mRenderSurface;
@@ -170,6 +182,12 @@ private:
std::unique_ptr<ClientCompositionRequestCache> mClientCompositionRequestCache;
std::unique_ptr<planner::Planner> mPlanner;
std::unique_ptr<HwcAsyncWorker> mHwComposerAsyncWorker;
+
+ bool mPredictCompositionStrategy = false;
+ bool mOffloadPresent = false;
+
+ // Whether the content must be recomposed this frame.
+ bool mMustRecompose = false;
};
// This template factory function standardizes the implementation details of the
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
index 7709b967b6..6b1c318d1c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputCompositionState.h
@@ -32,7 +32,9 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/ProjectionSpace.h>
+#include <renderengine/BorderRenderInfo.h>
#include <ui/LayerStack.h>
#include <ui/Rect.h>
#include <ui/Region.h>
@@ -51,6 +53,9 @@ struct OutputCompositionState {
// If false, this output is not considered secure
bool isSecure{false};
+ // If false, this output is not considered protected
+ bool isProtected{false};
+
// If true, the current frame on this output uses client composition
bool usesClientComposition{false};
@@ -114,23 +119,20 @@ struct OutputCompositionState {
// Current active dataspace
ui::Dataspace dataspace{ui::Dataspace::UNKNOWN};
- // Current target dataspace
- ui::Dataspace targetDataspace{ui::Dataspace::UNKNOWN};
-
std::optional<android::HWComposer::DeviceRequestedChanges> previousDeviceRequestedChanges{};
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<FenceTime> previousPresentFence;
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime;
// The expected time for the next present
nsecs_t expectedPresentTime{0};
+ // The frameInterval for the next present
+ Fps frameInterval{};
+
// Current display brightness
float displayBrightnessNits{-1.f};
@@ -164,9 +166,13 @@ struct OutputCompositionState {
bool treat170mAsSrgb = false;
+ std::vector<renderengine::BorderRenderInfo> borderInfoList;
+
uint64_t lastOutputLayerHash = 0;
uint64_t outputLayerHash = 0;
+ ICEPowerCallback* powerCallback = nullptr;
+
// Debugging
void dump(std::string& result) const;
};
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
index ecd432f629..f383392e55 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/OutputLayer.h
@@ -18,7 +18,9 @@
#include <cstdint>
#include <memory>
+#include <optional>
#include <string>
+#include <vector>
#include <compositionengine/LayerFE.h>
#include <compositionengine/OutputLayer.h>
@@ -43,6 +45,8 @@ public:
void setHwcLayer(std::shared_ptr<HWC2::Layer>) override;
+ void uncacheBuffers(const std::vector<uint64_t>& 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<LayerFE::LayerSettings> getOverrideCompositionList() const override;
+ std::optional<LayerFE::LayerSettings> 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 2b383c1dfe..6c419da716 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<Region> coveredRegionExcludingDisplayOverlays;
+
// The visibleRegion transformed to output space
Region outputSpaceVisibleRegion;
@@ -84,7 +90,7 @@ struct OutputLayerCompositionState {
// The source crop for this layer on this output
FloatRect sourceCrop;
- // The buffer transform to use for this layer o on this output.
+ // The buffer transform to use for this layer on this output.
Hwc2::Transform bufferTransform{static_cast<Hwc2::Transform>(0)};
// The dataspace for this layer
@@ -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 e4cb113645..202145ed6c 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/RenderSurface.h
@@ -60,17 +60,14 @@ public:
void prepareFrame(bool usesClientComposition, bool usesDeviceComposition) override;
std::shared_ptr<renderengine::ExternalTexture> dequeueBuffer(
base::unique_fd* bufferFence) override;
- void queueBuffer(base::unique_fd readyFence) override;
+ void queueBuffer(base::unique_fd readyFence, float hdrSdrRatio) 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<renderengine::ExternalTexture>& 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<compositionengine::RenderSurface> createRenderSurface(
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index 24a7744542..d26ca9dd00 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 5aec7c2e88..1f241b091c 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
@@ -225,6 +227,7 @@ public:
// Returns the bit-set of differing fields between this LayerState and another LayerState.
// This bit-set is based on NonUniqueFields only, and excludes GraphicBuffers.
ftl::Flags<LayerStateField> getDifferingFields(const LayerState& other) const;
+ bool isSourceCropSizeEqual(const LayerState& other) const;
compositionengine::OutputLayer* getOutputLayer() const { return mOutputLayer; }
int32_t getId() const { return mId.get(); }
@@ -245,9 +248,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<GraphicBuffer> 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 +491,19 @@ private:
return hash;
}};
- static const constexpr size_t kNumNonUniqueFields = 17;
+ OutputLayerState<bool, LayerStateField::HasProtectedContent> mIsProtected{[](auto layer) {
+ return layer->getLayerFE().getCompositionState()->hasProtectedContent;
+ }};
+
+ OutputLayerState<gui::CachingHint, LayerStateField::CachingHint>
+ mCachingHint{[](auto layer) {
+ return layer->getLayerFE().getCompositionState()->cachingHint;
+ },
+ [](const gui::CachingHint& cachingHint) {
+ return std::vector<std::string>{toString(cachingHint)};
+ }};
+
+ static const constexpr size_t kNumNonUniqueFields = 19;
std::array<StateInterface*, kNumNonUniqueFields> getNonUniqueFields() {
std::array<const StateInterface*, kNumNonUniqueFields> constFields =
@@ -496,13 +517,11 @@ private:
}
std::array<const StateInterface*, kNumNonUniqueFields> 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 f953d0b7d0..9b2387b966 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<HWComposer>));
MOCK_CONST_METHOD0(getRenderEngine, renderengine::RenderEngine&());
- MOCK_METHOD1(setRenderEngine, void(std::unique_ptr<renderengine::RenderEngine>));
+ MOCK_METHOD1(setRenderEngine, void(renderengine::RenderEngine*));
- MOCK_CONST_METHOD0(getTimeStats, TimeStats&());
+ MOCK_CONST_METHOD0(getTimeStats, TimeStats*());
MOCK_METHOD1(setTimeStats, void(const std::shared_ptr<TimeStats>&));
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/DisplayColorProfile.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h
index 1aaebea295..a9045211af 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplayColorProfile.h
@@ -43,8 +43,6 @@ public:
MOCK_CONST_METHOD0(getHdrCapabilities, const HdrCapabilities&());
MOCK_CONST_METHOD1(isDataspaceSupported, bool(ui::Dataspace));
- MOCK_CONST_METHOD3(getTargetDataspace,
- ui::Dataspace(ui::ColorMode, ui::Dataspace, ui::Dataspace));
MOCK_CONST_METHOD1(dump, void(std::string&));
};
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h
index 168e433da2..08d8ff756f 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/DisplaySurface.h
@@ -30,7 +30,7 @@ public:
MOCK_METHOD1(beginFrame, status_t(bool mustRecompose));
MOCK_METHOD1(prepareFrame, status_t(CompositionType compositionType));
- MOCK_METHOD0(advanceFrame, status_t());
+ MOCK_METHOD((status_t), advanceFrame, (float), (override));
MOCK_METHOD0(onFrameCommitted, void());
MOCK_CONST_METHOD1(dumpAsString, void(String8& result));
MOCK_METHOD1(resizeBuffers, void(const ui::Size&));
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/LayerFE.h
index 1c5c10f823..15e4577ae0 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<StrictMock<LayerFE>>::make()
friend class sp<LayerFE>;
friend class testing::StrictMock<LayerFE>;
+ friend class testing::NiceMock<LayerFE>;
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::LayerSettings>(
- compositionengine::LayerFE::ClientCompositionTargetSettings&));
+ MOCK_CONST_METHOD1(prepareClientComposition,
+ std::optional<compositionengine::LayerFE::LayerSettings>(
+ compositionengine::LayerFE::ClientCompositionTargetSettings&));
- MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>), (override));
+ MOCK_METHOD(void, onLayerDisplayed, (ftl::SharedFuture<FenceResult>, 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 2a04949cff..95ea3a4ed7 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/Output.h
@@ -80,8 +80,12 @@ public:
MOCK_METHOD1(setReleasedLayers, void(ReleasedLayers&&));
MOCK_METHOD2(prepare, void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
- MOCK_METHOD1(present, void(const compositionengine::CompositionRefreshArgs&));
+ MOCK_METHOD1(present,
+ ftl::Future<std::monostate>(const compositionengine::CompositionRefreshArgs&));
+ MOCK_CONST_METHOD0(supportsOffloadPresent, bool());
+ MOCK_METHOD(void, offloadPresentNextFrame, ());
+ MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
MOCK_METHOD2(rebuildLayerStacks,
void(const compositionengine::CompositionRefreshArgs&, LayerFESet&));
MOCK_METHOD2(collectVisibleLayers,
@@ -91,7 +95,6 @@ public:
void(sp<compositionengine::LayerFE>&, 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 +103,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<android::HWComposer::DeviceRequestedChanges>*));
MOCK_METHOD1(chooseCompositionStrategyAsync,
@@ -110,19 +113,17 @@ 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<base::unique_fd>(
- const Region&,
- const compositionengine::CompositionRefreshArgs& refreshArgs,
- std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
+ MOCK_METHOD3(composeSurfaces,
+ std::optional<base::unique_fd>(const Region&,
+ std::shared_ptr<renderengine::ExternalTexture>,
+ base::unique_fd&));
MOCK_CONST_METHOD0(getSkipColorTransform, bool());
- MOCK_METHOD0(postFramebuffer, void());
+ MOCK_METHOD0(presentFrameAndReleaseLayers, void());
MOCK_METHOD1(renderCachedSets, void(const CompositionRefreshArgs&));
- MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
+ MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
MOCK_METHOD3(generateClientCompositionRequests,
std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<compositionengine::LayerFE*>&));
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/OutputLayer.h
index a6cb811468..5fef63a510 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 <optional>
+
#include <compositionengine/CompositionEngine.h>
#include <compositionengine/LayerFE.h>
#include <compositionengine/Output.h>
@@ -33,6 +35,8 @@ public:
MOCK_METHOD1(setHwcLayer, void(std::shared_ptr<HWC2::Layer>));
+ MOCK_METHOD1(uncacheBuffers, void(const std::vector<uint64_t>&));
+
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<LayerFE::LayerSettings>());
+ MOCK_CONST_METHOD0(getOverrideCompositionSettings, std::optional<LayerFE::LayerSettings>());
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 e12aebb50c..c35fd3fbf6 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/mock/RenderSurface.h
@@ -40,11 +40,9 @@ public:
MOCK_METHOD1(beginFrame, status_t(bool mustRecompose));
MOCK_METHOD2(prepareFrame, void(bool, bool));
MOCK_METHOD1(dequeueBuffer, std::shared_ptr<renderengine::ExternalTexture>(base::unique_fd*));
- MOCK_METHOD1(queueBuffer, void(base::unique_fd));
+ MOCK_METHOD(void, queueBuffer, (base::unique_fd, float), (override));
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/ClientCompositionRequestCache.cpp b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
index 7e020ee1c1..bdaa1d0ae1 100644
--- a/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/ClientCompositionRequestCache.cpp
@@ -41,12 +41,10 @@ inline bool equalIgnoringSource(const renderengine::LayerSettings& lhs,
}
inline bool equalIgnoringBuffer(const renderengine::Buffer& lhs, const renderengine::Buffer& rhs) {
- return lhs.textureName == rhs.textureName &&
- lhs.useTextureFiltering == rhs.useTextureFiltering &&
+ return lhs.useTextureFiltering == rhs.useTextureFiltering &&
lhs.textureTransform == rhs.textureTransform &&
lhs.usePremultipliedAlpha == rhs.usePremultipliedAlpha &&
- lhs.isOpaque == rhs.isOpaque && lhs.isY410BT2020 == rhs.isY410BT2020 &&
- lhs.maxLuminanceNits == rhs.maxLuminanceNits;
+ lhs.isOpaque == rhs.isOpaque && lhs.maxLuminanceNits == rhs.maxLuminanceNits;
}
inline bool equalIgnoringBuffer(const renderengine::LayerSettings& lhs,
diff --git a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
index 6203dc6737..d87eae3812 100644
--- a/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp
@@ -20,6 +20,7 @@
#include <compositionengine/OutputLayer.h>
#include <compositionengine/impl/CompositionEngine.h>
#include <compositionengine/impl/Display.h>
+#include <ui/DisplayMap.h>
#include <renderengine/RenderEngine.h>
#include <utils/Trace.h>
@@ -65,15 +66,15 @@ void CompositionEngine::setHwComposer(std::unique_ptr<HWComposer> hwComposer) {
}
renderengine::RenderEngine& CompositionEngine::getRenderEngine() const {
- return *mRenderEngine.get();
+ return *mRenderEngine;
}
-void CompositionEngine::setRenderEngine(std::unique_ptr<renderengine::RenderEngine> 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>& timeStats) {
@@ -88,6 +89,44 @@ nsecs_t CompositionEngine::getLastFrameRefreshTimestamp() const {
return mRefreshStartTime;
}
+namespace {
+void offloadOutputs(Outputs& outputs) {
+ if (!FlagManager::getInstance().multithreaded_present() || outputs.size() < 2) {
+ return;
+ }
+
+ ui::PhysicalDisplayVector<compositionengine::Output*> outputsToOffload;
+ for (const auto& output : outputs) {
+ if (!ftl::Optional(output->getDisplayId()).and_then(HalDisplayId::tryCast)) {
+ // Not HWC-enabled, so it is always client-composited. No need to offload.
+ continue;
+ }
+ if (!output->getState().isEnabled) {
+ continue;
+ }
+
+ // Only run present in multiple threads if all HWC-enabled displays
+ // being refreshed support it.
+ if (!output->supportsOffloadPresent()) {
+ return;
+ }
+ outputsToOffload.push_back(output.get());
+ }
+
+ if (outputsToOffload.size() < 2) {
+ return;
+ }
+
+ // Leave the last eligible display on the main thread, which will
+ // allow it to run concurrently without an extra thread hop.
+ outputsToOffload.pop_back();
+
+ for (compositionengine::Output* output : outputsToOffload) {
+ output->offloadPresentNextFrame();
+ }
+}
+} // namespace
+
void CompositionEngine::present(CompositionRefreshArgs& args) {
ATRACE_CALL();
ALOGV(__FUNCTION__);
@@ -105,22 +144,31 @@ void CompositionEngine::present(CompositionRefreshArgs& args) {
}
}
- updateLayerStateFromFE(args);
+ // Offloading the HWC call for `present` allows us to simultaneously call it
+ // on multiple displays. This is desirable because these calls block and can
+ // be slow.
+ offloadOutputs(args.outputs);
+ ui::DisplayVector<ftl::Future<std::monostate>> presentFutures;
for (const auto& output : args.outputs) {
- output->present(args);
+ presentFutures.push_back(output->present(args));
+ }
+
+ {
+ ATRACE_NAME("Waiting on HWC");
+ for (auto& future : presentFutures) {
+ // TODO(b/185536303): Call ftl::Future::wait() once it exists, since
+ // we do not need the return value of get().
+ future.get();
+ }
}
}
void CompositionEngine::updateCursorAsync(CompositionRefreshArgs& args) {
- std::unordered_map<compositionengine::LayerFE*, compositionengine::LayerFECompositionState*>
- uniqueVisibleLayers;
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 +184,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 +192,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 +204,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 1ec6449ed0..7a727fdb83 100644
--- a/services/surfaceflinger/CompositionEngine/src/Display.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Display.cpp
@@ -25,6 +25,7 @@
#include <compositionengine/impl/DumpHelpers.h>
#include <compositionengine/impl/OutputLayer.h>
#include <compositionengine/impl/RenderSurface.h>
+#include <gui/TraceUtils.h>
#include <utils/Trace.h>
@@ -56,6 +57,7 @@ void Display::setConfiguration(const compositionengine::DisplayCreationArgs& arg
mId = args.id;
mPowerAdvisor = args.powerAdvisor;
editState().isSecure = args.isSecure;
+ editState().isProtected = args.isProtected;
editState().displaySpace.setBounds(args.pixels);
setName(args.name);
}
@@ -106,14 +108,9 @@ void Display::setColorTransform(const compositionengine::CompositionRefreshArgs&
}
void Display::setColorProfile(const ColorProfile& colorProfile) {
- const ui::Dataspace targetDataspace =
- getDisplayColorProfile()->getTargetDataspace(colorProfile.mode, colorProfile.dataspace,
- colorProfile.colorSpaceAgnosticDataspace);
-
if (colorProfile.mode == getState().colorMode &&
colorProfile.dataspace == getState().dataspace &&
- colorProfile.renderIntent == getState().renderIntent &&
- targetDataspace == getState().targetDataspace) {
+ colorProfile.renderIntent == getState().renderIntent) {
return;
}
@@ -196,7 +193,7 @@ void Display::setReleasedLayers(const compositionengine::CompositionRefreshArgs&
});
if (hasQueuedFrames) {
- releasedLayers.emplace_back(layerFE);
+ releasedLayers.emplace_back(wp<LayerFE>::fromExisting(layerFE));
}
}
@@ -235,7 +232,7 @@ void Display::beginFrame() {
bool Display::chooseCompositionStrategy(
std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
- ATRACE_CALL();
+ ATRACE_FORMAT("%s for %s", __func__, getNamePlusId().c_str());
ALOGV(__FUNCTION__);
if (mIsDisconnected) {
@@ -248,17 +245,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<android::HWComposer::DeviceRequestedChanges> changes;
auto& hwc = getCompositionEngine().getHwComposer();
const bool requiresClientComposition = anyLayersRequireClientComposition();
- if (status_t result =
- hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
- getState().earliestPresentTime,
- getState().previousPresentFence,
- getState().expectedPresentTime, outChanges);
+
+ if (isPowerHintSessionEnabled()) {
+ mPowerAdvisor->setRequiresClientComposition(mId, requiresClientComposition);
+ }
+
+ const TimePoint hwcValidateStartTime = TimePoint::now();
+
+ if (status_t result = hwc.getDeviceCompositionChanges(*halDisplayId, requiresClientComposition,
+ getState().earliestPresentTime,
+ getState().expectedPresentTime,
+ getState().frameInterval, outChanges);
result != NO_ERROR) {
ALOGE("chooseCompositionStrategy failed for %s: %d (%s)", getName().c_str(), result,
strerror(-result));
@@ -266,8 +266,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;
@@ -360,8 +362,8 @@ void Display::applyClientTargetRequests(const ClientTargetProperty& clientTarget
static_cast<ui::PixelFormat>(clientTargetProperty.clientTargetProperty.pixelFormat));
}
-compositionengine::Output::FrameFences Display::presentAndGetFrameFences() {
- auto fences = impl::Output::presentAndGetFrameFences();
+compositionengine::Output::FrameFences Display::presentFrame() {
+ auto fences = impl::Output::presentFrame();
const auto halDisplayIdOpt = HalDisplayId::tryCast(mId);
if (mIsDisconnected || !halDisplayIdOpt) {
@@ -370,21 +372,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,25 +417,26 @@ void Display::setHintSessionGpuFence(std::unique_ptr<FenceTime>&& 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
// 2) There is work to be done (the dirty region isn't empty)
- if (GpuVirtualDisplayId::tryCast(mId) && getDirtyRegion().isEmpty()) {
+ if (GpuVirtualDisplayId::tryCast(mId) && !mustRecompose()) {
ALOGV("Skipping display composition");
return;
}
- impl::Output::finishFrame(refreshArgs, std::move(result));
+ impl::Output::finishFrame(std::move(result));
+}
- if (isPowerHintSessionEnabled()) {
- auto& hwc = getCompositionEngine().getHwComposer();
- if (auto halDisplayId = HalDisplayId::tryCast(mId)) {
- mPowerAdvisor->setSkippedValidate(mId, hwc.getValidateSkipped(*halDisplayId));
- }
+bool Display::supportsOffloadPresent() const {
+ if (const auto halDisplayId = HalDisplayId::tryCast(mId)) {
+ const auto& hwc = getCompositionEngine().getHwComposer();
+ return hwc.hasDisplayCapability(*halDisplayId, DisplayCapability::MULTI_THREADED_PRESENT);
}
+
+ return false;
}
} // namespace android::compositionengine::impl
diff --git a/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp b/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp
index a7c45129b1..f339d41ef8 100644
--- a/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/DisplayColorProfile.cpp
@@ -220,17 +220,6 @@ DisplayColorProfile::DisplayColorProfile(const DisplayColorProfileCreationArgs&
minLuminance = minLuminance <= 0.0 ? sDefaultMinLumiance : minLuminance;
maxLuminance = maxLuminance <= 0.0 ? sDefaultMaxLumiance : maxLuminance;
maxAverageLuminance = maxAverageLuminance <= 0.0 ? sDefaultMaxLumiance : maxAverageLuminance;
- if (args.hasWideColorGamut) {
- // insert HDR10/HLG as we will force client composition for HDR10/HLG
- // layers
- if (!hasHDR10Support()) {
- types.push_back(ui::Hdr::HDR10);
- }
-
- if (!hasHLGSupport()) {
- types.push_back(ui::Hdr::HLG);
- }
- }
mHdrCapabilities = HdrCapabilities(types, maxLuminance, maxAverageLuminance, minLuminance);
}
@@ -391,17 +380,6 @@ bool DisplayColorProfile::isDataspaceSupported(Dataspace dataspace) const {
}
}
-ui::Dataspace DisplayColorProfile::getTargetDataspace(ColorMode mode, Dataspace dataspace,
- Dataspace colorSpaceAgnosticDataspace) const {
- if (isHdrColorMode(mode)) {
- return Dataspace::UNKNOWN;
- }
- if (colorSpaceAgnosticDataspace != ui::Dataspace::UNKNOWN) {
- return colorSpaceAgnosticDataspace;
- }
- return dataspace;
-}
-
void DisplayColorProfile::dump(std::string& out) const {
out.append(" Composition Display Color State:");
@@ -414,6 +392,10 @@ void DisplayColorProfile::dump(std::string& out) const {
dumpVal(out, "dv", hasDolbyVisionSupport());
dumpVal(out, "metadata", getSupportedPerFrameMetadata());
+ out.append("\n Hdr Luminance Info:");
+ dumpVal(out, "desiredMinLuminance", mHdrCapabilities.getDesiredMinLuminance());
+ dumpVal(out, "desiredMaxLuminance", mHdrCapabilities.getDesiredMaxLuminance());
+ dumpVal(out, "desiredMaxAverageLuminance", mHdrCapabilities.getDesiredMaxAverageLuminance());
out.append("\n");
}
diff --git a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
index f95382d921..f0105b2782 100644
--- a/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/HwcBufferCache.cpp
@@ -16,43 +16,75 @@
#include <compositionengine/impl/HwcBufferCache.h>
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-
#include <gui/BufferQueue.h>
#include <ui/GraphicBuffer.h>
-// 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<GraphicBuffer>(nullptr));
+ for (uint32_t i = kMaxLayerBufferCount; i-- > 0;) {
+ mFreeSlots.push(i);
+ }
+}
+
+HwcSlotAndBuffer HwcBufferCache::getHwcSlotAndBuffer(const sp<GraphicBuffer>& 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<GraphicBuffer>& buffer, uint32_t* outSlot,
- sp<GraphicBuffer>* outBuffer) {
- // default is 0
- if (slot == BufferQueue::INVALID_BUFFER_SLOT || slot < 0 ||
- slot >= static_cast<int32_t>(kMaxLayerBufferCount)) {
- *outSlot = 0;
- } else {
- *outSlot = static_cast<uint32_t>(slot);
+HwcSlotAndBuffer HwcBufferCache::getOverrideHwcSlotAndBuffer(const sp<GraphicBuffer>& 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<GraphicBuffer> weakCopy(buffer);
- if (currentBuffer == weakCopy) {
- // already cached in HWC, skip sending the buffer
- *outBuffer = nullptr;
- } else {
- *outBuffer = buffer;
+uint32_t HwcBufferCache::cache(const sp<GraphicBuffer>& 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 6631a2772c..2d10866db3 100644
--- a/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/LayerFECompositionState.cpp
@@ -68,7 +68,7 @@ void LayerFECompositionState::dump(std::string& out) const {
dumpVal(out, "geomLayerBounds", geomLayerBounds);
out.append(" ");
- dumpVal(out, "shadowRadius", shadowRadius);
+ dumpVal(out, "shadowLength", shadowSettings.length);
out.append("\n ");
dumpVal(out, "blend", toString(blendMode), blendMode);
@@ -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 b724daa8ce..55db5b67a2 100644
--- a/services/surfaceflinger/CompositionEngine/src/Output.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/Output.cpp
@@ -29,7 +29,9 @@
#include <compositionengine/impl/OutputLayerCompositionState.h>
#include <compositionengine/impl/planner/Planner.h>
#include <ftl/future.h>
+#include <gui/TraceUtils.h>
+#include <optional>
#include <thread>
#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) {
@@ -255,22 +261,16 @@ void Output::setColorTransform(const compositionengine::CompositionRefreshArgs&
}
void Output::setColorProfile(const ColorProfile& colorProfile) {
- ui::Dataspace targetDataspace =
- getDisplayColorProfile()->getTargetDataspace(colorProfile.mode, colorProfile.dataspace,
- colorProfile.colorSpaceAgnosticDataspace);
-
auto& outputState = editState();
if (outputState.colorMode == colorProfile.mode &&
outputState.dataspace == colorProfile.dataspace &&
- outputState.renderIntent == colorProfile.renderIntent &&
- outputState.targetDataspace == targetDataspace) {
+ outputState.renderIntent == colorProfile.renderIntent) {
return;
}
outputState.colorMode = colorProfile.mode;
outputState.dataspace = colorProfile.dataspace;
outputState.renderIntent = colorProfile.renderIntent;
- outputState.targetDataspace = targetDataspace;
mRenderSurface->setBufferDataspace(colorProfile.dataspace);
@@ -424,10 +424,12 @@ void Output::prepare(const compositionengine::CompositionRefreshArgs& refreshArg
ALOGV(__FUNCTION__);
rebuildLayerStacks(refreshArgs, geomSnapshots);
+ uncacheBuffers(refreshArgs.bufferIdsToUncache);
}
-void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
- ATRACE_CALL();
+ftl::Future<std::monostate> Output::present(
+ const compositionengine::CompositionRefreshArgs& refreshArgs) {
+ ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
ALOGV(__FUNCTION__);
updateColorProfile(refreshArgs);
@@ -440,31 +442,60 @@ 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));
- postFramebuffer();
+ finishFrame(std::move(result));
+ ftl::Future<std::monostate> future;
+ if (mOffloadPresent) {
+ future = presentFrameAndReleaseLayersAsync();
+
+ // Only offload for this frame. The next frame will determine whether it
+ // needs to be offloaded. Leave the HwcAsyncWorker in place. For one thing,
+ // it is currently presenting. Further, it may be needed next frame, and
+ // we don't want to churn.
+ mOffloadPresent = false;
+ } else {
+ presentFrameAndReleaseLayers();
+ future = ftl::yield<std::monostate>({});
+ }
renderCachedSets(refreshArgs);
+ return future;
+}
+
+void Output::offloadPresentNextFrame() {
+ mOffloadPresent = true;
+ updateHwcAsyncWorker();
+}
+
+void Output::uncacheBuffers(std::vector<uint64_t> const& bufferIdsToUncache) {
+ if (bufferIdsToUncache.empty()) {
+ return;
+ }
+ for (auto outputLayer : getOutputLayersOrderedByZ()) {
+ outputLayer->uncacheBuffers(bufferIdsToUncache);
+ }
}
void Output::rebuildLayerStacks(const compositionengine::CompositionRefreshArgs& refreshArgs,
LayerFESet& layerFESet) {
- ATRACE_CALL();
- ALOGV(__FUNCTION__);
-
auto& outputState = editState();
// Do nothing if this output is not enabled or there is no need to perform this update
if (!outputState.isEnabled || CC_LIKELY(!refreshArgs.updatingOutputGeometryThisFrame)) {
return;
}
+ ATRACE_CALL();
+ ALOGV(__FUNCTION__);
// Process the layers to determine visibility and coverage
compositionengine::Output::CoverageState coverage{layerFESet};
+ coverage.aboveCoveredLayersExcludingOverlays = refreshArgs.hasTrustedPresentationListener
+ ? std::make_optional<Region>()
+ : std::nullopt;
collectVisibleLayers(refreshArgs, coverage);
// Compute the resulting coverage for this output, and store it for later
@@ -501,7 +532,6 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& 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 +550,9 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE,
return;
}
+ bool computeAboveCoveredExcludingOverlays = coverage.aboveCoveredLayersExcludingOverlays &&
+ !layerFEState->outputFilter.toInternalDisplay;
+
/*
* opaqueRegion: area of a surface that is fully opaque.
*/
@@ -561,6 +594,11 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE,
*/
Region shadowRegion;
+ /**
+ * covered region above excluding internal display overlay layers
+ */
+ std::optional<Region> coveredRegionExcludingDisplayOverlays = std::nullopt;
+
const ui::Transform& tr = layerFEState->geomLayerTransform;
// Get the visible region
@@ -569,10 +607,10 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& layerFE,
const Rect visibleRect(tr.transform(layerFEState->geomLayerBounds));
visibleRegion.set(visibleRect);
- if (layerFEState->shadowRadius > 0.0f) {
+ if (layerFEState->shadowSettings.length > 0.0f) {
// if the layer casts a shadow, offset the layers visible region and
// calculate the shadow region.
- const auto inset = static_cast<int32_t>(ceilf(layerFEState->shadowRadius) * -1.0f);
+ const auto inset = static_cast<int32_t>(ceilf(layerFEState->shadowSettings.length) * -1.0f);
Rect visibleRectWithShadows(visibleRect);
visibleRectWithShadows.inset(inset, inset, inset, inset);
visibleRegion.set(visibleRectWithShadows);
@@ -633,6 +671,12 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& 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 +763,16 @@ void Output::ensureOutputLayerIfVisible(sp<compositionengine::LayerFE>& 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 +794,44 @@ void Output::updateCompositionState(const compositionengine::CompositionRefreshA
forceClientComposition = false;
}
}
+
+ updateCompositionStateForBorder(refreshArgs);
+}
+
+void Output::updateCompositionStateForBorder(
+ const compositionengine::CompositionRefreshArgs& refreshArgs) {
+ std::unordered_map<int32_t, const Region*> 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,8 +854,9 @@ void Output::writeCompositionState(const compositionengine::CompositionRefreshAr
}
editState().earliestPresentTime = refreshArgs.earliestPresentTime;
- editState().previousPresentFence = refreshArgs.previousPresentFence;
editState().expectedPresentTime = refreshArgs.expectedPresentTime;
+ editState().frameInterval = refreshArgs.frameInterval;
+ editState().powerCallback = refreshArgs.powerCallback;
compositionengine::OutputLayer* peekThroughLayer = nullptr;
sp<GraphicBuffer> previousOverride = nullptr;
@@ -836,13 +915,19 @@ void Output::writeCompositionState(const compositionengine::CompositionRefreshAr
compositionengine::OutputLayer* Output::findLayerRequestingBackgroundComposition() const {
compositionengine::OutputLayer* layerRequestingBgComposition = nullptr;
for (auto* layer : getOutputLayersOrderedByZ()) {
- auto* compState = layer->getLayerFE().getCompositionState();
+ const auto* compState = layer->getLayerFE().getCompositionState();
// If any layer has a sideband stream, we will disable blurs. In that case, we don't
// want to force client composition because of the blur.
if (compState->sidebandStream != nullptr) {
return nullptr;
}
+
+ // If RenderEngine cannot render protected content, we cannot blur.
+ if (compState->hasProtectedContent &&
+ !getCompositionEngine().getRenderEngine().supportsProtectedContent()) {
+ return nullptr;
+ }
if (compState->isOpaque) {
continue;
}
@@ -871,6 +956,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:
@@ -895,7 +987,7 @@ ui::Dataspace Output::getBestDataspace(ui::Dataspace* outHdrDataSpace,
case ui::Dataspace::BT2020_ITU_HLG:
bestDataSpace = ui::Dataspace::DISPLAY_P3;
// When there's mixed PQ content and HLG content, we set the HDR
- // data space to be BT2020_PQ and convert HLG to PQ.
+ // data space to be BT2020_HLG and convert PQ to HLG.
if (*outHdrDataSpace == ui::Dataspace::UNKNOWN) {
*outHdrDataSpace = ui::Dataspace::BT2020_HLG;
}
@@ -912,8 +1004,7 @@ compositionengine::Output::ColorProfile Output::pickColorProfile(
const compositionengine::CompositionRefreshArgs& refreshArgs) const {
if (refreshArgs.outputColorSetting == OutputColorSetting::kUnmanaged) {
return ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
- ui::RenderIntent::COLORIMETRIC,
- refreshArgs.colorSpaceAgnosticDataspace};
+ ui::RenderIntent::COLORIMETRIC};
}
ui::Dataspace hdrDataSpace;
@@ -959,8 +1050,7 @@ compositionengine::Output::ColorProfile Output::pickColorProfile(
mDisplayColorProfile->getBestColorMode(bestDataSpace, intent, &outDataSpace, &outMode,
&outRenderIntent);
- return ColorProfile{outMode, outDataSpace, outRenderIntent,
- refreshArgs.colorSpaceAgnosticDataspace};
+ return ColorProfile{outMode, outDataSpace, outRenderIntent};
}
void Output::beginFrame() {
@@ -977,17 +1067,17 @@ void Output::beginFrame() {
// frame, then nothing more until we get new layers.
// - When a display is created with a private layer stack, we won't
// emit any black frames until a layer is added to the layer stack.
- const bool mustRecompose = dirty && !(empty && wasEmpty);
+ mMustRecompose = dirty && !(empty && wasEmpty);
const char flagPrefix[] = {'-', '+'};
static_cast<void>(flagPrefix);
- ALOGV_IF("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __FUNCTION__,
- mustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty],
- flagPrefix[empty], flagPrefix[wasEmpty]);
+ ALOGV("%s: %s composition for %s (%cdirty %cempty %cwasEmpty)", __func__,
+ mMustRecompose ? "doing" : "skipping", getName().c_str(), flagPrefix[dirty],
+ flagPrefix[empty], flagPrefix[wasEmpty]);
- mRenderSurface->beginFrame(mustRecompose);
+ mRenderSurface->beginFrame(mMustRecompose);
- if (mustRecompose) {
+ if (mMustRecompose) {
outputState.lastCompositionHadVisibleLayers = !empty;
}
}
@@ -1013,13 +1103,21 @@ void Output::prepareFrame() {
finishPrepareFrame();
}
+ftl::Future<std::monostate> Output::presentFrameAndReleaseLayersAsync() {
+ return ftl::Future<bool>(std::move(mHwComposerAsyncWorker->send([&]() {
+ presentFrameAndReleaseLayers();
+ return true;
+ })))
+ .then([](bool) { return std::monostate{}; });
+}
+
std::future<bool> Output::chooseCompositionStrategyAsync(
std::optional<android::HWComposer::DeviceRequestedChanges>* changes) {
return mHwComposerAsyncWorker->send(
[&, changes]() { return chooseCompositionStrategy(changes); });
}
-GpuCompositionResult Output::prepareFrameAsync(const CompositionRefreshArgs& refreshArgs) {
+GpuCompositionResult Output::prepareFrameAsync() {
ATRACE_CALL();
ALOGV(__FUNCTION__);
auto& state = editState();
@@ -1039,7 +1137,7 @@ GpuCompositionResult Output::prepareFrameAsync(const CompositionRefreshArgs& ref
GpuCompositionResult compositionResult;
if (dequeueSucceeded) {
std::optional<base::unique_fd> optFd =
- composeSurfaces(Region::INVALID_REGION, refreshArgs, buffer, bufferFence);
+ composeSurfaces(Region::INVALID_REGION, buffer, bufferFence);
if (optFd) {
compositionResult.fence = std::move(*optFd);
}
@@ -1077,19 +1175,19 @@ void Output::devOptRepaintFlash(const compositionengine::CompositionRefreshArgs&
std::shared_ptr<renderengine::ExternalTexture> buffer;
updateProtectedContentState();
dequeueRenderBuffer(&bufferFence, &buffer);
- static_cast<void>(composeSurfaces(dirtyRegion, refreshArgs, buffer, bufferFence));
- mRenderSurface->queueBuffer(base::unique_fd());
+ static_cast<void>(composeSurfaces(dirtyRegion, buffer, bufferFence));
+ mRenderSurface->queueBuffer(base::unique_fd(), getHdrSdrRatio(buffer));
}
}
- postFramebuffer();
+ presentFrameAndReleaseLayers();
std::this_thread::sleep_for(*refreshArgs.devOptFlashDirtyRegionsDelay);
prepareFrame();
}
-void Output::finishFrame(const CompositionRefreshArgs& refreshArgs, GpuCompositionResult&& result) {
+void Output::finishFrame(GpuCompositionResult&& result) {
ATRACE_CALL();
ALOGV(__FUNCTION__);
const auto& outputState = getState();
@@ -1114,7 +1212,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,10 +1220,11 @@ 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<FenceTime>(new Fence(dup(optReadyFence->get()))));
+ setHintSessionGpuFence(
+ std::make_unique<FenceTime>(sp<Fence>::make(dup(optReadyFence->get()))));
}
// swap buffers (presentation)
- mRenderSurface->queueBuffer(std::move(*optReadyFence));
+ mRenderSurface->queueBuffer(std::move(*optReadyFence), getHdrSdrRatio(buffer));
}
void Output::updateProtectedContentState() {
@@ -1133,23 +1232,26 @@ void Output::updateProtectedContentState() {
auto& renderEngine = getCompositionEngine().getRenderEngine();
const bool supportsProtectedContent = renderEngine.supportsProtectedContent();
- // If we the display is secure, protected content support is enabled, and at
- // least one layer has protected content, we need to use a secure back
- // buffer.
- if (outputState.isSecure && supportsProtectedContent) {
+ bool isProtected;
+ if (FlagManager::getInstance().display_protected()) {
+ isProtected = outputState.isProtected;
+ } else {
+ isProtected = outputState.isSecure;
+ }
+
+ // We need to set the render surface as protected (DRM) if all the following conditions are met:
+ // 1. The display is protected (in legacy, check if the display is secure)
+ // 2. Protected content is supported
+ // 3. At least one layer has protected content.
+ if (isProtected && supportsProtectedContent) {
auto layers = getOutputLayersOrderedByZ();
bool needsProtected = std::any_of(layers.begin(), layers.end(), [](auto* layer) {
- return layer->getLayerFE().getCompositionState()->hasProtectedContent;
+ return layer->getLayerFE().getCompositionState()->hasProtectedContent
+ && layer->requiresClientComposition();
});
- 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 +1275,15 @@ bool Output::dequeueRenderBuffer(base::unique_fd* bufferFence,
}
std::optional<base::unique_fd> Output::composeSurfaces(
- const Region& debugRegion, const compositionengine::CompositionRefreshArgs& refreshArgs,
- std::shared_ptr<renderengine::ExternalTexture> tex, base::unique_fd& fd) {
+ const Region& debugRegion, std::shared_ptr<renderengine::ExternalTexture> tex,
+ base::unique_fd& fd) {
ATRACE_CALL();
ALOGV(__FUNCTION__);
const auto& outputState = getState();
- const TracedOrdinal<bool> hasClientComposition = {"hasClientComposition",
- outputState.usesClientComposition};
+ const TracedOrdinal<bool> hasClientComposition = {
+ base::StringPrintf("hasClientComposition %s", mNamePlusId.c_str()),
+ outputState.usesClientComposition};
if (!hasClientComposition) {
setExpensiveRenderingExpected(false);
return base::unique_fd();
@@ -1195,33 +1298,8 @@ std::optional<base::unique_fd> 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<aidl::android::hardware::graphics::composer3::RenderIntent>(
- outputState.renderIntent);
-
- // Compute the global color transform matrix.
- clientCompositionDisplay.colorTransform = outputState.colorTransformMatrix;
- clientCompositionDisplay.deviceHandlesColorTransform =
- outputState.usesDeviceComposition || getSkipColorTransform();
+ renderengine::DisplaySettings clientCompositionDisplay =
+ generateClientCompositionDisplaySettings(tex);
// Generate the client composition requests for the layers on this output.
auto& renderEngine = getCompositionEngine().getRenderEngine();
@@ -1255,9 +1333,7 @@ std::optional<base::unique_fd> 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) {
@@ -1276,19 +1352,10 @@ std::optional<base::unique_fd> Output::composeSurfaces(
});
const nsecs_t renderEngineStart = systemTime();
- // Only use the framebuffer cache when rendering to an internal display
- // TODO(b/173560331): This is only to help mitigate memory leaks from virtual displays because
- // right now we don't have a concrete eviction policy for output buffers: GLESRenderEngine
- // bounds its framebuffer cache but Skia RenderEngine has no current policy. The best fix is
- // probably to encapsulate the output buffer into a structure that dispatches resource cleanup
- // 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,
+ std::move(fd))
+ .get();
if (mClientCompositionRequestCache && fenceStatus(fenceResult) != NO_ERROR) {
// If rendering was not successful, remove the request from the cache.
@@ -1297,10 +1364,13 @@ std::optional<base::unique_fd> 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<FenceTime>(fence));
- } else {
- timeStats.recordRenderEngineDuration(renderEngineStart, systemTime());
+ if (auto timeStats = getCompositionEngine().getTimeStats()) {
+ if (fence->isValid()) {
+ timeStats->recordRenderEngineDuration(renderEngineStart,
+ std::make_shared<FenceTime>(fence));
+ } else {
+ timeStats->recordRenderEngineDuration(renderEngineStart, systemTime());
+ }
}
for (auto* clientComposedLayer : clientCompositionLayersFE) {
@@ -1310,6 +1380,50 @@ std::optional<base::unique_fd> Output::composeSurfaces(
return base::unique_fd(fence->dup());
}
+renderengine::DisplaySettings Output::generateClientCompositionDisplaySettings(
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer) 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();
+
+ float hdrSdrRatioMultiplier = 1.0f / getHdrSdrRatio(buffer);
+ clientCompositionDisplay.targetLuminanceNits = outputState.clientTargetBrightness *
+ outputState.displayBrightnessNits * hdrSdrRatioMultiplier;
+ clientCompositionDisplay.dimmingStage = outputState.clientTargetDimmingStage;
+ clientCompositionDisplay.renderIntent =
+ static_cast<aidl::android::hardware::graphics::composer3::RenderIntent>(
+ 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<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
bool supportsProtectedContent, ui::Dataspace outputDataspace, std::vector<LayerFE*>& outLayerFEs) {
std::vector<LayerFE::LayerSettings> clientCompositionLayers;
@@ -1320,7 +1434,7 @@ std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
bool firstLayer = true;
bool disableBlurs = false;
- sp<GraphicBuffer> previousOverrideBuffer = nullptr;
+ uint64_t previousOverrideBufferId = 0;
for (auto* layer : getOutputLayersOrderedByZ()) {
const auto& layerState = layer->getState();
@@ -1356,11 +1470,10 @@ std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
!layerState.visibleRegion.subtract(layerState.shadowRegion).isEmpty();
if (clientComposition || clearClientComposition) {
- std::vector<LayerFE::LayerSettings> 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",
@@ -1374,32 +1487,35 @@ std::vector<LayerFE::LayerSettings> Output::generateClientCompositionRequests(
BlurRegionsOnly
: LayerFE::ClientCompositionTargetSettings::BlurSetting::
Enabled);
+ bool isProtected = supportsProtectedContent;
+ if (FlagManager::getInstance().display_protected()) {
+ isProtected = outputState.isProtected && supportsProtectedContent;
+ }
compositionengine::LayerFE::ClientCompositionTargetSettings
targetSettings{.clip = clip,
.needsFiltering = layer->needsFiltering() ||
outputState.needsFiltering,
.isSecure = outputState.isSecure,
- .supportsProtectedContent = supportsProtectedContent,
+ .isProtected = isProtected,
.viewport = outputState.layerStackSpace.getContent(),
.dataspace = outputDataspace,
.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;
@@ -1437,8 +1553,8 @@ bool Output::isPowerHintSessionEnabled() {
return false;
}
-void Output::postFramebuffer() {
- ATRACE_CALL();
+void Output::presentFrameAndReleaseLayers() {
+ ATRACE_FORMAT("%s for %s", __func__, mNamePlusId.c_str());
ALOGV(__FUNCTION__);
if (!getState().isEnabled) {
@@ -1447,9 +1563,8 @@ void Output::postFramebuffer() {
auto& outputState = editState();
outputState.dirtyRegion.clear();
- mRenderSurface->flip();
- auto frame = presentAndGetFrameFences();
+ auto frame = presentFrame();
mRenderSurface->onPresentDisplayCompleted();
@@ -1475,8 +1590,9 @@ void Output::postFramebuffer() {
releaseFence =
Fence::merge("LayerRelease", releaseFence, frame.clientTargetAcquireFence);
}
- layer->getLayerFE().onLayerDisplayed(
- ftl::yield<FenceResult>(std::move(releaseFence)).share());
+ layer->getLayerFE()
+ .onLayerDisplayed(ftl::yield<FenceResult>(std::move(releaseFence)).share(),
+ outputState.layerFilter.layerStack);
}
// We've got a list of layers needing fences, that are disjoint with
@@ -1484,7 +1600,8 @@ void Output::postFramebuffer() {
// supply them with the present fence.
for (auto& weakLayer : mReleasedLayers) {
if (const auto layer = weakLayer.promote()) {
- layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share());
+ layer->onLayerDisplayed(ftl::yield<FenceResult>(frame.presentFence).share(),
+ outputState.layerFilter.layerStack);
}
}
@@ -1493,9 +1610,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());
}
}
@@ -1516,7 +1634,7 @@ bool Output::getSkipColorTransform() const {
return true;
}
-compositionengine::Output::FrameFences Output::presentAndGetFrameFences() {
+compositionengine::Output::FrameFences Output::presentFrame() {
compositionengine::Output::FrameFences result;
if (getState().usesClientComposition) {
result.clientTargetAcquireFence = mRenderSurface->getClientTargetAcquireFence();
@@ -1525,8 +1643,15 @@ compositionengine::Output::FrameFences Output::presentAndGetFrameFences() {
}
void Output::setPredictCompositionStrategy(bool predict) {
- if (predict) {
- mHwComposerAsyncWorker = std::make_unique<HwcAsyncWorker>();
+ mPredictCompositionStrategy = predict;
+ updateHwcAsyncWorker();
+}
+
+void Output::updateHwcAsyncWorker() {
+ if (mPredictCompositionStrategy || mOffloadPresent) {
+ if (!mHwComposerAsyncWorker) {
+ mHwComposerAsyncWorker = std::make_unique<HwcAsyncWorker>();
+ }
} else {
mHwComposerAsyncWorker.reset(nullptr);
}
@@ -1541,7 +1666,7 @@ bool Output::canPredictCompositionStrategy(const CompositionRefreshArgs& refresh
uint64_t outputLayerHash = getState().outputLayerHash;
editState().lastOutputLayerHash = outputLayerHash;
- if (!getState().isEnabled || !mHwComposerAsyncWorker) {
+ if (!getState().isEnabled || !mPredictCompositionStrategy) {
ALOGV("canPredictCompositionStrategy disabled");
return false;
}
@@ -1590,5 +1715,29 @@ void Output::finishPrepareFrame() {
mRenderSurface->prepareFrame(state.usesClientComposition, state.usesDeviceComposition);
}
+bool Output::mustRecompose() const {
+ return mMustRecompose;
+}
+
+float Output::getHdrSdrRatio(const std::shared_ptr<renderengine::ExternalTexture>& buffer) const {
+ if (buffer == nullptr) {
+ return 1.0f;
+ }
+
+ if (!FlagManager::getInstance().fp16_client_target()) {
+ return 1.0f;
+ }
+
+ if (getState().displayBrightnessNits < 0.0f || getState().sdrWhitePointNits <= 0.0f ||
+ buffer->getPixelFormat() != PIXEL_FORMAT_RGBA_FP16 ||
+ (static_cast<int32_t>(getState().dataspace) &
+ static_cast<int32_t>(ui::Dataspace::RANGE_MASK)) !=
+ static_cast<int32_t>(ui::Dataspace::RANGE_EXTENDED)) {
+ return 1.0f;
+ }
+
+ return getState().displayBrightnessNits / getState().sdrWhitePointNits;
+}
+
} // namespace impl
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
index 948c0c90bf..39cf67165a 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputCompositionState.cpp
@@ -52,7 +52,6 @@ void OutputCompositionState::dump(std::string& out) const {
dumpVal(out, "colorMode", toString(colorMode), colorMode);
dumpVal(out, "renderIntent", toString(renderIntent), renderIntent);
dumpVal(out, "dataspace", toString(dataspace), dataspace);
- dumpVal(out, "targetDataspace", toString(targetDataspace), targetDataspace);
out.append("\n ");
dumpVal(out, "colorTransformMatrix", colorTransformMatrix);
@@ -62,14 +61,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);
+ dumpVal(out, "treat170mAsSrgb", 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 1bb9d0eb63..7fe3369f88 100644
--- a/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/OutputLayer.cpp
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
#include <DisplayHardware/Hal.h>
#include <android-base/stringprintf.h>
#include <compositionengine/DisplayColorProfile.h>
@@ -26,7 +25,7 @@
#include <cstdint>
#include "system/graphics-base-v1.0.h"
-#include <ui/DataspaceUtils.h>
+#include <ui/HdrRenderTypeUtils.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
@@ -223,8 +222,8 @@ Rect OutputLayer::calculateOutputDisplayFrame() const {
// Some HWCs may clip client composited input to its displayFrame. Make sure
// that this does not cut off the shadow.
- if (layerState.forceClientComposition && layerState.shadowRadius > 0.0f) {
- const auto outset = layerState.shadowRadius;
+ if (layerState.forceClientComposition && layerState.shadowSettings.length > 0.0f) {
+ const auto outset = layerState.shadowSettings.length;
geomLayerBounds.left -= outset;
geomLayerBounds.top -= outset;
geomLayerBounds.right += outset;
@@ -312,12 +311,20 @@ void OutputLayer::updateCompositionState(
}
}
+ auto pixelFormat = layerFEState->buffer ? std::make_optional(static_cast<ui::PixelFormat>(
+ layerFEState->buffer->getPixelFormat()))
+ : std::nullopt;
+
+ auto hdrRenderType =
+ getHdrRenderType(outputState.dataspace, pixelFormat, layerFEState->desiredHdrSdrRatio);
+
// Determine the output dependent dataspace for this layer. If it is
// colorspace agnostic, it just uses the dataspace chosen for the output to
// avoid the need for color conversion.
- state.dataspace = layerFEState->isColorspaceAgnostic &&
- outputState.targetDataspace != ui::Dataspace::UNKNOWN
- ? outputState.targetDataspace
+ // For now, also respect the colorspace agnostic flag if we're drawing to HDR, to avoid drastic
+ // luminance shift. TODO(b/292162273): we should check if that's true though.
+ state.dataspace = layerFEState->isColorspaceAgnostic && hdrRenderType == HdrRenderType::SDR
+ ? outputState.dataspace
: layerFEState->dataspace;
// Override the dataspace transfer from 170M to sRGB if the device configuration requests this.
@@ -331,19 +338,29 @@ void OutputLayer::updateCompositionState(
(state.dataspace & HAL_DATASPACE_RANGE_MASK) | HAL_DATASPACE_TRANSFER_SRGB);
}
+ // re-get HdrRenderType after the dataspace gets changed.
+ hdrRenderType =
+ getHdrRenderType(state.dataspace, pixelFormat, layerFEState->desiredHdrSdrRatio);
+
// For hdr content, treat the white point as the display brightness - HDR content should not be
// boosted or dimmed.
// If the layer explicitly requests to disable dimming, then don't dim either.
- if (isHdrDataspace(state.dataspace) ||
+ if (hdrRenderType == HdrRenderType::GENERIC_HDR ||
getOutput().getState().displayBrightnessNits == getOutput().getState().sdrWhitePointNits ||
getOutput().getState().displayBrightnessNits == 0.f || !layerFEState->dimmingEnabled) {
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 (hdrRenderType == HdrRenderType::DISPLAY_HDR) {
+ 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 +595,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 +628,40 @@ void OutputLayer::writeSidebandStateToHWC(HWC2::Layer* hwcLayer,
}
}
+void OutputLayer::uncacheBuffers(const std::vector<uint64_t>& 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<uint32_t> 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<int32_t>(error));
+ }
+}
+
void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer,
const LayerFECompositionState& outputIndependentState,
bool skipLayer) {
@@ -622,27 +674,37 @@ void OutputLayer::writeBufferStateToHWC(HWC2::Layer* hwcLayer,
to_string(error).c_str(), static_cast<int32_t>(error));
}
- sp<GraphicBuffer> buffer = outputIndependentState.buffer;
- sp<Fence> 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<Fence> 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<GraphicBuffer> 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<int32_t>(error));
+ ALOGE("[%s] Failed to set buffer %p: %s (%d)", getLayerFE().getDebugName(),
+ hwcSlotAndBuffer.buffer->handle, to_string(error).c_str(),
+ static_cast<int32_t>(error));
}
}
@@ -729,6 +791,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;
}
@@ -781,13 +844,19 @@ void OutputLayer::applyDeviceLayerRequest(hal::LayerRequest request) {
bool OutputLayer::needsFiltering() const {
const auto& state = getState();
- const auto& displayFrame = state.displayFrame;
const auto& sourceCrop = state.sourceCrop;
- return sourceCrop.getHeight() != displayFrame.getHeight() ||
- sourceCrop.getWidth() != displayFrame.getWidth();
+ auto displayFrameWidth = static_cast<float>(state.displayFrame.getWidth());
+ auto displayFrameHeight = static_cast<float>(state.displayFrame.getHeight());
+
+ if (state.bufferTransform & HAL_TRANSFORM_ROT_90) {
+ std::swap(displayFrameWidth, displayFrameHeight);
+ }
+
+ return sourceCrop.getHeight() != displayFrameHeight ||
+ sourceCrop.getWidth() != displayFrameWidth;
}
-std::vector<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionList() const {
+std::optional<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionSettings() const {
if (getState().overrideInfo.buffer == nullptr) {
return {};
}
@@ -816,7 +885,7 @@ std::vector<LayerFE::LayerSettings> OutputLayer::getOverrideCompositionList() co
settings.alpha = 1.0f;
settings.whitePointNits = getOutput().getState().sdrWhitePointNits;
- return {static_cast<LayerFE::LayerSettings>(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 5a3af7bfea..c0b23d97d4 100644
--- a/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/RenderSurface.cpp
@@ -198,7 +198,7 @@ std::shared_ptr<renderengine::ExternalTexture> RenderSurface::dequeueBuffer(
return mTexture;
}
-void RenderSurface::queueBuffer(base::unique_fd readyFence) {
+void RenderSurface::queueBuffer(base::unique_fd readyFence, float hdrSdrRatio) {
auto& state = mDisplay.getState();
if (state.usesClientComposition || state.flipClientTarget) {
@@ -241,7 +241,7 @@ void RenderSurface::queueBuffer(base::unique_fd readyFence) {
}
}
- status_t result = mDisplaySurface->advanceFrame();
+ status_t result = mDisplaySurface->advanceFrame(hdrSdrRatio);
if (result != NO_ERROR) {
ALOGE("[%s] failed pushing new frame to HWC: %d", mDisplay.getName().c_str(), result);
}
@@ -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 d6f02ee42a..869dda61bb 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/CachedSet.cpp
@@ -162,6 +162,9 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te
const OutputCompositionState& outputState,
bool deviceHandlesColorTransform) {
ATRACE_CALL();
+ if (outputState.powerCallback) {
+ outputState.powerCallback->notifyCpuLoadUp();
+ }
const Rect& viewport = outputState.layerStackSpace.getContent();
const ui::Dataspace& outputDataspace = outputState.dataspace;
const ui::Transform::RotationFlags orientation =
@@ -177,27 +180,28 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te
.targetLuminanceNits = outputState.displayBrightnessNits,
};
- LayerFE::ClientCompositionTargetSettings targetSettings{
- .clip = Region(viewport),
- .needsFiltering = false,
- .isSecure = outputState.isSecure,
- .supportsProtectedContent = false,
- .viewport = viewport,
- .dataspace = outputDataspace,
- .realContentIsVisible = true,
- .clearContent = false,
- .blurSetting = LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
- .whitePointNits = outputState.displayBrightnessNits,
- };
+ LayerFE::ClientCompositionTargetSettings
+ targetSettings{.clip = Region(viewport),
+ .needsFiltering = false,
+ .isSecure = outputState.isSecure,
+ .isProtected = false,
+ .viewport = viewport,
+ .dataspace = outputDataspace,
+ .realContentIsVisible = true,
+ .clearContent = false,
+ .blurSetting =
+ LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
+ .whitePointNits = outputState.displayBrightnessNits,
+ .treat170mAsSrgb = outputState.treat170mAsSrgb};
std::vector<renderengine::LayerSettings> 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 +209,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) {
@@ -274,13 +275,10 @@ void CachedSet::render(renderengine::RenderEngine& renderEngine, TexturePool& te
bufferFence.reset(texture->getReadyFence()->dup());
}
- 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(),
+ std::move(bufferFence))
+ .get();
if (fenceStatus(fenceResult) == NO_ERROR) {
mDrawFence = std::move(fenceResult).value_or(Fence::NO_FENCE);
@@ -382,6 +380,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 +399,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 +429,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<GraphicBuffer> 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 +440,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<GraphicBuffer> 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 9175dd01a1..a18397d8ba 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -20,6 +20,7 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <android-base/properties.h>
+#include <common/FlagManager.h>
#include <compositionengine/impl/planner/Flattener.h>
#include <compositionengine/impl/planner/LayerState.h>
@@ -50,8 +51,19 @@ bool isSameStack(const std::vector<const LayerState*>& incomingLayers,
for (size_t i = 0; i < incomingLayers.size(); i++) {
// Checking the IDs here is very strict, but we do this as otherwise we may mistakenly try
// to access destroyed OutputLayers later on.
- if (incomingLayers[i]->getId() != existingLayers[i]->getId() ||
- incomingLayers[i]->getDifferingFields(*(existingLayers[i])) != LayerStateField::None) {
+ if (incomingLayers[i]->getId() != existingLayers[i]->getId()) {
+ return false;
+ }
+
+ // Do not unflatten if source crop is only moved.
+ if (FlagManager::getInstance().cache_if_source_crop_layer_only_moved() &&
+ incomingLayers[i]->isSourceCropSizeEqual(*(existingLayers[i])) &&
+ incomingLayers[i]->getDifferingFields(*(existingLayers[i])) ==
+ LayerStateField::SourceCrop) {
+ continue;
+ }
+
+ if (incomingLayers[i]->getDifferingFields(*(existingLayers[i])) != LayerStateField::None) {
return false;
}
}
@@ -413,6 +425,7 @@ std::vector<Flattener::Run> 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 +437,8 @@ std::vector<Flattener::Run> 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/LayerState.cpp b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
index f439caf9e1..0e3fdbb0dc 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/LayerState.cpp
@@ -34,7 +34,7 @@ LayerState::LayerState(compositionengine::OutputLayer* layer)
[](const mat4& mat) {
using namespace std::string_literals;
std::vector<std::string> split =
- base::Split(std::string(mat.asString().string()), "\n"s);
+ base::Split(std::string(mat.asString().c_str()), "\n"s);
split.pop_back(); // Strip the last (empty) line
return split;
}}) {
@@ -76,6 +76,11 @@ size_t LayerState::getHash() const {
return hash;
}
+bool LayerState::isSourceCropSizeEqual(const LayerState& other) const {
+ return mSourceCrop.get().getWidth() == other.mSourceCrop.get().getWidth() &&
+ mSourceCrop.get().getHeight() == other.mSourceCrop.get().getHeight();
+}
+
ftl::Flags<LayerStateField> LayerState::getDifferingFields(const LayerState& other) const {
ftl::Flags<LayerStateField> differences;
auto myFields = getNonUniqueFields();
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
index 54133d92b0..5e6cade56f 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Planner.cpp
@@ -216,32 +216,32 @@ void Planner::dump(const Vector<String16>& args, std::string& result) {
base::StringAppendF(&result,
"Expected two layer stack hashes, e.g. '--planner %s "
"<left_hash> <right_hash>'\n",
- command.string());
+ command.c_str());
return;
}
if (args.size() > 4) {
base::StringAppendF(&result,
"Too many arguments found, expected '--planner %s <left_hash> "
"<right_hash>'\n",
- command.string());
+ command.c_str());
return;
}
const String8 leftHashString(args[2]);
size_t leftHash = 0;
- int fieldsRead = sscanf(leftHashString.string(), "%zx", &leftHash);
+ int fieldsRead = sscanf(leftHashString.c_str(), "%zx", &leftHash);
if (fieldsRead != 1) {
base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
- leftHashString.string());
+ leftHashString.c_str());
return;
}
const String8 rightHashString(args[3]);
size_t rightHash = 0;
- fieldsRead = sscanf(rightHashString.string(), "%zx", &rightHash);
+ fieldsRead = sscanf(rightHashString.c_str(), "%zx", &rightHash);
if (fieldsRead != 1) {
base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
- rightHashString.string());
+ rightHashString.c_str());
return;
}
@@ -252,22 +252,22 @@ void Planner::dump(const Vector<String16>& args, std::string& result) {
if (args.size() < 3) {
base::StringAppendF(&result,
"Expected a layer stack hash, e.g. '--planner %s <hash>'\n",
- command.string());
+ command.c_str());
return;
}
if (args.size() > 3) {
base::StringAppendF(&result,
"Too many arguments found, expected '--planner %s <hash>'\n",
- command.string());
+ command.c_str());
return;
}
const String8 hashString(args[2]);
size_t hash = 0;
- const int fieldsRead = sscanf(hashString.string(), "%zx", &hash);
+ const int fieldsRead = sscanf(hashString.c_str(), "%zx", &hash);
if (fieldsRead != 1) {
base::StringAppendF(&result, "Failed to parse %s as a size_t\n",
- hashString.string());
+ hashString.c_str());
return;
}
@@ -279,20 +279,20 @@ void Planner::dump(const Vector<String16>& args, std::string& result) {
} else if (command == "--similar" || command == "-s") {
if (args.size() < 3) {
base::StringAppendF(&result, "Expected a plan string, e.g. '--planner %s <plan>'\n",
- command.string());
+ command.c_str());
return;
}
if (args.size() > 3) {
base::StringAppendF(&result,
"Too many arguments found, expected '--planner %s <plan>'\n",
- command.string());
+ command.c_str());
return;
}
const String8 planString(args[2]);
- std::optional<Plan> plan = Plan::fromString(std::string(planString.string()));
+ std::optional<Plan> plan = Plan::fromString(std::string(planString.c_str()));
if (!plan) {
- base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.string());
+ base::StringAppendF(&result, "Failed to parse %s as a Plan\n", planString.c_str());
return;
}
@@ -302,7 +302,7 @@ void Planner::dump(const Vector<String16>& args, std::string& result) {
} else if (command == "--layers" || command == "-l") {
mFlattener.dumpLayers(result);
} else {
- base::StringAppendF(&result, "Unknown command '%s'\n\n", command.string());
+ base::StringAppendF(&result, "Unknown command '%s'\n\n", command.c_str());
dumpUsage(result);
}
return;
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Predictor.cpp
index 2fc029fdc6..6064126099 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 de9de0150d..da578e2046 100644
--- a/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/CompositionEngineTest.cpp
@@ -14,18 +14,25 @@
* limitations under the License.
*/
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/LayerFECompositionState.h>
#include <compositionengine/impl/CompositionEngine.h>
#include <compositionengine/mock/LayerFE.h>
#include <compositionengine/mock/Output.h>
#include <compositionengine/mock/OutputLayer.h>
+#include <ftl/future.h>
#include <gtest/gtest.h>
#include <renderengine/mock/RenderEngine.h>
#include "MockHWComposer.h"
#include "TimeStats/TimeStats.h"
+#include <variant>
+
+using namespace com::android::graphics::surfaceflinger;
+
namespace android::compositionengine {
namespace {
@@ -62,17 +69,16 @@ TEST_F(CompositionEngineTest, canSetHWComposer) {
}
TEST_F(CompositionEngineTest, canSetRenderEngine) {
- renderengine::mock::RenderEngine* renderEngine =
- new StrictMock<renderengine::mock::RenderEngine>();
- mEngine.setRenderEngine(std::unique_ptr<renderengine::RenderEngine>(renderEngine));
+ auto renderEngine = std::make_unique<StrictMock<renderengine::mock::RenderEngine>>();
+ 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,16 +114,17 @@ 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)));
+ // All of mOutput<i> are StrictMocks. If the flag is true, it will introduce
+ // calls to getDisplayId, which are not relevant to this test.
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
// The last step is to actually present each output.
- EXPECT_CALL(*mOutput1, present(Ref(mRefreshArgs)));
- EXPECT_CALL(*mOutput2, present(Ref(mRefreshArgs)));
- EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)));
+ EXPECT_CALL(*mOutput1, present(Ref(mRefreshArgs)))
+ .WillOnce(Return(ftl::yield<std::monostate>({})));
+ EXPECT_CALL(*mOutput2, present(Ref(mRefreshArgs)))
+ .WillOnce(Return(ftl::yield<std::monostate>({})));
+ EXPECT_CALL(*mOutput3, present(Ref(mRefreshArgs)))
+ .WillOnce(Return(ftl::yield<std::monostate>({})));
mRefreshArgs.outputs = {mOutput1, mOutput2, mOutput3};
mEngine.present(mRefreshArgs);
@@ -175,21 +182,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 +226,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 +245,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 +262,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};
@@ -267,5 +274,214 @@ TEST_F(CompositionTestPreComposition,
EXPECT_TRUE(mEngine.needsAnotherUpdate());
}
+struct CompositionEngineOffloadTest : public testing::Test {
+ impl::CompositionEngine mEngine;
+ CompositionRefreshArgs mRefreshArgs;
+
+ std::shared_ptr<mock::Output> mDisplay1{std::make_shared<StrictMock<mock::Output>>()};
+ std::shared_ptr<mock::Output> mDisplay2{std::make_shared<StrictMock<mock::Output>>()};
+ std::shared_ptr<mock::Output> mVirtualDisplay{std::make_shared<StrictMock<mock::Output>>()};
+ std::shared_ptr<mock::Output> mHalVirtualDisplay{std::make_shared<StrictMock<mock::Output>>()};
+
+ static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(123u);
+ static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(234u);
+ static constexpr GpuVirtualDisplayId kGpuVirtualDisplayId{789u};
+ static constexpr HalVirtualDisplayId kHalVirtualDisplayId{456u};
+
+ std::array<impl::OutputCompositionState, 4> mOutputStates;
+
+ void SetUp() override {
+ EXPECT_CALL(*mDisplay1, getDisplayId)
+ .WillRepeatedly(Return(std::make_optional<DisplayId>(kDisplayId1)));
+ EXPECT_CALL(*mDisplay2, getDisplayId)
+ .WillRepeatedly(Return(std::make_optional<DisplayId>(kDisplayId2)));
+ EXPECT_CALL(*mVirtualDisplay, getDisplayId)
+ .WillRepeatedly(Return(std::make_optional<DisplayId>(kGpuVirtualDisplayId)));
+ EXPECT_CALL(*mHalVirtualDisplay, getDisplayId)
+ .WillRepeatedly(Return(std::make_optional<DisplayId>(kHalVirtualDisplayId)));
+
+ // Most tests will depend on the outputs being enabled.
+ for (auto& state : mOutputStates) {
+ state.isEnabled = true;
+ }
+
+ EXPECT_CALL(*mDisplay1, getState).WillRepeatedly(ReturnRef(mOutputStates[0]));
+ EXPECT_CALL(*mDisplay2, getState).WillRepeatedly(ReturnRef(mOutputStates[1]));
+ EXPECT_CALL(*mVirtualDisplay, getState).WillRepeatedly(ReturnRef(mOutputStates[2]));
+ EXPECT_CALL(*mHalVirtualDisplay, getState).WillRepeatedly(ReturnRef(mOutputStates[3]));
+ }
+
+ void setOutputs(std::initializer_list<std::shared_ptr<mock::Output>> outputs) {
+ for (auto& output : outputs) {
+ // If we call mEngine.present, prepare and present will be called on all the
+ // outputs in mRefreshArgs, but that's not the interesting part of the test.
+ EXPECT_CALL(*output, prepare(Ref(mRefreshArgs), _)).Times(1);
+ EXPECT_CALL(*output, present(Ref(mRefreshArgs)))
+ .WillOnce(Return(ftl::yield<std::monostate>({})));
+
+ mRefreshArgs.outputs.push_back(std::move(output));
+ }
+ }
+};
+
+TEST_F(CompositionEngineOffloadTest, basic) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(true));
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mDisplay2});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnSupport) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(false));
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).Times(0);
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mDisplay2});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnSupport2) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(false));
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mDisplay2});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnFlag) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).Times(0);
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).Times(0);
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
+ setOutputs({mDisplay1, mDisplay2});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, oneDisplay) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).Times(0);
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, virtualDisplay) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mVirtualDisplay, supportsOffloadPresent).Times(0);
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mDisplay2, mVirtualDisplay});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, virtualDisplay2) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mVirtualDisplay, supportsOffloadPresent).Times(0);
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mVirtualDisplay});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, halVirtual) {
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mHalVirtualDisplay, supportsOffloadPresent).WillOnce(Return(true));
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+ EXPECT_CALL(*mHalVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mHalVirtualDisplay});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, ordering) {
+ EXPECT_CALL(*mVirtualDisplay, supportsOffloadPresent).Times(0);
+ EXPECT_CALL(*mHalVirtualDisplay, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillOnce(Return(true));
+
+ EXPECT_CALL(*mVirtualDisplay, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mHalVirtualDisplay, offloadPresentNextFrame).Times(1);
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mVirtualDisplay, mHalVirtualDisplay, mDisplay1, mDisplay2});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, dependsOnEnabled) {
+ // Disable mDisplay2.
+ mOutputStates[1].isEnabled = false;
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+
+ // This is not actually called, because it is not enabled, but this distinguishes
+ // from the case where it did not return true.
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillRepeatedly(Return(true));
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mDisplay2});
+
+ mEngine.present(mRefreshArgs);
+}
+
+TEST_F(CompositionEngineOffloadTest, disabledDisplaysDoNotPreventOthersFromOffloading) {
+ // Disable mDisplay2.
+ mOutputStates[1].isEnabled = false;
+ EXPECT_CALL(*mDisplay1, supportsOffloadPresent).WillOnce(Return(true));
+
+ // This is not actually called, because it is not enabled, but this distinguishes
+ // from the case where it did not return true.
+ EXPECT_CALL(*mDisplay2, supportsOffloadPresent).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mHalVirtualDisplay, supportsOffloadPresent).WillOnce(Return(true));
+
+ EXPECT_CALL(*mDisplay1, offloadPresentNextFrame).Times(1);
+ EXPECT_CALL(*mDisplay2, offloadPresentNextFrame).Times(0);
+ EXPECT_CALL(*mHalVirtualDisplay, offloadPresentNextFrame).Times(0);
+
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ setOutputs({mDisplay1, mDisplay2, mHalVirtualDisplay});
+
+ mEngine.present(mRefreshArgs);
+}
+
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
index 21b9aa93ea..03a97dc770 100644
--- a/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/DisplayColorProfileTest.cpp
@@ -282,39 +282,6 @@ TEST_F(DisplayColorProfileTest, ctorUsesOrDefaultsLuminanceValuesFromInputArgs)
}
}
-TEST_F(DisplayColorProfileTest, ctorSignalsHdrSupportForAnyWideColorGamutDevice) {
- {
- // If the output does not profile wide color gamut, then no HDR modes
- // will be profileed in the generated HDR capabilities.
- auto profile = ProfileFactory().setHasWideColorGamut(false).build();
-
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), IsEmpty());
- }
-
- {
- // If the HWC does not show profile for certain HDR modes, then the
- // generated HDR capabilities will indicate profile anyway.
- auto profile = ProfileFactory().setHasWideColorGamut(true).build();
-
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), SizeIs(2));
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HDR10));
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HLG));
- }
-
- {
- // If the HWC profiles the HDR modes, then the generated capabilities
- // still has one entry for each HDR type.
- auto profile = ProfileFactory()
- .setHasWideColorGamut(true)
- .addHdrTypes({Hdr::HLG, Hdr::HDR10})
- .build();
-
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), SizeIs(2));
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HDR10));
- EXPECT_THAT(profile.getHdrCapabilities().getSupportedHdrTypes(), Contains(Hdr::HLG));
- }
-}
-
/* ------------------------------------------------------------------------
* DisplayColorProfile::hasRenderIntent
*/
@@ -646,29 +613,5 @@ TEST_F(DisplayColorProfileTest, isDataspaceSupportedWorksForProfileWithHlgSuppor
EXPECT_TRUE(profile.isDataspaceSupported(Dataspace::BT2020_ITU_HLG));
}
-/*
- * RenderSurface::getTargetDataspace()
- */
-
-TEST_F(DisplayColorProfileTest, getTargetDataspaceWorks) {
- auto profile = ProfileFactory::createProfileWithNoColorModeSupport();
-
- // For a non-HDR colorspace with no colorSpaceAgnosticDataspace override,
- // the input dataspace should be returned.
- EXPECT_EQ(Dataspace::DISPLAY_P3,
- profile.getTargetDataspace(ColorMode::DISPLAY_P3, Dataspace::DISPLAY_P3,
- Dataspace::UNKNOWN));
-
- // If colorSpaceAgnosticDataspace is set, its value should be returned
- EXPECT_EQ(Dataspace::V0_SRGB,
- profile.getTargetDataspace(ColorMode::DISPLAY_P3, Dataspace::DISPLAY_P3,
- Dataspace::V0_SRGB));
-
- // For an HDR colorspace, Dataspace::UNKNOWN should be returned.
- EXPECT_EQ(Dataspace::UNKNOWN,
- profile.getTargetDataspace(ColorMode::BT2100_PQ, Dataspace::BT2020_PQ,
- Dataspace::UNKNOWN));
-}
-
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp b/services/surfaceflinger/CompositionEngine/tests/DisplayTest.cpp
index 344fea3331..a95a5c62fd 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<Hwc2::mock::PowerAdvisor> mPowerAdvisor;
StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
StrictMock<mock::CompositionEngine> mCompositionEngine;
- sp<mock::NativeWindow> mNativeWindow = new StrictMock<mock::NativeWindow>();
+ sp<mock::NativeWindow> mNativeWindow = sp<StrictMock<mock::NativeWindow>>::make();
};
struct PartialMockDisplayTestCommon : public DisplayTestCommon {
@@ -403,23 +403,18 @@ TEST_F(DisplaySetColorModeTest, setsModeUnlessNoChange) {
mock::DisplayColorProfile* colorProfile = new StrictMock<mock::DisplayColorProfile>();
mDisplay->setDisplayColorProfileForTest(std::unique_ptr<DisplayColorProfile>(colorProfile));
- EXPECT_CALL(*colorProfile, getTargetDataspace(_, _, _))
- .WillRepeatedly(Return(ui::Dataspace::UNKNOWN));
-
// These values are expected to be the initial state.
ASSERT_EQ(ui::ColorMode::NATIVE, mDisplay->getState().colorMode);
ASSERT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().dataspace);
ASSERT_EQ(ui::RenderIntent::COLORIMETRIC, mDisplay->getState().renderIntent);
- ASSERT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().targetDataspace);
// Otherwise if the values are unchanged, nothing happens
mDisplay->setColorProfile(ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
- ui::RenderIntent::COLORIMETRIC, ui::Dataspace::UNKNOWN});
+ ui::RenderIntent::COLORIMETRIC});
EXPECT_EQ(ui::ColorMode::NATIVE, mDisplay->getState().colorMode);
EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().dataspace);
EXPECT_EQ(ui::RenderIntent::COLORIMETRIC, mDisplay->getState().renderIntent);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().targetDataspace);
// Otherwise if the values are different, updates happen
EXPECT_CALL(*renderSurface, setBufferDataspace(ui::Dataspace::DISPLAY_P3)).Times(1);
@@ -429,13 +424,11 @@ TEST_F(DisplaySetColorModeTest, setsModeUnlessNoChange) {
.Times(1);
mDisplay->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::RenderIntent::TONE_MAP_COLORIMETRIC,
- ui::Dataspace::UNKNOWN});
+ ui::RenderIntent::TONE_MAP_COLORIMETRIC});
EXPECT_EQ(ui::ColorMode::DISPLAY_P3, mDisplay->getState().colorMode);
EXPECT_EQ(ui::Dataspace::DISPLAY_P3, mDisplay->getState().dataspace);
EXPECT_EQ(ui::RenderIntent::TONE_MAP_COLORIMETRIC, mDisplay->getState().renderIntent);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, mDisplay->getState().targetDataspace);
}
TEST_F(DisplaySetColorModeTest, doesNothingForVirtualDisplay) {
@@ -448,19 +441,13 @@ TEST_F(DisplaySetColorModeTest, doesNothingForVirtualDisplay) {
virtualDisplay->setDisplayColorProfileForTest(
std::unique_ptr<DisplayColorProfile>(colorProfile));
- EXPECT_CALL(*colorProfile,
- getTargetDataspace(ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::Dataspace::UNKNOWN))
- .WillOnce(Return(ui::Dataspace::UNKNOWN));
-
- virtualDisplay->setColorProfile(
- ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::RenderIntent::TONE_MAP_COLORIMETRIC, ui::Dataspace::UNKNOWN});
+ virtualDisplay->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3,
+ ui::Dataspace::DISPLAY_P3,
+ ui::RenderIntent::TONE_MAP_COLORIMETRIC});
EXPECT_EQ(ui::ColorMode::NATIVE, virtualDisplay->getState().colorMode);
EXPECT_EQ(ui::Dataspace::UNKNOWN, virtualDisplay->getState().dataspace);
EXPECT_EQ(ui::RenderIntent::COLORIMETRIC, virtualDisplay->getState().renderIntent);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, virtualDisplay->getState().targetDataspace);
}
/*
@@ -524,7 +511,7 @@ TEST_F(DisplaySetReleasedLayersTest, doesNothingIfGpuDisplay) {
auto args = getDisplayCreationArgsForGpuVirtualDisplay();
std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args);
- sp<mock::LayerFE> layerXLayerFE = new StrictMock<mock::LayerFE>();
+ sp<mock::LayerFE> layerXLayerFE = sp<StrictMock<mock::LayerFE>>::make();
{
Output::ReleasedLayers releasedLayers;
@@ -542,7 +529,7 @@ TEST_F(DisplaySetReleasedLayersTest, doesNothingIfGpuDisplay) {
}
TEST_F(DisplaySetReleasedLayersTest, doesNothingIfNoLayersWithQueuedFrames) {
- sp<mock::LayerFE> layerXLayerFE = new StrictMock<mock::LayerFE>();
+ sp<mock::LayerFE> layerXLayerFE = sp<StrictMock<mock::LayerFE>>::make();
{
Output::ReleasedLayers releasedLayers;
@@ -558,7 +545,7 @@ TEST_F(DisplaySetReleasedLayersTest, doesNothingIfNoLayersWithQueuedFrames) {
}
TEST_F(DisplaySetReleasedLayersTest, setReleasedLayers) {
- sp<mock::LayerFE> unknownLayer = new StrictMock<mock::LayerFE>();
+ sp<mock::LayerFE> unknownLayer = sp<StrictMock<mock::LayerFE>>::make();
CompositionRefreshArgs refreshArgs;
refreshArgs.layersWithQueuedFrames.push_back(mLayer1.layerFE);
@@ -880,7 +867,7 @@ TEST_F(DisplayApplyLayerRequestsToLayersTest, applyClientTargetRequests) {
}
/*
- * Display::presentAndGetFrameFences()
+ * Display::presentFrame()
*/
using DisplayPresentAndGetFrameFencesTest = DisplayWithLayersTestCommon;
@@ -889,7 +876,7 @@ TEST_F(DisplayPresentAndGetFrameFencesTest, returnsNoFencesOnGpuDisplay) {
auto args = getDisplayCreationArgsForGpuVirtualDisplay();
auto gpuDisplay{impl::createDisplay(mCompositionEngine, args)};
- auto result = gpuDisplay->presentAndGetFrameFences();
+ auto result = gpuDisplay->presentFrame();
ASSERT_TRUE(result.presentFence.get());
EXPECT_FALSE(result.presentFence->isValid());
@@ -897,11 +884,11 @@ TEST_F(DisplayPresentAndGetFrameFencesTest, returnsNoFencesOnGpuDisplay) {
}
TEST_F(DisplayPresentAndGetFrameFencesTest, returnsPresentAndLayerFences) {
- sp<Fence> presentFence = new Fence();
- sp<Fence> layer1Fence = new Fence();
- sp<Fence> layer2Fence = new Fence();
+ sp<Fence> presentFence = sp<Fence>::make();
+ sp<Fence> layer1Fence = sp<Fence>::make();
+ sp<Fence> layer2Fence = sp<Fence>::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));
@@ -913,7 +900,7 @@ TEST_F(DisplayPresentAndGetFrameFencesTest, returnsPresentAndLayerFences) {
.WillOnce(Return(layer2Fence));
EXPECT_CALL(mHwComposer, clearReleaseFences(HalDisplayId(DEFAULT_DISPLAY_ID))).Times(1);
- auto result = mDisplay->presentAndGetFrameFences();
+ auto result = mDisplay->presentFrame();
EXPECT_EQ(presentFence, result.presentFence);
@@ -949,7 +936,7 @@ TEST_F(DisplayFinishFrameTest, doesNotSkipCompositionIfNotDirtyOnHwcDisplay) {
mDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
// We expect no calls to queueBuffer if composition was skipped.
- EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1);
+ EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(1);
// Expect a call to signal no expensive rendering since there is no client composition.
EXPECT_CALL(mPowerAdvisor, setExpensiveRenderingExpected(DEFAULT_DISPLAY_ID, false));
@@ -959,7 +946,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) {
@@ -970,17 +957,41 @@ TEST_F(DisplayFinishFrameTest, skipsCompositionIfNotDirty) {
gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
// We expect no calls to queueBuffer if composition was skipped.
- EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(0);
+ EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(0);
+ EXPECT_CALL(*renderSurface, beginFrame(false));
gpuDisplay->editState().isEnabled = true;
gpuDisplay->editState().usesClientComposition = false;
gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
gpuDisplay->editState().dirtyRegion = Region::INVALID_REGION;
+ gpuDisplay->editState().lastCompositionHadVisibleLayers = true;
+
+ gpuDisplay->beginFrame();
+ gpuDisplay->finishFrame(std::move(mResultWithoutBuffer));
+}
+
+TEST_F(DisplayFinishFrameTest, skipsCompositionIfEmpty) {
+ auto args = getDisplayCreationArgsForGpuVirtualDisplay();
+ std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args);
+
+ mock::RenderSurface* renderSurface = new StrictMock<mock::RenderSurface>();
+ gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
+
+ // We expect no calls to queueBuffer if composition was skipped.
+ EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(0);
+ EXPECT_CALL(*renderSurface, beginFrame(false));
- gpuDisplay->finishFrame({}, std::move(mResultWithoutBuffer));
+ gpuDisplay->editState().isEnabled = true;
+ gpuDisplay->editState().usesClientComposition = false;
+ gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
+ gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1));
+ gpuDisplay->editState().lastCompositionHadVisibleLayers = false;
+
+ gpuDisplay->beginFrame();
+ gpuDisplay->finishFrame(std::move(mResultWithoutBuffer));
}
-TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) {
+TEST_F(DisplayFinishFrameTest, performsCompositionIfDirtyAndNotEmpty) {
auto args = getDisplayCreationArgsForGpuVirtualDisplay();
std::shared_ptr<impl::Display> gpuDisplay = impl::createDisplay(mCompositionEngine, args);
@@ -988,13 +999,17 @@ TEST_F(DisplayFinishFrameTest, performsCompositionIfDirty) {
gpuDisplay->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(renderSurface));
// We expect a single call to queueBuffer when composition is not skipped.
- EXPECT_CALL(*renderSurface, queueBuffer(_)).Times(1);
+ EXPECT_CALL(*renderSurface, queueBuffer(_, _)).Times(1);
+ EXPECT_CALL(*renderSurface, beginFrame(true));
gpuDisplay->editState().isEnabled = true;
gpuDisplay->editState().usesClientComposition = false;
gpuDisplay->editState().layerStackSpace.setContent(Rect(0, 0, 1, 1));
gpuDisplay->editState().dirtyRegion = Region(Rect(0, 0, 1, 1));
- gpuDisplay->finishFrame({}, std::move(mResultWithBuffer));
+ gpuDisplay->editState().lastCompositionHadVisibleLayers = true;
+
+ gpuDisplay->beginFrame();
+ gpuDisplay->finishFrame(std::move(mResultWithBuffer));
}
/*
@@ -1018,8 +1033,8 @@ struct DisplayFunctionalTest : public testing::Test {
NiceMock<android::mock::HWComposer> mHwComposer;
NiceMock<Hwc2::mock::PowerAdvisor> mPowerAdvisor;
NiceMock<mock::CompositionEngine> mCompositionEngine;
- sp<mock::NativeWindow> mNativeWindow = new NiceMock<mock::NativeWindow>();
- sp<mock::DisplaySurface> mDisplaySurface = new NiceMock<mock::DisplaySurface>();
+ sp<mock::NativeWindow> mNativeWindow = sp<NiceMock<mock::NativeWindow>>::make();
+ sp<mock::DisplaySurface> mDisplaySurface = sp<NiceMock<mock::DisplaySurface>>::make();
std::shared_ptr<Display> mDisplay;
impl::RenderSurface* mRenderSurface;
@@ -1045,15 +1060,15 @@ struct DisplayFunctionalTest : public testing::Test {
}
};
-TEST_F(DisplayFunctionalTest, postFramebufferCriticalCallsAreOrdered) {
+TEST_F(DisplayFunctionalTest, presentFrameAndReleaseLayersCriticalCallsAreOrdered) {
InSequence seq;
mDisplay->editState().isEnabled = true;
- EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _, _));
+ EXPECT_CALL(mHwComposer, presentAndGetReleaseFences(_, _));
EXPECT_CALL(*mDisplaySurface, onFrameCommitted());
- mDisplay->postFramebuffer();
+ mDisplay->presentFrameAndReleaseLayers();
}
} // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp b/services/surfaceflinger/CompositionEngine/tests/HwcBufferCacheTest.cpp
index 00eafb15f5..c5fb5944fb 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<GraphicBuffer>& buffer, uint32_t* outSlot,
- sp<GraphicBuffer>* 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<GraphicBuffer> 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<GraphicBuffer>(), &outSlot, &outBuffer);
- EXPECT_EQ(expectedSlot, outSlot);
- EXPECT_EQ(nullptr, outBuffer.get());
+ sp<GraphicBuffer> mBuffer1 =
+ sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+ sp<GraphicBuffer> mBuffer2 =
+ sp<GraphicBuffer>::make(1u, 1u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+};
+
+TEST_F(HwcBufferCacheTest, getHwcSlotAndBuffer_returnsUniqueSlotNumberForEachBuffer) {
+ HwcBufferCache cache;
+ sp<GraphicBuffer> 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<GraphicBuffer> 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<GraphicBuffer> outBuffer;
+
+ sp<GraphicBuffer> graphicBuffers[100];
+ HwcSlotAndBuffer slotsAndBuffers[100];
+ int finalCachedBufferIndex = 0;
+ for (int i = 0; i < 100; ++i) {
+ graphicBuffers[i] = sp<GraphicBuffer>::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<GraphicBuffer> mBuffer1{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
- sp<GraphicBuffer> mBuffer2{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
-};
+TEST_F(HwcBufferCacheTest, uncache_whenCached_returnsSlotNumber) {
+ HwcBufferCache cache;
+ sp<GraphicBuffer> 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<GraphicBuffer> 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<GraphicBuffer> 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<GraphicBuffer> outBuffer;
+
+ sp<GraphicBuffer> graphicBuffers[100];
+ HwcSlotAndBuffer slotsAndBuffers[100];
+ int finalCachedBufferIndex = -1;
+ for (int i = 0; i < 100; ++i) {
+ graphicBuffers[i] = sp<GraphicBuffer>::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<GraphicBuffer> overrideBuffer =
+ sp<GraphicBuffer>::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<GraphicBuffer> 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<GraphicBuffer> 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 d933b94da9..b0b1a02164 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<android::GraphicBuffer>&,
const android::sp<android::Fence>&));
+ MOCK_METHOD2(setBufferSlotsToClear, Error(const std::vector<uint32_t>&, 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 d7704a893d..9e35717c95 100644
--- a/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
+++ b/services/surfaceflinger/CompositionEngine/tests/MockHWComposer.h
@@ -54,22 +54,22 @@ public:
MOCK_METHOD2(allocatePhysicalDisplay, void(hal::HWDisplayId, PhysicalDisplayId));
MOCK_METHOD1(createLayer, std::shared_ptr<HWC2::Layer>(HalDisplayId));
- MOCK_METHOD6(getDeviceCompositionChanges,
- status_t(HalDisplayId, bool, std::chrono::steady_clock::time_point,
- const std::shared_ptr<FenceTime>&, nsecs_t,
- std::optional<android::HWComposer::DeviceRequestedChanges>*));
- MOCK_METHOD5(setClientTarget,
- status_t(HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&,
- ui::Dataspace));
- MOCK_METHOD3(presentAndGetReleaseFences,
- status_t(HalDisplayId, std::chrono::steady_clock::time_point,
- const std::shared_ptr<FenceTime>&));
+ MOCK_METHOD(status_t, getDeviceCompositionChanges,
+ (HalDisplayId, bool, std::optional<std::chrono::steady_clock::time_point>, nsecs_t,
+ Fps, std::optional<android::HWComposer::DeviceRequestedChanges>*));
+ MOCK_METHOD(status_t, setClientTarget,
+ (HalDisplayId, uint32_t, const sp<Fence>&, const sp<GraphicBuffer>&, ui::Dataspace,
+ float),
+ (override));
+ MOCK_METHOD2(presentAndGetReleaseFences,
+ status_t(HalDisplayId, std::optional<std::chrono::steady_clock::time_point>));
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<DisplayId>&));
MOCK_CONST_METHOD1(getPresentFence, sp<Fence>(HalDisplayId));
+ MOCK_METHOD(nsecs_t, getPresentTimestamp, (PhysicalDisplayId), (const, override));
MOCK_CONST_METHOD2(getLayerReleaseFence, sp<Fence>(HalDisplayId, HWC2::Layer*));
MOCK_METHOD3(setOutputBuffer,
status_t(HalVirtualDisplayId, const sp<Fence>&, const sp<GraphicBuffer>&));
@@ -92,10 +92,11 @@ public:
MOCK_METHOD2(onHotplug,
std::optional<DisplayIdentificationInfo>(hal::HWDisplayId, hal::Connection));
MOCK_CONST_METHOD0(updatesDeviceProductInfoOnHotplugReconnect, bool());
- MOCK_METHOD2(onVsync, bool(hal::HWDisplayId, int64_t));
+ MOCK_METHOD(std::optional<PhysicalDisplayId>, onVsync, (hal::HWDisplayId, int64_t));
MOCK_METHOD2(setVsyncEnabled, void(PhysicalDisplayId, hal::Vsync));
MOCK_CONST_METHOD1(isConnected, bool(PhysicalDisplayId));
- MOCK_CONST_METHOD1(getModes, std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId));
+ MOCK_CONST_METHOD2(getModes,
+ std::vector<HWComposer::HWCDisplayMode>(PhysicalDisplayId, int32_t));
MOCK_CONST_METHOD1(getActiveMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
MOCK_CONST_METHOD1(getColorModes, std::vector<ui::ColorMode>(PhysicalDisplayId));
MOCK_METHOD3(setActiveColorMode, status_t(PhysicalDisplayId, ui::ColorMode, ui::RenderIntent));
@@ -111,6 +112,12 @@ public:
MOCK_METHOD1(clearBootDisplayMode, status_t(PhysicalDisplayId));
MOCK_METHOD1(getPreferredBootDisplayMode, std::optional<hal::HWConfigId>(PhysicalDisplayId));
MOCK_METHOD0(getBootDisplayModeSupport, bool());
+ MOCK_CONST_METHOD0(
+ getHdrConversionCapabilities,
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>());
+ 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<hal::ContentType>*), (const, override));
@@ -138,6 +145,10 @@ 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));
+ MOCK_METHOD(status_t, notifyExpectedPresent, (PhysicalDisplayId, TimePoint, Fps));
};
} // namespace mock
diff --git a/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h b/services/surfaceflinger/CompositionEngine/tests/MockPowerAdvisor.h
index c8bd5e436c..7253354e0f 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, notifyCpuLoadUp, (), (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, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
+ MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
+ MOCK_METHOD(void, reportActualWorkDuration, (), (override));
+ MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
+ MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
MOCK_METHOD(void, setGpuFenceTime,
(DisplayId displayId, std::unique_ptr<FenceTime>&& 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<DisplayId> & 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 5290bd9cba..1c54469cc0 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<compositionengine::LayerFE> 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<compositionengine::LayerFE> 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<StrictMock<compositionengine::mock::LayerFE>> mLayerFE =
- sp<StrictMock<compositionengine::mock::LayerFE>>::make();
+ NiceMock<compositionengine::mock::Output> mOutput;
+ sp<NiceMock<compositionengine::mock::LayerFE>> mLayerFE_ =
+ sp<NiceMock<compositionengine::mock::LayerFE>>::make();
+ NiceMock<compositionengine::mock::LayerFE>& mLayerFE = *mLayerFE_;
OutputLayer mOutputLayer{mOutput, mLayerFE};
LayerFECompositionState mLayerFEState;
@@ -331,7 +334,7 @@ TEST_F(OutputLayerDisplayFrameTest, outputTransformAffectsDisplayFrame) {
TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame) {
const int kShadowRadius = 5;
- mLayerFEState.shadowRadius = kShadowRadius;
+ mLayerFEState.shadowSettings.length = kShadowRadius;
mLayerFEState.forceClientComposition = true;
mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
@@ -342,7 +345,7 @@ TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame) {
TEST_F(OutputLayerDisplayFrameTest, shadowExpandsDisplayFrame_onlyIfForcingClientComposition) {
const int kShadowRadius = 5;
- mLayerFEState.shadowRadius = kShadowRadius;
+ mLayerFEState.shadowSettings.length = kShadowRadius;
mLayerFEState.forceClientComposition = false;
mLayerFEState.geomLayerBounds = FloatRect{100.f, 100.f, 200.f, 200.f};
@@ -530,7 +533,7 @@ TEST_F(OutputLayerTest,
struct OutputLayerPartialMockForUpdateCompositionState : public impl::OutputLayer {
OutputLayerPartialMockForUpdateCompositionState(const compositionengine::Output& output,
- sp<compositionengine::LayerFE> 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<compositionengine::LayerFE> 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);
}
@@ -639,7 +642,7 @@ TEST_F(OutputLayerUpdateCompositionStateTest,
TEST_F(OutputLayerUpdateCompositionStateTest, setsOutputLayerColorspaceCorrectly) {
mLayerFEState.dataspace = ui::Dataspace::DISPLAY_P3;
- mOutputState.targetDataspace = ui::Dataspace::V0_SCRGB;
+ mOutputState.dataspace = ui::Dataspace::V0_SCRGB;
// If the layer is not colorspace agnostic, the output layer dataspace
// should use the layers requested colorspace.
@@ -656,11 +659,18 @@ TEST_F(OutputLayerUpdateCompositionStateTest, setsOutputLayerColorspaceCorrectly
mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
EXPECT_EQ(ui::Dataspace::V0_SCRGB, mOutputLayer.getState().dataspace);
+
+ // If the output is HDR, then don't blind the user with a colorspace agnostic dataspace
+ // drawing all white
+ mOutputState.dataspace = ui::Dataspace::BT2020_PQ;
+
+ mOutputLayer.updateCompositionState(false, false, ui::Transform::RotationFlags::ROT_0);
+
+ EXPECT_EQ(ui::Dataspace::DISPLAY_P3, mOutputLayer.getState().dataspace);
}
TEST_F(OutputLayerUpdateCompositionStateTest, setsOutputLayerColorspaceWith170mReplacement) {
mLayerFEState.dataspace = ui::Dataspace::TRANSFER_SMPTE_170M;
- mOutputState.targetDataspace = ui::Dataspace::V0_SCRGB;
mOutputState.treat170mAsSrgb = false;
mLayerFEState.isColorspaceAgnostic = false;
@@ -774,7 +784,7 @@ struct OutputLayerWriteStateToHWCTest : public OutputLayerTest {
static constexpr ui::Dataspace kOverrideDataspace = static_cast<ui::Dataspace>(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 +833,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 +843,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 +961,16 @@ const Region OutputLayerWriteStateToHWCTest::kOverrideSurfaceDamage{Rect{1026, 1
const HdrMetadata OutputLayerWriteStateToHWCTest::kHdrMetadata{{/* LightFlattenable */}, 1029};
native_handle_t* OutputLayerWriteStateToHWCTest::kSidebandStreamHandle =
reinterpret_cast<native_handle_t*>(1031);
-const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer;
+const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kBuffer =
+ sp<GraphicBuffer>::make(1, 2, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
const sp<GraphicBuffer> OutputLayerWriteStateToHWCTest::kOverrideBuffer =
- new GraphicBuffer(4, 5, PIXEL_FORMAT_RGBA_8888,
- AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
- AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+ sp<GraphicBuffer>::make(4, 5, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
const sp<Fence> OutputLayerWriteStateToHWCTest::kFence;
-const sp<Fence> OutputLayerWriteStateToHWCTest::kOverrideFence = new Fence();
+const sp<Fence> OutputLayerWriteStateToHWCTest::kOverrideFence = sp<Fence>::make();
const std::string OutputLayerWriteStateToHWCTest::kLayerGenericMetadata1Key =
"com.example.metadata.1";
const std::vector<uint8_t> OutputLayerWriteStateToHWCTest::kLayerGenericMetadata1Value{{1, 2, 3}};
@@ -969,7 +980,7 @@ const std::vector<uint8_t> 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 +1005,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 +1030,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 +1049,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 +1060,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 +1071,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 +1084,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 +1122,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 +1134,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 +1148,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 +1163,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 +1178,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 +1193,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 +1207,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 +1223,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 +1240,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<compositionengine::mock::LayerFE>::make();
- OutputLayer peekThroughLayer{mOutput, peekThroughLayerFE};
+ auto peekThroughLayerFE = sp<NiceMock<compositionengine::mock::LayerFE>>::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 +1271,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 +1281,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 +1293,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 +1312,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 +1319,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<GraphicBuffer> kBuffer1;
+ static const sp<GraphicBuffer> kBuffer2;
+ static const sp<GraphicBuffer> kBuffer3;
+ static const sp<Fence> 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<HWC2::mock::Layer> mHwcLayer_{std::make_shared<NiceMock<HWC2::mock::Layer>>()};
+ HWC2::mock::Layer& mHwcLayer = *mHwcLayer_;
+ NiceMock<mock::DisplayColorProfile> mDisplayColorProfile;
+};
+
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer1 =
+ sp<GraphicBuffer>::make(1, 2, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer2 =
+ sp<GraphicBuffer>::make(2, 3, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<GraphicBuffer> OutputLayerUncacheBufferTest::kBuffer3 =
+ sp<GraphicBuffer>::make(4, 5, PIXEL_FORMAT_RGBA_8888,
+ AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
+ AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN);
+const sp<Fence> OutputLayerUncacheBufferTest::kFence = sp<Fence>::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<GraphicBuffer> 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<uint32_t> 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 +1446,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();
}
@@ -1527,5 +1614,20 @@ TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfDisplaySizeDifferentFromSourc
EXPECT_TRUE(mOutputLayer.needsFiltering());
}
+TEST_F(OutputLayerTest, needsFilteringReturnsFalseIfRotatedDisplaySizeSameAsSourceSize) {
+ mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+ mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+ mOutputLayer.editState().bufferTransform = Hwc2::Transform::ROT_90;
+
+ EXPECT_FALSE(mOutputLayer.needsFiltering());
+}
+
+TEST_F(OutputLayerTest, needsFilteringReturnsTrueIfRotatedDisplaySizeDiffersFromSourceSize) {
+ mOutputLayer.editState().displayFrame = Rect(100, 100, 300, 200);
+ mOutputLayer.editState().sourceCrop = FloatRect{0.f, 0.f, 100.f, 200.f};
+
+ EXPECT_TRUE(mOutputLayer.needsFiltering());
+}
+
} // namespace
} // namespace android::compositionengine
diff --git a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
index cf12890310..97a2de0a17 100644
--- a/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/OutputTest.cpp
@@ -15,6 +15,7 @@
*/
#include <android-base/stringprintf.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
#include <compositionengine/LayerFECompositionState.h>
#include <compositionengine/impl/Output.h>
#include <compositionengine/impl/OutputCompositionState.h>
@@ -35,15 +36,19 @@
#include <cmath>
#include <cstdint>
+#include <variant>
+#include <common/FlagManager.h>
+#include <common/test/FlagUtils.h>
#include "CallOrderStateMachineHelper.h"
#include "MockHWC2.h"
#include "RegionMatcher.h"
-#include "TestUtils.h"
namespace android::compositionengine {
namespace {
+using namespace com::android::graphics::surfaceflinger;
+
using testing::_;
using testing::ByMove;
using testing::ByRef;
@@ -55,6 +60,7 @@ using testing::InSequence;
using testing::Invoke;
using testing::IsEmpty;
using testing::Mock;
+using testing::NiceMock;
using testing::Pointee;
using testing::Property;
using testing::Ref;
@@ -176,12 +182,10 @@ const Rect OutputTest::kDefaultDisplaySize{100, 200};
using ColorProfile = compositionengine::Output::ColorProfile;
void dumpColorProfile(ColorProfile profile, std::string& result, const char* name) {
- android::base::StringAppendF(&result, "%s (%s[%d] %s[%d] %s[%d] %s[%d]) ", name,
+ android::base::StringAppendF(&result, "%s (%s[%d] %s[%d] %s[%d]) ", name,
toString(profile.mode).c_str(), profile.mode,
toString(profile.dataspace).c_str(), profile.dataspace,
- toString(profile.renderIntent).c_str(), profile.renderIntent,
- toString(profile.colorSpaceAgnosticDataspace).c_str(),
- profile.colorSpaceAgnosticDataspace);
+ toString(profile.renderIntent).c_str(), profile.renderIntent);
}
// Checks for a ColorProfile match
@@ -193,8 +197,7 @@ MATCHER_P(ColorProfileEq, expected, "") {
*result_listener << buf;
return (expected.mode == arg.mode) && (expected.dataspace == arg.dataspace) &&
- (expected.renderIntent == arg.renderIntent) &&
- (expected.colorSpaceAgnosticDataspace == arg.colorSpaceAgnosticDataspace);
+ (expected.renderIntent == arg.renderIntent);
}
/*
@@ -293,7 +296,7 @@ TEST_F(OutputTest, setLayerCachingEnabled_disablesCachingAndResetsOverrideInfo)
InjectedLayer layer;
layer.outputLayerState.overrideInfo.buffer = std::make_shared<
renderengine::impl::
- ExternalTexture>(new GraphicBuffer(), renderEngine,
+ ExternalTexture>(sp<GraphicBuffer>::make(), renderEngine,
renderengine::impl::ExternalTexture::Usage::READABLE |
renderengine::impl::ExternalTexture::Usage::WRITEABLE);
injectOutputLayer(layer);
@@ -541,20 +544,14 @@ using OutputSetColorProfileTest = OutputTest;
TEST_F(OutputSetColorProfileTest, setsStateAndDirtiesOutputIfChanged) {
using ColorProfile = Output::ColorProfile;
- EXPECT_CALL(*mDisplayColorProfile,
- getTargetDataspace(ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::Dataspace::UNKNOWN))
- .WillOnce(Return(ui::Dataspace::UNKNOWN));
EXPECT_CALL(*mRenderSurface, setBufferDataspace(ui::Dataspace::DISPLAY_P3)).Times(1);
mOutput->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::RenderIntent::TONE_MAP_COLORIMETRIC,
- ui::Dataspace::UNKNOWN});
+ ui::RenderIntent::TONE_MAP_COLORIMETRIC});
EXPECT_EQ(ui::ColorMode::DISPLAY_P3, mOutput->getState().colorMode);
EXPECT_EQ(ui::Dataspace::DISPLAY_P3, mOutput->getState().dataspace);
EXPECT_EQ(ui::RenderIntent::TONE_MAP_COLORIMETRIC, mOutput->getState().renderIntent);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, mOutput->getState().targetDataspace);
EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region(kDefaultDisplaySize)));
}
@@ -562,19 +559,12 @@ TEST_F(OutputSetColorProfileTest, setsStateAndDirtiesOutputIfChanged) {
TEST_F(OutputSetColorProfileTest, doesNothingIfNoChange) {
using ColorProfile = Output::ColorProfile;
- EXPECT_CALL(*mDisplayColorProfile,
- getTargetDataspace(ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::Dataspace::UNKNOWN))
- .WillOnce(Return(ui::Dataspace::UNKNOWN));
-
mOutput->editState().colorMode = ui::ColorMode::DISPLAY_P3;
mOutput->editState().dataspace = ui::Dataspace::DISPLAY_P3;
mOutput->editState().renderIntent = ui::RenderIntent::TONE_MAP_COLORIMETRIC;
- mOutput->editState().targetDataspace = ui::Dataspace::UNKNOWN;
mOutput->setColorProfile(ColorProfile{ui::ColorMode::DISPLAY_P3, ui::Dataspace::DISPLAY_P3,
- ui::RenderIntent::TONE_MAP_COLORIMETRIC,
- ui::Dataspace::UNKNOWN});
+ ui::RenderIntent::TONE_MAP_COLORIMETRIC});
EXPECT_THAT(mOutput->getState().dirtyRegion, RegionEq(Region()));
}
@@ -759,56 +749,6 @@ TEST_F(OutputSetReleasedLayersTest, setReleasedLayersTakesGivenLayers) {
}
/*
- * 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 +790,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 +827,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 +863,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 +899,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 +907,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 +917,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 +942,7 @@ TEST_F(OutputUpdateAndWriteCompositionStateTest, peekThroughLayerChangesOrder) {
std::shared_ptr<renderengine::ExternalTexture> buffer = std::make_shared<
renderengine::impl::
- ExternalTexture>(new GraphicBuffer(), renderEngine,
+ ExternalTexture>(sp<GraphicBuffer>::make(), renderEngine,
renderengine::impl::ExternalTexture::Usage::READABLE |
renderengine::impl::ExternalTexture::Usage::WRITEABLE);
layer1.outputLayerState.overrideInfo.buffer = buffer;
@@ -1101,10 +1026,10 @@ struct OutputPrepareFrameAsyncTest : public testing::Test {
MOCK_METHOD1(
chooseCompositionStrategyAsync,
std::future<bool>(std::optional<android::HWComposer::DeviceRequestedChanges>*));
- MOCK_METHOD4(composeSurfaces,
- std::optional<base::unique_fd>(
- const Region&, const compositionengine::CompositionRefreshArgs&,
- std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
+ MOCK_METHOD3(composeSurfaces,
+ std::optional<base::unique_fd>(const Region&,
+ std::shared_ptr<renderengine::ExternalTexture>,
+ base::unique_fd&));
MOCK_METHOD0(resetCompositionStrategy, void());
};
@@ -1138,9 +1063,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 +1088,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 +1116,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 +1148,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 +1168,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<mock::OutputLayer> outputLayer;
+ sp<StrictMock<mock::LayerFE>> layerFE = sp<StrictMock<mock::LayerFE>>::make();
+ };
+
StrictMock<OutputPartialMock> 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 +1497,6 @@ const Region OutputEnsureOutputLayerIfVisibleTest::kTransparentRegionHintNegativ
TEST_F(OutputEnsureOutputLayerIfVisibleTest, performsGeomLatchBeforeCheckingIfLayerIncluded) {
EXPECT_CALL(mOutput, includesLayer(sp<LayerFE>(mLayer.layerFE))).WillOnce(Return(false));
- EXPECT_CALL(*mLayer.layerFE,
- prepareCompositionState(compositionengine::LayerFE::StateSubset::BasicGeometry));
-
mGeomSnapshots.clear();
ensureOutputLayerIfVisible();
@@ -1880,7 +1837,7 @@ TEST_F(OutputEnsureOutputLayerIfVisibleTest, coverageAccumulatesWithShadowsTest)
ui::Transform translate;
translate.set(50, 50);
mLayer.layerFEState.geomLayerTransform = translate;
- mLayer.layerFEState.shadowRadius = 10.0f;
+ mLayer.layerFEState.shadowSettings.length = 10.0f;
mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
// half of the layer including the casting shadow is covered and opaque
@@ -1922,7 +1879,7 @@ TEST_F(OutputEnsureOutputLayerIfVisibleTest, shadowRegionOnlyTest) {
ui::Transform translate;
translate.set(50, 50);
mLayer.layerFEState.geomLayerTransform = translate;
- mLayer.layerFEState.shadowRadius = 10.0f;
+ mLayer.layerFEState.shadowSettings.length = 10.0f;
mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
// Casting layer is covered by an opaque region leaving only part of its shadow to be drawn
@@ -1947,7 +1904,7 @@ TEST_F(OutputEnsureOutputLayerIfVisibleTest, takesNotSoEarlyOutifLayerWithShadow
ui::Transform translate;
translate.set(50, 50);
mLayer.layerFEState.geomLayerTransform = translate;
- mLayer.layerFEState.shadowRadius = 10.0f;
+ mLayer.layerFEState.shadowSettings.length = 10.0f;
mCoverageState.dirtyRegion = Region(Rect(0, 0, 500, 500));
// Casting layer and its shadows are covered by an opaque region
@@ -2054,12 +2011,10 @@ 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_METHOD0(postFramebuffer, void());
+ MOCK_METHOD1(finishFrame, void(GpuCompositionResult&&));
+ MOCK_METHOD0(presentFrameAndReleaseLayers, void());
MOCK_METHOD1(renderCachedSets, void(const compositionengine::CompositionRefreshArgs&));
MOCK_METHOD1(canPredictCompositionStrategy, bool(const CompositionRefreshArgs&));
};
@@ -2080,8 +2035,8 @@ 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, postFramebuffer());
+ EXPECT_CALL(mOutput, finishFrame(_));
+ EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
mOutput.present(args);
@@ -2098,10 +2053,10 @@ 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, postFramebuffer());
+ EXPECT_CALL(mOutput, finishFrame(_));
+ EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
EXPECT_CALL(mOutput, renderCachedSets(Ref(args)));
mOutput.present(args);
@@ -2135,6 +2090,7 @@ struct OutputUpdateColorProfileTest : public testing::Test {
mOutput.setDisplayColorProfileForTest(
std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+ mOutput.editState().isEnabled = true;
EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
.WillRepeatedly(Return(&mLayer1.mOutputLayer));
@@ -2168,12 +2124,11 @@ TEST_F(OutputUpdateColorProfileTest, setsAColorProfileWhenUnmanaged) {
EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3u));
EXPECT_CALL(mOutput,
- setColorProfile(ColorProfileEq(
- ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
- ui::RenderIntent::COLORIMETRIC, ui::Dataspace::UNKNOWN})));
+ setColorProfile(
+ ColorProfileEq(ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
+ ui::RenderIntent::COLORIMETRIC})));
mRefreshArgs.outputColorSetting = OutputColorSetting::kUnmanaged;
- mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
mOutput.updateColorProfile(mRefreshArgs);
}
@@ -2183,7 +2138,6 @@ struct OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile
OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile() {
EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
- mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
}
struct ExpectBestColorModeCallResultUsedToSetColorProfileState
@@ -2198,8 +2152,7 @@ struct OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile
SetArgPointee<4>(renderIntent)));
EXPECT_CALL(getInstance()->mOutput,
setColorProfile(
- ColorProfileEq(ColorProfile{colorMode, dataspace, renderIntent,
- ui::Dataspace::UNKNOWN})));
+ ColorProfileEq(ColorProfile{colorMode, dataspace, renderIntent})));
return nextState<ExecuteState>();
}
};
@@ -2226,55 +2179,6 @@ TEST_F(OutputUpdateColorProfileTest_GetBestColorModeResultBecomesSetProfile,
.execute();
}
-struct OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile
- : public OutputUpdateColorProfileTest {
- OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile() {
- EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(0u));
- EXPECT_CALL(*mDisplayColorProfile,
- getBestColorMode(ui::Dataspace::V0_SRGB, ui::RenderIntent::ENHANCE, _, _, _))
- .WillRepeatedly(DoAll(SetArgPointee<2>(ui::Dataspace::UNKNOWN),
- SetArgPointee<3>(ui::ColorMode::NATIVE),
- SetArgPointee<4>(ui::RenderIntent::COLORIMETRIC)));
- mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
- }
-
- struct IfColorSpaceAgnosticDataspaceSetToState
- : public CallOrderStateMachineHelper<TestType, IfColorSpaceAgnosticDataspaceSetToState> {
- [[nodiscard]] auto ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace dataspace) {
- getInstance()->mRefreshArgs.colorSpaceAgnosticDataspace = dataspace;
- return nextState<ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState>();
- }
- };
-
- struct ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState
- : public CallOrderStateMachineHelper<
- TestType, ThenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspaceState> {
- [[nodiscard]] auto thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(
- ui::Dataspace dataspace) {
- EXPECT_CALL(getInstance()->mOutput,
- setColorProfile(ColorProfileEq(
- ColorProfile{ui::ColorMode::NATIVE, ui::Dataspace::UNKNOWN,
- ui::RenderIntent::COLORIMETRIC, dataspace})));
- return nextState<ExecuteState>();
- }
- };
-
- // Call this member function to start using the mini-DSL defined above.
- [[nodiscard]] auto verify() { return IfColorSpaceAgnosticDataspaceSetToState::make(this); }
-};
-
-TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, DisplayP3) {
- verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::DISPLAY_P3)
- .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::DISPLAY_P3)
- .execute();
-}
-
-TEST_F(OutputUpdateColorProfileTest_ColorSpaceAgnosticeDataspaceAffectsSetColorProfile, V0_SRGB) {
- verify().ifColorSpaceAgnosticDataspaceSetTo(ui::Dataspace::V0_SRGB)
- .thenExpectSetColorProfileCallUsesColorSpaceAgnosticDataspace(ui::Dataspace::V0_SRGB)
- .execute();
-}
-
struct OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference
: public OutputUpdateColorProfileTest {
// Internally the implementation looks through the dataspaces of all the
@@ -2283,7 +2187,6 @@ struct OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference
OutputUpdateColorProfileTest_TopmostLayerPreferenceSetsOutputPreference() {
mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
- mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(3u));
EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
@@ -2403,7 +2306,6 @@ struct OutputUpdateColorProfileTest_ForceOutputColorOverrides
OutputUpdateColorProfileTest_ForceOutputColorOverrides() {
mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
- mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
mLayer1.mLayerFEState.dataspace = ui::Dataspace::DISPLAY_BT2020;
@@ -2459,7 +2361,6 @@ TEST_F(OutputUpdateColorProfileTest_ForceOutputColorOverrides, DisplayP3_Overrid
struct OutputUpdateColorProfileTest_Hdr : public OutputUpdateColorProfileTest {
OutputUpdateColorProfileTest_Hdr() {
mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
- mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
}
@@ -2738,7 +2639,6 @@ struct OutputUpdateColorProfile_AffectsChosenRenderIntentTest
OutputUpdateColorProfile_AffectsChosenRenderIntentTest() {
mRefreshArgs.outputColorSetting = OutputColorSetting::kEnhanced;
- mRefreshArgs.colorSpaceAgnosticDataspace = ui::Dataspace::UNKNOWN;
mLayer1.mLayerFEState.dataspace = ui::Dataspace::BT2020_PQ;
EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(1u));
EXPECT_CALL(mOutput, setColorProfile(_)).WillRepeatedly(Return());
@@ -2998,11 +2898,11 @@ 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<base::unique_fd>(
- const Region&, const compositionengine::CompositionRefreshArgs&,
- std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
- MOCK_METHOD0(postFramebuffer, void());
+ MOCK_METHOD3(composeSurfaces,
+ std::optional<base::unique_fd>(const Region&,
+ std::shared_ptr<renderengine::ExternalTexture>,
+ base::unique_fd&));
+ MOCK_METHOD0(presentFrameAndReleaseLayers, void());
MOCK_METHOD0(prepareFrame, void());
MOCK_METHOD0(updateProtectedContentState, void());
MOCK_METHOD2(dequeueRenderBuffer,
@@ -3039,7 +2939,7 @@ TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfNotEnabled) {
mOutput.mState.isEnabled = false;
InSequence seq;
- EXPECT_CALL(mOutput, postFramebuffer());
+ EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
EXPECT_CALL(mOutput, prepareFrame());
mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -3051,7 +2951,7 @@ TEST_F(OutputDevOptRepaintFlashTest, postsAndPreparesANewFrameIfEnabled) {
InSequence seq;
EXPECT_CALL(mOutput, getDirtyRegion()).WillOnce(Return(kEmptyRegion));
- EXPECT_CALL(mOutput, postFramebuffer());
+ EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
EXPECT_CALL(mOutput, prepareFrame());
mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -3065,9 +2965,9 @@ 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(*mRenderSurface, queueBuffer(_));
- EXPECT_CALL(mOutput, postFramebuffer());
+ EXPECT_CALL(mOutput, composeSurfaces(RegionEq(kNotEmptyRegion), _, _));
+ EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+ EXPECT_CALL(mOutput, presentFrameAndReleaseLayers());
EXPECT_CALL(mOutput, prepareFrame());
mOutput.devOptRepaintFlash(mRefreshArgs);
@@ -3081,11 +2981,11 @@ 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<base::unique_fd>(
- const Region&, const compositionengine::CompositionRefreshArgs&,
- std::shared_ptr<renderengine::ExternalTexture>, base::unique_fd&));
- MOCK_METHOD0(postFramebuffer, void());
+ MOCK_METHOD3(composeSurfaces,
+ std::optional<base::unique_fd>(const Region&,
+ std::shared_ptr<renderengine::ExternalTexture>,
+ base::unique_fd&));
+ MOCK_METHOD0(presentFrameAndReleaseLayers, void());
MOCK_METHOD0(updateProtectedContentState, void());
MOCK_METHOD2(dequeueRenderBuffer,
bool(base::unique_fd*, std::shared_ptr<renderengine::ExternalTexture>*));
@@ -3095,29 +2995,32 @@ struct OutputFinishFrameTest : public testing::Test {
mOutput.setDisplayColorProfileForTest(
std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+ EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
+ EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
}
StrictMock<OutputPartialMock> mOutput;
mock::DisplayColorProfile* mDisplayColorProfile = new StrictMock<mock::DisplayColorProfile>();
mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
- CompositionRefreshArgs mRefreshArgs;
+ StrictMock<mock::CompositionEngine> mCompositionEngine;
+ StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
};
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,22 +3029,49 @@ 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(_));
+ EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
impl::GpuCompositionResult result;
- mOutput.finishFrame(mRefreshArgs, std::move(result));
+ mOutput.finishFrame(std::move(result));
+}
+
+TEST_F(OutputFinishFrameTest, queuesBufferWithHdrSdrRatio) {
+ SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
+ mOutput.mState.isEnabled = true;
+
+ InSequence seq;
+ auto texture = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_FP16,
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_SW_READ_OFTEN),
+ mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::READABLE |
+ renderengine::impl::ExternalTexture::Usage::WRITEABLE);
+ mOutput.mState.displayBrightnessNits = 400.f;
+ mOutput.mState.sdrWhitePointNits = 200.f;
+ mOutput.mState.dataspace = ui::Dataspace::V0_SCRGB;
+ EXPECT_CALL(mOutput, updateProtectedContentState());
+ EXPECT_CALL(mOutput, dequeueRenderBuffer(_, _))
+ .WillOnce(DoAll(SetArgPointee<1>(texture), Return(true)));
+ EXPECT_CALL(mOutput, composeSurfaces(RegionEq(Region::INVALID_REGION), _, _))
+ .WillOnce(Return(ByMove(base::unique_fd())));
+ EXPECT_CALL(*mRenderSurface, queueBuffer(_, 2.f));
+
+ impl::GpuCompositionResult result;
+ mOutput.finishFrame(std::move(result));
}
TEST_F(OutputFinishFrameTest, predictionSucceeded) {
mOutput.mState.isEnabled = true;
mOutput.mState.strategyPrediction = CompositionStrategyPredictionState::SUCCESS;
InSequence seq;
- EXPECT_CALL(*mRenderSurface, queueBuffer(_));
+ EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
impl::GpuCompositionResult result;
- mOutput.finishFrame(mRefreshArgs, std::move(result));
+ mOutput.finishFrame(std::move(result));
}
TEST_F(OutputFinishFrameTest, predictionFailedAndBufferIsReused) {
@@ -3157,22 +3087,22 @@ 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));
+ EXPECT_CALL(*mRenderSurface, queueBuffer(_, 1.f));
+ mOutput.finishFrame(std::move(result));
}
/*
- * Output::postFramebuffer()
+ * Output::presentFrameAndReleaseLayers()
*/
struct OutputPostFramebufferTest : public testing::Test {
struct OutputPartialMock : public OutputPartialMockBase {
// Sets up the helper functions called by the function under test to use
// mock implementations.
- MOCK_METHOD0(presentAndGetFrameFences, compositionengine::Output::FrameFences());
+ MOCK_METHOD0(presentFrame, compositionengine::Output::FrameFences());
};
struct Layer {
@@ -3212,7 +3142,7 @@ struct OutputPostFramebufferTest : public testing::Test {
TEST_F(OutputPostFramebufferTest, ifNotEnabledDoesNothing) {
mOutput.mState.isEnabled = false;
- mOutput.postFramebuffer();
+ mOutput.presentFrameAndReleaseLayers();
}
TEST_F(OutputPostFramebufferTest, ifEnabledMustFlipThenPresentThenSendPresentCompleted) {
@@ -3227,11 +3157,10 @@ 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(mOutput, presentFrame()).WillOnce(Return(frameFences));
EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
- mOutput.postFramebuffer();
+ mOutput.presentFrameAndReleaseLayers();
}
TEST_F(OutputPostFramebufferTest, releaseFencesAreSentToLayerFE) {
@@ -3250,28 +3179,30 @@ 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(mOutput, presentFrame()).WillOnce(Return(frameFences));
EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
// Compare the pointers values of each fence to make sure the correct ones
// are passed. This happens to work with the current implementation, but
// would not survive certain calls like Fence::merge() which would return a
// new instance.
- EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_))
- .WillOnce([&layer1Fence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+ EXPECT_CALL(*mLayer1.layerFE, onLayerDisplayed(_, _))
+ .WillOnce([&layer1Fence](ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack) {
EXPECT_EQ(FenceResult(layer1Fence), futureFenceResult.get());
});
- EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_))
- .WillOnce([&layer2Fence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+ EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed(_, _))
+ .WillOnce([&layer2Fence](ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack) {
EXPECT_EQ(FenceResult(layer2Fence), futureFenceResult.get());
});
- EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_))
- .WillOnce([&layer3Fence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+ EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed(_, _))
+ .WillOnce([&layer3Fence](ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack) {
EXPECT_EQ(FenceResult(layer3Fence), futureFenceResult.get());
});
- mOutput.postFramebuffer();
+ mOutput.presentFrameAndReleaseLayers();
}
TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence) {
@@ -3284,8 +3215,7 @@ TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence)
frameFences.layerFences.emplace(&mLayer2.hwc2Layer, sp<Fence>::make());
frameFences.layerFences.emplace(&mLayer3.hwc2Layer, sp<Fence>::make());
- EXPECT_CALL(*mRenderSurface, flip());
- EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+ EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
// Fence::merge is called, and since none of the fences are actually valid,
@@ -3295,7 +3225,7 @@ TEST_F(OutputPostFramebufferTest, releaseFencesIncludeClientTargetAcquireFence)
EXPECT_CALL(*mLayer2.layerFE, onLayerDisplayed).WillOnce(Return());
EXPECT_CALL(*mLayer3.layerFE, onLayerDisplayed).WillOnce(Return());
- mOutput.postFramebuffer();
+ mOutput.presentFrameAndReleaseLayers();
}
TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
@@ -3320,25 +3250,27 @@ TEST_F(OutputPostFramebufferTest, releasedLayersSentPresentFence) {
Output::FrameFences frameFences;
frameFences.presentFence = presentFence;
- EXPECT_CALL(*mRenderSurface, flip());
- EXPECT_CALL(mOutput, presentAndGetFrameFences()).WillOnce(Return(frameFences));
+ EXPECT_CALL(mOutput, presentFrame()).WillOnce(Return(frameFences));
EXPECT_CALL(*mRenderSurface, onPresentDisplayCompleted());
// Each released layer should be given the presentFence.
- EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_))
- .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+ EXPECT_CALL(*releasedLayer1, onLayerDisplayed(_, _))
+ .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack) {
EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
});
- EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_))
- .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+ EXPECT_CALL(*releasedLayer2, onLayerDisplayed(_, _))
+ .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack) {
EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
});
- EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_))
- .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult) {
+ EXPECT_CALL(*releasedLayer3, onLayerDisplayed(_, _))
+ .WillOnce([&presentFence](ftl::SharedFuture<FenceResult> futureFenceResult,
+ ui::LayerStack) {
EXPECT_EQ(FenceResult(presentFence), futureFenceResult.get());
});
- mOutput.postFramebuffer();
+ mOutput.presentFrameAndReleaseLayers();
// After the call the list of released layers should have been cleared.
EXPECT_TRUE(mOutput.getReleasedLayersForTest().empty());
@@ -3356,7 +3288,8 @@ struct OutputComposeSurfacesTest : public testing::Test {
// mock implementations.
MOCK_CONST_METHOD0(getSkipColorTransform, bool());
MOCK_METHOD3(generateClientCompositionRequests,
- std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace, std::vector<LayerFE*>&));
+ std::vector<LayerFE::LayerSettings>(bool, ui::Dataspace,
+ std::vector<LayerFE*>&));
MOCK_METHOD2(appendRegionFlashRequests,
void(const Region&, std::vector<LayerFE::LayerSettings>&));
MOCK_METHOD1(setExpensiveRenderingExpected, void(bool));
@@ -3389,8 +3322,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 +3335,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<FenceCheckState>();
}
@@ -3428,6 +3360,7 @@ struct OutputComposeSurfacesTest : public testing::Test {
static constexpr float kDefaultAvgLuminance = 0.7f;
static constexpr float kDefaultMinLuminance = 0.1f;
static constexpr float kDisplayLuminance = 400.f;
+ static constexpr float kWhitePointLuminance = 300.f;
static constexpr float kClientTargetLuminanceNits = 200.f;
static constexpr float kClientTargetBrightness = 0.5f;
@@ -3437,7 +3370,6 @@ struct OutputComposeSurfacesTest : public testing::Test {
static const mat4 kDefaultColorTransformMat;
static const Region kDebugRegion;
- static const compositionengine::CompositionRefreshArgs kDefaultRefreshArgs;
static const HdrCapabilities kHdrCapabilities;
StrictMock<mock::CompositionEngine> mCompositionEngine;
@@ -3449,7 +3381,7 @@ struct OutputComposeSurfacesTest : public testing::Test {
StrictMock<OutputPartialMock> mOutput;
std::shared_ptr<renderengine::ExternalTexture> mOutputBuffer = std::make_shared<
renderengine::impl::
- ExternalTexture>(new GraphicBuffer(), mRenderEngine,
+ ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
renderengine::impl::ExternalTexture::Usage::READABLE |
renderengine::impl::ExternalTexture::Usage::WRITEABLE);
@@ -3460,7 +3392,6 @@ const Rect OutputComposeSurfacesTest::kDefaultOutputFrame{1001, 1002, 1003, 1004
const Rect OutputComposeSurfacesTest::kDefaultOutputViewport{1005, 1006, 1007, 1008};
const Rect OutputComposeSurfacesTest::kDefaultOutputDestinationClip{1013, 1014, 1015, 1016};
const mat4 OutputComposeSurfacesTest::kDefaultColorTransformMat{mat4() * 0.5f};
-const compositionengine::CompositionRefreshArgs OutputComposeSurfacesTest::kDefaultRefreshArgs;
const Region OutputComposeSurfacesTest::kDebugRegion{Rect{100, 101, 102, 103}};
const HdrCapabilities OutputComposeSurfacesTest::
@@ -3527,13 +3458,12 @@ TEST_F(OutputComposeSurfacesTest, handlesZeroCompositionRequests) {
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, IsEmpty(), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&)
- -> std::future<renderengine::RenderEngineResult> {
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
verify().execute().expectAFenceWasReturned();
}
@@ -3559,13 +3489,12 @@ TEST_F(OutputComposeSurfacesTest, buildsAndRendersRequestList) {
}));
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&)
- -> std::future<renderengine::RenderEngineResult> {
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
verify().execute().expectAFenceWasReturned();
@@ -3594,13 +3523,12 @@ TEST_F(OutputComposeSurfacesTest,
}));
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, true, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&)
- -> std::future<renderengine::RenderEngineResult> {
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
verify().execute().expectAFenceWasReturned();
@@ -3624,12 +3552,10 @@ TEST_F(OutputComposeSurfacesTest, renderDuplicateClientCompositionRequestsWithou
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.Times(2)
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
verify().execute().expectAFenceWasReturned();
EXPECT_FALSE(mOutput.mState.reusedClientComposition);
@@ -3656,9 +3582,8 @@ TEST_F(OutputComposeSurfacesTest, skipDuplicateClientCompositionRequests) {
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
EXPECT_CALL(mOutput, setExpensiveRenderingExpected(false));
verify().execute().expectAFenceWasReturned();
@@ -3687,19 +3612,18 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfBufferChanges) {
const auto otherOutputBuffer = std::make_shared<
renderengine::impl::
- ExternalTexture>(new GraphicBuffer(), mRenderEngine,
+ ExternalTexture>(sp<GraphicBuffer>::make(), mRenderEngine,
renderengine::impl::ExternalTexture::Usage::READABLE |
renderengine::impl::ExternalTexture::Usage::WRITEABLE);
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_))
.WillOnce(Return(mOutputBuffer))
.WillOnce(Return(otherOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
.WillRepeatedly([&](const renderengine::DisplaySettings&,
const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&)
- -> std::future<renderengine::RenderEngineResult> {
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
});
verify().execute().expectAFenceWasReturned();
@@ -3729,12 +3653,10 @@ TEST_F(OutputComposeSurfacesTest, clientCompositionIfRequestChanges) {
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, false, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
- EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, false, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r2), _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, ElementsAre(r1, r3), _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
verify().execute().expectAFenceWasReturned();
EXPECT_FALSE(mOutput.mState.reusedClientComposition);
@@ -3747,7 +3669,7 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp
OutputComposeSurfacesTest_UsesExpectedDisplaySettings() {
EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(false));
EXPECT_CALL(mRenderEngine, isProtected()).WillRepeatedly(Return(false));
- EXPECT_CALL(mOutput, generateClientCompositionRequests(_, kDefaultOutputDataspace, _))
+ EXPECT_CALL(mOutput, generateClientCompositionRequests(_, _, _))
.WillRepeatedly(Return(std::vector<LayerFE::LayerSettings>{}));
EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
.WillRepeatedly(Return());
@@ -3774,6 +3696,14 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp
: public CallOrderStateMachineHelper<TestType, OutputWithDisplayBrightnessNits> {
auto withDisplayBrightnessNits(float nits) {
getInstance()->mOutput.mState.displayBrightnessNits = nits;
+ return nextState<OutputWithSdrWhitePointNits>();
+ }
+ };
+
+ struct OutputWithSdrWhitePointNits
+ : public CallOrderStateMachineHelper<TestType, OutputWithSdrWhitePointNits> {
+ auto withSdrWhitePointNits(float nits) {
+ getInstance()->mOutput.mState.sdrWhitePointNits = nits;
return nextState<OutputWithDimmingStage>();
}
};
@@ -3803,6 +3733,35 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp
// May be called zero or one times.
EXPECT_CALL(getInstance()->mOutput, getSkipColorTransform())
.WillRepeatedly(Return(skip));
+ return nextState<PixelFormatState>();
+ }
+ };
+
+ struct PixelFormatState : public CallOrderStateMachineHelper<TestType, PixelFormatState> {
+ auto withPixelFormat(std::optional<PixelFormat> format) {
+ // May be called zero or one times.
+ if (format) {
+ auto outputBuffer = std::make_shared<
+ renderengine::impl::
+ ExternalTexture>(sp<GraphicBuffer>::
+ make(1u, 1u, *format,
+ GRALLOC_USAGE_SW_WRITE_OFTEN |
+ GRALLOC_USAGE_SW_READ_OFTEN),
+ getInstance()->mRenderEngine,
+ renderengine::impl::ExternalTexture::Usage::
+ READABLE |
+ renderengine::impl::ExternalTexture::
+ Usage::WRITEABLE);
+ EXPECT_CALL(*getInstance()->mRenderSurface, dequeueBuffer(_))
+ .WillRepeatedly(Return(outputBuffer));
+ }
+ return nextState<DataspaceState>();
+ }
+ };
+
+ struct DataspaceState : public CallOrderStateMachineHelper<TestType, DataspaceState> {
+ auto withDataspace(ui::Dataspace dataspace) {
+ getInstance()->mOutput.mState.dataspace = dataspace;
return nextState<ExpectDisplaySettingsState>();
}
};
@@ -3810,9 +3769,8 @@ struct OutputComposeSurfacesTest_UsesExpectedDisplaySettings : public OutputComp
struct ExpectDisplaySettingsState
: public CallOrderStateMachineHelper<TestType, ExpectDisplaySettingsState> {
auto thenExpectDisplaySettingsUsed(renderengine::DisplaySettings settings) {
- EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, false, _))
- .WillOnce(Return(ByMove(futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(getInstance()->mRenderEngine, drawLayers(settings, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
return nextState<ExecuteState>();
}
};
@@ -3825,10 +3783,13 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrMixedComposi
verify().ifMixedCompositionIs(true)
.andIfUsesHdr(true)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -3852,10 +3813,13 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
verify().ifMixedCompositionIs(true)
.andIfUsesHdr(true)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -3879,11 +3843,14 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
verify().ifMixedCompositionIs(true)
.andIfUsesHdr(true)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(
aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -3907,9 +3874,12 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
verify().ifMixedCompositionIs(true)
.andIfUsesHdr(true)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(aidl::android::hardware::graphics::composer3::RenderIntent::ENHANCE)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -3932,10 +3902,13 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrMixedComp
verify().ifMixedCompositionIs(true)
.andIfUsesHdr(false)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -3958,10 +3931,13 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forHdrOnlyClientCo
verify().ifMixedCompositionIs(false)
.andIfUsesHdr(true)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -3984,10 +3960,13 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings, forNonHdrOnlyClien
verify().ifMixedCompositionIs(false)
.andIfUsesHdr(false)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(false)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -4011,10 +3990,13 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
verify().ifMixedCompositionIs(false)
.andIfUsesHdr(true)
.withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
.withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
.withRenderIntent(
aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
.andIfSkipColorTransform(true)
+ .withPixelFormat(std::nullopt)
+ .withDataspace(kDefaultOutputDataspace)
.thenExpectDisplaySettingsUsed(
{.physicalDisplay = kDefaultOutputDestinationClip,
.clip = kDefaultOutputViewport,
@@ -4033,11 +4015,44 @@ TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
.expectAFenceWasReturned();
}
+TEST_F(OutputComposeSurfacesTest_UsesExpectedDisplaySettings,
+ usesExpectedDisplaySettingsWithFp16Buffer) {
+ SET_FLAG_FOR_TEST(flags::fp16_client_target, true);
+ ALOGE("alecmouri: %d", flags::fp16_client_target());
+ verify().ifMixedCompositionIs(false)
+ .andIfUsesHdr(true)
+ .withDisplayBrightnessNits(kDisplayLuminance)
+ .withSdrWhitePointNits(kWhitePointLuminance)
+ .withDimmingStage(aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR)
+ .withRenderIntent(
+ aidl::android::hardware::graphics::composer3::RenderIntent::COLORIMETRIC)
+ .andIfSkipColorTransform(true)
+ .withPixelFormat(PIXEL_FORMAT_RGBA_FP16)
+ .withDataspace(ui::Dataspace::V0_SCRGB)
+ .thenExpectDisplaySettingsUsed(
+ {.physicalDisplay = kDefaultOutputDestinationClip,
+ .clip = kDefaultOutputViewport,
+ .maxLuminance = kDefaultMaxLuminance,
+ .currentLuminanceNits = kDisplayLuminance,
+ .outputDataspace = ui::Dataspace::V0_SCRGB,
+ .colorTransform = kDefaultColorTransformMat,
+ .deviceHandlesColorTransform = true,
+ .orientation = kDefaultOutputOrientationFlags,
+ .targetLuminanceNits = kClientTargetLuminanceNits * 0.75f,
+ .dimmingStage =
+ aidl::android::hardware::graphics::composer3::DimmingStage::LINEAR,
+ .renderIntent = aidl::android::hardware::graphics::composer3::RenderIntent::
+ COLORIMETRIC})
+ .execute()
+ .expectAFenceWasReturned();
+}
+
struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeSurfacesTest {
struct Layer {
Layer() {
EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
+ EXPECT_CALL(mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
}
StrictMock<mock::OutputLayer> mOutputLayer;
@@ -4064,61 +4079,31 @@ struct OutputComposeSurfacesTest_HandlesProtectedContent : public OutputComposeS
EXPECT_CALL(mOutput, appendRegionFlashRequests(RegionEq(kDebugRegion), _))
.WillRepeatedly(Return());
EXPECT_CALL(*mRenderSurface, dequeueBuffer(_)).WillRepeatedly(Return(mOutputBuffer));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
- .WillRepeatedly(
- [&](const renderengine::DisplaySettings&,
- const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
- return futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()});
- });
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillRepeatedly([&](const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
+ return ftl::yield<FenceResult>(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<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> 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 +4114,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<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
base::unique_fd fd;
std::shared_ptr<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> tex;
mOutput.updateProtectedContentState();
mOutput.dequeueRenderBuffer(&fd, &tex);
- mOutput.composeSurfaces(kDebugRegion, kDefaultRefreshArgs, tex, fd);
+ mOutput.composeSurfaces(kDebugRegion, tex, fd);
}
struct OutputComposeSurfacesTest_SetsExpensiveRendering : public OutputComposeSurfacesTest {
@@ -4229,70 +4177,14 @@ TEST_F(OutputComposeSurfacesTest_SetsExpensiveRendering, IfExepensiveOutputDatas
InSequence seq;
EXPECT_CALL(mOutput, setExpensiveRenderingExpected(true));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
base::unique_fd fd;
std::shared_ptr<renderengine::ExternalTexture> 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<LayerFE::LayerSettings>{}));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, false, _))
- .WillOnce(Return(ByMove(futureOf<renderengine::RenderEngineResult>(
- {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<renderengine::ExternalTexture> 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<renderengine::ExternalTexture> tex;
- mOutput.updateProtectedContentState();
- mOutput.dequeueRenderBuffer(&fd, &tex);
- mOutput.composeSurfaces(kDebugRegion, mRefreshArgs, tex, fd);
+ mOutput.composeSurfaces(kDebugRegion, tex, fd);
}
/*
@@ -4303,7 +4195,7 @@ struct GenerateClientCompositionRequestsTest : public testing::Test {
struct OutputPartialMock : public OutputPartialMockBase {
// compositionengine::Output overrides
std::vector<LayerFE::LayerSettings> generateClientCompositionRequestsHelper(
- bool supportsProtectedContent, ui::Dataspace dataspace) {
+ bool supportsProtectedContent, ui::Dataspace dataspace) {
std::vector<LayerFE*> ignore;
return impl::Output::generateClientCompositionRequests(supportsProtectedContent,
dataspace, ignore);
@@ -4312,6 +4204,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 +4287,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 +4298,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<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(_))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(mLayers[1].mLayerSettings)));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(_))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(mLayers[2].mLayerSettings)));
- EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientCompositionList(_))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(_))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[1].mLayerSettings})));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(_))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
- {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 +4334,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<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(_))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[1].mLayerSettings})));
+ EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(_))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(_))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(mLayers[1].mLayerSettings)));
EXPECT_CALL(*mLayers[2].mLayerFE,
- prepareClientCompositionList(ClientCompositionTargetSettingsBlurSettingsEq(
+ prepareClientComposition(ClientCompositionTargetSettingsBlurSettingsEq(
LayerFE::ClientCompositionTargetSettings::BlurSetting::BlurRegionsOnly)))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>(
- {mShadowSettings, mLayers[2].mLayerSettings})));
-
- auto requests = mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
- kDisplayDataspace);
- ASSERT_EQ(3u, requests.size());
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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 +4371,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<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(_))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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 +4395,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<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(_))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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 +4435,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu
true /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -4558,6 +4448,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers, clearsHWCLayersIfOpaqu
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
LayerFE::LayerSettings mBlackoutSettings = mLayers[1].mLayerSettings;
@@ -4566,13 +4457,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<LayerFE::LayerSettings>({mBlackoutSettings})));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>({mLayers[2].mLayerSettings})));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(mBlackoutSettings)));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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 +4490,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 +4503,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 +4516,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<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
static_cast<void>(
mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
- kDisplayDataspace));
+ kDisplayDataspace));
}
TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
@@ -4652,6 +4547,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -4664,6 +4560,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -4676,14 +4573,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<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
static_cast<void>(
mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
@@ -4706,6 +4604,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -4718,6 +4617,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -4730,14 +4630,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<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
static_cast<void>(
mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
@@ -4759,6 +4660,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -4771,6 +4673,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -4783,14 +4686,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<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
static_cast<void>(
mOutput.generateClientCompositionRequestsHelper(false /* supportsProtectedContent */,
@@ -4810,6 +4714,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer1TargetSettings{
Region(kDisplayFrame),
@@ -4822,6 +4727,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
compositionengine::LayerFE::ClientCompositionTargetSettings layer2TargetSettings{
Region(kDisplayFrame),
@@ -4834,17 +4740,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<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer1TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
- EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientCompositionList(Eq(ByRef(layer2TargetSettings))))
- .WillOnce(Return(std::vector<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[0].mLayerFE, prepareClientComposition(Eq(ByRef(layer0TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[1].mLayerFE, prepareClientComposition(Eq(ByRef(layer1TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2TargetSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>()));
- static_cast<void>(mOutput.generateClientCompositionRequestsHelper(true /* supportsProtectedContent */,
- kDisplayDataspace));
+ static_cast<void>(
+ mOutput.generateClientCompositionRequestsHelper(true /* supportsProtectedContent */,
+ kDisplayDataspace));
}
TEST_F(OutputUpdateAndWriteCompositionStateTest, noBackgroundBlurWhenOpaque) {
@@ -4857,14 +4765,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 +4799,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 +4839,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 +4918,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<LayerFE::LayerSettings>({leftLayer.mLayerSettings})));
+ EXPECT_CALL(*leftLayer.mLayerFE, prepareClientComposition(Eq(ByRef(leftLayerSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(leftLayer.mLayerSettings)));
compositionengine::LayerFE::ClientCompositionTargetSettings rightLayerSettings{
Region(Rect(1000, 0, 2000, 1000)),
@@ -5036,16 +4937,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<LayerFE::LayerSettings>({rightLayer.mLayerSettings})));
+ EXPECT_CALL(*rightLayer.mLayerFE, prepareClientComposition(Eq(ByRef(rightLayerSettings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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 +4971,7 @@ TEST_F(GenerateClientCompositionRequestsTest_ThreeLayers,
false /* clearContent */,
compositionengine::LayerFE::ClientCompositionTargetSettings::BlurSetting::Enabled,
kLayerWhitePointNits,
+ false /* treat170mAsSrgb */,
};
LayerFE::LayerSettings mShadowSettings;
@@ -5079,11 +4982,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<LayerFE::LayerSettings>({mShadowSettings})));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2Settings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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 +5001,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 +5015,133 @@ 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<LayerFE::LayerSettings>(
- {mShadowSettings, mLayers[2].mLayerSettings})));
+ EXPECT_CALL(*mLayers[2].mLayerFE, prepareClientComposition(Eq(ByRef(layer2Settings))))
+ .WillOnce(Return(std::optional<LayerFE::LayerSettings>(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]);
+}
+
+struct OutputPresentFrameAndReleaseLayersAsyncTest : public ::testing::Test {
+ // Piggy-back on OutputPrepareFrameAsyncTest's version to avoid some boilerplate.
+ struct OutputPartialMock : public OutputPrepareFrameAsyncTest::OutputPartialMock {
+ // Set up the helper functions called by the function under test to use
+ // mock implementations.
+ MOCK_METHOD0(presentFrameAndReleaseLayers, void());
+ MOCK_METHOD0(presentFrameAndReleaseLayersAsync, ftl::Future<std::monostate>());
+ };
+ OutputPresentFrameAndReleaseLayersAsyncTest() {
+ mOutput->setDisplayColorProfileForTest(
+ std::unique_ptr<DisplayColorProfile>(mDisplayColorProfile));
+ mOutput->setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+ mOutput->setCompositionEnabled(true);
+ mRefreshArgs.outputs = {mOutput};
+ }
+
+ mock::DisplayColorProfile* mDisplayColorProfile = new NiceMock<mock::DisplayColorProfile>();
+ mock::RenderSurface* mRenderSurface = new NiceMock<mock::RenderSurface>();
+ std::shared_ptr<OutputPartialMock> mOutput{std::make_shared<NiceMock<OutputPartialMock>>()};
+ CompositionRefreshArgs mRefreshArgs;
+};
+
+TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, notCalledWhenNotRequested) {
+ EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync()).Times(0);
+ EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+
+ mOutput->present(mRefreshArgs);
+}
+
+TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledWhenRequested) {
+ EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+ .WillOnce(Return(ftl::yield<std::monostate>({})));
+ EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(0);
+
+ mOutput->offloadPresentNextFrame();
+ mOutput->present(mRefreshArgs);
+}
+
+TEST_F(OutputPresentFrameAndReleaseLayersAsyncTest, calledForOneFrame) {
+ ::testing::InSequence inseq;
+ EXPECT_CALL(*mOutput, presentFrameAndReleaseLayersAsync())
+ .WillOnce(Return(ftl::yield<std::monostate>({})));
+ EXPECT_CALL(*mOutput, presentFrameAndReleaseLayers()).Times(1);
+
+ mOutput->offloadPresentNextFrame();
+ mOutput->present(mRefreshArgs);
+ mOutput->present(mRefreshArgs);
+}
+
+/*
+ * Output::updateProtectedContentState()
+ */
+
+struct OutputUpdateProtectedContentStateTest : public testing::Test {
+ struct OutputPartialMock : public OutputPartialMockBase {
+ // Sets up the helper functions called by the function under test to use
+ // mock implementations.
+ MOCK_CONST_METHOD0(getCompositionEngine, const CompositionEngine&());
+ };
+
+ OutputUpdateProtectedContentStateTest() {
+ mOutput.setRenderSurfaceForTest(std::unique_ptr<RenderSurface>(mRenderSurface));
+ EXPECT_CALL(mOutput, getCompositionEngine()).WillRepeatedly(ReturnRef(mCompositionEngine));
+ EXPECT_CALL(mCompositionEngine, getRenderEngine()).WillRepeatedly(ReturnRef(mRenderEngine));
+ EXPECT_CALL(mOutput, getOutputLayerCount()).WillRepeatedly(Return(2u));
+ EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(0))
+ .WillRepeatedly(Return(&mLayer1.mOutputLayer));
+ EXPECT_CALL(mOutput, getOutputLayerOrderedByZByIndex(1))
+ .WillRepeatedly(Return(&mLayer2.mOutputLayer));
+ }
+
+ struct Layer {
+ Layer() {
+ EXPECT_CALL(*mLayerFE, getCompositionState()).WillRepeatedly(Return(&mLayerFEState));
+ EXPECT_CALL(mOutputLayer, getLayerFE()).WillRepeatedly(ReturnRef(*mLayerFE));
+ }
+
+ StrictMock<mock::OutputLayer> mOutputLayer;
+ sp<StrictMock<mock::LayerFE>> mLayerFE = sp<StrictMock<mock::LayerFE>>::make();
+ LayerFECompositionState mLayerFEState;
+ };
+
+ mock::RenderSurface* mRenderSurface = new StrictMock<mock::RenderSurface>();
+ StrictMock<OutputPartialMock> mOutput;
+ StrictMock<mock::CompositionEngine> mCompositionEngine;
+ StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
+ Layer mLayer1;
+ Layer mLayer2;
+};
+
+TEST_F(OutputUpdateProtectedContentStateTest, ifProtectedContentLayerComposeByHWC) {
+ mOutput.mState.isSecure = true;
+ mLayer1.mLayerFEState.hasProtectedContent = false;
+ mLayer2.mLayerFEState.hasProtectedContent = true;
+ EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
+ EXPECT_CALL(mLayer1.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
+ EXPECT_CALL(mLayer2.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(false));
+ mOutput.updateProtectedContentState();
+}
+
+TEST_F(OutputUpdateProtectedContentStateTest, ifProtectedContentLayerComposeByClient) {
+ mOutput.mState.isSecure = true;
+ mLayer1.mLayerFEState.hasProtectedContent = false;
+ mLayer2.mLayerFEState.hasProtectedContent = true;
+ EXPECT_CALL(mRenderEngine, supportsProtectedContent()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*mRenderSurface, isProtected).WillOnce(Return(false));
+ EXPECT_CALL(*mRenderSurface, setProtected(true));
+ EXPECT_CALL(mLayer1.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
+ EXPECT_CALL(mLayer2.mOutputLayer, requiresClientComposition()).WillRepeatedly(Return(true));
+ mOutput.updateProtectedContentState();
}
} // namespace
diff --git a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
index e5f9ebfbeb..edfaa26c92 100644
--- a/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/RenderSurfaceTest.cpp
@@ -61,8 +61,8 @@ public:
StrictMock<renderengine::mock::RenderEngine> mRenderEngine;
StrictMock<mock::CompositionEngine> mCompositionEngine;
StrictMock<mock::Display> mDisplay;
- sp<mock::NativeWindow> mNativeWindow = new StrictMock<mock::NativeWindow>();
- sp<mock::DisplaySurface> mDisplaySurface = new StrictMock<mock::DisplaySurface>();
+ sp<mock::NativeWindow> mNativeWindow = sp<StrictMock<mock::NativeWindow>>::make();
+ sp<mock::DisplaySurface> mDisplaySurface = sp<StrictMock<mock::DisplaySurface>>::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> fence = new Fence();
+ sp<Fence> fence = sp<Fence>::make();
EXPECT_CALL(*mDisplaySurface, getClientTargetAcquireFence()).WillOnce(ReturnRef(fence));
@@ -234,7 +234,7 @@ TEST_F(RenderSurfaceTest, prepareFrameHandlesNoComposition) {
*/
TEST_F(RenderSurfaceTest, dequeueBufferObtainsABuffer) {
- sp<GraphicBuffer> buffer = new GraphicBuffer();
+ sp<GraphicBuffer> buffer = sp<GraphicBuffer>::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<GraphicBuffer>::make(), mRenderEngine,
renderengine::impl::ExternalTexture::Usage::READABLE |
renderengine::impl::ExternalTexture::Usage::WRITEABLE);
mSurface.mutableTextureForTest() = buffer;
@@ -263,16 +263,17 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesNoClientComposition) {
state.flipClientTarget = false;
EXPECT_CALL(mDisplay, getState()).WillOnce(ReturnRef(state));
- EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+ EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
- mSurface.queueBuffer(base::unique_fd());
+ mSurface.queueBuffer(base::unique_fd(), 0.5f);
EXPECT_EQ(buffer.get(), mSurface.mutableTextureForTest().get());
}
TEST_F(RenderSurfaceTest, queueBufferHandlesClientComposition) {
- const auto buffer = std::make_shared<renderengine::impl::ExternalTexture>(new GraphicBuffer(),
- mRenderEngine, false);
+ const auto buffer =
+ std::make_shared<renderengine::impl::ExternalTexture>(sp<GraphicBuffer>::make(),
+ mRenderEngine, false);
mSurface.mutableTextureForTest() = buffer;
impl::OutputCompositionState state;
@@ -282,16 +283,17 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesClientComposition) {
EXPECT_CALL(mDisplay, getState()).WillOnce(ReturnRef(state));
EXPECT_CALL(*mNativeWindow, queueBuffer(buffer->getBuffer()->getNativeBuffer(), -1))
.WillOnce(Return(NO_ERROR));
- EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+ EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
- mSurface.queueBuffer(base::unique_fd());
+ mSurface.queueBuffer(base::unique_fd(), 0.5f);
EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
}
TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequest) {
- const auto buffer = std::make_shared<renderengine::impl::ExternalTexture>(new GraphicBuffer(),
- mRenderEngine, false);
+ const auto buffer =
+ std::make_shared<renderengine::impl::ExternalTexture>(sp<GraphicBuffer>::make(),
+ mRenderEngine, false);
mSurface.mutableTextureForTest() = buffer;
impl::OutputCompositionState state;
@@ -301,15 +303,15 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequest) {
EXPECT_CALL(mDisplay, getState()).WillOnce(ReturnRef(state));
EXPECT_CALL(*mNativeWindow, queueBuffer(buffer->getBuffer()->getNativeBuffer(), -1))
.WillOnce(Return(NO_ERROR));
- EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+ EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
- mSurface.queueBuffer(base::unique_fd());
+ mSurface.queueBuffer(base::unique_fd(), 0.5f);
EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
}
TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequestWithNoBufferYetDequeued) {
- sp<GraphicBuffer> buffer = new GraphicBuffer();
+ sp<GraphicBuffer> buffer = sp<GraphicBuffer>::make();
impl::OutputCompositionState state;
state.usesClientComposition = false;
@@ -321,16 +323,17 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesFlipClientTargetRequestWithNoBufferY
DoAll(SetArgPointee<0>(buffer.get()), SetArgPointee<1>(-1), Return(NO_ERROR)));
EXPECT_CALL(*mNativeWindow, queueBuffer(buffer->getNativeBuffer(), -1))
.WillOnce(Return(NO_ERROR));
- EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+ EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
- mSurface.queueBuffer(base::unique_fd());
+ mSurface.queueBuffer(base::unique_fd(), 0.5f);
EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
}
TEST_F(RenderSurfaceTest, queueBufferHandlesNativeWindowQueueBufferFailureOnVirtualDisplay) {
- const auto buffer = std::make_shared<renderengine::impl::ExternalTexture>(new GraphicBuffer(),
- mRenderEngine, false);
+ const auto buffer =
+ std::make_shared<renderengine::impl::ExternalTexture>(sp<GraphicBuffer>::make(),
+ mRenderEngine, false);
mSurface.mutableTextureForTest() = buffer;
impl::OutputCompositionState state;
@@ -342,9 +345,9 @@ TEST_F(RenderSurfaceTest, queueBufferHandlesNativeWindowQueueBufferFailureOnVirt
EXPECT_CALL(mDisplay, isVirtual()).WillOnce(Return(true));
EXPECT_CALL(*mNativeWindow, cancelBuffer(buffer->getBuffer()->getNativeBuffer(), -1))
.WillOnce(Return(NO_ERROR));
- EXPECT_CALL(*mDisplaySurface, advanceFrame()).Times(1);
+ EXPECT_CALL(*mDisplaySurface, advanceFrame(0.5f)).Times(1);
- mSurface.queueBuffer(base::unique_fd());
+ mSurface.queueBuffer(base::unique_fd(), 0.5f);
EXPECT_EQ(nullptr, mSurface.mutableTextureForTest().get());
}
@@ -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 0e9db369e8..d61d7ba574 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -28,8 +28,6 @@
#include <utils/Errors.h>
#include <memory>
-#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<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
- clientCompList1[0].alpha = 0.5f;
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
+ clientComp1->alpha = 0.5f;
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
- clientCompList2[0].alpha = 0.75f;
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
+ clientComp2->alpha = 0.75f;
- const auto drawLayers =
- [&](const renderengine::DisplaySettings& displaySettings,
- const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
+ const std::vector<renderengine::LayerSettings>& layers,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()),
@@ -365,16 +362,14 @@ 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<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(*layerFE1,
- prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false)))
- .WillOnce(Return(clientCompList1));
- EXPECT_CALL(*layerFE2,
- prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(false)))
- .WillOnce(Return(clientCompList2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ 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);
expectReadyBuffer(cachedSet);
@@ -397,19 +392,18 @@ TEST_F(CachedSetTest, renderSecureOutput) {
CachedSet cachedSet(layer1);
cachedSet.append(CachedSet(layer2));
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
- clientCompList1[0].alpha = 0.5f;
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
+ clientComp1->alpha = 0.5f;
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
- clientCompList2[0].alpha = 0.75f;
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
+ clientComp2->alpha = 0.75f;
- const auto drawLayers =
- [&](const renderengine::DisplaySettings& displaySettings,
- const std::vector<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
+ const std::vector<renderengine::LayerSettings>& layers,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()),
@@ -418,16 +412,14 @@ TEST_F(CachedSetTest, renderSecureOutput) {
EXPECT_EQ(0.75f, layers[1].alpha);
EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace);
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(*layerFE1,
- prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true)))
- .WillOnce(Return(clientCompList1));
- EXPECT_CALL(*layerFE2,
- prepareClientCompositionList(ClientCompositionTargetSettingsSecureEq(true)))
- .WillOnce(Return(clientCompList2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ 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);
expectReadyBuffer(cachedSet);
@@ -450,32 +442,31 @@ TEST_F(CachedSetTest, renderWhitePoint) {
CachedSet cachedSet(layer1);
cachedSet.append(CachedSet(layer2));
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
mOutputState.displayBrightnessNits = 400.f;
- const auto drawLayers =
- [&](const renderengine::DisplaySettings& displaySettings,
- const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(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));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ .WillOnce(Return(clientComp2));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
mOutputState.isSecure = true;
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
expectReadyBuffer(cachedSet);
@@ -501,32 +492,31 @@ TEST_F(CachedSetTest, renderWhitePointNoColorTransform) {
CachedSet cachedSet(layer1);
cachedSet.append(CachedSet(layer2));
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
mOutputState.displayBrightnessNits = 400.f;
- const auto drawLayers =
- [&](const renderengine::DisplaySettings& displaySettings,
- const std::vector<renderengine::LayerSettings>&,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
+ const std::vector<renderengine::LayerSettings>&,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.displayBrightnessNits, displaySettings.targetLuminanceNits);
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(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));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ .WillOnce(Return(clientComp2));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
mOutputState.isSecure = true;
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, false);
expectReadyBuffer(cachedSet);
@@ -549,21 +539,20 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) {
CachedSet cachedSet(layer1);
cachedSet.append(CachedSet(layer2));
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
- clientCompList1[0].alpha = 0.5f;
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
+ clientComp1->alpha = 0.5f;
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
- clientCompList2[0].alpha = 0.75f;
+ std::optional<compositionengine::LayerFE::LayerSettings> 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<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings& displaySettings,
+ const std::vector<renderengine::LayerSettings>& layers,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
EXPECT_EQ(mOutputState.framebufferSpace.getContent(), displaySettings.physicalDisplay);
EXPECT_EQ(mOutputState.layerStackSpace.getContent(), displaySettings.clip);
EXPECT_EQ(ui::Transform::toRotationFlags(mOutputState.framebufferSpace.getOrientation()),
@@ -572,12 +561,12 @@ TEST_F(CachedSetTest, rendersWithOffsetFramebufferContent) {
EXPECT_EQ(0.75f, layers[1].alpha);
EXPECT_EQ(ui::Dataspace::SRGB, displaySettings.outputDataspace);
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(*layerFE1, prepareClientCompositionList(_)).WillOnce(Return(clientCompList1));
- EXPECT_CALL(*layerFE2, prepareClientCompositionList(_)).WillOnce(Return(clientCompList2));
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ 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>(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<GraphicBuffer>::make();
+ layerFECompositionState.blendMode = hal::BlendMode::NONE;
+ sp<mock::LayerFE> 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<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList3;
- clientCompList3.push_back({});
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp3;
+ clientComp3.emplace();
- clientCompList3[0].source.buffer.buffer =
+ clientComp3->source.buffer.buffer =
std::make_shared<renderengine::mock::FakeExternalTexture>(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<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>& layers,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
// 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,15 +836,15 @@ TEST_F(CachedSetTest, addHolePunch) {
EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha);
}
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
}
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<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
- std::vector<compositionengine::LayerFE::LayerSettings> 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<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
+ std::optional<compositionengine::LayerFE::LayerSettings> 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<renderengine::LayerSettings>& layers,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
// 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,10 +897,10 @@ TEST_F(CachedSetTest, addHolePunch_noBuffer) {
EXPECT_EQ(1.0f, holePunchBackgroundSettings.alpha);
}
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
}
@@ -974,40 +995,39 @@ TEST_F(CachedSetTest, addBlur) {
cachedSet.addBackgroundBlurLayer(layer3);
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList1;
- clientCompList1.push_back({});
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList2;
- clientCompList2.push_back({});
- std::vector<compositionengine::LayerFE::LayerSettings> clientCompList3;
- clientCompList3.push_back({});
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp1;
+ clientComp1.emplace();
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp2;
+ clientComp2.emplace();
+ std::optional<compositionengine::LayerFE::LayerSettings> clientComp3;
+ clientComp3.emplace();
- clientCompList3[0].source.buffer.buffer =
+ clientComp3->source.buffer.buffer =
std::make_shared<renderengine::mock::FakeExternalTexture>(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<renderengine::LayerSettings>& layers,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const auto drawLayers = [&](const renderengine::DisplaySettings&,
+ const std::vector<renderengine::LayerSettings>& layers,
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> ftl::Future<FenceResult> {
// 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,10 +1036,10 @@ TEST_F(CachedSetTest, addBlur) {
EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), blurSettings.source.solidColor);
EXPECT_EQ(0.0f, blurSettings.alpha);
- return futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()});
+ return ftl::yield<FenceResult>(Fence::NO_FENCE);
};
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).WillOnce(Invoke(drawLayers));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).WillOnce(Invoke(drawLayers));
cachedSet.render(mRenderEngine, mTexturePool, mOutputState, true);
}
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 96021ec104..763b998b3d 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -14,6 +14,9 @@
* limitations under the License.
*/
+#include <common/include/common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
+
#include <compositionengine/impl/OutputCompositionState.h>
#include <compositionengine/impl/planner/CachedSet.h>
#include <compositionengine/impl/planner/Flattener.h>
@@ -27,8 +30,6 @@
#include <renderengine/mock/RenderEngine.h>
#include <chrono>
-#include "tests/TestUtils.h"
-
namespace android::compositionengine {
using namespace std::chrono_literals;
using impl::planner::CachedSet;
@@ -108,11 +109,12 @@ void FlattenerTest::SetUp() {
testLayer->outputLayerCompositionState.visibleRegion =
Region(Rect(pos + 1, pos + 1, pos + 2, pos + 2));
+ const auto kUsageFlags =
+ static_cast<uint64_t>(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<GraphicBuffer>::make(100u, 100u, HAL_PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags,
+ "output");
testLayer->layerFE = sp<mock::LayerFE>::make();
@@ -123,12 +125,11 @@ void FlattenerTest::SetUp() {
EXPECT_CALL(*testLayer->layerFE, getCompositionState)
.WillRepeatedly(Return(&testLayer->layerFECompositionState));
- std::vector<LayerFE::LayerSettings> clientCompositionList = {
- LayerFE::LayerSettings{},
- };
+ std::optional<LayerFE::LayerSettings> 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)
@@ -170,9 +171,8 @@ void FlattenerTest::initializeFlattener(const std::vector<const LayerState*>& la
void FlattenerTest::expectAllLayersFlattened(const std::vector<const LayerState*>& layers) {
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -226,9 +226,27 @@ TEST_F(FlattenerTest, flattenLayers_ActiveLayersAreNotFlattened) {
}
TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+ auto& layerState2 = mTestLayers[1]->layerState;
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerState2.get(),
+ };
+
+ initializeFlattener(layers);
+
mTestLayers[0]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold / 2;
mTestLayers[1]->layerFECompositionState.fps = Flattener::kFpsActiveThreshold;
+ expectAllLayersFlattened(layers);
+}
+
+TEST_F(FlattenerTest, unflattenLayers_onlySourceCropMoved) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ cache_if_source_crop_layer_only_moved,
+ true);
+
auto& layerState1 = mTestLayers[0]->layerState;
auto& layerState2 = mTestLayers[1]->layerState;
@@ -238,7 +256,14 @@ TEST_F(FlattenerTest, flattenLayers_ActiveLayersWithLowFpsAreFlattened) {
};
initializeFlattener(layers);
- expectAllLayersFlattened(layers);
+
+ mTestLayers[0]->outputLayerCompositionState.sourceCrop = FloatRect{0.f, 0.f, 100.f, 100.f};
+ mTestLayers[1]->outputLayerCompositionState.sourceCrop = FloatRect{8.f, 16.f, 108.f, 116.f};
+
+ // only source crop is moved, so no flatten
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
}
TEST_F(FlattenerTest, flattenLayers_basicFlatten) {
@@ -422,9 +447,8 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) {
// caleed for Layer2 and Layer3
layerState1->resetFramesSinceBufferUpdate();
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -446,9 +470,8 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateToFlatten) {
layerState1->incrementFramesSinceBufferUpdate();
mTime += 200ms;
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -499,9 +522,8 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) {
// called for Layer1 and Layer2
layerState3->resetFramesSinceBufferUpdate();
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -514,9 +536,8 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) {
EXPECT_EQ(nullptr, overrideBuffer5);
// 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<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -544,9 +565,8 @@ TEST_F(FlattenerTest, flattenLayers_BufferUpdateForMiddleLayer) {
layerState3->incrementFramesSinceBufferUpdate();
mTime += 200ms;
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -600,9 +620,8 @@ TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -612,7 +631,7 @@ TEST_F(FlattenerTest, flattenLayers_pipRequiresRoundedCorners) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -637,16 +656,15 @@ TEST_F(FlattenerTest, flattenLayers_pip) {
EXPECT_CALL(*mTestLayers[2]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
- std::vector<LayerFE::LayerSettings> clientCompositionList = {
- LayerFE::LayerSettings{},
- };
- clientCompositionList[0].source.buffer.buffer = std::make_shared<
+ std::optional<LayerFE::LayerSettings> 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<const LayerState*> layers = {
layerState1.get(),
@@ -666,9 +684,8 @@ TEST_F(FlattenerTest, flattenLayers_pip) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -678,7 +695,7 @@ TEST_F(FlattenerTest, flattenLayers_pip) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -711,16 +728,15 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleLayer) {
EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
- std::vector<LayerFE::LayerSettings> clientCompositionList = {
- LayerFE::LayerSettings{},
- };
- clientCompositionList[0].source.buffer.buffer = std::make_shared<
+ std::optional<LayerFE::LayerSettings> 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<const LayerState*> layers = {
layerState0.get(),
@@ -740,16 +756,15 @@ 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<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
EXPECT_EQ(nullptr, overrideBuffer0);
// This time we merge the CachedSet in and we should still have only two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -783,16 +798,15 @@ TEST_F(FlattenerTest, flattenLayers_holePunchSingleColorLayer) {
EXPECT_CALL(*mTestLayers[1]->layerFE, hasRoundedCorners()).WillRepeatedly(Return(true));
- std::vector<LayerFE::LayerSettings> clientCompositionList = {
- LayerFE::LayerSettings{},
- };
- clientCompositionList[0].source.buffer.buffer = std::make_shared<
+ std::optional<LayerFE::LayerSettings> 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<const LayerState*> layers = {
layerState0.get(),
@@ -812,16 +826,15 @@ 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<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
EXPECT_EQ(nullptr, overrideBuffer0);
// This time we merge the CachedSet in and we should still have only two sets.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -864,9 +877,8 @@ TEST_F(FlattenerTest, flattenLayers_flattensBlurBehindRunIfFirstRun) {
layerState3->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -910,9 +922,8 @@ TEST_F(FlattenerTest, flattenLayers_doesNotFlattenBlurBehindRun) {
layerState1->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillRepeatedly(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillRepeatedly(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -964,9 +975,8 @@ TEST_F(FlattenerTest, flattenLayers_flattenSkipsLayerWithBlurBehind) {
layerState1->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -1013,9 +1023,8 @@ TEST_F(FlattenerTest, flattenLayers_whenBlurLayerIsChanging_appliesBlurToInactiv
layerStateWithBlurBehind->resetFramesSinceBufferUpdate();
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -1056,9 +1065,8 @@ TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) {
// Mark the layers inactive
mTime += 200ms;
// layers would be flattened but the buffer would not be overridden
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
initializeOverrideBuffer(layers);
EXPECT_EQ(getNonBufferHash(layers),
@@ -1070,7 +1078,7 @@ TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) {
// Simulate attempting to render prior to merging the new cached set with the layer stack.
// Here we should not try to re-render.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We provide the override buffer now that it's rendered
@@ -1117,22 +1125,70 @@ TEST_F(FlattenerRenderSchedulingTest, flattenLayers_renderCachedSets_defersUpToM
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
for (size_t i = 0; i < kMaxDeferRenderAttempts; i++) {
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _)).Times(0);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
mFlattener->renderCachedSets(mOutputState,
std::chrono::steady_clock::now() -
(kCachedSetRenderDuration + 10ms),
true);
}
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(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<const LayerState*> 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<FenceResult>(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;
@@ -1161,9 +1217,8 @@ TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -1173,7 +1228,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsBT601_625) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1212,9 +1267,8 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -1224,7 +1278,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1263,9 +1317,8 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -1275,7 +1328,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsHDR2) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1317,9 +1370,8 @@ TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -1330,7 +1382,7 @@ TEST_F(FlattenerTest, flattenLayers_skipsColorLayers) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
@@ -1370,9 +1422,8 @@ TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) {
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
// This will render a CachedSet.
- EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _))
- .WillOnce(Return(ByMove(
- futureOf<renderengine::RenderEngineResult>({NO_ERROR, base::unique_fd()}))));
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _))
+ .WillOnce(Return(ByMove(ftl::yield<FenceResult>(Fence::NO_FENCE))));
mFlattener->renderCachedSets(mOutputState, std::nullopt, true);
// We've rendered a CachedSet, but we haven't merged it in.
@@ -1382,7 +1433,7 @@ TEST_F(FlattenerTest, flattenLayers_includes_DISPLAY_DECORATION) {
// 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);
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _)).Times(0);
initializeOverrideBuffer(layers);
EXPECT_NE(getNonBufferHash(layers),
mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/LayerStateTest.cpp
index 5c6e8da58c..044917ead9 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<mock::LayerFE> mLayerFE = sp<mock::LayerFE>::make();
mock::OutputLayer mOutputLayer;
std::unique_ptr<LayerState> mLayerState;
+
+ static constexpr auto kUsageFlags = static_cast<uint32_t>(
+ 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<GraphicBuffer>::make();
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
@@ -354,7 +357,7 @@ TEST_F(LayerStateTest, updateBuffer) {
mock::OutputLayer newOutputLayer;
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
LayerFECompositionState layerFECompositionStateTwo;
- layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ layerFECompositionStateTwo.buffer = sp<GraphicBuffer>::make();
setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
layerFECompositionStateTwo);
ftl::Flags<LayerStateField> 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<GraphicBuffer>::make();
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
@@ -372,7 +375,7 @@ TEST_F(LayerStateTest, updateBufferSingleBufferedLegacy) {
mock::OutputLayer newOutputLayer;
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
LayerFECompositionState layerFECompositionStateTwo;
- layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ layerFECompositionStateTwo.buffer = sp<GraphicBuffer>::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<GraphicBuffer>::make();
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
@@ -396,7 +399,7 @@ TEST_F(LayerStateTest, updateBufferSingleBufferedUsage) {
mock::OutputLayer newOutputLayer;
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
LayerFECompositionState layerFECompositionStateTwo;
- layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ layerFECompositionStateTwo.buffer = sp<GraphicBuffer>::make();
layerFECompositionStateTwo.buffer->usage = static_cast<uint64_t>(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<GraphicBuffer>::make();
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
mock::OutputLayer newOutputLayer;
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
LayerFECompositionState layerFECompositionStateTwo;
- layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ layerFECompositionStateTwo.buffer = sp<GraphicBuffer>::make();
setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
layerFECompositionStateTwo);
auto otherLayerState = std::make_unique<LayerState>(&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<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, kUsageFlags, "buffer1");
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
@@ -732,10 +732,7 @@ TEST_F(LayerStateTest, updatePixelFormat) {
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::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<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBX_8888, kUsageFlags, "buffer2");
setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
layerFECompositionStateTwo);
ftl::Flags<LayerStateField> 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<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888, kUsageFlags, "buffer1");
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
@@ -759,10 +753,7 @@ TEST_F(LayerStateTest, comparePixelFormat) {
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::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<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBX_8888, kUsageFlags, "buffer2");
setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
layerFECompositionStateTwo);
auto otherLayerState = std::make_unique<LayerState>(&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<LayerState>(&mOutputLayer);
+
+ mock::OutputLayer newOutputLayer;
+ sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled;
+ setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ ftl::Flags<LayerStateField> updates = mLayerState->update(&newOutputLayer);
+ EXPECT_EQ(ftl::Flags<LayerStateField>(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<LayerState>(&mOutputLayer);
+ mock::OutputLayer newOutputLayer;
+ sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
+ LayerFECompositionState layerFECompositionStateTwo;
+ layerFECompositionStateTwo.cachingHint = gui::CachingHint::Disabled;
+ setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
+ layerFECompositionStateTwo);
+ auto otherLayerState = std::make_unique<LayerState>(&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<GraphicBuffer>::make();
setupMocksForLayer(mOutputLayer, *mLayerFE, outputLayerCompositionState,
layerFECompositionState);
mLayerState = std::make_unique<LayerState>(&mOutputLayer);
@@ -1077,7 +1107,7 @@ TEST_F(LayerStateTest, getNonBufferHash_filtersOutBuffers) {
mock::OutputLayer newOutputLayer;
sp<mock::LayerFE> newLayerFE = sp<mock::LayerFE>::make();
LayerFECompositionState layerFECompositionStateTwo;
- layerFECompositionStateTwo.buffer = new GraphicBuffer();
+ layerFECompositionStateTwo.buffer = sp<GraphicBuffer>::make();
setupMocksForLayer(newOutputLayer, *newLayerFE, outputLayerCompositionState,
layerFECompositionStateTwo);
auto otherLayerState = std::make_unique<LayerState>(&newOutputLayer);
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/PredictorTest.cpp
index 68c72e0945..35d0ffb6e9 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<GraphicBuffer> buffer = new GraphicBuffer();
+ sp<GraphicBuffer> buffer = sp<GraphicBuffer>::make();
mock::OutputLayer outputLayerOne;
sp<mock::LayerFE> layerFEOne = sp<mock::LayerFE>::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<GraphicBuffer>::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<GraphicBuffer>::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<GraphicBuffer>::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<GraphicBuffer>::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 3ccc229261..0000000000
--- 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<Layer> ContainerLayer::createClone() {
- sp<ContainerLayer> 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/DisplayModeRequest.h b/services/surfaceflinger/Display/DisplayModeRequest.h
new file mode 100644
index 0000000000..d07cdf55d2
--- /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 <ftl/non_null.h>
+
+#include <scheduler/FrameRateMode.h>
+
+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 0000000000..0c7a58ee71
--- /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 <algorithm>
+#include <functional>
+#include <utility>
+
+#include <ftl/algorithm.h>
+#include <ftl/enum.h>
+#include <ui/DebugUtils.h>
+
+#include "DisplaySnapshot.h"
+
+namespace android::display {
+
+DisplaySnapshot::DisplaySnapshot(PhysicalDisplayId displayId,
+ ui::DisplayConnectionType connectionType,
+ DisplayModes&& displayModes, ui::ColorModes&& colorModes,
+ std::optional<DeviceProductInfo>&& deviceProductInfo)
+ : mDisplayId(displayId),
+ mConnectionType(connectionType),
+ mDisplayModes(std::move(displayModes)),
+ mColorModes(std::move(colorModes)),
+ mDeviceProductInfo(std::move(deviceProductInfo)) {}
+
+std::optional<DisplayModeId> 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<DisplayModes>);
+}
+
+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 0000000000..23471f5d8e
--- /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 <optional>
+
+#include <ui/ColorMode.h>
+#include <ui/DisplayId.h>
+#include <ui/StaticDisplayInfo.h>
+
+#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<DeviceProductInfo>&&);
+
+ DisplaySnapshot(const DisplaySnapshot&) = delete;
+ DisplaySnapshot(DisplaySnapshot&&) = default;
+
+ PhysicalDisplayId displayId() const { return mDisplayId; }
+ ui::DisplayConnectionType connectionType() const { return mConnectionType; }
+
+ std::optional<DisplayModeId> 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<DeviceProductInfo> mDeviceProductInfo;
+};
+
+} // namespace android::display
diff --git a/services/surfaceflinger/Display/PhysicalDisplay.h b/services/surfaceflinger/Display/PhysicalDisplay.h
new file mode 100644
index 0000000000..ef36234942
--- /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 <functional>
+#include <utility>
+
+#include <binder/IBinder.h>
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+#include <utils/StrongPointer.h>
+
+#include "DisplaySnapshot.h"
+
+namespace android::display {
+
+// TODO(b/229877597): Replace with AIDL type.
+using DisplayToken = IBinder;
+
+class PhysicalDisplay {
+public:
+ template <typename... Args>
+ PhysicalDisplay(sp<DisplayToken> token, Args&&... args)
+ : mToken(std::move(token)), mSnapshot(std::forward<Args>(args)...) {}
+
+ PhysicalDisplay(const PhysicalDisplay&) = delete;
+ PhysicalDisplay(PhysicalDisplay&&) = default;
+
+ const sp<DisplayToken>& token() const { return mToken; }
+ const DisplaySnapshot& snapshot() const { return mSnapshot; }
+
+ // Transformers for PhysicalDisplays::get.
+
+ using SnapshotRef = std::reference_wrapper<const DisplaySnapshot>;
+ 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<DisplayToken>& token) {
+ return [&token](const std::pair<const PhysicalDisplayId, PhysicalDisplay>& pair) {
+ return pair.second.token() == token;
+ };
+ }
+
+private:
+ const sp<DisplayToken> mToken;
+
+ // Effectively const except in move constructor.
+ DisplaySnapshot mSnapshot;
+};
+
+using PhysicalDisplays = ui::PhysicalDisplayMap<PhysicalDisplayId, PhysicalDisplay>;
+
+// Combinator for ftl::Optional<PhysicalDisplayId>::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 26fbd55d6e..950b05e1da 100644
--- a/services/surfaceflinger/DisplayDevice.cpp
+++ b/services/surfaceflinger/DisplayDevice.cpp
@@ -37,9 +37,10 @@
#include <configstore/Utils.h>
#include <log/log.h>
#include <system/window.h>
-#include <ui/GraphicTypes.h>
#include "DisplayDevice.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "HdrSdrRatioOverlay.h"
#include "Layer.h"
#include "RefreshRateOverlay.h"
#include "SurfaceFlinger.h"
@@ -48,8 +49,6 @@ namespace android {
namespace hal = hardware::graphics::composer::hal;
-ui::Transform::RotationFlags DisplayDevice::sPrimaryDisplayRotationFlags = ui::Transform::ROT_0;
-
DisplayDeviceCreationArgs::DisplayDeviceCreationArgs(
const sp<SurfaceFlinger>& flinger, HWComposer& hwComposer, const wp<IBinder>& displayToken,
std::shared_ptr<compositionengine::Display> compositionDisplay)
@@ -63,15 +62,17 @@ 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())),
+ mPendingModeFpsTrace(concatId("PendingModeFps")),
+ mActiveModeFpsTrace(concatId("ActiveModeFps")),
+ mRenderRateFpsTrace(concatId("RenderRateFps")),
mPhysicalOrientation(args.physicalOrientation),
- mSupportedModes(std::move(args.supportedModes)),
mIsPrimary(args.isPrimary),
- mRefreshRateConfigs(std::move(args.refreshRateConfigs)) {
+ mRequestedRefreshRate(args.requestedRefreshRate),
+ mRefreshRateSelector(std::move(args.refreshRateSelector)),
+ mHasDesiredModeTrace(concatId("HasDesiredMode"), false) {
mCompositionDisplay->editState().isSecure = args.isSecure;
+ mCompositionDisplay->editState().isProtected = args.isProtected;
mCompositionDisplay->createRenderSurface(
compositionengine::RenderSurfaceCreationArgsBuilder()
.setDisplayWidth(ANativeWindow_getWidth(args.nativeWindow.get()))
@@ -104,7 +105,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 +135,7 @@ void DisplayDevice::setDisplayName(const std::string& displayName) {
}
}
-void DisplayDevice::setDeviceProductInfo(std::optional<DeviceProductInfo> 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,12 +164,16 @@ 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) {
if (mode == hal::PowerMode::OFF || mode == hal::PowerMode::ON) {
- if (mStagedBrightness && mBrightness != *mStagedBrightness) {
+ if (mStagedBrightness && mBrightness != mStagedBrightness) {
getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
mBrightness = *mStagedBrightness;
}
@@ -182,10 +181,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(isPoweredOn());
+}
- getCompositionDisplay()->setCompositionEnabled(mPowerMode.has_value() &&
- *mPowerMode != hal::PowerMode::OFF);
+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 +210,34 @@ 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());
- }
- if (mRefreshRateOverlay) {
- mRefreshRateOverlay->changeRefreshRate(mActiveMode->getFps());
- }
-}
+void DisplayDevice::setActiveMode(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
+ ATRACE_INT(mActiveModeFpsTrace.c_str(), vsyncRate.getIntValue());
+ ATRACE_INT(mRenderRateFpsTrace.c_str(), renderFps.getIntValue());
-status_t DisplayDevice::initiateModeChange(const ActiveModeInfo& info,
- const hal::VsyncPeriodChangeConstraints& constraints,
- hal::VsyncPeriodChangeTimeline* outTimeline) {
- if (!info.mode || info.mode->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",
- 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);
+ mRefreshRateSelector->setActiveMode(modeId, renderFps);
+ updateRefreshRateOverlayRate(vsyncRate, renderFps);
}
-const DisplayModePtr& DisplayDevice::getActiveMode() const {
- return mActiveMode;
-}
+bool DisplayDevice::initiateModeChange(display::DisplayModeRequest&& desiredMode,
+ const hal::VsyncPeriodChangeConstraints& constraints,
+ hal::VsyncPeriodChangeTimeline& outTimeline) {
+ mPendingModeOpt = std::move(desiredMode);
+ mIsModeSetPending = true;
-const DisplayModes& DisplayDevice::getSupportedModes() const {
- return mSupportedModes;
-}
+ const auto& mode = *mPendingModeOpt->mode.modePtr;
-DisplayModePtr DisplayDevice::getMode(DisplayModeId modeId) const {
- const DisplayModePtr nullMode;
- return mSupportedModes.get(modeId).value_or(std::cref(nullMode));
+ if (mHwComposer.setActiveModeWithConstraints(getPhysicalId(), mode.getHwcId(), constraints,
+ &outTimeline) != OK) {
+ return false;
+ }
+
+ ATRACE_INT(mPendingModeFpsTrace.c_str(), mode.getVsyncRate().getIntValue());
+ return true;
}
-std::optional<DisplayModeId> 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 {};
+void DisplayDevice::finalizeModeChange(DisplayModeId modeId, Fps vsyncRate, Fps renderFps) {
+ setActiveMode(modeId, vsyncRate, renderFps);
+ mIsModeSetPending = false;
}
nsecs_t DisplayDevice::getVsyncPeriodFromHWC() const {
@@ -264,27 +252,20 @@ 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->getVsyncRate().getPeriodNsecs();
}
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);
+ }
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->setLayerStack(filter.layerStack);
}
}
@@ -299,16 +280,16 @@ void DisplayDevice::setDisplaySize(int width, int height) {
if (mRefreshRateOverlay) {
mRefreshRateOverlay->setViewport(size);
}
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->setViewport(size);
+ }
}
void DisplayDevice::setProjection(ui::Rotation orientation, Rect layerStackSpaceRect,
Rect orientedDisplaySpaceRect) {
+ mIsOrientationChanged = mOrientation != orientation;
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;
@@ -336,7 +317,7 @@ void DisplayDevice::stageBrightness(float brightness) {
}
void DisplayDevice::persistBrightness(bool needsComposite) {
- if (mStagedBrightness && mBrightness != *mStagedBrightness) {
+ if (mStagedBrightness && mBrightness != mStagedBrightness) {
if (needsComposite) {
getCompositionDisplay()->setNextBrightness(*mStagedBrightness);
}
@@ -349,48 +330,14 @@ std::optional<float> DisplayDevice::getStagedBrightness() const {
return mStagedBrightness;
}
-ui::Transform::RotationFlags DisplayDevice::getPrimaryDisplayRotationFlags() {
- return sPrimaryDisplayRotationFlags;
-}
-
-std::string DisplayDevice::getDebugName() const {
- using namespace std::string_literals;
+void DisplayDevice::dump(utils::Dumper& dumper) const {
+ using namespace std::string_view_literals;
- std::string name = "Display "s + to_string(getId()) + " ("s;
+ dumper.dump("name"sv, '"' + mDisplayName + '"');
+ dumper.dump("powerMode"sv, mPowerMode);
- if (mConnectionType) {
- name += isInternal() ? "internal"s : "external"s;
- } else {
- name += "virtual"s;
- }
-
- 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 +361,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 +421,81 @@ HdrCapabilities DisplayDevice::getHdrCapabilities() const {
capabilities.getDesiredMinLuminance());
}
-void DisplayDevice::enableRefreshRateOverlay(bool enable, bool showSpinnner) {
+void DisplayDevice::enableHdrSdrRatioOverlay(bool enable) {
+ if (!enable) {
+ mHdrSdrRatioOverlay.reset();
+ return;
+ }
+
+ mHdrSdrRatioOverlay = HdrSdrRatioOverlay::create();
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->setLayerStack(getLayerStack());
+ mHdrSdrRatioOverlay->setViewport(getSize());
+ updateHdrSdrRatioOverlayRatio(mHdrSdrRatio);
+ }
+}
+
+void DisplayDevice::updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio) {
+ ATRACE_CALL();
+ mHdrSdrRatio = currentHdrSdrRatio;
+ if (mHdrSdrRatioOverlay) {
+ mHdrSdrRatioOverlay->changeHdrSdrRatio(currentHdrSdrRatio);
+ }
+}
+
+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<RefreshRateOverlay>(fpsRange, showSpinnner);
- mRefreshRateOverlay->setLayerStack(getLayerStack());
- mRefreshRateOverlay->setViewport(getSize());
- mRefreshRateOverlay->changeRefreshRate(getActiveMode()->getFps());
+ ftl::Flags<RefreshRateOverlay::Features> 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;
+ }
+
+ // TODO(b/296636258) Update to use the render rate range in VRR mode.
+ const auto fpsRange = mRefreshRateSelector->getSupportedRefreshRateRange();
+ mRefreshRateOverlay = RefreshRateOverlay::create(fpsRange, features);
+ if (mRefreshRateOverlay) {
+ mRefreshRateOverlay->setLayerStack(getLayerStack());
+ mRefreshRateOverlay->setViewport(getSize());
+ updateRefreshRateOverlayRate(getActiveMode().modePtr->getVsyncRate(), getActiveMode().fps,
+ setByHwc);
+ }
+}
+
+void DisplayDevice::updateRefreshRateOverlayRate(Fps vsyncRate, Fps renderFps, bool setByHwc) {
+ ATRACE_CALL();
+ if (mRefreshRateOverlay) {
+ if (!mRefreshRateOverlay->isSetByHwc() || setByHwc) {
+ mRefreshRateOverlay->changeRefreshRate(vsyncRate, renderFps);
+ } else {
+ mRefreshRateOverlay->changeRenderRate(renderFps);
+ }
+ }
}
bool DisplayDevice::onKernelTimerChanged(std::optional<DisplayModeId> 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->getVsyncRate(), newMode->fps);
return true;
}
}
@@ -505,71 +503,100 @@ bool DisplayDevice::onKernelTimerChanged(std::optional<DisplayModeId> desiredMod
return false;
}
-void DisplayDevice::animateRefreshRateOverlay() {
+void DisplayDevice::animateOverlay() {
if (mRefreshRateOverlay) {
mRefreshRateOverlay->animate();
}
+ if (mHdrSdrRatioOverlay) {
+ // hdr sdr ratio is designed to be on the top right of the screen,
+ // therefore, we need to re-calculate the display's width and height
+ if (mIsOrientationChanged) {
+ auto width = getWidth();
+ auto height = getHeight();
+ if (mOrientation == ui::ROTATION_90 || mOrientation == ui::ROTATION_270) {
+ std::swap(width, height);
+ }
+ mHdrSdrRatioOverlay->setViewport({width, height});
+ }
+ mHdrSdrRatioOverlay->animate();
+ }
}
-bool DisplayDevice::setDesiredActiveMode(const ActiveModeInfo& info) {
+auto DisplayDevice::setDesiredMode(display::DisplayModeRequest&& desiredMode, bool force)
+ -> DesiredModeAction {
ATRACE_CALL();
- LOG_ALWAYS_FATAL_IF(!info.mode, "desired mode not provided");
- LOG_ALWAYS_FATAL_IF(getPhysicalId() != info.mode->getPhysicalDisplayId(), "DisplayId mismatch");
+ const auto& desiredModePtr = desiredMode.mode.modePtr;
- ALOGV("%s(%s)", __func__, to_string(*info.mode).c_str());
+ LOG_ALWAYS_FATAL_IF(getPhysicalId() != desiredModePtr->getPhysicalDisplayId(),
+ "DisplayId mismatch");
- std::scoped_lock lock(mActiveModeLock);
- if (mDesiredActiveModeChanged) {
- // If a mode change is pending, just cache the latest request in mDesiredActiveMode
- const auto prevConfig = mDesiredActiveMode.event;
- mDesiredActiveMode = info;
- mDesiredActiveMode.event = mDesiredActiveMode.event | prevConfig;
- return false;
+ ALOGV("%s(%s)", __func__, to_string(*desiredModePtr).c_str());
+
+ std::scoped_lock lock(mDesiredModeLock);
+ if (mDesiredModeOpt) {
+ // A mode transition was already scheduled, so just override the desired mode.
+ const bool emitEvent = mDesiredModeOpt->emitEvent;
+ mDesiredModeOpt = std::move(desiredMode);
+ mDesiredModeOpt->emitEvent |= emitEvent;
+ return DesiredModeAction::None;
}
- // Check if we are already at the desired mode
- if (getActiveMode()->getId() == info.mode->getId()) {
- return false;
+ // If the desired mode is already active...
+ const auto activeMode = refreshRateSelector().getActiveMode();
+ if (!force && activeMode.modePtr->getId() == desiredModePtr->getId()) {
+ if (activeMode == desiredMode.mode) {
+ return DesiredModeAction::None;
+ }
+
+ // ...but the render rate changed:
+ setActiveMode(desiredModePtr->getId(), desiredModePtr->getVsyncRate(),
+ desiredMode.mode.fps);
+ return DesiredModeAction::InitiateRenderRateSwitch;
}
+ // Set the render frame rate to the active physical refresh rate to schedule the next
+ // frame as soon as possible.
+ setActiveMode(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
+ activeMode.modePtr->getVsyncRate());
+
// Initiate a mode change.
- mDesiredActiveModeChanged = true;
- mDesiredActiveMode = info;
- return true;
+ mDesiredModeOpt = std::move(desiredMode);
+ mHasDesiredModeTrace = true;
+ return DesiredModeAction::InitiateDisplayModeSwitch;
}
-std::optional<DisplayDevice::ActiveModeInfo> DisplayDevice::getDesiredActiveMode() const {
- std::scoped_lock lock(mActiveModeLock);
- if (mDesiredActiveModeChanged) return mDesiredActiveMode;
- return std::nullopt;
+auto DisplayDevice::getDesiredMode() const -> DisplayModeRequestOpt {
+ std::scoped_lock lock(mDesiredModeLock);
+ return mDesiredModeOpt;
}
-void DisplayDevice::clearDesiredActiveModeState() {
- std::scoped_lock lock(mActiveModeLock);
- mDesiredActiveMode.event = scheduler::DisplayModeEvent::None;
- mDesiredActiveModeChanged = false;
+void DisplayDevice::clearDesiredMode() {
+ std::scoped_lock lock(mDesiredModeLock);
+ mDesiredModeOpt.reset();
+ mHasDesiredModeTrace = false;
}
-status_t DisplayDevice::setRefreshRatePolicy(
- const std::optional<scheduler::RefreshRateConfigs::Policy>& 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<unsigned>(
+ std::floor(pacesetterDisplayRefreshRate.getValue() / mRequestedRefreshRate.getValue()));
+ if (divisor == 0) {
+ mAdjustedRefreshRate = 0_Hz;
+ return;
}
- return setPolicyResult;
+ mAdjustedRefreshRate = pacesetterDisplayRefreshRate / divisor;
}
std::atomic<int32_t> DisplayDeviceState::sNextSequenceId(1);
diff --git a/services/surfaceflinger/DisplayDevice.h b/services/surfaceflinger/DisplayDevice.h
index fc24a9ce49..ac390cb8ff 100644
--- a/services/surfaceflinger/DisplayDevice.h
+++ b/services/surfaceflinger/DisplayDevice.h
@@ -17,13 +17,14 @@
#pragma once
#include <memory>
-#include <optional>
#include <string>
#include <unordered_map>
#include <android-base/thread_annotations.h>
#include <android/native_window.h>
#include <binder/IBinder.h>
+#include <ftl/concat.h>
+#include <ftl/optional.h>
#include <gui/LayerState.h>
#include <math/mat4.h>
#include <renderengine/RenderEngine.h>
@@ -41,17 +42,21 @@
#include <utils/RefBase.h>
#include <utils/Timers.h>
+#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;
class HWComposer;
+class HdrSdrRatioOverlay;
class IGraphicBufferProducer;
class Layer;
class RefreshRateOverlay;
@@ -65,6 +70,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 +89,8 @@ public:
return mCompositionDisplay;
}
- std::optional<ui::DisplayConnectionType> 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 +100,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 +111,11 @@ public:
ui::Rotation getPhysicalOrientation() const { return mPhysicalOrientation; }
ui::Rotation getOrientation() const { return mOrientation; }
- static ui::Transform::RotationFlags getPrimaryDisplayRotationFlags();
-
std::optional<float> 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 +167,7 @@ public:
void setDisplayName(const std::string& displayName);
const std::string& getDisplayName() const { return mDisplayName; }
- void setDeviceProductInfo(std::optional<DeviceProductInfo> info);
- const std::optional<DeviceProductInfo>& 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 +175,7 @@ public:
std::optional<hardware::graphics::composer::hal::PowerMode> 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 +185,136 @@ public:
/* ------------------------------------------------------------------------
* Display mode management.
*/
- const DisplayModePtr& getActiveMode() const;
- struct ActiveModeInfo {
- DisplayModePtr mode;
- scheduler::DisplayModeEvent event = scheduler::DisplayModeEvent::None;
+ enum class DesiredModeAction { None, InitiateDisplayModeSwitch, InitiateRenderRateSwitch };
- bool operator!=(const ActiveModeInfo& other) const {
- return mode != other.mode || event != other.event;
- }
- };
+ DesiredModeAction setDesiredMode(display::DisplayModeRequest&&, bool force = false)
+ EXCLUDES(mDesiredModeLock);
- bool setDesiredActiveMode(const ActiveModeInfo&) EXCLUDES(mActiveModeLock);
- std::optional<ActiveModeInfo> getDesiredActiveMode() const EXCLUDES(mActiveModeLock);
- void clearDesiredActiveModeState() EXCLUDES(mActiveModeLock);
- ActiveModeInfo getUpcomingActiveMode() const REQUIRES(kMainThreadContext) {
- return mUpcomingActiveMode;
+ using DisplayModeRequestOpt = ftl::Optional<display::DisplayModeRequest>;
+
+ DisplayModeRequestOpt getDesiredMode() const EXCLUDES(mDesiredModeLock);
+ void clearDesiredMode() EXCLUDES(mDesiredModeLock);
+
+ DisplayModeRequestOpt getPendingMode() const REQUIRES(kMainThreadContext) {
+ return mPendingModeOpt;
}
+ bool isModeSetPending() const REQUIRES(kMainThreadContext) { return mIsModeSetPending; }
- void setActiveMode(DisplayModeId) REQUIRES(kMainThreadContext);
- status_t initiateModeChange(const ActiveModeInfo&,
- const hal::VsyncPeriodChangeConstraints& constraints,
- hal::VsyncPeriodChangeTimeline* outTimeline)
- REQUIRES(kMainThreadContext);
+ scheduler::FrameRateMode getActiveMode() const REQUIRES(kMainThreadContext) {
+ return mRefreshRateSelector->getActiveMode();
+ }
- // 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;
+ void setActiveMode(DisplayModeId, Fps vsyncRate, Fps renderFps);
- // 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;
+ bool initiateModeChange(display::DisplayModeRequest&&, const hal::VsyncPeriodChangeConstraints&,
+ hal::VsyncPeriodChangeTimeline& outTimeline)
+ REQUIRES(kMainThreadContext);
- std::optional<DisplayModeId> translateModeId(hal::HWConfigId) const;
+ void finalizeModeChange(DisplayModeId, Fps vsyncRate, Fps renderFps)
+ REQUIRES(kMainThreadContext);
- // 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<scheduler::RefreshRateConfigs> holdRefreshRateConfigs() const {
- return mRefreshRateConfigs;
+ // Extends the lifetime of the RefreshRateSelector, so it can outlive this DisplayDevice.
+ std::shared_ptr<scheduler::RefreshRateSelector> holdRefreshRateSelector() const {
+ return mRefreshRateSelector;
}
+ void animateOverlay();
+
// 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 vsyncRate, Fps renderFps, bool setByHwc = false);
bool isRefreshRateOverlayEnabled() const { return mRefreshRateOverlay != nullptr; }
bool onKernelTimerChanged(std::optional<DisplayModeId>, bool timerExpired);
- void animateRefreshRateOverlay();
- void onVsync(nsecs_t timestamp);
+ // Enables an overlay to be display with the hdr/sdr ratio
+ void enableHdrSdrRatioOverlay(bool enable) REQUIRES(kMainThreadContext);
+ void updateHdrSdrRatioOverlayRatio(float currentHdrSdrRatio);
+ bool isHdrSdrRatioOverlayEnabled() const { return mHdrSdrRatioOverlay != nullptr; }
+
nsecs_t getVsyncPeriodFromHWC() const;
- nsecs_t getRefreshTimestamp() const;
- status_t setRefreshRatePolicy(
- const std::optional<scheduler::RefreshRateConfigs::Policy>& 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:
+ template <size_t N>
+ inline std::string concatId(const char (&str)[N]) const {
+ return std::string(ftl::Concat(str, ' ', getId().value).str());
+ }
+
const sp<SurfaceFlinger> mFlinger;
HWComposer& mHwComposer;
const wp<IBinder> mDisplayToken;
const int32_t mSequenceId;
- const std::optional<ui::DisplayConnectionType> mConnectionType;
const std::shared_ptr<compositionengine::Display> mCompositionDisplay;
std::string mDisplayName;
- std::string mActiveModeFPSTrace;
- std::string mActiveModeFPSHwcTrace;
+ std::string mPendingModeFpsTrace;
+ std::string mActiveModeFpsTrace;
+ std::string mRenderRateFpsTrace;
const ui::Rotation mPhysicalOrientation;
ui::Rotation mOrientation = ui::ROTATION_0;
+ bool mIsOrientationChanged = false;
- static ui::Transform::RotationFlags sPrimaryDisplayRotationFlags;
-
- // allow initial power mode as null.
- std::optional<hardware::graphics::composer::hal::PowerMode> mPowerMode;
- DisplayModePtr mActiveMode;
- std::optional<float> mStagedBrightness = std::nullopt;
- float mBrightness = -1.f;
- const DisplayModes mSupportedModes;
+ // Allow nullopt as initial power mode.
+ using TracedPowerMode = TracedOrdinal<hardware::graphics::composer::hal::PowerMode>;
+ std::optional<TracedPowerMode> mPowerMode;
- std::atomic<nsecs_t> mLastHwVsync = 0;
+ std::optional<float> mStagedBrightness;
+ std::optional<float> mBrightness;
// TODO(b/182939859): Remove special cases for primary display.
const bool mIsPrimary;
uint32_t mFlags = 0;
- std::optional<DeviceProductInfo> 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<ui::Hdr> mOverrideHdrTypes;
- std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+ std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
std::unique_ptr<RefreshRateOverlay> mRefreshRateOverlay;
+ std::unique_ptr<HdrSdrRatioOverlay> mHdrSdrRatioOverlay;
+ // This parameter is only used for hdr/sdr ratio overlay
+ float mHdrSdrRatio = 1.0f;
- mutable std::mutex mActiveModeLock;
- ActiveModeInfo mDesiredActiveMode GUARDED_BY(mActiveModeLock);
- TracedOrdinal<bool> mDesiredActiveModeChanged
- GUARDED_BY(mActiveModeLock) = {"DesiredActiveModeChanged", false};
- ActiveModeInfo mUpcomingActiveMode GUARDED_BY(kMainThreadContext);
+ mutable std::mutex mDesiredModeLock;
+ DisplayModeRequestOpt mDesiredModeOpt GUARDED_BY(mDesiredModeLock);
+ TracedOrdinal<bool> mHasDesiredModeTrace GUARDED_BY(mDesiredModeLock);
- std::atomic_int mNumModeSwitchesInPolicy = 0;
+ DisplayModeRequestOpt mPendingModeOpt GUARDED_BY(kMainThreadContext);
+ bool mIsModeSetPending GUARDED_BY(kMainThreadContext) = false;
};
struct DisplayDeviceState {
struct Physical {
PhysicalDisplayId id;
- ui::DisplayConnectionType type;
hardware::graphics::composer::hal::HWDisplayId hwcDisplayId;
- std::optional<DeviceProductInfo> 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 +332,9 @@ struct DisplayDeviceState {
uint32_t height = 0;
std::string displayName;
bool isSecure = false;
+ bool isProtected = false;
+ // Refer to DisplayDevice::mRequestedRefreshRate, for virtual display only
+ Fps requestedRefreshRate;
private:
static std::atomic<int32_t> sNextSequenceId;
@@ -354,11 +350,11 @@ struct DisplayDeviceCreationArgs {
HWComposer& hwComposer;
const wp<IBinder> displayToken;
const std::shared_ptr<compositionengine::Display> compositionDisplay;
- std::shared_ptr<scheduler::RefreshRateConfigs> refreshRateConfigs;
+ std::shared_ptr<scheduler::RefreshRateSelector> refreshRateSelector;
int32_t sequenceId{0};
- std::optional<ui::DisplayConnectionType> connectionType;
bool isSecure{false};
+ bool isProtected{false};
sp<ANativeWindow> nativeWindow;
sp<compositionengine::DisplaySurface> displaySurface;
ui::Rotation physicalOrientation{ui::ROTATION_0};
@@ -368,8 +364,9 @@ struct DisplayDeviceCreationArgs {
std::unordered_map<ui::ColorMode, std::vector<ui::RenderIntent>> hwcColorModes;
std::optional<hardware::graphics::composer::hal::PowerMode> 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 36512311d1..6250b1155e 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.cpp
@@ -20,9 +20,12 @@
#include "AidlComposerHal.h"
+#include <SurfaceFlingerProperties.h>
#include <android-base/file.h>
#include <android/binder_ibinder_platform.h>
#include <android/binder_manager.h>
+#include <common/FlagManager.h>
+#include <gui/TraceUtils.h>
#include <log/log.h>
#include <utils/Trace.h>
@@ -55,6 +58,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;
@@ -71,6 +78,7 @@ using AidlDisplayConnectionType =
using AidlColorTransform = aidl::android::hardware::graphics::common::ColorTransform;
using AidlDataspace = aidl::android::hardware::graphics::common::Dataspace;
+using AidlDisplayHotplugEvent = aidl::android::hardware::graphics::common::DisplayHotplugEvent;
using AidlFRect = aidl::android::hardware::graphics::common::FRect;
using AidlRect = aidl::android::hardware::graphics::common::Rect;
using AidlTransform = aidl::android::hardware::graphics::common::Transform;
@@ -167,9 +175,9 @@ public:
AidlIComposerCallbackWrapper(HWC2::ComposerCallback& callback) : mCallback(callback) {}
::ndk::ScopedAStatus onHotplug(int64_t in_display, bool in_connected) override {
- const auto connection = in_connected ? V2_4::IComposerCallback::Connection::CONNECTED
- : V2_4::IComposerCallback::Connection::DISCONNECTED;
- mCallback.onComposerHalHotplug(translate<Display>(in_display), connection);
+ const auto event = in_connected ? AidlDisplayHotplugEvent::CONNECTED
+ : AidlDisplayHotplugEvent::DISCONNECTED;
+ mCallback.onComposerHalHotplugEvent(translate<Display>(in_display), event);
return ::ndk::ScopedAStatus::ok();
}
@@ -203,6 +211,18 @@ public:
return ::ndk::ScopedAStatus::ok();
}
+ ::ndk::ScopedAStatus onRefreshRateChangedDebug(
+ const RefreshRateChangedDebugData& refreshRateChangedDebugData) override {
+ mCallback.onRefreshRateChangedDebug(refreshRateChangedDebugData);
+ return ::ndk::ScopedAStatus::ok();
+ }
+
+ ::ndk::ScopedAStatus onHotplugEvent(int64_t in_display,
+ AidlDisplayHotplugEvent event) override {
+ mCallback.onComposerHalHotplugEvent(translate<Display>(in_display), event);
+ return ::ndk::ScopedAStatus::ok();
+ }
+
private:
HWC2::ComposerCallback& mCallback;
};
@@ -229,6 +249,32 @@ AidlComposer::AidlComposer(const std::string& serviceName) {
return;
}
+ addReader(translate<Display>(kSingleReaderKey));
+
+ // If unable to read interface version, then become backwards compatible.
+ const auto status = mAidlComposerClient->getInterfaceVersion(&mComposerInterfaceVersion);
+ if (!status.isOk()) {
+ ALOGE("getInterfaceVersion for AidlComposer constructor failed %s",
+ status.getDescription().c_str());
+ }
+
+ if (mComposerInterfaceVersion <= 1) {
+ if (sysprop::clear_slots_with_set_layer_buffer(false)) {
+ mClearSlotBuffer = sp<GraphicBuffer>::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;
+ }
+ }
+ }
+ if (getLayerLifecycleBatchCommand()) {
+ mEnableLayerCommandBatchingFlag =
+ FlagManager::getInstance().enable_layer_command_batching();
+ }
ALOGI("Loaded AIDL composer3 HAL service");
}
@@ -245,6 +291,10 @@ bool AidlComposer::isSupported(OptionalFeature feature) const {
}
}
+bool AidlComposer::isVrrSupported() const {
+ return mComposerInterfaceVersion >= 3 && FlagManager::getInstance().vrr_config();
+}
+
std::vector<Capability> AidlComposer::getCapabilities() {
std::vector<Capability> capabilities;
const auto status = mAidlComposer->getCapabilities(&capabilities);
@@ -289,7 +339,9 @@ void AidlComposer::registerCallback(HWC2::ComposerCallback& callback) {
}
mAidlComposerCallback = ndk::SharedRefBase::make<AidlIComposerCallbackWrapper>(callback);
- AIBinder_setMinSchedulerPolicy(mAidlComposerCallback->asBinder().get(), SCHED_FIFO, 2);
+
+ ndk::SpAIBinder binder = mAidlComposerCallback->asBinder();
+ AIBinder_setMinSchedulerPolicy(binder.get(), SCHED_FIFO, 2);
const auto status = mAidlComposerClient->registerCallback(mAidlComposerCallback);
if (!status.isOk()) {
@@ -297,12 +349,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 +384,7 @@ Error AidlComposer::createVirtualDisplay(uint32_t width, uint32_t height, PixelF
*outDisplay = translate<Display>(virtualDisplay.display);
*format = static_cast<PixelFormat>(virtualDisplay.format);
+ addDisplay(translate<Display>(virtualDisplay.display));
return Error::NONE;
}
@@ -342,35 +394,76 @@ Error AidlComposer::destroyVirtualDisplay(Display display) {
ALOGE("destroyVirtualDisplay failed %s", status.getDescription().c_str());
return static_cast<Error>(status.getServiceSpecificError());
}
+ removeDisplay(display);
return Error::NONE;
}
Error AidlComposer::acceptDisplayChanges(Display display) {
- mWriter.acceptDisplayChanges(translate<int64_t>(display));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().acceptDisplayChanges(translate<int64_t>(display));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::createLayer(Display display, Layer* outLayer) {
int64_t layer;
- const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
- kMaxLayerBufferCount, &layer);
- if (!status.isOk()) {
- ALOGE("createLayer failed %s", status.getDescription().c_str());
- return static_cast<Error>(status.getServiceSpecificError());
+ Error error = Error::NONE;
+ if (!mEnableLayerCommandBatchingFlag) {
+ const auto status = mAidlComposerClient->createLayer(translate<int64_t>(display),
+ kMaxLayerBufferCount, &layer);
+ if (!status.isOk()) {
+ ALOGE("createLayer failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+ } else {
+ // generate a unique layerID. map in AidlComposer with <SF_layerID, HWC_layerID>
+ // Add this as a new displayCommand in execute command.
+ // return the SF generated layerID instead of calling HWC
+ layer = mLayerID++;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerLifecycleBatchCommandType(translate<int64_t>(display),
+ translate<int64_t>(layer),
+ LayerLifecycleBatchCommandType::CREATE);
+ writer->get().setNewBufferSlotCount(translate<int64_t>(display),
+ translate<int64_t>(layer), kMaxLayerBufferCount);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
}
-
*outLayer = translate<Layer>(layer);
- return Error::NONE;
+ return error;
}
Error AidlComposer::destroyLayer(Display display, Layer layer) {
- const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
- translate<int64_t>(layer));
- if (!status.isOk()) {
- ALOGE("destroyLayer failed %s", status.getDescription().c_str());
- return static_cast<Error>(status.getServiceSpecificError());
+ Error error = Error::NONE;
+ if (!mEnableLayerCommandBatchingFlag) {
+ const auto status = mAidlComposerClient->destroyLayer(translate<int64_t>(display),
+ translate<int64_t>(layer));
+ if (!status.isOk()) {
+ ALOGE("destroyLayer failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+ } else {
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get()
+ .setLayerLifecycleBatchCommandType(translate<int64_t>(display),
+ translate<int64_t>(layer),
+ LayerLifecycleBatchCommandType::DESTROY);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
}
- return Error::NONE;
+
+ return error;
}
Error AidlComposer::getActiveConfig(Display display, Config* outConfig) {
@@ -387,7 +480,17 @@ Error AidlComposer::getActiveConfig(Display display, Config* outConfig) {
Error AidlComposer::getChangedCompositionTypes(
Display display, std::vector<Layer>* outLayers,
std::vector<aidl::android::hardware::graphics::composer3::Composition>* outTypes) {
- const auto changedLayers = mReader.takeChangedCompositionTypes(translate<int64_t>(display));
+ std::vector<ChangedCompositionLayer> changedLayers;
+ Error error = Error::NONE;
+ {
+ mMutex.lock_shared();
+ if (auto reader = getReader(display)) {
+ changedLayers = reader->get().takeChangedCompositionTypes(translate<int64_t>(display));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ }
outLayers->reserve(changedLayers.size());
outTypes->reserve(changedLayers.size());
@@ -395,7 +498,7 @@ Error AidlComposer::getChangedCompositionTypes(
outLayers->emplace_back(translate<Layer>(layer.layer));
outTypes->emplace_back(layer.composition);
}
- return Error::NONE;
+ return error;
}
Error AidlComposer::getColorModes(Display display, std::vector<ColorMode>* outModes) {
@@ -435,6 +538,19 @@ Error AidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outC
return Error::NONE;
}
+Error AidlComposer::getDisplayConfigurations(Display display, int32_t maxFrameIntervalNs,
+ std::vector<DisplayConfiguration>* outConfigs) {
+ const auto status =
+ mAidlComposerClient->getDisplayConfigurations(translate<int64_t>(display),
+ maxFrameIntervalNs, outConfigs);
+ if (!status.isOk()) {
+ ALOGE("getDisplayConfigurations failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+
+ return Error::NONE;
+}
+
Error AidlComposer::getDisplayName(Display display, std::string* outName) {
const auto status = mAidlComposerClient->getDisplayName(translate<int64_t>(display), outName);
if (!status.isOk()) {
@@ -447,7 +563,17 @@ Error AidlComposer::getDisplayName(Display display, std::string* outName) {
Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
std::vector<Layer>* outLayers,
std::vector<uint32_t>* outLayerRequestMasks) {
- const auto displayRequests = mReader.takeDisplayRequests(translate<int64_t>(display));
+ Error error = Error::NONE;
+ DisplayRequest displayRequests;
+ {
+ mMutex.lock_shared();
+ if (auto reader = getReader(display)) {
+ displayRequests = reader->get().takeDisplayRequests(translate<int64_t>(display));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ }
*outDisplayRequestMask = translate<uint32_t>(displayRequests.mask);
outLayers->reserve(displayRequests.layerRequests.size());
outLayerRequestMasks->reserve(displayRequests.layerRequests.size());
@@ -456,7 +582,7 @@ Error AidlComposer::getDisplayRequests(Display display, uint32_t* outDisplayRequ
outLayers->emplace_back(translate<Layer>(layer.layer));
outLayerRequestMasks->emplace_back(translate<uint32_t>(layer.mask));
}
- return Error::NONE;
+ return error;
}
Error AidlComposer::getDozeSupport(Display display, bool* outSupport) {
@@ -496,16 +622,42 @@ Error AidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTyp
return static_cast<Error>(status.getServiceSpecificError());
}
- *outTypes = translate<Hdr>(capabilities.types);
+ *outTypes = capabilities.types;
*outMaxLuminance = capabilities.maxLuminance;
*outMaxAverageLuminance = capabilities.maxAverageLuminance;
*outMinLuminance = capabilities.minLuminance;
return Error::NONE;
}
+bool AidlComposer::getLayerLifecycleBatchCommand() {
+ std::vector<Capability> capabilities = getCapabilities();
+ bool hasCapability = std::find(capabilities.begin(), capabilities.end(),
+ Capability::LAYER_LIFECYCLE_BATCH_COMMAND) != capabilities.end();
+ return hasCapability;
+}
+
+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<Error>(status.getServiceSpecificError());
+ }
+ return Error::NONE;
+}
+
Error AidlComposer::getReleaseFences(Display display, std::vector<Layer>* outLayers,
std::vector<int>* outReleaseFences) {
- auto fences = mReader.takeReleaseFences(translate<int64_t>(display));
+ Error error = Error::NONE;
+ std::vector<ReleaseFences::Layer> fences;
+ {
+ mMutex.lock_shared();
+ if (auto reader = getReader(display)) {
+ fences = reader->get().takeReleaseFences(translate<int64_t>(display));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ }
outLayers->reserve(fences.size());
outReleaseFences->reserve(fences.size());
@@ -516,19 +668,31 @@ Error AidlComposer::getReleaseFences(Display display, std::vector<Layer>* 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<int64_t>(display));
+ const auto displayId = translate<int64_t>(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<int64_t>(display));
+ auto fence = reader->get().takePresentFence(displayId);
+ mMutex.unlock_shared();
// take ownership
*outPresentFence = fence.get();
*fence.getR() = -1;
@@ -547,17 +711,26 @@ Error AidlComposer::setActiveConfig(Display display, Config config) {
Error AidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
int acquireFence, Dataspace dataspace,
- const std::vector<IComposerClient::Rect>& damage) {
+ const std::vector<IComposerClient::Rect>& damage,
+ float hdrSdrRatio) {
const native_handle_t* handle = nullptr;
if (target.get()) {
handle = target->getNativeBuffer()->handle;
}
- mWriter.setClientTarget(translate<int64_t>(display), slot, handle, acquireFence,
- translate<aidl::android::hardware::graphics::common::Dataspace>(
- dataspace),
- translate<AidlRect>(damage));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get()
+ .setClientTarget(translate<int64_t>(display), slot, handle, acquireFence,
+ translate<aidl::android::hardware::graphics::common::Dataspace>(
+ dataspace),
+ translate<AidlRect>(damage), hdrSdrRatio);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) {
@@ -573,14 +746,28 @@ Error AidlComposer::setColorMode(Display display, ColorMode mode, RenderIntent r
}
Error AidlComposer::setColorTransform(Display display, const float* matrix) {
- mWriter.setColorTransform(translate<int64_t>(display), matrix);
- return Error::NONE;
+ auto error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setColorTransform(translate<int64_t>(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<int64_t>(display), 0, buffer, dup(releaseFence));
- return Error::NONE;
+ auto error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setOutputBuffer(translate<int64_t>(display), 0, buffer, dup(releaseFence));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setPowerMode(Display display, IComposerClient::PowerMode mode) {
@@ -616,58 +803,94 @@ 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<int64_t>(display),
- ClockMonotonicTimestamp{expectedPresentTime});
+ int32_t frameIntervalNs, uint32_t* outNumTypes,
+ uint32_t* outNumRequests) {
+ const auto displayId = translate<int64_t>(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},
+ frameIntervalNs);
+ error = execute(display);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
- Error error = execute();
if (error != Error::NONE) {
+ mMutex.unlock_shared();
return error;
}
- mReader.hasChanges(translate<int64_t>(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<int64_t>(display),
- ClockMonotonicTimestamp{expectedPresentTime});
+ int32_t frameIntervalNs, uint32_t* outNumTypes,
+ uint32_t* outNumRequests, int* outPresentFence,
+ uint32_t* state) {
+ const auto displayId = translate<int64_t>(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},
+ frameIntervalNs);
+ 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<int64_t>(display));
+ const auto result = reader->get().takePresentOrValidateStage(displayId);
if (!result.has_value()) {
*state = translate<uint32_t>(-1);
+ mMutex.unlock_shared();
return Error::NO_RESOURCES;
}
*state = translate<uint32_t>(*result);
if (*result == PresentOrValidate::Result::Presented) {
- auto fence = mReader.takePresentFence(translate<int64_t>(display));
+ auto fence = reader->get().takePresentFence(displayId);
// take ownership
*outPresentFence = fence.get();
*fence.getR() = -1;
}
if (*result == PresentOrValidate::Result::Validated) {
- mReader.hasChanges(translate<int64_t>(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<int64_t>(display), translate<int64_t>(layer), x, y);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerCursorPosition(translate<int64_t>(display), translate<int64_t>(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 +900,232 @@ Error AidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot,
handle = buffer->getNativeBuffer()->handle;
}
- mWriter.setLayerBuffer(translate<int64_t>(display), translate<int64_t>(layer), slot, handle,
- acquireFence);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerBuffer(translate<int64_t>(display), translate<int64_t>(layer), slot,
+ handle, acquireFence);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
+}
+
+Error AidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) {
+ if (slotsToClear.empty()) {
+ return Error::NONE;
+ }
+
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ if (mComposerInterfaceVersion > 1) {
+ writer->get().setLayerBufferSlotsToClear(translate<int64_t>(display),
+ translate<int64_t>(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<int64_t>(display),
+ translate<int64_t>(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<int64_t>(display),
+ translate<int64_t>(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<IComposerClient::Rect>& damage) {
- mWriter.setLayerSurfaceDamage(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlRect>(damage));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerSurfaceDamage(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlRect>(damage));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerBlendMode(Display display, Layer layer,
IComposerClient::BlendMode mode) {
- mWriter.setLayerBlendMode(translate<int64_t>(display), translate<int64_t>(layer),
- translate<BlendMode>(mode));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerBlendMode(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<BlendMode>(mode));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerColor(Display display, Layer layer, const Color& color) {
- mWriter.setLayerColor(translate<int64_t>(display), translate<int64_t>(layer), color);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerColor(translate<int64_t>(display), translate<int64_t>(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<int64_t>(display), translate<int64_t>(layer), type);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerCompositionType(translate<int64_t>(display),
+ translate<int64_t>(layer), type);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerDataspace(Display display, Layer layer, Dataspace dataspace) {
- mWriter.setLayerDataspace(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlDataspace>(dataspace));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerDataspace(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlDataspace>(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<int64_t>(display), translate<int64_t>(layer),
- translate<AidlRect>(frame));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerDisplayFrame(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlRect>(frame));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerPlaneAlpha(Display display, Layer layer, float alpha) {
- mWriter.setLayerPlaneAlpha(translate<int64_t>(display), translate<int64_t>(layer), alpha);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerPlaneAlpha(translate<int64_t>(display), translate<int64_t>(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<int64_t>(display), translate<int64_t>(layer), stream);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerSidebandStream(translate<int64_t>(display), translate<int64_t>(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<int64_t>(display), translate<int64_t>(layer),
- translate<AidlFRect>(crop));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerSourceCrop(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlFRect>(crop));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerTransform(Display display, Layer layer, Transform transform) {
- mWriter.setLayerTransform(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlTransform>(transform));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerTransform(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlTransform>(transform));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerVisibleRegion(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& visible) {
- mWriter.setLayerVisibleRegion(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlRect>(visible));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerVisibleRegion(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlRect>(visible));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerZOrder(Display display, Layer layer, uint32_t z) {
- mWriter.setLayerZOrder(translate<int64_t>(display), translate<int64_t>(layer), z);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerZOrder(translate<int64_t>(display), translate<int64_t>(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 +1137,9 @@ Error AidlComposer::execute() {
return static_cast<Error>(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<size_t>(cmdErr.commandIndex);
@@ -792,17 +1157,23 @@ Error AidlComposer::execute() {
}
}
- mWriter.reset();
-
return error;
}
Error AidlComposer::setLayerPerFrameMetadata(
Display display, Layer layer,
const std::vector<IComposerClient::PerFrameMetadata>& perFrameMetadatas) {
- mWriter.setLayerPerFrameMetadata(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlPerFrameMetadata>(perFrameMetadatas));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerPerFrameMetadata(translate<int64_t>(display),
+ translate<int64_t>(layer),
+ translate<AidlPerFrameMetadata>(perFrameMetadatas));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
std::vector<IComposerClient::PerFrameMetadataKey> AidlComposer::getPerFrameMetadataKeys(
@@ -862,8 +1233,16 @@ Error AidlComposer::getDisplayIdentificationData(Display display, uint8_t* outPo
}
Error AidlComposer::setLayerColorTransform(Display display, Layer layer, const float* matrix) {
- mWriter.setLayerColorTransform(translate<int64_t>(display), translate<int64_t>(layer), matrix);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerColorTransform(translate<int64_t>(display), translate<int64_t>(layer),
+ matrix);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::getDisplayedContentSamplingAttributes(Display display, PixelFormat* outFormat,
@@ -926,20 +1305,36 @@ Error AidlComposer::getDisplayedContentSample(Display display, uint64_t maxFrame
Error AidlComposer::setLayerPerFrameMetadataBlobs(
Display display, Layer layer,
const std::vector<IComposerClient::PerFrameMetadataBlob>& metadata) {
- mWriter.setLayerPerFrameMetadataBlobs(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlPerFrameMetadataBlob>(metadata));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerPerFrameMetadataBlobs(translate<int64_t>(display),
+ translate<int64_t>(layer),
+ translate<AidlPerFrameMetadataBlob>(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<int64_t>(display), brightness, brightnessNits);
-
- if (options.applyImmediately) {
- return execute();
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setDisplayBrightness(translate<int64_t>(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 +1472,95 @@ Error AidlComposer::getPreferredBootDisplayConfig(Display display, Config* confi
return Error::NONE;
}
+Error AidlComposer::getHdrConversionCapabilities(
+ std::vector<AidlHdrConversionCapability>* hdrConversionCapabilities) {
+ const auto status =
+ mAidlComposerClient->getHdrConversionCapabilities(hdrConversionCapabilities);
+ if (!status.isOk()) {
+ hdrConversionCapabilities = {};
+ ALOGE("getHdrConversionCapabilities failed %s", status.getDescription().c_str());
+ return static_cast<Error>(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<Error>(status.getServiceSpecificError());
+ }
+ return Error::NONE;
+}
+
+Error AidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display displayId, bool enabled) {
+ const auto status =
+ mAidlComposerClient->setRefreshRateChangedCallbackDebugEnabled(translate<int64_t>(
+ displayId),
+ enabled);
+ if (!status.isOk()) {
+ ALOGE("setRefreshRateChangedCallbackDebugEnabled failed %s",
+ status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+ return Error::NONE;
+}
+
+Error AidlComposer::notifyExpectedPresent(Display displayId, nsecs_t expectedPresentTime,
+ int32_t frameIntervalNs) {
+ const auto status =
+ mAidlComposerClient->notifyExpectedPresent(translate<int64_t>(displayId),
+ ClockMonotonicTimestamp{expectedPresentTime},
+ frameIntervalNs);
+
+ if (!status.isOk()) {
+ ALOGE("notifyExpectedPresent failed %s", status.getDescription().c_str());
+ return static_cast<Error>(status.getServiceSpecificError());
+ }
+ return Error::NONE;
+}
+
Error AidlComposer::getClientTargetProperty(
Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
- *outClientTargetProperty = mReader.takeClientTargetProperty(translate<int64_t>(display));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto reader = getReader(display)) {
+ *outClientTargetProperty =
+ reader->get().takeClientTargetProperty(translate<int64_t>(display));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerBrightness(Display display, Layer layer, float brightness) {
- mWriter.setLayerBrightness(translate<int64_t>(display), translate<int64_t>(layer), brightness);
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerBrightness(translate<int64_t>(display), translate<int64_t>(layer),
+ brightness);
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::setLayerBlockingRegion(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& blocking) {
- mWriter.setLayerBlockingRegion(translate<int64_t>(display), translate<int64_t>(layer),
- translate<AidlRect>(blocking));
- return Error::NONE;
+ Error error = Error::NONE;
+ mMutex.lock_shared();
+ if (auto writer = getWriter(display)) {
+ writer->get().setLayerBlockingRegion(translate<int64_t>(display), translate<int64_t>(layer),
+ translate<AidlRect>(blocking));
+ } else {
+ error = Error::BAD_DISPLAY;
+ }
+ mMutex.unlock_shared();
+ return error;
}
Error AidlComposer::getDisplayDecorationSupport(Display display,
@@ -1130,5 +1598,89 @@ Error AidlComposer::getPhysicalDisplayOrientation(Display displayId,
return Error::NONE;
}
+ftl::Optional<std::reference_wrapper<ComposerClientWriter>> AidlComposer::getWriter(Display display)
+ REQUIRES_SHARED(mMutex) {
+ return mWriters.get(display);
+}
+
+ftl::Optional<std::reference_wrapper<ComposerClientReader>> AidlComposer::getReader(Display display)
+ REQUIRES_SHARED(mMutex) {
+ if (mSingleReader) {
+ display = translate<Display>(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<int64_t>(display));
+ if (!mSingleReader) {
+ removeReader(display);
+ }
+ mMutex.unlock();
+}
+
+void AidlComposer::onHotplugDisconnect(Display display) {
+ removeDisplay(display);
+}
+
+bool AidlComposer::hasMultiThreadedPresentSupport(Display display) {
+ if (!FlagManager::getInstance().multithreaded_present()) return false;
+ const auto displayId = translate<int64_t>(display);
+ std::vector<AidlDisplayCapability> 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();
+}
+
+void AidlComposer::addReader(Display display) {
+ const auto displayId = translate<int64_t>(display);
+ std::optional<int64_t> 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<int64_t>(display));
+}
+
+void AidlComposer::addDisplay(Display display) {
+ const auto displayId = translate<int64_t>(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<Display>(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 18d2242c7e..ea0e53a202 100644
--- a/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/AidlComposerHal.h
@@ -18,9 +18,12 @@
#include "ComposerHal.h"
+#include <ftl/shared_mutex.h>
+#include <ui/DisplayMap.h>
+
+#include <functional>
#include <optional>
#include <string>
-#include <unordered_map>
#include <utility>
#include <vector>
@@ -46,8 +49,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;
@@ -60,6 +66,7 @@ public:
~AidlComposer() override;
bool isSupported(OptionalFeature) const;
+ bool isVrrSupported() const;
std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
override;
@@ -67,12 +74,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,
@@ -93,6 +96,8 @@ public:
Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
int32_t* outValue) override;
Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
+ Error getDisplayConfigurations(Display, int32_t maxFrameIntervalNs,
+ std::vector<DisplayConfiguration>*);
Error getDisplayName(Display display, std::string* outName) override;
Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
@@ -101,8 +106,9 @@ public:
Error getDozeSupport(Display display, bool* outSupport) override;
Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
- Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
+ Error getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes, float* outMaxLuminance,
float* outMaxAverageLuminance, float* outMinLuminance) override;
+ Error getOverlaySupport(OverlayProperties* outProperties) override;
Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
std::vector<int>* outReleaseFences) override;
@@ -118,7 +124,8 @@ public:
*/
Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
int acquireFence, Dataspace dataspace,
- const std::vector<IComposerClient::Rect>& damage) override;
+ const std::vector<IComposerClient::Rect>& damage,
+ float hdrSdrRatio) override;
Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
Error setColorTransform(Display display, const float* matrix) override;
Error setOutputBuffer(Display display, const native_handle_t* buffer,
@@ -128,17 +135,21 @@ public:
Error setClientTargetSlotCount(Display display) override;
- Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
- uint32_t* outNumRequests) override;
+ Error validateDisplay(Display display, nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+ uint32_t* outNumTypes, uint32_t* outNumRequests) override;
Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
- uint32_t* outNumTypes, uint32_t* outNumRequests,
- int* outPresentFence, uint32_t* state) override;
+ int32_t frameIntervalNs, uint32_t* outNumTypes,
+ uint32_t* outNumRequests, int* outPresentFence,
+ uint32_t* state) override;
Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
/* see setClientTarget for the purpose of slot */
Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
const sp<GraphicBuffer>& buffer, int acquireFence) override;
+ Error setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) override;
Error setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) override;
Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
@@ -226,16 +237,34 @@ public:
Error getPhysicalDisplayOrientation(Display displayId,
AidlTransform* outDisplayOrientation) override;
+ void onHotplugConnect(Display) override;
+ void onHotplugDisconnect(Display) override;
+ Error getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) override;
+ Error setHdrConversionStrategy(HdrConversionStrategy, Hdr*) override;
+ Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
+ Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
+ int32_t frameIntervalNs) 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<std::reference_wrapper<ComposerClientWriter>> getWriter(Display)
+ REQUIRES_SHARED(mMutex);
+ ftl::Optional<std::reference_wrapper<ComposerClientReader>> 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 getLayerLifecycleBatchCommand();
+ 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 +272,32 @@ 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;
+
+ ui::PhysicalDisplayMap<Display, ComposerClientWriter> mWriters GUARDED_BY(mMutex);
+ ui::PhysicalDisplayMap<Display, ComposerClientReader> 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;
+
+ int32_t mComposerInterfaceVersion = 1;
+ bool mEnableLayerCommandBatchingFlag = false;
+ std::atomic<int64_t> mLayerID = 1;
+
+ // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
+ sp<GraphicBuffer> 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 d266d942fb..bc067a0e5d 100644
--- a/services/surfaceflinger/DisplayHardware/ComposerHal.h
+++ b/services/surfaceflinger/DisplayHardware/ComposerHal.h
@@ -32,12 +32,16 @@
#include <utils/StrongPointer.h>
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
#include <aidl/android/hardware/graphics/composer3/Capability.h>
#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
#include <aidl/android/hardware/graphics/composer3/Color.h>
#include <aidl/android/hardware/graphics/composer3/Composition.h>
#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
#include <aidl/android/hardware/graphics/composer3/IComposerCallback.h>
+#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
#include <aidl/android/hardware/graphics/common/Transform.h>
#include <optional>
@@ -65,7 +69,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 +86,8 @@ using PerFrameMetadata = IComposerClient::PerFrameMetadata;
using PerFrameMetadataKey = IComposerClient::PerFrameMetadataKey;
using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
using AidlTransform = ::aidl::android::hardware::graphics::common::Transform;
+using DisplayConfiguration = V3_0::DisplayConfiguration;
+using aidl::android::hardware::graphics::common::Hdr;
class Composer {
public:
@@ -100,6 +105,7 @@ public:
};
virtual bool isSupported(OptionalFeature) const = 0;
+ virtual bool isVrrSupported() const = 0;
virtual std::vector<aidl::android::hardware::graphics::composer3::Capability>
getCapabilities() = 0;
@@ -107,12 +113,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*,
@@ -131,6 +133,10 @@ public:
virtual Error getDisplayAttribute(Display display, Config config,
IComposerClient::Attribute attribute, int32_t* outValue) = 0;
virtual Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs) = 0;
+
+ virtual Error getDisplayConfigurations(Display, int32_t maxFrameIntervalNs,
+ std::vector<DisplayConfiguration>*) = 0;
+
virtual Error getDisplayName(Display display, std::string* outName) = 0;
virtual Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
@@ -139,7 +145,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<Hdr>* outTypes,
+ virtual Error getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes,
float* outMaxLuminance, float* outMaxAverageLuminance,
float* outMinLuminance) = 0;
@@ -157,7 +163,8 @@ public:
*/
virtual Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
int acquireFence, Dataspace dataspace,
- const std::vector<IComposerClient::Rect>& damage) = 0;
+ const std::vector<IComposerClient::Rect>& damage,
+ float hdrSdrRatio) = 0;
virtual Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) = 0;
virtual Error setColorTransform(Display display, const float* matrix) = 0;
virtual Error setOutputBuffer(Display display, const native_handle_t* buffer,
@@ -168,16 +175,21 @@ public:
virtual Error setClientTargetSlotCount(Display display) = 0;
virtual Error validateDisplay(Display display, nsecs_t expectedPresentTime,
- uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
+ int32_t frameIntervalNs, uint32_t* outNumTypes,
+ uint32_t* outNumRequests) = 0;
virtual Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
- uint32_t* outNumTypes, uint32_t* outNumRequests,
- int* outPresentFence, uint32_t* state) = 0;
+ int32_t frameIntervalNs, uint32_t* outNumTypes,
+ uint32_t* outNumRequests, int* outPresentFence,
+ uint32_t* state) = 0;
virtual Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) = 0;
/* see setClientTarget for the purpose of slot */
virtual Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
const sp<GraphicBuffer>& buffer, int acquireFence) = 0;
+ virtual Error setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) = 0;
virtual Error setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) = 0;
virtual Error setLayerBlendMode(Display display, Layer layer,
@@ -281,6 +293,16 @@ 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;
+ virtual Error notifyExpectedPresent(Display, nsecs_t expectedPresentTime,
+ int32_t frameIntervalNs) = 0;
};
} // namespace Hwc2
diff --git a/services/surfaceflinger/DisplayHardware/DisplayMode.h b/services/surfaceflinger/DisplayHardware/DisplayMode.h
index 61a9a08a5c..ba0825c5af 100644
--- a/services/surfaceflinger/DisplayHardware/DisplayMode.h
+++ b/services/surfaceflinger/DisplayHardware/DisplayMode.h
@@ -29,6 +29,7 @@
#include <scheduler/Fps.h>
+#include <common/FlagManager.h>
#include "DisplayHardware/Hal.h"
#include "Scheduler/StrongTyping.h"
@@ -76,24 +77,29 @@ public:
}
Builder& setVsyncPeriod(nsecs_t vsyncPeriod) {
- mDisplayMode->mFps = Fps::fromPeriodNsecs(vsyncPeriod);
+ mDisplayMode->mVsyncRate = Fps::fromPeriodNsecs(vsyncPeriod);
return *this;
}
- Builder& setDpiX(int32_t dpiX) {
- if (dpiX == -1) {
+ Builder& setVrrConfig(std::optional<hal::VrrConfig> vrrConfig) {
+ mDisplayMode->mVrrConfig = std::move(vrrConfig);
+ return *this;
+ }
+
+ Builder& setDpiX(float dpiX) {
+ if (dpiX == -1.f) {
mDisplayMode->mDpi.x = getDefaultDensity();
} else {
- mDisplayMode->mDpi.x = dpiX / 1000.f;
+ mDisplayMode->mDpi.x = dpiX;
}
return *this;
}
- Builder& setDpiY(int32_t dpiY) {
- if (dpiY == -1) {
+ Builder& setDpiY(float dpiY) {
+ if (dpiY == -1.f) {
mDisplayMode->mDpi.y = getDefaultDensity();
} else {
- mDisplayMode->mDpi.y = dpiY / 1000.f;
+ mDisplayMode->mDpi.y = dpiY;
}
return *this;
}
@@ -130,8 +136,17 @@ public:
int32_t getWidth() const { return mResolution.getWidth(); }
int32_t getHeight() const { return mResolution.getHeight(); }
- Fps getFps() const { return mFps; }
- nsecs_t getVsyncPeriod() const { return mFps.getPeriodNsecs(); }
+ // Peak refresh rate represents the highest refresh rate that can be used
+ // for the presentation.
+ Fps getPeakFps() const {
+ return FlagManager::getInstance().vrr_config() && mVrrConfig
+ ? Fps::fromPeriodNsecs(mVrrConfig->minFrameIntervalNs)
+ : mVsyncRate;
+ }
+
+ Fps getVsyncRate() const { return mVsyncRate; }
+
+ std::optional<hal::VrrConfig> getVrrConfig() const { return mVrrConfig; }
struct Dpi {
float x = -1;
@@ -155,23 +170,25 @@ private:
PhysicalDisplayId mPhysicalDisplayId;
ui::Size mResolution;
- Fps mFps;
+ Fps mVsyncRate;
Dpi mDpi;
int32_t mGroup = -1;
+ std::optional<hal::VrrConfig> mVrrConfig;
};
inline bool equalsExceptDisplayModeId(const DisplayMode& lhs, const DisplayMode& rhs) {
return lhs.getHwcId() == rhs.getHwcId() && lhs.getResolution() == rhs.getResolution() &&
- lhs.getVsyncPeriod() == rhs.getVsyncPeriod() && lhs.getDpi() == rhs.getDpi() &&
- lhs.getGroup() == rhs.getGroup();
+ lhs.getVsyncRate().getPeriodNsecs() == rhs.getVsyncRate().getPeriodNsecs() &&
+ lhs.getDpi() == rhs.getDpi() && lhs.getGroup() == rhs.getGroup();
}
inline std::string to_string(const DisplayMode& mode) {
- return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, refreshRate=%s, "
- "dpi=%.2fx%.2f, group=%d}",
+ return base::StringPrintf("{id=%d, hwcId=%d, resolution=%dx%d, vsyncRate=%s, "
+ "dpi=%.2fx%.2f, group=%d, vrrConfig=%s}",
mode.getId().value(), mode.getHwcId(), mode.getWidth(),
- mode.getHeight(), to_string(mode.getFps()).c_str(), mode.getDpi().x,
- mode.getDpi().y, mode.getGroup());
+ mode.getHeight(), to_string(mode.getVsyncRate()).c_str(),
+ mode.getDpi().x, mode.getDpi().y, mode.getGroup(),
+ to_string(mode.getVrrConfig()).c_str());
}
template <typename... DisplayModePtrs>
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp b/services/surfaceflinger/DisplayHardware/FramebufferSurface.cpp
index eb14933a61..c77cdd4432 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) {
@@ -87,32 +91,17 @@ status_t FramebufferSurface::prepareFrame(CompositionType /*compositionType*/) {
return NO_ERROR;
}
-status_t FramebufferSurface::advanceFrame() {
- uint32_t slot = 0;
- sp<GraphicBuffer> buf;
- sp<Fence> 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<GraphicBuffer>& outBuffer, sp<Fence>& outFence,
- Dataspace& outDataspace) {
+status_t FramebufferSurface::advanceFrame(float hdrSdrRatio) {
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<Dataspace>(item.mDataSpace);
- outFence = item.mFence;
- mHwcBufferCache.getHwcBuffer(mCurrentBufferSlot, mCurrentBuffer, &outSlot, &outBuffer);
- outDataspace = static_cast<Dataspace>(item.mDataSpace);
- status_t result = mHwc.setClientTarget(mDisplayId, outSlot, outFence, outBuffer, outDataspace);
+ // assume HWC has previously seen the buffer in this slot
+ sp<GraphicBuffer> hwcBuffer = sp<GraphicBuffer>(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, hdrSdrRatio);
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<android_dataspace>(mDataSpace)).c_str(),
- mDataSpace);
+ result.appendFormat(" mDataspace=%s (%d)\n",
+ dataspaceDetails(static_cast<android_dataspace>(mDataspace)).c_str(),
+ mDataspace);
ConsumerBase::dumpLocked(result, " ");
}
diff --git a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
index d41a856e68..2728cf637e 100644
--- a/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
+++ b/services/surfaceflinger/DisplayHardware/FramebufferSurface.h
@@ -21,7 +21,7 @@
#include <sys/types.h>
#include <compositionengine/DisplaySurface.h>
-#include <compositionengine/impl/HwcBufferCache.h>
+#include <gui/BufferQueue.h>
#include <gui/ConsumerBase.h>
#include <ui/DisplayId.h>
#include <ui/Size.h>
@@ -46,7 +46,7 @@ public:
virtual status_t beginFrame(bool mustRecompose);
virtual status_t prepareFrame(CompositionType compositionType);
- virtual status_t advanceFrame();
+ virtual status_t advanceFrame(float hdrSdrRatio);
virtual void onFrameCommitted();
virtual void dumpAsString(String8& result) const;
@@ -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<GraphicBuffer>& outBuffer,
- sp<Fence>& 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 8364ed9d28..704ece516d 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 {
@@ -164,8 +165,7 @@ Error Display::getChangedCompositionTypes(std::unordered_map<HWC2::Layer*, Compo
auto intError = mComposer.getChangedCompositionTypes(
mId, &layerIds, &types);
uint32_t numElements = layerIds.size();
- auto error = static_cast<Error>(intError);
- error = static_cast<Error>(intError);
+ const auto error = static_cast<Error>(intError);
if (error != Error::NONE) {
return error;
}
@@ -310,6 +310,14 @@ bool Display::hasCapability(DisplayCapability capability) const {
}
Error Display::supportsDoze(bool* outSupport) const {
+ {
+ std::scoped_lock lock(mDisplayCapabilitiesMutex);
+ if (!mDisplayCapabilities) {
+ // The display has not turned on since boot, so DOZE support is unknown.
+ ALOGW("%s: haven't queried capabilities yet!", __func__);
+ return Error::NO_RESOURCES;
+ }
+ }
*outSupport = hasCapability(DisplayCapability::DOZE);
return Error::NONE;
}
@@ -319,20 +327,25 @@ Error Display::getHdrCapabilities(HdrCapabilities* outCapabilities) const
float maxLuminance = -1.0f;
float maxAverageLuminance = -1.0f;
float minLuminance = -1.0f;
- std::vector<Hwc2::Hdr> types;
- auto intError = mComposer.getHdrCapabilities(mId, &types,
- &maxLuminance, &maxAverageLuminance, &minLuminance);
+ std::vector<Hwc2::Hdr> hdrTypes;
+ auto intError = mComposer.getHdrCapabilities(mId, &hdrTypes, &maxLuminance,
+ &maxAverageLuminance, &minLuminance);
auto error = static_cast<HWC2::Error>(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<Error>(intError);
+}
+
Error Display::getDisplayedContentSamplingAttributes(hal::PixelFormat* outFormat,
Dataspace* outDataspace,
uint8_t* outComponentMask) const {
@@ -369,7 +382,7 @@ Error Display::getReleaseFences(std::unordered_map<HWC2::Layer*, sp<Fence>>* out
for (uint32_t element = 0; element < numElements; ++element) {
auto layer = getLayerById(layerIds[element]);
if (layer) {
- sp<Fence> fence(new Fence(fenceFds[element]));
+ sp<Fence> fence(sp<Fence>::make(fenceFds[element]));
releaseFences.emplace(layer.get(), fence);
} else {
ALOGE("getReleaseFences: invalid layer %" PRIu64
@@ -394,7 +407,7 @@ Error Display::present(sp<Fence>* outPresentFence)
return error;
}
- *outPresentFence = new Fence(presentFenceFd);
+ *outPresentFence = sp<Fence>::make(presentFenceFd);
return Error::NONE;
}
@@ -432,12 +445,13 @@ Error Display::setActiveConfigWithConstraints(hal::HWConfigId configId,
}
Error Display::setClientTarget(uint32_t slot, const sp<GraphicBuffer>& target,
- const sp<Fence>& acquireFence, Dataspace dataspace)
-{
+ const sp<Fence>& acquireFence, Dataspace dataspace,
+ float hdrSdrRatio) {
// TODO: Properly encode client target surface damage
int32_t fenceFd = acquireFence->dup();
- auto intError = mComposer.setClientTarget(mId, slot, target,
- fenceFd, dataspace, std::vector<Hwc2::IComposerClient::Rect>());
+ auto intError =
+ mComposer.setClientTarget(mId, slot, target, fenceFd, dataspace,
+ std::vector<Hwc2::IComposerClient::Rect>(), hdrSdrRatio);
return static_cast<Error>(intError);
}
@@ -503,11 +517,12 @@ Error Display::setVsyncEnabled(Vsync enabled)
return static_cast<Error>(intError);
}
-Error Display::validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+Error Display::validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs, uint32_t* outNumTypes,
uint32_t* outNumRequests) {
uint32_t numTypes = 0;
uint32_t numRequests = 0;
- auto intError = mComposer.validateDisplay(mId, expectedPresentTime, &numTypes, &numRequests);
+ auto intError = mComposer.validateDisplay(mId, expectedPresentTime, frameIntervalNs, &numTypes,
+ &numRequests);
auto error = static_cast<Error>(intError);
if (error != Error::NONE && !hasChangesError(error)) {
return error;
@@ -518,21 +533,22 @@ Error Display::validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
return error;
}
-Error Display::presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
- uint32_t* outNumRequests, sp<android::Fence>* outPresentFence,
- uint32_t* state) {
+Error Display::presentOrValidate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+ uint32_t* outNumTypes, uint32_t* outNumRequests,
+ sp<android::Fence>* outPresentFence, uint32_t* state) {
uint32_t numTypes = 0;
uint32_t numRequests = 0;
int32_t presentFenceFd = -1;
- auto intError = mComposer.presentOrValidateDisplay(mId, expectedPresentTime, &numTypes,
- &numRequests, &presentFenceFd, state);
+ auto intError =
+ mComposer.presentOrValidateDisplay(mId, expectedPresentTime, frameIntervalNs, &numTypes,
+ &numRequests, &presentFenceFd, state);
auto error = static_cast<Error>(intError);
if (error != Error::NONE && !hasChangesError(error)) {
return error;
}
if (*state == 1) {
- *outPresentFence = new Fence(presentFenceFd);
+ *outPresentFence = sp<Fence>::make(presentFenceFd);
}
if (*state == 0) {
@@ -711,6 +727,16 @@ Error Layer::setBuffer(uint32_t slot, const sp<GraphicBuffer>& buffer,
return static_cast<Error>(intError);
}
+Error Layer::setBufferSlotsToClear(const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) {
+ if (CC_UNLIKELY(!mDisplay)) {
+ return Error::BAD_DISPLAY;
+ }
+ auto intError = mComposer.setLayerBufferSlotsToClear(mDisplay->getId(), mId, slotsToClear,
+ activeBufferSlot);
+ return static_cast<Error>(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 24aef9b73c..f907061774 100644
--- a/services/surfaceflinger/DisplayHardware/HWC2.h
+++ b/services/surfaceflinger/DisplayHardware/HWC2.h
@@ -38,11 +38,14 @@
#include "Hal.h"
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
#include <aidl/android/hardware/graphics/composer3/Capability.h>
#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
#include <aidl/android/hardware/graphics/composer3/Color.h>
#include <aidl/android/hardware/graphics/composer3/Composition.h>
#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
+#include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
namespace android {
@@ -62,20 +65,24 @@ class Layer;
namespace hal = android::hardware::graphics::composer::hal;
+using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
+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
-// when first registering the callback the onComposerHalHotplug() function will
-// immediately be called on the thread calling registerCallback().
+// when first registering the callback the onComposerHalHotplugEvent() function
+// will immediately be called on the thread calling registerCallback().
struct ComposerCallback {
- virtual void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) = 0;
+ virtual void onComposerHalHotplugEvent(hal::HWDisplayId, DisplayHotplugEvent) = 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<hal::VsyncPeriodNanos>) = 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 +95,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 +124,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;
@@ -131,7 +141,8 @@ public:
[[nodiscard]] virtual hal::Error present(android::sp<android::Fence>* outPresentFence) = 0;
[[nodiscard]] virtual hal::Error setClientTarget(
uint32_t slot, const android::sp<android::GraphicBuffer>& target,
- const android::sp<android::Fence>& acquireFence, hal::Dataspace dataspace) = 0;
+ const android::sp<android::Fence>& acquireFence, hal::Dataspace dataspace,
+ float hdrSdrRatio) = 0;
[[nodiscard]] virtual hal::Error setColorMode(hal::ColorMode mode,
hal::RenderIntent renderIntent) = 0;
[[nodiscard]] virtual hal::Error setColorTransform(const android::mat4& matrix) = 0;
@@ -140,9 +151,10 @@ public:
const android::sp<android::Fence>& releaseFence) = 0;
[[nodiscard]] virtual hal::Error setPowerMode(hal::PowerMode mode) = 0;
[[nodiscard]] virtual hal::Error setVsyncEnabled(hal::Vsync enabled) = 0;
- [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
- uint32_t* outNumRequests) = 0;
+ [[nodiscard]] virtual hal::Error validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+ uint32_t* outNumTypes, uint32_t* outNumRequests) = 0;
[[nodiscard]] virtual hal::Error presentOrValidate(nsecs_t expectedPresentTime,
+ int32_t frameIntervalNs,
uint32_t* outNumTypes,
uint32_t* outNumRequests,
android::sp<android::Fence>* outPresentFence,
@@ -204,6 +216,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;
@@ -216,17 +230,17 @@ public:
hal::Error present(android::sp<android::Fence>* outPresentFence) override;
hal::Error setClientTarget(uint32_t slot, const android::sp<android::GraphicBuffer>& target,
const android::sp<android::Fence>& acquireFence,
- hal::Dataspace dataspace) override;
+ hal::Dataspace dataspace, float hdrSdrRatio) override;
hal::Error setColorMode(hal::ColorMode, hal::RenderIntent) override;
hal::Error setColorTransform(const android::mat4& matrix) override;
hal::Error setOutputBuffer(const android::sp<android::GraphicBuffer>&,
const android::sp<android::Fence>& releaseFence) override;
hal::Error setPowerMode(hal::PowerMode) override;
hal::Error setVsyncEnabled(hal::Vsync enabled) override;
- hal::Error validate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
+ hal::Error validate(nsecs_t expectedPresentTime, int32_t frameIntervalNs, uint32_t* outNumTypes,
uint32_t* outNumRequests) override;
- hal::Error presentOrValidate(nsecs_t expectedPresentTime, uint32_t* outNumTypes,
- uint32_t* outNumRequests,
+ hal::Error presentOrValidate(nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+ uint32_t* outNumTypes, uint32_t* outNumRequests,
android::sp<android::Fence>* outPresentFence,
uint32_t* state) override;
ftl::Future<hal::Error> setDisplayBrightness(
@@ -253,7 +267,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 +285,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 +318,8 @@ public:
[[nodiscard]] virtual hal::Error setBuffer(uint32_t slot,
const android::sp<android::GraphicBuffer>& buffer,
const android::sp<android::Fence>& acquireFence) = 0;
+ [[nodiscard]] virtual hal::Error setBufferSlotsToClear(
+ const std::vector<uint32_t>& 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 +370,8 @@ public:
hal::Error setCursorPosition(int32_t x, int32_t y) override;
hal::Error setBuffer(uint32_t slot, const android::sp<android::GraphicBuffer>& buffer,
const android::sp<android::Fence>& acquireFence) override;
+ hal::Error setBufferSlotsToClear(const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) override;
hal::Error setSurfaceDamage(const android::Region& damage) override;
hal::Error setBlendMode(hal::BlendMode mode) override;
@@ -383,7 +401,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 a6aee1f2f5..3ffd8ea316 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.cpp
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.cpp
@@ -15,6 +15,7 @@
*/
// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <chrono>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
@@ -30,6 +31,8 @@
#include <compositionengine/Output.h>
#include <compositionengine/OutputLayer.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
+#include <ftl/concat.h>
+#include <gui/TraceUtils.h>
#include <log/log.h>
#include <ui/DebugUtils.h>
#include <ui/GraphicBuffer.h>
@@ -70,6 +73,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 +101,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 +151,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<PhysicalDisplayId> 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,17 +260,53 @@ std::shared_ptr<HWC2::Layer> 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::HWCDisplayMode> HWComposer::getModes(PhysicalDisplayId displayId) const {
+std::vector<HWComposer::HWCDisplayMode> HWComposer::getModes(PhysicalDisplayId displayId,
+ int32_t maxFrameIntervalNs) const {
RETURN_IF_INVALID_DISPLAY(displayId, {});
const auto hwcDisplayId = mDisplayData.at(displayId).hwcDisplay->getId();
+
+ if (mComposer->isVrrSupported()) {
+ return getModesFromDisplayConfigurations(hwcDisplayId, maxFrameIntervalNs);
+ }
+
+ return getModesFromLegacyDisplayConfigs(hwcDisplayId);
+}
+
+std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromDisplayConfigurations(
+ uint64_t hwcDisplayId, int32_t maxFrameIntervalNs) const {
+ std::vector<hal::DisplayConfiguration> configs;
+ auto error = static_cast<hal::Error>(
+ mComposer->getDisplayConfigurations(hwcDisplayId, maxFrameIntervalNs, &configs));
+ RETURN_IF_HWC_ERROR_FOR("getDisplayConfigurations", error, *toPhysicalDisplayId(hwcDisplayId),
+ {});
+
+ std::vector<HWCDisplayMode> modes;
+ modes.reserve(configs.size());
+ for (auto config : configs) {
+ auto hwcMode = HWCDisplayMode{.hwcId = static_cast<hal::HWConfigId>(config.configId),
+ .width = config.width,
+ .height = config.height,
+ .vsyncPeriod = config.vsyncPeriod,
+ .configGroup = config.configGroup,
+ .vrrConfig = config.vrrConfig};
+
+ if (config.dpi) {
+ hwcMode.dpiX = config.dpi->x;
+ hwcMode.dpiY = config.dpi->y;
+ }
+
+ modes.push_back(hwcMode);
+ }
+
+ return modes;
+}
+
+std::vector<HWComposer::HWCDisplayMode> HWComposer::getModesFromLegacyDisplayConfigs(
+ uint64_t hwcDisplayId) const {
std::vector<hal::HWConfigId> configIds;
auto error = static_cast<hal::Error>(mComposer->getDisplayConfigs(hwcDisplayId, &configIds));
RETURN_IF_HWC_ERROR_FOR("getDisplayConfigs", error, *toPhysicalDisplayId(hwcDisplayId), {});
@@ -270,33 +314,36 @@ std::vector<HWComposer::HWCDisplayMode> HWComposer::getModes(PhysicalDisplayId d
std::vector<HWCDisplayMode> modes;
modes.reserve(configIds.size());
for (auto configId : configIds) {
- modes.push_back(HWCDisplayMode{
+ auto hwcMode = HWCDisplayMode{
.hwcId = configId,
.width = getAttribute(hwcDisplayId, configId, hal::Attribute::WIDTH),
.height = getAttribute(hwcDisplayId, configId, hal::Attribute::HEIGHT),
.vsyncPeriod = getAttribute(hwcDisplayId, configId, hal::Attribute::VSYNC_PERIOD),
- .dpiX = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_X),
- .dpiY = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_Y),
.configGroup = getAttribute(hwcDisplayId, configId, hal::Attribute::CONFIG_GROUP),
- });
- }
+ };
+ const int32_t dpiX = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_X);
+ const int32_t dpiY = getAttribute(hwcDisplayId, configId, hal::Attribute::DPI_Y);
+ if (dpiX != -1) {
+ hwcMode.dpiX = static_cast<float>(dpiX) / 1000.f;
+ }
+ if (dpiY != -1) {
+ hwcMode.dpiY = static_cast<float>(dpiY) / 1000.f;
+ }
+
+ modes.push_back(hwcMode);
+ }
return modes;
}
std::optional<hal::HWConfigId> 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<hal::Error>(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<hal::Error>(mComposer->getActiveConfig(hwcId, &configId));
+ RETURN_IF_HWC_ERROR_FOR("getActiveConfig", error, displayId, std::nullopt);
return configId;
}
@@ -380,26 +427,26 @@ 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,
const sp<Fence>& acquireFence, const sp<GraphicBuffer>& target,
- ui::Dataspace dataspace) {
+ ui::Dataspace dataspace, float hdrSdrRatio) {
RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
ALOGV("%s for display %s", __FUNCTION__, to_string(displayId).c_str());
auto& hwcDisplay = mDisplayData[displayId].hwcDisplay;
- auto error = hwcDisplay->setClientTarget(slot, target, acquireFence, dataspace);
+ auto error = hwcDisplay->setClientTarget(slot, target, acquireFence, dataspace, hdrSdrRatio);
RETURN_IF_HWC_ERROR(error, displayId, BAD_VALUE);
return NO_ERROR;
}
status_t HWComposer::getDeviceCompositionChanges(
HalDisplayId displayId, bool frameUsesClientComposition,
- std::chrono::steady_clock::time_point earliestPresentTime,
- const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
+ nsecs_t expectedPresentTime, Fps frameInterval,
std::optional<android::HWComposer::DeviceRequestedChanges>* outChanges) {
ATRACE_CALL();
@@ -429,22 +476,22 @@ 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;
+ ATRACE_FORMAT("NextFrameInterval %d_Hz", frameInterval.getIntValue());
if (canSkipValidate) {
- sp<Fence> outPresentFence;
+ sp<Fence> outPresentFence = Fence::NO_FENCE;
uint32_t state = UINT32_MAX;
- error = hwcDisplay->presentOrValidate(expectedPresentTime, &numTypes, &numRequests,
- &outPresentFence, &state);
+ error = hwcDisplay->presentOrValidate(expectedPresentTime, frameInterval.getPeriodNsecs(),
+ &numTypes, &numRequests, &outPresentFence, &state);
if (!hasChangesError(error)) {
RETURN_IF_HWC_ERROR_FOR("presentOrValidate", error, displayId, UNKNOWN_ERROR);
}
@@ -459,7 +506,8 @@ status_t HWComposer::getDeviceCompositionChanges(
}
// Present failed but Validate ran.
} else {
- error = hwcDisplay->validate(expectedPresentTime, &numTypes, &numRequests);
+ error = hwcDisplay->validate(expectedPresentTime, frameInterval.getPeriodNsecs(), &numTypes,
+ &numRequests);
}
ALOGV("SkipValidate failed, Falling back to SLOW validate/present");
if (!hasChangesError(error)) {
@@ -494,6 +542,11 @@ sp<Fence> 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<Fence> 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 +559,8 @@ sp<Fence> HWComposer::getLayerReleaseFence(HalDisplayId displayId, HWC2::Layer*
}
status_t HWComposer::presentAndGetReleaseFences(
- HalDisplayId displayId, std::chrono::steady_clock::time_point earliestPresentTime,
- const std::shared_ptr<FenceTime>& previousPresentFence) {
+ HalDisplayId displayId,
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) {
ATRACE_CALL();
RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
@@ -517,19 +570,15 @@ status_t HWComposer::presentAndGetReleaseFences(
if (displayData.validateWasSkipped) {
// explicitly flush all pending commands
- auto error = static_cast<hal::Error>(mComposer->executeCommands());
+ auto error = static_cast<hal::Error>(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);
@@ -570,19 +619,29 @@ status_t HWComposer::setPowerMode(PhysicalDisplayId displayId, hal::PowerMode mo
ALOGV("setPowerMode: Calling HWC %s", to_string(mode).c_str());
{
bool supportsDoze = false;
- auto error = hwcDisplay->supportsDoze(&supportsDoze);
- if (error != hal::Error::NONE) {
- LOG_HWC_ERROR("supportsDoze", error, displayId);
- }
+ const auto queryDozeError = hwcDisplay->supportsDoze(&supportsDoze);
- if (!supportsDoze) {
+ // queryDozeError might be NO_RESOURCES, in the case of a display that has never
+ // been turned on. In that case, attempt to set to DOZE anyway.
+ if (!supportsDoze && queryDozeError == hal::Error::NONE) {
mode = hal::PowerMode::ON;
}
- error = hwcDisplay->setPowerMode(mode);
+ auto error = hwcDisplay->setPowerMode(mode);
if (error != hal::Error::NONE) {
LOG_HWC_ERROR(("setPowerMode(" + to_string(mode) + ")").c_str(), error,
displayId);
+ // If the display had never been turned on, so its doze
+ // support was unknown, it may truly not support doze. Try
+ // switching it to ON instead.
+ if (queryDozeError == hal::Error::NO_RESOURCES) {
+ ALOGD("%s: failed to set %s to %s. Trying again with ON", __func__,
+ to_string(displayId).c_str(), to_string(mode).c_str());
+ error = hwcDisplay->setPowerMode(hal::PowerMode::ON);
+ if (error != hal::Error::NONE) {
+ LOG_HWC_ERROR("setPowerMode(ON)", error, displayId);
+ }
+ }
}
}
break;
@@ -656,6 +715,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 +849,53 @@ std::optional<hal::HWConfigId> HWComposer::getPreferredBootDisplayMode(
return displayModeId;
}
+std::vector<HdrConversionCapability> 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::notifyExpectedPresent(PhysicalDisplayId displayId,
+ TimePoint expectedPresentTime, Fps frameInterval) {
+ RETURN_IF_INVALID_DISPLAY(displayId, BAD_INDEX);
+ ATRACE_FORMAT("%s ExpectedPresentTime in %.2fms frameInterval %.2fms", __func__,
+ ticks<std::milli, float>(expectedPresentTime - TimePoint::now()),
+ ticks<std::milli, float>(Duration::fromNs(frameInterval.getPeriodNsecs())));
+ const auto error = mComposer->notifyExpectedPresent(mDisplayData[displayId].hwcDisplay->getId(),
+ expectedPresentTime.ns(),
+ frameInterval.getPeriodNsecs());
+ if (error != hal::Error::NONE) {
+ ALOGE("Error in notifyExpectedPresent call %s", to_string(error).c_str());
+ return INVALID_OPERATION;
+ }
+ return NO_ERROR;
+}
+
status_t HWComposer::getDisplayDecorationSupport(
PhysicalDisplayId displayId,
std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
@@ -932,6 +1043,8 @@ std::optional<DisplayIdentificationInfo> HWComposer::onHotplugConnect(
: "Secondary display",
.deviceProductInfo = std::nullopt};
}();
+
+ mComposer->onHotplugConnect(hwcDisplayId);
}
if (!isConnected(info->id)) {
@@ -951,18 +1064,16 @@ std::optional<DisplayIdentificationInfo> 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 +1084,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 92a8f30f1b..4ca528a6e7 100644
--- a/services/surfaceflinger/DisplayHardware/HWComposer.h
+++ b/services/surfaceflinger/DisplayHardware/HWComposer.h
@@ -44,10 +44,14 @@
#include "Hal.h"
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/Hdr.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionCapability.h>
+#include <aidl/android/hardware/graphics/common/HdrConversionStrategy.h>
#include <aidl/android/hardware/graphics/composer3/Capability.h>
#include <aidl/android/hardware/graphics/composer3/ClientTargetPropertyWithBrightness.h>
#include <aidl/android/hardware/graphics/composer3/Composition.h>
#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/OverlayProperties.h>
namespace android {
@@ -56,6 +60,7 @@ namespace hal = hardware::graphics::composer::hal;
struct DisplayedFrameStats;
class GraphicBuffer;
class TestableSurfaceFlinger;
+struct HWComposerTest;
struct CompositionInfo;
namespace Hwc2 {
@@ -95,14 +100,16 @@ public:
int32_t width = -1;
int32_t height = -1;
nsecs_t vsyncPeriod = -1;
- int32_t dpiX = -1;
- int32_t dpiY = -1;
+ float dpiX = -1.f;
+ float dpiY = -1.f;
int32_t configGroup = -1;
+ std::optional<hal::VrrConfig> vrrConfig;
friend std::ostream& operator<<(std::ostream& os, const HWCDisplayMode& mode) {
return os << "id=" << mode.hwcId << " res=" << mode.width << "x" << mode.height
<< " vsyncPeriod=" << mode.vsyncPeriod << " dpi=" << mode.dpiX << "x"
- << mode.dpiY << " group=" << mode.configGroup;
+ << mode.dpiY << " group=" << mode.configGroup
+ << " vrrConfig=" << to_string(mode.vrrConfig).c_str();
}
};
@@ -139,17 +146,18 @@ public:
// expected.
virtual status_t getDeviceCompositionChanges(
HalDisplayId, bool frameUsesClientComposition,
- std::chrono::steady_clock::time_point earliestPresentTime,
- const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
+ nsecs_t expectedPresentTime, Fps frameInterval,
std::optional<DeviceRequestedChanges>* outChanges) = 0;
virtual status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
- const sp<GraphicBuffer>& target, ui::Dataspace) = 0;
+ const sp<GraphicBuffer>& target, ui::Dataspace,
+ float hdrSdrRatio) = 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<FenceTime>& previousPresentFence) = 0;
+ HalDisplayId,
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) = 0;
// set power mode
virtual status_t setPowerMode(PhysicalDisplayId, hal::PowerMode) = 0;
@@ -160,8 +168,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<Fence> getPresentFence(HalDisplayId) const = 0;
+ virtual nsecs_t getPresentTimestamp(PhysicalDisplayId) const = 0;
// Get last release fence for the given layer
virtual sp<Fence> getLayerReleaseFence(HalDisplayId, HWC2::Layer*) const = 0;
@@ -177,6 +186,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,12 +226,16 @@ 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<PhysicalDisplayId> onVsync(hal::HWDisplayId, nsecs_t timestamp) = 0;
+
virtual void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) = 0;
virtual bool isConnected(PhysicalDisplayId) const = 0;
- virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId) const = 0;
+ virtual std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
+ int32_t maxFrameIntervalNs) const = 0;
virtual std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const = 0;
@@ -281,6 +297,14 @@ 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<aidl::android::hardware::graphics::common::HdrConversionCapability>
+ 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;
+ virtual status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+ Fps frameInterval) = 0;
};
static inline bool operator==(const android::HWComposer::DeviceRequestedChanges& lhs,
@@ -322,17 +346,18 @@ public:
status_t getDeviceCompositionChanges(
HalDisplayId, bool frameUsesClientComposition,
- std::chrono::steady_clock::time_point earliestPresentTime,
- const std::shared_ptr<FenceTime>& previousPresentFence, nsecs_t expectedPresentTime,
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime,
+ nsecs_t expectedPresentTime, Fps frameInterval,
std::optional<DeviceRequestedChanges>* outChanges) override;
status_t setClientTarget(HalDisplayId, uint32_t slot, const sp<Fence>& acquireFence,
- const sp<GraphicBuffer>& target, ui::Dataspace) override;
+ const sp<GraphicBuffer>& target, ui::Dataspace,
+ float hdrSdrRatio) override;
// Present layers to the display and read releaseFences.
status_t presentAndGetReleaseFences(
- HalDisplayId, std::chrono::steady_clock::time_point earliestPresentTime,
- const std::shared_ptr<FenceTime>& previousPresentFence) override;
+ HalDisplayId,
+ std::optional<std::chrono::steady_clock::time_point> earliestPresentTime) override;
// set power mode
status_t setPowerMode(PhysicalDisplayId, hal::PowerMode mode) override;
@@ -343,8 +368,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<Fence> getPresentFence(HalDisplayId) const override;
+ nsecs_t getPresentTimestamp(PhysicalDisplayId) const override;
// Get last release fence for the given layer
sp<Fence> getLayerReleaseFence(HalDisplayId, HWC2::Layer*) const override;
@@ -360,6 +386,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,12 +416,13 @@ public:
bool updatesDeviceProductInfoOnHotplugReconnect() const override;
- bool onVsync(hal::HWDisplayId, int64_t timestamp) override;
+ std::optional<PhysicalDisplayId> onVsync(hal::HWDisplayId, nsecs_t timestamp) override;
void setVsyncEnabled(PhysicalDisplayId, hal::Vsync enabled) override;
bool isConnected(PhysicalDisplayId) const override;
- std::vector<HWCDisplayMode> getModes(PhysicalDisplayId) const override;
+ std::vector<HWCDisplayMode> getModes(PhysicalDisplayId,
+ int32_t maxFrameIntervalNs) const override;
std::optional<hal::HWConfigId> getActiveMode(PhysicalDisplayId) const override;
@@ -428,6 +458,14 @@ public:
status_t setIdleTimerEnabled(PhysicalDisplayId, std::chrono::milliseconds timeout) override;
bool hasDisplayIdleTimerCapability(PhysicalDisplayId) const override;
Hwc2::AidlTransform getPhysicalDisplayOrientation(PhysicalDisplayId) const override;
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+ 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;
+ status_t notifyExpectedPresent(PhysicalDisplayId, TimePoint expectedPresentTime,
+ Fps frameInterval) override;
// for debugging ----------------------------------------------------------
void dump(std::string& out) const override;
@@ -456,7 +494,10 @@ private:
struct DisplayData {
std::unique_ptr<HWC2::Display> hwcDisplay;
+
sp<Fence> lastPresentFence = Fence::NO_FENCE; // signals when the last set op retires
+ nsecs_t lastPresentTimestamp = 0;
+
std::unordered_map<HWC2::Layer*, sp<Fence>> releaseFences;
bool validateWasSkipped;
@@ -466,24 +507,32 @@ private:
std::mutex vsyncEnabledLock;
hal::Vsync vsyncEnabled GUARDED_BY(vsyncEnabledLock) = hal::Vsync::DISABLE;
-
- nsecs_t lastHwVsync = 0;
};
std::optional<DisplayIdentificationInfo> onHotplugConnect(hal::HWDisplayId);
std::optional<DisplayIdentificationInfo> onHotplugDisconnect(hal::HWDisplayId);
bool shouldIgnoreHotplugConnect(hal::HWDisplayId, bool hasDisplayIdentificationData) const;
+ std::vector<HWCDisplayMode> getModesFromDisplayConfigurations(uint64_t hwcDisplayId,
+ int32_t maxFrameIntervalNs) const;
+ std::vector<HWCDisplayMode> getModesFromLegacyDisplayConfigs(uint64_t hwcDisplayId) const;
+
int32_t getAttribute(hal::HWDisplayId hwcDisplayId, hal::HWConfigId configId,
hal::Attribute attribute) const;
void loadCapabilities();
void loadLayerMetadataSupport();
+ void loadOverlayProperties();
+ void loadHdrConversionCapabilities();
std::unordered_map<HalDisplayId, DisplayData> mDisplayData;
std::unique_ptr<android::Hwc2::Composer> mComposer;
std::unordered_set<aidl::android::hardware::graphics::composer3::Capability> mCapabilities;
+ aidl::android::hardware::graphics::composer3::OverlayProperties mOverlayProperties;
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>
+ mHdrConversionCapabilities = {};
+
std::unordered_map<std::string, bool> mSupportedLayerGenericMetadata;
bool mRegisteredCallback = false;
diff --git a/services/surfaceflinger/DisplayHardware/Hal.h b/services/surfaceflinger/DisplayHardware/Hal.h
index 473703423e..31c2833229 100644
--- a/services/surfaceflinger/DisplayHardware/Hal.h
+++ b/services/surfaceflinger/DisplayHardware/Hal.h
@@ -20,8 +20,12 @@
#include <android/hardware/graphics/composer/2.4/IComposer.h>
#include <android/hardware/graphics/composer/2.4/IComposerClient.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
+#include <aidl/android/hardware/graphics/common/Hdr.h>
#include <aidl/android/hardware/graphics/composer3/Composition.h>
#include <aidl/android/hardware/graphics/composer3/DisplayCapability.h>
+#include <aidl/android/hardware/graphics/composer3/DisplayConfiguration.h>
+#include <aidl/android/hardware/graphics/composer3/VrrConfig.h>
#define ERROR_HAS_CHANGES 5
@@ -33,13 +37,13 @@ namespace V2_1 = android::hardware::graphics::composer::V2_1;
namespace V2_2 = android::hardware::graphics::composer::V2_2;
namespace V2_3 = android::hardware::graphics::composer::V2_3;
namespace V2_4 = android::hardware::graphics::composer::V2_4;
+namespace V3_0 = ::aidl::android::hardware::graphics::composer3;
using types::V1_0::ColorTransform;
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;
@@ -55,6 +59,7 @@ using Connection = IComposerCallback::Connection;
using ContentType = IComposerClient::ContentType;
using Capability = IComposer::Capability;
using ClientTargetProperty = IComposerClient::ClientTargetProperty;
+using DisplayHotplugEvent = aidl::android::hardware::graphics::common::DisplayHotplugEvent;
using DisplayRequest = IComposerClient::DisplayRequest;
using DisplayType = IComposerClient::DisplayType;
using HWConfigId = V2_1::Config;
@@ -69,6 +74,9 @@ using PerFrameMetadataBlob = IComposerClient::PerFrameMetadataBlob;
using PowerMode = IComposerClient::PowerMode;
using Vsync = IComposerClient::Vsync;
using VsyncPeriodChangeConstraints = IComposerClient::VsyncPeriodChangeConstraints;
+using Hdr = aidl::android::hardware::graphics::common::Hdr;
+using DisplayConfiguration = V3_0::DisplayConfiguration;
+using VrrConfig = V3_0::VrrConfig;
} // namespace hardware::graphics::composer::hal
@@ -112,6 +120,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";
}
@@ -142,6 +152,34 @@ inline std::string to_string(
}
}
+inline std::string to_string(
+ const std::optional<aidl::android::hardware::graphics::composer3::VrrConfig>& vrrConfig) {
+ if (vrrConfig) {
+ std::ostringstream out;
+ out << "{minFrameIntervalNs=" << vrrConfig->minFrameIntervalNs << ", ";
+ out << "frameIntervalPowerHints={";
+ if (vrrConfig->frameIntervalPowerHints) {
+ const auto& powerHint = *vrrConfig->frameIntervalPowerHints;
+ for (size_t i = 0; i < powerHint.size(); i++) {
+ if (i > 0) out << ", ";
+ out << "[frameIntervalNs=" << powerHint[i]->frameIntervalNs
+ << ", averageRefreshPeriodNs=" << powerHint[i]->averageRefreshPeriodNs << "]";
+ }
+ }
+ out << "}, ";
+ out << "notifyExpectedPresentConfig={";
+ if (vrrConfig->notifyExpectedPresentConfig) {
+ out << "notifyExpectedPresentHeadsUpNs="
+ << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentHeadsUpNs
+ << ", notifyExpectedPresentTimeoutNs="
+ << vrrConfig->notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs;
+ }
+ out << "}}";
+ return out.str();
+ }
+ return "N/A";
+}
+
inline std::string to_string(hardware::graphics::composer::hal::V2_4::Error error) {
// 5 is reserved for historical reason, during validation 5 means has changes.
if (ERROR_HAS_CHANGES == static_cast<int32_t>(error)) {
@@ -177,6 +215,10 @@ inline std::string to_string(hardware::graphics::composer::hal::Error error) {
return to_string(static_cast<hardware::graphics::composer::hal::V2_4::Error>(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 +236,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 +251,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 2597ae6091..c4ff9cc6be 100644
--- a/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
+++ b/services/surfaceflinger/DisplayHardware/HidlComposerHal.cpp
@@ -24,22 +24,29 @@
#include "HidlComposerHal.h"
+#include <SurfaceFlingerProperties.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
#include <android/binder_manager.h>
#include <composer-command-buffer/2.2/ComposerCommandBuffer.h>
#include <hidl/HidlTransportSupport.h>
#include <hidl/HidlTransportUtils.h>
#include <log/log.h>
#include <utils/Trace.h>
+
#include "HWC2.h"
#include "Hal.h"
#include <algorithm>
#include <cinttypes>
+using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
+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 {
@@ -59,8 +66,13 @@ public:
ComposerCallbackBridge(ComposerCallback& callback, bool vsyncSwitchingSupported)
: mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
+ // For code sharing purposes, `ComposerCallback` (implemented by SurfaceFlinger)
+ // replaced `onComposerHalHotplug` with `onComposerHalHotplugEvent` by converting
+ // from HIDL's connection into an AIDL DisplayHotplugEvent.
Return<void> onHotplug(Display display, Connection connection) override {
- mCallback.onComposerHalHotplug(display, connection);
+ const auto event = connection == Connection::CONNECTED ? DisplayHotplugEvent::CONNECTED
+ : DisplayHotplugEvent::DISCONNECTED;
+ mCallback.onComposerHalHotplugEvent(display, event);
return Void();
}
@@ -185,9 +197,25 @@ std::vector<To> translate(const hidl_vec<From>& in) {
return out;
}
+sp<GraphicBuffer> allocateClearSlotBuffer() {
+ if (!sysprop::clear_slots_with_set_layer_buffer(false)) {
+ return nullptr;
+ }
+ sp<GraphicBuffer> buffer = sp<GraphicBuffer>::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 +257,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 {
@@ -243,6 +276,11 @@ bool HidlComposer::isSupported(OptionalFeature feature) const {
}
}
+bool HidlComposer::isVrrSupported() const {
+ // VRR is not supported on the HIDL composer.
+ return false;
+};
+
std::vector<Capability> HidlComposer::getCapabilities() {
std::vector<Capability> capabilities;
mComposer->getCapabilities([&](const auto& tmpCapabilities) {
@@ -272,11 +310,7 @@ void HidlComposer::registerCallback(const sp<IComposerCallback>& callback) {
}
}
-void HidlComposer::resetCommands() {
- mWriter.reset();
-}
-
-Error HidlComposer::executeCommands() {
+Error HidlComposer::executeCommands(Display) {
return execute();
}
@@ -455,6 +489,12 @@ Error HidlComposer::getDisplayConfigs(Display display, std::vector<Config>* outC
return error;
}
+Error HidlComposer::getDisplayConfigurations(Display, int32_t /*maxFrameIntervalNs*/,
+ std::vector<DisplayConfiguration>*) {
+ LOG_ALWAYS_FATAL("getDisplayConfigurations should not have been called on this, as "
+ "it's a HWC3 interface version 3 feature");
+}
+
Error HidlComposer::getDisplayName(Display display, std::string* outName) {
Error error = kDefaultError;
mClient->getDisplayName(display, [&](const auto& tmpError, const auto& tmpName) {
@@ -495,13 +535,13 @@ Error HidlComposer::hasDisplayIdleTimerCapability(Display, bool*) {
"OptionalFeature::KernelIdleTimer is not supported on HIDL");
}
-Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTypes,
+Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* 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 +549,15 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTyp
if (error != Error::NONE) {
return;
}
+ *outHdrTypes = translate<ui::Hdr>(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 +565,7 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTyp
if (error != Error::NONE) {
return;
}
-
- outTypes->clear();
- for (auto type : tmpTypes) {
- outTypes->push_back(static_cast<Hdr>(type));
- }
+ *outHdrTypes = translate<ui::Hdr>(tmpHdrTypes);
*outMaxLuminance = tmpMaxLuminance;
*outMaxAverageLuminance = tmpMaxAverageLuminance;
@@ -540,6 +576,10 @@ Error HidlComposer::getHdrCapabilities(Display display, std::vector<Hdr>* outTyp
return error;
}
+Error HidlComposer::getOverlaySupport(OverlayProperties* /*outProperties*/) {
+ return Error::NONE;
+}
+
Error HidlComposer::getReleaseFences(Display display, std::vector<Layer>* outLayers,
std::vector<int>* outReleaseFences) {
mReader.takeReleaseFences(display, outLayers, outReleaseFences);
@@ -568,7 +608,8 @@ Error HidlComposer::setActiveConfig(Display display, Config config) {
Error HidlComposer::setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
int acquireFence, Dataspace dataspace,
- const std::vector<IComposerClient::Rect>& damage) {
+ const std::vector<IComposerClient::Rect>& damage,
+ float /*hdrSdrRatio*/) {
mWriter.selectDisplay(display);
const native_handle_t* handle = nullptr;
@@ -632,7 +673,8 @@ Error HidlComposer::setClientTargetSlotCount(Display display) {
}
Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
- uint32_t* outNumTypes, uint32_t* outNumRequests) {
+ int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
+ uint32_t* outNumRequests) {
ATRACE_NAME("HwcValidateDisplay");
mWriter.selectDisplay(display);
mWriter.validateDisplay();
@@ -648,8 +690,9 @@ Error HidlComposer::validateDisplay(Display display, nsecs_t /*expectedPresentTi
}
Error HidlComposer::presentOrValidateDisplay(Display display, nsecs_t /*expectedPresentTime*/,
- uint32_t* outNumTypes, uint32_t* outNumRequests,
- int* outPresentFence, uint32_t* state) {
+ int32_t /*frameIntervalNs*/, uint32_t* outNumTypes,
+ uint32_t* outNumRequests, int* outPresentFence,
+ uint32_t* state) {
ATRACE_NAME("HwcPresentOrValidateDisplay");
mWriter.selectDisplay(display);
mWriter.presentOrvalidateDisplay();
@@ -693,6 +736,36 @@ Error HidlComposer::setLayerBuffer(Display display, Layer layer, uint32_t slot,
return Error::NONE;
}
+Error HidlComposer::setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& 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<IComposerClient::Rect>& damage) {
mWriter.selectDisplay(display);
@@ -1303,6 +1376,22 @@ Error HidlComposer::getPreferredBootDisplayConfig(Display /*displayId*/, Config*
return Error::UNSUPPORTED;
}
+Error HidlComposer::getHdrConversionCapabilities(std::vector<HdrConversionCapability>*) {
+ return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::setHdrConversionStrategy(HdrConversionStrategy, Hdr*) {
+ return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::setRefreshRateChangedCallbackDebugEnabled(Display, bool) {
+ return Error::UNSUPPORTED;
+}
+
+Error HidlComposer::notifyExpectedPresent(Display, nsecs_t, int32_t) {
+ return Error::UNSUPPORTED;
+}
+
Error HidlComposer::getClientTargetProperty(
Display display, ClientTargetPropertyWithBrightness* outClientTargetProperty) {
IComposerClient::ClientTargetProperty property;
@@ -1352,6 +1441,9 @@ void HidlComposer::registerCallback(ComposerCallback& callback) {
registerCallback(sp<ComposerCallbackBridge>::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 d0d3c2e6d7..d78bfb7c6b 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;
@@ -168,6 +167,7 @@ public:
~HidlComposer() override;
bool isSupported(OptionalFeature) const;
+ bool isVrrSupported() const;
std::vector<aidl::android::hardware::graphics::composer3::Capability> getCapabilities()
override;
@@ -175,12 +175,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,
@@ -201,6 +197,8 @@ public:
Error getDisplayAttribute(Display display, Config config, IComposerClient::Attribute attribute,
int32_t* outValue) override;
Error getDisplayConfigs(Display display, std::vector<Config>* outConfigs);
+ Error getDisplayConfigurations(Display, int32_t maxFrameIntervalNs,
+ std::vector<DisplayConfiguration>*);
Error getDisplayName(Display display, std::string* outName) override;
Error getDisplayRequests(Display display, uint32_t* outDisplayRequestMask,
@@ -209,8 +207,10 @@ public:
Error getDozeSupport(Display display, bool* outSupport) override;
Error hasDisplayIdleTimerCapability(Display display, bool* outSupport) override;
- Error getHdrCapabilities(Display display, std::vector<Hdr>* outTypes, float* outMaxLuminance,
+ Error getHdrCapabilities(Display display, std::vector<Hdr>* outHdrTypes, float* outMaxLuminance,
float* outMaxAverageLuminance, float* outMinLuminance) override;
+ Error getOverlaySupport(aidl::android::hardware::graphics::composer3::OverlayProperties*
+ outProperties) override;
Error getReleaseFences(Display display, std::vector<Layer>* outLayers,
std::vector<int>* outReleaseFences) override;
@@ -226,7 +226,8 @@ public:
*/
Error setClientTarget(Display display, uint32_t slot, const sp<GraphicBuffer>& target,
int acquireFence, Dataspace dataspace,
- const std::vector<IComposerClient::Rect>& damage) override;
+ const std::vector<IComposerClient::Rect>& damage,
+ float hdrSdrRatio) override;
Error setColorMode(Display display, ColorMode mode, RenderIntent renderIntent) override;
Error setColorTransform(Display display, const float* matrix) override;
Error setOutputBuffer(Display display, const native_handle_t* buffer,
@@ -236,17 +237,21 @@ public:
Error setClientTargetSlotCount(Display display) override;
- Error validateDisplay(Display display, nsecs_t expectedPresentTime, uint32_t* outNumTypes,
- uint32_t* outNumRequests) override;
+ Error validateDisplay(Display display, nsecs_t expectedPresentTime, int32_t frameIntervalNs,
+ uint32_t* outNumTypes, uint32_t* outNumRequests) override;
Error presentOrValidateDisplay(Display display, nsecs_t expectedPresentTime,
- uint32_t* outNumTypes, uint32_t* outNumRequests,
- int* outPresentFence, uint32_t* state) override;
+ int32_t frameIntervalNs, uint32_t* outNumTypes,
+ uint32_t* outNumRequests, int* outPresentFence,
+ uint32_t* state) override;
Error setCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
/* see setClientTarget for the purpose of slot */
Error setLayerBuffer(Display display, Layer layer, uint32_t slot,
const sp<GraphicBuffer>& buffer, int acquireFence) override;
+ Error setLayerBufferSlotsToClear(Display display, Layer layer,
+ const std::vector<uint32_t>& slotsToClear,
+ uint32_t activeBufferSlot) override;
Error setLayerSurfaceDamage(Display display, Layer layer,
const std::vector<IComposerClient::Rect>& damage) override;
Error setLayerBlendMode(Display display, Layer layer, IComposerClient::BlendMode mode) override;
@@ -337,6 +342,15 @@ public:
Error getPhysicalDisplayOrientation(Display displayId,
AidlTransform* outDisplayOrientation) override;
+ void onHotplugConnect(Display) override;
+ void onHotplugDisconnect(Display) override;
+ Error getHdrConversionCapabilities(
+ std::vector<aidl::android::hardware::graphics::common::HdrConversionCapability>*)
+ override;
+ Error setHdrConversionStrategy(aidl::android::hardware::graphics::common::HdrConversionStrategy,
+ Hdr*) override;
+ Error setRefreshRateChangedCallbackDebugEnabled(Display, bool) override;
+ Error notifyExpectedPresent(Display, nsecs_t, int32_t) override;
private:
class CommandWriter : public CommandWriterBase {
@@ -359,6 +373,9 @@ private:
sp<V2_3::IComposerClient> mClient_2_3;
sp<IComposerClient> mClient_2_4;
+ // Buffer slots for layers are cleared by setting the slot buffer to this buffer.
+ sp<GraphicBuffer> 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 a0350b717a..dd228b4523 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.cpp
@@ -31,9 +31,9 @@
#include <utils/Mutex.h>
#include <utils/Trace.h>
-#include <android/hardware/power/1.3/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <android/hardware/power/WorkDuration.h>
+#include <aidl/android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPowerHintSession.h>
+#include <aidl/android/hardware/power/WorkDuration.h>
#include <binder/IServiceManager.h>
@@ -49,17 +49,10 @@ 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::WorkDuration;
-
-using scheduler::OneShotTimer;
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::WorkDuration;
PowerAdvisor::~PowerAdvisor() = default;
@@ -81,7 +74,8 @@ void traceExpensiveRendering(bool enabled) {
} // namespace
-PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger) : mFlinger(flinger) {
+PowerAdvisor::PowerAdvisor(SurfaceFlinger& flinger)
+ : mPowerHal(std::make_unique<power::PowerHalController>()), mFlinger(flinger) {
if (getUpdateTimeout() > 0ms) {
mScreenUpdateTimer.emplace("UpdateImminentTimer", getUpdateTimeout(),
/* resetCallback */ nullptr,
@@ -118,6 +112,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 +124,37 @@ 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::notifyCpuLoadUp() {
+ // 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()) {
+ return;
+ }
+ if (usePowerHintSession()) {
+ std::lock_guard lock(mHintSessionMutex);
+ if (ensurePowerHintSessionRunning()) {
+ auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_UP);
+ if (!ret.isOk()) {
+ mHintSession = nullptr;
+ }
+ }
+ }
+}
+
+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 +162,24 @@ 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()) {
+ std::lock_guard lock(mHintSessionMutex);
+ if (ensurePowerHintSessionRunning()) {
+ auto ret = mHintSession->sendHint(SessionHint::CPU_LOAD_RESET);
+ if (!ret.isOk()) {
+ mHintSession = nullptr;
+ }
+ }
}
- 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) {
@@ -176,91 +196,137 @@ 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 (mHintSession == nullptr && !mHintSessionThreadIds.empty() && usePowerHintSession()) {
+ auto ret = getPowerHal().createHintSession(getpid(), static_cast<int32_t>(getuid()),
+ mHintSessionThreadIds, mTargetDuration.ns());
+
+ if (ret.isOk()) {
+ mHintSession = ret.value();
+ }
+ }
+ return mHintSession != nullptr;
}
-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 (targetDuration == mLastTargetDurationSent) return;
+ std::lock_guard lock(mHintSessionMutex);
+ if (ensurePowerHintSessionRunning()) {
+ ALOGV("Sending target time: %" PRId64 "ns", targetDuration.ns());
+ mLastTargetDurationSent = targetDuration;
+ auto ret = mHintSession->updateTargetWorkDuration(targetDuration.ns());
+ if (!ret.isOk()) {
+ ALOGW("Failed to set power hint target work duration with error: %s",
+ ret.getDescription().c_str());
+ mHintSession = nullptr;
+ }
}
}
}
-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<nsecs_t> 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<Duration> actualDuration = estimateWorkDuration();
+ if (!actualDuration.has_value() || actualDuration < 0ns) {
+ ALOGV("Failed to send actual work duration, skipping");
+ return;
}
-}
+ actualDuration = std::make_optional(*actualDuration + sTargetSafetyMargin);
+ mActualDuration = actualDuration;
-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());
+ }
+
+ ALOGV("Sending actual work duration of: %" PRId64 " on reported target: %" PRId64
+ " with error: %" PRId64,
+ actualDuration->ns(), mLastTargetDurationSent.ns(),
+ Duration{*actualDuration - mLastTargetDurationSent}.ns());
+
+ if (mTimingTestingMode) {
+ mDelayReportActualMutexAcquisitonPromise.get_future().wait();
+ mDelayReportActualMutexAcquisitonPromise = std::promise<bool>{};
}
- const std::optional<nsecs_t> predictedDuration = estimateWorkDuration(true);
+ {
+ std::lock_guard lock(mHintSessionMutex);
+ if (!ensurePowerHintSessionRunning()) {
+ ALOGV("Hint session not running and could not be started, skipping");
+ return;
+ }
- if (predictedDuration.has_value()) {
- std::lock_guard lock(mPowerHalMutex);
- HalWrapper* const halWrapper = getPowerHal();
- if (halWrapper != nullptr) {
- halWrapper->sendActualWorkDuration(*predictedDuration + kTargetSafetyMargin.count(),
- systemTime());
+ WorkDuration duration{
+ .timeStampNanos = TimePoint::now().ns(),
+ // TODO(b/284324521): Correctly calculate total duration.
+ .durationNanos = actualDuration->ns(),
+ .workPeriodStartTimestampNanos = mCommitStartTimes[0].ns(),
+ .cpuDurationNanos = actualDuration->ns(),
+ // TODO(b/284324521): Calculate RenderEngine GPU time.
+ .gpuDurationNanos = 0,
+ };
+ mHintSessionQueue.push_back(duration);
+
+ auto ret = mHintSession->reportActualWorkDuration(mHintSessionQueue);
+ if (!ret.isOk()) {
+ ALOGW("Failed to report actual work durations with error: %s",
+ ret.getDescription().c_str());
+ mHintSession = nullptr;
+ return;
}
}
+ mHintSessionQueue.clear();
}
-void PowerAdvisor::enablePowerHint(bool enabled) {
- mPowerHintEnabled = enabled;
+void PowerAdvisor::enablePowerHintSession(bool enabled) {
+ mHintSessionEnabled = enabled;
}
-bool PowerAdvisor::startPowerHintSession(const std::vector<int32_t>& threadIds) {
+bool PowerAdvisor::startPowerHintSession(std::vector<int32_t>&& threadIds) {
+ mHintSessionThreadIds = 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;
}
- {
- std::lock_guard lock(mPowerHalMutex);
- HalWrapper* halWrapper = getPowerHal();
- if (halWrapper != nullptr && usePowerHintSession()) {
- halWrapper->setPowerHintSessionThreadIds(threadIds);
- mPowerHintSessionRunning = halWrapper->startPowerHintSession();
- }
+ LOG_ALWAYS_FATAL_IF(mHintSessionThreadIds.empty(),
+ "No thread IDs provided to power hint session!");
+ std::lock_guard lock(mHintSessionMutex);
+ if (mHintSession != nullptr) {
+ ALOGE("Cannot start power hint session: already running");
+ return false;
}
- return mPowerHintSessionRunning;
+ return ensurePowerHintSessionRunning();
}
void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) {
@@ -281,22 +347,22 @@ void PowerAdvisor::setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTim
}
}
displayData.lastValidGpuStartTime = displayData.gpuStartTime;
- displayData.lastValidGpuEndTime = signalTime;
+ displayData.lastValidGpuEndTime = TimePoint::fromNs(signalTime);
}
}
displayData.gpuEndFenceTime = std::move(fenceTime);
- displayData.gpuStartTime = systemTime();
+ displayData.gpuStartTime = TimePoint::now();
}
-void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, nsecs_t validateStartTime,
- nsecs_t validateEndTime) {
+void PowerAdvisor::setHwcValidateTiming(DisplayId displayId, TimePoint validateStartTime,
+ TimePoint validateEndTime) {
DisplayTimingData& displayData = mDisplayTimingData[displayId];
displayData.hwcValidateStartTime = validateStartTime;
displayData.hwcValidateEndTime = validateEndTime;
}
-void PowerAdvisor::setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime,
- nsecs_t presentEndTime) {
+void PowerAdvisor::setHwcPresentTiming(DisplayId displayId, TimePoint presentStartTime,
+ TimePoint presentEndTime) {
DisplayTimingData& displayData = mDisplayTimingData[displayId];
displayData.hwcPresentStartTime = presentStartTime;
displayData.hwcPresentEndTime = presentEndTime;
@@ -311,43 +377,41 @@ void PowerAdvisor::setRequiresClientComposition(DisplayId displayId,
mDisplayTimingData[displayId].usedClientComposition = requiresClientComposition;
}
-void PowerAdvisor::setExpectedPresentTime(nsecs_t expectedPresentTime) {
+void PowerAdvisor::setExpectedPresentTime(TimePoint expectedPresentTime) {
mExpectedPresentTimes.append(expectedPresentTime);
}
-void PowerAdvisor::setSfPresentTiming(nsecs_t presentFenceTime, nsecs_t presentEndTime) {
+void PowerAdvisor::setSfPresentTiming(TimePoint presentFenceTime, TimePoint presentEndTime) {
mLastSfPresentEndTime = presentEndTime;
mLastPresentFenceTime = presentFenceTime;
}
-void PowerAdvisor::setFrameDelay(nsecs_t frameDelayDuration) {
+void PowerAdvisor::setFrameDelay(Duration frameDelayDuration) {
mFrameDelayDuration = frameDelayDuration;
}
-void PowerAdvisor::setHwcPresentDelayedTime(
- DisplayId displayId, std::chrono::steady_clock::time_point earliestFrameStartTime) {
- mDisplayTimingData[displayId].hwcPresentDelayedTime =
- (earliestFrameStartTime - std::chrono::steady_clock::now()).count() + systemTime();
+void PowerAdvisor::setHwcPresentDelayedTime(DisplayId displayId, TimePoint earliestFrameStartTime) {
+ mDisplayTimingData[displayId].hwcPresentDelayedTime = earliestFrameStartTime;
}
-void PowerAdvisor::setCommitStart(nsecs_t commitStartTime) {
+void PowerAdvisor::setCommitStart(TimePoint commitStartTime) {
mCommitStartTimes.append(commitStartTime);
}
-void PowerAdvisor::setCompositeEnd(nsecs_t compositeEnd) {
- mLastPostcompDuration = compositeEnd - mLastSfPresentEndTime;
+void PowerAdvisor::setCompositeEnd(TimePoint compositeEndTime) {
+ mLastPostcompDuration = compositeEndTime - mLastSfPresentEndTime;
}
void PowerAdvisor::setDisplays(std::vector<DisplayId>& displayIds) {
mDisplayIds = displayIds;
}
-void PowerAdvisor::setTotalFrameTargetWorkDuration(nsecs_t targetDuration) {
+void PowerAdvisor::setTotalFrameTargetWorkDuration(Duration targetDuration) {
mTotalFrameTargetDuration = targetDuration;
}
std::vector<DisplayId> PowerAdvisor::getOrderedDisplayIds(
- std::optional<nsecs_t> DisplayTimingData::*sortBy) {
+ std::optional<TimePoint> DisplayTimingData::*sortBy) {
std::vector<DisplayId> sortedDisplays;
std::copy_if(mDisplayIds.begin(), mDisplayIds.end(), std::back_inserter(sortedDisplays),
[&](DisplayId id) {
@@ -360,39 +424,30 @@ std::vector<DisplayId> PowerAdvisor::getOrderedDisplayIds(
return sortedDisplays;
}
-std::optional<nsecs_t> PowerAdvisor::estimateWorkDuration(bool earlyHint) {
- if (earlyHint && (!mExpectedPresentTimes.isFull() || !mCommitStartTimes.isFull())) {
+std::optional<Duration> 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<nsecs_t> previousValidGpuEndTime;
+ std::optional<TimePoint> previousValidGpuEndTime;
// The currently estimated gpu end time for the frame,
// used to accumulate gpu time as we iterate over the active displays
- std::optional<nsecs_t> 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<TimePoint> estimatedGpuEndTime;
// The timing info for the previously calculated display, if there was one
- std::optional<DisplayTimeline> previousDisplayReferenceTiming;
+ std::optional<DisplayTimeline> previousDisplayTiming;
std::vector<DisplayId>&& 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 +457,26 @@ std::optional<nsecs_t> 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 +484,54 @@ std::optional<nsecs_t> 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 +546,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 +561,45 @@ PowerAdvisor::DisplayTimeline PowerAdvisor::DisplayTimingData::calculateDisplayT
}
std::optional<PowerAdvisor::GpuTimeline> PowerAdvisor::DisplayTimingData::estimateGpuTiming(
- std::optional<nsecs_t> previousEnd) {
+ std::optional<TimePoint> 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<V1_3::IPower> powerHal) : mPowerHal(std::move(powerHal)) {}
-
- ~HidlPowerHalWrapper() override = default;
-
- static std::unique_ptr<HalWrapper> 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<V1_3::IPower> powerHal = nullptr;
- sp<V1_0::IPower> 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<HidlPowerHalWrapper>(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<int32_t>&) 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<int32_t> getPowerHintSessionThreadIds() override { return std::vector<int32_t>{}; }
-
- std::optional<int64_t> getTargetWorkDuration() override { return std::nullopt; }
-
-private:
- const sp<V1_3::IPower> mPowerHal = nullptr;
-};
-
-AidlPowerHalWrapper::AidlPowerHalWrapper(sp<IPower> 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<nsecs_t>("debug.sf.allowed_actual_deviation", 0);
-}
-
-AidlPowerHalWrapper::~AidlPowerHalWrapper() {
- if (mPowerHintSession != nullptr) {
- mPowerHintSession->close();
- mPowerHintSession = nullptr;
- }
-}
-
-std::unique_ptr<PowerAdvisor::HalWrapper> AidlPowerHalWrapper::connect() {
- // This only waits if the service is actually declared
- sp<IPower> powerHal = waitForVintfService<IPower>();
- if (powerHal == nullptr) {
- return nullptr;
- }
- ALOGI("Loaded AIDL Power HAL service");
-
- return std::make_unique<AidlPowerHalWrapper>(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<int32_t>& 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<int32_t>(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<int32_t> AidlPowerHalWrapper::getPowerHintSessionThreadIds() {
- return mPowerHintThreadIds;
-}
-
-std::optional<int64_t> 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<int32_t> oldPowerHintSessionThreadIds;
- std::optional<int64_t> 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;
- }
-
- 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();
+const Duration PowerAdvisor::sTargetSafetyMargin = std::chrono::microseconds(
+ base::GetIntProperty<int64_t>("debug.sf.hint_margin_us",
+ ticks<std::micro>(PowerAdvisor::kDefaultTargetSafetyMargin)));
- // 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 6e25f787d7..0276e44986 100644
--- a/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
+++ b/services/surfaceflinger/DisplayHardware/PowerAdvisor.h
@@ -25,8 +25,10 @@
#include <ui/FenceTime.h>
#include <utils/Mutex.h>
-#include <android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPower.h>
#include <compositionengine/impl/OutputCompositionState.h>
+#include <powermanager/PowerHalController.h>
+#include <scheduler/Time.h>
#include <ui/DisplayIdentification.h>
#include "../Scheduler/OneShotTimer.h"
@@ -44,54 +46,59 @@ public:
// Initializes resources that cannot be initialized on construction
virtual void init() = 0;
+ // Used to indicate that power hints can now be reported
virtual void onBootFinished() = 0;
virtual void setExpensiveRenderingExpected(DisplayId displayId, bool expected) = 0;
virtual bool isUsingExpensiveRendering() = 0;
- virtual void notifyDisplayUpdateImminent() = 0;
- // Checks both if it supports and if it's enabled
+ // Checks both if it's supported and if it's enabled; this is thread-safe since its values are
+ // set before onBootFinished, which gates all methods that run on threads other than SF main
virtual bool usePowerHintSession() = 0;
virtual bool supportsPowerHintSession() = 0;
- virtual bool isPowerHintSessionRunning() = 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<int32_t>& threadIds) = 0;
+ virtual bool startPowerHintSession(std::vector<int32_t>&& 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>&& 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<DisplayId>& 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;
+
+ // --- The following methods may run on threads besides SF main ---
+ // Send a hint about an upcoming increase in the CPU workload
+ virtual void notifyCpuLoadUp() = 0;
+ // Send a hint about the imminent start of a new CPU workload
+ virtual void notifyDisplayUpdateImminentAndCpuReset() = 0;
};
namespace impl {
@@ -100,24 +107,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<int32_t>& 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<int32_t> getPowerHintSessionThreadIds() = 0;
- virtual std::optional<nsecs_t> getTargetWorkDuration() = 0;
- };
-
PowerAdvisor(SurfaceFlinger& flinger);
~PowerAdvisor() override;
@@ -125,46 +114,36 @@ public:
void onBootFinished() override;
void setExpensiveRenderingExpected(DisplayId displayId, bool expected) override;
bool isUsingExpensiveRendering() override { return mNotifiedExpensiveRendering; };
- void notifyDisplayUpdateImminent() 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 startPowerHintSession(const std::vector<int32_t>& threadIds) override;
- void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime);
- void setHwcValidateTiming(DisplayId displayId, nsecs_t valiateStartTime,
- nsecs_t validateEndTime) override;
- void setHwcPresentTiming(DisplayId displayId, nsecs_t presentStartTime,
- nsecs_t presentEndTime) override;
+ void updateTargetWorkDuration(Duration targetDuration) override;
+ void reportActualWorkDuration() override;
+ void enablePowerHintSession(bool enabled) override;
+ bool startPowerHintSession(std::vector<int32_t>&& threadIds) override;
+ void setGpuFenceTime(DisplayId displayId, std::unique_ptr<FenceTime>&& fenceTime) 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<DisplayId>& displayIds) override;
- void setTotalFrameTargetWorkDuration(nsecs_t targetDuration) override;
+ void setTotalFrameTargetWorkDuration(Duration targetDuration) override;
+
+ // --- The following methods may run on threads besides SF main ---
+ void notifyCpuLoadUp() override;
+ void notifyDisplayUpdateImminentAndCpuReset() override;
private:
friend class PowerAdvisorTest;
- // Tracks if powerhal exists
- bool mHasHal = true;
- // Holds the hal wrapper for getPowerHal
- std::unique_ptr<HalWrapper> mHalWrapper GUARDED_BY(mPowerHalMutex) = nullptr;
-
- HalWrapper* getPowerHal() REQUIRES(mPowerHalMutex);
- bool mReconnectPowerHal GUARDED_BY(mPowerHalMutex) = false;
- std::mutex mPowerHalMutex;
-
+ std::unique_ptr<power::PowerHalController> mPowerHal;
std::atomic_bool mBootFinished = false;
std::unordered_set<DisplayId> mExpensiveDisplays;
@@ -178,44 +157,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<FenceTime> gpuEndFenceTime;
- std::optional<nsecs_t> gpuStartTime;
- std::optional<nsecs_t> lastValidGpuEndTime;
- std::optional<nsecs_t> lastValidGpuStartTime;
- std::optional<nsecs_t> hwcPresentStartTime;
- std::optional<nsecs_t> hwcPresentEndTime;
- std::optional<nsecs_t> hwcValidateStartTime;
- std::optional<nsecs_t> hwcValidateEndTime;
- std::optional<nsecs_t> hwcPresentDelayedTime;
+ std::optional<TimePoint> gpuStartTime;
+ std::optional<TimePoint> lastValidGpuEndTime;
+ std::optional<TimePoint> lastValidGpuStartTime;
+ std::optional<TimePoint> hwcPresentStartTime;
+ std::optional<TimePoint> hwcPresentEndTime;
+ std::optional<TimePoint> hwcValidateStartTime;
+ std::optional<TimePoint> hwcValidateEndTime;
+ std::optional<TimePoint> 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<GpuTimeline> estimateGpuTiming(std::optional<nsecs_t> previousEnd);
+ std::optional<GpuTimeline> estimateGpuTiming(std::optional<TimePoint> previousEndTime);
};
template <class T, size_t N>
@@ -240,107 +217,79 @@ private:
};
// Filter and sort the display ids by a given property
- std::vector<DisplayId> getOrderedDisplayIds(std::optional<nsecs_t> DisplayTimingData::*sortBy);
+ std::vector<DisplayId> getOrderedDisplayIds(
+ std::optional<TimePoint> 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<nsecs_t> estimateWorkDuration(bool earlyHint);
+ std::optional<Duration> 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);
+ bool ensurePowerHintSessionRunning() REQUIRES(mHintSessionMutex);
std::unordered_map<DisplayId, DisplayTimingData> 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<nsecs_t, 2> mCommitStartTimes;
+ RingBuffer<TimePoint, 2> mCommitStartTimes;
// Buffer of recent expected present times
- RingBuffer<nsecs_t, 2> 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<nsecs_t> mTotalFrameTargetDuration;
+ RingBuffer<TimePoint, 2> 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<Duration> mTotalFrameTargetDuration;
// Updated list of display IDs
std::vector<DisplayId> mDisplayIds;
- std::optional<bool> mPowerHintEnabled;
- std::optional<bool> 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;
-
- // 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;
-};
-
-class AidlPowerHalWrapper : public PowerAdvisor::HalWrapper {
-public:
- explicit AidlPowerHalWrapper(sp<hardware::power::IPower> powerHal);
- ~AidlPowerHalWrapper() override;
+ // These variables are set before mBootFinished and never mutated after, so it's safe to access
+ // from threaded methods.
+ std::optional<bool> mHintSessionEnabled;
+ std::optional<bool> mSupportsHintSession;
- static std::unique_ptr<HalWrapper> connect();
+ std::mutex mHintSessionMutex;
+ std::shared_ptr<aidl::android::hardware::power::IPowerHintSession> mHintSession
+ GUARDED_BY(mHintSessionMutex) = nullptr;
- bool setExpensiveRendering(bool enabled) override;
- bool notifyDisplayUpdateImminent() override;
- bool supportsPowerHintSession() override;
- bool isPowerHintSessionRunning() override;
- void restartPowerHintSession() override;
- void setPowerHintSessionThreadIds(const std::vector<int32_t>& 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<int32_t> getPowerHintSessionThreadIds() override;
- std::optional<nsecs_t> getTargetWorkDuration() override;
+ // 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<aidl::android::hardware::power::WorkDuration> mHintSessionQueue;
+ // The latest values we have received for target and actual
+ Duration mTargetDuration = kDefaultTargetDuration;
+ std::optional<Duration> mActualDuration;
+ // The list of thread ids, stored so we can restart the session from this class if needed
+ std::vector<int32_t> mHintSessionThreadIds;
+ Duration mLastTargetDurationSent = kDefaultTargetDuration;
-private:
- friend class AidlPowerHalWrapperTest;
+ // Used to manage the execution ordering of reportActualWorkDuration for concurrency testing
+ std::promise<bool> mDelayReportActualMutexAcquisitonPromise;
+ bool mTimingTestingMode = false;
- bool checkPowerHintSessionSupported();
- void closePowerHintSession();
- bool shouldReportActualDurations();
+ // Whether we should emit ATRACE_INT data for hint sessions
+ static const bool sTraceHintSessionData;
- // Used for testing
- void setAllowedActualDeviation(nsecs_t);
+ // Default target duration for the hint session
+ static constexpr const Duration kDefaultTargetDuration{16ms};
- const sp<hardware::power::IPower> mPowerHal = nullptr;
- bool mHasExpensiveRendering = false;
- bool mHasDisplayUpdateImminent = false;
- // Used to indicate an error state and need for reconstruction
- bool mShouldReconnectHal = false;
+ // 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};
- // Power hint session data
+ // Whether we should send reportActualWorkDuration calls
+ static const bool sUseReportActualDuration;
- // Concurrent access for this is protected by mPowerHalMutex
- sp<hardware::power::IPowerHintSession> mPowerHintSession = nullptr;
- // Queue of actual durations saved to report
- std::vector<hardware::power::WorkDuration> mPowerHintQueue;
- // The latest values we have received for target and actual
- nsecs_t mTargetDuration = kDefaultTarget.count();
- std::optional<nsecs_t> mActualDuration;
- // The list of thread ids, stored so we can restart the session from this class if needed
- std::vector<int32_t> mPowerHintThreadIds;
- bool mSupportsPowerHint = false;
- // Keep track of the last messages sent for rate limiter change detection
- std::optional<nsecs_t> 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;
- // 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;
+ // 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 3803a78670..4b5a68cefa 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() {
@@ -171,7 +175,7 @@ status_t VirtualDisplaySurface::prepareFrame(CompositionType compositionType) {
return NO_ERROR;
}
-status_t VirtualDisplaySurface::advanceFrame() {
+status_t VirtualDisplaySurface::advanceFrame(float hdrSdrRatio) {
if (GpuVirtualDisplayId::tryCast(mDisplayId)) {
return NO_ERROR;
}
@@ -197,9 +201,9 @@ status_t VirtualDisplaySurface::advanceFrame() {
return NO_MEMORY;
}
- sp<GraphicBuffer> fbBuffer = mFbProducerSlot >= 0 ?
- mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(nullptr);
- sp<GraphicBuffer> outBuffer = mProducerBuffers[mOutputProducerSlot];
+ sp<GraphicBuffer> const& fbBuffer =
+ mFbProducerSlot >= 0 ? mProducerBuffers[mFbProducerSlot] : sp<GraphicBuffer>(nullptr);
+ sp<GraphicBuffer> const& outBuffer = mProducerBuffers[mOutputProducerSlot];
VDS_LOGV("%s: fb=%d(%p) out=%d(%p)", __func__, mFbProducerSlot, fbBuffer.get(),
mOutputProducerSlot, outBuffer.get());
@@ -211,13 +215,15 @@ status_t VirtualDisplaySurface::advanceFrame() {
status_t result = NO_ERROR;
if (fbBuffer != nullptr) {
- uint32_t hwcSlot = 0;
- sp<GraphicBuffer> hwcBuffer;
- mHwcBufferCache.getHwcBuffer(mFbProducerSlot, fbBuffer, &hwcSlot, &hwcBuffer);
-
+ // assume that HWC has previously seen the buffer in this slot
+ sp<GraphicBuffer> hwcBuffer = sp<GraphicBuffer>(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,
- ui::Dataspace::UNKNOWN);
+ result = mHwc.setClientTarget(*halDisplayId, mFbProducerSlot, mFbFence, hwcBuffer,
+ ui::Dataspace::UNKNOWN, hdrSdrRatio);
}
return result;
diff --git a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
index e21095aa88..90426f729a 100644
--- a/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
+++ b/services/surfaceflinger/DisplayHardware/VirtualDisplaySurface.h
@@ -20,7 +20,7 @@
#include <string>
#include <compositionengine/DisplaySurface.h>
-#include <compositionengine/impl/HwcBufferCache.h>
+#include <gui/BufferQueue.h>
#include <gui/ConsumerBase.h>
#include <gui/IGraphicBufferProducer.h>
#include <ui/DisplayId.h>
@@ -84,7 +84,7 @@ public:
//
virtual status_t beginFrame(bool mustRecompose);
virtual status_t prepareFrame(CompositionType);
- virtual status_t advanceFrame();
+ virtual status_t advanceFrame(float hdrSdrRatio);
virtual void onFrameCommitted();
virtual void dumpAsString(String8& result) const;
virtual void resizeBuffers(const ui::Size&) override;
@@ -164,6 +164,10 @@ private:
sp<IGraphicBufferProducer> 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 20486e0aa3..55b395b458 100644
--- a/services/surfaceflinger/DisplayRenderArea.cpp
+++ b/services/surfaceflinger/DisplayRenderArea.cpp
@@ -18,38 +18,26 @@
#include "DisplayDevice.h"
namespace android {
-namespace {
-
-RenderArea::RotationFlags applyDeviceOrientation(bool useIdentityTransform,
- const DisplayDevice& display) {
- if (!useIdentityTransform) {
- return RenderArea::RotationFlags::ROT_0;
- }
-
- return ui::Transform::toRotationFlags(display.getOrientation());
-}
-
-} // namespace
std::unique_ptr<RenderArea> DisplayRenderArea::create(wp<const DisplayDevice> displayWeak,
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<DisplayRenderArea>(
new DisplayRenderArea(std::move(display), sourceCrop, reqSize, reqDataSpace,
- useIdentityTransform, allowSecureLayers));
+ hintForSeamlessTransition, allowSecureLayers));
}
return nullptr;
}
DisplayRenderArea::DisplayRenderArea(sp<const DisplayDevice> display, const Rect& sourceCrop,
ui::Size reqSize, ui::Dataspace reqDataSpace,
- bool useIdentityTransform, bool allowSecureLayers)
- : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, display->getLayerStackSpaceRect(),
- allowSecureLayers, applyDeviceOrientation(useIdentityTransform, *display)),
+ bool hintForSeamlessTransition, bool allowSecureLayers)
+ : RenderArea(reqSize, CaptureFill::OPAQUE, reqDataSpace, hintForSeamlessTransition,
+ allowSecureLayers),
mDisplay(std::move(display)),
mSourceCrop(sourceCrop) {}
@@ -57,18 +45,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,34 +53,12 @@ sp<const DisplayDevice> 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()) {
return mDisplay->getLayerStackSpaceRect();
}
-
- // Correct for the orientation when the screen capture request contained
- // useIdentityTransform. This will cause the rotation flag to be non 0 since
- // it needs to rotate based on the screen orientation to allow the screenshot
- // to be taken in the ROT_0 orientation
- const auto flags = getRotationFlags();
- int width = mDisplay->getLayerStackSpaceRect().getWidth();
- int height = mDisplay->getLayerStackSpaceRect().getHeight();
- ui::Transform rotation;
- rotation.set(flags, width, height);
- return rotation.transform(mSourceCrop);
+ return mSourceCrop;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/DisplayRenderArea.h b/services/surfaceflinger/DisplayRenderArea.h
index 3478fc16c4..4555a9ed66 100644
--- a/services/surfaceflinger/DisplayRenderArea.h
+++ b/services/surfaceflinger/DisplayRenderArea.h
@@ -29,25 +29,21 @@ class DisplayRenderArea : public RenderArea {
public:
static std::unique_ptr<RenderArea> create(wp<const DisplayDevice>, 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<const DisplayDevice> getDisplayDevice() const override;
- bool needsFiltering() const override;
Rect getSourceCrop() const override;
private:
DisplayRenderArea(sp<const DisplayDevice>, const Rect& sourceCrop, ui::Size reqSize,
- ui::Dataspace, bool useIdentityTransform, bool allowSecureLayers = true);
+ ui::Dataspace, bool hintForSeamlessTransition, bool allowSecureLayers = true);
const sp<const DisplayDevice> 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 e8c590e3c4..0000000000
--- 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 <stdint.h>
-#include <stdlib.h>
-#include <sys/types.h>
-
-#include <compositionengine/CompositionEngine.h>
-#include <compositionengine/LayerFECompositionState.h>
-#include <renderengine/RenderEngine.h>
-#include <ui/GraphicBuffer.h>
-#include <utils/Errors.h>
-#include <utils/Log.h>
-
-#include "DisplayDevice.h"
-#include "SurfaceFlinger.h"
-
-namespace android {
-// ---------------------------------------------------------------------------
-
-EffectLayer::EffectLayer(const LayerCreationArgs& args)
- : Layer(args),
- mCompositionState{mFlinger->getCompositionEngine().createLayerFECompositionState()} {}
-
-EffectLayer::~EffectLayer() = default;
-
-std::vector<compositionengine::LayerFE::LayerSettings> EffectLayer::prepareClientCompositionList(
- compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
- std::vector<compositionengine::LayerFE::LayerSettings> results;
- std::optional<compositionengine::LayerFE::LayerSettings> 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<compositionengine::LayerFE> 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<Layer> EffectLayer::createClone() {
- sp<EffectLayer> 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 1dcb633251..0000000000
--- 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 <sys/types.h>
-
-#include <cstdint>
-
-#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<compositionengine::LayerFE> 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<compositionengine::LayerFE::LayerSettings> prepareClientCompositionList(
- compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) override;
-
- std::unique_ptr<compositionengine::LayerFECompositionState> mCompositionState;
-
- sp<Layer> 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/FlagManager.cpp b/services/surfaceflinger/FlagManager.cpp
deleted file mode 100644
index f8ad8f6941..0000000000
--- a/services/surfaceflinger/FlagManager.cpp
+++ /dev/null
@@ -1,110 +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.
- */
-
-#include "FlagManager.h"
-
-#include <SurfaceFlingerProperties.sysprop.h>
-#include <android-base/parsebool.h>
-#include <android-base/parseint.h>
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <log/log.h>
-#include <renderengine/RenderEngine.h>
-#include <server_configurable_flags/get_flags.h>
-#include <cinttypes>
-
-namespace android {
-static constexpr const char* kExperimentNamespace = "surface_flinger_native_boot";
-static constexpr const int64_t kDemoFlag = -1;
-
-FlagManager::~FlagManager() = default;
-
-void FlagManager::dump(std::string& result) const {
- base::StringAppendF(&result, "FlagManager values: \n");
- base::StringAppendF(&result, "demo_flag: %" PRId64 "\n", demo_flag());
- base::StringAppendF(&result, "use_adpf_cpu_hint: %s\n", use_adpf_cpu_hint() ? "true" : "false");
- base::StringAppendF(&result, "use_skia_tracing: %s\n", use_skia_tracing() ? "true" : "false");
-}
-
-namespace {
-template <typename T>
-std::optional<T> doParse(const char* str);
-
-template <>
-[[maybe_unused]] std::optional<int32_t> doParse(const char* str) {
- int32_t ret;
- return base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
-}
-
-template <>
-[[maybe_unused]] std::optional<int64_t> doParse(const char* str) {
- int64_t ret;
- return base::ParseInt(str, &ret) ? std::make_optional(ret) : std::nullopt;
-}
-
-template <>
-[[maybe_unused]] std::optional<bool> doParse(const char* str) {
- base::ParseBoolResult parseResult = base::ParseBool(str);
- switch (parseResult) {
- case base::ParseBoolResult::kTrue:
- return std::make_optional(true);
- case base::ParseBoolResult::kFalse:
- return std::make_optional(false);
- case base::ParseBoolResult::kError:
- return std::nullopt;
- }
-}
-} // namespace
-
-std::string FlagManager::getServerConfigurableFlag(const std::string& experimentFlagName) const {
- return server_configurable_flags::GetServerConfigurableFlag(kExperimentNamespace,
- experimentFlagName, "");
-}
-
-template int32_t FlagManager::getValue<int32_t>(const std::string&, std::optional<int32_t>,
- int32_t) const;
-template int64_t FlagManager::getValue<int64_t>(const std::string&, std::optional<int64_t>,
- int64_t) const;
-template bool FlagManager::getValue<bool>(const std::string&, std::optional<bool>, bool) const;
-template <typename T>
-T FlagManager::getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
- T defaultValue) const {
- // System property takes precedence over the experiment config server value.
- if (systemPropertyOpt.has_value()) {
- return *systemPropertyOpt;
- }
- std::string str = getServerConfigurableFlag(experimentFlagName);
- return str.empty() ? defaultValue : doParse<T>(str.c_str()).value_or(defaultValue);
-}
-
-int64_t FlagManager::demo_flag() const {
- std::optional<int64_t> sysPropVal = std::nullopt;
- return getValue("DemoFeature__demo_flag", sysPropVal, kDemoFlag);
-}
-
-bool FlagManager::use_adpf_cpu_hint() const {
- std::optional<bool> sysPropVal =
- doParse<bool>(base::GetProperty("debug.sf.enable_adpf_cpu_hint", "").c_str());
- return getValue("AdpfFeature__adpf_cpu_hint", sysPropVal, false);
-}
-
-bool FlagManager::use_skia_tracing() const {
- std::optional<bool> sysPropVal =
- doParse<bool>(base::GetProperty(PROPERTY_SKIA_ATRACE_ENABLED, "").c_str());
- return getValue("SkiaTracingFeature__use_skia_tracing", sysPropVal, false);
-}
-
-} // namespace android
diff --git a/services/surfaceflinger/FlagManager.h b/services/surfaceflinger/FlagManager.h
deleted file mode 100644
index e8341424e7..0000000000
--- a/services/surfaceflinger/FlagManager.h
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <cstdint>
-#include <optional>
-#include <string>
-
-namespace android {
-// Manages flags for SurfaceFlinger, including default values, system properties, and Mendel
-// experiment configuration values.
-class FlagManager {
-public:
- FlagManager() = default;
- virtual ~FlagManager();
- void dump(std::string& result) const;
-
- int64_t demo_flag() const;
-
- bool use_adpf_cpu_hint() const;
-
- bool use_skia_tracing() const;
-
-private:
- friend class FlagManagerTest;
-
- // Wrapper for mocking in test.
- virtual std::string getServerConfigurableFlag(const std::string& experimentFlagName) const;
-
- template <typename T>
- T getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
- T defaultValue) const;
-};
-} // namespace android
diff --git a/services/surfaceflinger/FpsReporter.cpp b/services/surfaceflinger/FpsReporter.cpp
index e12835f0f6..155cf4da58 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>(layer)});
+ listenersAndLayersToReport.push_back(
+ {listener, sp<Layer>::fromExisting(layer)});
break;
}
}
@@ -90,7 +91,7 @@ void FpsReporter::binderDied(const wp<IBinder>& who) {
void FpsReporter::addListener(const sp<gui::IFpsListener>& listener, int32_t taskId) {
sp<IBinder> asBinder = IInterface::asBinder(listener);
- asBinder->linkToDeath(this);
+ asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
std::lock_guard lock(mMutex);
mListeners.emplace(wp<IBinder>(asBinder), TrackedListener{listener, taskId});
}
diff --git a/services/surfaceflinger/FrameTimeline/Android.bp b/services/surfaceflinger/FrameTimeline/Android.bp
index 2d4ec04cfc..29c9432af6 100644
--- a/services/surfaceflinger/FrameTimeline/Android.bp
+++ b/services/surfaceflinger/FrameTimeline/Android.bp
@@ -28,6 +28,7 @@ cc_library_static {
],
static_libs: [
"libperfetto_client_experimental",
+ "libsurfaceflinger_common",
],
export_include_dirs: ["."],
}
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
index 66beff2953..803299cf6f 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.cpp
@@ -21,6 +21,7 @@
#include "FrameTimeline.h"
#include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
#include <utils/Log.h>
#include <utils/Trace.h>
@@ -109,11 +110,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) {
@@ -140,6 +141,10 @@ std::string jankTypeBitmaskToString(int32_t jankType) {
janks.emplace_back("SurfaceFlinger Stuffing");
jankType &= ~JankType::SurfaceFlingerStuffing;
}
+ if (jankType & JankType::Dropped) {
+ janks.emplace_back("Dropped Frame");
+ jankType &= ~JankType::Dropped;
+ }
// jankType should be 0 if all types of jank were checked for.
LOG_ALWAYS_FATAL_IF(jankType != 0, "Unrecognized jank type value 0x%x", jankType);
@@ -264,12 +269,30 @@ int32_t jankTypeBitmaskToProto(int32_t jankType) {
protoJank |= FrameTimelineEvent::JANK_SF_STUFFING;
jankType &= ~JankType::SurfaceFlingerStuffing;
}
+ if (jankType & JankType::Dropped) {
+ // Jank dropped does not append to other janks, it fully overrides.
+ protoJank |= FrameTimelineEvent::JANK_DROPPED;
+ jankType &= ~JankType::Dropped;
+ }
// jankType should be 0 if all types of jank were checked for.
LOG_ALWAYS_FATAL_IF(jankType != 0, "Unrecognized jank type value 0x%x", jankType);
return protoJank;
}
+FrameTimelineEvent::JankSeverityType toProto(JankSeverityType jankSeverityType) {
+ switch (jankSeverityType) {
+ case JankSeverityType::Unknown:
+ return FrameTimelineEvent::SEVERITY_UNKNOWN;
+ case JankSeverityType::None:
+ return FrameTimelineEvent::SEVERITY_NONE;
+ case JankSeverityType::Partial:
+ return FrameTimelineEvent::SEVERITY_PARTIAL;
+ case JankSeverityType::Full:
+ return FrameTimelineEvent::SEVERITY_FULL;
+ }
+}
+
// Returns the smallest timestamp from the set of predictions and actuals.
nsecs_t getMinTime(PredictionState predictionState, TimelineItem predictions,
TimelineItem actuals) {
@@ -289,7 +312,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;
}
@@ -357,6 +380,11 @@ void SurfaceFrame::setRenderRate(Fps renderRate) {
mRenderRate = renderRate;
}
+Fps SurfaceFrame::getRenderRate() const {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mRenderRate ? *mRenderRate : mDisplayFrameRenderRate;
+}
+
void SurfaceFrame::setGpuComposition() {
std::scoped_lock lock(mMutex);
mGpuComposition = true;
@@ -365,8 +393,7 @@ void SurfaceFrame::setGpuComposition() {
std::optional<int32_t> SurfaceFrame::getJankType() const {
std::scoped_lock lock(mMutex);
if (mPresentState == PresentState::Dropped) {
- // Return no jank if it's a dropped frame since we cannot attribute a jank to a it.
- return JankType::None;
+ return JankType::Dropped;
}
if (mActuals.presentTime == 0) {
// Frame hasn't been presented yet.
@@ -375,6 +402,15 @@ std::optional<int32_t> SurfaceFrame::getJankType() const {
return mJankType;
}
+std::optional<JankSeverityType> SurfaceFrame::getJankSeverityType() const {
+ std::scoped_lock lock(mMutex);
+ if (mActuals.presentTime == 0) {
+ // Frame hasn't been presented yet.
+ return std::nullopt;
+ }
+ return mJankSeverityType;
+}
+
nsecs_t SurfaceFrame::getBaseTime() const {
std::scoped_lock lock(mMutex);
return getMinTime(mPredictionState, mPredictions, mActuals);
@@ -491,10 +527,11 @@ std::string SurfaceFrame::miniDump() const {
}
void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
- nsecs_t& deadlineDelta) {
+ Fps displayFrameRenderRate, nsecs_t& deadlineDelta) {
if (mActuals.presentTime == Fence::SIGNAL_TIME_INVALID) {
// Cannot do any classification for invalid present time.
mJankType = JankType::Unknown;
+ mJankSeverityType = JankSeverityType::Unknown;
deadlineDelta = -1;
return;
}
@@ -503,7 +540,9 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r
// We classify prediction expired as AppDeadlineMissed as the
// TokenManager::kMaxTokens we store is large enough to account for a
// reasonable app, so prediction expire would mean a huge scheduling delay.
- mJankType = JankType::AppDeadlineMissed;
+ mJankType = mPresentState != PresentState::Presented ? JankType::Dropped
+ : JankType::AppDeadlineMissed;
+ mJankSeverityType = JankSeverityType::Unknown;
deadlineDelta = -1;
return;
}
@@ -528,6 +567,11 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r
if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
: FramePresentMetadata::EarlyPresent;
+ // Jank that is missing by less than the render rate period is classified as partial jank,
+ // otherwise it is a full jank.
+ mJankSeverityType = std::abs(presentDelta) < displayFrameRenderRate.getPeriodNsecs()
+ ? JankSeverityType::Partial
+ : JankSeverityType::Full;
} else {
mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
}
@@ -594,21 +638,24 @@ void SurfaceFrame::classifyJankLocked(int32_t displayFrameJankType, const Fps& r
mJankType |= displayFrameJankType;
}
}
+ if (mPresentState != PresentState::Presented) {
+ mJankType = JankType::Dropped;
+ // Since frame was not presented, lets drop any present value
+ mActuals.presentTime = 0;
+ mJankSeverityType = JankSeverityType::Unknown;
+ }
}
void SurfaceFrame::onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
- nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta) {
+ Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
+ nsecs_t displayPresentDelta) {
std::scoped_lock lock(mMutex);
- if (mPresentState != PresentState::Presented) {
- // No need to update dropped buffers
- return;
- }
-
+ mDisplayFrameRenderRate = displayFrameRenderRate;
mActuals.presentTime = presentTime;
nsecs_t deadlineDelta = 0;
- classifyJankLocked(displayFrameJankType, refreshRate, deadlineDelta);
+ classifyJankLocked(displayFrameJankType, refreshRate, displayFrameRenderRate, deadlineDelta);
if (mPredictionState != PredictionState::None) {
// Only update janky frames if the app used vsync predictions
@@ -701,6 +748,7 @@ void SurfaceFrame::traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffse
actualSurfaceFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
actualSurfaceFrameStartEvent->set_prediction_type(toProto(mPredictionState));
actualSurfaceFrameStartEvent->set_is_buffer(mIsBuffer);
+ actualSurfaceFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
});
// Actual timeline end
@@ -828,10 +876,11 @@ void FrameTimeline::addSurfaceFrame(std::shared_ptr<SurfaceFrame> surfaceFrame)
mCurrentDisplayFrame->addSurfaceFrame(surfaceFrame);
}
-void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate) {
+void FrameTimeline::setSfWakeUp(int64_t token, nsecs_t wakeUpTime, Fps refreshRate,
+ Fps renderRate) {
ATRACE_CALL();
std::scoped_lock lock(mMutex);
- mCurrentDisplayFrame->onSfWakeUp(token, refreshRate,
+ mCurrentDisplayFrame->onSfWakeUp(token, refreshRate, renderRate,
mTokenManager.getPredictionsForToken(token), wakeUpTime);
}
@@ -851,11 +900,12 @@ void FrameTimeline::DisplayFrame::addSurfaceFrame(std::shared_ptr<SurfaceFrame>
mSurfaceFrames.push_back(surfaceFrame);
}
-void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, Fps refreshRate,
+void FrameTimeline::DisplayFrame::onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
std::optional<TimelineItem> predictions,
nsecs_t wakeUpTime) {
mToken = token;
mRefreshRate = refreshRate;
+ mRenderRate = renderRate;
if (!predictions) {
mPredictionState = PredictionState::Expired;
} else {
@@ -885,13 +935,20 @@ void FrameTimeline::DisplayFrame::setGpuFence(const std::shared_ptr<FenceTime>&
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;
+ mJankSeverityType = JankSeverityType::Unknown;
deadlineDelta = 0;
deltaToVsync = 0;
+ if (!presentTimeValid) {
+ mSurfaceFlingerActuals.presentTime = mSurfaceFlingerActuals.endTime;
+ mJankType |= JankType::DisplayHAL;
+ }
+
return;
}
@@ -916,6 +973,11 @@ void FrameTimeline::DisplayFrame::classifyJank(nsecs_t& deadlineDelta, nsecs_t&
if (std::abs(presentDelta) > mJankClassificationThresholds.presentThreshold) {
mFramePresentMetadata = presentDelta > 0 ? FramePresentMetadata::LatePresent
: FramePresentMetadata::EarlyPresent;
+ // Jank that is missing by less than the render rate period is classified as partial jank,
+ // otherwise it is a full jank.
+ mJankSeverityType = std::abs(presentDelta) < mRenderRate.getPeriodNsecs()
+ ? JankSeverityType::Partial
+ : JankSeverityType::Full;
} else {
mFramePresentMetadata = FramePresentMetadata::OnTimePresent;
}
@@ -986,11 +1048,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;
@@ -1014,7 +1073,8 @@ void FrameTimeline::DisplayFrame::onPresent(nsecs_t signalTime, nsecs_t previous
classifyJank(deadlineDelta, deltaToVsync, previousPresentTime);
for (auto& surfaceFrame : mSurfaceFrames) {
- surfaceFrame->onPresent(signalTime, mJankType, mRefreshRate, deadlineDelta, deltaToVsync);
+ surfaceFrame->onPresent(signalTime, mJankType, mRefreshRate, mRenderRate, deadlineDelta,
+ deltaToVsync);
}
}
@@ -1052,6 +1112,67 @@ void FrameTimeline::DisplayFrame::tracePredictions(pid_t surfaceFlingerPid,
});
}
+void FrameTimeline::DisplayFrame::addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+ nsecs_t previousActualPresentTime) const {
+ nsecs_t skippedFrameStartTime = 0, skippedFramePresentTime = 0;
+ const constexpr float kThresh = 0.5f;
+ const constexpr float kRange = 1.5f;
+ for (auto& surfaceFrame : mSurfaceFrames) {
+ if (previousActualPresentTime != 0 &&
+ static_cast<float>(mSurfaceFlingerActuals.presentTime - previousActualPresentTime) >=
+ static_cast<float>(mRenderRate.getPeriodNsecs()) * kRange &&
+ static_cast<float>(surfaceFrame->getPredictions().presentTime) <=
+ (static_cast<float>(mSurfaceFlingerActuals.presentTime) -
+ kThresh * static_cast<float>(mRenderRate.getPeriodNsecs())) &&
+ static_cast<float>(surfaceFrame->getPredictions().presentTime) >=
+ (static_cast<float>(previousActualPresentTime) -
+ kThresh * static_cast<float>(mRenderRate.getPeriodNsecs()))) {
+ skippedFrameStartTime = surfaceFrame->getPredictions().endTime;
+ skippedFramePresentTime = surfaceFrame->getPredictions().presentTime;
+ break;
+ }
+ }
+
+ // add slice
+ if (skippedFrameStartTime != 0 && skippedFramePresentTime != 0) {
+ int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
+
+ // Actual timeline start
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+ packet->set_timestamp(static_cast<uint64_t>(skippedFrameStartTime + monoBootOffset));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* actualDisplayFrameStartEvent = event->set_actual_display_frame_start();
+
+ actualDisplayFrameStartEvent->set_cookie(actualTimelineCookie);
+
+ actualDisplayFrameStartEvent->set_token(0);
+ actualDisplayFrameStartEvent->set_pid(surfaceFlingerPid);
+ actualDisplayFrameStartEvent->set_on_time_finish(mFrameReadyMetadata ==
+ FrameReadyMetadata::OnTimeFinish);
+ actualDisplayFrameStartEvent->set_gpu_composition(false);
+ actualDisplayFrameStartEvent->set_prediction_type(toProto(PredictionState::Valid));
+ actualDisplayFrameStartEvent->set_present_type(FrameTimelineEvent::PRESENT_DROPPED);
+ actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(JankType::Dropped));
+ actualDisplayFrameStartEvent->set_jank_severity_type(toProto(JankSeverityType::None));
+ });
+
+ // Actual timeline end
+ FrameTimelineDataSource::Trace([&](FrameTimelineDataSource::TraceContext ctx) {
+ auto packet = ctx.NewTracePacket();
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_BOOTTIME);
+ packet->set_timestamp(static_cast<uint64_t>(skippedFramePresentTime + monoBootOffset));
+
+ auto* event = packet->set_frame_timeline_event();
+ auto* actualDisplayFrameEndEvent = event->set_frame_end();
+
+ actualDisplayFrameEndEvent->set_cookie(actualTimelineCookie);
+ });
+ }
+}
+
void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
nsecs_t monoBootOffset) const {
int64_t actualTimelineCookie = mTraceCookieCounter.getCookieForTracing();
@@ -1077,6 +1198,7 @@ void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
actualDisplayFrameStartEvent->set_gpu_composition(mGpuFence != FenceTime::NO_FENCE);
actualDisplayFrameStartEvent->set_jank_type(jankTypeBitmaskToProto(mJankType));
actualDisplayFrameStartEvent->set_prediction_type(toProto(mPredictionState));
+ actualDisplayFrameStartEvent->set_jank_severity_type(toProto(mJankSeverityType));
});
// Actual timeline end
@@ -1093,7 +1215,14 @@ void FrameTimeline::DisplayFrame::traceActuals(pid_t surfaceFlingerPid,
});
}
-void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const {
+void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+ nsecs_t previousActualPresentTime) 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");
@@ -1110,6 +1239,10 @@ void FrameTimeline::DisplayFrame::trace(pid_t surfaceFlingerPid, nsecs_t monoBoo
for (auto& surfaceFrame : mSurfaceFrames) {
surfaceFrame->trace(mToken, monoBootOffset);
}
+
+ if (FlagManager::getInstance().add_sf_skipped_frames_to_trace()) {
+ addSkippedFrame(surfaceFlingerPid, monoBootOffset, previousActualPresentTime);
+ }
}
float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
@@ -1171,25 +1304,53 @@ float FrameTimeline::computeFps(const std::unordered_set<int32_t>& layerIds) {
static_cast<float>(totalPresentToPresentWalls);
}
+std::optional<size_t> 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, mPreviousPresentTime);
+ 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);
+ displayFrame->trace(mSurfaceFlingerPid, monoBootOffset, mPreviousPresentTime);
mPreviousPresentTime = signalTime;
mPendingPresentFences.erase(mPendingPresentFences.begin() + static_cast<int>(i));
diff --git a/services/surfaceflinger/FrameTimeline/FrameTimeline.h b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
index 3611deabce..b5047a3467 100644
--- a/services/surfaceflinger/FrameTimeline/FrameTimeline.h
+++ b/services/surfaceflinger/FrameTimeline/FrameTimeline.h
@@ -168,6 +168,7 @@ public:
// Returns std::nullopt if the frame hasn't been classified yet.
// Used by both SF and FrameTimeline.
std::optional<int32_t> getJankType() const;
+ std::optional<JankSeverityType> getJankSeverityType() const;
// Functions called by SF
int64_t getToken() const { return mToken; };
@@ -183,6 +184,8 @@ public:
void setDropTime(nsecs_t dropTime);
void setPresentState(PresentState presentState, nsecs_t lastLatchTime = 0);
void setRenderRate(Fps renderRate);
+ // Return the render rate if it exists, otherwise returns the DisplayFrame's render rate.
+ Fps getRenderRate() const;
void setGpuComposition();
// When a bufferless SurfaceFrame is promoted to a buffer SurfaceFrame, we also have to update
@@ -197,7 +200,8 @@ public:
// displayRefreshRate, displayDeadlineDelta, and displayPresentDelta are propagated from the
// display frame.
void onPresent(nsecs_t presentTime, int32_t displayFrameJankType, Fps refreshRate,
- nsecs_t displayDeadlineDelta, nsecs_t displayPresentDelta);
+ Fps displayFrameRenderRate, nsecs_t displayDeadlineDelta,
+ nsecs_t displayPresentDelta);
// All the timestamps are dumped relative to the baseTime
void dump(std::string& result, const std::string& indent, nsecs_t baseTime) const;
// Dumps only the layer, token, is buffer, jank metadata, prediction and present states.
@@ -229,7 +233,7 @@ private:
void tracePredictions(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
void traceActuals(int64_t displayFrameToken, nsecs_t monoBootOffset) const;
void classifyJankLocked(int32_t displayFrameJankType, const Fps& refreshRate,
- nsecs_t& deadlineDelta) REQUIRES(mMutex);
+ Fps displayFrameRenderRate, nsecs_t& deadlineDelta) REQUIRES(mMutex);
const int64_t mToken;
const int32_t mInputEventId;
@@ -249,8 +253,12 @@ private:
mutable std::mutex mMutex;
// Bitmask for the type of jank
int32_t mJankType GUARDED_BY(mMutex) = JankType::None;
+ // Enum for the severity of jank
+ JankSeverityType mJankSeverityType GUARDED_BY(mMutex) = JankSeverityType::None;
// Indicates if this frame was composited by the GPU or not
bool mGpuComposition GUARDED_BY(mMutex) = false;
+ // Refresh rate for this frame.
+ Fps mDisplayFrameRenderRate GUARDED_BY(mMutex);
// Rendering rate for this frame.
std::optional<Fps> mRenderRate GUARDED_BY(mMutex);
// Enum for the type of present
@@ -298,7 +306,8 @@ public:
// The first function called by SF for the current DisplayFrame. Fetches SF predictions based on
// the token and sets the actualSfWakeTime for the current DisplayFrame.
- virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) = 0;
+ virtual void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate,
+ Fps renderRate) = 0;
// Sets the sfPresentTime and finalizes the current DisplayFrame. Tracks the
// given present fence until it's signaled, and updates the present timestamps of all presented
@@ -372,10 +381,11 @@ public:
// Emits a packet for perfetto tracing. The function body will be executed only if tracing
// is enabled. monoBootOffset is the difference between SYSTEM_TIME_BOOTTIME
// and SYSTEM_TIME_MONOTONIC.
- void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+ void trace(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+ nsecs_t previousActualPresentTime) const;
// Sets the token, vsyncPeriod, predictions and SF start time.
- void onSfWakeUp(int64_t token, Fps refreshRate, std::optional<TimelineItem> predictions,
- nsecs_t wakeUpTime);
+ void onSfWakeUp(int64_t token, Fps refreshRate, Fps renderRate,
+ std::optional<TimelineItem> predictions, nsecs_t wakeUpTime);
// Sets the appropriate metadata and classifies the jank.
void onPresent(nsecs_t signalTime, nsecs_t previousPresentTime);
// Adds the provided SurfaceFrame to the current display frame.
@@ -397,6 +407,7 @@ public:
FramePresentMetadata getFramePresentMetadata() const { return mFramePresentMetadata; };
FrameReadyMetadata getFrameReadyMetadata() const { return mFrameReadyMetadata; };
int32_t getJankType() const { return mJankType; }
+ JankSeverityType getJankSeverityType() const { return mJankSeverityType; }
const std::vector<std::shared_ptr<SurfaceFrame>>& getSurfaceFrames() const {
return mSurfaceFrames;
}
@@ -405,6 +416,8 @@ public:
void dump(std::string& result, nsecs_t baseTime) const;
void tracePredictions(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
void traceActuals(pid_t surfaceFlingerPid, nsecs_t monoBootOffset) const;
+ void addSkippedFrame(pid_t surfaceFlingerPid, nsecs_t monoBootOffset,
+ nsecs_t previousActualPresentTime) const;
void classifyJank(nsecs_t& deadlineDelta, nsecs_t& deltaToVsync,
nsecs_t previousPresentTime);
@@ -426,6 +439,8 @@ public:
PredictionState mPredictionState = PredictionState::None;
// Bitmask for the type of jank
int32_t mJankType = JankType::None;
+ // Enum for the severity of jank
+ JankSeverityType mJankSeverityType = JankSeverityType::None;
// A valid gpu fence indicates that the DisplayFrame was composited by the GPU
std::shared_ptr<FenceTime> mGpuFence = FenceTime::NO_FENCE;
// Enum for the type of present
@@ -437,6 +452,8 @@ public:
// The refresh rate (vsync period) in nanoseconds as seen by SF during this DisplayFrame's
// timeline
Fps mRefreshRate;
+ // The current render rate for this DisplayFrame.
+ Fps mRenderRate;
// TraceCookieCounter is used to obtain the cookie for sendig trace packets to perfetto.
// Using a reference here because the counter is owned by FrameTimeline, which outlives
// DisplayFrame.
@@ -453,7 +470,7 @@ public:
int32_t layerId, std::string layerName, std::string debugName, bool isBuffer,
GameMode) override;
void addSurfaceFrame(std::shared_ptr<frametimeline::SurfaceFrame> surfaceFrame) override;
- void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate) override;
+ void setSfWakeUp(int64_t token, nsecs_t wakeupTime, Fps refreshRate, Fps renderRate) override;
void setSfPresent(nsecs_t sfPresentTime, const std::shared_ptr<FenceTime>& presentFence,
const std::shared_ptr<FenceTime>& gpuFence = FenceTime::NO_FENCE) override;
void parseArgs(const Vector<String16>& args, std::string& result) override;
@@ -474,6 +491,7 @@ private:
friend class android::frametimeline::FrameTimelineTest;
void flushPendingPresentFences() REQUIRES(mMutex);
+ std::optional<size_t> getFirstSignalFenceIndex() const REQUIRES(mMutex);
void finalizeCurrentDisplayFrame() REQUIRES(mMutex);
void dumpAll(std::string& result);
void dumpJank(std::string& result);
diff --git a/services/surfaceflinger/FrameTracker.cpp b/services/surfaceflinger/FrameTracker.cpp
index 178c531a2a..ca8cdc3c0a 100644
--- a/services/surfaceflinger/FrameTracker.cpp
+++ b/services/surfaceflinger/FrameTracker.cpp
@@ -18,9 +18,6 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
-// This is needed for stdint.h to define INT64_MAX in C++
-#define __STDC_LIMIT_MACROS
-
#include <inttypes.h>
#include <android-base/stringprintf.h>
diff --git a/services/surfaceflinger/FrontEnd/DisplayInfo.h b/services/surfaceflinger/FrontEnd/DisplayInfo.h
new file mode 100644
index 0000000000..6502f36f70
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/DisplayInfo.h
@@ -0,0 +1,52 @@
+/*
+ * 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 <sstream>
+
+#include <gui/DisplayInfo.h>
+#include <ui/DisplayMap.h>
+#include <ui/LayerStack.h>
+#include <ui/Transform.h>
+
+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();
+ }
+};
+
+using DisplayInfos = ui::DisplayMap<ui::LayerStack, DisplayInfo>;
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp b/services/surfaceflinger/FrontEnd/LayerCreationArgs.cpp
new file mode 100644
index 0000000000..97af445513
--- /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 <binder/IPCThreadState.h>
+#include <private/android_filesystem_config.h>
+#include "Client.h"
+#include "gui/LayerMetadata.h"
+
+namespace android::surfaceflinger {
+
+std::atomic<uint32_t> LayerCreationArgs::sSequence{1};
+std::atomic<uint32_t> LayerCreationArgs::sInternalSequence{1};
+
+uint32_t LayerCreationArgs::getInternalLayerId(uint32_t id) {
+ return id | INTERNAL_LAYER_PREFIX;
+}
+
+LayerCreationArgs::LayerCreationArgs(SurfaceFlinger* flinger, sp<Client> client, std::string name,
+ uint32_t flags, gui::LayerMetadata metadataArg,
+ std::optional<uint32_t> 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<int32_t>(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<uid_t>(
+ metadata.getInt32(gui::METADATA_OWNER_UID, static_cast<int32_t>(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<uint32_t> 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 0000000000..0788d1abce
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerCreationArgs.h
@@ -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.
+ */
+
+#pragma once
+
+#include <binder/Binder.h>
+#include <gui/LayerMetadata.h>
+#include <ui/LayerStack.h>
+#include <utils/StrongPointer.h>
+#include <cstdint>
+#include <limits>
+#include <optional>
+
+constexpr uint32_t UNASSIGNED_LAYER_ID = std::numeric_limits<uint32_t>::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<uint32_t> sSequence;
+ static std::atomic<uint32_t> sInternalSequence;
+ static uint32_t getInternalLayerId(uint32_t id);
+ static LayerCreationArgs fromOtherArgs(const LayerCreationArgs& other);
+
+ LayerCreationArgs(android::SurfaceFlinger*, sp<android::Client>, std::string name,
+ uint32_t flags, gui::LayerMetadata, std::optional<uint32_t> id = std::nullopt,
+ bool internalLayer = false);
+ LayerCreationArgs(std::optional<uint32_t> id, bool internalLayer = false);
+ LayerCreationArgs() = default; // for tracing
+ std::string getDebugString() const;
+
+ android::SurfaceFlinger* flinger;
+ sp<android::Client> client;
+ std::string name;
+ uint32_t flags; // ISurfaceComposerClient flags
+ gui::LayerMetadata metadata;
+ pid_t ownerPid;
+ uid_t ownerUid;
+ uint32_t sequence;
+ bool addToRoot = true;
+ wp<IBinder> parentHandle = nullptr;
+ wp<IBinder> 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 0000000000..75e4e3ae11
--- /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 <cstdint>
+#include "Layer.h"
+#include "LayerCreationArgs.h"
+#include "SurfaceFlinger.h"
+
+namespace android::surfaceflinger {
+
+LayerHandle::LayerHandle(const sp<android::SurfaceFlinger>& flinger,
+ const sp<android::Layer>& layer)
+ : mFlinger(flinger), mLayer(layer), mLayerId(static_cast<uint32_t>(layer->getSequence())) {}
+
+LayerHandle::~LayerHandle() {
+ if (mFlinger) {
+ mFlinger->onHandleDestroyed(this, mLayer, mLayerId);
+ }
+}
+
+const String16 LayerHandle::kDescriptor = String16("android.Layer.LayerHandle");
+
+sp<LayerHandle> LayerHandle::fromIBinder(const sp<IBinder>& 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<LayerHandle>::cast(binder);
+}
+
+sp<android::Layer> LayerHandle::getLayer(const sp<IBinder>& binder) {
+ sp<LayerHandle> handle = LayerHandle::fromIBinder(binder);
+ return handle ? handle->mLayer : nullptr;
+}
+
+uint32_t LayerHandle::getLayerId(const sp<IBinder>& binder) {
+ sp<LayerHandle> 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 0000000000..5d0f783515
--- /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 <binder/Binder.h>
+#include <utils/StrongPointer.h>
+
+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<android::SurfaceFlinger>& flinger, const sp<android::Layer>& 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<LayerHandle> fromIBinder(const sp<IBinder>& handle);
+ static sp<android::Layer> getLayer(const sp<IBinder>& handle);
+ static uint32_t getLayerId(const sp<IBinder>& handle);
+ static const String16 kDescriptor;
+
+ const String16& getInterfaceDescriptor() const override { return kDescriptor; }
+
+private:
+ sp<android::SurfaceFlinger> mFlinger;
+ sp<android::Layer> 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 0000000000..1e5a6fbd1e
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.cpp
@@ -0,0 +1,501 @@
+/*
+ * 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 "SurfaceFlinger"
+
+#include "LayerHierarchy.h"
+#include "LayerLog.h"
+#include "SwapErase.h"
+
+namespace android::surfaceflinger::frontend {
+
+namespace {
+auto layerZCompare = [](const std::pair<LayerHierarchy*, LayerHierarchy::Variant>& lhs,
+ const std::pair<LayerHierarchy*, LayerHierarchy::Variant>& 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<std::pair<LayerHierarchy*, LayerHierarchy::Variant>>& vec,
+ std::pair<LayerHierarchy*, LayerHierarchy::Variant> 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;
+ }
+ }
+
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(traversalPath.hasRelZLoop(), "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<LayerHierarchy*, Variant>& x) {
+ return x.first == child;
+ });
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mChildren.end(), "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<LayerHierarchy*, Variant>& child) {
+ return child.first == hierarchy;
+ });
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mChildren.end(), "Could not find child!");
+ 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 + "}";
+}
+
+void LayerHierarchy::dump(std::ostream& out, const std::string& prefix,
+ LayerHierarchy::Variant variant, bool isLastChild,
+ bool includeMirroredHierarchy) const {
+ if (!mLayer) {
+ out << " ROOT";
+ } else {
+ out << prefix + (isLastChild ? "└─ " : "├─ ");
+ if (variant == LayerHierarchy::Variant::Relative) {
+ out << "(Relative) ";
+ } else if (variant == LayerHierarchy::Variant::Mirror) {
+ if (!includeMirroredHierarchy) {
+ out << "(Mirroring) " << *mLayer << "\n" + prefix + " └─ ...";
+ return;
+ }
+ out << "(Mirroring) ";
+ }
+ out << *mLayer;
+ }
+
+ for (size_t i = 0; i < mChildren.size(); i++) {
+ auto& [child, childVariant] = mChildren[i];
+ if (childVariant == LayerHierarchy::Variant::Detached) continue;
+ const bool lastChild = i == (mChildren.size() - 1);
+ std::string childPrefix = prefix;
+ if (mLayer) {
+ childPrefix += (isLastChild ? " " : "│ ");
+ }
+ out << "\n";
+ child->dump(out, childPrefix, childVariant, lastChild, includeMirroredHierarchy);
+ }
+ return;
+}
+
+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<std::unique_ptr<RequestedLayerState>>& layers) {
+ mHierarchies.reserve(layers.size());
+ mLayerIdToHierarchy.reserve(layers.size());
+ for (auto& layer : layers) {
+ mHierarchies.emplace_back(std::make_unique<LayerHierarchy>(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>& 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<std::unique_ptr<RequestedLayerState>>& layers,
+ const std::vector<std::unique_ptr<RequestedLayerState>>& destroyedLayers) {
+ // rebuild map
+ for (auto& layer : layers) {
+ if (layer->changes.test(RequestedLayerState::Changes::Created)) {
+ mHierarchies.emplace_back(std::make_unique<LayerHierarchy>(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()) {
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(crashOnFailure, "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 (!mirrorRootIds.empty()) {
+ ss << ", .mirrorRootIds=";
+ for (auto rootId : mirrorRootIds) {
+ ss << rootId << ",";
+ }
+ }
+
+ if (!relativeRootIds.empty()) {
+ ss << ", .relativeRootIds=";
+ for (auto rootId : relativeRootIds) {
+ ss << rootId << ",";
+ }
+ }
+
+ if (hasRelZLoop()) {
+ ss << "hasRelZLoop=true invalidRelativeRootId=" << invalidRelativeRootId << ",";
+ }
+ ss << "}";
+ return ss.str();
+}
+
+// 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.mirrorRootIds.emplace_back(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.mirrorRootIds.pop_back();
+ } 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 0000000000..ba2e262baf
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerHierarchy.h
@@ -0,0 +1,226 @@
+/*
+ * 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, // 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
+ 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 (Mirrors C) {Traversal path id = 5}
+ // └─ F (Mirrors B) {Traversal path id = 6}
+ //
+ // C can be traversed via B or E or F and or via F then E.
+ // 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, mirrorRootIds = 5}
+ // - {Traversal path id = 3, mirrorRootIds = 6}
+ // - {Traversal path id = 3, mirrorRootIds = 6, 5}
+
+ 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.
+ ftl::SmallVector<uint32_t, 5> mirrorRootIds;
+ // 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<uint32_t, 5> 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 !mirrorRootIds.empty(); }
+
+ bool operator==(const TraversalPath& other) const {
+ return id == other.id && mirrorRootIds == other.mirrorRootIds;
+ }
+ 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;
+ for (uint32_t mirrorRootId : key.mirrorRootIds) {
+ hashCode += mirrorRootId * 31;
+ }
+ return std::hash<size_t>{}(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<bool(const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath& traversalPath)>
+ 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;
+ friend std::ostream& operator<<(std::ostream& os, const LayerHierarchy& obj) {
+ std::string prefix = " ";
+ obj.dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false,
+ /*includeMirroredHierarchy*/ false);
+ return os;
+ }
+ std::string dump() const {
+ std::string prefix = " ";
+ std::ostringstream os;
+ dump(os, prefix, LayerHierarchy::Variant::Attached, /*isLastChild=*/false,
+ /*includeMirroredHierarchy*/ true);
+ return os.str();
+ }
+
+ 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<std::pair<LayerHierarchy*, Variant>> 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;
+ void dump(std::ostream& out, const std::string& prefix, LayerHierarchy::Variant variant,
+ bool isLastChild, bool includeMirroredHierarchy) 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<std::unique_ptr<RequestedLayerState>>&);
+ void update(const std::vector<std::unique_ptr<RequestedLayerState>>& layers,
+ const std::vector<std::unique_ptr<RequestedLayerState>>& 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<uint32_t, LayerHierarchy*> mLayerIdToHierarchy;
+ std::vector<std::unique_ptr<LayerHierarchy>> 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 0000000000..0983e7c1ac
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.cpp
@@ -0,0 +1,447 @@
+/*
+ * 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 "SurfaceFlinger"
+
+#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;
+
+namespace {
+// Returns true if the layer is root of a display and can be mirrored by mirroringLayer
+bool canMirrorRootLayer(RequestedLayerState& mirroringLayer, RequestedLayerState& rootLayer) {
+ return rootLayer.isRoot() && rootLayer.layerStack == mirroringLayer.layerStackToMirror &&
+ rootLayer.id != mirroringLayer.id;
+}
+} // namespace
+
+void LayerLifecycleManager::addLayers(std::vector<std::unique_ptr<RequestedLayerState>> 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});
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!inserted,
+ "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());
+ mChangedLayers.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) {
+ // Set mirror layer's default layer stack to -1 so it doesn't end up rendered on a
+ // display accidentally.
+ layer.layerStack = 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 (canMirrorRootLayer(layer, *rootLayer)) {
+ 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<std::pair<uint32_t, std::string /* debugName */>>& destroyedHandles,
+ bool ignoreUnknownHandles) {
+ std::vector<uint32_t> layersToBeDestroyed;
+ for (const auto& [layerId, name] : destroyedHandles) {
+ auto it = mIdToLayer.find(layerId);
+ if (it == mIdToLayer.end()) {
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!ignoreUnknownHandles, "%s Layerid not found %s[%d]",
+ __func__, name.c_str(), 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);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mIdToLayer.end(), "%s Layer with id %d not found",
+ __func__, layerId);
+
+ 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);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!linkedLayer,
+ "%s Layerid reference %d not found for %d", __func__,
+ linkedLayerId, layer.id);
+ 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<TransactionState>& 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) {
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!ignoreUnknownLayers,
+ "%s Layer with layerid=%d not found", __func__,
+ layerId);
+ continue;
+ }
+
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(!layer->handleAlive,
+ "%s Layer's with layerid=%d) is not alive. Possible "
+ "out of "
+ "order LayerLifecycleManager updates",
+ __func__, layerId);
+
+ if (layer->changes.get() == 0) {
+ mChangedLayers.push_back(layer);
+ }
+
+ 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<std::unique_ptr<RequestedLayerState>> newLayers;
+ newLayers.emplace_back(
+ std::make_unique<RequestedLayerState>(backgroundLayerArgs));
+ RequestedLayerState* backgroundLayer = newLayers.back().get();
+ backgroundLayer->bgColorLayer = true;
+ backgroundLayer->handleAlive = false;
+ backgroundLayer->parentId = layer->id;
+ backgroundLayer->z = std::numeric_limits<int32_t>::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, bgColorLayer->debugName}});
+ } 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;
+ mChangedLayers.push_back(bgColorLayer);
+ 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();
+ mChangedLayers.clear();
+ mGlobalChanges.clear();
+}
+
+void LayerLifecycleManager::addLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+ mListeners.emplace_back(std::move(listener));
+}
+
+void LayerLifecycleManager::removeLifecycleListener(std::shared_ptr<ILifecycleListener> listener) {
+ swapErase(mListeners, listener);
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getLayers() const {
+ return mLayers;
+}
+
+const std::vector<std::unique_ptr<RequestedLayerState>>& LayerLifecycleManager::getDestroyedLayers()
+ const {
+ return mDestroyedLayers;
+}
+
+const std::vector<RequestedLayerState*>& LayerLifecycleManager::getChangedLayers() const {
+ return mChangedLayers;
+}
+
+const ftl::Flags<RequestedLayerState::Changes> LayerLifecycleManager::getGlobalChanges() const {
+ return mGlobalChanges;
+}
+
+const RequestedLayerState* LayerLifecycleManager::getLayerFromId(uint32_t id) const {
+ if (id == UNASSIGNED_LAYER_ID) {
+ return nullptr;
+ }
+ auto it = mIdToLayer.find(id);
+ if (it == mIdToLayer.end()) {
+ return nullptr;
+ }
+ return &it->second.owner;
+}
+
+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<uint32_t>* 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<uint32_t>* 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<uint32_t>* linkedLayers = getLinkedLayersFromId(layerId);
+ if (!linkedLayers) {
+ return UNASSIGNED_LAYER_ID;
+ }
+ swapErase(*linkedLayers, linkedLayer);
+ return UNASSIGNED_LAYER_ID;
+}
+
+std::vector<uint32_t> LayerLifecycleManager::unlinkLayers(const std::vector<uint32_t>& 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 mirroringLayerId : mDisplayMirroringLayers) {
+ RequestedLayerState* mirrorLayer = getLayerFromId(mirroringLayerId);
+ bool canBeMirrored = canMirrorRootLayer(*mirrorLayer, rootLayer);
+ 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;
+ }
+ }
+}
+
+bool LayerLifecycleManager::isLayerSecure(uint32_t layerId) const {
+ if (layerId == UNASSIGNED_LAYER_ID) {
+ return false;
+ }
+
+ if (getLayerFromId(layerId)->flags & layer_state_t::eLayerSecure) {
+ return true;
+ }
+ return isLayerSecure(getLayerFromId(layerId)->parentId);
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
new file mode 100644
index 0000000000..330da9a3d3
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLifecycleManager.h
@@ -0,0 +1,122 @@
+/*
+ * 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<std::unique_ptr<RequestedLayerState>>);
+ // 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<TransactionState>&, 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<std::pair<uint32_t, std::string /* debugName */>>&,
+ 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<ILifecycleListener>);
+ void removeLifecycleListener(std::shared_ptr<ILifecycleListener>);
+ const std::vector<std::unique_ptr<RequestedLayerState>>& getLayers() const;
+ const std::vector<std::unique_ptr<RequestedLayerState>>& getDestroyedLayers() const;
+ const std::vector<RequestedLayerState*>& getChangedLayers() const;
+ const ftl::Flags<RequestedLayerState::Changes> getGlobalChanges() const;
+ const RequestedLayerState* getLayerFromId(uint32_t) const;
+ bool isLayerSecure(uint32_t) const;
+
+private:
+ friend class LayerLifecycleManagerTest;
+ friend class HierarchyBuilderTest;
+ friend class android::SurfaceFlinger;
+
+ RequestedLayerState* getLayerFromId(uint32_t);
+ std::vector<uint32_t>* getLinkedLayersFromId(uint32_t);
+ uint32_t linkLayer(uint32_t layerId, uint32_t layerToLink);
+ uint32_t unlinkLayer(uint32_t layerId, uint32_t linkedLayer);
+ std::vector<uint32_t> unlinkLayers(const std::vector<uint32_t>& layerIds, uint32_t linkedLayer);
+
+ void updateDisplayMirrorLayers(RequestedLayerState& rootLayer);
+
+ struct References {
+ // Lifetime tied to mLayers
+ RequestedLayerState& owner;
+ std::vector<uint32_t> references;
+ std::string getDebugString() const;
+ };
+ std::unordered_map<uint32_t, References> mIdToLayer;
+ // Listeners are invoked once changes are committed.
+ std::vector<std::shared_ptr<ILifecycleListener>> mListeners;
+ // Layers that mirror a display stack (see updateDisplayMirrorLayers)
+ std::vector<uint32_t> mDisplayMirroringLayers;
+
+ // Aggregation of changes since last commit.
+ ftl::Flags<RequestedLayerState::Changes> mGlobalChanges;
+ std::vector<std::unique_ptr<RequestedLayerState>> mLayers;
+ // Layers pending destruction. Layers will be destroyed once changes are committed.
+ std::vector<std::unique_ptr<RequestedLayerState>> mDestroyedLayers;
+ // Keeps track of all the layers that were added in order. Changes will be cleared once
+ // committed.
+ std::vector<RequestedLayerState*> mAddedLayers;
+ // Keeps track of new and layers with states changes since last commit.
+ std::vector<RequestedLayerState*> mChangedLayers;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerLog.h b/services/surfaceflinger/FrontEnd/LayerLog.h
new file mode 100644
index 0000000000..3845dfeb26
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerLog.h
@@ -0,0 +1,45 @@
+/*
+ * 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 "Tracing/TransactionTracing.h"
+
+// 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__);
+
+#define LLOG_ALWAYS_FATAL_WITH_TRACE(...) \
+ do { \
+ TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false); \
+ LOG_ALWAYS_FATAL(##__VA_ARGS__); \
+ } while (false)
+
+#define LLOG_ALWAYS_FATAL_WITH_TRACE_IF(cond, ...) \
+ do { \
+ if (__predict_false(cond)) { \
+ TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false); \
+ } \
+ LOG_ALWAYS_FATAL_IF(cond, ##__VA_ARGS__); \
+ } while (false) \ No newline at end of file
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
new file mode 100644
index 0000000000..38974a2f58
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.cpp
@@ -0,0 +1,508 @@
+/*
+ * 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 "SurfaceFlinger"
+
+#include "LayerSnapshot.h"
+#include "Layer.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+namespace {
+
+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;
+ }
+}
+
+std::ostream& operator<<(std::ostream& os, const ui::Transform& transform) {
+ const uint32_t type = transform.getType();
+ const uint32_t orientation = transform.getOrientation();
+ if (type == ui::Transform::IDENTITY) {
+ return os;
+ }
+
+ if (type & ui::Transform::UNKNOWN) {
+ std::string out;
+ transform.dump(out, "", "");
+ os << out;
+ return os;
+ }
+
+ if (type & ui::Transform::ROTATE) {
+ switch (orientation) {
+ case ui::Transform::ROT_0:
+ os << "ROT_0";
+ break;
+ case ui::Transform::FLIP_H:
+ os << "FLIP_H";
+ break;
+ case ui::Transform::FLIP_V:
+ os << "FLIP_V";
+ break;
+ case ui::Transform::ROT_90:
+ os << "ROT_90";
+ break;
+ case ui::Transform::ROT_180:
+ os << "ROT_180";
+ break;
+ case ui::Transform::ROT_270:
+ os << "ROT_270";
+ break;
+ case ui::Transform::ROT_INVALID:
+ default:
+ os << "ROT_INVALID";
+ break;
+ }
+ }
+
+ if (type & ui::Transform::SCALE) {
+ std::string out;
+ android::base::StringAppendF(&out, " scale x=%.4f y=%.4f ", transform.getScaleX(),
+ transform.getScaleY());
+ os << out;
+ }
+
+ if (type & ui::Transform::TRANSLATE) {
+ std::string out;
+ android::base::StringAppendF(&out, " tx=%.4f ty=%.4f ", transform.tx(), transform.ty());
+ os << out;
+ }
+
+ return os;
+}
+
+} // namespace
+
+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<int32_t>(state.id);
+ name = state.name;
+ debugName = state.debugName;
+ premultipliedAlpha = state.premultipliedAlpha;
+ inputInfo.name = state.name;
+ inputInfo.id = static_cast<int32_t>(uniqueSequence);
+ inputInfo.ownerUid = gui::Uid{state.ownerUid};
+ inputInfo.ownerPid = gui::Pid{state.ownerPid};
+ uid = state.ownerUid;
+ pid = state.ownerPid;
+ changes = RequestedLayerState::Changes::Created;
+ clientChanges = 0;
+ mirrorRootPath = path.variant == LayerHierarchy::Variant::Mirror
+ ? path
+ : LayerHierarchy::TraversalPath::ROOT;
+ reachablilty = LayerSnapshot::Reachablilty::Unreachable;
+ frameRateSelectionPriority = state.frameRateSelectionPriority;
+ layerMetadata = state.metadata;
+}
+
+// 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 (reachablilty != LayerSnapshot::Reachablilty::Reachable) {
+ return false;
+ }
+
+ 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 (reachablilty == LayerSnapshot::Reachablilty::Unreachable)
+ return "layer not reachable from root";
+ if (reachablilty == LayerSnapshot::Reachablilty::ReachableByRelativeParent)
+ return "layer only reachable via relative parent";
+ if (isHiddenByPolicyFromParent) return "hidden by parent or layer flag";
+ if (isHiddenByPolicyFromRelativeParent) return "hidden by relative parent";
+ if (handleSkipScreenshotFlag & outputFilter.toInternalDisplay) return "eLayerSkipScreenshot";
+ if (invalidTransform) return "invalidTransform";
+ if (color.a == 0.0f && !hasBlur()) return "alpha = 0 and no blur";
+ if (!hasSomethingToDraw()) return "nothing to draw";
+
+ // 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)) &&
+ reachablilty == Reachablilty::Reachable;
+}
+
+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() << "}"
+ << "}";
+ if (hasInputInfo()) {
+ debug << " input{"
+ << "(" << inputInfo.inputConfig.string() << ")";
+ if (touchCropId != UNASSIGNED_LAYER_ID) debug << " touchCropId=" << touchCropId;
+ if (inputInfo.replaceTouchableRegionWithCrop) debug << " replaceTouchableRegionWithCrop";
+ auto touchableRegion = inputInfo.touchableRegion.getBounds();
+ debug << " touchableRegion={" << touchableRegion.left << "," << touchableRegion.top << ","
+ << touchableRegion.bottom << "," << touchableRegion.right << "}"
+ << "}";
+ }
+ return debug.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const LayerSnapshot& obj) {
+ out << "Layer [" << obj.path.id;
+ if (!obj.path.mirrorRootIds.empty()) {
+ out << " mirrored from ";
+ for (auto rootId : obj.path.mirrorRootIds) {
+ out << rootId << ",";
+ }
+ }
+ out << "] " << obj.name << "\n " << (obj.isVisible ? "visible" : "invisible")
+ << " reason=" << obj.getIsVisibleReason();
+
+ if (!obj.geomLayerBounds.isEmpty()) {
+ out << "\n bounds={" << obj.transformedBounds.left << "," << obj.transformedBounds.top
+ << "," << obj.transformedBounds.bottom << "," << obj.transformedBounds.right << "}";
+ }
+
+ if (obj.geomLayerTransform.getType() != ui::Transform::IDENTITY) {
+ out << " toDisplayTransform={" << obj.geomLayerTransform << "}";
+ }
+
+ if (obj.hasInputInfo()) {
+ out << "\n input{"
+ << "(" << obj.inputInfo.inputConfig.string() << ")";
+ if (obj.touchCropId != UNASSIGNED_LAYER_ID) out << " touchCropId=" << obj.touchCropId;
+ if (obj.inputInfo.replaceTouchableRegionWithCrop) out << " replaceTouchableRegionWithCrop";
+ auto touchableRegion = obj.inputInfo.touchableRegion.getBounds();
+ out << " touchableRegion={" << touchableRegion.left << "," << touchableRegion.top << ","
+ << touchableRegion.bottom << "," << touchableRegion.right << "}"
+ << "}";
+ }
+ return out;
+}
+
+FloatRect LayerSnapshot::sourceBounds() const {
+ if (!externalTexture) {
+ return geomLayerBounds;
+ }
+ return geomBufferSize.toFloatRect();
+}
+
+bool LayerSnapshot::isFrontBuffered() const {
+ if (!externalTexture) {
+ return false;
+ }
+
+ return externalTexture->getUsage() & AHARDWAREBUFFER_USAGE_FRONT_BUFFER;
+}
+
+Hwc2::IComposerClient::BlendMode LayerSnapshot::getBlendMode(
+ const RequestedLayerState& requested) const {
+ auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
+ if (alpha != 1.0f || !contentOpaque) {
+ blendMode = requested.premultipliedAlpha ? Hwc2::IComposerClient::BlendMode::PREMULTIPLIED
+ : Hwc2::IComposerClient::BlendMode::COVERAGE;
+ }
+ return blendMode;
+}
+
+void LayerSnapshot::merge(const RequestedLayerState& requested, bool forceUpdate,
+ bool displayChanges, bool forceFullDamage,
+ uint32_t displayRotationFlags) {
+ clientChanges = requested.what;
+ changes = requested.changes;
+ contentDirty = requested.what & layer_state_t::CONTENT_DIRTY;
+ hasReadyFrame = requested.autoRefresh;
+ sidebandStreamHasFrame = requested.hasSidebandStreamFrame();
+ updateSurfaceDamage(requested, requested.hasReadyFrame(), forceFullDamage, surfaceDamage);
+
+ if (forceUpdate || requested.what & layer_state_t::eTransparentRegionChanged) {
+ transparentRegionHint = requested.transparentRegion;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eFlagsChanged) {
+ layerOpaqueFlagSet =
+ (requested.flags & layer_state_t::eLayerOpaque) == layer_state_t::eLayerOpaque;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eBufferTransformChanged) {
+ geomBufferTransform = requested.bufferTransform;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eTransformToDisplayInverseChanged) {
+ geomBufferUsesDisplayInverseTransform = requested.transformToDisplayInverse;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eDataspaceChanged) {
+ dataspace = Layer::translateDataspace(requested.dataspace);
+ }
+ if (forceUpdate || requested.what & layer_state_t::eExtendedRangeBrightnessChanged) {
+ currentHdrSdrRatio = requested.currentHdrSdrRatio;
+ desiredHdrSdrRatio = requested.desiredHdrSdrRatio;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eCachingHintChanged) {
+ cachingHint = requested.cachingHint;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eHdrMetadataChanged) {
+ hdrMetadata = requested.hdrMetadata;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eSidebandStreamChanged) {
+ sidebandStream = requested.sidebandStream;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eShadowRadiusChanged) {
+ shadowSettings.length = requested.shadowRadius;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eFrameRateSelectionPriority) {
+ frameRateSelectionPriority = requested.frameRateSelectionPriority;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eColorSpaceAgnosticChanged) {
+ isColorspaceAgnostic = requested.colorSpaceAgnostic;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eDimmingEnabledChanged) {
+ dimmingEnabled = requested.dimmingEnabled;
+ }
+ if (forceUpdate || requested.what & layer_state_t::eCropChanged) {
+ geomCrop = requested.crop;
+ }
+
+ if (forceUpdate || requested.what & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
+ const auto compatibility =
+ Layer::FrameRate::convertCompatibility(requested.defaultFrameRateCompatibility);
+ if (defaultFrameRateCompatibility != compatibility) {
+ clientChanges |= layer_state_t::eDefaultFrameRateCompatibilityChanged;
+ }
+ defaultFrameRateCompatibility = compatibility;
+ }
+
+ if (forceUpdate ||
+ requested.what &
+ (layer_state_t::eFlagsChanged | layer_state_t::eBufferChanged |
+ layer_state_t::eSidebandStreamChanged)) {
+ compositionType = requested.getCompositionType();
+ }
+
+ if (forceUpdate || requested.what & layer_state_t::eInputInfoChanged) {
+ if (requested.windowInfoHandle) {
+ inputInfo = *requested.windowInfoHandle->getInfo();
+ } else {
+ inputInfo = {};
+ // b/271132344 revisit this and see if we can always use the layers uid/pid
+ inputInfo.name = requested.name;
+ inputInfo.ownerUid = requested.ownerUid;
+ inputInfo.ownerPid = requested.ownerPid;
+ }
+ inputInfo.id = static_cast<int32_t>(uniqueSequence);
+ touchCropId = requested.touchCropId;
+ }
+
+ if (forceUpdate ||
+ requested.what &
+ (layer_state_t::eColorChanged | layer_state_t::eBufferChanged |
+ layer_state_t::eSidebandStreamChanged)) {
+ color.rgb = requested.getColor().rgb;
+ }
+
+ if (forceUpdate || requested.what & layer_state_t::eBufferChanged) {
+ acquireFence =
+ (requested.externalTexture &&
+ requested.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged))
+ ? requested.bufferData->acquireFence
+ : Fence::NO_FENCE;
+ buffer = requested.externalTexture ? requested.externalTexture->getBuffer() : nullptr;
+ externalTexture = requested.externalTexture;
+ frameNumber = (requested.bufferData) ? requested.bufferData->frameNumber : 0;
+ hasProtectedContent = requested.externalTexture &&
+ requested.externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED;
+ geomUsesSourceCrop = hasBufferOrSidebandStream();
+ }
+
+ if (forceUpdate ||
+ requested.what &
+ (layer_state_t::eCropChanged | layer_state_t::eBufferCropChanged |
+ layer_state_t::eBufferTransformChanged |
+ layer_state_t::eTransformToDisplayInverseChanged) ||
+ requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) {
+ bufferSize = requested.getBufferSize(displayRotationFlags);
+ geomBufferSize = bufferSize;
+ croppedBufferSize = requested.getCroppedBufferSize(bufferSize);
+ geomContentCrop = requested.getBufferCrop();
+ }
+
+ if (forceUpdate ||
+ requested.what &
+ (layer_state_t::eFlagsChanged | layer_state_t::eDestinationFrameChanged |
+ layer_state_t::ePositionChanged | layer_state_t::eMatrixChanged |
+ layer_state_t::eBufferTransformChanged |
+ layer_state_t::eTransformToDisplayInverseChanged) ||
+ requested.changes.test(RequestedLayerState::Changes::BufferSize) || displayChanges) {
+ localTransform = requested.getTransform(displayRotationFlags);
+ localTransformInverse = localTransform.inverse();
+ }
+
+ if (forceUpdate || requested.what & (layer_state_t::eColorChanged) ||
+ requested.changes.test(RequestedLayerState::Changes::BufferSize)) {
+ color.rgb = requested.getColor().rgb;
+ }
+
+ if (forceUpdate ||
+ requested.what &
+ (layer_state_t::eBufferChanged | layer_state_t::eDataspaceChanged |
+ layer_state_t::eApiChanged | layer_state_t::eShadowRadiusChanged |
+ layer_state_t::eBlurRegionsChanged | layer_state_t::eStretchChanged)) {
+ forceClientComposition = shadowSettings.length > 0 || stretchEffect.hasEffect();
+ }
+
+ if (forceUpdate ||
+ requested.what &
+ (layer_state_t::eColorChanged | layer_state_t::eShadowRadiusChanged |
+ layer_state_t::eBlurRegionsChanged | layer_state_t::eBackgroundBlurRadiusChanged |
+ layer_state_t::eCornerRadiusChanged | layer_state_t::eAlphaChanged |
+ layer_state_t::eFlagsChanged | layer_state_t::eBufferChanged |
+ layer_state_t::eSidebandStreamChanged)) {
+ contentOpaque = isContentOpaque();
+ isOpaque = contentOpaque && !roundedCorner.hasRoundedCorners() && color.a == 1.f;
+ blendMode = getBlendMode(requested);
+ }
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshot.h b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
new file mode 100644
index 0000000000..73ee22fa21
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshot.h
@@ -0,0 +1,153 @@
+/*
+ * 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 <compositionengine/LayerFECompositionState.h>
+#include <renderengine/LayerSettings.h>
+#include "DisplayHardware/ComposerHal.h"
+#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;
+ }
+};
+
+// 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<ssize_t>::max();
+ bool invalidTransform = false;
+ bool isHiddenByPolicyFromParent = false;
+ bool isHiddenByPolicyFromRelativeParent = false;
+ ftl::Flags<RequestedLayerState::Changes> changes;
+ uint64_t clientChanges = 0;
+ // 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;
+ std::string debugName;
+ bool contentOpaque;
+ bool layerOpaqueFlagSet;
+ RoundedCornerState roundedCorner;
+ FloatRect transformedBounds;
+ Rect transformedBoundsWithoutTransparentRegion;
+ bool premultipliedAlpha;
+ ui::Transform parentTransform;
+ Rect bufferSize;
+ Rect croppedBufferSize;
+ std::shared_ptr<renderengine::ExternalTexture> externalTexture;
+ gui::LayerMetadata layerMetadata;
+ gui::LayerMetadata relativeLayerMetadata;
+ bool hasReadyFrame; // used in post composition to check if there is another frame ready
+ ui::Transform localTransformInverse;
+ gui::WindowInfo inputInfo;
+ ui::Transform localTransform;
+ gui::DropInputMode dropInputMode;
+ bool isTrustedOverlay;
+ gui::GameMode gameMode;
+ scheduler::LayerInfo::FrameRate frameRate;
+ scheduler::LayerInfo::FrameRate inheritedFrameRate;
+ scheduler::LayerInfo::FrameRateSelectionStrategy frameRateSelectionStrategy;
+ scheduler::FrameRateCompatibility defaultFrameRateCompatibility =
+ scheduler::FrameRateCompatibility::Default;
+ ui::Transform::RotationFlags fixedTransformHint;
+ std::optional<ui::Transform::RotationFlags> transformHint;
+ bool handleSkipScreenshotFlag = false;
+ int32_t frameRateSelectionPriority = -1;
+ LayerHierarchy::TraversalPath mirrorRootPath;
+ uint32_t touchCropId;
+ gui::Uid uid = gui::Uid::INVALID;
+ gui::Pid pid = gui::Pid::INVALID;
+ enum class Reachablilty : uint32_t {
+ // Can traverse the hierarchy from a root node and reach this snapshot
+ Reachable,
+ // Cannot traverse the hierarchy from a root node and reach this snapshot
+ Unreachable,
+ // Can only reach this node from a relative parent. This means the nodes parents are
+ // not reachable.
+ // See example scenario:
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ └ - 111 (relative)
+ // │ ├── 13
+ // │ └── 14
+ // │ └ * 12 (mirroring)
+ // └── 2
+ // 111 will create two snapshots, first when visited from 1 -> 12 or 1 -> 11 and the
+ // second when visited from 1 -> 14 -> 12. Because its parent 11 doesn't exist in the
+ // mirrored hierarchy, the second snapshot will be marked as ReachableByRelativeParent.
+ // This snapshot doesn't have any valid properties because it cannot inherit from its
+ // parent. Therefore, snapshots that are not reachable will be ignored for composition
+ // and input.
+ ReachableByRelativeParent
+ };
+ Reachablilty reachablilty;
+ // True when the surfaceDamage is recognized as a small area update.
+ bool isSmallDirty = false;
+
+ 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;
+ bool isFrontBuffered() const;
+ Hwc2::IComposerClient::BlendMode getBlendMode(const RequestedLayerState& requested) const;
+ friend std::ostream& operator<<(std::ostream& os, const LayerSnapshot& obj);
+ void merge(const RequestedLayerState& requested, bool forceUpdate, bool displayChanges,
+ bool forceFullDamage, uint32_t displayRotationFlags);
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
new file mode 100644
index 0000000000..ad5e42be37
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.cpp
@@ -0,0 +1,1235 @@
+/*
+ * 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 "SurfaceFlinger"
+
+#include <numeric>
+#include <optional>
+
+#include <ftl/small_map.h>
+#include <gui/TraceUtils.h>
+#include <ui/DisplayMap.h>
+#include <ui/FloatRect.h>
+
+#include "DisplayHardware/HWC2.h"
+#include "DisplayHardware/Hal.h"
+#include "Layer.h" // eFrameRateSelectionPriority constants
+#include "LayerLog.h"
+#include "LayerSnapshotBuilder.h"
+#include "TimeStats/TimeStats.h"
+#include "Tracing/TransactionTracing.h"
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+namespace {
+
+FloatRect getMaxDisplayBounds(const DisplayInfos& 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<float>(maxSize.getWidth()) * 10.f;
+ const float yMax = static_cast<float>(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<int32_t>(t.tx() + 0.5);
+ const auto ty = static_cast<int32_t>(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<FloatRect, bool> 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<float>(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();
+ }
+
+ info.frame = getInputBoundsInDisplaySpace(snapshot, inputBounds, screenToDisplay);
+
+ 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<float>(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 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);
+ LLOGV(snapshot.sequence, "updating visibility %s %s", visible ? "true" : "false",
+ snapshot.getDebugString().c_str());
+}
+
+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.clientChanges = 0;
+ snapshot.contentDirty = false;
+ snapshot.hasReadyFrame = false;
+ snapshot.sidebandStreamHasFrame = false;
+ snapshot.surfaceDamage.clear();
+}
+
+// TODO (b/259407931): Remove.
+uint32_t getPrimaryDisplayRotationFlags(
+ const ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo>& displays) {
+ for (auto& [_, display] : displays) {
+ if (display.isPrimary) {
+ return display.rotationFlags;
+ }
+ }
+ return 0;
+}
+
+} // namespace
+
+LayerSnapshot LayerSnapshotBuilder::getRootSnapshot() {
+ LayerSnapshot snapshot;
+ snapshot.path = LayerHierarchy::TraversalPath::ROOT;
+ snapshot.changes = ftl::Flags<RequestedLayerState::Changes>();
+ snapshot.clientChanges = 0;
+ 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.shadowSettings.length = 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() {}
+
+LayerSnapshotBuilder::LayerSnapshotBuilder(Args args) : LayerSnapshotBuilder() {
+ args.forceUpdate = ForceUpdateFlags::ALL;
+ updateSnapshots(args);
+}
+
+bool LayerSnapshotBuilder::tryFastUpdate(const Args& args) {
+ const bool forceUpdate = args.forceUpdate != ForceUpdateFlags::NONE;
+
+ if (args.layerLifecycleManager.getGlobalChanges().get() == 0 && !forceUpdate &&
+ !args.displayChanges) {
+ return true;
+ }
+
+ // There are only content changes which do not require any child layer snapshots to be updated.
+ ALOGV("%s", __func__);
+ ATRACE_NAME("FastPath");
+
+ uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
+ if (forceUpdate || args.displayChanges) {
+ for (auto& snapshot : mSnapshots) {
+ const RequestedLayerState* requested =
+ args.layerLifecycleManager.getLayerFromId(snapshot->path.id);
+ if (!requested) continue;
+ snapshot->merge(*requested, forceUpdate, args.displayChanges, args.forceFullDamage,
+ primaryDisplayRotationFlags);
+ }
+ return false;
+ }
+
+ // Walk through all the updated requested layer states and update the corresponding snapshots.
+ for (const RequestedLayerState* requested : args.layerLifecycleManager.getChangedLayers()) {
+ auto range = mIdToSnapshots.equal_range(requested->id);
+ for (auto it = range.first; it != range.second; it++) {
+ it->second->merge(*requested, forceUpdate, args.displayChanges, args.forceFullDamage,
+ primaryDisplayRotationFlags);
+ }
+ }
+
+ if ((args.layerLifecycleManager.getGlobalChanges().get() &
+ ~(RequestedLayerState::Changes::Content | RequestedLayerState::Changes::Buffer).get()) !=
+ 0) {
+ // We have changes that require us to walk the hierarchy and update child layers.
+ // No fast path for you.
+ return false;
+ }
+ return true;
+}
+
+void LayerSnapshotBuilder::updateSnapshots(const Args& args) {
+ ATRACE_NAME("UpdateSnapshots");
+ LayerSnapshot rootSnapshot = args.rootSnapshot;
+ if (args.parentCrop) {
+ rootSnapshot.geomLayerBounds = *args.parentCrop;
+ } else if (args.forceUpdate == ForceUpdateFlags::ALL || args.displayChanges) {
+ rootSnapshot.geomLayerBounds = getMaxDisplayBounds(args.displays);
+ }
+ if (args.displayChanges) {
+ rootSnapshot.changes = RequestedLayerState::Changes::AffectsChildren |
+ RequestedLayerState::Changes::Geometry;
+ }
+ if (args.forceUpdate == ForceUpdateFlags::HIERARCHY) {
+ rootSnapshot.changes |=
+ RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Visibility;
+ rootSnapshot.clientChanges |= layer_state_t::eReparent;
+ }
+
+ for (auto& snapshot : mSnapshots) {
+ if (snapshot->reachablilty == LayerSnapshot::Reachablilty::Reachable) {
+ snapshot->reachablilty = LayerSnapshot::Reachablilty::Unreachable;
+ }
+ }
+
+ 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, rootSnapshot, /*depth=*/0);
+ } else {
+ for (auto& [childHierarchy, variant] : args.root.mChildren) {
+ LayerHierarchy::ScopedAddToTraversalPath addChildToPath(root,
+ childHierarchy->getLayer()->id,
+ variant);
+ updateSnapshotsInHierarchy(args, *childHierarchy, root, rootSnapshot, /*depth=*/0);
+ }
+ }
+
+ // 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);
+
+ // 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<uint32_t> destroyedLayerIds;
+ for (auto& destroyedLayer : args.layerLifecycleManager.getDestroyedLayers()) {
+ destroyedLayerIds.insert(destroyedLayer->id);
+ }
+
+ auto it = mSnapshots.begin();
+ while (it < mSnapshots.end()) {
+ auto& traversalPath = it->get()->path;
+ const bool unreachable =
+ it->get()->reachablilty == LayerSnapshot::Reachablilty::Unreachable;
+ const bool isClone = traversalPath.isClone();
+ const bool layerIsDestroyed =
+ destroyedLayerIds.find(traversalPath.id) != destroyedLayerIds.end();
+ const bool destroySnapshot = (unreachable && isClone) || layerIsDestroyed;
+
+ if (!destroySnapshot) {
+ it++;
+ continue;
+ }
+
+ mPathToSnapshot.erase(traversalPath);
+
+ auto range = mIdToSnapshots.equal_range(traversalPath.id);
+ auto matchingSnapshot =
+ std::find_if(range.first, range.second, [&traversalPath](auto& snapshotWithId) {
+ return snapshotWithId.second->path == traversalPath;
+ });
+ mIdToSnapshots.erase(matchingSnapshot);
+ 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,
+ int depth) {
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(depth > 50,
+ "Cycle detected in LayerSnapshotBuilder. See "
+ "builder_stack_overflow_transactions.winscope");
+
+ const RequestedLayerState* layer = hierarchy.getLayer();
+ LayerSnapshot* snapshot = getSnapshot(traversalPath);
+ const bool newSnapshot = snapshot == nullptr;
+ uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
+ if (newSnapshot) {
+ snapshot = createSnapshot(traversalPath, *layer, parentSnapshot);
+ snapshot->merge(*layer, /*forceUpdate=*/true, /*displayChanges=*/true, args.forceFullDamage,
+ primaryDisplayRotationFlags);
+ snapshot->changes |= RequestedLayerState::Changes::Created;
+ }
+
+ 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,
+ depth + 1);
+ updateFrameRateFromChildSnapshot(*snapshot, childSnapshot, args);
+ }
+
+ 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 = mPathToSnapshot.find(id);
+ return it == mPathToSnapshot.end() ? nullptr : it->second;
+}
+
+LayerSnapshot* LayerSnapshotBuilder::createSnapshot(const LayerHierarchy::TraversalPath& path,
+ const RequestedLayerState& layer,
+ const LayerSnapshot& parentSnapshot) {
+ mSnapshots.emplace_back(std::make_unique<LayerSnapshot>(layer, path));
+ LayerSnapshot* snapshot = mSnapshots.back().get();
+ snapshot->globalZ = static_cast<size_t>(mSnapshots.size()) - 1;
+ if (path.isClone() && path.variant != LayerHierarchy::Variant::Mirror) {
+ snapshot->mirrorRootPath = parentSnapshot.mirrorRootPath;
+ }
+ mPathToSnapshot[path] = snapshot;
+
+ mIdToSnapshots.emplace(path.id, 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;
+
+ size_t globalZ = 0;
+ args.root.traverseInZOrder(
+ [this, &globalZ](const LayerHierarchy&,
+ const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+ LayerSnapshot* snapshot = getSnapshot(traversalPath);
+ if (!snapshot) {
+ return true;
+ }
+
+ 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<ssize_t>(oldZ),
+ mSnapshots.begin() + static_cast<ssize_t>(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]->reachablilty == LayerSnapshot::Reachablilty::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;
+ }
+ }
+ if (snapshot.reachablilty == LayerSnapshot::Reachablilty::Unreachable) {
+ snapshot.reachablilty = LayerSnapshot::Reachablilty::ReachableByRelativeParent;
+ }
+}
+
+void LayerSnapshotBuilder::updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
+ const LayerSnapshot& childSnapshot,
+ const Args& args) {
+ if (args.forceUpdate == ForceUpdateFlags::NONE &&
+ !args.layerLifecycleManager.getGlobalChanges().any(
+ RequestedLayerState::Changes::Hierarchy) &&
+ !childSnapshot.changes.any(RequestedLayerState::Changes::FrameRate) &&
+ !snapshot.changes.any(RequestedLayerState::Changes::FrameRate)) {
+ return;
+ }
+
+ using FrameRateCompatibility = scheduler::FrameRateCompatibility;
+ if (snapshot.frameRate.isValid()) {
+ // we already have a valid framerate.
+ return;
+ }
+
+ // We return whether this layer or 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.
+ const auto layerVotedWithDefaultCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
+ childSnapshot.frameRate.vote.type == FrameRateCompatibility::Default;
+ const auto layerVotedWithNoVote =
+ childSnapshot.frameRate.vote.type == FrameRateCompatibility::NoVote;
+ const auto layerVotedWithCategory =
+ childSnapshot.frameRate.category != FrameRateCategory::Default;
+ const auto layerVotedWithExactCompatibility = childSnapshot.frameRate.vote.rate.isValid() &&
+ childSnapshot.frameRate.vote.type == FrameRateCompatibility::Exact;
+
+ bool childHasValidFrameRate = layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
+ layerVotedWithCategory || 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 (childHasValidFrameRate) {
+ snapshot.frameRate = scheduler::LayerInfo::FrameRate(Fps(), FrameRateCompatibility::NoVote);
+ snapshot.changes |= RequestedLayerState::Changes::FrameRate;
+ }
+}
+
+void LayerSnapshotBuilder::resetRelativeState(LayerSnapshot& snapshot) {
+ snapshot.isHiddenByPolicyFromRelativeParent = false;
+ snapshot.relativeLayerMetadata.mMap.clear();
+}
+
+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<RequestedLayerState::Changes> parentChanges = parentSnapshot.changes &
+ (RequestedLayerState::Changes::Hierarchy | RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Metadata |
+ RequestedLayerState::Changes::AffectsChildren | RequestedLayerState::Changes::Input |
+ RequestedLayerState::Changes::FrameRate | RequestedLayerState::Changes::GameMode);
+ snapshot.changes |= parentChanges;
+ if (args.displayChanges) snapshot.changes |= RequestedLayerState::Changes::Geometry;
+ snapshot.reachablilty = LayerSnapshot::Reachablilty::Reachable;
+ snapshot.clientChanges |= (parentSnapshot.clientChanges & layer_state_t::AFFECTS_CHILDREN);
+ snapshot.isHiddenByPolicyFromParent = parentSnapshot.isHiddenByPolicyFromParent ||
+ parentSnapshot.invalidTransform || requested.isHiddenByPolicy() ||
+ (args.excludeLayerIds.find(path.id) != args.excludeLayerIds.end());
+
+ const bool forceUpdate = args.forceUpdate == ForceUpdateFlags::ALL ||
+ snapshot.clientChanges & layer_state_t::eReparent ||
+ snapshot.changes.any(RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::Created);
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eLayerStackChanged) {
+ // If root layer, use the layer stack otherwise get the parent's layer stack.
+ snapshot.outputFilter.layerStack =
+ parentSnapshot.path == LayerHierarchy::TraversalPath::ROOT
+ ? requested.layerStack
+ : parentSnapshot.outputFilter.layerStack;
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eTrustedOverlayChanged) {
+ snapshot.isTrustedOverlay = parentSnapshot.isTrustedOverlay || requested.isTrustedOverlay;
+ }
+
+ if (snapshot.isHiddenByPolicyFromParent &&
+ !snapshot.changes.test(RequestedLayerState::Changes::Created)) {
+ if (forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::BufferSize |
+ RequestedLayerState::Changes::Input)) {
+ updateInput(snapshot, requested, parentSnapshot, path, args);
+ }
+ return;
+ }
+
+ if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Mirror)) {
+ // 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.clientChanges & layer_state_t::eAlphaChanged) {
+ snapshot.color.a = parentSnapshot.color.a * requested.color.a;
+ snapshot.alpha = snapshot.color.a;
+ snapshot.inputInfo.alpha = snapshot.color.a;
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eFlagsChanged) {
+ snapshot.isSecure =
+ parentSnapshot.isSecure || (requested.flags & layer_state_t::eLayerSecure);
+ snapshot.outputFilter.toInternalDisplay = parentSnapshot.outputFilter.toInternalDisplay ||
+ (requested.flags & layer_state_t::eLayerSkipScreenshot);
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eStretchChanged) {
+ snapshot.stretchEffect = (requested.stretchEffect.hasEffect())
+ ? requested.stretchEffect
+ : parentSnapshot.stretchEffect;
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eColorTransformChanged) {
+ if (!parentSnapshot.colorTransformIsIdentity) {
+ snapshot.colorTransform = parentSnapshot.colorTransform * requested.colorTransform;
+ snapshot.colorTransformIsIdentity = false;
+ } else {
+ snapshot.colorTransform = requested.colorTransform;
+ snapshot.colorTransformIsIdentity = !requested.hasColorTransform;
+ }
+ }
+
+ if (forceUpdate || snapshot.changes.test(RequestedLayerState::Changes::GameMode)) {
+ snapshot.gameMode = requested.metadata.has(gui::METADATA_GAME_MODE)
+ ? requested.gameMode
+ : parentSnapshot.gameMode;
+ updateMetadata(snapshot, requested, args);
+ if (args.includeMetadata) {
+ snapshot.layerMetadata = parentSnapshot.layerMetadata;
+ snapshot.layerMetadata.merge(requested.metadata);
+ }
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eFixedTransformHintChanged ||
+ 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 ||
+ args.layerLifecycleManager.getGlobalChanges().any(
+ RequestedLayerState::Changes::Hierarchy) ||
+ snapshot.changes.any(RequestedLayerState::Changes::FrameRate |
+ RequestedLayerState::Changes::Hierarchy)) {
+ const bool shouldOverrideChildren = parentSnapshot.frameRateSelectionStrategy ==
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+ const bool propagationAllowed = parentSnapshot.frameRateSelectionStrategy !=
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Self;
+ if ((!requested.requestedFrameRate.isValid() && propagationAllowed) ||
+ shouldOverrideChildren) {
+ snapshot.inheritedFrameRate = parentSnapshot.inheritedFrameRate;
+ } else {
+ snapshot.inheritedFrameRate = requested.requestedFrameRate;
+ }
+ // Set the framerate as the inherited frame rate and allow children to override it if
+ // needed.
+ snapshot.frameRate = snapshot.inheritedFrameRate;
+ snapshot.changes |= RequestedLayerState::Changes::FrameRate;
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionStrategyChanged) {
+ if (parentSnapshot.frameRateSelectionStrategy ==
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren) {
+ snapshot.frameRateSelectionStrategy =
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren;
+ } else {
+ const auto strategy = scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+ requested.frameRateSelectionStrategy);
+ snapshot.frameRateSelectionStrategy = strategy;
+ }
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eFrameRateSelectionPriority) {
+ snapshot.frameRateSelectionPriority =
+ (requested.frameRateSelectionPriority == Layer::PRIORITY_UNSET)
+ ? parentSnapshot.frameRateSelectionPriority
+ : requested.frameRateSelectionPriority;
+ }
+
+ if (forceUpdate ||
+ snapshot.clientChanges &
+ (layer_state_t::eBackgroundBlurRadiusChanged | layer_state_t::eBlurRegionsChanged |
+ layer_state_t::eAlphaChanged)) {
+ snapshot.backgroundBlurRadius = args.supportsBlur
+ ? static_cast<int>(parentSnapshot.color.a * (float)requested.backgroundBlurRadius)
+ : 0;
+ snapshot.blurRegions = requested.blurRegions;
+ for (auto& region : snapshot.blurRegions) {
+ region.alpha = region.alpha * snapshot.color.a;
+ }
+ }
+
+ if (forceUpdate || snapshot.changes.any(RequestedLayerState::Changes::Geometry)) {
+ uint32_t primaryDisplayRotationFlags = getPrimaryDisplayRotationFlags(args.displays);
+ updateLayerBounds(snapshot, requested, parentSnapshot, primaryDisplayRotationFlags);
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eCornerRadiusChanged ||
+ snapshot.changes.any(RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::BufferUsageFlags)) {
+ updateRoundedCorner(snapshot, requested, parentSnapshot, args);
+ }
+
+ if (forceUpdate || snapshot.clientChanges & layer_state_t::eShadowRadiusChanged ||
+ snapshot.changes.any(RequestedLayerState::Changes::Geometry)) {
+ updateShadows(snapshot, requested, args.globalShadowSettings);
+ }
+
+ if (forceUpdate ||
+ snapshot.changes.any(RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::Input)) {
+ updateInput(snapshot, requested, parentSnapshot, path, args);
+ }
+
+ // computed snapshot properties
+ snapshot.forceClientComposition =
+ snapshot.shadowSettings.length > 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,
+ const Args& args) {
+ if (args.skipRoundCornersWhenProtected && requested.isProtected()) {
+ snapshot.roundedCorner = RoundedCornerState();
+ return;
+ }
+ 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.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&,
+ const ShadowSettings& globalShadowSettings) {
+ if (snapshot.shadowSettings.length > 0.f) {
+ snapshot.shadowSettings.ambientColor = globalShadowSettings.ambientColor;
+ snapshot.shadowSettings.spotColor = globalShadowSettings.spotColor;
+ snapshot.shadowSettings.lightPos = globalShadowSettings.lightPos;
+ snapshot.shadowSettings.lightRadius = globalShadowSettings.lightRadius;
+
+ // 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 = gui::Uid{requested.ownerUid};
+ snapshot.inputInfo.ownerPid = gui::Pid{requested.ownerPid};
+ }
+ snapshot.touchCropId = requested.touchCropId;
+
+ snapshot.inputInfo.id = static_cast<int32_t>(snapshot.uniqueSequence);
+ snapshot.inputInfo.displayId = static_cast<int32_t>(snapshot.outputFilter.layerStack.id);
+ 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;
+ }
+
+ updateVisibility(snapshot, snapshot.isVisible);
+ if (!needsInputInfo(snapshot, requested)) {
+ return;
+ }
+
+ static frontend::DisplayInfo sDefaultInfo = {.isSecure = false};
+ const std::optional<frontend::DisplayInfo> 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;
+
+ 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;
+ }
+
+ if (requested.touchCropId != UNASSIGNED_LAYER_ID || path.isClone()) {
+ mNeedsTouchableRegionCrop.insert(path);
+ }
+ auto cropLayerSnapshot = getSnapshot(requested.touchCropId);
+ if (!cropLayerSnapshot && 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;
+ }
+
+ snapshot.inputInfo.contentSize = snapshot.croppedBufferSize.getSize();
+
+ // 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);
+ }
+}
+
+std::vector<std::unique_ptr<LayerSnapshot>>& 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<LayerSnapshot>& 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<RequestedLayerState::Changes> 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) && !args.displayChanges) {
+ return;
+ }
+
+ for (auto& path : mNeedsTouchableRegionCrop) {
+ frontend::LayerSnapshot* snapshot = getSnapshot(path);
+ if (!snapshot) {
+ continue;
+ }
+ LLOGV(snapshot->sequence, "updateTouchableRegionCrop=%s",
+ snapshot->getDebugString().c_str());
+ const std::optional<frontend::DisplayInfo> 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 0000000000..1cec0183b9
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/LayerSnapshotBuilder.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 "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 {
+private:
+ static LayerSnapshot getRootSnapshot();
+
+public:
+ enum class ForceUpdateFlags {
+ NONE,
+ ALL,
+ HIERARCHY,
+ };
+ struct Args {
+ LayerHierarchy root;
+ const LayerLifecycleManager& layerLifecycleManager;
+ ForceUpdateFlags forceUpdate = ForceUpdateFlags::NONE;
+ bool includeMetadata = false;
+ const DisplayInfos& displays;
+ // Set to true if there were display changes since last update.
+ bool displayChanges = false;
+ const ShadowSettings& globalShadowSettings;
+ bool supportsBlur = true;
+ bool forceFullDamage = false;
+ std::optional<FloatRect> parentCrop = std::nullopt;
+ std::unordered_set<uint32_t> excludeLayerIds;
+ const std::unordered_map<std::string, bool>& supportedLayerGenericMetadata;
+ const std::unordered_map<std::string, uint32_t>& genericLayerMetadataKeyMap;
+ bool skipRoundCornersWhenProtected = false;
+ LayerSnapshot rootSnapshot = getRootSnapshot();
+ };
+ 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<std::unique_ptr<LayerSnapshot>>& getSnapshots();
+ LayerSnapshot* getSnapshot(uint32_t layerId) const;
+ LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath& id) const;
+
+ typedef std::function<void(const LayerSnapshot& snapshot)> 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<void(std::unique_ptr<LayerSnapshot>& 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;
+
+ // 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, int depth);
+ 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, const Args& args);
+ void updateLayerBounds(LayerSnapshot& snapshot, const RequestedLayerState& layerState,
+ const LayerSnapshot& parentSnapshot, uint32_t displayRotationFlags);
+ static void updateShadows(LayerSnapshot& snapshot, const RequestedLayerState& requested,
+ const 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 updateFrameRateFromChildSnapshot(LayerSnapshot& snapshot,
+ const LayerSnapshot& childSnapshot, const Args& args);
+ void updateTouchableRegionCrop(const Args& args);
+
+ std::unordered_map<LayerHierarchy::TraversalPath, LayerSnapshot*,
+ LayerHierarchy::TraversalPathHash>
+ mPathToSnapshot;
+ std::multimap<uint32_t, LayerSnapshot*> mIdToSnapshots;
+
+ // Track snapshots that needs touchable region crop from other snapshots
+ std::unordered_set<LayerHierarchy::TraversalPath, LayerHierarchy::TraversalPathHash>
+ mNeedsTouchableRegionCrop;
+ std::vector<std::unique_ptr<LayerSnapshot>> mSnapshots;
+ 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 0000000000..209df79f8e
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.cpp
@@ -0,0 +1,631 @@
+/*
+ * 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 "SurfaceFlinger"
+
+#include <gui/TraceUtils.h>
+#include <log/log.h>
+#include <private/android_filesystem_config.h>
+#include <sys/types.h>
+
+#include <scheduler/Fps.h>
+
+#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<uint32_t>& 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),
+ ownerUid(args.ownerUid),
+ ownerPid(args.ownerPid),
+ parentId(args.parentId),
+ layerIdToMirror(args.layerIdToMirror) {
+ layerId = static_cast<int32_t>(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;
+ 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>();
+ bufferData->frameNumber = 0;
+ bufferData->acquireFence = sp<Fence>::make(-1);
+ acquireFenceTime = std::make_shared<FenceTime>(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<int8_t>(scheduler::FrameRateCompatibility::Default);
+ frameRateCategory = static_cast<int8_t>(FrameRateCategory::Default);
+ frameRateCategorySmoothSwitchOnly = false;
+ frameRateSelectionStrategy =
+ static_cast<int8_t>(scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ dataspace = ui::Dataspace::V0_SRGB;
+ gameMode = gui::GameMode::Unsupported;
+ requestedFrameRate = {};
+ cachingHint = gui::CachingHint::Enabled;
+
+ if (name.length() > 77) {
+ std::string shortened;
+ shortened.append(name, 0, 36);
+ shortened.append("[...]");
+ shortened.append(name, name.length() - 36);
+ debugName = std::move(shortened);
+ } else {
+ debugName = name;
+ }
+}
+
+void RequestedLayerState::merge(const ResolvedComposerState& resolvedComposerState) {
+ const uint32_t oldFlags = flags;
+ const half oldAlpha = color.a;
+ const bool hadBuffer = externalTexture != nullptr;
+ uint64_t oldFramenumber = hadBuffer ? bufferData->frameNumber : 0;
+ const ui::Size oldBufferSize = hadBuffer
+ ? ui::Size(externalTexture->getWidth(), externalTexture->getHeight())
+ : ui::Size();
+ const uint64_t oldUsageFlags = hadBuffer ? externalTexture->getUsage() : 0;
+ const bool oldBufferFormatOpaque = LayerSnapshot::isOpaqueFormat(
+ externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE);
+
+ const bool hadSideStream = sidebandStream != nullptr;
+ const layer_state_t& clientState = resolvedComposerState.state;
+ const bool hadSomethingToDraw = hasSomethingToDraw();
+ uint64_t clientChanges = what | layer_state_t::diff(clientState);
+ layer_state_t::merge(clientState);
+ what = clientChanges;
+ LLOGV(layerId, "requested=%" PRIu64 "flags=%" PRIu64, clientState.what, clientChanges);
+
+ if (clientState.what & layer_state_t::eFlagsChanged) {
+ if ((oldFlags ^ flags) & (layer_state_t::eLayerHidden | layer_state_t::eLayerOpaque)) {
+ 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;
+ const bool hasBuffer = externalTexture != nullptr;
+ if (hasBuffer || hasBuffer != hadBuffer) {
+ changes |= RequestedLayerState::Changes::Buffer;
+ const ui::Size newBufferSize = hasBuffer
+ ? ui::Size(externalTexture->getWidth(), externalTexture->getHeight())
+ : ui::Size();
+ if (oldBufferSize != newBufferSize) {
+ changes |= RequestedLayerState::Changes::BufferSize;
+ changes |= RequestedLayerState::Changes::Geometry;
+ }
+ const uint64_t usageFlags = hasBuffer ? externalTexture->getUsage() : 0;
+ if (oldUsageFlags != usageFlags) {
+ changes |= RequestedLayerState::Changes::BufferUsageFlags;
+ }
+ }
+
+ if (hasBuffer != hadBuffer) {
+ changes |= RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::VisibleRegion |
+ RequestedLayerState::Changes::Visibility | RequestedLayerState::Changes::Input;
+ }
+
+ if (hasBuffer) {
+ const bool frameNumberChanged =
+ bufferData->flags.test(BufferData::BufferDataChange::frameNumberChanged);
+ const uint64_t frameNumber =
+ frameNumberChanged ? bufferData->frameNumber : oldFramenumber + 1;
+ bufferData->frameNumber = frameNumber;
+
+ if ((barrierProducerId > bufferData->producerId) ||
+ ((barrierProducerId == bufferData->producerId) &&
+ (barrierFrameNumber > bufferData->frameNumber))) {
+ ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
+ " -> producedId=%d frameNumber=%" PRIu64,
+ getDebugString().c_str(), barrierProducerId, barrierFrameNumber,
+ bufferData->producerId, frameNumber);
+ TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_",
+ /*overwrite=*/false);
+ }
+
+ barrierProducerId = std::max(bufferData->producerId, barrierProducerId);
+ barrierFrameNumber = std::max(bufferData->frameNumber, barrierFrameNumber);
+ }
+
+ const bool newBufferFormatOpaque = LayerSnapshot::isOpaqueFormat(
+ externalTexture ? externalTexture->getPixelFormat() : PIXEL_FORMAT_NONE);
+ if (newBufferFormatOpaque != oldBufferFormatOpaque) {
+ changes |= RequestedLayerState::Changes::Visibility |
+ RequestedLayerState::Changes::VisibleRegion;
+ }
+ }
+
+ 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;
+ }
+ }
+
+ if (hadSomethingToDraw != hasSomethingToDraw()) {
+ 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<int32_t>(gameMode) != requestedGameMode) {
+ gameMode = static_cast<gui::GameMode>(requestedGameMode);
+ changes |= RequestedLayerState::Changes::GameMode;
+ }
+ }
+ }
+ if (clientState.what & layer_state_t::eFrameRateChanged) {
+ const auto compatibility =
+ Layer::FrameRate::convertCompatibility(clientState.frameRateCompatibility);
+ const auto strategy = Layer::FrameRate::convertChangeFrameRateStrategy(
+ clientState.changeFrameRateStrategy);
+ requestedFrameRate.vote =
+ Layer::FrameRate::FrameRateVote(Fps::fromValue(clientState.frameRate),
+ compatibility, strategy);
+ changes |= RequestedLayerState::Changes::FrameRate;
+ }
+ if (clientState.what & layer_state_t::eFrameRateCategoryChanged) {
+ const auto category = Layer::FrameRate::convertCategory(clientState.frameRateCategory);
+ requestedFrameRate.category = category;
+ 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<float>(destRect.left), static_cast<float>(destRect.top));
+ return transform;
+ }
+
+ ui::Size bufferSize = getUnrotatedBufferSize(displayRotationFlags);
+
+ float sx = static_cast<float>(destW) / static_cast<float>(bufferSize.width);
+ float sy = static_cast<float>(destH) / static_cast<float>(bufferSize.height);
+ ui::Transform transform;
+ transform.set(sx, 0, 0, sy);
+ transform.set(static_cast<float>(destRect.left), static_cast<float>(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;
+ debug << "}";
+ return debug.str();
+}
+
+std::ostream& operator<<(std::ostream& out, const scheduler::LayerInfo::FrameRate& obj) {
+ out << obj.vote.rate;
+ out << " " << ftl::enum_string_full(obj.vote.type);
+ out << " " << ftl::enum_string_full(obj.category);
+ return out;
+}
+
+std::ostream& operator<<(std::ostream& out, const RequestedLayerState& obj) {
+ out << obj.debugName;
+ if (obj.relativeParentId != UNASSIGNED_LAYER_ID) out << " parent=" << obj.parentId;
+ if (!obj.handleAlive) out << " handleNotAlive";
+ if (obj.requestedFrameRate.isValid())
+ out << " requestedFrameRate: {" << obj.requestedFrameRate << "}";
+ if (obj.dropInputMode != gui::DropInputMode::NONE)
+ out << " dropInputMode=" << static_cast<uint32_t>(obj.dropInputMode);
+ return out;
+}
+
+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 || externalTexture) {
+ 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<int32_t>(bufWidth), static_cast<int32_t>(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() && externalTexture != nullptr) {
+ // if the buffer crop is defined and there's a valid buffer, intersect buffer size and crop
+ // since the crop should never exceed the size of the buffer.
+ Rect sizeAndCrop;
+ externalTexture->getBounds().intersect(bufferCrop, &sizeAndCrop);
+ return sizeAndCrop;
+ } else if (externalTexture != nullptr) {
+ // otherwise we use the whole buffer
+ return externalTexture->getBounds();
+ } else if (!bufferCrop.isEmpty()) {
+ // if the buffer crop is defined, we use that
+ return bufferCrop;
+ } 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 (flags & layer_state_t::eLayerIsRefreshRateIndicator) {
+ return Composition::REFRESH_RATE_INDICATOR;
+ }
+ 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;
+}
+
+bool RequestedLayerState::backpressureEnabled() const {
+ return flags & layer_state_t::eEnableBackpressure;
+}
+
+bool RequestedLayerState::isSimpleBufferUpdate(const layer_state_t& s) const {
+ static constexpr uint64_t requiredFlags = layer_state_t::eBufferChanged;
+ if ((s.what & requiredFlags) != requiredFlags) {
+ ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+ (s.what | requiredFlags) & ~s.what);
+ return false;
+ }
+
+ static constexpr 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;
+ if (s.what & deniedFlags) {
+ ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+ s.what & deniedFlags);
+ return false;
+ }
+
+ bool changedFlags = diff(s);
+ static constexpr auto deniedChanges = layer_state_t::ePositionChanged |
+ layer_state_t::eAlphaChanged | layer_state_t::eColorTransformChanged |
+ layer_state_t::eBackgroundColorChanged | layer_state_t::eMatrixChanged |
+ layer_state_t::eCornerRadiusChanged | layer_state_t::eBackgroundBlurRadiusChanged |
+ layer_state_t::eBufferTransformChanged |
+ layer_state_t::eTransformToDisplayInverseChanged | layer_state_t::eCropChanged |
+ layer_state_t::eDataspaceChanged | layer_state_t::eHdrMetadataChanged |
+ layer_state_t::eSidebandStreamChanged | layer_state_t::eColorSpaceAgnosticChanged |
+ layer_state_t::eShadowRadiusChanged | layer_state_t::eFixedTransformHintChanged |
+ layer_state_t::eTrustedOverlayChanged | layer_state_t::eStretchChanged |
+ layer_state_t::eBufferCropChanged | layer_state_t::eDestinationFrameChanged |
+ layer_state_t::eDimmingEnabledChanged | layer_state_t::eExtendedRangeBrightnessChanged;
+ if (changedFlags & deniedChanges) {
+ ATRACE_FORMAT_INSTANT("%s: false [has denied changes flags 0x%" PRIx64 "]", __func__,
+ s.what & deniedChanges);
+ return false;
+ }
+
+ return true;
+}
+
+bool RequestedLayerState::isProtected() const {
+ return externalTexture && externalTexture->getUsage() & GRALLOC_USAGE_PROTECTED;
+}
+
+bool RequestedLayerState::hasSomethingToDraw() const {
+ return externalTexture != nullptr || sidebandStream != nullptr || shadowRadius > 0.f ||
+ backgroundBlurRadius > 0 || blurRegions.size() > 0 ||
+ (color.r >= 0.0_hf && color.g >= 0.0_hf && color.b >= 0.0_hf);
+}
+
+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 0000000000..09f33de63b
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/RequestedLayerState.h
@@ -0,0 +1,134 @@
+/*
+ * 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 <aidl/android/hardware/graphics/composer3/Composition.h>
+#include <ftl/flags.h>
+#include <gui/LayerState.h>
+#include <renderengine/ExternalTexture.h>
+#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,
+ BufferSize = 1u << 18,
+ GameMode = 1u << 19,
+ BufferUsageFlags = 1u << 20,
+ };
+ 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;
+ friend std::ostream& operator<<(std::ostream& os, const RequestedLayerState& obj);
+ 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;
+ bool backpressureEnabled() const;
+ bool isSimpleBufferUpdate(const layer_state_t&) const;
+ bool isProtected() const;
+ bool hasSomethingToDraw() 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;
+ // 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 gui::Uid 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 gui::Pid 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<FenceTime> acquireFenceTime;
+ std::shared_ptr<renderengine::ExternalTexture> 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;
+ std::string debugName;
+
+ // book keeping states
+ bool handleAlive = true;
+ bool isRelativeOf = false;
+ std::vector<uint32_t> mirrorIds{};
+ ftl::Flags<RequestedLayerState::Changes> 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 0000000000..0061c53e62
--- /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 <vector>
+
+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 <typename T>
+bool swapErase(std::vector<T>& 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<T>& vec, const T& value) but erases the first element
+// that returns true for predicate.
+template <typename T, class P>
+void swapErase(std::vector<T>& 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 0000000000..d3d950974f
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.cpp
@@ -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.
+ */
+
+// #define LOG_NDEBUG 0
+#undef LOG_TAG
+#define LOG_TAG "SurfaceFlinger"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <cutils/trace.h>
+#include <utils/Log.h>
+#include <utils/Trace.h>
+#include "FrontEnd/LayerLog.h"
+
+#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<int>(mPendingTransactionCount.load()));
+}
+
+void TransactionHandler::collectTransactions() {
+ while (!mLocklessTransactionQueue.isEmpty()) {
+ auto maybeTransaction = mLocklessTransactionQueue.pop();
+ if (!maybeTransaction.has_value()) {
+ break;
+ }
+ auto transaction = maybeTransaction.value();
+ mPendingTransactionQueues[transaction.applyToken].emplace(std::move(transaction));
+ }
+}
+
+std::vector<TransactionState> TransactionHandler::flushTransactions() {
+ // Collect transaction that are ready to be applied.
+ std::vector<TransactionState> 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<int>(mPendingTransactionCount.load()));
+ return transactions;
+}
+
+void TransactionHandler::applyUnsignaledBufferTransaction(
+ std::vector<TransactionState>& 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);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_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<TransactionState>& transactions,
+ TransactionFlushState& flushState,
+ std::queue<TransactionState>& 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<uint64_t>::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<TransactionState>& 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,
+ StalledTransactionInfo stalledTransactionInfo) {
+ std::lock_guard lock{mStalledMutex};
+ mStalledTransactions.emplace(transactionId, std::move(stalledTransactionInfo));
+}
+
+void TransactionHandler::removeFromStalledTransactions(uint64_t transactionId) {
+ std::lock_guard lock{mStalledMutex};
+ mStalledTransactions.erase(transactionId);
+}
+
+std::optional<TransactionHandler::StalledTransactionInfo>
+TransactionHandler::getStalledTransactionInfo(pid_t pid) {
+ std::lock_guard lock{mStalledMutex};
+ for (auto [_, stalledTransactionInfo] : mStalledTransactions) {
+ if (pid == stalledTransactionInfo.pid) {
+ return stalledTransactionInfo;
+ }
+ }
+ return std::nullopt;
+}
+
+void TransactionHandler::onLayerDestroyed(uint32_t layerId) {
+ std::lock_guard lock{mStalledMutex};
+ for (auto it = mStalledTransactions.begin(); it != mStalledTransactions.end();) {
+ if (it->second.layerId == layerId) {
+ it = mStalledTransactions.erase(it);
+ } else {
+ it++;
+ }
+ }
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/TransactionHandler.h b/services/surfaceflinger/FrontEnd/TransactionHandler.h
new file mode 100644
index 0000000000..00f6bcebe6
--- /dev/null
+++ b/services/surfaceflinger/FrontEnd/TransactionHandler.h
@@ -0,0 +1,100 @@
+/*
+ * 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 <semaphore.h>
+#include <cstdint>
+#include <optional>
+#include <vector>
+
+#include <LocklessQueue.h>
+#include <TransactionState.h>
+#include <android-base/thread_annotations.h>
+#include <ftl/small_map.h>
+#include <ftl/small_vector.h>
+
+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<IBinder* /* binder address */, uint64_t /* framenumber */, 15>
+ 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<IBinder> 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<TransactionReadiness(const TransactionFlushState&)>;
+
+ bool hasPendingTransactions();
+ // Moves transactions from the lockless queue.
+ void collectTransactions();
+ std::vector<TransactionState> flushTransactions();
+ void addTransactionReadyFilter(TransactionFilter&&);
+ void queueTransaction(TransactionState&&);
+
+ struct StalledTransactionInfo {
+ pid_t pid;
+ uint32_t layerId;
+ std::string layerName;
+ uint64_t bufferId;
+ uint64_t frameNumber;
+ };
+ void onTransactionQueueStalled(uint64_t transactionId, StalledTransactionInfo);
+ void removeFromStalledTransactions(uint64_t transactionId);
+ std::optional<StalledTransactionInfo> getStalledTransactionInfo(pid_t pid);
+ void onLayerDestroyed(uint32_t layerId);
+
+private:
+ // For unit tests
+ friend class ::android::TestableSurfaceFlinger;
+
+ int flushPendingTransactionQueues(std::vector<TransactionState>&, TransactionFlushState&);
+ void applyUnsignaledBufferTransaction(std::vector<TransactionState>&, TransactionFlushState&);
+ void popTransactionFromPending(std::vector<TransactionState>&, TransactionFlushState&,
+ std::queue<TransactionState>&);
+ TransactionReadiness applyFilters(TransactionFlushState&);
+ std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
+ mPendingTransactionQueues;
+ LocklessQueue<TransactionState> mLocklessTransactionQueue;
+ std::atomic<size_t> mPendingTransactionCount = 0;
+ ftl::SmallVector<TransactionFilter, 2> mTransactionReadyFilters;
+
+ std::mutex mStalledMutex;
+ std::unordered_map<uint64_t /* transactionId */, StalledTransactionInfo> mStalledTransactions
+ GUARDED_BY(mStalledMutex);
+};
+} // namespace surfaceflinger::frontend
+} // namespace android
diff --git a/services/surfaceflinger/FrontEnd/Update.h b/services/surfaceflinger/FrontEnd/Update.h
new file mode 100644
index 0000000000..e5cca8fa95
--- /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 <gui/DisplayInfo.h>
+
+#include "FrontEnd/LayerCreationArgs.h"
+#include "RequestedLayerState.h"
+#include "TransactionState.h"
+
+namespace android {
+struct LayerCreatedState {
+ LayerCreatedState(const wp<Layer>& layer, const wp<Layer>& parent, bool addToRoot)
+ : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
+ wp<Layer> 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<Layer> 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<TransactionState> transactions;
+ std::vector<LayerCreatedState> layerCreatedStates;
+ std::vector<std::unique_ptr<frontend::RequestedLayerState>> newLayers;
+ std::vector<LayerCreationArgs> layerCreationArgs;
+ std::vector<std::pair<uint32_t, std::string /* debugName */>> destroyedHandles;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/FrontEnd/readme.md b/services/surfaceflinger/FrontEnd/readme.md
new file mode 100644
index 0000000000..e5f51a5773
--- /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)`
+
+<p>
+
+`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 c06e300cdc..278833244c 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.cpp
+++ b/services/surfaceflinger/HdrLayerInfoReporter.cpp
@@ -18,14 +18,22 @@
#define LOG_TAG "HdrLayerInfoReporter"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <android-base/stringprintf.h>
+#include <inttypes.h>
#include <utils/Trace.h>
#include "HdrLayerInfoReporter.h"
namespace android {
+using base::StringAppendF;
+
void HdrLayerInfoReporter::dispatchHdrLayerInfo(const HdrLayerInfo& info) {
ATRACE_CALL();
+ if (mHdrInfoHistory.size() == 0 || mHdrInfoHistory.back().info != info) {
+ mHdrInfoHistory.next() = EventHistoryEntry{info};
+ }
+
std::vector<sp<gui::IHdrLayerInfoListener>> toInvoke;
{
std::scoped_lock lock(mMutex);
@@ -40,7 +48,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 +60,7 @@ void HdrLayerInfoReporter::binderDied(const wp<IBinder>& who) {
void HdrLayerInfoReporter::addListener(const sp<gui::IHdrLayerInfoListener>& listener) {
sp<IBinder> asBinder = IInterface::asBinder(listener);
- asBinder->linkToDeath(this);
+ asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
std::lock_guard lock(mMutex);
mListeners.emplace(wp<IBinder>(asBinder), TrackedListener{listener, HdrLayerInfo{}});
}
@@ -61,4 +70,15 @@ void HdrLayerInfoReporter::removeListener(const sp<gui::IHdrLayerInfoListener>&
mListeners.erase(wp<IBinder>(IInterface::asBinder(listener)));
}
+void HdrLayerInfoReporter::dump(std::string& result) const {
+ for (size_t i = 0; i < mHdrInfoHistory.size(); i++) {
+ const auto& event = mHdrInfoHistory[i];
+ const auto& info = event.info;
+ StringAppendF(&result,
+ "%" PRId64 ": numHdrLayers(%d), size(%dx%d), flags(%X), desiredRatio(%.2f)\n",
+ event.timestamp, info.numberOfHdrLayers, info.maxW, info.maxH, info.flags,
+ info.maxDesiredHdrSdrRatio);
+ }
+}
+
} // namespace android \ No newline at end of file
diff --git a/services/surfaceflinger/HdrLayerInfoReporter.h b/services/surfaceflinger/HdrLayerInfoReporter.h
index 4ada2b6372..614f33fbce 100644
--- a/services/surfaceflinger/HdrLayerInfoReporter.h
+++ b/services/surfaceflinger/HdrLayerInfoReporter.h
@@ -19,9 +19,11 @@
#include <android-base/thread_annotations.h>
#include <android/gui/IHdrLayerInfoListener.h>
#include <binder/IBinder.h>
+#include <utils/Timers.h>
#include <unordered_map>
+#include "Utils/RingBuffer.h"
#include "WpHash.h"
namespace android {
@@ -33,13 +35,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;
@@ -63,6 +81,8 @@ public:
return !mListeners.empty();
}
+ void dump(std::string& result) const;
+
private:
mutable std::mutex mMutex;
@@ -72,6 +92,17 @@ private:
};
std::unordered_map<wp<IBinder>, TrackedListener, WpHash> mListeners GUARDED_BY(mMutex);
+
+ struct EventHistoryEntry {
+ nsecs_t timestamp = -1;
+ HdrLayerInfo info;
+
+ EventHistoryEntry() {}
+
+ EventHistoryEntry(const HdrLayerInfo& info) : info(info) { timestamp = systemTime(); }
+ };
+
+ utils::RingBuffer<EventHistoryEntry, 32> mHdrInfoHistory;
};
} // namespace android \ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.cpp b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
new file mode 100644
index 0000000000..dfb1c1e251
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.cpp
@@ -0,0 +1,202 @@
+/**
+ * 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_NDEBUG 0
+#include <algorithm>
+
+#include "HdrSdrRatioOverlay.h"
+
+#include <SkSurface.h>
+
+#undef LOG_TAG
+#define LOG_TAG "HdrSdrRatioOverlay"
+
+namespace android {
+
+void HdrSdrRatioOverlay::drawNumber(float number, int left, SkColor color, SkCanvas& canvas) {
+ if (!isfinite(number) || number >= 10.f) return;
+ // We assume that the number range is [1.f, 10.f)
+ // and the decimal places are 2.
+ int value = static_cast<int>(number * 100);
+ SegmentDrawer::drawDigit(value / 100, left, color, canvas);
+
+ left += kDigitWidth + kDigitSpace;
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::DecimalPoint, left, color, canvas);
+ left += kDigitWidth + kDigitSpace;
+
+ SegmentDrawer::drawDigit((value / 10) % 10, left, color, canvas);
+ left += kDigitWidth + kDigitSpace;
+ SegmentDrawer::drawDigit(value % 10, left, color, canvas);
+}
+
+sp<GraphicBuffer> HdrSdrRatioOverlay::draw(float currentHdrSdrRatio, SkColor color,
+ ui::Transform::RotationFlags rotation,
+ sp<GraphicBuffer>& ringBuffer) {
+ const int32_t bufferWidth = kBufferWidth;
+ const int32_t bufferHeight = kBufferWidth;
+
+ const auto kUsageFlags = static_cast<uint64_t>(
+ GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_TEXTURE);
+
+ // ring buffers here to do double-buffered rendering to avoid
+ // possible tearing and also to reduce memory take-up.
+ if (ringBuffer == nullptr) {
+ ringBuffer = sp<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+ static_cast<uint32_t>(bufferHeight),
+ HAL_PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags,
+ "HdrSdrRatioOverlayBuffer");
+ }
+
+ auto& buffer = ringBuffer;
+
+ SkMatrix canvasTransform = SkMatrix();
+ switch (rotation) {
+ case ui::Transform::ROT_90:
+ canvasTransform.setTranslate(bufferHeight, 0);
+ canvasTransform.preRotate(90.f);
+ break;
+ case ui::Transform::ROT_270:
+ canvasTransform.setRotate(270.f, bufferWidth / 2.f, bufferWidth / 2.f);
+ break;
+ default:
+ break;
+ }
+
+ const status_t bufferStatus = buffer->initCheck();
+ LOG_ALWAYS_FATAL_IF(bufferStatus != OK, "HdrSdrRatioOverlay: Buffer failed to allocate: %d",
+ bufferStatus);
+
+ sk_sp<SkSurface> surface =
+ SkSurfaces::Raster(SkImageInfo::MakeN32Premul(bufferWidth, bufferHeight));
+ SkCanvas* canvas = surface->getCanvas();
+ canvas->setMatrix(canvasTransform);
+
+ drawNumber(currentHdrSdrRatio, 0, color, *canvas);
+
+ void* pixels = nullptr;
+ buffer->lock(GRALLOC_USAGE_SW_WRITE_RARELY, reinterpret_cast<void**>(&pixels));
+
+ const SkImageInfo& imageInfo = surface->imageInfo();
+ const size_t dstRowBytes = buffer->getStride() * static_cast<size_t>(imageInfo.bytesPerPixel());
+
+ canvas->readPixels(imageInfo, pixels, dstRowBytes, 0, 0);
+ buffer->unlock();
+ return buffer;
+}
+
+std::unique_ptr<HdrSdrRatioOverlay> HdrSdrRatioOverlay::create() {
+ std::unique_ptr<HdrSdrRatioOverlay> overlay =
+ std::make_unique<HdrSdrRatioOverlay>(ConstructorTag{});
+ if (overlay->initCheck()) {
+ return overlay;
+ }
+
+ ALOGE("%s: Failed to create HdrSdrRatioOverlay", __func__);
+ return {};
+}
+
+HdrSdrRatioOverlay::HdrSdrRatioOverlay(ConstructorTag)
+ : mSurfaceControl(
+ SurfaceControlHolder::createSurfaceControlHolder(String8("HdrSdrRatioOverlay"))) {
+ if (!mSurfaceControl) {
+ ALOGE("%s: Failed to create buffer state layer", __func__);
+ return;
+ }
+ SurfaceComposerClient::Transaction()
+ .setLayer(mSurfaceControl->get(), INT32_MAX - 2)
+ .setTrustedOverlay(mSurfaceControl->get(), true)
+ .apply();
+}
+
+bool HdrSdrRatioOverlay::initCheck() const {
+ return mSurfaceControl != nullptr;
+}
+
+void HdrSdrRatioOverlay::changeHdrSdrRatio(float currentHdrSdrRatio) {
+ mCurrentHdrSdrRatio = currentHdrSdrRatio;
+ animate();
+}
+
+void HdrSdrRatioOverlay::setLayerStack(ui::LayerStack stack) {
+ SurfaceComposerClient::Transaction().setLayerStack(mSurfaceControl->get(), stack).apply();
+}
+
+void HdrSdrRatioOverlay::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((5 * width) >> 4, height >> 5);
+ // set the ratio frame to the top right of the screen
+ frame.offsetBy(viewport.width - frame.width(), height >> 4);
+
+ SurfaceComposerClient::Transaction()
+ .setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
+ 0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
+ .setPosition(mSurfaceControl->get(), frame.left, frame.top)
+ .apply();
+}
+
+auto HdrSdrRatioOverlay::getOrCreateBuffers(float currentHdrSdrRatio) -> const sp<GraphicBuffer> {
+ static const sp<GraphicBuffer> kNoBuffer;
+ if (!mSurfaceControl) return kNoBuffer;
+
+ const auto transformHint =
+ static_cast<ui::Transform::RotationFlags>(mSurfaceControl->get()->getTransformHint());
+
+ // Tell SurfaceFlinger about the pre-rotation on the buffer.
+ const auto transform = [&] {
+ switch (transformHint) {
+ case ui::Transform::ROT_90:
+ return ui::Transform::ROT_270;
+ case ui::Transform::ROT_270:
+ return ui::Transform::ROT_90;
+ default:
+ return ui::Transform::ROT_0;
+ }
+ }();
+
+ SurfaceComposerClient::Transaction().setTransform(mSurfaceControl->get(), transform).apply();
+
+ constexpr SkColor kMinRatioColor = SK_ColorBLUE;
+ constexpr SkColor kMaxRatioColor = SK_ColorGREEN;
+ constexpr float kAlpha = 0.8f;
+
+ // 9.f is picked here as ratio range, given that we assume that
+ // hdr/sdr ratio is [1.f, 10.f)
+ const float scale = currentHdrSdrRatio / 9.f;
+
+ SkColor4f colorBase = SkColor4f::FromColor(kMaxRatioColor) * scale;
+ const SkColor4f minRatioColor = SkColor4f::FromColor(kMinRatioColor) * (1 - scale);
+
+ colorBase.fR = colorBase.fR + minRatioColor.fR;
+ colorBase.fG = colorBase.fG + minRatioColor.fG;
+ colorBase.fB = colorBase.fB + minRatioColor.fB;
+ colorBase.fA = kAlpha;
+
+ const SkColor color = colorBase.toSkColor();
+
+ auto buffer = draw(currentHdrSdrRatio, color, transformHint, mRingBuffer[mIndex]);
+ mIndex = (mIndex + 1) % 2;
+ return buffer;
+}
+
+void HdrSdrRatioOverlay::animate() {
+ if (!std::isfinite(mCurrentHdrSdrRatio) || mCurrentHdrSdrRatio < 1.0f) return;
+ SurfaceComposerClient::Transaction()
+ .setBuffer(mSurfaceControl->get(), getOrCreateBuffers(mCurrentHdrSdrRatio))
+ .apply();
+}
+
+} // namespace android \ No newline at end of file
diff --git a/services/surfaceflinger/HdrSdrRatioOverlay.h b/services/surfaceflinger/HdrSdrRatioOverlay.h
new file mode 100644
index 0000000000..72d401d444
--- /dev/null
+++ b/services/surfaceflinger/HdrSdrRatioOverlay.h
@@ -0,0 +1,57 @@
+/**
+ * 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 "Utils/OverlayUtils.h"
+
+#include <ui/Size.h>
+#include <utils/StrongPointer.h>
+
+class SkCanvas;
+
+namespace android {
+class HdrSdrRatioOverlay {
+private:
+ // Effectively making the constructor private, while keeping std::make_unique work
+ struct ConstructorTag {};
+
+public:
+ static std::unique_ptr<HdrSdrRatioOverlay> create();
+
+ void setLayerStack(ui::LayerStack);
+ void setViewport(ui::Size);
+ void animate();
+ void changeHdrSdrRatio(float currentRatio);
+
+ HdrSdrRatioOverlay(ConstructorTag);
+
+private:
+ bool initCheck() const;
+
+ static sp<GraphicBuffer> draw(float currentHdrSdrRatio, SkColor, ui::Transform::RotationFlags,
+ sp<GraphicBuffer>& ringBufer);
+ static void drawNumber(float number, int left, SkColor, SkCanvas&);
+
+ const sp<GraphicBuffer> getOrCreateBuffers(float currentHdrSdrRatio);
+
+ float mCurrentHdrSdrRatio = 1.f;
+ const std::unique_ptr<SurfaceControlHolder> mSurfaceControl;
+
+ size_t mIndex = 0;
+ std::array<sp<GraphicBuffer>, 2> mRingBuffer;
+};
+} // namespace android
diff --git a/services/surfaceflinger/Layer.cpp b/services/surfaceflinger/Layer.cpp
index 0c4b012fe8..c8b1059b94 100644
--- a/services/surfaceflinger/Layer.cpp
+++ b/services/surfaceflinger/Layer.cpp
@@ -27,8 +27,8 @@
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
-#include <android/native_window.h>
#include <binder/IPCThreadState.h>
+#include <compositionengine/CompositionEngine.h>
#include <compositionengine/Display.h>
#include <compositionengine/LayerFECompositionState.h>
#include <compositionengine/OutputLayer.h>
@@ -41,6 +41,7 @@
#include <gui/BufferItem.h>
#include <gui/LayerDebugInfo.h>
#include <gui/Surface.h>
+#include <gui/TraceUtils.h>
#include <math.h>
#include <private/android_filesystem_config.h>
#include <renderengine/RenderEngine.h>
@@ -48,10 +49,13 @@
#include <stdlib.h>
#include <sys/types.h>
#include <system/graphics-base-v1.0.h>
-#include <ui/DataspaceUtils.h>
#include <ui/DebugUtils.h>
+#include <ui/FloatRect.h>
#include <ui/GraphicBuffer.h>
+#include <ui/HdrRenderTypeUtils.h>
#include <ui/PixelFormat.h>
+#include <ui/Rect.h>
+#include <ui/Transform.h>
#include <utils/Errors.h>
#include <utils/Log.h>
#include <utils/NativeHandle.h>
@@ -60,74 +64,117 @@
#include <algorithm>
#include <mutex>
+#include <optional>
#include <sstream>
-#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.vote.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.vote.seamlessness) {
+ case scheduler::Seamlessness::OnlySeamless:
+ return Seamlessness::ShouldBeSeamless;
+ case scheduler::Seamlessness::SeamedAndSeamless:
+ return Seamlessness::NotRequired;
+ default:
+ return Seamlessness::Undefined;
+ }
+ }();
+
+ return TimeStats::SetFrameRateVote{.frameRate = frameRate.vote.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 ui::Size;
using PresentState = frametimeline::SurfaceFrame::PresentState;
-std::atomic<int32_t> Layer::sSequence{1};
-
-Layer::Layer(const LayerCreationArgs& args)
- : sequence(args.sequence.value_or(sSequence++)),
- mFlinger(args.flinger),
+Layer::Layer(const surfaceflinger::LayerCreationArgs& args)
+ : sequence(args.sequence),
+ mFlinger(sp<SurfaceFlinger>::fromExisting(args.flinger)),
mName(base::StringPrintf("%s#%d", args.name.c_str(), sequence)),
mClientRef(args.client),
- mWindowType(static_cast<WindowInfo::Type>(args.metadata.getInt32(METADATA_WINDOW_TYPE, 0))),
- mLayerCreationFlags(args.flags) {
+ mWindowType(static_cast<WindowInfo::Type>(
+ args.metadata.getInt32(gui::METADATA_WINDOW_TYPE, 0))),
+ mLayerCreationFlags(args.flags),
+ mBorderEnabled(false),
+ 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.previousFrameNumber = 0;
+ mDrawingState.barrierFrameNumber = 0;
+ mDrawingState.producerId = 0;
+ mDrawingState.barrierProducerId = 0;
mDrawingState.bufferTransform = 0;
mDrawingState.transformToDisplayInverse = false;
- mDrawingState.crop.makeInvalid();
mDrawingState.acquireFence = sp<Fence>::make(-1);
mDrawingState.acquireFenceTime = std::make_shared<FenceTime>(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;
@@ -145,6 +192,8 @@ Layer::Layer(const LayerCreationArgs& args)
mDrawingState.isTrustedOverlay = false;
mDrawingState.dropInputMode = gui::DropInputMode::NONE;
mDrawingState.dimmingEnabled = true;
+ mDrawingState.defaultFrameRateCompatibility = FrameRateCompatibility::Default;
+ mDrawingState.frameRateSelectionStrategy = FrameRateSelectionStrategy::Propagate;
if (args.flags & ISurfaceComposerClient::eNoColorFill) {
// Set an invalid color so there is no color fill.
@@ -152,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;
+ mOwnerAppId = mOwnerUid % PER_USER_RANGE;
+
+ mPremultipliedAlpha = !(args.flags & ISurfaceComposerClient::eNonPremultiplied);
+ mPotentialCursor = args.flags & ISurfaceComposerClient::eCursorWindow;
+ mProtectedByApp = args.flags & ISurfaceComposerClient::eProtectedByApp;
+
+ mSnapshot->sequence = sequence;
+ mSnapshot->name = getDebugName();
+ mSnapshot->premultipliedAlpha = mPremultipliedAlpha;
+ mSnapshot->parentTransform = {};
}
void Layer::onFirstRef() {
@@ -175,10 +224,21 @@ void Layer::onFirstRef() {
}
Layer::~Layer() {
- sp<Client> 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);
}
+ const int32_t layerId = getSequence();
+ mFlinger->mTimeStats->onDestroy(layerId);
+ mFlinger->mFrameTracer->onDestroy(layerId);
mFrameTracker.logAndResetStats(mName);
mFlinger->onLayerDestroyed(this);
@@ -187,33 +247,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> 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<FenceResult>) {}
-
void Layer::removeRelativeZ(const std::vector<Layer*>& layersInTree) {
if (mDrawingState.zOrderRelativeOf == nullptr) {
return;
@@ -226,7 +272,7 @@ void Layer::removeRelativeZ(const std::vector<Layer*>& layersInTree) {
}
if (!std::binary_search(layersInTree.begin(), layersInTree.end(), strongRelative.get())) {
- strongRelative->removeZOrderRelative(this);
+ strongRelative->removeZOrderRelative(wp<Layer>::fromExisting(this));
mFlinger->setTransactionFlags(eTraversalNeeded);
setZOrderRelativeOf(nullptr);
}
@@ -237,14 +283,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<Layer>::fromExisting(this));
}
sp<Layer> Layer::getRootLayer() {
sp<Layer> parent = getParent();
if (parent == nullptr) {
- return this;
+ return sp<Layer>::fromExisting(this);
}
return parent->getRootLayer();
}
@@ -254,10 +301,12 @@ void Layer::onRemovedFromCurrentState() {
auto layersInTree = getRootLayer()->getLayersInTree(LayerVector::StateSet::Current);
std::sort(layersInTree.begin(), layersInTree.end());
- traverse(LayerVector::StateSet::Current, [&](Layer* layer) {
- layer->removeFromCurrentState();
- layer->removeRelativeZ(layersInTree);
- });
+ REQUIRE_MUTEX(mFlinger->mStateLock);
+ traverse(LayerVector::StateSet::Current,
+ [&](Layer* layer) REQUIRES(layer->mFlinger->mStateLock) {
+ layer->removeFromCurrentState();
+ layer->removeRelativeZ(layersInTree);
+ });
}
void Layer::addToCurrentState() {
@@ -287,7 +336,8 @@ sp<IBinder> Layer::getHandle() {
return nullptr;
}
mGetHandleCalled = true;
- return new Handle(mFlinger, this);
+ mHandleAlive = true;
+ return sp<LayerHandle>::make(mFlinger, sp<Layer>::fromExisting(this));
}
// ---------------------------------------------------------------------------
@@ -334,6 +384,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, snapshot->path);
+ 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());
@@ -379,6 +530,10 @@ void Layer::computeBounds(FloatRect parentBounds, ui::Transform parentTransform,
for (const sp<Layer>& child : mDrawingChildren) {
child->computeBounds(mBounds, mEffectiveTransform, childShadowRadius);
}
+
+ if (mPotentialCursor) {
+ prepareCursorCompositionState();
+ }
}
Rect Layer::getCroppedBufferSize(const State& s) const {
@@ -406,7 +561,7 @@ void Layer::prepareBasicGeometryCompositionState() {
const auto& drawingState{getDrawingState()};
const auto alpha = static_cast<float>(getAlpha());
const bool opaque = isOpaque(drawingState);
- const bool usesRoundedCorners = getRoundedCornerState().radius != 0.f;
+ const bool usesRoundedCorners = hasRoundedCorners();
auto blendMode = Hwc2::IComposerClient::BlendMode::NONE;
if (!opaque || alpha != 1.0f) {
@@ -414,40 +569,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->shadowSettings.length = 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<Hwc2::IComposerClient::BlendMode>(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<Hwc2::IComposerClient::BlendMode>(blendMode);
+ snapshot->alpha = alpha;
+ snapshot->backgroundBlurRadius = getBackgroundBlurRadius();
+ snapshot->blurRegions = getBlurRegions();
+ 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 =
@@ -463,50 +621,96 @@ 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();
+ // Please keep in sync with LayerSnapshotBuilder
+ auto* snapshot = editLayerSnapshot();
- compositionState->forceClientComposition = false;
+ snapshot->forceClientComposition = false;
- compositionState->isColorspaceAgnostic = isColorSpaceAgnostic();
- compositionState->dataspace = getDataSpace();
- compositionState->colorTransform = getColorTransform();
- compositionState->colorTransformIsIdentity = !hasColorTransform();
- compositionState->surfaceDamage = surfaceDamageRegion;
- compositionState->hasProtectedContent = isProtected();
- compositionState->dimmingEnabled = isDimmingEnabled();
+ 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 = getRoundedCornerState().radius != 0.f;
+ 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;
+ if (drawShadows() || 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 = getBlurRegions();
+ snapshot->backgroundBlurRadius = getBackgroundBlurRadius();
// 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
@@ -515,52 +719,7 @@ void Layer::prepareCursorCompositionState() {
Rect bounds = reduce(win, getActiveTransparentRegion(drawingState));
Rect frame(getTransform().transform(bounds));
- compositionState->cursorFrame = frame;
-}
-
-sp<compositionengine::LayerFE> Layer::asLayerFE() const {
- return const_cast<compositionengine::LayerFE*>(
- static_cast<const compositionengine::LayerFE*>(this));
-}
-
-sp<compositionengine::LayerFE> 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 {
@@ -571,112 +730,14 @@ const char* Layer::getDebugName() const {
// drawing...
// ---------------------------------------------------------------------------
-std::optional<compositionengine::LayerFE::LayerSettings> 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<ui::Dataspace>(
- (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<compositionengine::LayerFE::LayerSettings> Layer::prepareClientCompositionList(
- compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) {
- std::optional<compositionengine::LayerFE::LayerSettings> 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);
+ return getCompositionType(outputLayer);
+}
+
+aidl::android::hardware::graphics::composer3::Composition Layer::getCompositionType(
+ const compositionengine::OutputLayer* outputLayer) const {
if (outputLayer == nullptr) {
return aidl::android::hardware::graphics::composer3::Composition::INVALID;
}
@@ -701,6 +762,41 @@ bool Layer::isSecure() const {
return (p != nullptr) ? p->isSecure() : false;
}
+void Layer::transferAvailableJankData(const std::deque<sp<CallbackHandle>>& handles,
+ std::vector<JankData>& 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<frametimeline::SurfaceFrame> surfaceFrame =
+ mPendingJankClassifications.front();
+ jankData.emplace_back(JankData(surfaceFrame->getToken(),
+ surfaceFrame->getJankType().value(),
+ surfaceFrame->getRenderRate().getPeriodNsecs()));
+ }
+ mPendingJankClassifications.pop_front();
+ }
+}
+
// ----------------------------------------------------------------------------
// transaction
// ----------------------------------------------------------------------------
@@ -721,7 +817,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;
@@ -729,19 +825,23 @@ uint32_t Layer::doTransaction(uint32_t flags) {
mNeedsFiltering = getActiveTransform(s).needsBilinearFiltering();
}
- commitTransaction(mDrawingState);
+ if (!mPotentialCursor && (flags & Layer::eVisibleRegion)) {
+ mFlinger->mUpdateInputInfo = true;
+ }
+
+ commitTransaction();
return flags;
}
-void Layer::commitTransaction(State&) {
+void Layer::commitTransaction() {
// Set the present state for all bufferlessSurfaceFramesTX to Presented. The
// bufferSurfaceFrameTX will be presented in latchBuffer.
for (auto& [token, surfaceFrame] : mDrawingState.bufferlessSurfaceFramesTX) {
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);
}
}
@@ -758,16 +858,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<Layer>& childLayer, int32_t z) {
ssize_t idx = mCurrentChildren.indexOf(childLayer);
if (idx < 0) {
@@ -807,7 +897,7 @@ bool Layer::setLayer(int32_t z) {
if (mDrawingState.zOrderRelativeOf != nullptr) {
sp<Layer> strongRelative = mDrawingState.zOrderRelativeOf.promote();
if (strongRelative != nullptr) {
- strongRelative->removeZOrderRelative(this);
+ strongRelative->removeZOrderRelative(wp<Layer>::fromExisting(this));
}
setZOrderRelativeOf(nullptr);
}
@@ -839,7 +929,7 @@ void Layer::setZOrderRelativeOf(const wp<Layer>& relativeOf) {
}
bool Layer::setRelativeLayer(const sp<IBinder>& relativeToHandle, int32_t relativeZ) {
- sp<Layer> relative = fromHandle(relativeToHandle).promote();
+ sp<Layer> relative = LayerHandle::getLayer(relativeToHandle);
if (relative == nullptr) {
return false;
}
@@ -865,10 +955,10 @@ bool Layer::setRelativeLayer(const sp<IBinder>& relativeToHandle, int32_t relati
auto oldZOrderRelativeOf = mDrawingState.zOrderRelativeOf.promote();
if (oldZOrderRelativeOf != nullptr) {
- oldZOrderRelativeOf->removeZOrderRelative(this);
+ oldZOrderRelativeOf->removeZOrderRelative(wp<Layer>::fromExisting(this));
}
setZOrderRelativeOf(relative);
- relative->addZOrderRelative(this);
+ relative->addZOrderRelative(wp<Layer>::fromExisting(this));
setTransactionFlags(eTransactionNeeded);
@@ -879,7 +969,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;
}
@@ -892,20 +982,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++;
@@ -928,18 +1004,20 @@ bool Layer::setBackgroundColor(const half3& color, float alpha, ui::Dataspace da
uint32_t flags = ISurfaceComposerClient::eFXSurfaceEffect;
std::string name = mName + "BackgroundColorLayer";
mDrawingState.bgColorLayer = mFlinger->getFactory().createEffectLayer(
- LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), flags,
- LayerMetadata()));
+ surfaceflinger::LayerCreationArgs(mFlinger.get(), nullptr, std::move(name), flags,
+ LayerMetadata()));
// add to child list
addChild(mDrawingState.bgColorLayer);
mFlinger->mLayersAdded = true;
// set up SF to handle added color layer
if (isRemovedFromCurrentState()) {
+ MUTEX_ALIAS(mFlinger->mStateLock, mDrawingState.bgColorLayer->mFlinger->mStateLock);
mDrawingState.bgColorLayer->onRemovedFromCurrentState();
}
mFlinger->setTransactionFlags(eTransactionNeeded);
} else if (mDrawingState.bgColorLayer && alpha == 0) {
+ MUTEX_ALIAS(mFlinger->mStateLock, mDrawingState.bgColorLayer->mFlinger->mStateLock);
mDrawingState.bgColorLayer->reparent(nullptr);
mDrawingState.bgColorLayer = nullptr;
return true;
@@ -975,20 +1053,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;
@@ -1017,9 +1085,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;
@@ -1087,12 +1154,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(sequence, compatibility);
+ setTransactionFlags(eTransactionNeeded);
+ return true;
+}
+
+scheduler::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;
@@ -1151,49 +1233,81 @@ StretchEffect Layer::getStretchEffect() const {
return StretchEffect{};
}
-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
+bool Layer::enableBorder(bool shouldEnable, float width, const half4& color) {
+ if (mBorderEnabled == shouldEnable && mBorderWidth == width && mBorderColor == color) {
+ return false;
+ }
+ mBorderEnabled = shouldEnable;
+ mBorderWidth = width;
+ mBorderColor = color;
+ return true;
+}
+
+bool Layer::isBorderEnabled() {
+ return mBorderEnabled;
+}
+
+float Layer::getBorderWidth() {
+ return mBorderWidth;
+}
+
+const half4& Layer::getBorderColor() {
+ return mBorderColor;
+}
+
+bool Layer::propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+ bool* transactionNeeded) {
+ // Gets the frame rate to propagate to children.
const auto frameRate = [&] {
- if (mDrawingState.frameRate.rate.isValid() ||
- mDrawingState.frameRate.type == FrameRateCompatibility::NoVote) {
+ if (overrideChildren && parentFrameRate.isValid()) {
+ return parentFrameRate;
+ }
+
+ if (mDrawingState.frameRate.isValid()) {
return mDrawingState.frameRate;
}
return parentFrameRate;
}();
- *transactionNeeded |= setFrameRateForLayerTree(frameRate);
+ auto now = systemTime();
+ *transactionNeeded |= setFrameRateForLayerTreeLegacy(frameRate, now);
- // The frame rate is propagated to the children
+ // The frame rate is propagated to the children by default, but some properties may override it.
bool childrenHaveFrameRate = false;
+ const bool overrideChildrenFrameRate = overrideChildren || shouldOverrideChildrenFrameRate();
+ const bool canPropagateFrameRate = shouldPropagateFrameRate() || overrideChildrenFrameRate;
for (const sp<Layer>& child : mCurrentChildren) {
childrenHaveFrameRate |=
- child->propagateFrameRateForLayerTree(frameRate, transactionNeeded);
+ child->propagateFrameRateForLayerTree(canPropagateFrameRate ? frameRate
+ : FrameRate(),
+ overrideChildrenFrameRate, transactionNeeded);
}
- // If we don't have a valid frame rate, but the children do, we set this
+ // If we don't have a valid frame rate specification, but the children do, we set this
// layer as NoVote to allow the children to control the refresh rate
- if (!frameRate.rate.isValid() && frameRate.type != FrameRateCompatibility::NoVote &&
- childrenHaveFrameRate) {
+ if (!frameRate.isValid() && childrenHaveFrameRate) {
*transactionNeeded |=
- setFrameRateForLayerTree(FrameRate(Fps(), FrameRateCompatibility::NoVote));
+ setFrameRateForLayerTreeLegacy(FrameRate(Fps(), FrameRateCompatibility::NoVote),
+ now);
}
- // We return whether this layer ot its children has a vote. We ignore ExactOrMultiple votes for
+ // We return whether this layer or 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;
+ frameRate.vote.rate.isValid() && frameRate.vote.type == FrameRateCompatibility::Default;
+ const auto layerVotedWithNoVote = frameRate.vote.type == FrameRateCompatibility::NoVote;
+ const auto layerVotedWithCategory = frameRate.category != FrameRateCategory::Default;
const auto layerVotedWithExactCompatibility =
- frameRate.rate.isValid() && frameRate.type == FrameRateCompatibility::Exact;
- return layerVotedWithDefaultCompatibility || layerVotedWithNoVote ||
+ frameRate.vote.rate.isValid() && frameRate.vote.type == FrameRateCompatibility::Exact;
+ return layerVotedWithDefaultCompatibility || layerVotedWithNoVote || layerVotedWithCategory ||
layerVotedWithExactCompatibility || childrenHaveFrameRate;
}
void Layer::updateTreeHasFrameRateVote() {
const auto root = [&]() -> sp<Layer> {
- sp<Layer> layer = this;
+ sp<Layer> layer = sp<Layer>::fromExisting(this);
while (auto parent = layer->getParent()) {
layer = parent;
}
@@ -1201,7 +1315,7 @@ void Layer::updateTreeHasFrameRateVote() {
}();
bool transactionNeeded = false;
- root->propagateFrameRateForLayerTree({}, &transactionNeeded);
+ root->propagateFrameRateForLayerTree({}, false, &transactionNeeded);
// TODO(b/195668952): we probably don't need eTraversalNeeded here
if (transactionNeeded) {
@@ -1209,13 +1323,30 @@ void Layer::updateTreeHasFrameRateVote() {
}
}
-bool Layer::setFrameRate(FrameRate frameRate) {
- if (mDrawingState.frameRate == frameRate) {
+bool Layer::setFrameRate(FrameRate::FrameRateVote frameRateVote) {
+ if (mDrawingState.frameRate.vote == frameRateVote) {
+ return false;
+ }
+
+ mDrawingState.sequence++;
+ mDrawingState.frameRate.vote = frameRateVote;
+ mDrawingState.modified = true;
+
+ updateTreeHasFrameRateVote();
+
+ setTransactionFlags(eTransactionNeeded);
+ return true;
+}
+
+bool Layer::setFrameRateCategory(FrameRateCategory category, bool smoothSwitchOnly) {
+ if (mDrawingState.frameRate.category == category &&
+ mDrawingState.frameRate.categorySmoothSwitchOnly == smoothSwitchOnly) {
return false;
}
mDrawingState.sequence++;
- mDrawingState.frameRate = frameRate;
+ mDrawingState.frameRate.category = category;
+ mDrawingState.frameRate.categorySmoothSwitchOnly = smoothSwitchOnly;
mDrawingState.modified = true;
updateTreeHasFrameRateVote();
@@ -1224,6 +1355,17 @@ bool Layer::setFrameRate(FrameRate frameRate) {
return true;
}
+bool Layer::setFrameRateSelectionStrategy(FrameRateSelectionStrategy strategy) {
+ if (mDrawingState.frameRateSelectionStrategy == strategy) return false;
+ mDrawingState.frameRateSelectionStrategy = strategy;
+ mDrawingState.sequence++;
+ mDrawingState.modified = true;
+
+ updateTreeHasFrameRateVote();
+ setTransactionFlags(eTransactionNeeded);
+ return true;
+}
+
void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info,
nsecs_t postTime) {
mDrawingState.postTime = postTime;
@@ -1243,6 +1385,8 @@ void Layer::setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& i
mDrawingState.bufferSurfaceFrameTX =
createSurfaceFrameForBuffer(info, postTime, mTransactionName);
}
+
+ setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName);
}
void Layer::setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
@@ -1274,11 +1418,13 @@ void Layer::setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInf
it->second = createSurfaceFrameForTransaction(info, postTime);
}
}
+
+ setFrameTimelineVsyncForSkippedFrames(info, postTime, mTransactionName);
}
void Layer::addSurfaceFrameDroppedForBuffer(
- std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame) {
- surfaceFrame->setDropTime(systemTime());
+ std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame, nsecs_t dropTime) {
+ surfaceFrame->setDropTime(dropTime);
surfaceFrame->setPresentState(PresentState::Dropped);
mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame);
}
@@ -1289,7 +1435,7 @@ void Layer::addSurfaceFramePresentedForBuffer(
surfaceFrame->setAcquireFenceTime(acquireFenceTime);
surfaceFrame->setPresentState(PresentState::Presented, mLastLatchTime);
mFlinger->mFrameTimeline->addSurfaceFrame(surfaceFrame);
- mLastLatchTime = currentLatchTime;
+ updateLastLatchTime(currentLatchTime);
}
std::shared_ptr<frametimeline::SurfaceFrame> Layer::createSurfaceFrameForTransaction(
@@ -1324,12 +1470,37 @@ std::shared_ptr<frametimeline::SurfaceFrame> 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) {
+void Layer::setFrameTimelineVsyncForSkippedFrames(const FrameTimelineInfo& info, nsecs_t postTime,
+ std::string debugName) {
+ if (info.skippedFrameVsyncId == FrameTimelineInfo::INVALID_VSYNC_ID) {
+ return;
+ }
+
+ FrameTimelineInfo skippedFrameTimelineInfo = info;
+ skippedFrameTimelineInfo.vsyncId = info.skippedFrameVsyncId;
+
+ auto surfaceFrame =
+ mFlinger->mFrameTimeline->createSurfaceFrameForToken(skippedFrameTimelineInfo,
+ mOwnerPid, mOwnerUid,
+ getSequence(), mName, debugName,
+ /*isBuffer*/ false, getGameMode());
+ surfaceFrame->setActualStartTime(skippedFrameTimelineInfo.skippedFrameStartTimeNanos);
+ // For Transactions, the post time is considered to be both queue and acquire fence time.
+ surfaceFrame->setActualQueueTime(postTime);
+ surfaceFrame->setAcquireFenceTime(postTime);
+ const auto fps = mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
+ if (fps) {
+ surfaceFrame->setRenderRate(*fps);
+ }
+ onSurfaceFrameCreated(surfaceFrame);
+ addSurfaceFrameDroppedForBuffer(surfaceFrame, postTime);
+}
+
+bool Layer::setFrameRateForLayerTreeLegacy(FrameRate frameRate, nsecs_t now) {
if (mDrawingState.frameRateForLayerTree == frameRate) {
return false;
}
@@ -1342,9 +1513,22 @@ 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(), now, now,
+ scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
+ return true;
+}
+bool Layer::setFrameRateForLayerTree(FrameRate frameRate, const scheduler::LayerProps& layerProps,
+ nsecs_t now) {
+ if (mDrawingState.frameRateForLayerTree == frameRate) {
+ return false;
+ }
+
+ mDrawingState.frameRateForLayerTree = frameRate;
+ mFlinger->mScheduler
+ ->recordLayerHistory(sequence, layerProps, now, now,
+ scheduler::LayerHistory::LayerUpdateType::SetFrameRate);
return true;
}
@@ -1386,12 +1570,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);
}
// ----------------------------------------------------------------------------
@@ -1399,16 +1587,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<Layer> 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;
@@ -1416,8 +1603,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;
@@ -1463,7 +1648,7 @@ void Layer::miniDumpHeader(std::string& result) {
result.append("\n");
}
-void Layer::miniDump(std::string& result, const DisplayDevice& display) const {
+void Layer::miniDumpLegacy(std::string& result, const DisplayDevice& display) const {
const auto outputLayer = findOutputLayerForDisplay(&display);
if (!outputLayer) {
return;
@@ -1499,10 +1684,10 @@ void Layer::miniDump(std::string& result, const DisplayDevice& display) const {
StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
crop.bottom);
const auto frameRate = getFrameRateForLayerTree();
- if (frameRate.rate.isValid() || frameRate.type != FrameRateCompatibility::Default) {
- StringAppendF(&result, "%s %15s %17s", to_string(frameRate.rate).c_str(),
- ftl::enum_string(frameRate.type).c_str(),
- ftl::enum_string(frameRate.seamlessness).c_str());
+ if (frameRate.vote.rate.isValid() || frameRate.vote.type != FrameRateCompatibility::Default) {
+ StringAppendF(&result, "%s %15s %17s", to_string(frameRate.vote.rate).c_str(),
+ ftl::enum_string(frameRate.vote.type).c_str(),
+ ftl::enum_string(frameRate.vote.seamlessness).c_str());
} else {
result.append(41, ' ');
}
@@ -1514,6 +1699,49 @@ void Layer::miniDump(std::string& result, const DisplayDevice& display) const {
result.append("\n");
}
+void Layer::miniDump(std::string& result, const frontend::LayerSnapshot& snapshot,
+ const DisplayDevice& display) const {
+ const auto outputLayer = findOutputLayerForDisplay(&display, snapshot.path);
+ if (!outputLayer) {
+ return;
+ }
+
+ StringAppendF(&result, " %s\n", snapshot.debugName.c_str());
+ StringAppendF(&result, " %10zu | ", snapshot.globalZ);
+ StringAppendF(&result, " %10d | ",
+ snapshot.layerMetadata.getInt32(gui::METADATA_WINDOW_TYPE, 0));
+ StringAppendF(&result, "%10s | ", toString(getCompositionType(outputLayer)).c_str());
+ const auto& outputLayerState = outputLayer->getState();
+ StringAppendF(&result, "%10s | ", toString(outputLayerState.bufferTransform).c_str());
+ const Rect& frame = outputLayerState.displayFrame;
+ StringAppendF(&result, "%4d %4d %4d %4d | ", frame.left, frame.top, frame.right, frame.bottom);
+ const FloatRect& crop = outputLayerState.sourceCrop;
+ StringAppendF(&result, "%6.1f %6.1f %6.1f %6.1f | ", crop.left, crop.top, crop.right,
+ crop.bottom);
+ const auto frameRate = snapshot.frameRate;
+ std::string frameRateStr;
+ if (frameRate.vote.rate.isValid()) {
+ StringAppendF(&frameRateStr, "%.2f", frameRate.vote.rate.getValue());
+ }
+ if (frameRate.vote.rate.isValid() || frameRate.vote.type != FrameRateCompatibility::Default) {
+ StringAppendF(&result, "%6s %15s %17s", frameRateStr.c_str(),
+ ftl::enum_string(frameRate.vote.type).c_str(),
+ ftl::enum_string(frameRate.vote.seamlessness).c_str());
+ } else if (frameRate.category != FrameRateCategory::Default) {
+ StringAppendF(&result, "%6s %15s %17s", frameRateStr.c_str(),
+ (std::string("Cat::") + ftl::enum_string(frameRate.category)).c_str(),
+ ftl::enum_string(frameRate.vote.seamlessness).c_str());
+ } else {
+ result.append(41, ' ');
+ }
+
+ const auto focused = isLayerFocusedBasedOnPriority(snapshot.frameRateSelectionPriority);
+ StringAppendF(&result, " [%s]\n", focused ? "*" : " ");
+
+ result.append(kDumpTableRowLength, '-');
+ result.append("\n");
+}
+
void Layer::dumpFrameStats(std::string& result) const {
mFrameTracker.dumpStats(result);
}
@@ -1530,9 +1758,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() {
@@ -1541,9 +1770,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<Layer>& child : mCurrentChildren) {
+ for (const sp<Layer>& child : mDrawingChildren) {
count += 1 + child->getChildrenCount();
}
return count;
@@ -1551,8 +1780,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<GameMode>(currentState.metadata.getInt32(METADATA_GAME_MODE, 0));
+ if (currentState.metadata.has(gui::METADATA_GAME_MODE)) {
+ gameMode =
+ static_cast<GameMode>(currentState.metadata.getInt32(gui::METADATA_GAME_MODE, 0));
}
setGameMode(gameMode);
for (const sp<Layer>& child : mCurrentChildren) {
@@ -1565,7 +1795,7 @@ void Layer::addChild(const sp<Layer>& layer) {
setTransactionFlags(eTransactionNeeded);
mCurrentChildren.add(layer);
- layer->setParent(this);
+ layer->setParent(sp<Layer>::fromExisting(this));
layer->setGameModeForTree(mGameMode);
updateTreeHasFrameRateVote();
}
@@ -1597,7 +1827,7 @@ void Layer::setChildrenDrawingParent(const sp<Layer>& newParent) {
bool Layer::reparent(const sp<IBinder>& newParentHandle) {
sp<Layer> newParent;
if (newParentHandle != nullptr) {
- newParent = fromHandle(newParentHandle).promote();
+ newParent = LayerHandle::getLayer(newParentHandle);
if (newParent == nullptr) {
ALOGE("Unable to promote Layer handle");
return false;
@@ -1610,11 +1840,11 @@ bool Layer::reparent(const sp<IBinder>& newParentHandle) {
sp<Layer> parent = getParent();
if (parent != nullptr) {
- parent->removeChild(this);
+ parent->removeChild(sp<Layer>::fromExisting(this));
}
if (newParentHandle != nullptr) {
- newParent->addChild(this);
+ newParent->addChild(sp<Layer>::fromExisting(this));
if (!newParent->isRemovedFromCurrentState()) {
addToCurrentState();
} else {
@@ -1789,6 +2019,12 @@ void Layer::traverse(LayerVector::StateSet state, const LayerVector::Visitor& vi
}
}
+void Layer::traverseChildren(const LayerVector::Visitor& visitor) {
+ for (const sp<Layer>& child : mDrawingChildren) {
+ visitor(child.get());
+ }
+}
+
LayerVector Layer::makeChildrenTraversalList(LayerVector::StateSet stateSet,
const std::vector<Layer*>& layersInTree) {
LOG_ALWAYS_FATAL_IF(stateSet == LayerVector::StateSet::Invalid,
@@ -1896,8 +2132,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;
}
@@ -1911,33 +2150,35 @@ const std::vector<BlurRegion> Layer::getBlurRegions() const {
return regionsCopy;
}
-Layer::RoundedCornerState Layer::getRoundedCornerState() const {
+RoundedCornerState Layer::getRoundedCornerState() const {
+ // Today's DPUs cannot do rounded corners. If RenderEngine cannot render
+ // protected content, remove rounded corners from protected content so it
+ // can be rendered by the DPU.
+ if (isProtected() && !mFlinger->getRenderEngine().supportsProtectedContent()) {
+ return {};
+ }
+
// Get parent settings
RoundedCornerState parentSettings;
const auto& parent = mDrawingParent.promote();
if (parent != nullptr) {
parentSettings = parent->getRoundedCornerState();
- if (parentSettings.radius > 0) {
+ if (parentSettings.hasRoundedCorners()) {
ui::Transform t = getActiveTransform(getDrawingState());
t = t.inverse();
parentSettings.cropRect = t.transform(parentSettings.cropRect);
- // The rounded corners shader only accepts 1 corner radius for performance reasons,
- // but a transform matrix can define horizontal and vertical scales.
- // Let's take the average between both of them and pass into the shader, practically we
- // never do this type of transformation on windows anyway.
- auto scaleX = sqrtf(t[0][0] * t[0][0] + t[0][1] * t[0][1]);
- auto scaleY = sqrtf(t[1][0] * t[1][0] + t[1][1] * t[1][1]);
- parentSettings.radius *= (scaleX + scaleY) / 2.0f;
+ parentSettings.radius.x *= t.getScaleX();
+ parentSettings.radius.y *= t.getScaleY();
}
}
// Get layer settings
Rect layerCropRect = getCroppedBufferSize(getDrawingState());
- const float radius = getDrawingState().cornerRadius;
+ const vec2 radius(getDrawingState().cornerRadius, getDrawingState().cornerRadius);
RoundedCornerState layerSettings(layerCropRect.toFloatRect(), radius);
- const bool layerSettingsValid = layerSettings.radius > 0 && layerCropRect.isValid();
+ const bool layerSettingsValid = layerSettings.hasRoundedCorners() && layerCropRect.isValid();
- if (layerSettingsValid && parentSettings.radius > 0) {
+ if (layerSettingsValid && parentSettings.hasRoundedCorners()) {
// 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.
@@ -1951,45 +2192,12 @@ Layer::RoundedCornerState Layer::getRoundedCornerState() const {
}
} else if (layerSettingsValid) {
return layerSettings;
- } else if (parentSettings.radius > 0) {
+ } else if (parentSettings.hasRoundedCorners()) {
return parentSettings;
}
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<Layer>& l) {
if (l == this) {
return true;
@@ -2017,7 +2225,7 @@ void Layer::commitChildList() {
zOrderRelativeOf->mName.c_str());
ALOGE("Severing rel Z loop, potentially dangerous");
mDrawingState.isRelativeOf = false;
- zOrderRelativeOf->removeZOrderRelative(this);
+ zOrderRelativeOf->removeZOrderRelative(wp<Layer>::fromExisting(this));
}
}
}
@@ -2025,27 +2233,23 @@ 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);
}
-LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) {
- LayerProto* layerProto = layersProto.add_layers();
+perfetto::protos::LayerProto* Layer::writeToProto(perfetto::protos::LayersProto& layersProto,
+ uint32_t traceFlags) {
+ perfetto::protos::LayerProto* layerProto = layersProto.add_layers();
writeToProtoDrawingState(layerProto);
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<HwcCompositionType>(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>& layer : mDrawingChildren) {
@@ -2055,7 +2259,22 @@ LayerProto* Layer::writeToProto(LayersProto& layersProto, uint32_t traceFlags) {
return layerProto;
}
-void Layer::writeToProtoDrawingState(LayerProto* layerInfo) {
+void Layer::writeCompositionStateToProto(perfetto::protos::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<perfetto::protos::HwcCompositionType>(compositionType));
+ LayerProtoHelper::writeToProto(getVisibleRegion(display),
+ [&]() { return layerProto->mutable_visible_region(); });
+ }
+}
+
+void Layer::writeToProtoDrawingState(perfetto::protos::LayerProto* layerInfo) {
const ui::Transform transform = getTransform();
auto buffer = getExternalTexture();
if (buffer != nullptr) {
@@ -2069,10 +2288,9 @@ void Layer::writeToProtoDrawingState(LayerProto* layerInfo) {
layerInfo->set_dataspace(dataspaceDetails(static_cast<android_dataspace>(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);
+ layerInfo->set_corner_radius(
+ (getRoundedCornerState().radius.x + getRoundedCornerState().radius.y) / 2.0);
layerInfo->set_background_blur_radius(getBackgroundBlurRadius());
layerInfo->set_is_trusted_overlay(isTrustedOverlay());
LayerProtoHelper::writeToProtoDeprecated(transform, layerInfo->mutable_transform());
@@ -2095,8 +2313,8 @@ void Layer::writeToProtoDrawingState(LayerProto* layerInfo) {
layerInfo->set_shadow_radius(mEffectiveShadowRadius);
}
-void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet stateSet,
- uint32_t traceFlags) {
+void Layer::writeToProtoCommonState(perfetto::protos::LayerProto* layerInfo,
+ LayerVector::StateSet stateSet, uint32_t traceFlags) {
const bool useDrawing = stateSet == LayerVector::StateSet::Drawing;
const LayerVector& children = useDrawing ? mDrawingChildren : mCurrentChildren;
const State& state = useDrawing ? mDrawingState : mDrawingState;
@@ -2118,7 +2336,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);
@@ -2128,9 +2346,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));
@@ -2147,15 +2362,11 @@ void Layer::writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet
auto parent = useDrawing ? mDrawingParent.promote() : mCurrentParent.promote();
if (parent != nullptr) {
layerInfo->set_parent(parent->sequence);
- } else {
- layerInfo->set_parent(-1);
}
auto zOrderRelativeOf = state.zOrderRelativeOf.promote();
if (zOrderRelativeOf != nullptr) {
layerInfo->set_z_order_relative_of(zOrderRelativeOf->sequence);
- } else {
- layerInfo->set_z_order_relative_of(-1);
}
layerInfo->set_is_relative_of(state.isRelativeOf);
@@ -2190,14 +2401,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.
@@ -2230,62 +2433,17 @@ 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<float>(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)};
- info.frameLeft = roundedFrameInDisplay.left;
- info.frameTop = roundedFrameInDisplay.top;
- info.frameRight = roundedFrameInDisplay.right;
- info.frameBottom = roundedFrameInDisplay.bottom;
+ info.frame = getInputBoundsInDisplaySpace(inputBounds, screenToDisplay);
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();
@@ -2296,7 +2454,7 @@ void Layer::fillInputFrameInfo(WindowInfo& info, const ui::Transform& screenToDi
}
void Layer::fillTouchOcclusionMode(WindowInfo& info) {
- sp<Layer> p = this;
+ sp<Layer> p = sp<Layer>::fromExisting(this);
while (p != nullptr && !p->hasInputInfo()) {
p = p->mDrawingParent.promote();
}
@@ -2376,8 +2534,8 @@ void Layer::handleDropInputMode(gui::WindowInfo& info) const {
WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) {
if (!hasInputInfo()) {
mDrawingState.inputInfo.name = getName();
- mDrawingState.inputInfo.ownerUid = mOwnerUid;
- mDrawingState.inputInfo.ownerPid = mOwnerPid;
+ mDrawingState.inputInfo.ownerUid = gui::Uid{mOwnerUid};
+ mDrawingState.inputInfo.ownerPid = gui::Pid{mOwnerPid};
mDrawingState.inputInfo.inputConfig |= WindowInfo::InputConfig::NO_INPUT_CHANNEL;
mDrawingState.inputInfo.displayId = getLayerStack().id;
}
@@ -2397,16 +2555,7 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) {
info.inputConfig |= WindowInfo::InputConfig::NOT_TOUCHABLE;
}
- // 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 visible = hasInputInfo() ? canReceiveInput() : isVisible();
- info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !visible);
+ info.setInputConfig(WindowInfo::InputConfig::NOT_VISIBLE, !isVisibleForInput());
info.alpha = getAlpha();
fillTouchOcclusionMode(info);
@@ -2418,13 +2567,23 @@ WindowInfo Layer::fillInputInfo(const InputDisplayArgs& displayArgs) {
info.inputConfig |= WindowInfo::InputConfig::DROP_INPUT;
}
- auto cropLayer = mDrawingState.touchableRegionCrop.promote();
+ sp<Layer> cropLayer = mDrawingState.touchableRegionCrop.promote();
if (info.replaceTouchableRegionWithCrop) {
- const Rect bounds(cropLayer ? cropLayer->mScreenBounds : mScreenBounds);
- info.touchableRegion = Region(displayTransform.transform(bounds));
+ 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) {
- info.touchableRegion = info.touchableRegion.intersect(
- displayTransform.transform(Rect{cropLayer->mScreenBounds}));
+ 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
@@ -2436,19 +2595,43 @@ 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<Layer> clonedRoot = getClonedRoot()) {
const Rect rect = displayTransform.transform(Rect{clonedRoot->mScreenBounds});
info.touchableRegion = info.touchableRegion.intersect(rect);
}
}
+ Rect bufferSize = getBufferSize(getDrawingState());
+ info.contentSize = Size(bufferSize.width(), bufferSize.height());
+
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> Layer::getClonedRoot() {
if (mClonedChild != nullptr) {
- return this;
+ return sp<Layer>::fromExisting(this);
}
if (mDrawingParent == nullptr || mDrawingParent.promote() == nullptr) {
return nullptr;
@@ -2461,14 +2644,41 @@ 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;
+ if (!mFlinger->mLayerLifecycleManagerEnabled) {
+ return display->getCompositionDisplay()->getOutputLayerForLayer(
+ getCompositionEngineLayerFE());
+ }
+ sp<LayerFE> layerFE;
+ frontend::LayerHierarchy::TraversalPath path{.id = static_cast<uint32_t>(sequence)};
+ for (auto& [p, layer] : mLayerFEs) {
+ if (p == path) {
+ layerFE = layer;
+ }
+ }
+
+ if (!layerFE) return nullptr;
+ return display->getCompositionDisplay()->getOutputLayerForLayer(layerFE);
}
compositionengine::OutputLayer* Layer::findOutputLayerForDisplay(
- const DisplayDevice* display) const {
+ const DisplayDevice* display, const frontend::LayerHierarchy::TraversalPath& path) const {
if (!display) return nullptr;
- return display->getCompositionDisplay()->getOutputLayerForLayer(getCompositionEngineLayerFE());
+ if (!mFlinger->mLayerLifecycleManagerEnabled) {
+ return display->getCompositionDisplay()->getOutputLayerForLayer(
+ getCompositionEngineLayerFE());
+ }
+ sp<LayerFE> layerFE;
+ 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 {
@@ -2476,12 +2686,50 @@ Region Layer::getVisibleRegion(const DisplayDevice* display) const {
return outputLayer ? outputLayer->getState().visibleRegion : Region();
}
-void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom) {
+void Layer::setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId) {
+ if (mFlinger->mLayerLifecycleManagerEnabled) return;
+ mSnapshot->path.id = clonedFrom->getSequence();
+ mSnapshot->path.mirrorRootIds.emplace_back(mirrorRootId);
+
cloneDrawingState(clonedFrom.get());
mClonedFrom = clonedFrom;
+ mPremultipliedAlpha = clonedFrom->mPremultipliedAlpha;
+ mPotentialCursor = clonedFrom->mPotentialCursor;
+ mProtectedByApp = clonedFrom->mProtectedByApp;
+ updateCloneBufferInfo();
}
-void Layer::updateMirrorInfo() {
+void Layer::updateCloneBufferInfo() {
+ if (!isClone() || !isClonedFromAlive()) {
+ return;
+ }
+
+ sp<Layer> 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<Layer> tmpZOrderRelativeOf = mDrawingState.zOrderRelativeOf;
+ SortedVector<wp<Layer>> tmpZOrderRelatives = mDrawingState.zOrderRelatives;
+ wp<Layer> tmpTouchableRegionCrop = mDrawingState.touchableRegionCrop;
+ WindowInfo tmpInputInfo = mDrawingState.inputInfo;
+
+ cloneDrawingState(clonedFrom.get());
+
+ mDrawingState.touchableRegionCrop = tmpTouchableRegionCrop;
+ mDrawingState.zOrderRelativeOf = tmpZOrderRelativeOf;
+ mDrawingState.zOrderRelatives = tmpZOrderRelatives;
+ mDrawingState.inputInfo = tmpInputInfo;
+}
+
+bool Layer::updateMirrorInfo(const std::deque<Layer*>& 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
@@ -2489,7 +2737,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<Layer>, sp<Layer>> clonedLayersMap;
@@ -2502,8 +2750,15 @@ void Layer::updateMirrorInfo() {
}
mClonedChild->updateClonedDrawingState(clonedLayersMap);
- mClonedChild->updateClonedChildren(this, clonedLayersMap);
+ mClonedChild->updateClonedChildren(sp<Layer>::fromExisting(this), clonedLayersMap);
mClonedChild->updateClonedRelatives(clonedLayersMap);
+
+ for (Layer* root : cloneRootsPendingUpdates) {
+ if (clonedLayersMap.find(sp<Layer>::fromExisting(root)) != clonedLayersMap.end()) {
+ return false;
+ }
+ }
+ return true;
}
void Layer::updateClonedDrawingState(std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
@@ -2513,7 +2768,7 @@ void Layer::updateClonedDrawingState(std::map<sp<Layer>, sp<Layer>>& clonedLayer
if (isClonedFromAlive()) {
sp<Layer> clonedFrom = getClonedFrom();
cloneDrawingState(clonedFrom.get());
- clonedLayersMap.emplace(clonedFrom, this);
+ clonedLayersMap.emplace(clonedFrom, sp<Layer>::fromExisting(this));
}
// The clone layer may have children in drawingState since they may have been created and
@@ -2543,7 +2798,7 @@ void Layer::updateClonedChildren(const sp<Layer>& mirrorRoot,
}
sp<Layer> clonedChild = clonedLayersMap[child];
if (clonedChild == nullptr) {
- clonedChild = child->createClone();
+ clonedChild = child->createClone(mirrorRoot->getSequence());
clonedLayersMap[child] = clonedChild;
}
addChildToDrawing(clonedChild);
@@ -2557,7 +2812,7 @@ void Layer::updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& 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<Layer>::fromExisting(this);
} else {
const sp<Layer>& clonedCropLayer = clonedLayersMap.at(cropLayer);
mDrawingState.touchableRegionCrop = clonedCropLayer;
@@ -2569,7 +2824,7 @@ void Layer::updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& clonedLa
}
void Layer::updateClonedRelatives(const std::map<sp<Layer>, sp<Layer>>& clonedLayersMap) {
- mDrawingState.zOrderRelativeOf = nullptr;
+ mDrawingState.zOrderRelativeOf = wp<Layer>();
mDrawingState.zOrderRelatives.clear();
if (!isClonedFromAlive()) {
@@ -2605,35 +2860,7 @@ void Layer::updateClonedRelatives(const std::map<sp<Layer>, sp<Layer>>& clonedLa
void Layer::addChildToDrawing(const sp<Layer>& layer) {
mDrawingChildren.add(layer);
- layer->mDrawingParent = this;
-}
-
-Layer::FrameRateCompatibility Layer::FrameRate::convertCompatibility(int8_t compatibility) {
- switch (compatibility) {
- case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT:
- return FrameRateCompatibility::Default;
- case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
- return FrameRateCompatibility::ExactOrMultiple;
- case ANATIVEWINDOW_FRAME_RATE_EXACT:
- return FrameRateCompatibility::Exact;
- case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
- return FrameRateCompatibility::NoVote;
- default:
- LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
- return FrameRateCompatibility::Default;
- }
-}
-
-scheduler::Seamlessness Layer::FrameRate::convertChangeFrameRateStrategy(int8_t strategy) {
- switch (strategy) {
- case ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
- return Seamlessness::OnlySeamless;
- case ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS:
- return Seamlessness::SeamedAndSeamless;
- default:
- LOG_ALWAYS_FATAL("Invalid change frame sate strategy value %d", strategy);
- return Seamlessness::Default;
- }
+ layer->mDrawingParent = sp<Layer>::fromExisting(this);
}
bool Layer::isInternalDisplayOverlay() const {
@@ -2649,24 +2876,7 @@ bool Layer::isInternalDisplayOverlay() const {
void Layer::setClonedChild(const sp<Layer>& clonedChild) {
mClonedChild = clonedChild;
mHadClonedChild = true;
- mFlinger->mNumClones++;
-}
-
-const String16 Layer::Handle::kDescriptor = String16("android.Layer.Handle");
-
-wp<Layer> Layer::fromHandle(const sp<IBinder>& 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> handle = static_cast<Handle*>(handleBinder.get());
- return handle->owner;
+ mFlinger->mLayerMirrorRoots.push_back(this);
}
bool Layer::setDropInputMode(gui::DropInputMode mode) {
@@ -2681,26 +2891,1582 @@ 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 = {};
}
-bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& handles) {
+void Layer::callReleaseBufferCallback(const sp<ITransactionCompletedListener>& listener,
+ const sp<GraphicBuffer>& buffer, uint64_t framenumber,
+ const sp<Fence>& 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<FenceResult> 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<CallbackHandle> ch;
+ for (auto& handle : mDrawingState.callbackHandles) {
+ if (handle->releasePreviousBuffer && mPreviousReleaseBufferEndpoint == handle->listener) {
+ ch = handle;
+ break;
+ }
+ }
+ if (ch != nullptr) {
+ ch->previousReleaseCallbackId = mPreviousReleaseCallbackId;
+ ch->previousReleaseFences.emplace_back(std::move(futureFenceResult));
+ ch->name = mName;
+ }
+ if (mBufferInfo.mBuffer) {
+ mPreviouslyPresentedLayerStacks.push_back(layerStack);
+ }
+
+ if (mDrawingState.frameNumber > 0) {
+ mDrawingState.previousFrameNumber = mDrawingState.frameNumber;
+ }
+}
+
+void Layer::onSurfaceFrameCreated(
+ const std::shared_ptr<frametimeline::SurfaceFrame>& 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<uint32_t>(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> jankData;
+ transferAvailableJankData(mDrawingState.callbackHandles, jankData);
+ mFlinger->getTransactionCallbackInvoker().addCallbackHandles(mDrawingState.callbackHandles,
+ jankData);
+ mDrawingState.callbackHandles = {};
+}
+
+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<float>(bufferWidth);
+ float sy = destH / static_cast<float>(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::releasePreviousBuffer() {
+ 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, systemTime());
+ mDrawingState.bufferSurfaceFrameTX.reset();
+ }
+ } else if (EARLY_RELEASE_ENABLED && mLastClientCompositionFence != nullptr) {
+ callReleaseBufferCallback(mDrawingState.releaseBufferListener,
+ mDrawingState.buffer->getBuffer(), mDrawingState.frameNumber,
+ mLastClientCompositionFence);
+ mLastClientCompositionFence = nullptr;
+ }
+}
+
+void Layer::resetDrawingStateBufferInfo() {
+ mDrawingState.producerId = 0;
+ mDrawingState.frameNumber = 0;
+ mDrawingState.previousFrameNumber = 0;
+ mDrawingState.releaseBufferListener = nullptr;
+ mDrawingState.buffer = nullptr;
+ mDrawingState.acquireFence = sp<Fence>::make(-1);
+ mDrawingState.acquireFenceTime = std::make_unique<FenceTime>(mDrawingState.acquireFence);
+ mCallbackHandleAcquireTimeOrFence = mDrawingState.acquireFenceTime->getSignalTime();
+ mDrawingState.releaseBufferEndpoint = nullptr;
+}
+
+bool Layer::setBuffer(std::shared_ptr<renderengine::ExternalTexture>& buffer,
+ const BufferData& bufferData, nsecs_t postTime, nsecs_t desiredPresentTime,
+ bool isAutoTimestamp, std::optional<nsecs_t> 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) {
+ releasePreviousBuffer();
+ } 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;
+ } else {
+ // release sideband stream if it exists and a non null buffer is being set
+ if (mDrawingState.sidebandStream != nullptr) {
+ setSidebandStream(nullptr, info, postTime);
+ }
+ }
+
+ if ((mDrawingState.producerId > bufferData.producerId) ||
+ ((mDrawingState.producerId == bufferData.producerId) &&
+ (mDrawingState.frameNumber > frameNumber))) {
+ ALOGE("Out of order buffers detected for %s producedId=%d frameNumber=%" PRIu64
+ " -> producedId=%d frameNumber=%" PRIu64,
+ getDebugName(), mDrawingState.producerId, mDrawingState.frameNumber,
+ bufferData.producerId, frameNumber);
+ TransactionTraceWriter::getInstance().invoke("out_of_order_buffers_", /*overwrite=*/false);
+ }
+
+ mDrawingState.producerId = bufferData.producerId;
+ mDrawingState.barrierProducerId =
+ std::max(mDrawingState.producerId, mDrawingState.barrierProducerId);
+ mDrawingState.frameNumber = frameNumber;
+ mDrawingState.barrierFrameNumber =
+ std::max(mDrawingState.frameNumber, mDrawingState.barrierFrameNumber);
+
+ 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<FenceTime>(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(), systemTime());
+ }
+
+ 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;
+
+ // If the layer had been updated a TextureView, this would make sure the present time could be
+ // same to TextureView update when it's a small dirty, and get the correct heuristic rate.
+ if (mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
+ if (mDrawingState.useVsyncIdForRefreshRateSelection) {
+ mUsedVsyncIdForRefreshRateSelection = true;
+ }
+ }
+ return true;
+}
+
+void Layer::setDesiredPresentTime(nsecs_t desiredPresentTime, bool isAutoTimestamp) {
+ mDrawingState.desiredPresentTime = desiredPresentTime;
+ mDrawingState.isAutoTimestamp = isAutoTimestamp;
+}
+
+void Layer::recordLayerHistoryBufferUpdate(const scheduler::LayerProps& layerProps, nsecs_t now) {
+ 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");
+ mMaxTimeForUseVsyncId = prediction->presentTime +
+ scheduler::LayerHistory::kMaxPeriodForHistory.count();
+ return prediction->presentTime;
+ }
+ }
+
+ if (!mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
+ return static_cast<nsecs_t>(0);
+ }
+
+ // If the layer is not an application and didn't set an explicit rate or desiredPresentTime,
+ // return "0" to tell the layer history that it will use the max refresh rate without
+ // calculating the adaptive rate.
+ if (mWindowType != WindowInfo::Type::APPLICATION &&
+ mWindowType != WindowInfo::Type::BASE_APPLICATION) {
+ return static_cast<nsecs_t>(0);
+ }
+
+ // Return the valid present time only when the layer potentially updated a TextureView so
+ // LayerHistory could heuristically calculate the rate if the UI is continually updating.
+ if (mUsedVsyncIdForRefreshRateSelection) {
+ const auto prediction =
+ mFlinger->mFrameTimeline->getTokenManager()->getPredictionsForToken(
+ mDrawingState.latchedVsyncId);
+ if (prediction.has_value()) {
+ if (mMaxTimeForUseVsyncId >= prediction->presentTime) {
+ return prediction->presentTime;
+ }
+ mUsedVsyncIdForRefreshRateSelection = false;
+ }
+ }
+
+ return static_cast<nsecs_t>(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, now,
+ scheduler::LayerHistory::LayerUpdateType::Buffer);
+}
+
+void Layer::recordLayerHistoryAnimationTx(const scheduler::LayerProps& layerProps, nsecs_t now) {
+ const nsecs_t presentTime =
+ mDrawingState.isAutoTimestamp ? 0 : mDrawingState.desiredPresentTime;
+ mFlinger->mScheduler->recordLayerHistory(sequence, layerProps, presentTime, now,
+ 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);
+ setIsSmallDirty(surfaceDamage, getTransform());
+ 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<NativeHandle>& sidebandStream, const FrameTimelineInfo& info,
+ nsecs_t postTime) {
+ 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;
+ if (sidebandStream != nullptr && mDrawingState.buffer != nullptr) {
+ releasePreviousBuffer();
+ resetDrawingStateBufferInfo();
+ mDrawingState.bufferSurfaceFrameTX = nullptr;
+ setFrameTimelineVsyncForBufferlessTransaction(info, postTime);
+ }
+ setTransactionFlags(eTransactionNeeded);
+ if (!mSidebandStreamChanged.exchange(true)) {
+ // mSidebandStreamChanged was false
+ mFlinger->onLayerUpdate();
+ }
+ return true;
+}
+
+bool Layer::setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& 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<sp<CallbackHandle>> 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;
+ handle->previousFrameNumber = mDrawingState.previousFrameNumber;
+
+ // 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);
+ }
+ }
+
+ 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> 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<int32_t>(bufWidth), static_cast<int32_t>(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<FenceTime>(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<sp<CallbackHandle>> 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<FenceTime>(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<ui::Dataspace>(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.board.api_level", __ANDROID_API_FUTURE__);
+ if (const auto format =
+ static_cast<aidl::android::hardware::graphics::common::PixelFormat>(
+ 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> Layer::createClone(uint32_t mirrorRootId) {
+ surfaceflinger::LayerCreationArgs args(mFlinger.get(), nullptr, mName + " (Mirror)", 0,
+ LayerMetadata());
+ sp<Layer> layer = mFlinger->getFactory().createBufferStateLayer(args);
+ layer->setInitialValuesForClone(sp<Layer>::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<Layer> 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<FloatRect, bool> 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<float>(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::isSimpleBufferUpdate(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;
+
+ if ((s.what & requiredFlags) != requiredFlags) {
+ ATRACE_FORMAT_INSTANT("%s: false [missing required flags 0x%" PRIx64 "]", __func__,
+ (s.what | requiredFlags) & ~s.what);
+ return false;
+ }
+
+ if (s.what & deniedFlags) {
+ ATRACE_FORMAT_INSTANT("%s: false [has denied flags 0x%" PRIx64 "]", __func__,
+ s.what & deniedFlags);
+ return false;
+ }
+
+ if (s.what & layer_state_t::ePositionChanged) {
+ if (mRequestedTransform.tx() != s.x || mRequestedTransform.ty() != s.y) {
+ ATRACE_FORMAT_INSTANT("%s: false [ePositionChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eAlphaChanged) {
+ if (mDrawingState.color.a != s.color.a) {
+ ATRACE_FORMAT_INSTANT("%s: false [eAlphaChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eColorTransformChanged) {
+ if (mDrawingState.colorTransform != s.colorTransform) {
+ ATRACE_FORMAT_INSTANT("%s: false [eColorTransformChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eBackgroundColorChanged) {
+ if (mDrawingState.bgColorLayer || s.bgColor.a != 0) {
+ ATRACE_FORMAT_INSTANT("%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) {
+ ATRACE_FORMAT_INSTANT("%s: false [eMatrixChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eCornerRadiusChanged) {
+ if (mDrawingState.cornerRadius != s.cornerRadius) {
+ ATRACE_FORMAT_INSTANT("%s: false [eCornerRadiusChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eBackgroundBlurRadiusChanged) {
+ if (mDrawingState.backgroundBlurRadius != static_cast<int>(s.backgroundBlurRadius)) {
+ ATRACE_FORMAT_INSTANT("%s: false [eBackgroundBlurRadiusChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eBufferTransformChanged) {
+ if (mDrawingState.bufferTransform != s.bufferTransform) {
+ ATRACE_FORMAT_INSTANT("%s: false [eBufferTransformChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eTransformToDisplayInverseChanged) {
+ if (mDrawingState.transformToDisplayInverse != s.transformToDisplayInverse) {
+ ATRACE_FORMAT_INSTANT("%s: false [eTransformToDisplayInverseChanged changed]",
+ __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eCropChanged) {
+ if (mDrawingState.crop != s.crop) {
+ ATRACE_FORMAT_INSTANT("%s: false [eCropChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eDataspaceChanged) {
+ if (mDrawingState.dataspace != s.dataspace) {
+ ATRACE_FORMAT_INSTANT("%s: false [eDataspaceChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eHdrMetadataChanged) {
+ if (mDrawingState.hdrMetadata != s.hdrMetadata) {
+ ATRACE_FORMAT_INSTANT("%s: false [eHdrMetadataChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eSidebandStreamChanged) {
+ if (mDrawingState.sidebandStream != s.sidebandStream) {
+ ATRACE_FORMAT_INSTANT("%s: false [eSidebandStreamChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eColorSpaceAgnosticChanged) {
+ if (mDrawingState.colorSpaceAgnostic != s.colorSpaceAgnostic) {
+ ATRACE_FORMAT_INSTANT("%s: false [eColorSpaceAgnosticChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eShadowRadiusChanged) {
+ if (mDrawingState.shadowRadius != s.shadowRadius) {
+ ATRACE_FORMAT_INSTANT("%s: false [eShadowRadiusChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eFixedTransformHintChanged) {
+ if (mDrawingState.fixedTransformHint != s.fixedTransformHint) {
+ ATRACE_FORMAT_INSTANT("%s: false [eFixedTransformHintChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eTrustedOverlayChanged) {
+ if (mDrawingState.isTrustedOverlay != s.isTrustedOverlay) {
+ ATRACE_FORMAT_INSTANT("%s: false [eTrustedOverlayChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eStretchChanged) {
+ StretchEffect temp = s.stretchEffect;
+ temp.sanitize();
+ if (mDrawingState.stretchEffect != temp) {
+ ATRACE_FORMAT_INSTANT("%s: false [eStretchChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eBufferCropChanged) {
+ if (mDrawingState.bufferCrop != s.bufferCrop) {
+ ATRACE_FORMAT_INSTANT("%s: false [eBufferCropChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eDestinationFrameChanged) {
+ if (mDrawingState.destinationFrame != s.destinationFrame) {
+ ATRACE_FORMAT_INSTANT("%s: false [eDestinationFrameChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eDimmingEnabledChanged) {
+ if (mDrawingState.dimmingEnabled != s.dimmingEnabled) {
+ ATRACE_FORMAT_INSTANT("%s: false [eDimmingEnabledChanged changed]", __func__);
+ return false;
+ }
+ }
+
+ if (s.what & layer_state_t::eExtendedRangeBrightnessChanged) {
+ if (mDrawingState.currentHdrSdrRatio != s.currentHdrSdrRatio ||
+ mDrawingState.desiredHdrSdrRatio != s.desiredHdrSdrRatio) {
+ ATRACE_FORMAT_INSTANT("%s: false [eExtendedRangeBrightnessChanged changed]", __func__);
+ return false;
+ }
}
return true;
}
-// ---------------------------------------------------------------------------
+sp<LayerFE> 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<frontend::LayerSnapshot> Layer::stealLayerSnapshot() {
+ return std::move(mSnapshot);
+}
+
+void Layer::updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> snapshot) {
+ mSnapshot = std::move(snapshot);
+}
+
+const compositionengine::LayerFECompositionState* Layer::getCompositionState() const {
+ return mSnapshot.get();
+}
+
+sp<LayerFE> Layer::copyCompositionEngineLayerFE() const {
+ auto result = mFlinger->getFactory().createLayerFE(mName);
+ result->mSnapshot = std::make_unique<LayerSnapshot>(*mSnapshot);
+ return result;
+}
+
+sp<LayerFE> 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::onCompositionPresented(const DisplayDevice* display,
+ const std::shared_ptr<FenceTime>& glDoneFence,
+ const std::shared_ptr<FenceTime>& 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<FenceTime> 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<Fps> renderRate =
+ mFlinger->mScheduler->getFrameRateOverride(getOwnerUid());
+
+ const auto vote = frameRateToSetFrameRateVotePayload(getFrameRateForLayerTree());
+ 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<FenceTime>(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;
+}
+
+bool Layer::isFrontBuffered() const {
+ if (mBufferInfo.mBuffer == nullptr) {
+ return false;
+ }
+
+ return mBufferInfo.mBuffer->getUsage() & AHARDWAREBUFFER_USAGE_FRONT_BUFFER;
+}
+
+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<GraphicBuffer> 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<renderengine::ExternalTexture>& 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->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;
+ sp<Layer> 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<Layer>& 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<Layer>& child : mDrawingChildren) {
+ child->updateMetadataSnapshot(mSnapshot->layerMetadata);
+ }
+}
+
+void Layer::updateRelativeMetadataSnapshot(const LayerMetadata& relativeLayerMetadata,
+ std::unordered_set<Layer*>& 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<Layer> weakRelative : mDrawingState.zOrderRelatives) {
+ sp<Layer> 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;
+}
+
+void Layer::setIsSmallDirty(const Region& damageRegion,
+ const ui::Transform& layerToDisplayTransform) {
+ mSmallDirty = false;
+ if (!mFlinger->mScheduler->supportSmallDirtyDetection(mOwnerAppId)) {
+ return;
+ }
+
+ if (mWindowType != WindowInfo::Type::APPLICATION &&
+ mWindowType != WindowInfo::Type::BASE_APPLICATION) {
+ return;
+ }
+
+ Rect bounds = damageRegion.getBounds();
+ if (!bounds.isValid()) {
+ return;
+ }
+
+ // Transform to screen space.
+ bounds = layerToDisplayTransform.transform(bounds);
+
+ // If the damage region is a small dirty, this could give the hint for the layer history that
+ // it could suppress the heuristic rate when calculating.
+ mSmallDirty = mFlinger->mScheduler->isSmallDirtyArea(mOwnerAppId,
+ bounds.getWidth() * bounds.getHeight());
+}
-std::ostream& operator<<(std::ostream& stream, const Layer::FrameRate& rate) {
- return stream << "{rate=" << rate.rate << " type=" << ftl::enum_string(rate.type)
- << " seamlessness=" << ftl::enum_string(rate.seamlessness) << '}';
+void Layer::setIsSmallDirty(frontend::LayerSnapshot* snapshot) {
+ setIsSmallDirty(snapshot->surfaceDamage, snapshot->localTransform);
+ snapshot->isSmallDirty = mSmallDirty;
}
} // namespace android
diff --git a/services/surfaceflinger/Layer.h b/services/surfaceflinger/Layer.h
index c547da06cb..c772e0ea1b 100644
--- a/services/surfaceflinger/Layer.h
+++ b/services/surfaceflinger/Layer.h
@@ -17,14 +17,12 @@
#pragma once
#include <android/gui/DropInputMode.h>
+#include <android/gui/ISurfaceComposerClient.h>
#include <gui/BufferQueue.h>
-#include <gui/ISurfaceComposerClient.h>
#include <gui/LayerState.h>
#include <gui/WindowInfo.h>
#include <layerproto/LayerProtoHeader.h>
#include <math/vec4.h>
-#include <renderengine/Mesh.h>
-#include <renderengine/Texture.h>
#include <sys/types.h>
#include <ui/BlurRegion.h>
#include <ui/FloatRect.h>
@@ -38,6 +36,7 @@
#include <utils/Timers.h>
#include <compositionengine/LayerFE.h>
+#include <compositionengine/LayerFECompositionState.h>
#include <scheduler/Fps.h>
#include <scheduler/Seamlessness.h>
@@ -48,13 +47,10 @@
#include <vector>
#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 +65,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<Client>, std::string name, uint32_t flags, LayerMetadata);
-
- SurfaceFlinger* flinger;
- const sp<Client> client;
- std::string name;
- uint32_t flags;
- LayerMetadata metadata;
-
- pid_t callingPid;
- uid_t callingUid;
- uint32_t textureName;
- std::optional<uint32_t> sequence = std::nullopt;
- bool addToRoot = true;
-};
-
-class Layer : public virtual RefBase, compositionengine::LayerFE {
- static std::atomic<int32_t> 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 +92,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,84 +110,57 @@ public:
inline bool operator!=(const Geometry& rhs) const { return !operator==(rhs); }
};
- struct RoundedCornerState {
- RoundedCornerState() = default;
- RoundedCornerState(FloatRect cropRect, float radius)
- : cropRect(cropRect), radius(radius) {}
-
- // Rounded rectangle in local layer coordinate space.
- FloatRect cropRect = FloatRect();
- // Radius of the rounded rectangle.
- float radius = 0.0f;
- };
-
using FrameRate = scheduler::LayerInfo::FrameRate;
- using FrameRateCompatibility = scheduler::LayerInfo::FrameRateCompatibility;
+ using FrameRateCompatibility = scheduler::FrameRateCompatibility;
+ using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
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<Layer> zOrderRelativeOf;
bool isRelativeOf{false};
// A list of surfaces whose Z-order is interpreted relative to ours.
SortedVector<wp<Layer>> zOrderRelatives;
-
half4 color;
float cornerRadius;
int backgroundBlurRadius;
-
gui::WindowInfo inputInfo;
wp<Layer> 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;
+ uint64_t previousFrameNumber;
+ // 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<renderengine::ExternalTexture> buffer;
- client_cache_t clientCacheId;
sp<Fence> acquireFence;
std::shared_ptr<FenceTime> acquireFenceTime;
HdrMetadata hdrMetadata;
Region surfaceDamageRegion;
int32_t api;
-
sp<NativeHandle> 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
@@ -232,11 +183,15 @@ 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
FrameRate frameRateForLayerTree;
+ FrameRateSelectionStrategy frameRateSelectionStrategy;
+
// 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
@@ -251,7 +206,6 @@ public:
// When the transaction was posted
nsecs_t postTime;
-
sp<ITransactionCompletedListener> 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.
@@ -272,103 +226,37 @@ public:
// Whether or not this layer is a trusted overlay for input
bool isTrustedOverlay;
-
Rect bufferCrop;
Rect destinationFrame;
-
sp<IBinder> 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<SurfaceFlinger> mFlinger;
- sp<Layer> mLayer;
- BBinder* mHandle;
-
- protected:
- ~LayerCleaner() {
- // destroy client resources
- mFlinger->onHandleDestroyed(mHandle, mLayer);
- }
-
- public:
- LayerCleaner(const sp<SurfaceFlinger>& flinger, const sp<Layer>& 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<SurfaceFlinger>& flinger, const sp<Layer>& layer)
- : LayerCleaner(flinger, layer, this), owner(layer) {}
- const String16& getInterfaceDescriptor() const override { return kDescriptor; }
-
- static const String16 kDescriptor;
- wp<Layer> owner;
- };
-
- static wp<Layer> fromHandle(const sp<IBinder>& handle);
-
- explicit Layer(const LayerCreationArgs& args);
+ explicit Layer(const surfaceflinger::LayerCreationArgs& args);
virtual ~Layer();
static bool isLayerFocusedBasedOnPriority(int32_t priority);
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<Layer> createClone() = 0;
+ virtual sp<Layer> 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
@@ -378,9 +266,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?
@@ -389,7 +277,7 @@ public:
virtual bool setRelativeLayer(const sp<IBinder>& 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.
//
@@ -401,59 +289,79 @@ public:
// is specified in pixels.
virtual bool setBackgroundBlurRadius(int backgroundBlurRadius);
virtual bool setBlurRegions(const std::vector<BlurRegion>& 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<Layer>&);
- virtual bool reparent(const sp<IBinder>& newParentHandle);
+ virtual bool reparent(const sp<IBinder>& newParentHandle) REQUIRES(mFlinger->mStateLock);
virtual bool setColorTransform(const mat4& matrix);
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<renderengine::ExternalTexture>& /* buffer */,
- const BufferData& /* bufferData */, nsecs_t /* postTime */,
- nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
- std::optional<nsecs_t> /* 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<NativeHandle>& /*sidebandStream*/) { return false; };
- virtual bool setTransactionCompletedListeners(
- const std::vector<sp<CallbackHandle>>& /*handles*/);
- virtual bool setBackgroundColor(const half3& color, float alpha, ui::Dataspace dataspace);
+ 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<renderengine::ExternalTexture>& /* buffer */,
+ const BufferData& /* bufferData */, nsecs_t /* postTime */,
+ nsecs_t /*desiredPresentTime*/, bool /*isAutoTimestamp*/,
+ std::optional<nsecs_t> /* 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<NativeHandle>& /*sidebandStream*/,
+ const FrameTimelineInfo& /* info*/, nsecs_t /* postTime */);
+ bool setTransactionCompletedListeners(const std::vector<sp<CallbackHandle>>& /*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 bool isFrontBuffered() const;
- virtual sp<compositionengine::LayerFE> getCompositionEngineLayerFE() const;
- virtual compositionengine::LayerFECompositionState* editCompositionState();
+ virtual sp<LayerFE> getCompositionEngineLayerFE() const;
+ virtual sp<LayerFE> copyCompositionEngineLayerFE() const;
+ sp<LayerFE> getCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
+ sp<LayerFE> getOrCreateCompositionEngineLayerFE(const frontend::LayerHierarchy::TraversalPath&);
+
+ const frontend::LayerSnapshot* getLayerSnapshot() const;
+ frontend::LayerSnapshot* editLayerSnapshot();
+ std::unique_ptr<frontend::LayerSnapshot> stealLayerSnapshot();
+ void updateLayerSnapshot(std::unique_ptr<frontend::LayerSnapshot> 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
@@ -462,18 +370,33 @@ 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.
+ */
+ virtual bool isVisibleForInput() const {
+ // 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.
+ return hasInputInfo() ? canReceiveInput() : isVisible();
+ }
/*
* 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
@@ -483,21 +406,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
@@ -508,32 +429,20 @@ 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;
- }
-
- virtual void updateCloneBufferInfo(){};
-
- virtual void setDefaultBufferSize(uint32_t /*w*/, uint32_t /*h*/) {}
+ bool needsFilteringForScreenshots(const DisplayDevice*, const ui::Transform&) const;
- virtual bool isHdrY410() const { return false; }
+ // from graphics API
+ static ui::Dataspace translateDataspace(ui::Dataspace dataspace);
+ void updateCloneBufferInfo();
+ uint64_t mPreviousFrameNumber = 0;
- virtual bool shouldPresentNow(nsecs_t /*expectedPresentTime*/) const { return false; }
-
- /*
- * called after composition.
- * returns true if the layer latched a new buffer this frame.
- */
- virtual void onPostComposition(const DisplayDevice*,
- const std::shared_ptr<FenceTime>& /*glDoneFence*/,
- const std::shared_ptr<FenceTime>& /*presentFence*/,
- const CompositorTiming&) {}
+ void onCompositionPresented(const DisplayDevice*,
+ const std::shared_ptr<FenceTime>& /*glDoneFence*/,
+ const std::shared_ptr<FenceTime>& /*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<FenceTime>& /*glDoneFence*/,
- const CompositorTiming& /*compositorTiming*/) {}
+ void releasePendingBuffer(nsecs_t /*dequeueReadyTime*/);
/*
* latchBuffer - called each time the screen is redrawn and returns whether
@@ -541,89 +450,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<GraphicBuffer> getBuffer() const { return nullptr; }
- virtual const std::shared_ptr<renderengine::ExternalTexture>& getExternalTexture() const {
- return mDrawingState.buffer;
- };
+ uint32_t getBufferTransform() const;
- virtual ui::Transform::RotationFlags getTransformHint() const { return ui::Transform::ROT_0; }
+ sp<GraphicBuffer> getBuffer() const;
+ const std::shared_ptr<renderengine::ExternalTexture>& 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().radius > .0f; }
+ 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<FenceTime> mFenceTime;
+ sp<Fence> 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<renderengine::ExternalTexture> mBuffer;
+ uint64_t mFrameNumber;
+ sp<IBinder> 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<compositionengine::LayerFE::LayerSettings> prepareClientCompositionList(
- compositionengine::LayerFE::ClientCompositionTargetSettings&) override;
- void onLayerDisplayed(ftl::SharedFuture<FenceResult>) override;
-
- void setWasClientComposed(const sp<Fence>& fence) override {
+ const compositionengine::LayerFECompositionState* getCompositionState() const;
+ bool fenceHasSignaled() const;
+ void onPreComposition(nsecs_t refreshStartTime);
+ void onLayerDisplayed(ftl::SharedFuture<FenceResult>, ui::LayerStack layerStack);
+
+ void setWasClientComposed(const sp<Fence>& fence) {
mLastClientCompositionFence = fence;
mClearClientCompositionFenceOnLayerDisplayed = false;
}
- const char* getDebugName() const override;
+ const char* getDebugName() const;
bool setShadowRadius(float shadowRadius);
@@ -636,6 +575,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);
@@ -644,11 +597,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.
@@ -683,21 +638,24 @@ public:
bool isRemovedFromCurrentState() const;
- LayerProto* writeToProto(LayersProto& layersProto, uint32_t traceFlags);
+ perfetto::protos::LayerProto* writeToProto(perfetto::protos::LayersProto& layersProto,
+ uint32_t traceFlags);
+ void writeCompositionStateToProto(perfetto::protos::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
// thread.
- void writeToProtoDrawingState(LayerProto* layerInfo);
+ void writeToProtoDrawingState(perfetto::protos::LayerProto* layerInfo);
// Write drawing or current state. If writing current state, the caller should hold the
// external mStateLock. If writing drawing state, this function should be called on the
// main or tracing thread.
- void writeToProtoCommonState(LayerProto* layerInfo, LayerVector::StateSet,
+ void writeToProtoCommonState(perfetto::protos::LayerProto* layerInfo, LayerVector::StateSet,
uint32_t traceFlags = LayerTracing::TRACE_ALL);
gui::WindowInfo::Type getWindowType() const { return mWindowType; }
- void updateMirrorInfo();
+ bool updateMirrorInfo(const std::deque<Layer*>& cloneRootsPendingUpdates);
/*
* doTransaction - process the transaction. This is a good place to figure
@@ -714,13 +672,13 @@ public:
/*
* Remove from current state and mark for removal.
*/
- void removeFromCurrentState();
+ void removeFromCurrentState() REQUIRES(mFlinger->mStateLock);
/*
* called with the state lock from a binder thread when the layer is
* removed from the current list to the pending removal list
*/
- void onRemovedFromCurrentState();
+ void onRemovedFromCurrentState() REQUIRES(mFlinger->mStateLock);
/*
* Called when the layer is added back to the current state list.
@@ -731,15 +689,16 @@ 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 miniDumpLegacy(std::string& result, const DisplayDevice&) const;
+ void miniDump(std::string& result, const frontend::LayerSnapshot&, 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;
@@ -773,6 +732,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
@@ -780,7 +740,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
@@ -819,15 +782,18 @@ public:
*/
Rect getCroppedBufferSize(const Layer::State& s) const;
- bool setFrameRate(FrameRate);
+ bool setFrameRate(FrameRate::FrameRateVote);
+ bool setFrameRateCategory(FrameRateCategory, bool smoothSwitchOnly);
+
+ bool setFrameRateSelectionStrategy(FrameRateSelectionStrategy);
virtual void setFrameTimelineInfoForBuffer(const FrameTimelineInfo& /*info*/) {}
void setFrameTimelineVsyncForBufferTransaction(const FrameTimelineInfo& info, nsecs_t postTime);
void setFrameTimelineVsyncForBufferlessTransaction(const FrameTimelineInfo& info,
nsecs_t postTime);
- void addSurfaceFrameDroppedForBuffer(
- std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame);
+ void addSurfaceFrameDroppedForBuffer(std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame,
+ nsecs_t dropTime);
void addSurfaceFramePresentedForBuffer(
std::shared_ptr<frametimeline::SurfaceFrame>& surfaceFrame, nsecs_t acquireFenceTime,
nsecs_t currentLatchTime);
@@ -836,6 +802,11 @@ public:
const FrameTimelineInfo& info, nsecs_t postTime);
std::shared_ptr<frametimeline::SurfaceFrame> createSurfaceFrameForBuffer(
const FrameTimelineInfo& info, nsecs_t queueTime, std::string debugName);
+ void setFrameTimelineVsyncForSkippedFrames(const FrameTimelineInfo& info, nsecs_t postTime,
+ 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.
@@ -855,17 +826,19 @@ 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; }
pid_t getOwnerPid() { return mOwnerPid; }
+ int32_t getOwnerAppId() { return mOwnerAppId; }
+
// This layer is not a clone, but it's the parent to the cloned hierarchy. The
// variable mClonedChild represents the top layer that will be cloned so this
// layer will be the parent of mClonedChild.
@@ -878,6 +851,14 @@ public:
mutable bool contentDirty{false};
Region surfaceDamageRegion;
+ // True when the surfaceDamageRegion is recognized as a small area update.
+ bool mSmallDirty{false};
+ // Used to check if mUsedVsyncIdForRefreshRateSelection should be expired when it stop updating.
+ nsecs_t mMaxTimeForUseVsyncId = 0;
+ // True when DrawState.useVsyncIdForRefreshRateSelection previously set to true during updating
+ // buffer.
+ bool mUsedVsyncIdForRefreshRateSelection{false};
+
// 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.
@@ -885,22 +866,80 @@ 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<int32_t>* getPendingBufferCounter() { return &mPendingBufferTransactions; }
+ std::string getPendingBufferCounterName() { return mBlastTransactionName; }
+ bool updateGeometry();
+
+ bool isSimpleBufferUpdate(const layer_state_t& s) 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<Layer*>& visited);
+ sp<Layer> 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<int32_t>* 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<ITransactionCompletedListener>& listener,
+ const sp<GraphicBuffer>& buffer, uint64_t framenumber,
+ const sp<Fence>& releaseFence);
+ bool setFrameRateForLayerTreeLegacy(FrameRate, nsecs_t now);
+ bool setFrameRateForLayerTree(FrameRate, const scheduler::LayerProps&, nsecs_t now);
+ void recordLayerHistoryBufferUpdate(const scheduler::LayerProps&, nsecs_t now);
+ void recordLayerHistoryAnimationTx(const scheduler::LayerProps&, nsecs_t now);
+ auto getLayerProps() const {
+ return scheduler::LayerProps{.visible = isVisible(),
+ .bounds = getBounds(),
+ .transform = getTransform(),
+ .setFrameRateVote = getFrameRateForLayerTree(),
+ .frameRateSelectionPriority = getFrameRateSelectionPriority(),
+ .isSmallDirty = mSmallDirty,
+ .isFrontBuffered = isFrontBuffered()};
+ };
+ bool hasBuffer() const { return mBufferInfo.mBuffer != nullptr; }
+ void setTransformHint(std::optional<ui::Transform::RotationFlags> transformHint) {
+ mTransformHint = transformHint;
+ }
+ void commitTransaction();
+ // 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<ui::LayerStack> mPreviouslyPresentedLayerStacks;
+ // Exposed so SurfaceFlinger can assert that it's held
+ const sp<SurfaceFlinger> mFlinger;
+
+ // Check if the damage region is a small dirty.
+ void setIsSmallDirty(const Region& damageRegion, const ui::Transform& layerToDisplayTransform);
+ void setIsSmallDirty(frontend::LayerSnapshot* snapshot);
protected:
- friend class impl::SurfaceInterceptor;
-
// For unit tests
friend class TestableSurfaceFlinger;
friend class FpsReporterTest;
@@ -909,21 +948,13 @@ protected:
friend class TransactionFrameTracerTest;
friend class TransactionSurfaceFrameTest;
- virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom);
- virtual std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
- compositionengine::LayerFE::ClientCompositionTargetSettings&);
- virtual void preparePerFrameCompositionState();
- virtual void commitTransaction(State& stateToCommit);
- virtual void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&) {}
+ virtual void setInitialValuesForClone(const sp<Layer>& clonedFrom, uint32_t mirrorRootId);
+ void preparePerFrameCompositionState();
+ void preparePerFrameBufferCompositionState();
+ void preparePerFrameEffectsCompositionState();
+ void gatherBufferInfo();
+ void onSurfaceFrameCreated(const std::shared_ptr<frametimeline::SurfaceFrame>&);
- // 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<compositionengine::LayerFE> asLayerFE() const;
- sp<Layer> getClonedFrom() { return mClonedFrom != nullptr ? mClonedFrom.promote() : nullptr; }
- bool isClone() { return mClonedFrom != nullptr; }
bool isClonedFromAlive() { return getClonedFrom() != nullptr; }
void cloneDrawingState(const Layer* from);
@@ -934,11 +965,6 @@ protected:
void addChildToDrawing(const sp<Layer>&);
void updateClonedInputInfo(const std::map<sp<Layer>, sp<Layer>>& 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();
@@ -955,6 +981,8 @@ protected:
void addZOrderRelative(const wp<Layer>& relative);
void removeZOrderRelative(const wp<Layer>& relative);
compositionengine::OutputLayer* findOutputLayerForDisplay(const DisplayDevice*) const;
+ compositionengine::OutputLayer* findOutputLayerForDisplay(
+ const DisplayDevice*, const frontend::LayerHierarchy::TraversalPath& path) const;
bool usingRelativeZ(LayerVector::StateSet) const;
virtual ui::Transform getInputTransform() const;
@@ -971,10 +999,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;
-
- // constant
- sp<SurfaceFlinger> mFlinger;
+ std::pair<FloatRect, bool> getInputBounds(bool fillParentBounds) const;
bool mPremultipliedAlpha{true};
const std::string mName;
@@ -983,6 +1008,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
@@ -1033,6 +1064,8 @@ protected:
// If created from a system process, the value can be passed in.
pid_t mOwnerPid;
+ int32_t mOwnerAppId;
+
// Keeps track of the time SF latched the last buffer from this layer.
// Used in buffer stuffing analysis in FrameTimeline.
nsecs_t mLastLatchTime = 0;
@@ -1042,14 +1075,26 @@ protected:
sp<Fence> mLastClientCompositionFence;
bool mClearClientCompositionFenceOnLayerDisplayed = false;
private:
- virtual void setTransformHint(ui::Transform::RotationFlags) {}
+ // Range of uids allocated for a user.
+ // This value is taken from android.os.UserHandle#PER_USER_RANGE.
+ static constexpr int32_t PER_USER_RANGE = 100000;
+
+ friend class SlotGenerationTest;
+ friend class TransactionFrameTracerTest;
+ friend class TransactionSurfaceFrameTest;
+
+ bool getAutoRefresh() const { return mDrawingState.autoRefresh; }
+ bool getSidebandStreamChanged() const { return mSidebandStreamChanged; }
+
+ std::atomic<bool> mSidebandStreamChanged{false};
// Returns true if the layer can draw shadows on its border.
virtual bool canDrawShadows() const { return true; }
aidl::android::hardware::graphics::composer3::Composition getCompositionType(
const DisplayDevice&) const;
-
+ aidl::android::hardware::graphics::composer3::Composition getCompositionType(
+ const compositionengine::OutputLayer*) const;
/**
* Returns an unsorted vector of all layers that are part of this tree.
* That includes the current layer and all its descendants.
@@ -1065,8 +1110,8 @@ private:
const std::vector<Layer*>& layersInTree);
void updateTreeHasFrameRateVote();
- bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool* transactionNeeded);
- bool setFrameRateForLayerTree(FrameRate);
+ bool propagateFrameRateForLayerTree(FrameRate parentFrameRate, bool overrideChildren,
+ bool* transactionNeeded);
void setZOrderRelativeOf(const wp<Layer>& relativeOf);
bool isTrustedOverlay() const;
gui::DropInputMode getDropInputMode() const;
@@ -1087,6 +1132,56 @@ 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<ITransactionCompletedListener>& listener,
+ const sp<GraphicBuffer>& buffer, uint64_t framenumber,
+ const sp<Fence>& 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<sp<CallbackHandle>>& handles,
+ std::vector<JankData>& jankData);
+
+ bool shouldOverrideChildrenFrameRate() const {
+ return getDrawingState().frameRateSelectionStrategy ==
+ FrameRateSelectionStrategy::OverrideChildren;
+ }
+
+ bool shouldPropagateFrameRate() const {
+ return getDrawingState().frameRateSelectionStrategy != FrameRateSelectionStrategy::Self;
+ }
+
// 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.
@@ -1105,11 +1200,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,
@@ -1122,7 +1212,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<BlurRegion> getBlurRegions() const;
@@ -1130,7 +1220,57 @@ private:
bool mIsAtRoot = false;
uint32_t mLayerCreationFlags;
+
bool findInHierarchy(const sp<Layer>&);
+
+ bool mBorderEnabled = false;
+ float mBorderWidth;
+ half4 mBorderColor;
+
+ void setTransformHintLegacy(ui::Transform::RotationFlags);
+ void releasePreviousBuffer();
+ void resetDrawingStateBufferInfo();
+
+ // 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<ui::Transform::RotationFlags> mTransformHint = std::nullopt;
+
+ ReleaseCallbackId mPreviousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
+ sp<IBinder> mPreviousReleaseBufferEndpoint;
+
+ bool mReleasePreviousBuffer = false;
+
+ // Stores the last set acquire fence signal time used to populate the callback handle's acquire
+ // time.
+ std::variant<nsecs_t, sp<Fence>> mCallbackHandleAcquireTimeOrFence = -1;
+
+ std::deque<std::shared_ptr<android::frametimeline::SurfaceFrame>> 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<int32_t> 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<LayerFE> mLegacyLayerFE;
+ std::vector<std::pair<frontend::LayerHierarchy::TraversalPath, sp<LayerFE>>> mLayerFEs;
+ std::unique_ptr<frontend::LayerSnapshot> mSnapshot =
+ std::make_unique<frontend::LayerSnapshot>();
+ 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 0000000000..2dbcb841ac
--- /dev/null
+++ b/services/surfaceflinger/LayerFE.cpp
@@ -0,0 +1,391 @@
+/*
+ * 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 "SurfaceFlinger"
+#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+
+#include <gui/GLConsumer.h>
+#include <gui/TraceUtils.h>
+#include <math/vec3.h>
+#include <system/window.h>
+#include <utils/Log.h>
+
+#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<renderengine::ExternalTexture>& 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<float>(buffer->getWidth()),
+ static_cast<float>(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<compositionengine::LayerFE::LayerSettings> LayerFE::prepareClientComposition(
+ compositionengine::LayerFE::ClientCompositionTargetSettings& targetSettings) const {
+ std::optional<compositionengine::LayerFE::LayerSettings> 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<compositionengine::LayerFE::LayerSettings> 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<ui::Dataspace>(
+ (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;
+ }
+ bool blackOutLayer;
+ if (FlagManager::getInstance().display_protected()) {
+ blackOutLayer = (mSnapshot->hasProtectedContent && !targetSettings.isProtected) ||
+ (mSnapshot->isSecure && !targetSettings.isSecure);
+ } else {
+ blackOutLayer = (mSnapshot->hasProtectedContent && !targetSettings.isProtected) ||
+ ((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.usePremultipliedAlpha = mSnapshot->premultipliedAlpha;
+ 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();
+
+ const bool useFiltering = targetSettings.needsFiltering ||
+ mSnapshot->geomLayerTransform.needsBilinearFiltering();
+
+ // Query the texture matrix given our current filtering mode.
+ float textureMatrix[16];
+ getDrawingTransformMatrix(layerSettings.source.buffer.buffer, mSnapshot->geomContentCrop,
+ mSnapshot->geomBufferTransform, useFiltering,
+ 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<const float*>(textureMatrix)) * tr);
+ memcpy(textureMatrix, texTransform.asArray(), sizeof(textureMatrix));
+ }
+
+ const Rect win{layerSettings.geometry.boundaries};
+ float bufferWidth = static_cast<float>(mSnapshot->bufferSize.getWidth());
+ float bufferHeight = static_cast<float>(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 = useFiltering;
+ layerSettings.source.buffer.textureTransform =
+ mat4(static_cast<const float*>(textureMatrix)) * tr;
+
+ return;
+}
+
+void LayerFE::prepareShadowClientComposition(LayerFE::LayerSettings& caster,
+ const Rect& layerStackRect) const {
+ 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<float>(layerStackRect.width()) / 2.f) - mSnapshot->transformedBounds.left;
+ state.lightPos.y -= mSnapshot->transformedBounds.top;
+ caster.shadow = state;
+}
+
+void LayerFE::onLayerDisplayed(ftl::SharedFuture<FenceResult> 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 static_cast<int32_t>(mSnapshot->uniqueSequence);
+}
+
+bool LayerFE::hasRoundedCorners() const {
+ return mSnapshot->roundedCorner.hasRoundedCorners();
+}
+
+void LayerFE::setWasClientComposed(const sp<Fence>& 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<GraphicBuffer> 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 0000000000..d584fb7eab
--- /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 <android/gui/CachingHint.h>
+#include <gui/LayerMetadata.h>
+#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<std::pair<ftl::SharedFuture<FenceResult>, ui::LayerStack>> releaseFences;
+ sp<Fence> 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<FenceResult>, ui::LayerStack) override;
+ const char* getDebugName() const override;
+ int32_t getSequence() const override;
+ bool hasRoundedCorners() const override;
+ void setWasClientComposed(const sp<Fence>&) override;
+ const gui::LayerMetadata* getMetadata() const override;
+ const gui::LayerMetadata* getRelativeMetadata() const override;
+ std::optional<compositionengine::LayerFE::LayerSettings> prepareClientComposition(
+ compositionengine::LayerFE::ClientCompositionTargetSettings&) const;
+ CompositionResult&& stealCompositionResult();
+
+ std::unique_ptr<surfaceflinger::frontend::LayerSnapshot> mSnapshot;
+
+private:
+ std::optional<compositionengine::LayerFE::LayerSettings> 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<GraphicBuffer> getBuffer() const;
+
+ CompositionResult mCompositionResult;
+ std::string mName;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/LayerProtoHelper.cpp b/services/surfaceflinger/LayerProtoHelper.cpp
index 0506c47555..aa6026ef79 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"
@@ -27,28 +29,30 @@ using gui::WindowInfo;
namespace surfaceflinger {
-void LayerProtoHelper::writePositionToProto(const float x, const float y,
- std::function<PositionProto*()> getPositionProto) {
+void LayerProtoHelper::writePositionToProto(
+ const float x, const float y,
+ std::function<perfetto::protos::PositionProto*()> getPositionProto) {
if (x != 0 || y != 0) {
// Use a lambda do avoid writing the object header when the object is empty
- PositionProto* position = getPositionProto();
+ perfetto::protos::PositionProto* position = getPositionProto();
position->set_x(x);
position->set_y(y);
}
}
-void LayerProtoHelper::writeSizeToProto(const uint32_t w, const uint32_t h,
- std::function<SizeProto*()> getSizeProto) {
+void LayerProtoHelper::writeSizeToProto(
+ const uint32_t w, const uint32_t h,
+ std::function<perfetto::protos::SizeProto*()> getSizeProto) {
if (w != 0 || h != 0) {
// Use a lambda do avoid writing the object header when the object is empty
- SizeProto* size = getSizeProto();
+ perfetto::protos::SizeProto* size = getSizeProto();
size->set_w(w);
size->set_h(h);
}
}
-void LayerProtoHelper::writeToProto(const Region& region,
- std::function<RegionProto*()> getRegionProto) {
+void LayerProtoHelper::writeToProto(
+ const Region& region, std::function<perfetto::protos::RegionProto*()> getRegionProto) {
if (region.isEmpty()) {
return;
}
@@ -56,7 +60,8 @@ void LayerProtoHelper::writeToProto(const Region& region,
writeToProto(region, getRegionProto());
}
-void LayerProtoHelper::writeToProto(const Region& region, RegionProto* regionProto) {
+void LayerProtoHelper::writeToProto(const Region& region,
+ perfetto::protos::RegionProto* regionProto) {
if (region.isEmpty()) {
return;
}
@@ -70,7 +75,8 @@ void LayerProtoHelper::writeToProto(const Region& region, RegionProto* regionPro
}
}
-void LayerProtoHelper::readFromProto(const RegionProto& regionProto, Region& outRegion) {
+void LayerProtoHelper::readFromProto(const perfetto::protos::RegionProto& regionProto,
+ Region& outRegion) {
for (int i = 0; i < regionProto.rect_size(); i++) {
Rect rect;
readFromProto(regionProto.rect(i), rect);
@@ -78,32 +84,34 @@ void LayerProtoHelper::readFromProto(const RegionProto& regionProto, Region& out
}
}
-void LayerProtoHelper::writeToProto(const Rect& rect, std::function<RectProto*()> getRectProto) {
+void LayerProtoHelper::writeToProto(const Rect& rect,
+ std::function<perfetto::protos::RectProto*()> getRectProto) {
if (rect.left != 0 || rect.right != 0 || rect.top != 0 || rect.bottom != 0) {
// Use a lambda do avoid writing the object header when the object is empty
writeToProto(rect, getRectProto());
}
}
-void LayerProtoHelper::writeToProto(const Rect& rect, RectProto* rectProto) {
+void LayerProtoHelper::writeToProto(const Rect& rect, perfetto::protos::RectProto* rectProto) {
rectProto->set_left(rect.left);
rectProto->set_top(rect.top);
rectProto->set_bottom(rect.bottom);
rectProto->set_right(rect.right);
}
-void LayerProtoHelper::readFromProto(const RectProto& proto, Rect& outRect) {
+void LayerProtoHelper::readFromProto(const perfetto::protos::RectProto& proto, Rect& outRect) {
outRect.left = proto.left();
outRect.top = proto.top();
outRect.bottom = proto.bottom();
outRect.right = proto.right();
}
-void LayerProtoHelper::writeToProto(const FloatRect& rect,
- std::function<FloatRectProto*()> getFloatRectProto) {
+void LayerProtoHelper::writeToProto(
+ const FloatRect& rect,
+ std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto) {
if (rect.left != 0 || rect.right != 0 || rect.top != 0 || rect.bottom != 0) {
// Use a lambda do avoid writing the object header when the object is empty
- FloatRectProto* rectProto = getFloatRectProto();
+ perfetto::protos::FloatRectProto* rectProto = getFloatRectProto();
rectProto->set_left(rect.left);
rectProto->set_top(rect.top);
rectProto->set_bottom(rect.bottom);
@@ -111,10 +119,11 @@ void LayerProtoHelper::writeToProto(const FloatRect& rect,
}
}
-void LayerProtoHelper::writeToProto(const half4 color, std::function<ColorProto*()> getColorProto) {
+void LayerProtoHelper::writeToProto(const half4 color,
+ std::function<perfetto::protos::ColorProto*()> getColorProto) {
if (color.r != 0 || color.g != 0 || color.b != 0 || color.a != 0) {
// Use a lambda do avoid writing the object header when the object is empty
- ColorProto* colorProto = getColorProto();
+ perfetto::protos::ColorProto* colorProto = getColorProto();
colorProto->set_r(color.r);
colorProto->set_g(color.g);
colorProto->set_b(color.b);
@@ -123,7 +132,7 @@ void LayerProtoHelper::writeToProto(const half4 color, std::function<ColorProto*
}
void LayerProtoHelper::writeToProtoDeprecated(const ui::Transform& transform,
- TransformProto* transformProto) {
+ perfetto::protos::TransformProto* transformProto) {
const uint32_t type = transform.getType() | (transform.getOrientation() << 8);
transformProto->set_type(type);
@@ -139,7 +148,7 @@ void LayerProtoHelper::writeToProtoDeprecated(const ui::Transform& transform,
}
void LayerProtoHelper::writeTransformToProto(const ui::Transform& transform,
- TransformProto* transformProto) {
+ perfetto::protos::TransformProto* transformProto) {
const uint32_t type = transform.getType() | (transform.getOrientation() << 8);
transformProto->set_type(type);
@@ -154,12 +163,13 @@ void LayerProtoHelper::writeTransformToProto(const ui::Transform& transform,
}
}
-void LayerProtoHelper::writeToProto(const renderengine::ExternalTexture& buffer,
- std::function<ActiveBufferProto*()> getActiveBufferProto) {
+void LayerProtoHelper::writeToProto(
+ const renderengine::ExternalTexture& buffer,
+ std::function<perfetto::protos::ActiveBufferProto*()> getActiveBufferProto) {
if (buffer.getWidth() != 0 || buffer.getHeight() != 0 || buffer.getUsage() != 0 ||
buffer.getPixelFormat() != 0) {
// Use a lambda do avoid writing the object header when the object is empty
- ActiveBufferProto* activeBufferProto = getActiveBufferProto();
+ auto* activeBufferProto = getActiveBufferProto();
activeBufferProto->set_width(buffer.getWidth());
activeBufferProto->set_height(buffer.getHeight());
activeBufferProto->set_stride(buffer.getUsage());
@@ -169,21 +179,22 @@ void LayerProtoHelper::writeToProto(const renderengine::ExternalTexture& buffer,
void LayerProtoHelper::writeToProto(
const WindowInfo& inputInfo, const wp<Layer>& touchableRegionBounds,
- std::function<InputWindowInfoProto*()> getInputWindowInfoProto) {
+ std::function<perfetto::protos::InputWindowInfoProto*()> getInputWindowInfoProto) {
if (inputInfo.token == nullptr) {
return;
}
- InputWindowInfoProto* proto = getInputWindowInfoProto();
+ perfetto::protos::InputWindowInfoProto* proto = getInputWindowInfoProto();
proto->set_layout_params_flags(inputInfo.layoutParamsFlags.get());
+ proto->set_input_config(inputInfo.inputConfig.get());
using U = std::underlying_type_t<WindowInfo::Type>;
// TODO(b/129481165): This static assert can be safely removed once conversion warnings
// are re-enabled.
static_assert(std::is_same_v<U, int32_t>);
proto->set_layout_params_type(static_cast<U>(inputInfo.layoutParamsType));
- LayerProtoHelper::writeToProto({inputInfo.frameLeft, inputInfo.frameTop, inputInfo.frameRight,
- inputInfo.frameBottom},
+ LayerProtoHelper::writeToProto({inputInfo.frame.left, inputInfo.frame.top,
+ inputInfo.frame.right, inputInfo.frame.bottom},
[&]() { return proto->mutable_frame(); });
LayerProtoHelper::writeToProto(inputInfo.touchableRegion,
[&]() { return proto->mutable_touchable_region(); });
@@ -206,7 +217,8 @@ void LayerProtoHelper::writeToProto(
}
}
-void LayerProtoHelper::writeToProto(const mat4 matrix, ColorTransformProto* colorTransformProto) {
+void LayerProtoHelper::writeToProto(const mat4 matrix,
+ perfetto::protos::ColorTransformProto* colorTransformProto) {
for (int i = 0; i < mat4::ROW_SIZE; i++) {
for (int j = 0; j < mat4::COL_SIZE; j++) {
colorTransformProto->add_val(matrix[i][j]);
@@ -214,7 +226,8 @@ void LayerProtoHelper::writeToProto(const mat4 matrix, ColorTransformProto* colo
}
}
-void LayerProtoHelper::readFromProto(const ColorTransformProto& colorTransformProto, mat4& matrix) {
+void LayerProtoHelper::readFromProto(
+ const perfetto::protos::ColorTransformProto& colorTransformProto, mat4& matrix) {
for (int i = 0; i < mat4::ROW_SIZE; i++) {
for (int j = 0; j < mat4::COL_SIZE; j++) {
matrix[i][j] = colorTransformProto.val(i * mat4::COL_SIZE + j);
@@ -222,7 +235,8 @@ void LayerProtoHelper::readFromProto(const ColorTransformProto& colorTransformPr
}
}
-void LayerProtoHelper::writeToProto(const android::BlurRegion region, BlurRegion* proto) {
+void LayerProtoHelper::writeToProto(const android::BlurRegion region,
+ perfetto::protos::BlurRegion* proto) {
proto->set_blur_radius(region.blurRadius);
proto->set_corner_radius_tl(region.cornerRadiusTL);
proto->set_corner_radius_tr(region.cornerRadiusTR);
@@ -235,7 +249,8 @@ void LayerProtoHelper::writeToProto(const android::BlurRegion region, BlurRegion
proto->set_bottom(region.bottom);
}
-void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegion& outRegion) {
+void LayerProtoHelper::readFromProto(const perfetto::protos::BlurRegion& proto,
+ android::BlurRegion& outRegion) {
outRegion.blurRadius = proto.blur_radius();
outRegion.cornerRadiusTL = proto.corner_radius_tl();
outRegion.cornerRadiusTR = proto.corner_radius_tr();
@@ -247,6 +262,219 @@ void LayerProtoHelper::readFromProto(const BlurRegion& proto, android::BlurRegio
outRegion.right = proto.right();
outRegion.bottom = proto.bottom();
}
+
+perfetto::protos::LayersProto LayerProtoFromSnapshotGenerator::generate(
+ const frontend::LayerHierarchy& root) {
+ mLayersProto.clear_layers();
+ std::unordered_set<uint64_t> 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;
+ perfetto::protos::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(perfetto::protos::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<android_dataspace>(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.shadowSettings.length);
+
+ 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.val());
+
+ 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<perfetto::protos::DisplayProto>
+LayerProtoHelper::writeDisplayInfoToProto(const frontend::DisplayInfos& displayInfos) {
+ google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> 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 6ade1435e5..20c226006c 100644
--- a/services/surfaceflinger/LayerProtoHelper.h
+++ b/services/surfaceflinger/LayerProtoHelper.h
@@ -25,39 +25,90 @@
#include <ui/Rect.h>
#include <ui/Region.h>
#include <ui/Transform.h>
+#include <cstdint>
+
+#include "FrontEnd/DisplayInfo.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerSnapshot.h"
namespace android {
namespace surfaceflinger {
class LayerProtoHelper {
public:
- static void writePositionToProto(const float x, const float y,
- std::function<PositionProto*()> getPositionProto);
+ static void writePositionToProto(
+ const float x, const float y,
+ std::function<perfetto::protos::PositionProto*()> getPositionProto);
static void writeSizeToProto(const uint32_t w, const uint32_t h,
- std::function<SizeProto*()> getSizeProto);
- static void writeToProto(const Rect& rect, std::function<RectProto*()> getRectProto);
- static void writeToProto(const Rect& rect, RectProto* rectProto);
- static void readFromProto(const RectProto& proto, Rect& outRect);
+ std::function<perfetto::protos::SizeProto*()> getSizeProto);
+ static void writeToProto(const Rect& rect,
+ std::function<perfetto::protos::RectProto*()> getRectProto);
+ static void writeToProto(const Rect& rect, perfetto::protos::RectProto* rectProto);
+ static void readFromProto(const perfetto::protos::RectProto& proto, Rect& outRect);
static void writeToProto(const FloatRect& rect,
- std::function<FloatRectProto*()> getFloatRectProto);
- static void writeToProto(const Region& region, std::function<RegionProto*()> getRegionProto);
- static void writeToProto(const Region& region, RegionProto* regionProto);
- static void readFromProto(const RegionProto& regionProto, Region& outRegion);
- static void writeToProto(const half4 color, std::function<ColorProto*()> getColorProto);
+ std::function<perfetto::protos::FloatRectProto*()> getFloatRectProto);
+ static void writeToProto(const Region& region,
+ std::function<perfetto::protos::RegionProto*()> getRegionProto);
+ static void writeToProto(const Region& region, perfetto::protos::RegionProto* regionProto);
+ static void readFromProto(const perfetto::protos::RegionProto& regionProto, Region& outRegion);
+ static void writeToProto(const half4 color,
+ std::function<perfetto::protos::ColorProto*()> getColorProto);
// This writeToProto for transform is incorrect, but due to backwards compatibility, we can't
// update Layers to use it. Use writeTransformToProto for any new transform proto data.
static void writeToProtoDeprecated(const ui::Transform& transform,
- TransformProto* transformProto);
+ perfetto::protos::TransformProto* transformProto);
static void writeTransformToProto(const ui::Transform& transform,
- TransformProto* transformProto);
- static void writeToProto(const renderengine::ExternalTexture& buffer,
- std::function<ActiveBufferProto*()> getActiveBufferProto);
- static void writeToProto(const gui::WindowInfo& inputInfo,
- const wp<Layer>& touchableRegionBounds,
- std::function<InputWindowInfoProto*()> getInputWindowInfoProto);
- static void writeToProto(const mat4 matrix, ColorTransformProto* colorTransformProto);
- 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);
+ perfetto::protos::TransformProto* transformProto);
+ static void writeToProto(
+ const renderengine::ExternalTexture& buffer,
+ std::function<perfetto::protos::ActiveBufferProto*()> getActiveBufferProto);
+ static void writeToProto(
+ const gui::WindowInfo& inputInfo, const wp<Layer>& touchableRegionBounds,
+ std::function<perfetto::protos::InputWindowInfoProto*()> getInputWindowInfoProto);
+ static void writeToProto(const mat4 matrix,
+ perfetto::protos::ColorTransformProto* colorTransformProto);
+ static void readFromProto(const perfetto::protos::ColorTransformProto& colorTransformProto,
+ mat4& matrix);
+ static void writeToProto(const android::BlurRegion region, perfetto::protos::BlurRegion*);
+ static void readFromProto(const perfetto::protos::BlurRegion& proto,
+ android::BlurRegion& outRegion);
+ static void writeSnapshotToProto(perfetto::protos::LayerProto* outProto,
+ const frontend::RequestedLayerState& requestedState,
+ const frontend::LayerSnapshot& snapshot, uint32_t traceFlags);
+ static google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
+ writeDisplayInfoToProto(const frontend::DisplayInfos&);
+};
+
+class LayerProtoFromSnapshotGenerator {
+public:
+ LayerProtoFromSnapshotGenerator(const frontend::LayerSnapshotBuilder& snapshotBuilder,
+ const frontend::DisplayInfos& displayInfos,
+ const std::unordered_map<uint32_t, sp<Layer>>& legacyLayers,
+ uint32_t traceFlags)
+ : mSnapshotBuilder(snapshotBuilder),
+ mLegacyLayers(legacyLayers),
+ mDisplayInfos(displayInfos),
+ mTraceFlags(traceFlags) {}
+ perfetto::protos::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<uint32_t, sp<Layer>>& mLegacyLayers;
+ const frontend::DisplayInfos& mDisplayInfos;
+ uint32_t mTraceFlags;
+ perfetto::protos::LayersProto mLayersProto;
+ // winscope expects all the layers, so provide a snapshot even if it not currently drawing
+ std::unordered_map<frontend::LayerHierarchy::TraversalPath, frontend::LayerSnapshot,
+ frontend::LayerHierarchy::TraversalPathHash>
+ mDefaultSnapshots;
+ std::unordered_map<uint32_t /* child unique seq*/, uint32_t /* relative parent unique seq*/>
+ mChildToRelativeParent;
+ std::unordered_map<uint32_t /* child unique seq*/, uint32_t /* parent unique seq*/>
+ mChildToParent;
};
} // namespace surfaceflinger
diff --git a/services/surfaceflinger/LayerRejecter.cpp b/services/surfaceflinger/LayerRejecter.cpp
deleted file mode 100644
index 1c0263ba0b..0000000000
--- 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 <gui/BufferItem.h>
-#include <system/window.h>
-
-#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<GraphicBuffer>& 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 4981f451d9..0000000000
--- 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<GraphicBuffer>&, 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 896f25404d..51d4ff854f 100644
--- a/services/surfaceflinger/LayerRenderArea.cpp
+++ b/services/surfaceflinger/LayerRenderArea.cpp
@@ -17,8 +17,8 @@
#include <ui/GraphicTypes.h>
#include <ui/Transform.h>
-#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<Layer>& oldParent, const sp<Layer>& 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<Layer>& oldParent, const sp<Layer>& newParent,
LayerRenderArea::LayerRenderArea(SurfaceFlinger& flinger, sp<Layer> 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<const DisplayDevice> 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<void()> 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<void()> 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<ContainerLayer> 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<void()> 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 41273e01c1..aa609eea38 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> 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<const DisplayDevice> getDisplayDevice() const override;
Rect getSourceCrop() const override;
@@ -50,10 +47,11 @@ public:
private:
const sp<Layer> 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 0000000000..6b633607ae
--- /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 <atomic>
+#include <optional>
+
+template <typename T>
+// 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<Entry*> mNext;
+ Entry(T value) : mValue(value) {}
+ };
+ std::atomic<Entry*> mPush = nullptr;
+ std::atomic<Entry*> 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<T> 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 df76f50112..0000000000
--- 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<IGraphicBufferProducer>& producer,
- const sp<SurfaceFlinger>& flinger,
- const wp<Layer>& layer) :
- mProducer(producer),
- mFlinger(flinger),
- mLayer(layer) {}
-
-MonitoredProducer::~MonitoredProducer() {}
-
-status_t MonitoredProducer::requestBuffer(int slot, sp<GraphicBuffer>* 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>* 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<GraphicBuffer>* outBuffer,
- sp<Fence>* outFence) {
- return mProducer->detachNextBuffer(outBuffer, outFence);
-}
-
-status_t MonitoredProducer::attachBuffer(int* outSlot,
- const sp<GraphicBuffer>& 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>& fence) {
- return mProducer->cancelBuffer(slot, fence);
-}
-
-int MonitoredProducer::query(int what, int* value) {
- return mProducer->query(what, value);
-}
-
-status_t MonitoredProducer::connect(const sp<IProducerListener>& 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<NativeHandle>& 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<GraphicBuffer>* outBuffer,
- sp<Fence>* outFence, float outTransformMatrix[16]) {
- return mProducer->getLastQueuedBuffer(outBuffer, outFence,
- outTransformMatrix);
-}
-
-status_t MonitoredProducer::getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* 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<Layer> 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 3778277fd3..0000000000
--- 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 <gui/IGraphicBufferProducer.h>
-
-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<IGraphicBufferProducer>& producer,
- const sp<SurfaceFlinger>& flinger,
- const wp<Layer>& layer);
- virtual ~MonitoredProducer();
-
- // From IGraphicBufferProducer
- virtual status_t requestBuffer(int slot, sp<GraphicBuffer>* buf);
- virtual status_t setMaxDequeuedBufferCount(int maxDequeuedBuffers);
- virtual status_t setAsyncMode(bool async);
- virtual status_t dequeueBuffer(int* slot, sp<Fence>* 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<GraphicBuffer>* outBuffer,
- sp<Fence>* outFence);
- virtual status_t attachBuffer(int* outSlot,
- const sp<GraphicBuffer>& buffer);
- virtual status_t queueBuffer(int slot, const QueueBufferInput& input,
- QueueBufferOutput* output);
- virtual status_t cancelBuffer(int slot, const sp<Fence>& fence);
- virtual int query(int what, int* value);
- virtual status_t connect(const sp<IProducerListener>& token, int api,
- bool producerControlledByApp, QueueBufferOutput* output);
- virtual status_t disconnect(int api, DisconnectMode mode);
- virtual status_t setSidebandStream(const sp<NativeHandle>& 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<GraphicBuffer>* outBuffer,
- sp<Fence>* outFence, float outTransformMatrix[16]) override;
- virtual status_t getLastQueuedBuffer(sp<GraphicBuffer>* outBuffer, sp<Fence>* 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<Layer> getLayer() const;
-
-private:
- sp<IGraphicBufferProducer> mProducer;
- sp<SurfaceFlinger> mFlinger;
- // The Layer which created this producer, and on which queued Buffer's will be displayed.
- wp<Layer> mLayer;
-};
-
-}; // namespace android
-
-#endif // ANDROID_MONITORED_PRODUCER_H
diff --git a/services/surfaceflinger/MutexUtils.h b/services/surfaceflinger/MutexUtils.h
index f8be6f3b85..58f7cb4c43 100644
--- a/services/surfaceflinger/MutexUtils.h
+++ b/services/surfaceflinger/MutexUtils.h
@@ -50,4 +50,14 @@ struct SCOPED_CAPABILITY TimedLock {
const status_t status;
};
+// Require, under penalty of compilation failure, that the compiler thinks that a mutex is held.
+#define REQUIRE_MUTEX(expr) ([]() REQUIRES(expr) {})()
+
+// Tell the compiler that we know that a mutex is held.
+#define ASSERT_MUTEX(expr) ([]() ASSERT_CAPABILITY(expr) {})()
+
+// Specify that one mutex is an alias for another.
+// (e.g. SurfaceFlinger::mStateLock and Layer::mFlinger->mStateLock)
+#define MUTEX_ALIAS(held, alias) (REQUIRE_MUTEX(held), ASSERT_MUTEX(alias))
+
} // namespace android
diff --git a/services/surfaceflinger/NativeWindowSurface.cpp b/services/surfaceflinger/NativeWindowSurface.cpp
index 3fff9283ee..a6a3eec8ff 100644
--- a/services/surfaceflinger/NativeWindowSurface.cpp
+++ b/services/surfaceflinger/NativeWindowSurface.cpp
@@ -30,7 +30,7 @@ std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
class NativeWindowSurface final : public surfaceflinger::NativeWindowSurface {
public:
explicit NativeWindowSurface(const sp<IGraphicBufferProducer>& producer)
- : mSurface(new Surface(producer, /* controlledByApp */ false)) {}
+ : mSurface(sp<Surface>::make(producer, /* controlledByApp */ false)) {}
~NativeWindowSurface() override = default;
diff --git a/services/surfaceflinger/OWNERS b/services/surfaceflinger/OWNERS
index 6011d0d0f7..ffc1dd7979 100644
--- a/services/surfaceflinger/OWNERS
+++ b/services/surfaceflinger/OWNERS
@@ -1,8 +1,14 @@
+# Bug component: 1075131
+
adyabr@google.com
alecmouri@google.com
-chaviw@google.com
+domlaskowski@google.com
+jreck@google.com
lpy@google.com
pdwilliams@google.com
racarr@google.com
+ramindani@google.com
+rnlee@google.com
+sallyqi@google.com
scroggo@google.com
vishnun@google.com
diff --git a/services/surfaceflinger/RefreshRateOverlay.cpp b/services/surfaceflinger/RefreshRateOverlay.cpp
index a9180d4483..b960e33682 100644
--- a/services/surfaceflinger/RefreshRateOverlay.cpp
+++ b/services/surfaceflinger/RefreshRateOverlay.cpp
@@ -16,121 +16,22 @@
#include <algorithm>
-#include "BackgroundExecutor.h"
+#include <common/FlagManager.h>
#include "Client.h"
#include "Layer.h"
#include "RefreshRateOverlay.h"
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wconversion"
-#include <SkCanvas.h>
-#include <SkPaint.h>
-#pragma clang diagnostic pop
-#include <SkBlendMode.h>
-#include <SkRect.h>
#include <SkSurface.h>
-#include <gui/SurfaceComposerClient.h>
-#include <gui/SurfaceControl.h>
#undef LOG_TAG
#define LOG_TAG "RefreshRateOverlay"
namespace android {
-namespace {
-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 kBufferHeight = kDigitHeight;
-
-SurfaceComposerClient::Transaction createTransaction(const sp<SurfaceControl>& 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() {
- // Hand the sp<SurfaceControl> to the helper thread to release the last
- // reference. This makes sure that the SurfaceControl is destructed without
- // SurfaceFlinger::mStateLock held.
- BackgroundExecutor::getInstance().sendCallbacks(
- {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawSegment(Segment segment, int left, SkColor color,
- SkCanvas& canvas) {
- const SkRect rect = [&]() {
- switch (segment) {
- case Segment::Upper:
- return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
- case Segment::UpperLeft:
- return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2);
- case Segment::UpperRight:
- return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
- kDigitHeight / 2);
- case Segment::Middle:
- return SkRect::MakeLTRB(left, kDigitHeight / 2 - kDigitSpace / 2,
- left + kDigitWidth, kDigitHeight / 2 + kDigitSpace / 2);
- case Segment::LowerLeft:
- return SkRect::MakeLTRB(left, kDigitHeight / 2, left + kDigitSpace, kDigitHeight);
- case Segment::LowerRight:
- return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2,
- left + kDigitWidth, kDigitHeight);
- case Segment::Bottom:
- return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
- kDigitHeight);
- }
- }();
-
- SkPaint paint;
- paint.setColor(color);
- paint.setBlendMode(SkBlendMode::kSrc);
- canvas.drawRect(rect, paint);
-}
-
-void RefreshRateOverlay::SevenSegmentDrawer::drawDigit(int digit, int left, SkColor color,
- SkCanvas& canvas) {
- if (digit < 0 || digit > 9) return;
-
- if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
- digit == 8 || digit == 9)
- drawSegment(Segment::Upper, left, color, canvas);
- if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
- drawSegment(Segment::UpperLeft, left, color, canvas);
- if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
- digit == 8 || digit == 9)
- drawSegment(Segment::UpperRight, left, color, canvas);
- if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
- digit == 9)
- drawSegment(Segment::Middle, left, color, canvas);
- if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
- drawSegment(Segment::LowerLeft, left, color, canvas);
- if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
- digit == 7 || digit == 8 || digit == 9)
- drawSegment(Segment::LowerRight, left, color, canvas);
- if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
- digit == 9)
- drawSegment(Segment::Bottom, left, color, canvas);
-}
-
-auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, 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;
+auto RefreshRateOverlay::draw(int vsyncRate, int renderFps, SkColor color,
+ ui::Transform::RotationFlags rotation, ftl::Flags<Features> features)
+ -> Buffers {
+ const size_t loopCount = features.test(Features::Spinner) ? 6 : 1;
Buffers buffers;
buffers.reserve(loopCount);
@@ -152,59 +53,61 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color,
}
}();
- sp<GraphicBuffer> buffer =
- new GraphicBuffer(static_cast<uint32_t>(bufferWidth),
- static_cast<uint32_t>(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<uint64_t>(GRALLOC_USAGE_SW_WRITE_RARELY | GRALLOC_USAGE_HW_COMPOSER |
+ GRALLOC_USAGE_HW_TEXTURE);
+ sp<GraphicBuffer> buffer = sp<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+ static_cast<uint32_t>(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",
bufferStatus);
- sk_sp<SkSurface> surface = SkSurface::MakeRasterN32Premul(bufferWidth, bufferHeight);
+ sk_sp<SkSurface> surface = SkSurfaces::Raster(
+ SkImageInfo::MakeN32Premul(bufferWidth, bufferHeight));
SkCanvas* canvas = surface->getCanvas();
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(vsyncRate, left, color, *canvas);
+ left += 3 * (kDigitWidth + kDigitSpace);
+ if (features.test(Features::Spinner)) {
switch (i) {
case 0:
- drawSegment(Segment::Upper, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::Upper, left, color, *canvas);
break;
case 1:
- drawSegment(Segment::UpperRight, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperRight, left, color,
+ *canvas);
break;
case 2:
- drawSegment(Segment::LowerRight, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerRight, left, color,
+ *canvas);
break;
case 3:
- drawSegment(Segment::Bottom, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::Bottom, left, color,
+ *canvas);
break;
case 4:
- drawSegment(Segment::LowerLeft, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::LowerLeft, left, color,
+ *canvas);
break;
case 5:
- drawSegment(Segment::UpperLeft, left, color, *canvas);
+ SegmentDrawer::drawSegment(SegmentDrawer::Segment::UpperLeft, left, color,
+ *canvas);
break;
}
}
+ 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<void**>(&pixels));
@@ -219,34 +122,64 @@ auto RefreshRateOverlay::SevenSegmentDrawer::draw(int number, SkColor color,
return buffers;
}
-std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder() {
- sp<SurfaceControl> surfaceControl =
- SurfaceComposerClient::getDefault()
- ->createSurface(String8("RefreshRateOverlay"), kBufferWidth, kBufferHeight,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceBufferState);
- return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+void RefreshRateOverlay::drawNumber(int number, int left, SkColor color, SkCanvas& canvas) {
+ if (number < 0 || number >= 1000) return;
+
+ if (number >= 100) {
+ SegmentDrawer::drawDigit(number / 100, left, color, canvas);
+ }
+ left += kDigitWidth + kDigitSpace;
+
+ if (number >= 10) {
+ SegmentDrawer::drawDigit((number / 10) % 10, left, color, canvas);
+ }
+ left += kDigitWidth + kDigitSpace;
+
+ SegmentDrawer::drawDigit(number % 10, left, color, canvas);
}
-RefreshRateOverlay::RefreshRateOverlay(FpsRange fpsRange, bool showSpinner)
+std::unique_ptr<RefreshRateOverlay> RefreshRateOverlay::create(FpsRange range,
+ ftl::Flags<Features> features) {
+ std::unique_ptr<RefreshRateOverlay> overlay =
+ std::make_unique<RefreshRateOverlay>(ConstructorTag{}, range, features);
+ if (overlay->initCheck()) {
+ return overlay;
+ }
+
+ ALOGE("%s: Failed to create RefreshRateOverlay", __func__);
+ return {};
+}
+
+RefreshRateOverlay::RefreshRateOverlay(ConstructorTag, FpsRange fpsRange,
+ ftl::Flags<Features> features)
: mFpsRange(fpsRange),
- mShowSpinner(showSpinner),
- mSurfaceControl(createSurfaceControlHolder()) {
+ mFeatures(features),
+ mSurfaceControl(
+ SurfaceControlHolder::createSurfaceControlHolder(String8("RefreshRateOverlay"))) {
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& {
+bool RefreshRateOverlay::initCheck() const {
+ return mSurfaceControl != nullptr;
+}
+
+auto RefreshRateOverlay::getOrCreateBuffers(Fps vsyncRate, 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<ui::Transform::RotationFlags>(mSurfaceControl->get()->getTransformHint());
@@ -262,21 +195,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({vsyncRate.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 vsyncRate may be outside of this range if the display
+ // has changed its set of supported refresh rates.
+ const int displayIntFps = std::clamp(vsyncRate.getIntValue(), minFps, maxFps);
+ const int renderIntFps = renderFps.getIntValue();
// Ensure non-zero range to avoid division by zero.
- const float fpsScale = static_cast<float>(intFps - minFps) / std::max(1, maxFps - minFps);
+ const float fpsScale =
+ static_cast<float>(displayIntFps - minFps) / std::max(1, maxFps - minFps);
constexpr SkColor kMinFpsColor = SK_ColorRED;
constexpr SkColor kMaxFpsColor = SK_ColorGREEN;
@@ -292,8 +228,10 @@ 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 = draw(displayIntFps, renderIntFps, color, transformHint, mFeatures);
+ it = mBufferCache
+ .try_emplace({displayIntFps, renderIntFps, transformHint}, std::move(buffers))
+ .first;
}
return it->second;
@@ -303,10 +241,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);
+
+ if (!mFeatures.test(Features::ShowInMiddle)) {
+ frame.offsetBy(width >> 5, height >> 4);
+ } else {
+ frame.offsetBy(width >> 1, height >> 4);
+ }
- createTransaction(mSurfaceControl->get())
+ createTransaction()
.setMatrix(mSurfaceControl->get(), frame.getWidth() / static_cast<float>(kBufferWidth),
0, 0, frame.getHeight() / static_cast<float>(kBufferHeight))
.setPosition(mSurfaceControl->get(), frame.left, frame.top)
@@ -314,22 +257,49 @@ 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 vsyncRate, Fps renderFps) {
+ mVsyncRate = vsyncRate;
+ mRenderFps = renderFps;
+ const auto buffer = getOrCreateBuffers(vsyncRate, renderFps)[mFrame];
+ createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
+}
+
+void RefreshRateOverlay::changeRenderRate(Fps renderFps) {
+ if (mFeatures.test(Features::RenderRate) && mVsyncRate && FlagManager::getInstance().misc1()) {
+ mRenderFps = renderFps;
+ const auto buffer = getOrCreateBuffers(*mVsyncRate, renderFps)[mFrame];
+ createTransaction().setBuffer(mSurfaceControl->get(), buffer).apply();
+ }
}
void RefreshRateOverlay::animate() {
- if (!mShowSpinner || !mCurrentFps) return;
+ if (!mFeatures.test(Features::Spinner) || !mVsyncRate) return;
- const auto& buffers = getOrCreateBuffers(*mCurrentFps);
+ const auto& buffers = getOrCreateBuffers(*mVsyncRate, *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<SurfaceControl>& 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 a2966e654e..0fec470edc 100644
--- a/services/surfaceflinger/RefreshRateOverlay.h
+++ b/services/surfaceflinger/RefreshRateOverlay.h
@@ -16,9 +16,11 @@
#pragma once
-#include <SkColor.h>
+#include "Utils/OverlayUtils.h"
+
#include <vector>
+#include <ftl/flags.h>
#include <ftl/small_map.h>
#include <ui/LayerStack.h>
#include <ui/Size.h>
@@ -32,62 +34,65 @@ class SkCanvas;
namespace android {
class GraphicBuffer;
-class SurfaceControl;
class SurfaceFlinger;
-// Helper class to delete the SurfaceControl on a helper thread as
-// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
-class SurfaceControlHolder {
-public:
- explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
- ~SurfaceControlHolder();
-
- const sp<SurfaceControl>& get() const { return mSurfaceControl; }
-
+class RefreshRateOverlay {
private:
- sp<SurfaceControl> mSurfaceControl;
-};
+ // Effectively making the constructor private, while keeping std::make_unique work
+ struct ConstructorTag {};
-class RefreshRateOverlay {
public:
- RefreshRateOverlay(FpsRange, bool showSpinner);
+ enum class Features {
+ Spinner = 1 << 0,
+ RenderRate = 1 << 1,
+ ShowInMiddle = 1 << 2,
+ SetByHwc = 1 << 3,
+ };
+
+ static std::unique_ptr<RefreshRateOverlay> create(FpsRange, ftl::Flags<Features>);
void setLayerStack(ui::LayerStack);
void setViewport(ui::Size);
- void changeRefreshRate(Fps);
+ void changeRefreshRate(Fps, Fps);
+ void changeRenderRate(Fps);
void animate();
+ bool isSetByHwc() const { return mFeatures.test(RefreshRateOverlay::Features::SetByHwc); }
+
+ RefreshRateOverlay(ConstructorTag, FpsRange, ftl::Flags<Features>);
private:
- using Buffers = std::vector<sp<GraphicBuffer>>;
+ bool initCheck() const;
- class SevenSegmentDrawer {
- public:
- static Buffers draw(int number, SkColor, ui::Transform::RotationFlags, bool showSpinner);
+ using Buffers = std::vector<sp<GraphicBuffer>>;
- private:
- enum class Segment { Upper, UpperLeft, UpperRight, Middle, LowerLeft, LowerRight, Bottom };
+ static Buffers draw(int vsyncRate, int renderFps, SkColor, ui::Transform::RotationFlags,
+ ftl::Flags<Features>);
+ static void drawNumber(int number, int left, SkColor, SkCanvas&);
- static void drawSegment(Segment, int left, SkColor, SkCanvas&);
- static void drawDigit(int digit, int left, SkColor, SkCanvas&);
- };
+ const Buffers& getOrCreateBuffers(Fps, Fps);
- const Buffers& getOrCreateBuffers(Fps);
+ SurfaceComposerClient::Transaction createTransaction() const;
struct Key {
- int fps;
+ int vsyncRate;
+ int renderFps;
ui::Transform::RotationFlags flags;
- bool operator==(Key other) const { return fps == other.fps && flags == other.flags; }
+ bool operator==(Key other) const {
+ return vsyncRate == other.vsyncRate && renderFps == other.renderFps &&
+ flags == other.flags;
+ }
};
using BufferCache = ftl::SmallMap<Key, Buffers, 9>;
BufferCache mBufferCache;
- std::optional<Fps> mCurrentFps;
+ std::optional<Fps> mVsyncRate;
+ std::optional<Fps> mRenderFps;
size_t mFrame = 0;
const FpsRange mFpsRange; // For color interpolation.
- const bool mShowSpinner;
+ const ftl::Flags<Features> mFeatures;
const std::unique_ptr<SurfaceControlHolder> mSurfaceControl;
};
diff --git a/services/surfaceflinger/RegionSamplingThread.cpp b/services/surfaceflinger/RegionSamplingThread.cpp
index 2487dbd793..c888ccc8ae 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<Layer>& stopLayer,
+void RegionSamplingThread::addListener(const Rect& samplingArea, uint32_t stopLayerId,
const sp<IRegionSamplingListener>& listener) {
sp<IBinder> asBinder = IInterface::asBinder(listener);
- asBinder->linkToDeath(this);
+ asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
std::lock_guard lock(mSamplingMutex);
- mDescriptors.emplace(wp<IBinder>(asBinder), Descriptor{samplingArea, stopLayer, listener});
+ mDescriptors.emplace(wp<IBinder>(asBinder), Descriptor{samplingArea, stopLayerId, listener});
}
void RegionSamplingThread::removeListener(const sp<IRegionSamplingListener>& listener) {
@@ -203,25 +204,14 @@ float sampleArea(const uint32_t* data, int32_t width, int32_t height, int32_t st
return 0.0f;
}
- // (b/133849373) ROT_90 screencap images produced upside down
- auto area = sample_area;
- if (orientation & ui::Transform::ROT_90) {
- area.top = height - area.top;
- area.bottom = height - area.bottom;
- std::swap(area.top, area.bottom);
-
- area.left = width - area.left;
- area.right = width - area.right;
- std::swap(area.left, area.right);
- }
-
- const uint32_t pixelCount = (area.bottom - area.top) * (area.right - area.left);
+ const uint32_t pixelCount =
+ (sample_area.bottom - sample_area.top) * (sample_area.right - sample_area.left);
uint32_t accumulatedLuma = 0;
// Calculates luma with approximation of Rec. 709 primaries
- for (int32_t row = area.top; row < area.bottom; ++row) {
+ for (int32_t row = sample_area.top; row < sample_area.bottom; ++row) {
const uint32_t* rowBase = data + row * stride;
- for (int32_t column = area.left; column < area.right; ++column) {
+ for (int32_t column = sample_area.left; column < sample_area.right; ++column) {
uint32_t pixel = rowBase[column];
const uint32_t r = pixel & 0xFF;
const uint32_t g = (pixel >> 8) & 0xFF;
@@ -286,56 +276,83 @@ 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, kHintForSeamlessTransition);
});
std::unordered_set<sp<IRegionSamplingListener>, SpHash<IRegionSamplingListener>> 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<std::vector<std::pair<Layer*, sp<LayerFE>>>()> 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<renderengine::ExternalTexture> buffer = nullptr;
if (mCachedBuffer && mCachedBuffer->getBuffer()->getWidth() == sampledBounds.getWidth() &&
mCachedBuffer->getBuffer()->getHeight() == sampledBounds.getHeight()) {
@@ -344,8 +361,8 @@ void RegionSamplingThread::captureSample() {
const uint32_t usage =
GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
sp<GraphicBuffer> graphicBuffer =
- new GraphicBuffer(sampledBounds.getWidth(), sampledBounds.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 1, usage, "RegionSamplingThread");
+ sp<GraphicBuffer>::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);
@@ -357,10 +374,11 @@ void RegionSamplingThread::captureSample() {
constexpr bool kRegionSampling = true;
constexpr bool kGrayscale = false;
+ constexpr bool kIsProtected = false;
if (const auto fenceResult =
- mFlinger.captureScreenCommon(std::move(renderAreaFuture), traverseLayers, buffer,
- kRegionSampling, kGrayscale, nullptr)
+ mFlinger.captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, buffer,
+ kRegionSampling, kGrayscale, kIsProtected, nullptr)
.get();
fenceResult.ok()) {
fenceResult.value()->waitForever(LOG_TAG);
diff --git a/services/surfaceflinger/RegionSamplingThread.h b/services/surfaceflinger/RegionSamplingThread.h
index 686b4b1e1f..e8c891e4bf 100644
--- a/services/surfaceflinger/RegionSamplingThread.h
+++ b/services/surfaceflinger/RegionSamplingThread.h
@@ -26,6 +26,7 @@
#include <chrono>
#include <condition_variable>
+#include <cstdint>
#include <mutex>
#include <thread>
#include <unordered_map>
@@ -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<Layer>& stopLayer,
+ void addListener(const Rect& samplingArea, uint32_t stopLayerId,
const sp<IRegionSamplingListener>& listener);
// Remove the listener to stop receiving median luma notifications.
void removeListener(const sp<IRegionSamplingListener>& listener);
@@ -87,7 +87,7 @@ public:
private:
struct Descriptor {
Rect area = Rect::EMPTY_RECT;
- wp<Layer> stopLayer;
+ uint32_t stopLayerId;
sp<IRegionSamplingListener> listener;
};
diff --git a/services/surfaceflinger/RenderArea.h b/services/surfaceflinger/RenderArea.h
index 387364c03a..5de148e3bd 100644
--- a/services/surfaceflinger/RenderArea.h
+++ b/services/surfaceflinger/RenderArea.h
@@ -18,21 +18,32 @@ class DisplayDevice;
// physical render area.
class RenderArea {
public:
- using RotationFlags = ui::Transform::RotationFlags;
-
enum class CaptureFill {CLEAR, OPAQUE};
static float getCaptureFillValue(CaptureFill captureFill);
RenderArea(ui::Size reqSize, CaptureFill captureFill, ui::Dataspace reqDataSpace,
- const Rect& layerStackRect, bool allowSecureLayers = false,
- RotationFlags rotation = ui::Transform::ROT_0)
+ bool hintForSeamlessTransition, bool allowSecureLayers = false)
: mAllowSecureLayers(allowSecureLayers),
mReqSize(reqSize),
mReqDataSpace(reqDataSpace),
mCaptureFill(captureFill),
- mRotationFlags(rotation),
- mLayerStackSpaceRect(layerStackRect) {}
+ mHintForSeamlessTransition(hintForSeamlessTransition) {}
+
+ static std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> fromTraverseLayersLambda(
+ std::function<void(const LayerVector::Visitor&)> traverseLayers) {
+ return [traverseLayers = std::move(traverseLayers)]() {
+ std::vector<std::pair<Layer*, sp<LayerFE>>> 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 +54,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
@@ -67,9 +68,6 @@ public:
// on the display).
virtual Rect getSourceCrop() const = 0;
- // Returns the rotation of the source crop and the layers.
- RotationFlags getRotationFlags() const { return mRotationFlags; }
-
// Returns the size of the physical render area.
int getReqWidth() const { return mReqSize.width; }
int getReqHeight() const { return mReqSize.height; }
@@ -83,13 +81,14 @@ public:
virtual sp<const DisplayDevice> 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<Layer> 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;
@@ -97,8 +96,7 @@ private:
const ui::Size mReqSize;
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 5de796d742..d71484889d 100644
--- a/services/surfaceflinger/Scheduler/Android.bp
+++ b/services/surfaceflinger/Scheduler/Android.bp
@@ -18,8 +18,10 @@ cc_defaults {
"libbase",
"libcutils",
"liblog",
+ "libui",
"libutils",
],
+ static_libs: ["libsurfaceflinger_common"],
}
cc_library_headers {
@@ -39,6 +41,8 @@ cc_library_static {
name: "libscheduler",
defaults: ["libscheduler_defaults"],
srcs: [
+ "src/FrameTargeter.cpp",
+ "src/PresentLatencyTracker.cpp",
"src/Timer.cpp",
],
local_include_dirs: ["include"],
@@ -50,14 +54,17 @@ cc_test {
test_suites: ["device-tests"],
defaults: ["libscheduler_defaults"],
srcs: [
+ "tests/FrameTargeterTest.cpp",
+ "tests/PresentLatencyTrackerTest.cpp",
"tests/TimerTest.cpp",
],
static_libs: [
"libgmock",
"libgtest",
"libscheduler",
+ "libsurfaceflingerflags_test",
+ ],
+ shared_libs: [
+ "server_configurable_flags",
],
- sanitize: {
- address: true,
- },
}
diff --git a/services/surfaceflinger/Scheduler/DispSyncSource.cpp b/services/surfaceflinger/Scheduler/DispSyncSource.cpp
deleted file mode 100644
index 4af1f5c65f..0000000000
--- 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 <android-base/stringprintf.h>
-#include <utils/Trace.h>
-#include <mutex>
-
-#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<CallbackRepeater>(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 edcd3ac709..0000000000
--- 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 <mutex>
-#include <string>
-
-#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<int> mValue;
-
- const bool mTraceVsync;
- const std::string mVsyncOnLabel;
-
- const VSyncTracker& mVSyncTracker;
-
- std::unique_ptr<CallbackRepeater> mCallbackRepeater;
-
- std::mutex mCallbackMutex;
- VSyncSource::Callback* mCallback GUARDED_BY(mCallbackMutex) = nullptr;
-
- mutable std::mutex mVsyncMutex;
- TracedOrdinal<std::chrono::nanoseconds> 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 639ba5a3f1..693a357de5 100644
--- a/services/surfaceflinger/Scheduler/EventThread.cpp
+++ b/services/surfaceflinger/Scheduler/EventThread.cpp
@@ -28,6 +28,7 @@
#include <cstdint>
#include <optional>
#include <type_traits>
+#include <utility>
#include <android-base/stringprintf.h>
@@ -37,12 +38,17 @@
#include <cutils/sched_policy.h>
#include <gui/DisplayEventReceiver.h>
+#include <gui/SchedulingPolicy.h>
#include <utils/Errors.h>
#include <utils/Trace.h>
+#include <common/FlagManager.h>
+#include <scheduler/VsyncConfig.h>
#include "DisplayHardware/DisplayMode.h"
#include "FrameTimeline.h"
+#include "VSyncDispatch.h"
+#include "VSyncTracker.h"
#include "EventThread.h"
@@ -107,6 +113,15 @@ DisplayEventReceiver::Event makeHotplug(PhysicalDisplayId displayId, nsecs_t tim
return event;
}
+DisplayEventReceiver::Event makeHotplugError(nsecs_t timestamp, int32_t connectionError) {
+ DisplayEventReceiver::Event event;
+ PhysicalDisplayId unusedDisplayId;
+ event.header = {DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG, unusedDisplayId, timestamp};
+ event.hotplug.connected = false;
+ event.hotplug.connectionError = connectionError;
+ return event;
+}
+
DisplayEventReceiver::Event makeVSync(PhysicalDisplayId displayId, nsecs_t timestamp,
uint32_t count, nsecs_t expectedPresentationTime,
nsecs_t deadlineTimestamp) {
@@ -124,12 +139,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,11 +172,9 @@ DisplayEventReceiver::Event makeFrameRateOverrideFlushEvent(PhysicalDisplayId di
} // namespace
-EventThreadConnection::EventThreadConnection(
- EventThread* eventThread, uid_t callingUid, ResyncCallback resyncCallback,
- ISurfaceComposer::EventRegistrationFlags eventRegistration)
- : resyncCallback(std::move(resyncCallback)),
- mOwnerUid(callingUid),
+EventThreadConnection::EventThreadConnection(EventThread* eventThread, uid_t callingUid,
+ EventRegistrationFlags eventRegistration)
+ : mOwnerUid(callingUid),
mEventRegistration(eventRegistration),
mEventThread(eventThread),
mChannel(gui::BitTube::DefaultSize) {}
@@ -173,7 +186,7 @@ EventThreadConnection::~EventThreadConnection() {
void EventThreadConnection::onFirstRef() {
// NOTE: mEventThread doesn't hold a strong reference on us
- mEventThread->registerDisplayEventConnection(this);
+ mEventThread->registerDisplayEventConnection(sp<EventThreadConnection>::fromExisting(this));
}
binder::Status EventThreadConnection::stealReceiveChannel(gui::BitTube* outChannel) {
@@ -188,23 +201,29 @@ binder::Status EventThreadConnection::stealReceiveChannel(gui::BitTube* outChann
}
binder::Status EventThreadConnection::setVsyncRate(int rate) {
- mEventThread->setVsyncRate(static_cast<uint32_t>(rate), this);
+ mEventThread->setVsyncRate(static_cast<uint32_t>(rate),
+ sp<EventThreadConnection>::fromExisting(this));
return binder::Status::ok();
}
binder::Status EventThreadConnection::requestNextVsync() {
ATRACE_CALL();
- mEventThread->requestNextVsync(this);
+ mEventThread->requestNextVsync(sp<EventThreadConnection>::fromExisting(this));
return binder::Status::ok();
}
binder::Status EventThreadConnection::getLatestVsyncEventData(
ParcelableVsyncEventData* outVsyncEventData) {
ATRACE_CALL();
- outVsyncEventData->vsync = mEventThread->getLatestVsyncEventData(this);
+ outVsyncEventData->vsync =
+ mEventThread->getLatestVsyncEventData(sp<EventThreadConnection>::fromExisting(this));
return binder::Status::ok();
}
+binder::Status EventThreadConnection::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ return gui::getSchedulingPolicy(outPolicy);
+}
+
status_t EventThreadConnection::postEvent(const DisplayEventReceiver::Event& event) {
constexpr auto toStatus = [](ssize_t size) {
return size < 0 ? status_t(size) : status_t(NO_ERROR);
@@ -233,23 +252,18 @@ EventThread::~EventThread() = default;
namespace impl {
-EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
+EventThread::EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule> vsyncSchedule,
android::frametimeline::TokenManager* tokenManager,
- InterceptVSyncsCallback interceptVSyncsCallback,
- ThrottleVsyncCallback throttleVsyncCallback,
- GetVsyncPeriodFunction getVsyncPeriodFunction)
- : mVSyncSource(std::move(vsyncSource)),
+ IEventThreadCallback& callback, 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()) {
-
- LOG_ALWAYS_FATAL_IF(getVsyncPeriodFunction == nullptr,
- "getVsyncPeriodFunction must not be null");
-
- mVSyncSource->setCallback(this);
-
+ mCallback(callback) {
mThread = std::thread([this]() NO_THREAD_SAFETY_ANALYSIS {
std::unique_lock<std::mutex> lock(mMutex);
threadMain(lock);
@@ -271,8 +285,6 @@ EventThread::EventThread(std::unique_ptr<VSyncSource> vsyncSource,
}
EventThread::~EventThread() {
- mVSyncSource->setCallback(nullptr);
-
{
std::lock_guard<std::mutex> lock(mMutex);
mState = State::Quit;
@@ -284,15 +296,24 @@ EventThread::~EventThread() {
void EventThread::setDuration(std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration) {
std::lock_guard<std::mutex> lock(mMutex);
- mVSyncSource->setDuration(workDuration, readyDuration);
+ mWorkDuration = workDuration;
+ mReadyDuration = readyDuration;
+
+ mVsyncRegistration.update({.workDuration = mWorkDuration.get().count(),
+ .readyDuration = mReadyDuration.count(),
+ .earliestVsync = mLastVsyncCallbackTime.ns()});
}
sp<EventThreadConnection> EventThread::createEventConnection(
- ResyncCallback resyncCallback,
- ISurfaceComposer::EventRegistrationFlags eventRegistration) const {
- return new EventThreadConnection(const_cast<EventThread*>(this),
- IPCThreadState::self()->getCallingUid(),
- std::move(resyncCallback), eventRegistration);
+ EventRegistrationFlags eventRegistration) const {
+ auto connection = sp<EventThreadConnection>::make(const_cast<EventThread*>(this),
+ IPCThreadState::self()->getCallingUid(),
+ eventRegistration);
+ if (FlagManager::getInstance().misc1()) {
+ const int policy = SCHED_FIFO;
+ connection->setMinSchedulerPolicy(policy, sched_get_priority_min(policy));
+ }
+ return connection;
}
status_t EventThread::registerDisplayEventConnection(const sp<EventThreadConnection>& connection) {
@@ -335,9 +356,7 @@ void EventThread::setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& c
}
void EventThread::requestNextVsync(const sp<EventThreadConnection>& connection) {
- if (connection->resyncCallback) {
- connection->resyncCallback();
- }
+ mCallback.resync();
std::lock_guard<std::mutex> lock(mMutex);
@@ -353,61 +372,58 @@ VsyncEventData EventThread::getLatestVsyncEventData(
const sp<EventThreadConnection>& connection) const {
// Resync so that the vsync is accurate with hardware. getLatestVsyncEventData is an alternate
// way to get vsync data (instead of posting callbacks to Choreographer).
- if (connection->resyncCallback) {
- connection->resyncCallback();
- }
+ mCallback.resync();
VsyncEventData vsyncEventData;
- nsecs_t frameInterval = mGetVsyncPeriodFunction(connection->mOwnerUid);
- vsyncEventData.frameInterval = frameInterval;
- VSyncSource::VSyncData vsyncData;
- {
+ const Period frameInterval = mCallback.getVsyncPeriod(connection->mOwnerUid);
+ vsyncEventData.frameInterval = frameInterval.ns();
+ const auto [presentTime, deadline] = [&]() -> std::pair<nsecs_t, nsecs_t> {
std::lock_guard<std::mutex> lock(mMutex);
- vsyncData = mVSyncSource->getLatestVSyncData();
- }
- generateFrameTimeline(vsyncEventData, frameInterval, systemTime(SYSTEM_TIME_MONOTONIC),
- vsyncData.expectedPresentationTime, vsyncData.deadlineTimestamp);
+ const auto vsyncTime = mVsyncSchedule->getTracker().nextAnticipatedVSyncTimeFrom(
+ systemTime() + mWorkDuration.get().count() + mReadyDuration.count());
+ return {vsyncTime, vsyncTime - mReadyDuration.count()};
+ }();
+ generateFrameTimeline(vsyncEventData, frameInterval.ns(), systemTime(SYSTEM_TIME_MONOTONIC),
+ presentTime, deadline);
return vsyncEventData;
}
-void EventThread::onScreenReleased() {
+void EventThread::enableSyntheticVsync(bool enable) {
std::lock_guard<std::mutex> lock(mMutex);
- if (!mVSyncState || mVSyncState->synthetic) {
+ if (!mVSyncState || mVSyncState->synthetic == enable) {
return;
}
- mVSyncState->synthetic = true;
+ mVSyncState->synthetic = enable;
mCondition.notify_all();
}
-void EventThread::onScreenAcquired() {
+void EventThread::onVsync(nsecs_t vsyncTime, nsecs_t wakeupTime, nsecs_t readyTime) {
std::lock_guard<std::mutex> lock(mMutex);
- if (!mVSyncState || !mVSyncState->synthetic) {
- return;
- }
+ mLastVsyncCallbackTime = TimePoint::fromNs(vsyncTime);
- mVSyncState->synthetic = false;
+ LOG_FATAL_IF(!mVSyncState);
+ mVsyncTracer = (mVsyncTracer + 1) % 2;
+ mPendingEvents.push_back(makeVSync(mVSyncState->displayId, wakeupTime, ++mVSyncState->count,
+ vsyncTime, readyTime));
mCondition.notify_all();
}
-void EventThread::onVSyncEvent(nsecs_t timestamp, VSyncSource::VSyncData vsyncData) {
+void EventThread::onHotplugReceived(PhysicalDisplayId displayId, bool connected) {
std::lock_guard<std::mutex> lock(mMutex);
- LOG_FATAL_IF(!mVSyncState);
- mPendingEvents.push_back(makeVSync(mVSyncState->displayId, timestamp, ++mVSyncState->count,
- vsyncData.expectedPresentationTime,
- vsyncData.deadlineTimestamp));
+ mPendingEvents.push_back(makeHotplug(displayId, systemTime(), connected));
mCondition.notify_all();
}
-void EventThread::onHotplugReceived(PhysicalDisplayId displayId, bool connected) {
+void EventThread::onHotplugConnectionError(int32_t errorCode) {
std::lock_guard<std::mutex> lock(mMutex);
- mPendingEvents.push_back(makeHotplug(displayId, systemTime(), connected));
+ mPendingEvents.push_back(makeHotplugError(systemTime(), errorCode));
mCondition.notify_all();
}
-void EventThread::onModeChanged(DisplayModePtr mode) {
+void EventThread::onModeChanged(const scheduler::FrameRateMode& mode) {
std::lock_guard<std::mutex> lock(mMutex);
mPendingEvents.push_back(makeModeChanged(mode));
@@ -426,11 +442,6 @@ void EventThread::onFrameRateOverridesChanged(PhysicalDisplayId displayId,
mCondition.notify_all();
}
-size_t EventThread::getEventThreadConnectionCount() {
- std::lock_guard<std::mutex> lock(mMutex);
- return mDisplayEventConnections.size();
-}
-
void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
DisplayEventConsumers consumers;
@@ -442,21 +453,17 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
event = mPendingEvents.front();
mPendingEvents.pop_front();
- switch (event->header.type) {
- case DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG:
+ if (event->header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG) {
+ if (event->hotplug.connectionError == 0) {
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;
+ } else {
+ // Ignore vsync stuff on an error.
+ }
}
}
@@ -466,12 +473,12 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& 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 +490,24 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& 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 +522,6 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& 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,14 +534,23 @@ void EventThread::threadMain(std::unique_lock<std::mutex>& lock) {
}
}
}
+ // cancel any pending vsync event before exiting
+ mVsyncRegistration.cancel();
}
bool EventThread::shouldConsumeEvent(const DisplayEventReceiver::Event& event,
const sp<EventThreadConnection>& connection) const {
- const auto throttleVsync = [&] {
- return mThrottleVsyncCallback &&
- mThrottleVsyncCallback(event.vsync.vsyncData.preferredExpectedPresentationTime(),
- connection->mOwnerUid);
+ const auto throttleVsync = [&]() REQUIRES(mMutex) {
+ const auto& vsyncData = event.vsync.vsyncData;
+ if (connection->frameRate.isValid()) {
+ return !mVsyncSchedule->getTracker()
+ .isVSyncInPhase(vsyncData.preferredExpectedPresentationTime(),
+ connection->frameRate);
+ }
+
+ const auto expectedPresentTime =
+ TimePoint::fromNs(event.vsync.vsyncData.preferredExpectedPresentationTime());
+ return mCallback.throttleVsync(expectedPresentTime, connection->mOwnerUid);
};
switch (event.header.type) {
@@ -553,7 +559,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 +592,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 +612,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<int64_t>(
+ 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<int64_t>(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,
@@ -632,9 +670,9 @@ void EventThread::dispatchEvent(const DisplayEventReceiver::Event& event,
for (const auto& consumer : consumers) {
DisplayEventReceiver::Event copy = event;
if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_VSYNC) {
- const int64_t frameInterval = mGetVsyncPeriodFunction(consumer->mOwnerUid);
- copy.vsync.vsyncData.frameInterval = frameInterval;
- generateFrameTimeline(copy.vsync.vsyncData, frameInterval, copy.header.timestamp,
+ const Period frameInterval = mCallback.getVsyncPeriod(consumer->mOwnerUid);
+ copy.vsync.vsyncData.frameInterval = frameInterval.ns();
+ generateFrameTimeline(copy.vsync.vsyncData, frameInterval.ns(), copy.header.timestamp,
event.vsync.vsyncData.preferredExpectedPresentationTime(),
event.vsync.vsyncData.preferredDeadlineTimestamp());
}
@@ -667,6 +705,12 @@ void EventThread::dump(std::string& result) const {
StringAppendF(&result, "none\n");
}
+ const auto relativeLastCallTime =
+ ticks<std::milli, float>(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 +722,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 +738,36 @@ const char* EventThread::toCString(State state) {
}
}
+void EventThread::onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule> 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<scheduler::VsyncSchedule> schedule) {
+ std::lock_guard<std::mutex> 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 adb96fd462..7842318e2e 100644
--- a/services/surfaceflinger/Scheduler/EventThread.h
+++ b/services/surfaceflinger/Scheduler/EventThread.h
@@ -23,6 +23,7 @@
#include <sys/types.h>
#include <utils/Errors.h>
+#include <scheduler/FrameRateMode.h>
#include <condition_variable>
#include <cstdint>
#include <deque>
@@ -32,6 +33,9 @@
#include <vector>
#include "DisplayHardware/DisplayMode.h"
+#include "TracedOrdinal.h"
+#include "VSyncDispatch.h"
+#include "VsyncSchedule.h"
// ---------------------------------------------------------------------------
namespace android {
@@ -50,7 +54,6 @@ using gui::VsyncEventData;
// ---------------------------------------------------------------------------
-using ResyncCallback = std::function<void()>;
using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
enum class VSyncRequest {
@@ -63,36 +66,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 = {});
+ EventThreadConnection(EventThread*, uid_t callingUid,
+ EventRegistrationFlags eventRegistration = {});
virtual ~EventThreadConnection();
virtual status_t postEvent(const DisplayEventReceiver::Event& event);
@@ -101,13 +78,14 @@ public:
binder::Status setVsyncRate(int rate) override;
binder::Status requestNextVsync() override; // asynchronous
binder::Status getLatestVsyncEventData(ParcelableVsyncEventData* outVsyncEventData) override;
-
- // Called in response to requestNextVsync.
- const ResyncCallback resyncCallback;
+ binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) override;
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 +101,17 @@ public:
virtual ~EventThread();
virtual sp<EventThreadConnection> createEventConnection(
- ResyncCallback,
- ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) const = 0;
-
- // called before the screen is turned off from main thread
- virtual void onScreenReleased() = 0;
+ 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;
+ virtual void onHotplugConnectionError(int32_t connectionError) = 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,
@@ -154,25 +130,28 @@ public:
virtual VsyncEventData getLatestVsyncEventData(
const sp<EventThreadConnection>& connection) const = 0;
- // Retrieves the number of event connections tracked by this EventThread.
- virtual size_t getEventThreadConnectionCount() = 0;
+ virtual void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) = 0;
+};
+
+struct IEventThreadCallback {
+ virtual ~IEventThreadCallback() = default;
+
+ virtual bool throttleVsync(TimePoint, uid_t) = 0;
+ virtual Period getVsyncPeriod(uid_t) = 0;
+ virtual void resync() = 0;
};
namespace impl {
-class EventThread : public android::EventThread, private VSyncSource::Callback {
+class EventThread : public android::EventThread {
public:
- using InterceptVSyncsCallback = std::function<void(nsecs_t)>;
- using ThrottleVsyncCallback = std::function<bool(nsecs_t, uid_t)>;
- using GetVsyncPeriodFunction = std::function<nsecs_t(uid_t)>;
-
- EventThread(std::unique_ptr<VSyncSource>, frametimeline::TokenManager*, InterceptVSyncsCallback,
- ThrottleVsyncCallback, GetVsyncPeriodFunction);
+ EventThread(const char* name, std::shared_ptr<scheduler::VsyncSchedule>,
+ frametimeline::TokenManager*, IEventThreadCallback& callback,
+ std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration);
~EventThread();
sp<EventThreadConnection> createEventConnection(
- ResyncCallback,
- ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) const override;
+ EventRegistrationFlags eventRegistration = {}) const override;
status_t registerDisplayEventConnection(const sp<EventThreadConnection>& connection) override;
void setVsyncRate(uint32_t rate, const sp<EventThreadConnection>& connection) override;
@@ -180,15 +159,13 @@ public:
VsyncEventData getLatestVsyncEventData(
const sp<EventThreadConnection>& 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 onHotplugConnectionError(int32_t connectionError) override;
+
+ void onModeChanged(const scheduler::FrameRateMode&) override;
void onFrameRateOverridesChanged(PhysicalDisplayId displayId,
std::vector<FrameRateOverride> overrides) override;
@@ -198,7 +175,7 @@ public:
void setDuration(std::chrono::nanoseconds workDuration,
std::chrono::nanoseconds readyDuration) override;
- size_t getEventThreadConnectionCount() override;
+ void onNewVsyncSchedule(std::shared_ptr<scheduler::VsyncSchedule>) override EXCLUDES(mMutex);
private:
friend EventThreadTest;
@@ -215,8 +192,7 @@ private:
void removeDisplayEventConnectionLocked(const wp<EventThreadConnection>& 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 +200,23 @@ private:
nsecs_t timestamp, nsecs_t preferredExpectedPresentationTime,
nsecs_t preferredDeadlineTimestamp) const;
- const std::unique_ptr<VSyncSource> mVSyncSource GUARDED_BY(mMutex);
- frametimeline::TokenManager* const mTokenManager;
+ 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<scheduler::VsyncSchedule>) EXCLUDES(mMutex);
- const InterceptVSyncsCallback mInterceptVSyncsCallback;
- const ThrottleVsyncCallback mThrottleVsyncCallback;
- const GetVsyncPeriodFunction mGetVsyncPeriodFunction;
const char* const mThreadName;
+ TracedOrdinal<int> mVsyncTracer;
+ TracedOrdinal<std::chrono::nanoseconds> mWorkDuration GUARDED_BY(mMutex);
+ std::chrono::nanoseconds mReadyDuration GUARDED_BY(mMutex);
+ std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule GUARDED_BY(mMutex);
+ TimePoint mLastVsyncCallbackTime GUARDED_BY(mMutex) = TimePoint::now();
+ scheduler::VSyncCallbackRegistration mVsyncRegistration GUARDED_BY(mMutex);
+ frametimeline::TokenManager* const mTokenManager;
+
+ IEventThreadCallback& mCallback;
std::thread mThread;
mutable std::mutex mMutex;
diff --git a/services/surfaceflinger/Scheduler/FrameRateCompatibility.h b/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
new file mode 100644
index 0000000000..d8c408f485
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/FrameRateCompatibility.h
@@ -0,0 +1,40 @@
+/*
+ * 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 {
+// FrameRateCompatibility specifies how we should interpret the frame rate associated with
+// the layer.
+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
+ // content properly. Any other value will result in a pull down.
+
+ Gte, // Layer needs greater than or equal to the frame rate.
+
+ NoVote, // Layer doesn't have any requirements for the refresh rate and
+ // should not be considered when the display refresh rate is determined.
+
+ ftl_last = NoVote
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
index c233455994..82af61a032 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.cpp
@@ -15,6 +15,7 @@
*/
#include "FrameRateOverrideMappings.h"
+#include <common/FlagManager.h>
namespace android::scheduler {
using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
@@ -30,7 +31,7 @@ std::optional<Fps> FrameRateOverrideMappings::getFrameRateOverrideForUid(
}
}
- {
+ if (!FlagManager::getInstance().game_default_frame_rate()) {
const auto iter = mFrameRateOverridesFromGameManager.find(uid);
if (iter != mFrameRateOverridesFromGameManager.end()) {
return iter->second;
@@ -54,18 +55,20 @@ std::optional<Fps> FrameRateOverrideMappings::getFrameRateOverrideForUid(
std::vector<FrameRateOverride> FrameRateOverrideMappings::getAllFrameRateOverrides(
bool supportsFrameRateOverrideByContent) {
std::lock_guard lock(mFrameRateOverridesLock);
+
std::vector<FrameRateOverride> 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()});
}
- for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
- if (std::find_if(overrides.begin(), overrides.end(),
- [uid = uid](auto i) { return i.uid == uid; }) == overrides.end()) {
- overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+
+ if (!FlagManager::getInstance().game_default_frame_rate()) {
+ for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
+ if (std::find_if(overrides.begin(), overrides.end(),
+ [uid = uid](auto i) { return i.uid == uid; }) == overrides.end()) {
+ overrides.emplace_back(FrameRateOverride{uid, frameRate.getValue()});
+ }
}
}
@@ -83,28 +86,36 @@ std::vector<FrameRateOverride> 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);
+
+ if (!hasOverrides) return;
- StringAppendF(&result, "Frame Rate Overrides (GameManager): {");
- for (const auto& [uid, frameRate] : mFrameRateOverridesFromGameManager) {
- StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+ dump(dumper, "setFrameRate"sv, mFrameRateOverridesByContent);
+ if (!FlagManager::getInstance().game_default_frame_rate()) {
+ dump(dumper, "GameManager"sv, mFrameRateOverridesFromGameManager);
}
- StringAppendF(&result, "}\n");
+ dump(dumper, "Backdoor"sv, mFrameRateOverridesFromBackdoor);
+}
- StringAppendF(&result, "Frame Rate Overrides (setFrameRate): {");
- for (const auto& [uid, frameRate] : mFrameRateOverridesByContent) {
- StringAppendF(&result, "[uid: %d frameRate: %s], ", uid, to_string(frameRate).c_str());
+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 4185a4c0ab..da0f276a3b 100644
--- a/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
+++ b/services/surfaceflinger/Scheduler/FrameRateOverrideMappings.h
@@ -23,7 +23,10 @@
#include <map>
#include <optional>
+#include "Utils/Dumper.h"
+
namespace android::scheduler {
+
class FrameRateOverrideMappings {
using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
using UidToFrameRateOverride = std::map<uid_t, Fps>;
@@ -34,7 +37,6 @@ public:
EXCLUDES(mFrameRateOverridesLock);
std::vector<FrameRateOverride> 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/ContainerLayer.h b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
index 9b7bab1e1d..3b61de7e83 100644
--- a/services/surfaceflinger/ContainerLayer.h
+++ b/services/surfaceflinger/Scheduler/ISchedulerCallback.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 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.
@@ -13,29 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-#pragma once
-
-#include <sys/types.h>
-#include <cstdint>
+#pragma once
-#include "Layer.h"
+#include <vector>
-namespace android {
+#include <ui/DisplayId.h>
-class ContainerLayer : public Layer {
-public:
- explicit ContainerLayer(const LayerCreationArgs&);
- ~ContainerLayer() override;
+#include "Display/DisplayModeRequest.h"
- const char* getType() const override { return "ContainerLayer"; }
- bool isVisible() const override;
+namespace android::scheduler {
- bool isCreatedFromMainThread() const override { return true; }
+struct ISchedulerCallback {
+ virtual void requestHardwareVsync(PhysicalDisplayId, bool enabled) = 0;
+ virtual void requestDisplayModes(std::vector<display::DisplayModeRequest>) = 0;
+ virtual void kernelTimerChanged(bool expired) = 0;
+ virtual void triggerOnFrameRateOverridesChanged() = 0;
+ virtual void onChoreographerAttached() = 0;
protected:
- bool canDrawShadows() const override { return false; }
- sp<Layer> createClone() override;
+ ~ISchedulerCallback() = default;
};
-} // namespace android
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/InjectVSyncSource.h b/services/surfaceflinger/Scheduler/InjectVSyncSource.h
deleted file mode 100644
index 760a4ee886..0000000000
--- 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 <mutex>
-
-#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<std::mutex> lock(mCallbackMutex);
- mCallback = callback;
- }
-
- void onInjectSyncEvent(nsecs_t when, nsecs_t expectedVSyncTimestamp,
- nsecs_t deadlineTimestamp) {
- std::lock_guard<std::mutex> 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 5f64efa3d1..5ce883ce39 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.cpp
+++ b/services/surfaceflinger/Scheduler/LayerHistory.cpp
@@ -22,16 +22,18 @@
#include <android-base/stringprintf.h>
#include <cutils/properties.h>
+#include <gui/TraceUtils.h>
#include <utils/Log.h>
#include <utils/Timers.h>
-#include <utils/Trace.h>
#include <algorithm>
#include <cmath>
#include <string>
#include <utility>
+#include <common/FlagManager.h>
#include "../Layer.h"
+#include "EventThread.h"
#include "LayerInfo.h"
namespace android::scheduler {
@@ -39,8 +41,18 @@ namespace android::scheduler {
namespace {
bool isLayerActive(const LayerInfo& info, nsecs_t threshold) {
- // Layers with an explicit vote are always kept active
- if (info.getSetFrameRateVote().rate.isValid()) {
+ if (FlagManager::getInstance().misc1() && !info.isVisible()) {
+ return false;
+ }
+
+ // Layers with an explicit frame rate or frame rate category are kept active,
+ // but ignore NoVote.
+ if (info.getSetFrameRateVote().isValid() && !info.getSetFrameRateVote().isNoVote()) {
+ return true;
+ }
+
+ // Make all front buffered layers active
+ if (FlagManager::getInstance().vrr_config() && info.isFrontBuffered() && info.isVisible()) {
return true;
}
@@ -69,9 +81,24 @@ void trace(const LayerInfo& info, LayerHistory::LayerVoteType type, int fps) {
traceType(LayerHistory::LayerVoteType::ExplicitExact, fps);
traceType(LayerHistory::LayerVoteType::Min, 1);
traceType(LayerHistory::LayerVoteType::Max, 1);
+ traceType(LayerHistory::LayerVoteType::ExplicitCategory, 1);
ALOGD("%s: %s @ %d Hz", __FUNCTION__, info.getName().c_str(), fps);
}
+
+LayerHistory::LayerVoteType getVoteType(FrameRateCompatibility compatibility,
+ bool contentDetectionEnabled) {
+ LayerHistory::LayerVoteType voteType;
+ if (!contentDetectionEnabled || compatibility == FrameRateCompatibility::NoVote) {
+ voteType = LayerHistory::LayerVoteType::NoVote;
+ } else if (compatibility == FrameRateCompatibility::Min) {
+ voteType = LayerHistory::LayerVoteType::Min;
+ } else {
+ voteType = LayerHistory::LayerVoteType::Heuristic;
+ }
+ return voteType;
+}
+
} // namespace
LayerHistory::LayerHistory()
@@ -81,10 +108,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<LayerInfo>(layer->getName(), layer->getOwnerUid(), type);
// The layer can be placed on either map, it is assumed that partitionLayers() will be called
@@ -101,27 +130,17 @@ 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);
// Activate layer if inactive.
@@ -132,7 +151,45 @@ void LayerHistory::record(Layer* layer, nsecs_t presentTime, nsecs_t now,
}
}
-auto LayerHistory::summarize(const RefreshRateConfigs& configs, nsecs_t now) -> Summary {
+void LayerHistory::setDefaultFrameRateCompatibility(int32_t id,
+ FrameRateCompatibility frameRateCompatibility,
+ bool contentDetectionEnabled) {
+ std::lock_guard lock(mLock);
+
+ auto [found, layerPair] = findLayer(id);
+ if (found == LayerStatus::NotFound) {
+ // Offscreen layer
+ ALOGV("%s: %d not registered", __func__, id);
+ return;
+ }
+
+ const auto& info = layerPair->second;
+ info->setDefaultLayerVote(getVoteType(frameRateCompatibility, contentDetectionEnabled));
+}
+
+void LayerHistory::setLayerProperties(int32_t id, const LayerProps& properties) {
+ std::lock_guard lock(mLock);
+
+ auto [found, layerPair] = findLayer(id);
+ if (found == LayerStatus::NotFound) {
+ // Offscreen layer
+ ALOGV("%s: %d not registered", __func__, id);
+ return;
+ }
+
+ const auto& info = layerPair->second;
+ info->setProperties(properties);
+
+ // Activate layer if inactive and visible.
+ if (found == LayerStatus::LayerInInactiveMap && info->isVisible()) {
+ mActiveLayerInfos.insert(
+ {id, std::make_pair(layerPair->first, std::move(layerPair->second))});
+ mInactiveLayerInfos.erase(id);
+ }
+}
+
+auto LayerHistory::summarize(const RefreshRateSelector& selector, nsecs_t now) -> Summary {
+ ATRACE_CALL();
Summary summary;
std::lock_guard lock(mLock);
@@ -146,25 +203,34 @@ 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);
- // Skip NoVote layer as those don't have any requirements
- if (vote.type == LayerVoteType::NoVote) {
- continue;
- }
-
- // Compute the layer's position on the screen
- const Rect bounds = Rect(info->getBounds());
- const ui::Transform transform = info->getTransform();
- constexpr bool roundOutwards = true;
- Rect transformed = transform.transform(bounds, roundOutwards);
+ ATRACE_FORMAT("%s", info->getName().c_str());
+ const auto votes = info->getRefreshRateVote(selector, now);
+ for (LayerInfo::LayerVote vote : votes) {
+ if (vote.isNoVote()) {
+ continue;
+ }
- const float layerArea = transformed.getWidth() * transformed.getHeight();
- float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
- summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
- vote.seamlessness, weight, layerFocused});
+ // Compute the layer's position on the screen
+ const Rect bounds = Rect(info->getBounds());
+ const ui::Transform transform = info->getTransform();
+ constexpr bool roundOutwards = true;
+ Rect transformed = transform.transform(bounds, roundOutwards);
+
+ const float layerArea = transformed.getWidth() * transformed.getHeight();
+ float weight = mDisplayArea ? layerArea / mDisplayArea : 0.0f;
+ const std::string categoryString = vote.category == FrameRateCategory::Default
+ ? ""
+ : base::StringPrintf("category=%s", ftl::enum_string(vote.category).c_str());
+ ATRACE_FORMAT_INSTANT("%s %s %s (%d%)", ftl::enum_string(vote.type).c_str(),
+ to_string(vote.fps).c_str(), categoryString.c_str(),
+ weight * 100);
+ summary.push_back({info->getName(), info->getOwnerUid(), vote.type, vote.fps,
+ vote.seamlessness, vote.category, vote.categorySmoothSwitchOnly,
+ weight, layerFocused});
- if (CC_UNLIKELY(mTraceEnabled)) {
- trace(*info, vote.type, vote.fps.getIntValue());
+ if (CC_UNLIKELY(mTraceEnabled)) {
+ trace(*info, vote.type, vote.fps.getIntValue());
+ }
}
}
@@ -172,6 +238,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
@@ -199,24 +266,58 @@ void LayerHistory::partitionLayers(nsecs_t now) {
if (isLayerActive(*info, threshold)) {
// Set layer vote if set
const auto frameRate = info->getSetFrameRateVote();
+
const auto voteType = [&]() {
- switch (frameRate.type) {
+ switch (frameRate.vote.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:
return LayerVoteType::NoVote;
case Layer::FrameRateCompatibility::Exact:
return LayerVoteType::ExplicitExact;
+ case Layer::FrameRateCompatibility::Gte:
+ return LayerVoteType::ExplicitGte;
}
}();
- if (frameRate.rate.isValid() || voteType == LayerVoteType::NoVote) {
- const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
- info->setLayerVote({type, frameRate.rate, frameRate.seamlessness});
+ if (FlagManager::getInstance().game_default_frame_rate()) {
+ // Determine the layer frame rate considering the following priorities:
+ // 1. Game mode intervention frame rate override
+ // 2. setFrameRate vote
+ // 3. Game default frame rate override
+
+ const auto& [gameModeFrameRateOverride, gameDefaultFrameRateOverride] =
+ getGameFrameRateOverrideLocked(info->getOwnerUid());
+
+ const auto gameFrameRateOverrideVoteType =
+ info->isVisible() ? LayerVoteType::ExplicitDefault : LayerVoteType::NoVote;
+
+ const auto setFrameRateVoteType =
+ info->isVisible() ? voteType : LayerVoteType::NoVote;
+
+ if (gameModeFrameRateOverride.isValid()) {
+ info->setLayerVote({gameFrameRateOverrideVoteType, gameModeFrameRateOverride});
+ } else if (frameRate.isValid()) {
+ info->setLayerVote({setFrameRateVoteType, frameRate.vote.rate,
+ frameRate.vote.seamlessness, frameRate.category});
+ } else if (gameDefaultFrameRateOverride.isValid()) {
+ info->setLayerVote(
+ {gameFrameRateOverrideVoteType, gameDefaultFrameRateOverride});
+ } else {
+ info->resetLayerVote();
+ }
} else {
- info->resetLayerVote();
+ if (frameRate.isValid()) {
+ const auto type = info->isVisible() ? voteType : LayerVoteType::NoVote;
+ info->setLayerVote({type, frameRate.vote.rate, frameRate.vote.seamlessness,
+ frameRate.category});
+ } else {
+ info->resetLayerVote();
+ }
}
it++;
@@ -241,7 +342,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());
}
@@ -268,4 +369,63 @@ auto LayerHistory::findLayer(int32_t id) -> std::pair<LayerStatus, LayerPair*> {
return {LayerStatus::NotFound, nullptr};
}
+bool LayerHistory::isSmallDirtyArea(uint32_t dirtyArea, float threshold) const {
+ const float ratio = (float)dirtyArea / mDisplayArea;
+ const bool isSmallDirty = ratio <= threshold;
+ ATRACE_FORMAT_INSTANT("small dirty=%s, ratio=%.3f", isSmallDirty ? "true" : "false", ratio);
+ return isSmallDirty;
+}
+
+void LayerHistory::updateGameModeFrameRateOverride(FrameRateOverride frameRateOverride) {
+ const uid_t uid = frameRateOverride.uid;
+ std::lock_guard lock(mLock);
+ if (frameRateOverride.frameRateHz != 0.f) {
+ mGameFrameRateOverride[uid].first = Fps::fromValue(frameRateOverride.frameRateHz);
+ } else {
+ if (mGameFrameRateOverride[uid].second.getValue() == 0.f) {
+ mGameFrameRateOverride.erase(uid);
+ } else {
+ mGameFrameRateOverride[uid].first = Fps();
+ }
+ }
+}
+
+void LayerHistory::updateGameDefaultFrameRateOverride(FrameRateOverride frameRateOverride) {
+ const uid_t uid = frameRateOverride.uid;
+ std::lock_guard lock(mLock);
+ if (frameRateOverride.frameRateHz != 0.f) {
+ mGameFrameRateOverride[uid].second = Fps::fromValue(frameRateOverride.frameRateHz);
+ } else {
+ if (mGameFrameRateOverride[uid].first.getValue() == 0.f) {
+ mGameFrameRateOverride.erase(uid);
+ } else {
+ mGameFrameRateOverride[uid].second = Fps();
+ }
+ }
+}
+
+std::pair<Fps, Fps> LayerHistory::getGameFrameRateOverride(uid_t uid) const {
+ if (!FlagManager::getInstance().game_default_frame_rate()) {
+ return std::pair<Fps, Fps>();
+ }
+
+ std::lock_guard lock(mLock);
+
+ return getGameFrameRateOverrideLocked(uid);
+}
+
+std::pair<Fps, Fps> LayerHistory::getGameFrameRateOverrideLocked(uid_t uid) const {
+ if (!FlagManager::getInstance().game_default_frame_rate()) {
+ return std::pair<Fps, Fps>();
+ }
+
+ const auto it = mGameFrameRateOverride.find(uid);
+
+ if (it == mGameFrameRateOverride.end()) {
+ return std::pair<Fps, Fps>(Fps(), Fps());
+ }
+
+ return it->second;
+}
+
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerHistory.h b/services/surfaceflinger/Scheduler/LayerHistory.h
index 7b6096f7f3..930d06c8f0 100644
--- a/services/surfaceflinger/Scheduler/LayerHistory.h
+++ b/services/surfaceflinger/Scheduler/LayerHistory.h
@@ -27,7 +27,10 @@
#include <utility>
#include <vector>
-#include "RefreshRateConfigs.h"
+#include "EventThread.h"
+
+#include "FrameRateCompatibility.h"
+#include "RefreshRateSelector.h"
namespace android {
@@ -36,16 +39,19 @@ class Layer;
namespace scheduler {
class LayerInfo;
+struct LayerProps;
class LayerHistory {
public:
- using LayerVoteType = RefreshRateConfigs::LayerVoteType;
+ using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
+ using LayerVoteType = RefreshRateSelector::LayerVoteType;
+ static constexpr std::chrono::nanoseconds kMaxPeriodForHistory = 1s;
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 +67,18 @@ 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);
- using Summary = std::vector<RefreshRateConfigs::LayerRequirement>;
+ // Updates the default frame rate compatibility which takes effect when the app
+ // does not set a preference for refresh rate.
+ void setDefaultFrameRateCompatibility(int32_t id, FrameRateCompatibility frameRateCompatibility,
+ bool contentDetectionEnabled);
+ void setLayerProperties(int32_t id, const LayerProps&);
+ using Summary = std::vector<RefreshRateSelector::LayerRequirement>;
// 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,8 +88,20 @@ public:
// return the frames per second of the layer with the given sequence id.
float getLayerFramerate(nsecs_t now, int32_t id) const;
+ bool isSmallDirtyArea(uint32_t dirtyArea, float threshold) const;
+
+ // Updates the frame rate override set by game mode intervention
+ void updateGameModeFrameRateOverride(FrameRateOverride frameRateOverride) EXCLUDES(mLock);
+
+ // Updates the frame rate override set by game default frame rate
+ void updateGameDefaultFrameRateOverride(FrameRateOverride frameRateOverride) EXCLUDES(mLock);
+
+ std::pair<Fps, Fps> getGameFrameRateOverride(uid_t uid) const EXCLUDES(mLock);
+ std::pair<Fps, Fps> getGameFrameRateOverrideLocked(uid_t uid) const REQUIRES(mLock);
+
private:
friend class LayerHistoryTest;
+ friend class LayerHistoryIntegrationTest;
friend class TestableScheduler;
using LayerPair = std::pair<Layer*, std::unique_ptr<LayerInfo>>;
@@ -123,6 +147,13 @@ private:
// Whether a mode change is in progress or not
std::atomic<bool> mModeChangePending = false;
+
+ // A list to look up the game frame rate overrides
+ // Each entry includes:
+ // 1. the uid of the app
+ // 2. a pair of game mode intervention frame frame and game default frame rate override
+ // set to 0.0 if there is no such override
+ std::map<uid_t, std::pair<Fps, Fps>> mGameFrameRateOverride GUARDED_BY(mLock);
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.cpp b/services/surfaceflinger/Scheduler/LayerInfo.cpp
index 943615c45c..97fca395b4 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.cpp
+++ b/services/surfaceflinger/Scheduler/LayerInfo.cpp
@@ -26,9 +26,12 @@
#include <algorithm>
#include <utility>
+#include <android/native_window.h>
#include <cutils/compiler.h>
#include <cutils/trace.h>
#include <ftl/enum.h>
+#include <gui/TraceUtils.h>
+#include <system/window.h>
#undef LOG_TAG
#define LOG_TAG "LayerInfo"
@@ -43,14 +46,17 @@ LayerInfo::LayerInfo(const std::string& name, uid_t ownerUid,
mOwnerUid(ownerUid),
mDefaultVote(defaultVote),
mLayerVote({defaultVote, Fps()}),
- mRefreshRateHistory(name) {}
+ mLayerProps(std::make_unique<LayerProps>()),
+ 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<nsecs_t>(0));
mLastUpdatedTime = std::max(lastPresentTime, now);
- mLayerProps = props;
+ *mLayerProps = props;
switch (updateType) {
case LayerUpdateType::AnimationTX:
mLastAnimationTime = std::max(lastPresentTime, now);
@@ -59,7 +65,8 @@ void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUp
case LayerUpdateType::Buffer:
FrameTimeData frameTime = {.presentTime = lastPresentTime,
.queueTime = mLastUpdatedTime,
- .pendingModeChange = pendingModeChange};
+ .pendingModeChange = pendingModeChange,
+ .isSmallDirty = props.isSmallDirty};
mFrameTimes.push_back(frameTime);
if (mFrameTimes.size() > HISTORY_SIZE) {
mFrameTimes.pop_front();
@@ -68,20 +75,79 @@ void LayerInfo::setLastPresentTime(nsecs_t lastPresentTime, nsecs_t now, LayerUp
}
}
+void LayerInfo::setProperties(const android::scheduler::LayerProps& properties) {
+ *mLayerProps = properties;
+}
+
bool LayerInfo::isFrameTimeValid(const FrameTimeData& frameTime) const {
return frameTime.queueTime >= std::chrono::duration_cast<std::chrono::nanoseconds>(
mFrameTimeValidSince.time_since_epoch())
.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};
+ }
+
+ // 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;
+ int32_t smallDirtyCount = 0;
+ 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;
+ if (mFrameTimes[n - i].presentTime == 0 && mFrameTimes[n - i].isSmallDirty) {
+ smallDirtyCount++;
+ }
+ } else {
+ isFrequent = false;
+ }
+ }
+
+ // Vote the small dirty when a layer contains at least HISTORY_SIZE of small dirty updates.
+ bool isSmallDirty = false;
+ if (smallDirtyCount >= kNumSmallDirtyThreshold) {
+ if (mLastSmallDirtyCount >= HISTORY_SIZE) {
+ isSmallDirty = true;
+ } else {
+ mLastSmallDirtyCount++;
+ }
+ } else {
+ mLastSmallDirtyCount = 0;
+ }
+
+ 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,
+ isSmallDirty};
}
- return getFps(now) >= kMinFpsForFrequentLayer;
+
+ // 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 {
@@ -160,6 +226,7 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
nsecs_t totalDeltas = 0;
int numDeltas = 0;
+ int32_t smallDirtyCount = 0;
auto prevFrame = mFrameTimes.begin();
for (auto it = mFrameTimes.begin() + 1; it != mFrameTimes.end(); ++it) {
const auto currDelta = getFrameTime(*it) - getFrameTime(*prevFrame);
@@ -168,6 +235,13 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
continue;
}
+ // If this is a small area update, we don't want to consider it for calculating the average
+ // frame time. Instead, we let the bigger frame updates to drive the calculation.
+ if (it->isSmallDirty && currDelta < kMinPeriodBetweenSmallDirtyFrames) {
+ smallDirtyCount++;
+ continue;
+ }
+
prevFrame = it;
if (currDelta > kMaxPeriodBetweenFrames) {
@@ -179,6 +253,10 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
numDeltas++;
}
+ if (smallDirtyCount > 0) {
+ ATRACE_FORMAT_INSTANT("small dirty = %" PRIu32, smallDirtyCount);
+ }
+
if (numDeltas == 0) {
return std::nullopt;
}
@@ -187,8 +265,9 @@ std::optional<nsecs_t> LayerInfo::calculateAverageFrameTime() const {
return static_cast<nsecs_t>(averageFrameTime);
}
-std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(
- const RefreshRateConfigs& refreshRateConfigs, nsecs_t now) {
+std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(const RefreshRateSelector& selector,
+ nsecs_t now) {
+ ATRACE_CALL();
static constexpr float MARGIN = 1.0f; // 1Hz
if (!hasEnoughDataForHeuristic()) {
ALOGV("Not enough data");
@@ -197,17 +276,16 @@ std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(
if (const auto averageFrameTime = calculateAverageFrameTime()) {
const auto refreshRate = Fps::fromPeriodNsecs(*averageFrameTime);
- const bool refreshRateConsistent = mRefreshRateHistory.add(refreshRate, now);
- if (refreshRateConsistent) {
- const auto knownRefreshRate = refreshRateConfigs.findClosestKnownFrameRate(refreshRate);
+ const auto closestKnownRefreshRate = mRefreshRateHistory.add(refreshRate, now, selector);
+ if (closestKnownRefreshRate.isValid()) {
using fps_approx_ops::operator!=;
// To avoid oscillation, use the last calculated refresh rate if it is close enough.
if (std::abs(mLastRefreshRate.calculated.getValue() - refreshRate.getValue()) >
MARGIN &&
- mLastRefreshRate.reported != knownRefreshRate) {
+ mLastRefreshRate.reported != closestKnownRefreshRate) {
mLastRefreshRate.calculated = refreshRate;
- mLastRefreshRate.reported = knownRefreshRate;
+ mLastRefreshRate.reported = closestKnownRefreshRate;
}
ALOGV("%s %s rounded to nearest known frame rate %s", mName.c_str(),
@@ -222,42 +300,88 @@ std::optional<Fps> LayerInfo::calculateRefreshRateIfPossible(
: std::nullopt;
}
-LayerInfo::LayerVote LayerInfo::getRefreshRateVote(const RefreshRateConfigs& refreshRateConfigs,
- nsecs_t now) {
+LayerInfo::RefreshRateVotes LayerInfo::getRefreshRateVote(const RefreshRateSelector& selector,
+ nsecs_t now) {
+ ATRACE_CALL();
+ LayerInfo::RefreshRateVotes votes;
+
if (mLayerVote.type != LayerHistory::LayerVoteType::Heuristic) {
- ALOGV("%s voted %d ", mName.c_str(), static_cast<int>(mLayerVote.type));
- return mLayerVote;
+ if (mLayerVote.category != FrameRateCategory::Default) {
+ const auto voteType = mLayerVote.type == LayerHistory::LayerVoteType::NoVote
+ ? LayerHistory::LayerVoteType::NoVote
+ : LayerHistory::LayerVoteType::ExplicitCategory;
+ ATRACE_FORMAT_INSTANT("Vote %s (category=%s)", ftl::enum_string(voteType).c_str(),
+ ftl::enum_string(mLayerVote.category).c_str());
+ ALOGV("%s voted %s with category: %s", mName.c_str(),
+ ftl::enum_string(voteType).c_str(),
+ ftl::enum_string(mLayerVote.category).c_str());
+ votes.push_back({voteType, Fps(), Seamlessness::Default, mLayerVote.category,
+ mLayerVote.categorySmoothSwitchOnly});
+ }
+
+ if (mLayerVote.fps.isValid() ||
+ mLayerVote.type != LayerHistory::LayerVoteType::ExplicitDefault) {
+ ATRACE_FORMAT_INSTANT("Vote %s", ftl::enum_string(mLayerVote.type).c_str());
+ ALOGV("%s voted %d", mName.c_str(), static_cast<int>(mLayerVote.type));
+ votes.push_back(mLayerVote);
+ }
+
+ return votes;
}
if (isAnimating(now)) {
+ ATRACE_FORMAT_INSTANT("animating");
ALOGV("%s is animating", mName.c_str());
- mLastRefreshRate.animatingOrInfrequent = true;
- return {LayerHistory::LayerVoteType::Max, Fps()};
+ mLastRefreshRate.animating = true;
+ votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
+ return votes;
}
- if (!isFrequent(now)) {
+ // Vote for max refresh rate whenever we're front-buffered.
+ if (FlagManager::getInstance().vrr_config() && isFrontBuffered()) {
+ ATRACE_FORMAT_INSTANT("front buffered");
+ ALOGV("%s is front-buffered", mName.c_str());
+ votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
+ return votes;
+ }
+
+ 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;
+ mLastSmallDirtyCount = 0;
+ // Infrequent layers vote for minimal refresh rate for
// battery saving purposes and also to prevent b/135718869.
- return {LayerHistory::LayerVoteType::Min, Fps()};
+ votes.push_back({LayerHistory::LayerVoteType::Min, Fps()});
+ return votes;
}
- // 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);
+ // Return no vote if the recent frames are small dirty.
+ if (frequent.isSmallDirty && !mLastRefreshRate.reported.isValid()) {
+ ATRACE_FORMAT_INSTANT("NoVote (small dirty)");
+ ALOGV("%s is small dirty", mName.c_str());
+ votes.push_back({LayerHistory::LayerVoteType::NoVote, Fps()});
+ return votes;
+ }
+
+ auto refreshRate = calculateRefreshRateIfPossible(selector, now);
if (refreshRate.has_value()) {
+ ATRACE_FORMAT_INSTANT("calculated (%s)", to_string(*refreshRate).c_str());
ALOGV("%s calculated refresh rate: %s", mName.c_str(), to_string(*refreshRate).c_str());
- return {LayerHistory::LayerVoteType::Heuristic, refreshRate.value()};
+ votes.push_back({LayerHistory::LayerVoteType::Heuristic, refreshRate.value()});
+ return votes;
}
+ ATRACE_FORMAT_INSTANT("Max (can't resolve refresh rate)");
ALOGV("%s Max (can't resolve refresh rate)", mName.c_str());
- return {LayerHistory::LayerVoteType::Max, Fps()};
+ votes.push_back({LayerHistory::LayerVoteType::Max, Fps()});
+ return votes;
}
const char* LayerInfo::getTraceTag(LayerHistory::LayerVoteType type) const {
@@ -269,6 +393,30 @@ 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;
+}
+
+bool LayerInfo::isFrontBuffered() const {
+ return mLayerProps->isFrontBuffered;
+}
+
+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 ";
@@ -283,7 +431,8 @@ void LayerInfo::RefreshRateHistory::clear() {
mRefreshRates.clear();
}
-bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
+Fps LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now,
+ const RefreshRateSelector& selector) {
mRefreshRates.push_back({refreshRate, now});
while (mRefreshRates.size() >= HISTORY_SIZE ||
now - mRefreshRates.front().timestamp > HISTORY_DURATION.count()) {
@@ -298,11 +447,11 @@ bool LayerInfo::RefreshRateHistory::add(Fps refreshRate, nsecs_t now) {
ATRACE_INT(mHeuristicTraceTagData->average.c_str(), refreshRate.getIntValue());
}
- return isConsistent();
+ return selectRefreshRate(selector);
}
-bool LayerInfo::RefreshRateHistory::isConsistent() const {
- if (mRefreshRates.empty()) return true;
+Fps LayerInfo::RefreshRateHistory::selectRefreshRate(const RefreshRateSelector& selector) const {
+ if (mRefreshRates.empty()) return Fps();
const auto [min, max] =
std::minmax_element(mRefreshRates.begin(), mRefreshRates.end(),
@@ -310,8 +459,19 @@ bool LayerInfo::RefreshRateHistory::isConsistent() const {
return isStrictlyLess(lhs.refreshRate, rhs.refreshRate);
});
- const bool consistent =
- max->refreshRate.getValue() - min->refreshRate.getValue() < MARGIN_CONSISTENT_FPS;
+ const auto maxClosestRate = selector.findClosestKnownFrameRate(max->refreshRate);
+ const bool consistent = [&](Fps maxFps, Fps minFps) {
+ if (FlagManager::getInstance().use_known_refresh_rate_for_fps_consistency()) {
+ if (maxFps.getValue() - minFps.getValue() <
+ MARGIN_CONSISTENT_FPS_FOR_CLOSEST_REFRESH_RATE) {
+ const auto minClosestRate = selector.findClosestKnownFrameRate(minFps);
+ using fps_approx_ops::operator==;
+ return maxClosestRate == minClosestRate;
+ }
+ return false;
+ }
+ return maxFps.getValue() - minFps.getValue() < MARGIN_CONSISTENT_FPS;
+ }(max->refreshRate, min->refreshRate);
if (CC_UNLIKELY(sTraceEnabled)) {
if (!mHeuristicTraceTagData.has_value()) {
@@ -323,7 +483,85 @@ bool LayerInfo::RefreshRateHistory::isConsistent() const {
ATRACE_INT(mHeuristicTraceTagData->consistent.c_str(), consistent);
}
- return consistent;
+ return consistent ? maxClosestRate : Fps();
+}
+
+FrameRateCompatibility LayerInfo::FrameRate::convertCompatibility(int8_t compatibility) {
+ switch (compatibility) {
+ case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT:
+ return FrameRateCompatibility::Default;
+ case ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE:
+ return FrameRateCompatibility::ExactOrMultiple;
+ case ANATIVEWINDOW_FRAME_RATE_EXACT:
+ return FrameRateCompatibility::Exact;
+ case ANATIVEWINDOW_FRAME_RATE_MIN:
+ return FrameRateCompatibility::Min;
+ case ANATIVEWINDOW_FRAME_RATE_GTE:
+ return FrameRateCompatibility::Gte;
+ case ANATIVEWINDOW_FRAME_RATE_NO_VOTE:
+ return FrameRateCompatibility::NoVote;
+ default:
+ LOG_ALWAYS_FATAL("Invalid frame rate compatibility value %d", compatibility);
+ return FrameRateCompatibility::Default;
+ }
+}
+
+Seamlessness LayerInfo::FrameRate::convertChangeFrameRateStrategy(int8_t strategy) {
+ switch (strategy) {
+ case ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS:
+ return Seamlessness::OnlySeamless;
+ case ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS:
+ return Seamlessness::SeamedAndSeamless;
+ default:
+ LOG_ALWAYS_FATAL("Invalid change frame sate strategy value %d", strategy);
+ return Seamlessness::Default;
+ }
+}
+
+FrameRateCategory LayerInfo::FrameRate::convertCategory(int8_t category) {
+ switch (category) {
+ case ANATIVEWINDOW_FRAME_RATE_CATEGORY_DEFAULT:
+ return FrameRateCategory::Default;
+ case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NO_PREFERENCE:
+ return FrameRateCategory::NoPreference;
+ case ANATIVEWINDOW_FRAME_RATE_CATEGORY_LOW:
+ return FrameRateCategory::Low;
+ case ANATIVEWINDOW_FRAME_RATE_CATEGORY_NORMAL:
+ return FrameRateCategory::Normal;
+ case ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH:
+ return FrameRateCategory::High;
+ default:
+ LOG_ALWAYS_FATAL("Invalid frame rate category value %d", category);
+ return FrameRateCategory::Default;
+ }
+}
+
+LayerInfo::FrameRateSelectionStrategy LayerInfo::convertFrameRateSelectionStrategy(
+ int8_t strategy) {
+ switch (strategy) {
+ case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_PROPAGATE:
+ return FrameRateSelectionStrategy::Propagate;
+ case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN:
+ return FrameRateSelectionStrategy::OverrideChildren;
+ case ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_SELF:
+ return FrameRateSelectionStrategy::Self;
+ default:
+ LOG_ALWAYS_FATAL("Invalid frame rate selection strategy value %d", strategy);
+ return FrameRateSelectionStrategy::Self;
+ }
+}
+
+bool LayerInfo::FrameRate::isNoVote() const {
+ return vote.type == FrameRateCompatibility::NoVote;
+}
+
+bool LayerInfo::FrameRate::isValid() const {
+ return isNoVote() || vote.rate.isValid() || category != FrameRateCategory::Default;
+}
+
+std::ostream& operator<<(std::ostream& stream, const LayerInfo::FrameRate& rate) {
+ return stream << "{rate=" << rate.vote.rate << " type=" << ftl::enum_string(rate.vote.type)
+ << " seamlessness=" << ftl::enum_string(rate.vote.seamlessness) << '}';
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/LayerInfo.h b/services/surfaceflinger/Scheduler/LayerInfo.h
index 8a3b0b97e4..326e444815 100644
--- a/services/surfaceflinger/Scheduler/LayerInfo.h
+++ b/services/surfaceflinger/Scheduler/LayerInfo.h
@@ -25,10 +25,12 @@
#include <ui/Transform.h>
#include <utils/Timers.h>
+#include <scheduler/Fps.h>
#include <scheduler/Seamlessness.h>
+#include "FrameRateCompatibility.h"
#include "LayerHistory.h"
-#include "RefreshRateConfigs.h"
+#include "RefreshRateSelector.h"
namespace android {
@@ -37,7 +39,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,12 +55,14 @@ 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;
+ static constexpr size_t kNumSmallDirtyThreshold = 2;
friend class LayerHistoryTest;
+ friend class LayerHistoryIntegrationTest;
friend class LayerInfoTest;
public:
@@ -67,42 +71,58 @@ public:
LayerHistory::LayerVoteType type = LayerHistory::LayerVoteType::Heuristic;
Fps fps;
Seamlessness seamlessness = Seamlessness::Default;
- };
+ FrameRateCategory category = FrameRateCategory::Default;
+ bool categorySmoothSwitchOnly = false;
- // FrameRateCompatibility specifies how we should interpret the frame rate associated with
- // the layer.
- enum class FrameRateCompatibility {
- Default, // Layer didn't specify any specific handling strategy
-
- Exact, // Layer needs the exact frame rate.
+ // Returns true if the layer explicitly should contribute to frame rate scoring.
+ bool isNoVote() const { return RefreshRateSelector::isNoVote(type); }
+ };
- ExactOrMultiple, // Layer needs the exact frame rate (or a multiple of it) to present the
- // content properly. Any other value will result in a pull down.
+ using RefreshRateVotes = ftl::SmallVector<LayerInfo::LayerVote, 2>;
- NoVote, // Layer doesn't have any requirements for the refresh rate and
- // should not be considered when the display refresh rate is determined.
+ enum class FrameRateSelectionStrategy {
+ Propagate,
+ OverrideChildren,
+ Self,
- ftl_last = NoVote
+ ftl_last = Self
};
- // Encapsulates the frame rate and compatibility of the layer. This information will be used
+ // Encapsulates the frame rate specifications of the layer. This information will be used
// when the display refresh rate is determined.
struct FrameRate {
using Seamlessness = scheduler::Seamlessness;
- Fps rate;
- FrameRateCompatibility type = FrameRateCompatibility::Default;
- Seamlessness seamlessness = Seamlessness::Default;
+ // Information related to a specific desired frame rate vote.
+ struct FrameRateVote {
+ Fps rate;
+ FrameRateCompatibility type = FrameRateCompatibility::Default;
+ Seamlessness seamlessness = Seamlessness::Default;
+
+ bool operator==(const FrameRateVote& other) const {
+ return isApproxEqual(rate, other.rate) && type == other.type &&
+ seamlessness == other.seamlessness;
+ }
+
+ FrameRateVote() = default;
+
+ FrameRateVote(Fps rate, FrameRateCompatibility type,
+ Seamlessness seamlessness = Seamlessness::OnlySeamless)
+ : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
+ } vote;
+
+ FrameRateCategory category = FrameRateCategory::Default;
+ bool categorySmoothSwitchOnly = false;
FrameRate() = default;
FrameRate(Fps rate, FrameRateCompatibility type,
- Seamlessness seamlessness = Seamlessness::OnlySeamless)
- : rate(rate), type(type), seamlessness(getSeamlessness(rate, seamlessness)) {}
+ Seamlessness seamlessness = Seamlessness::OnlySeamless,
+ FrameRateCategory category = FrameRateCategory::Default)
+ : vote(FrameRateVote(rate, type, seamlessness)), category(category) {}
bool operator==(const FrameRate& other) const {
- return isApproxEqual(rate, other.rate) && type == other.type &&
- seamlessness == other.seamlessness;
+ return vote == other.vote && category == other.category;
}
bool operator!=(const FrameRate& other) const { return !(*this == other); }
@@ -110,8 +130,22 @@ public:
// Convert an ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_* value to a
// Layer::FrameRateCompatibility. Logs fatal if the compatibility value is invalid.
static FrameRateCompatibility convertCompatibility(int8_t compatibility);
+
+ // Convert an ANATIVEWINDOW_CHANGE_FRAME_RATE_* value to a scheduler::Seamlessness.
+ // Logs fatal if the strategy value is invalid.
static scheduler::Seamlessness convertChangeFrameRateStrategy(int8_t strategy);
+ // Convert an ANATIVEWINDOW_FRAME_RATE_CATEGORY_* value to a FrameRateCategory.
+ // Logs fatal if the category value is invalid.
+ static FrameRateCategory convertCategory(int8_t category);
+
+ // True if the FrameRate has explicit frame rate specifications.
+ bool isValid() const;
+
+ // Returns true if the FrameRate explicitly instructs to not contribute to frame rate
+ // selection.
+ bool isNoVote() const;
+
private:
static Seamlessness getSeamlessness(Fps rate, Seamlessness seamlessness) {
if (!rate.isValid()) {
@@ -123,6 +157,10 @@ public:
}
};
+ // Convert an ANATIVEWINDOW_FRAME_RATE_SELECTION_STRATEGY_* value to FrameRateSelectionStrategy.
+ // Logs fatal if the strategy value is invalid.
+ static FrameRateSelectionStrategy convertFrameRateSelectionStrategy(int8_t strategy);
+
static void setTraceEnabled(bool enabled) { sTraceEnabled = enabled; }
LayerInfo(const std::string& name, uid_t ownerUid, LayerHistory::LayerVoteType defaultVote);
@@ -130,22 +168,15 @@ 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
+ // ANativeWindow_setFrameRate API. This is also used by Game Default Frame Rate and
+ // Game Mode Intervention Frame Rate.
void setLayerVote(LayerVote vote) { mLayerVote = vote; }
// Sets the default layer vote. This will be the layer vote after calling to resetLayerVote().
@@ -153,26 +184,29 @@ public:
// layer can go back to whatever vote it had before the app voted for it.
void setDefaultLayerVote(LayerHistory::LayerVoteType type) { mDefaultVote = type; }
+ void setProperties(const LayerProps&);
+
// Resets the layer vote to its default.
- void resetLayerVote() { mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default}; }
+ void resetLayerVote() {
+ mLayerVote = {mDefaultVote, Fps(), Seamlessness::Default, FrameRateCategory::Default};
+ }
std::string getName() const { return mName; }
uid_t getOwnerUid() const { return mOwnerUid; }
- LayerVote getRefreshRateVote(const RefreshRateConfigs&, nsecs_t now);
+ RefreshRateVotes 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;
+ bool isFrontBuffered() 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 +223,7 @@ public:
mFrameTimeValidSince = std::chrono::time_point<std::chrono::steady_clock>(timePoint);
mLastRefreshRate = {};
mRefreshRateHistory.clear();
+ mIsFrequencyConclusive = true;
}
void clearHistory(nsecs_t now) {
@@ -202,6 +237,7 @@ private:
nsecs_t presentTime; // desiredPresentTime, if provided
nsecs_t queueTime; // buffer queue time
bool pendingModeChange;
+ bool isSmallDirty;
};
// Holds information about the calculated and reported refresh rate
@@ -212,7 +248,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
@@ -227,11 +266,12 @@ private:
// Clears History
void clear();
- // Adds a new refresh rate and returns true if it is consistent
- bool add(Fps refreshRate, nsecs_t now);
+ // Adds a new refresh rate and returns valid refresh rate if it is consistent enough
+ Fps add(Fps refreshRate, nsecs_t now, const RefreshRateSelector&);
private:
friend class LayerHistoryTest;
+ friend class LayerHistoryIntegrationTest;
// Holds the refresh rate when it was calculated
struct RefreshRateData {
@@ -247,19 +287,30 @@ private:
std::string average;
};
- bool isConsistent() const;
+ Fps selectRefreshRate(const RefreshRateSelector&) const;
HeuristicTraceTagData makeHeuristicTraceTagData() const;
const std::string mName;
mutable std::optional<HeuristicTraceTagData> mHeuristicTraceTagData;
std::deque<RefreshRateData> mRefreshRates;
static constexpr float MARGIN_CONSISTENT_FPS = 1.0;
+ static constexpr float MARGIN_CONSISTENT_FPS_FOR_CLOSEST_REFRESH_RATE = 5.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;
+ // Represents whether the latest frames are small dirty.
+ bool isSmallDirty = false;
+ };
+ Frequent isFrequent(nsecs_t now) const;
bool isAnimating(nsecs_t now) const;
bool hasEnoughDataForHeuristic() const;
- std::optional<Fps> calculateRefreshRateIfPossible(const RefreshRateConfigs&, nsecs_t now);
+ std::optional<Fps> calculateRefreshRateIfPossible(const RefreshRateSelector&, nsecs_t now);
std::optional<nsecs_t> calculateAverageFrameTime() const;
bool isFrameTimeValid(const FrameTimeData&) const;
@@ -273,6 +324,11 @@ private:
// this period apart from each other, the interval between them won't be
// taken into account when calculating average frame rate.
static constexpr nsecs_t kMaxPeriodBetweenFrames = kMinFpsForFrequentLayer.getPeriodNsecs();
+ // Used for sanitizing the heuristic data. If frames are small dirty updating and are less
+ // than this period apart from each other, the interval between them won't be
+ // taken into account when calculating average frame rate.
+ static constexpr nsecs_t kMinPeriodBetweenSmallDirtyFrames = (60_Hz).getPeriodNsecs();
+
LayerHistory::LayerVoteType mDefaultVote;
LayerVote mLayerVote;
@@ -287,17 +343,31 @@ private:
std::chrono::time_point<std::chrono::steady_clock> mFrameTimeValidSince =
std::chrono::steady_clock::now();
static constexpr size_t HISTORY_SIZE = RefreshRateHistory::HISTORY_SIZE;
- static constexpr std::chrono::nanoseconds HISTORY_DURATION = 1s;
+ static constexpr std::chrono::nanoseconds HISTORY_DURATION = LayerHistory::kMaxPeriodForHistory;
- LayerProps mLayerProps;
+ std::unique_ptr<LayerProps> mLayerProps;
RefreshRateHistory mRefreshRateHistory;
+ // This will be accessed from only one thread when counting a layer is frequent or infrequent,
+ // and to determine whether a layer is in small dirty updating.
+ mutable int32_t mLastSmallDirtyCount = 0;
+
mutable std::unordered_map<LayerHistory::LayerVoteType, std::string> mTraceTags;
// Shared for all LayerInfo instances
static bool sTraceEnabled;
};
+struct LayerProps {
+ bool visible = false;
+ FloatRect bounds;
+ ui::Transform transform;
+ LayerInfo::FrameRate setFrameRateVote;
+ int32_t frameRateSelectionPriority = -1;
+ bool isSmallDirty = false;
+ bool isFrontBuffered = false;
+};
+
} // namespace scheduler
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.cpp b/services/surfaceflinger/Scheduler/MessageQueue.cpp
index f2af85e94a..18c0a696d5 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.cpp
+++ b/services/surfaceflinger/Scheduler/MessageQueue.cpp
@@ -17,12 +17,12 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <binder/IPCThreadState.h>
-
+#include <gui/DisplayEventReceiver.h>
#include <utils/Log.h>
#include <utils/Timers.h>
#include <utils/threads.h>
-#include <gui/DisplayEventReceiver.h>
+#include <scheduler/interface/ICompositor.h>
#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<MessageHandler>::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> handler)
mLooper(sp<Looper>::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<EventThreadConnection> 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<MessageQueue*>(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<scheduler::VSyncDispatch> dispatch,
frametimeline::TokenManager& tokenManager,
std::chrono::nanoseconds workDuration) {
- setDuration(workDuration);
- mVsync.tokenManager = &tokenManager;
+ std::unique_ptr<scheduler::VSyncCallbackRegistration> 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<scheduler::VSyncDispatch> dispatch) {
+ std::unique_ptr<scheduler::VSyncCallbackRegistration> 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<scheduler::VSyncCallbackRegistration> MessageQueue::onNewVsyncScheduleLocked(
+ std::shared_ptr<scheduler::VSyncDispatch> 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<MessageHandler>&& handler) {
mLooper->sendMessage(handler, Message());
}
+void MessageQueue::postMessageDelayed(sp<MessageHandler>&& 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<ConfigureHandler>::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<Clock::time_point> {
diff --git a/services/surfaceflinger/Scheduler/MessageQueue.h b/services/surfaceflinger/Scheduler/MessageQueue.h
index 4082e26874..a523147733 100644
--- a/services/surfaceflinger/Scheduler/MessageQueue.h
+++ b/services/surfaceflinger/Scheduler/MessageQueue.h
@@ -25,28 +25,28 @@
#include <android/gui/IDisplayEventConnection.h>
#include <private/gui/BitTube.h>
#include <utils/Looper.h>
+#include <utils/StrongPointer.h>
#include <utils/Timers.h>
+#include <scheduler/Time.h>
+#include <scheduler/VsyncId.h>
+
#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 <typename F>
class Task : public MessageHandler {
template <typename G>
friend auto makeTask(G&&);
+ template <typename... Args>
+ friend sp<Task<F>> sp<Task<F>>::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 <typename F>
inline auto makeTask(F&& f) {
- sp<Task<F>> task = new Task<F>(std::move(f));
+ sp<Task<F>> task = sp<Task<F>>::make(std::forward<F>(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<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
std::chrono::nanoseconds workDuration) = 0;
+ virtual void destroyVsync() = 0;
virtual void setDuration(std::chrono::nanoseconds workDuration) = 0;
- virtual void setInjector(sp<EventThreadConnection>) = 0;
virtual void waitMessage() = 0;
virtual void postMessage(sp<MessageHandler>&&) = 0;
+ virtual void postMessageDelayed(sp<MessageHandler>&&, 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<int64_t> mVsyncId = 0;
- std::atomic<nsecs_t> mExpectedVsyncTime = 0;
+
+ std::atomic<VsyncId> mVsyncId;
+ std::atomic<TimePoint> 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<scheduler::VSyncDispatch>) EXCLUDES(mVsync.mutex);
+
private:
+ virtual void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) = 0;
+
ICompositor& mCompositor;
const sp<Looper> mLooper;
const sp<Handler> mHandler;
struct Vsync {
frametimeline::TokenManager* tokenManager = nullptr;
- std::unique_ptr<scheduler::VSyncCallbackRegistration> registration;
mutable std::mutex mutex;
+ std::unique_ptr<scheduler::VSyncCallbackRegistration> registration GUARDED_BY(mutex);
TracedOrdinal<std::chrono::nanoseconds> 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<nsecs_t> scheduledFrameTime GUARDED_BY(mutex);
TracedOrdinal<int> value = {"VSYNC-sf", 0};
};
- struct Injector {
- gui::BitTube tube;
- std::mutex mutex;
- sp<EventThreadConnection> 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<scheduler::VSyncCallbackRegistration> onNewVsyncScheduleLocked(
+ std::shared_ptr<scheduler::VSyncDispatch>) REQUIRES(mVsync.mutex);
public:
explicit MessageQueue(ICompositor&);
- void initVsync(scheduler::VSyncDispatch&, frametimeline::TokenManager&,
+ void initVsync(std::shared_ptr<scheduler::VSyncDispatch>, frametimeline::TokenManager&,
std::chrono::nanoseconds workDuration) override;
+ void destroyVsync() override;
void setDuration(std::chrono::nanoseconds workDuration) override;
- void setInjector(sp<EventThreadConnection>) override;
void waitMessage() override;
void postMessage(sp<MessageHandler>&&) override;
+ void postMessageDelayed(sp<MessageHandler>&&, nsecs_t uptimeDelay) override;
+ void scheduleConfigure() override;
void scheduleFrame() override;
std::optional<Clock::time_point> getScheduledFrameTime() const override;
diff --git a/services/surfaceflinger/Scheduler/OneShotTimer.cpp b/services/surfaceflinger/Scheduler/OneShotTimer.cpp
index 3c8dc64f10..cd45bfdab3 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 2017c31513..02e8719c08 100644
--- a/services/surfaceflinger/Scheduler/OneShotTimer.h
+++ b/services/surfaceflinger/Scheduler/OneShotTimer.h
@@ -23,6 +23,7 @@
#include "../Clock.h"
#include <android-base/thread_annotations.h>
+#include <scheduler/Time.h>
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> clock = std::make_unique<SteadyClock>());
+ std::unique_ptr<android::Clock> clock = std::make_unique<SteadyClock>());
~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<Clock> mClock;
+ std::unique_ptr<android::Clock> 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 a48c921378..0000000000
--- 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 <chrono>
-#include <cmath>
-
-#include <android-base/properties.h>
-#include <android-base/stringprintf.h>
-#include <ftl/enum.h>
-#include <utils/Trace.h>
-
-#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 <typename Iterator>
-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<int>(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<Fps> constructKnownFrameRates(const DisplayModes& modes) {
- std::vector<Fps> 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 <typename Filter>
-std::vector<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes, Filter&& filter) {
- std::vector<DisplayModeIterator> 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<DisplayModeIterator>& 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<nsecs_t, nsecs_t> 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<float>(layerPeriod) / static_cast<float>(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<float>(layerPeriod) / static_cast<float>(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<LayerRequirement>& layers,
- GlobalSignals signals) const
- -> std::pair<DisplayModePtr, GlobalSignals> {
- 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<LayerRequirement>& layers,
- GlobalSignals signals) const
- -> std::pair<DisplayModePtr, GlobalSignals> {
- 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<RefreshRateScore> 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<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>>
-groupLayersByUid(const std::vector<RefreshRateConfigs::LayerRequirement>& layers) {
- std::unordered_map<uid_t, std::vector<const RefreshRateConfigs::LayerRequirement*>> layersByUid;
- for (const auto& layer : layers) {
- auto iter = layersByUid.emplace(layer.ownerUid,
- std::vector<const RefreshRateConfigs::LayerRequirement*>());
- 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<LayerRequirement>& layers, Fps displayRefreshRate,
- GlobalSignals globalSignals) const {
- ATRACE_CALL();
-
- ALOGV("%s: %zu layers", __func__, layers.size());
-
- std::lock_guard lock(mLock);
-
- std::vector<RefreshRateScore> 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<uid_t, std::vector<const LayerRequirement*>> 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<Fps> RefreshRateConfigs::onKernelTimerChanged(
- std::optional<DisplayModeId> 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>& 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<int>(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 0000000000..e06221a43d
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.cpp
@@ -0,0 +1,1530 @@
+/*
+ * 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 <chrono>
+#include <cmath>
+#include <deque>
+#include <map>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <ftl/enum.h>
+#include <ftl/fake_guard.h>
+#include <ftl/match.h>
+#include <ftl/unit.h>
+#include <gui/TraceUtils.h>
+#include <scheduler/FrameRateMode.h>
+#include <utils/Trace.h>
+
+#include "RefreshRateSelector.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+#undef LOG_TAG
+#define LOG_TAG "RefreshRateSelector"
+
+namespace android::scheduler {
+namespace {
+
+using namespace com::android::graphics::surfaceflinger;
+
+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<Fps> constructKnownFrameRates(const DisplayModes& modes) {
+ std::vector<Fps> 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->getPeakFps());
+ }
+
+ // 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<DisplayModeIterator> sortByRefreshRate(const DisplayModes& modes) {
+ std::vector<DisplayModeIterator> 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->getVsyncRate().getPeriodNsecs() == mode2->getVsyncRate().getPeriodNsecs()) {
+ return mode1->getGroup() > mode2->getGroup();
+ }
+
+ return mode1->getVsyncRate().getPeriodNsecs() > mode2->getVsyncRate().getPeriodNsecs();
+ });
+
+ return sortedModes;
+}
+
+std::pair<unsigned, unsigned> divisorRange(Fps vsyncRate, Fps peakFps, 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
+ auto start = std::max(1, static_cast<int>(peakFps / range.max) - 1);
+ if (FlagManager::getInstance().vrr_config()) {
+ start = std::max(1,
+ static_cast<int>(vsyncRate /
+ std::min(range.max, peakFps, fps_approx_ops::operator<)) -
+ 1);
+ }
+ const auto end = vsyncRate /
+ std::max(range.min, RefreshRateSelector::kMinSupportedFrameRate,
+ fps_approx_ops::operator<);
+
+ return {start, end};
+}
+
+bool shouldEnableFrameRateOverride(const std::vector<DisplayModeIterator>& 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->getPeakFps(),
+ mode2->getPeakFps()) >= 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(
+ const Policy& policy, std::function<bool(const DisplayMode&)>&& filterModes,
+ const FpsRange& renderRange) const -> std::vector<FrameRateMode> {
+ 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<Key, DisplayModeIterator, KeyLess> ratesMap;
+ for (auto it = mDisplayModes.begin(); it != mDisplayModes.end(); ++it) {
+ const auto& [id, mode] = *it;
+
+ if (!filterModes(*mode)) {
+ continue;
+ }
+ const auto vsyncRate = mode->getVsyncRate();
+ const auto peakFps = mode->getPeakFps();
+ const auto [start, end] =
+ divisorRange(vsyncRate, peakFps, renderRange, mConfig.enableFrameRateOverride);
+ for (auto divisor = start; divisor <= end; divisor++) {
+ const auto fps = vsyncRate / 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(%s))", __func__, to_string(fps).c_str(),
+ to_string(peakFps).c_str(), to_string(vsyncRate).c_str());
+ } else {
+ // If the primary physical range is a single rate, prefer to stay in that rate
+ // even if there is a lower physical refresh rate available. This would cause more
+ // cases to stay within the primary physical range
+ const Fps existingModeFps = existingIter->second->second->getPeakFps();
+ const bool existingModeIsPrimaryRange = policy.primaryRangeIsSingleRate() &&
+ policy.primaryRanges.physical.includes(existingModeFps);
+ const bool newModeIsPrimaryRange = policy.primaryRangeIsSingleRate() &&
+ policy.primaryRanges.physical.includes(mode->getPeakFps());
+ if (newModeIsPrimaryRange == existingModeIsPrimaryRange) {
+ // We might need to update the map as we found a lower refresh rate
+ if (isStrictlyLess(mode->getPeakFps(), existingModeFps)) {
+ existingIter->second = it;
+ ALOGV("%s: changing %s (%s(%s)) as we found a lower physical rate",
+ __func__, to_string(fps).c_str(), to_string(peakFps).c_str(),
+ to_string(vsyncRate).c_str());
+ }
+ } else if (newModeIsPrimaryRange) {
+ existingIter->second = it;
+ ALOGV("%s: changing %s (%s(%s)) to stay in the primary range", __func__,
+ to_string(fps).c_str(), to_string(peakFps).c_str(),
+ to_string(vsyncRate).c_str());
+ }
+ }
+ }
+ }
+
+ std::vector<FrameRateMode> 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->getVsyncRate(),
+ rhs.modePtr->getVsyncRate());
+ });
+ 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<nsecs_t, nsecs_t> 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::calculateNonExactMatchingDefaultLayerScoreLocked(
+ nsecs_t displayPeriod, nsecs_t layerPeriod) const {
+ // 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<float>(layerPeriod) / static_cast<float>(actualLayerPeriod));
+}
+
+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) {
+ return calculateNonExactMatchingDefaultLayerScoreLocked(displayPeriod, layerPeriod);
+ }
+
+ if (layer.vote == LayerVoteType::ExplicitGte) {
+ using fps_approx_ops::operator>=;
+ if (refreshRate >= layer.desiredRefreshRate) {
+ return 1.0f;
+ } else {
+ return calculateDistanceScoreLocked(layer.desiredRefreshRate, refreshRate);
+ }
+ }
+
+ if (layer.vote == LayerVoteType::ExplicitExactOrMultiple ||
+ layer.vote == LayerVoteType::Heuristic) {
+ using fps_approx_ops::operator<;
+ if (refreshRate < 60_Hz) {
+ const bool favorsAtLeast60 =
+ std::find_if(mFrameRatesThatFavorsAtLeast60.begin(),
+ mFrameRatesThatFavorsAtLeast60.end(), [&](Fps fps) {
+ using fps_approx_ops::operator==;
+ return fps == layer.desiredRefreshRate;
+ }) != mFrameRatesThatFavorsAtLeast60.end();
+ if (favorsAtLeast60) {
+ return 0;
+ }
+ }
+
+ 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<float>(layerPeriod) / static_cast<float>(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::calculateDistanceScoreLocked(Fps referenceRate, Fps refreshRate) const {
+ using fps_approx_ops::operator>=;
+ const float ratio = referenceRate >= refreshRate
+ ? refreshRate.getValue() / referenceRate.getValue()
+ : referenceRate.getValue() / refreshRate.getValue();
+ // Use ratio^2 to get a lower score the more we get further from the reference rate.
+ return ratio * ratio;
+}
+
+float RefreshRateSelector::calculateDistanceScoreFromMaxLocked(Fps refreshRate) const {
+ const auto& maxFps = mAppRequestFrameRates.back().fps;
+ return calculateDistanceScoreLocked(maxFps, refreshRate);
+}
+
+float RefreshRateSelector::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 (layer.vote == LayerVoteType::ExplicitCategory) {
+ if (getFrameRateCategoryRange(layer.frameRateCategory).includes(refreshRate)) {
+ return 1.f;
+ }
+
+ FpsRange categoryRange = getFrameRateCategoryRange(layer.frameRateCategory);
+ using fps_approx_ops::operator<;
+ if (refreshRate < categoryRange.min) {
+ return calculateNonExactMatchingDefaultLayerScoreLocked(refreshRate.getPeriodNsecs(),
+ categoryRange.min
+ .getPeriodNsecs());
+ }
+ return calculateNonExactMatchingDefaultLayerScoreLocked(refreshRate.getPeriodNsecs(),
+ categoryRange.max.getPeriodNsecs());
+ }
+
+ // If the layer wants Max, give higher score to the higher refresh rate
+ if (layer.vote == LayerVoteType::Max) {
+ return calculateDistanceScoreFromMaxLocked(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 (layer.desiredRefreshRate.isValid() &&
+ 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<LayerRequirement>& 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<LayerRequirement>& 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 explicitGteLayers = 0;
+ int explicitCategoryVoteLayers = 0;
+ int seamedFocusedLayers = 0;
+ int categorySmoothSwitchOnlyLayers = 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::ExplicitGte:
+ explicitGteLayers++;
+ break;
+ case LayerVoteType::ExplicitCategory:
+ explicitCategoryVoteLayers++;
+ if (layer.frameRateCategory == FrameRateCategory::NoPreference) {
+ // Count this layer for Min vote as well. The explicit vote avoids
+ // touch boost and idle for choosing a category, while Min vote is for correct
+ // behavior when all layers are Min or no vote.
+ minVoteLayers++;
+ }
+ break;
+ case LayerVoteType::Heuristic:
+ break;
+ }
+
+ if (layer.seamlessness == Seamlessness::SeamedAndSeamless && layer.focused) {
+ seamedFocusedLayers++;
+ }
+ if (layer.frameRateCategorySmoothSwitchOnly) {
+ categorySmoothSwitchOnlyLayers++;
+ }
+ }
+
+ const bool hasExplicitVoteLayers = explicitDefaultVoteLayers > 0 ||
+ explicitExactOrMultipleVoteLayers > 0 || explicitExact > 0 || explicitGteLayers > 0 ||
+ explicitCategoryVoteLayers > 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.
+ if (!signals.touch && signals.idle &&
+ !(policy->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};
+ }
+
+ const bool smoothSwitchOnly = categorySmoothSwitchOnlyLayers > 0;
+ const DisplayModeId activeModeId = activeMode.getId();
+
+ // 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,
+ std::nullopt, [&](FrameRateMode mode) {
+ return !smoothSwitchOnly ||
+ mode.modePtr->getId() == activeModeId;
+ });
+ 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<RefreshRateScore> 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, category %s) ",
+ layer.name.c_str(), ftl::enum_string(layer.vote).c_str(), layer.weight,
+ layer.desiredRefreshRate.getValue(),
+ ftl::enum_string(layer.frameRateCategory).c_str());
+ if (layer.isNoVote() || layer.frameRateCategory == FrameRateCategory::NoPreference ||
+ 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;
+ }
+
+ if (smoothSwitchOnly && modePtr->getId() != activeModeId) {
+ ALOGV("%s ignores %s because it's non-VRR and smooth switch only."
+ " 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 inPrimaryPhysicalRange =
+ policy->primaryRanges.physical.includes(modePtr->getPeakFps());
+ const bool inPrimaryRenderRange = policy->primaryRanges.render.includes(fps);
+ if (((policy->primaryRangeIsSingleRate() && !inPrimaryPhysicalRange) ||
+ !inPrimaryRenderRange) &&
+ !(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:
+ case LayerVoteType::ExplicitGte:
+ case LayerVoteType::ExplicitCategory:
+ return false;
+ }
+ }(layer.vote);
+ const bool layerBelowThreshold = mConfig.frameRateMultipleThreshold != 0 &&
+ layer.desiredRefreshRate <
+ Fps::fromValue(mConfig.frameRateMultipleThreshold / 2);
+ if (fixedSourceLayer && layerBelowThreshold) {
+ const bool modeAboveThreshold =
+ modePtr->getPeakFps() >= Fps::fromValue(mConfig.frameRateMultipleThreshold);
+ if (modeAboveThreshold) {
+ ALOGV("%s gives %s (%s(%s)) fixed source (above threshold) score of %.4f",
+ formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+ to_string(modePtr->getPeakFps()).c_str(),
+ to_string(modePtr->getVsyncRate()).c_str(), layerScore);
+ fixedRateBelowThresholdLayersScore.modeAboveThreshold += weightedLayerScore;
+ } else {
+ ALOGV("%s gives %s (%s(%s)) fixed source (below threshold) score of %.4f",
+ formatLayerInfo(layer, weight).c_str(), to_string(fps).c_str(),
+ to_string(modePtr->getPeakFps()).c_str(),
+ to_string(modePtr->getVsyncRate()).c_str(), layerScore);
+ fixedRateBelowThresholdLayersScore.modeBelowThreshold += weightedLayerScore;
+ }
+ } else {
+ ALOGV("%s gives %s (%s(%s)) score of %.4f", formatLayerInfo(layer, weight).c_str(),
+ to_string(fps).c_str(), to_string(modePtr->getPeakFps()).c_str(),
+ to_string(modePtr->getVsyncRate()).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(%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->getPeakFps()).c_str(),
+ to_string(maxScoreIt->frameRateMode.modePtr->getVsyncRate()).c_str(),
+ maxScoreAboveThreshold ? "above" : "below");
+ return maxScoreIt->frameRateMode.modePtr->getPeakFps() >=
+ 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(%s)) adjusted overallScore is %.4f", to_string(frameRateMode.fps).c_str(),
+ to_string(frameRateMode.modePtr->getPeakFps()).c_str(),
+ to_string(frameRateMode.modePtr->getVsyncRate()).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 (policy->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 {
+ ALOGV("primaryRangeIsSingleRate");
+ 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 && explicitCategoryVoteLayers == 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) {
+ ALOGV("preferredDisplayMode");
+ 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};
+ }
+
+ ALOGV("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
+ ATRACE_FORMAT_INSTANT("%s (scored))", to_string(ranking.front().frameRateMode.fps).c_str());
+ return {ranking, kNoSignals};
+}
+
+using LayerRequirementPtrs = std::vector<const RefreshRateSelector::LayerRequirement*>;
+using PerUidLayerRequirements = std::unordered_map<uid_t, LayerRequirementPtrs>;
+
+PerUidLayerRequirements groupLayersByUid(
+ const std::vector<RefreshRateSelector::LayerRequirement>& 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<LayerRequirement>& 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
+ // TODO(b/297600226): revise this for dVRR
+ const Fps minFrameRate = std::max(policyPtr->appRequestRanges.render.min, 30_Hz, isApproxLess);
+
+ using fps_approx_ops::operator/;
+ const unsigned numMultiples = displayRefreshRate / minFrameRate;
+
+ std::vector<std::pair<Fps, float>> 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->isNoVote() || layer->frameRateCategory == FrameRateCategory::NoPreference ||
+ layer->vote == LayerVoteType::Min) {
+ continue;
+ }
+
+ LOG_ALWAYS_FATAL_IF(layer->vote != LayerVoteType::ExplicitDefault &&
+ layer->vote != LayerVoteType::ExplicitExactOrMultiple &&
+ layer->vote != LayerVoteType::ExplicitExact &&
+ layer->vote != LayerVoteType::ExplicitCategory,
+ "Invalid layer vote type for frame rate overrides");
+ 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);
+ ATRACE_FORMAT_INSTANT("%s: overriding to %s for uid=%d", __func__,
+ to_string(overrideFps).c_str(), uid);
+ frameRateOverrides.emplace(uid, overrideFps);
+ }
+
+ return frameRateOverrides;
+}
+
+ftl::Optional<FrameRateMode> RefreshRateSelector::onKernelTimerChanged(
+ ftl::Optional<DisplayModeId> desiredModeIdOpt, bool timerExpired) const {
+ std::lock_guard lock(mLock);
+
+ const auto current =
+ desiredModeIdOpt
+ .and_then([this](DisplayModeId modeId)
+ REQUIRES(mLock) { return mDisplayModes.get(modeId); })
+ .transform([](const DisplayModePtr& modePtr) {
+ return FrameRateMode{modePtr->getPeakFps(), ftl::as_non_null(modePtr)};
+ })
+ .or_else([this] {
+ ftl::FakeGuard guard(mLock);
+ return std::make_optional(getActiveModeLocked());
+ })
+ .value();
+
+ const DisplayModePtr& min = mMinRefreshRateModeIt->second;
+ if (current.modePtr->getId() == min->getId()) {
+ return {};
+ }
+
+ return timerExpired ? FrameRateMode{min->getPeakFps(), 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<DisplayModePtr>* maxByAnchor = &mPrimaryFrameRates.back().modePtr;
+ const ftl::NonNull<DisplayModePtr>* max = &mPrimaryFrameRates.back().modePtr;
+
+ bool maxByAnchorFound = false;
+ for (auto it = mPrimaryFrameRates.rbegin(); it != mPrimaryFrameRates.rend(); ++it) {
+ using namespace fps_approx_ops;
+ if (it->modePtr->getPeakFps() > (*max)->getPeakFps()) {
+ max = &it->modePtr;
+ }
+
+ if (anchorGroup == it->modePtr->getGroup() &&
+ it->modePtr->getPeakFps() >= (*maxByAnchor)->getPeakFps()) {
+ 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<int> anchorGroupOpt,
+ RefreshRateOrder refreshRateOrder,
+ std::optional<DisplayModeId> preferredDisplayModeOpt,
+ const RankFrameRatesPredicate& predicate) const
+ -> FrameRateRanking {
+ using fps_approx_ops::operator<;
+ const char* const whence = __func__;
+
+ // find the highest frame rate for each display mode
+ ftl::SmallMap<DisplayModeId, Fps, 8> 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<ScoredFrameRate> ranking;
+ const auto rankFrameRate = [&](const FrameRateMode& frameRateMode) REQUIRES(mLock) {
+ const auto& modePtr = frameRateMode.modePtr;
+ if ((anchorGroupOpt && modePtr->getGroup() != anchorGroupOpt) ||
+ !predicate(frameRateMode)) {
+ 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 = calculateDistanceScoreFromMaxLocked(frameRateMode.fps);
+
+ if (ascending) {
+ score = 1.0f / score;
+ }
+
+ constexpr float kScore = std::numeric_limits<float>::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(%s)) scored %.2f", whence, ftl::enum_string(refreshRateOrder).c_str(),
+ to_string(frameRateMode.fps).c_str(), to_string(modePtr->getPeakFps()).c_str(),
+ to_string(modePtr->getVsyncRate()).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<int> 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()->getPeakFps(),
+ 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->getPeakFps(), 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()->getPeakFps())) {
+ 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<decltype(policy)>;
+
+ if constexpr (std::is_same_v<T, DisplayManagerPolicy>) {
+ mDisplayManagerPolicy = policy;
+ } else {
+ static_assert(std::is_same_v<T, OverridePolicy>);
+ 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.getPeakFps()) &&
+ (supportsFrameRateOverride() || ranges.render.includes(mode.getPeakFps()));
+ };
+
+ auto frameRateModes = createFrameRateModes(*policy, filterModes, ranges.render);
+ if (frameRateModes.empty()) {
+ ALOGW("No matching frame rate modes for %s range. policy: %s", rangeName,
+ policy->toString().c_str());
+ // TODO(b/292105422): Ideally DisplayManager should not send render ranges smaller than
+ // the min supported. See b/292047939.
+ // For not we just ignore the render ranges.
+ frameRateModes = createFrameRateModes(*policy, filterModes, {});
+ }
+ LOG_ALWAYS_FATAL_IF(frameRateModes.empty(),
+ "No matching frame rate modes for %s range even after ignoring the "
+ "render 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->getPeakFps();
+ 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->getPeakFps())) {
+ 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<int>(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<KernelIdleTimerController>)
+ .value_or("Platform"sv));
+ }
+}
+
+std::chrono::milliseconds RefreshRateSelector::getIdleTimerTimeout() {
+ return mConfig.idleTimerTimeout;
+}
+
+// TODO(b/293651105): Extract category FpsRange mapping to OEM-configurable config.
+FpsRange RefreshRateSelector::getFrameRateCategoryRange(FrameRateCategory category) {
+ switch (category) {
+ case FrameRateCategory::High:
+ return FpsRange{90_Hz, 120_Hz};
+ case FrameRateCategory::Normal:
+ return FpsRange{60_Hz, 90_Hz};
+ case FrameRateCategory::Low:
+ return FpsRange{30_Hz, 30_Hz};
+ case FrameRateCategory::NoPreference:
+ case FrameRateCategory::Default:
+ LOG_ALWAYS_FATAL("Should not get fps range for frame rate category: %s",
+ ftl::enum_string(category).c_str());
+ return FpsRange{0_Hz, 0_Hz};
+ default:
+ LOG_ALWAYS_FATAL("Invalid frame rate category for range: %s",
+ ftl::enum_string(category).c_str());
+ return FpsRange{0_Hz, 0_Hz};
+ }
+}
+
+} // 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
index a79002e959..a1a7c289cf 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateConfigs.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateSelector.h
@@ -16,48 +16,44 @@
#pragma once
-#include <algorithm>
-#include <numeric>
-#include <optional>
#include <type_traits>
#include <utility>
+#include <variant>
+#include <ftl/concat.h>
+#include <ftl/optional.h>
+#include <ftl/unit.h>
#include <gui/DisplayEventReceiver.h>
#include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
#include <scheduler/Seamlessness.h>
#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 {
using namespace std::chrono_literals;
-enum class DisplayModeEvent : unsigned { None = 0b0, Changed = 0b1 };
-
-inline DisplayModeEvent operator|(DisplayModeEvent lhs, DisplayModeEvent rhs) {
- using T = std::underlying_type_t<DisplayModeEvent>;
- return static_cast<DisplayModeEvent>(static_cast<T>(lhs) | static_cast<T>(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,64 +62,65 @@ 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 range,
+ bool allowGroupSwitching = kAllowGroupSwitchingDefault)
+ : Policy(defaultMode, FpsRanges{range, range}, FpsRanges{range, range},
+ allowGroupSwitching) {}
- Policy(DisplayModeId defaultMode, FpsRange primaryRange, FpsRange appRequestRange)
- : Policy(defaultMode, kAllowGroupSwitchingDefault, primaryRange, appRequestRange) {}
-
- 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;
}
bool operator!=(const Policy& other) const { return !(*this == other); }
+
+ bool primaryRangeIsSingleRate() const {
+ return isApproxEqual(primaryRanges.physical.min, primaryRanges.physical.max);
+ }
+
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<DisplayManagerPolicy, OverridePolicy, NoOverridePolicy>;
+
+ 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>& 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 +128,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 {
@@ -145,8 +142,10 @@ public:
// ExactOrMultiple compatibility
ExplicitExact, // Specific refresh rate that was provided by the app with
// Exact compatibility
+ ExplicitGte, // Greater than or equal to frame rate provided by the app
+ ExplicitCategory, // Specific frame rate category was provided by the app
- ftl_last = ExplicitExact
+ ftl_last = ExplicitCategory
};
// Captures the layer requirements for a refresh rate. This will be used to determine the
@@ -162,6 +161,11 @@ public:
Fps desiredRefreshRate;
// If a seamless mode switch is required.
Seamlessness seamlessness = Seamlessness::Default;
+ // Layer frame rate category.
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+ // Goes together with frame rate category vote. Allow refresh rate changes only
+ // if there would be no jank.
+ bool frameRateCategorySmoothSwitchOnly = false;
// Layer's weight in the range of [0, 1]. The higher the weight the more impact this layer
// would have on choosing the refresh rate.
float weight = 0.0f;
@@ -172,43 +176,86 @@ public:
return name == other.name && vote == other.vote &&
isApproxEqual(desiredRefreshRate, other.desiredRefreshRate) &&
seamlessness == other.seamlessness && weight == other.weight &&
- focused == other.focused;
+ focused == other.focused && frameRateCategory == other.frameRateCategory;
}
bool operator!=(const LayerRequirement& other) const { return !(*this == other); }
+
+ bool isNoVote() const { return RefreshRateSelector::isNoVote(vote); }
};
+ // Returns true if the layer explicitly instructs to not contribute to refresh rate selection.
+ // In other words, true if the layer should be ignored.
+ static bool isNoVote(LayerVoteType vote) { return vote == LayerVoteType::NoVote; }
+
// Global state describing signals that affect refresh rate choice.
struct GlobalSignals {
// Whether the user touched the screen recently. Used to apply touch boost.
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<DisplayModePtr, GlobalSignals> getBestRefreshRate(
- const std::vector<LayerRequirement>&, 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<ScoredFrameRate>;
+
+ 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<LayerRequirement>&, GlobalSignals) const
+ EXCLUDES(mLock);
FpsRange getSupportedRefreshRateRange() const EXCLUDES(mLock) {
std::lock_guard lock(mLock);
- return {mMinRefreshRateModeIt->second->getFps(), mMaxRefreshRateModeIt->second->getFps()};
+ return {mMinRefreshRateModeIt->second->getPeakFps(),
+ mMaxRefreshRateModeIt->second->getPeakFps()};
}
- std::optional<Fps> onKernelTimerChanged(std::optional<DisplayModeId> desiredActiveModeId,
- bool timerExpired) const EXCLUDES(mLock);
+ ftl::Optional<FrameRateMode> onKernelTimerChanged(ftl::Optional<DisplayModeId> desiredModeIdOpt,
+ 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 +264,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 +292,49 @@ public:
// The controller representing how the kernel idle timer will be configured
// either on the HWC api or sysprop.
- std::optional<KernelIdleTimerController> kernelIdleTimerController;
+ ftl::Optional<KernelIdleTimerController> 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
@@ -277,6 +352,9 @@ public:
Fps displayFrameRate, GlobalSignals) const
EXCLUDES(mLock);
+ // Gets the FpsRange that the FrameRateCategory represents.
+ static FpsRange getFrameRateCategoryRange(FrameRateCategory category);
+
std::optional<KernelIdleTimerController> kernelIdleTimerController() {
return mConfig.kernelIdleTimerController;
}
@@ -313,27 +391,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<DisplayModePtr, GlobalSignals> getBestRefreshRateLocked(
- const std::vector<LayerRequirement>&, GlobalSignals) const REQUIRES(mLock);
+ // See mActiveModeOpt for thread safety.
+ const FrameRateMode& getActiveModeLocked() const REQUIRES(mLock);
+
+ RankedFrameRates getRankedFrameRatesLocked(const std::vector<LayerRequirement>& 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 +429,36 @@ 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
+ };
+
+ typedef std::function<bool(const FrameRateMode)> RankFrameRatesPredicate;
+
+ // Rank the frame rates.
+ // Only modes in the primary range for which `predicate` is `true` will be scored.
+ // Does not use the app requested range.
+ FrameRateRanking rankFrameRates(
+ std::optional<int> anchorGroupOpt, RefreshRateOrder refreshRateOrder,
+ std::optional<DisplayModeId> preferredDisplayModeOpt = std::nullopt,
+ const RankFrameRatesPredicate& predicate = [](FrameRateMode) { return true; }) 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 calculateDistanceScoreFromMaxLocked(Fps refreshRate) const REQUIRES(mLock);
+
+ // Returns the refresh rate score based on its distance from the reference rate.
+ float calculateDistanceScoreLocked(Fps referenceRate, 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 +467,13 @@ private:
float calculateNonExactMatchingLayerScoreLocked(const LayerRequirement&, Fps refreshRate) const
REQUIRES(mLock);
- void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock);
+ // Calculates the score for non-exact matching layer that has LayerVoteType::ExplicitDefault.
+ float calculateNonExactMatchingDefaultLayerScoreLocked(nsecs_t displayPeriod,
+ nsecs_t layerPeriod) const
+ REQUIRES(mLock);
+
+ void updateDisplayModes(DisplayModes, DisplayModeId activeModeId) EXCLUDES(mLock)
+ REQUIRES(kMainThreadContext);
void initializeIdleTimer();
@@ -372,22 +484,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<FrameRateMode> createFrameRateModes(
+ const Policy&, std::function<bool(const DisplayMode&)>&& 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<Fps, ftl::Unit, 8, FpsApproxEqual> mAppOverrideNativeRefreshRates;
+
+ ftl::Optional<FrameRateMode> 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<DisplayModeIterator> mPrimaryRefreshRates GUARDED_BY(mLock);
- std::vector<DisplayModeIterator> mAppRequestRefreshRates GUARDED_BY(mLock);
+ std::vector<FrameRateMode> mPrimaryFrameRates GUARDED_BY(mLock);
+ std::vector<FrameRateMode> mAppRequestFrameRates GUARDED_BY(mLock);
Policy mDisplayManagerPolicy GUARDED_BY(mLock);
std::optional<Policy> 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 +526,25 @@ private:
const std::vector<Fps> mKnownFrameRates;
const Config mConfig;
- bool mSupportsFrameRateOverrideByContent;
- struct GetBestRefreshRateCache {
+ // A list of known frame rates that favors at least 60Hz if there is no exact match display
+ // refresh rate
+ const std::vector<Fps> mFrameRatesThatFavorsAtLeast60 = {23.976_Hz, 25_Hz, 29.97_Hz, 50_Hz,
+ 59.94_Hz};
+
+ Config::FrameRateOverride mFrameRateOverrideConfig;
+
+ struct GetRankedFrameRatesCache {
std::pair<std::vector<LayerRequirement>, GlobalSignals> arguments;
- std::pair<DisplayModePtr, GlobalSignals> result;
+ RankedFrameRates result;
};
- mutable std::optional<GetBestRefreshRateCache> mGetBestRefreshRateCache GUARDED_BY(mLock);
+ mutable std::optional<GetRankedFrameRatesCache> mGetRankedFrameRatesCache GUARDED_BY(mLock);
// Declare mIdleTimer last to ensure its thread joins before the mutex/callbacks are destroyed.
std::mutex mIdleTimerCallbacksMutex;
std::optional<IdleTimerCallbacks> mIdleTimerCallbacks GUARDED_BY(mIdleTimerCallbacksMutex);
// Used to detect (lack of) frame activity.
- std::optional<scheduler::OneShotTimer> mIdleTimer;
+ ftl::Optional<scheduler::OneShotTimer> mIdleTimer;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/RefreshRateStats.h b/services/surfaceflinger/Scheduler/RefreshRateStats.h
index ed65bc607d..67e1b9c2ea 100644
--- a/services/surfaceflinger/Scheduler/RefreshRateStats.h
+++ b/services/surfaceflinger/Scheduler/RefreshRateStats.h
@@ -22,6 +22,7 @@
#include <string>
#include <android-base/stringprintf.h>
+#include <ftl/algorithm.h>
#include <ftl/small_map.h>
#include <utils/Timers.h>
@@ -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<std::chrono::milliseconds>([] {
+ 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<std::chrono::milliseconds>([] {
+ using namespace std::chrono_literals;
+ return 0ms;
+ }))
+ .value();
+
mFpsTotalTimes.emplace_or_replace(mCurrentRefreshRate, total.get() + duration);
fps = static_cast<uint32_t>(mCurrentRefreshRate.getIntValue());
diff --git a/services/surfaceflinger/Scheduler/Scheduler.cpp b/services/surfaceflinger/Scheduler/Scheduler.cpp
index 727cb0817e..ce59a04808 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.cpp
+++ b/services/surfaceflinger/Scheduler/Scheduler.cpp
@@ -25,14 +25,19 @@
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
#include <configstore/Utils.h>
+#include <ftl/concat.h>
+#include <ftl/enum.h>
#include <ftl/fake_guard.h>
+#include <ftl/small_map.h>
+#include <gui/TraceUtils.h>
#include <gui/WindowInfo.h>
#include <system/window.h>
-#include <ui/DisplayStatInfo.h>
+#include <ui/DisplayMap.h>
#include <utils/Timers.h>
-#include <utils/Trace.h>
#include <FrameTimeline/FrameTimeline.h>
+#include <scheduler/interface/ICompositor.h>
+
#include <algorithm>
#include <cinttypes>
#include <cstdint>
@@ -40,15 +45,16 @@
#include <memory>
#include <numeric>
+#include <common/FlagManager.h>
#include "../Layer.h"
-#include "DispSyncSource.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,23 +66,38 @@
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<VsyncModulator> modulatorPtr, IVsyncTrackerCallback& vsyncTrackerCallback)
+ : impl::MessageQueue(compositor),
+ mFeatures(features),
+ mVsyncModulator(std::move(modulatorPtr)),
+ mSchedulerCallback(callback),
+ mVsyncTrackerCallback(vsyncTrackerCallback) {}
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() {
using namespace sysprop;
using namespace std::string_literals;
- if (const int64_t millis = set_touch_timer_ms(0); millis > 0) {
+ const int32_t defaultTouchTimerValue =
+ FlagManager::getInstance().enable_fro_dependent_features() &&
+ sysprop::enable_frame_rate_override(true)
+ ? 200
+ : 0;
+ if (const int32_t millis = set_touch_timer_ms(defaultTouchTimerValue); millis > 0) {
// Touch events are coming to SF every 100ms, so the timer needs to be higher than that
mTouchTimer.emplace(
"TouchTimer", std::chrono::milliseconds(millis),
@@ -94,36 +115,60 @@ void Scheduler::startTimers() {
}
}
-void Scheduler::setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs> 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();
- }
- }
- {
- // Clear state that depends on the current instance.
- std::scoped_lock lock(mPolicyLock);
- mPolicy = {};
+void Scheduler::setPacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
+ demotePacesetterDisplay();
+
+ promotePacesetterDisplay(pacesetterIdOpt);
+}
+
+void Scheduler::registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr) {
+ auto schedulePtr = std::make_shared<VsyncSchedule>(
+ selectorPtr->getActiveMode().modePtr, mFeatures,
+ [this](PhysicalDisplayId id, bool enable) { onHardwareVsyncRequest(id, enable); },
+ mVsyncTrackerCallback);
+
+ registerDisplayInternal(displayId, std::move(selectorPtr), std::move(schedulePtr));
+}
+
+void Scheduler::registerDisplayInternal(PhysicalDisplayId displayId,
+ RefreshRateSelectorPtr selectorPtr,
+ VsyncSchedulePtr schedulePtr) {
+ demotePacesetterDisplay();
+
+ auto [pacesetterVsyncSchedule, isNew] = [&]() FTL_FAKE_GUARD(kMainThreadContext) {
+ std::scoped_lock lock(mDisplayLock);
+ const bool isNew = mDisplays
+ .emplace_or_replace(displayId, displayId, std::move(selectorPtr),
+ std::move(schedulePtr), mFeatures)
+ .second;
+
+ return std::make_pair(promotePacesetterDisplayLocked(), isNew);
+ }();
+
+ applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
+
+ // Disable hardware VSYNC if the registration is new, as opposed to a renewal.
+ if (isNew) {
+ onHardwareVsyncRequest(displayId, false);
}
+}
+
+void Scheduler::unregisterDisplay(PhysicalDisplayId displayId) {
+ demotePacesetterDisplay();
- std::scoped_lock lock(mRefreshRateConfigsLock);
- mRefreshRateConfigs = std::move(configs);
- if (!mRefreshRateConfigs) return;
+ std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
+ {
+ std::scoped_lock lock(mDisplayLock);
+ mDisplays.erase(displayId);
- mRefreshRateConfigs->setIdleTimerCallbacks(
- {.platform = {.onReset = [this] { idleTimerCallback(TimerState::Reset); },
- .onExpired = [this] { idleTimerCallback(TimerState::Expired); }},
- .kernel = {.onReset = [this] { kernelIdleTimerCallback(TimerState::Reset); },
- .onExpired = [this] { kernelIdleTimerCallback(TimerState::Expired); }}});
+ // 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!");
- mRefreshRateConfigs->startIdleTimer();
+ pacesetterVsyncSchedule = promotePacesetterDisplayLocked();
+ }
+ applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
}
void Scheduler::run() {
@@ -132,97 +177,182 @@ void Scheduler::run() {
}
}
-void Scheduler::createVsyncSchedule(FeatureFlags features) {
- mVsyncSchedule.emplace(features);
-}
+void Scheduler::onFrameSignal(ICompositor& compositor, VsyncId vsyncId,
+ TimePoint expectedVsyncTime) {
+ const FrameTargeter::BeginFrameArgs beginFrameArgs =
+ {.frameBeginTime = SchedulerClock::now(),
+ .vsyncId = vsyncId,
+ // TODO(b/255601557): Calculate per display.
+ .expectedVsyncTime = expectedVsyncTime,
+ .sfWorkDuration = mVsyncModulator->getVsyncConfig().sfWorkDuration};
+
+ ftl::NonNull<const Display*> pacesetterPtr = pacesetterPtrLocked();
+ pacesetterPtr->targeterPtr->beginFrame(beginFrameArgs, *pacesetterPtr->schedulePtr);
+
+ {
+ FrameTargets targets;
+ targets.try_emplace(pacesetterPtr->displayId, &pacesetterPtr->targeterPtr->target());
+
+ for (const auto& [id, display] : mDisplays) {
+ if (id == pacesetterPtr->displayId) continue;
+
+ FrameTargeter& targeter = *display.targeterPtr;
+ targeter.beginFrame(beginFrameArgs, *display.schedulePtr);
+ targets.try_emplace(id, &targeter.target());
+ }
+
+ if (!compositor.commit(pacesetterPtr->displayId, targets)) return;
+ }
-std::unique_ptr<VSyncSource> Scheduler::makePrimaryDispSyncSource(
- const char* name, std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration, bool traceVsync) {
- return std::make_unique<scheduler::DispSyncSource>(mVsyncSchedule->getDispatch(),
- mVsyncSchedule->getTracker(), workDuration,
- readyDuration, traceVsync, name);
+ // The pacesetter may have changed or been registered anew during commit.
+ pacesetterPtr = pacesetterPtrLocked();
+
+ // TODO(b/256196556): Choose the frontrunner display.
+ FrameTargeters targeters;
+ targeters.try_emplace(pacesetterPtr->displayId, pacesetterPtr->targeterPtr.get());
+
+ for (auto& [id, display] : mDisplays) {
+ if (id == pacesetterPtr->displayId) continue;
+
+ FrameTargeter& targeter = *display.targeterPtr;
+ targeters.try_emplace(id, &targeter);
+ }
+
+ if (FlagManager::getInstance().vrr_config() &&
+ CC_UNLIKELY(mPacesetterFrameDurationFractionToSkip > 0.f)) {
+ const auto period = pacesetterPtr->targeterPtr->target().expectedFrameDuration();
+ const auto skipDuration = Duration::fromNs(
+ static_cast<nsecs_t>(period.ns() * mPacesetterFrameDurationFractionToSkip));
+ ATRACE_FORMAT("Injecting jank for %f%% of the frame (%" PRId64 " ns)",
+ mPacesetterFrameDurationFractionToSkip * 100, skipDuration.ns());
+ std::this_thread::sleep_for(skipDuration);
+ mPacesetterFrameDurationFractionToSkip = 0.f;
+ }
+
+ if (FlagManager::getInstance().vrr_config()) {
+ const auto minFramePeriod = pacesetterPtr->schedulePtr->minFramePeriod();
+ const auto presentFenceForPastVsync =
+ pacesetterPtr->targeterPtr->target().presentFenceForPastVsync(minFramePeriod);
+ const auto lastConfirmedPresentTime = presentFenceForPastVsync->getSignalTime();
+ if (lastConfirmedPresentTime != Fence::SIGNAL_TIME_PENDING &&
+ lastConfirmedPresentTime != Fence::SIGNAL_TIME_INVALID) {
+ pacesetterPtr->schedulePtr->getTracker()
+ .onFrameBegin(expectedVsyncTime, TimePoint::fromNs(lastConfirmedPresentTime));
+ }
+ }
+
+ const auto resultsPerDisplay = compositor.composite(pacesetterPtr->displayId, targeters);
+ compositor.sample();
+
+ for (const auto& [id, targeter] : targeters) {
+ const auto resultOpt = resultsPerDisplay.get(id);
+ LOG_ALWAYS_FATAL_IF(!resultOpt);
+ targeter->endFrame(*resultOpt);
+ }
}
std::optional<Fps> 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 expectedVsyncTime, 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(expectedVsyncTime.ns(), *frameRate);
}
-impl::EventThread::ThrottleVsyncCallback Scheduler::makeThrottleVsyncCallback() const {
- std::scoped_lock lock(mRefreshRateConfigsLock);
+bool Scheduler::isVsyncInPhase(TimePoint expectedVsyncTime, Fps frameRate) const {
+ return getVsyncSchedule()->getTracker().isVSyncInPhase(expectedVsyncTime.ns(), frameRate);
+}
- return [this](nsecs_t expectedVsyncTimestamp, uid_t uid) {
- return !isVsyncValid(expectedVsyncTimestamp, uid);
- };
+bool Scheduler::throttleVsync(android::TimePoint expectedPresentTime, uid_t uid) {
+ return !isVsyncValid(expectedPresentTime, 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();
+Period Scheduler::getVsyncPeriod(uid_t uid) {
+ 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 auto frameRate = getFrameRateOverride(uid);
- if (!frameRate.has_value()) {
- return currentPeriod;
- }
+ const Period currentPeriod = period != Period::zero() ? period : refreshRate.getPeriod();
- const auto divisor = RefreshRateConfigs::getFrameRateDivisor(refreshRate, *frameRate);
- if (divisor <= 1) {
- return currentPeriod;
- }
- return currentPeriod * divisor;
- };
+ const auto frameRate = getFrameRateOverride(uid);
+ if (!frameRate.has_value()) {
+ return currentPeriod;
+ }
+
+ const auto divisor = RefreshRateSelector::getFrameRateDivisor(refreshRate, *frameRate);
+ if (divisor <= 1) {
+ return currentPeriod;
+ }
+
+ // TODO(b/299378819): the casting is not needed, but we need a flag as it might change
+ // behaviour.
+ return Period::fromNs(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<impl::EventThread>(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<impl::EventThread>(cycle == Cycle::Render ? "app" : "appSf",
+ getVsyncSchedule(), tokenManager, *this,
+ workDuration, readyDuration);
+
+ auto& handle = cycle == Cycle::Render ? mAppConnectionHandle : mSfConnectionHandle;
+ handle = createConnection(std::move(eventThread));
+ return handle;
}
ConnectionHandle Scheduler::createConnection(std::unique_ptr<EventThread> eventThread) {
const ConnectionHandle handle = ConnectionHandle{mNextConnectionHandleId++};
ALOGV("Creating a connection handle with ID %" PRIuPTR, handle.id);
- auto connection = createConnectionInternal(eventThread.get());
+ auto connection = eventThread->createEventConnection();
std::lock_guard<std::mutex> lock(mConnectionsLock);
mConnections.emplace(handle, Connection{connection, std::move(eventThread)});
return handle;
}
-sp<EventThreadConnection> Scheduler::createConnectionInternal(
- EventThread* eventThread, ISurfaceComposer::EventRegistrationFlags eventRegistration) {
- return eventThread->createEventConnection([&] { resync(); }, eventRegistration);
-}
-
sp<IDisplayEventConnection> Scheduler::createDisplayEventConnection(
- ConnectionHandle handle, ISurfaceComposer::EventRegistrationFlags eventRegistration) {
- std::lock_guard<std::mutex> lock(mConnectionsLock);
- RETURN_IF_INVALID_HANDLE(handle, nullptr);
- return createConnectionInternal(mConnections[handle].thread.get(), eventRegistration);
+ ConnectionHandle handle, EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& layerHandle) {
+ const auto connection = [&]() -> sp<EventThreadConnection> {
+ std::scoped_lock lock(mConnectionsLock);
+ RETURN_IF_INVALID_HANDLE(handle, nullptr);
+
+ return mConnections[handle].thread->createEventConnection(eventRegistration);
+ }();
+ const auto layerId = static_cast<int32_t>(LayerHandle::getLayerId(layerHandle));
+
+ if (layerId != static_cast<int32_t>(UNASSIGNED_LAYER_ID)) {
+ // TODO(b/290409668): Moving the choreographer attachment to be a transaction that will be
+ // processed on the main thread.
+ mSchedulerCallback.onChoreographerAttached();
+
+ std::scoped_lock lock(mChoreographerLock);
+ const auto [iter, emplaced] =
+ mAttachedChoreographers.emplace(layerId,
+ AttachedChoreographers{Fps(), {connection}});
+ if (!emplaced) {
+ iter->second.connections.emplace(connection);
+ connection->frameRate = iter->second.frameRate;
+ }
+ }
+ return connection;
}
sp<EventThreadConnection> Scheduler::getEventConnection(ConnectionHandle handle) {
@@ -243,32 +373,32 @@ void Scheduler::onHotplugReceived(ConnectionHandle handle, PhysicalDisplayId dis
thread->onHotplugReceived(displayId, connected);
}
-void Scheduler::onScreenAcquired(ConnectionHandle handle) {
+void Scheduler::onHotplugConnectionError(ConnectionHandle handle, int32_t errorCode) {
android::EventThread* thread;
{
std::lock_guard<std::mutex> lock(mConnectionsLock);
RETURN_IF_INVALID_HANDLE(handle);
thread = mConnections[handle].thread.get();
}
- thread->onScreenAcquired();
- mScreenAcquired = true;
+
+ thread->onHotplugConnectionError(errorCode);
}
-void Scheduler::onScreenReleased(ConnectionHandle handle) {
+void Scheduler::enableSyntheticVsync(bool enable) {
+ // TODO(b/241285945): Remove connection handles.
+ const ConnectionHandle handle = mAppConnectionHandle;
android::EventThread* thread;
{
std::lock_guard<std::mutex> 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<FrameRateOverride> overrides =
mFrameRateOverrideMappings.getAllFrameRateOverrides(supportsFrameRateOverrideByContent);
@@ -282,7 +412,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<std::mutex> lock(mPolicyLock);
// Cache the last reported modes for primary display.
@@ -297,7 +427,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 +439,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<std::mutex> lock(mConnectionsLock);
@@ -334,12 +463,6 @@ void Scheduler::onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayM
thread->onModeChanged(mode);
}
-size_t Scheduler::getEventThreadConnectionCount(ConnectionHandle handle) {
- std::lock_guard<std::mutex> lock(mConnectionsLock);
- RETURN_IF_INVALID_HANDLE(handle, 0);
- return mConnections[handle].thread->getEventThreadConnectionCount();
-}
-
void Scheduler::dump(ConnectionHandle handle, std::string& result) const {
android::EventThread* thread;
{
@@ -361,83 +484,125 @@ 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<InjectVSyncSource>();
- mVSyncInjector = vsyncSource.get();
+void Scheduler::enableHardwareVsync(PhysicalDisplayId id) {
+ auto schedule = getVsyncSchedule(id);
+ LOG_ALWAYS_FATAL_IF(!schedule);
+ schedule->enableHardwareVsync();
+}
- auto eventThread =
- std::make_unique<impl::EventThread>(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(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, display] : mDisplays) {
+ if (display.powerMode != hal::PowerMode::OFF ||
+ !FlagManager::getInstance().multithreaded_present()) {
+ 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,
+ DisplayModePtr modePtr) {
+ 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;
+ if (display.schedulePtr->isHardwareVsyncAllowed(allowToEnable)) {
+ if (!modePtr) {
+ modePtr = display.selectorPtr->getActiveMode().modePtr.get();
+ }
+ if (modePtr->getVsyncRate().isValid()) {
+ constexpr bool kForce = false;
+ display.schedulePtr->onDisplayModeChanged(ftl::as_non_null(modePtr), kForce);
+ }
+ }
}
-void Scheduler::enableHardwareVsync() {
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- if (!mPrimaryHWVsyncEnabled && mHWVsyncAvailable) {
- mVsyncSchedule->getTracker().resetModel();
- mSchedulerCallback.setVsyncEnabled(true);
- mPrimaryHWVsyncEnabled = true;
- }
+void Scheduler::onHardwareVsyncRequest(PhysicalDisplayId id, bool enabled) {
+ static const auto& whence = __func__;
+ ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
+
+ // On main thread to serialize reads/writes of pending hardware VSYNC state.
+ static_cast<void>(
+ schedule([=]() FTL_FAKE_GUARD(mDisplayLock) FTL_FAKE_GUARD(kMainThreadContext) {
+ ATRACE_NAME(ftl::Concat(whence, ' ', id.value, ' ', enabled).c_str());
+
+ if (const auto displayOpt = mDisplays.get(id)) {
+ auto& display = displayOpt->get();
+ display.schedulePtr->setPendingHardwareVsyncState(enabled);
+
+ if (display.powerMode != hal::PowerMode::OFF) {
+ mSchedulerCallback.requestHardwareVsync(id, enabled);
+ }
+ }
+ }));
}
-void Scheduler::disableHardwareVsync(bool makeUnavailable) {
- std::lock_guard<std::mutex> 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);
+
+ 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);
+
+ ALOGV("%s %s (%s)", __func__, to_string(mode.fps).c_str(),
+ to_string(mode.modePtr->getVsyncRate()).c_str());
+
+ display.schedulePtr->getTracker().setRenderRate(renderFrameRate);
}
-void Scheduler::resyncToHardwareVsync(bool makeAvailable, Fps refreshRate) {
- {
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- if (makeAvailable) {
- mHWVsyncAvailable = makeAvailable;
- } else if (!mHWVsyncAvailable) {
- // Hardware vsync is not currently available, so abort the resync
- // attempt for now
- return;
- }
- }
+Fps Scheduler::getNextFrameInterval(PhysicalDisplayId id,
+ TimePoint currentExpectedPresentTime) const {
+ std::scoped_lock lock(mDisplayLock);
+ ftl::FakeGuard guard(kMainThreadContext);
- setVsyncPeriod(refreshRate.getPeriodNsecs());
+ const auto displayOpt = mDisplays.get(id);
+ if (!displayOpt) {
+ ALOGW("%s: Invalid display %s!", __func__, to_string(id).c_str());
+ return Fps{};
+ }
+ const Display& display = *displayOpt;
+ const nsecs_t threshold =
+ display.selectorPtr->getActiveMode().modePtr->getVsyncRate().getPeriodNsecs() / 2;
+ const nsecs_t nextVsyncTime = display.schedulePtr->getTracker().nextAnticipatedVSyncTimeFrom(
+ currentExpectedPresentTime.ns() + threshold);
+ return Fps::fromPeriodNsecs(nextVsyncTime - currentExpectedPresentTime.ns());
}
void Scheduler::resync() {
@@ -447,124 +612,139 @@ 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);
- }
-}
-
-void Scheduler::setVsyncPeriod(nsecs_t period) {
- if (period <= 0) return;
-
- std::lock_guard<std::mutex> lock(mHWVsyncLock);
- mVsyncSchedule->getController().startPeriodTransition(period);
-
- if (!mPrimaryHWVsyncEnabled) {
- mVsyncSchedule->getTracker().resetModel();
- mSchedulerCallback.setVsyncEnabled(true);
- mPrimaryHWVsyncEnabled = true;
+ resyncAllToHardwareVsync(false /* allowToEnable */);
}
}
-void Scheduler::addResyncSample(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
- bool* periodFlushed) {
- bool needsHwVsync = false;
- *periodFlushed = false;
- { // Scope for the lock
- std::lock_guard<std::mutex> 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<nsecs_t> 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(TimePoint::fromNs(timestamp), hwcVsyncPeriod);
}
-void Scheduler::addPresentFence(std::shared_ptr<FenceTime> fence) {
- if (mVsyncSchedule->getController().addPresentFence(std::move(fence))) {
- enableHardwareVsync();
+void Scheduler::addPresentFence(PhysicalDisplayId id, std::shared_ptr<FenceTime> fence) {
+ ATRACE_NAME(ftl::Concat(__func__, ' ', id.value).c_str());
+ const auto scheduleOpt =
+ (ftl::FakeGuard(mDisplayLock), mDisplays.get(id)).and_then([](const Display& display) {
+ return display.powerMode == hal::PowerMode::OFF
+ ? std::nullopt
+ : std::make_optional(display.schedulePtr);
+ });
+
+ if (!scheduleOpt) return;
+ const auto& schedule = scheduleOpt->get();
+
+ const bool needMoreSignals = schedule->getController().addPresentFence(std::move(fence));
+ if (needMoreSignals) {
+ schedule->enableHardwareVsync();
} else {
- disableHardwareVsync(false);
+ constexpr bool kDisallow = false;
+ schedule->disableHardwareVsync(kDisallow);
}
}
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,
- LayerHistory::LayerUpdateType updateType) {
- {
- std::scoped_lock lock(mRefreshRateConfigsLock);
- if (!mRefreshRateConfigs->canSwitch()) return;
- }
+void Scheduler::onLayerDestroyed(Layer* layer) {
+ std::scoped_lock lock(mChoreographerLock);
+ mAttachedChoreographers.erase(layer->getSequence());
+}
- mLayerHistory.record(layer, presentTime, systemTime(), updateType);
+void Scheduler::recordLayerHistory(int32_t id, const LayerProps& layerProps, nsecs_t presentTime,
+ nsecs_t now, LayerHistory::LayerUpdateType updateType) {
+ if (pacesetterSelectorPtr()->canSwitch()) {
+ mLayerHistory.record(id, layerProps, presentTime, now, updateType);
+ }
}
void Scheduler::setModeChangePending(bool pending) {
mLayerHistory.setModeChangePending(pending);
}
-void Scheduler::chooseRefreshRateForContent() {
- const auto configs = holdRefreshRateConfigs();
- if (!configs->canSwitch()) return;
+void Scheduler::setDefaultFrameRateCompatibility(
+ int32_t id, scheduler::FrameRateCompatibility frameRateCompatibility) {
+ mLayerHistory.setDefaultFrameRateCompatibility(id, frameRateCompatibility,
+ mFeatures.test(Feature::kContentDetection));
+}
+
+void Scheduler::setLayerProperties(int32_t id, const android::scheduler::LayerProps& properties) {
+ mLayerHistory.setLayerProperties(id, properties);
+}
+
+void Scheduler::chooseRefreshRateForContent(
+ const surfaceflinger::frontend::LayerHierarchy* hierarchy,
+ bool updateAttachedChoreographer) {
+ 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));
+
+ if (updateAttachedChoreographer) {
+ LOG_ALWAYS_FATAL_IF(!hierarchy);
+
+ // update the attached choreographers after we selected the render rate.
+ const ftl::Optional<FrameRateMode> modeOpt = [&] {
+ std::scoped_lock lock(mPolicyLock);
+ return mPolicy.modeOpt;
+ }();
+
+ if (modeOpt) {
+ updateAttachedChoreographers(*hierarchy, modeOpt->fps);
+ }
+ }
}
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<std::mutex> lock(mPolicyLock);
mPolicy.displayPowerMode = powerMode;
}
- mVsyncSchedule->getController().setDisplayPowerMode(powerMode);
+ {
+ std::scoped_lock lock(mDisplayLock);
+
+ const auto displayOpt = mDisplays.get(id);
+ LOG_ALWAYS_FATAL_IF(!displayOpt);
+ auto& display = displayOpt->get();
+
+ display.powerMode = powerMode;
+ display.schedulePtr->getController().setDisplayPowerMode(powerMode);
+ }
+ if (!isPacesetter) return;
if (mDisplayPowerTimer) {
mDisplayPowerTimer->reset();
@@ -575,15 +755,34 @@ void Scheduler::setDisplayPowerMode(hal::PowerMode powerMode) {
mLayerHistory.clear();
}
+auto Scheduler::getVsyncSchedule(std::optional<PhysicalDisplayId> idOpt) const
+ -> ConstVsyncSchedulePtr {
+ std::scoped_lock lock(mDisplayLock);
+ return getVsyncScheduleLocked(idOpt);
+}
+
+auto Scheduler::getVsyncScheduleLocked(std::optional<PhysicalDisplayId> 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<int>(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->getPeakFps();
constexpr Fps FPS_THRESHOLD_FOR_KERNEL_TIMER = 65_Hz;
using namespace fps_approx_ops;
@@ -592,12 +791,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(kDisallow);
+ }
}
mSchedulerCallback.kernelTimerChanged(state == TimerState::Expired);
@@ -625,78 +829,309 @@ void Scheduler::displayPowerTimerCallback(TimerState state) {
ATRACE_INT("ExpiredDisplayPowerTimer", static_cast<int>(state));
}
-void Scheduler::dump(std::string& result) const {
- using base::StringAppendF;
+void Scheduler::dump(utils::Dumper& dumper) const {
+ using namespace std::string_view_literals;
- 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());
+ {
+ utils::Dumper::Section section(dumper, "Features"sv);
- mFrameRateOverrideMappings.dump(result);
+ for (Feature feature : ftl::enum_range<Feature>()) {
+ if (const auto flagOpt = ftl::flag_name(feature)) {
+ dumper.dump(flagOpt->substr(1), mFeatures.test(feature));
+ }
+ }
+ }
+ {
+ 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();
{
- std::lock_guard lock(mHWVsyncLock);
- StringAppendF(&result,
- "mScreenAcquired=%d mPrimaryHWVsyncEnabled=%d mHWVsyncAvailable=%d\n",
- mScreenAcquired.load(), mPrimaryHWVsyncEnabled, mHWVsyncAvailable);
+ utils::Dumper::Section section(dumper, "Frame Targeting"sv);
+
+ std::scoped_lock lock(mDisplayLock);
+ ftl::FakeGuard guard(kMainThreadContext);
+
+ for (const auto& [id, display] : mDisplays) {
+ utils::Dumper::Section
+ section(dumper,
+ id == mPacesetterDisplayId
+ ? ftl::Concat("Pacesetter Display ", id.value).c_str()
+ : ftl::Concat("Follower Display ", id.value).c_str());
+
+ display.targeterPtr->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();
+ std::scoped_lock lock(mPolicyLock);
+ return updateFrameRateOverridesLocked(consideredSignals, displayRefreshRate);
+}
+
+bool Scheduler::updateFrameRateOverridesLocked(GlobalSignals consideredSignals,
+ Fps displayRefreshRate) {
+ if (consideredSignals.idle) return false;
+
+ const auto frameRateOverrides =
+ pacesetterSelectorPtr()->getFrameRateOverrides(mPolicy.contentRequirements,
+ displayRefreshRate, consideredSignals);
+
+ // Note that RefreshRateSelector::supportsFrameRateOverrideByContent is checked when querying
+ // the FrameRateOverrideMappings rather than here.
+ return mFrameRateOverrideMappings.updateFrameRateOverridesByContent(frameRateOverrides);
+}
+
+void Scheduler::promotePacesetterDisplay(std::optional<PhysicalDisplayId> pacesetterIdOpt) {
+ std::shared_ptr<VsyncSchedule> pacesetterVsyncSchedule;
- // 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);
+ {
+ std::scoped_lock lock(mDisplayLock);
+ pacesetterVsyncSchedule = promotePacesetterDisplayLocked(pacesetterIdOpt);
}
- return false;
+
+ applyNewVsyncSchedule(std::move(pacesetterVsyncSchedule));
+}
+
+std::shared_ptr<VsyncSchedule> Scheduler::promotePacesetterDisplayLocked(
+ std::optional<PhysicalDisplayId> 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<VsyncSchedule> 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;
+
+ constexpr bool kForce = true;
+ newVsyncSchedulePtr->onDisplayModeChanged(pacesetter.selectorPtr->getActiveMode().modePtr,
+ kForce);
+ }
+ return newVsyncSchedulePtr;
+}
+
+void Scheduler::applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule> vsyncSchedule) {
+ onNewVsyncSchedule(vsyncSchedule->getDispatch());
+ std::vector<android::EventThread*> threads;
+ {
+ std::lock_guard<std::mutex> 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 = {};
+}
+
+void Scheduler::updateAttachedChoreographersFrameRate(
+ const surfaceflinger::frontend::RequestedLayerState& layer, Fps fps) {
+ std::scoped_lock lock(mChoreographerLock);
+
+ const auto layerId = static_cast<int32_t>(layer.id);
+ const auto choreographers = mAttachedChoreographers.find(layerId);
+ if (choreographers == mAttachedChoreographers.end()) {
+ return;
+ }
+
+ auto& layerChoreographers = choreographers->second;
+
+ layerChoreographers.frameRate = fps;
+ ATRACE_FORMAT_INSTANT("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
+ ALOGV("%s: %s for %s", __func__, to_string(fps).c_str(), layer.name.c_str());
+
+ auto it = layerChoreographers.connections.begin();
+ while (it != layerChoreographers.connections.end()) {
+ sp<EventThreadConnection> choreographerConnection = it->promote();
+ if (choreographerConnection) {
+ choreographerConnection->frameRate = fps;
+ it++;
+ } else {
+ it = choreographers->second.connections.erase(it);
+ }
+ }
+
+ if (layerChoreographers.connections.empty()) {
+ mAttachedChoreographers.erase(choreographers);
+ }
+}
+
+int Scheduler::updateAttachedChoreographersInternal(
+ const surfaceflinger::frontend::LayerHierarchy& layerHierarchy, Fps displayRefreshRate,
+ int parentDivisor) {
+ const char* name = layerHierarchy.getLayer() ? layerHierarchy.getLayer()->name.c_str() : "Root";
+
+ int divisor = 0;
+ if (layerHierarchy.getLayer()) {
+ const auto frameRateCompatibility = layerHierarchy.getLayer()->frameRateCompatibility;
+ const auto frameRate = Fps::fromValue(layerHierarchy.getLayer()->frameRate);
+ ALOGV("%s: %s frameRate %s parentDivisor=%d", __func__, name, to_string(frameRate).c_str(),
+ parentDivisor);
+
+ if (frameRate.isValid()) {
+ if (frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE ||
+ frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_EXACT) {
+ // Since this layer wants an exact match, we would only set a frame rate if the
+ // desired rate is a divisor of the display refresh rate.
+ divisor = RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate);
+ } else if (frameRateCompatibility == ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT) {
+ // find the closest frame rate divisor for the desired frame rate.
+ divisor = static_cast<int>(
+ std::round(displayRefreshRate.getValue() / frameRate.getValue()));
+ }
+ }
+ }
+
+ // We start by traversing the children, updating their choreographers, and getting back the
+ // aggregated frame rate.
+ int childrenDivisor = 0;
+ for (const auto& [child, _] : layerHierarchy.mChildren) {
+ LOG_ALWAYS_FATAL_IF(child == nullptr || child->getLayer() == nullptr);
+
+ ALOGV("%s: %s traversing child %s", __func__, name, child->getLayer()->name.c_str());
+
+ const int childDivisor =
+ updateAttachedChoreographersInternal(*child, displayRefreshRate, divisor);
+ childrenDivisor = childrenDivisor > 0 ? childrenDivisor : childDivisor;
+ if (childDivisor > 0) {
+ childrenDivisor = std::gcd(childrenDivisor, childDivisor);
+ }
+ ALOGV("%s: %s childrenDivisor=%d", __func__, name, childrenDivisor);
+ }
+
+ ALOGV("%s: %s divisor=%d", __func__, name, divisor);
+
+ // If there is no explicit vote for this layer. Use the children's vote if exists
+ divisor = (divisor == 0) ? childrenDivisor : divisor;
+ ALOGV("%s: %s divisor=%d with children", __func__, name, divisor);
+
+ // If there is no explicit vote for this layer or its children, Use the parent vote if exists
+ divisor = (divisor == 0) ? parentDivisor : divisor;
+ ALOGV("%s: %s divisor=%d with parent", __func__, name, divisor);
+
+ if (layerHierarchy.getLayer()) {
+ Fps fps = divisor > 1 ? displayRefreshRate / (unsigned int)divisor : Fps();
+ updateAttachedChoreographersFrameRate(*layerHierarchy.getLayer(), fps);
+ }
+
+ return divisor;
+}
+
+void Scheduler::updateAttachedChoreographers(
+ const surfaceflinger::frontend::LayerHierarchy& layerHierarchy, Fps displayRefreshRate) {
+ ATRACE_CALL();
+ updateAttachedChoreographersInternal(layerHierarchy, displayRefreshRate, 0);
}
template <typename S, typename T>
auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals {
- DisplayModePtr newMode;
+ ATRACE_CALL();
+ std::vector<display::DisplayModeRequest> modeRequests;
GlobalSignals consideredSignals;
bool refreshRateChanged = false;
bool frameRateOverridesChanged;
- const auto refreshRateConfigs = holdRefreshRateConfigs();
{
- std::lock_guard<std::mutex> lock(mPolicyLock);
+ std::scoped_lock lock(mPolicyLock);
auto& currentState = mPolicy.*statePtr;
if (currentState == newState) return {};
currentState = std::forward<T>(newState);
- std::tie(newMode, consideredSignals) = chooseDisplayMode();
- frameRateOverridesChanged = updateFrameRateOverrides(consideredSignals, newMode->getFps());
+ DisplayModeChoiceMap modeChoices;
+ ftl::Optional<FrameRateMode> 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 = updateFrameRateOverridesLocked(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,33 +1139,66 @@ auto Scheduler::applyPolicy(S Policy::*statePtr, T&& newState) -> GlobalSignals
return consideredSignals;
}
-auto Scheduler::chooseDisplayMode() -> std::pair<DisplayModePtr, GlobalSignals> {
+auto Scheduler::chooseDisplayModes() const -> DisplayModeChoiceMap {
ATRACE_CALL();
- const auto configs = holdRefreshRateConfigs();
+ using RankedRefreshRates = RefreshRateSelector::RankedFrameRates;
+ ui::PhysicalDisplayVector<RankedRefreshRates> 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));
+ }
- // 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};
+ DisplayModeChoiceMap modeChoices;
+ using fps_approx_ops::operator==;
+
+ for (auto& [rankings, signals] : perDisplayRanking) {
+ const auto chosenFrameRateMode =
+ ftl::find_if(rankings,
+ [&](const auto& ranking) {
+ return ranking.frameRateMode.fps == pacesetterFps;
+ })
+ .transform([](const auto& scoredFrameRate) {
+ return scoredFrameRate.get().frameRateMode;
+ })
+ .value_or(rankings.front().frameRateMode);
+
+ modeChoices.try_emplace(chosenFrameRateMode.modePtr->getPhysicalDisplayId(),
+ DisplayModeChoice{chosenFrameRateMode, signals});
}
+ 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<std::mutex> lock(mPolicyLock);
+ const auto frameRateMode =
+ pacesetterSelectorPtr()
+ ->getRankedFrameRates(mPolicy.contentRequirements, makeGlobalSignals())
+ .ranking.front()
+ .frameRateMode;
+
// Make sure the stored mode is up to date.
- if (mPolicy.mode) {
- mPolicy.mode = chooseDisplayMode().first;
- }
- return mPolicy.mode;
+ mPolicy.modeOpt = frameRateMode;
+
+ return frameRateMode;
}
void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimeline& timeline) {
@@ -743,7 +1211,7 @@ void Scheduler::onNewVsyncPeriodChangeTimeline(const hal::VsyncPeriodChangeTimel
}
}
-bool Scheduler::onPostComposition(nsecs_t presentTime) {
+bool Scheduler::onCompositionPresented(nsecs_t presentTime) {
std::lock_guard<std::mutex> lock(mVsyncTimelineLock);
if (mLastVsyncPeriodChangeTimeline && mLastVsyncPeriodChangeTimeline->refreshRequired) {
if (presentTime < mLastVsyncPeriodChangeTimeline->refreshTimeNanos) {
@@ -760,12 +1228,27 @@ void Scheduler::onActiveDisplayAreaChanged(uint32_t displayArea) {
mLayerHistory.setDisplayArea(displayArea);
}
-void Scheduler::setGameModeRefreshRateForUid(FrameRateOverride frameRateOverride) {
+void Scheduler::setGameModeFrameRateForUid(FrameRateOverride frameRateOverride) {
if (frameRateOverride.frameRateHz > 0.f && frameRateOverride.frameRateHz < 1.f) {
return;
}
- mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
+ if (FlagManager::getInstance().game_default_frame_rate()) {
+ // update the frame rate override mapping in LayerHistory
+ mLayerHistory.updateGameModeFrameRateOverride(frameRateOverride);
+ } else {
+ mFrameRateOverrideMappings.setGameModeRefreshRateForUid(frameRateOverride);
+ }
+}
+
+void Scheduler::setGameDefaultFrameRateForUid(FrameRateOverride frameRateOverride) {
+ if (!FlagManager::getInstance().game_default_frame_rate() ||
+ (frameRateOverride.frameRateHz > 0.f && frameRateOverride.frameRateHz < 1.f)) {
+ return;
+ }
+
+ // update the frame rate override mapping in LayerHistory
+ mLayerHistory.updateGameDefaultFrameRateOverride(frameRateOverride);
}
void Scheduler::setPreferredRefreshRateForUid(FrameRateOverride frameRateOverride) {
@@ -776,11 +1259,21 @@ 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);
+void Scheduler::updateSmallAreaDetection(
+ std::vector<std::pair<int32_t, float>>& uidThresholdMappings) {
+ mSmallAreaDetectionAllowMappings.update(uidThresholdMappings);
+}
+
+void Scheduler::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+ mSmallAreaDetectionAllowMappings.setThresholdForAppId(appId, threshold);
+}
+
+bool Scheduler::isSmallDirtyArea(int32_t appId, uint32_t dirtyArea) {
+ std::optional<float> oThreshold = mSmallAreaDetectionAllowMappings.getThresholdForAppId(appId);
+ if (oThreshold) {
+ return mLayerHistory.isSmallDirtyArea(dirtyArea, oThreshold.value());
+ }
+ return false;
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/Scheduler.h b/services/surfaceflinger/Scheduler/Scheduler.h
index a8043bf94c..ce585c624a 100644
--- a/services/surfaceflinger/Scheduler/Scheduler.h
+++ b/services/surfaceflinger/Scheduler/Scheduler.h
@@ -22,8 +22,8 @@
#include <future>
#include <memory>
#include <mutex>
-#include <optional>
#include <unordered_map>
+#include <utility>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
@@ -32,15 +32,29 @@
#include <ui/GraphicTypes.h>
#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
+#include <ftl/fake_guard.h>
+#include <ftl/non_null.h>
+#include <ftl/optional.h>
#include <scheduler/Features.h>
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/Time.h>
+#include <scheduler/VsyncConfig.h>
+#include <ui/DisplayId.h>
+#include <ui/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 "SmallAreaDetectionAllowMappings.h"
+#include "Utils/Dumper.h"
+#include "VsyncModulator.h"
+
+#include <FrontEnd/LayerHierarchy.h>
namespace android::scheduler {
@@ -74,7 +88,6 @@ struct hash<android::scheduler::ConnectionHandle> {
namespace android {
class FenceTime;
-class InjectVSyncSource;
namespace frametimeline {
class TokenManager;
@@ -82,39 +95,41 @@ class TokenManager;
namespace scheduler {
-struct ISchedulerCallback {
- using DisplayModeEvent = scheduler::DisplayModeEvent;
-
- virtual void setVsyncEnabled(bool) = 0;
- virtual void requestDisplayMode(DisplayModePtr, DisplayModeEvent) = 0;
- virtual void kernelTimerChanged(bool expired) = 0;
- virtual void triggerOnFrameRateOverridesChanged() = 0;
+using GlobalSignals = RefreshRateSelector::GlobalSignals;
-protected:
- ~ISchedulerCallback() = default;
-};
+class VsyncSchedule;
-class Scheduler : impl::MessageQueue {
- using Impl = impl::MessageQueue;
+class Scheduler : public IEventThreadCallback, android::impl::MessageQueue {
+ using Impl = android::impl::MessageQueue;
public:
- Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags);
+ Scheduler(ICompositor&, ISchedulerCallback&, FeatureFlags, sp<VsyncModulator>,
+ IVsyncTrackerCallback&);
virtual ~Scheduler();
void startTimers();
- void setRefreshRateConfigs(std::shared_ptr<RefreshRateConfigs>)
- EXCLUDES(mRefreshRateConfigsLock);
- void run();
+ // TODO(b/241285191): Remove this API by promoting pacesetter in onScreen{Acquired,Released}.
+ void setPacesetterDisplay(std::optional<PhysicalDisplayId>) REQUIRES(kMainThreadContext)
+ EXCLUDES(mDisplayLock);
+
+ using RefreshRateSelectorPtr = std::shared_ptr<RefreshRateSelector>;
- void createVsyncSchedule(FeatureFlags);
+ using ConstVsyncSchedulePtr = std::shared_ptr<const VsyncSchedule>;
+ using VsyncSchedulePtr = std::shared_ptr<VsyncSchedule>;
+
+ 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 +140,35 @@ public:
return std::move(future);
}
- ConnectionHandle createConnection(const char* connectionName, frametimeline::TokenManager*,
- std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration,
- impl::EventThread::InterceptVSyncsCallback);
+ template <typename F, typename T = std::invoke_result_t<F>>
+ [[nodiscard]] std::future<T> 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<IDisplayEventConnection> createDisplayEventConnection(
- ConnectionHandle, ISurfaceComposer::EventRegistrationFlags eventRegistration = {});
+ ConnectionHandle, EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr) EXCLUDES(mChoreographerLock);
sp<EventThreadConnection> 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 onHotplugConnectionError(ConnectionHandle, int32_t errorCode);
+
+ 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,96 +177,173 @@ 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 <typename... Args,
+ typename Handler = std::optional<VsyncConfig> (VsyncModulator::*)(Args...)>
+ void modulateVsync(std::optional<PhysicalDisplayId> 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);
+
+ // Sets the render rate for the scheduler to run at.
+ void setRenderRate(PhysicalDisplayId, Fps);
- // 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);
+ 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 modePtr is nullopt, use the active display mode.
+ void resyncToHardwareVsync(PhysicalDisplayId id, bool allowToEnable,
+ DisplayModePtr modePtr = nullptr) EXCLUDES(mDisplayLock) {
+ std::scoped_lock lock(mDisplayLock);
+ ftl::FakeGuard guard(kMainThreadContext);
+ resyncToHardwareVsyncLocked(id, allowToEnable, modePtr);
+ }
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<nsecs_t> hwcVsyncPeriod,
- bool* periodFlushed);
- void addPresentFence(std::shared_ptr<FenceTime>);
+ // 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<nsecs_t> hwcVsyncPeriod);
+ void addPresentFence(PhysicalDisplayId, std::shared_ptr<FenceTime>)
+ 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,
+ nsecs_t now, LayerHistory::LayerUpdateType) EXCLUDES(mDisplayLock);
void setModeChangePending(bool pending);
+ void setDefaultFrameRateCompatibility(int32_t id, scheduler::FrameRateCompatibility);
+ void setLayerProperties(int32_t id, const LayerProps&);
void deregisterLayer(Layer*);
+ void onLayerDestroyed(Layer*) EXCLUDES(mChoreographerLock);
// Detects content using layer history, and selects a matching refresh rate.
- void chooseRefreshRateForContent() EXCLUDES(mRefreshRateConfigsLock);
+ void chooseRefreshRateForContent(const surfaceflinger::frontend::LayerHierarchy*,
+ bool updateAttachedChoreographer) 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);
+
+ ConstVsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> = std::nullopt) const
+ EXCLUDES(mDisplayLock);
- VSyncDispatch& getVsyncDispatch() { return mVsyncSchedule->getDispatch(); }
+ VsyncSchedulePtr getVsyncSchedule(std::optional<PhysicalDisplayId> idOpt = std::nullopt)
+ EXCLUDES(mDisplayLock) {
+ return std::const_pointer_cast<VsyncSchedule>(std::as_const(*this).getVsyncSchedule(idOpt));
+ }
+
+ TimePoint expectedPresentTimeForPacesetter() const EXCLUDES(mDisplayLock) {
+ std::scoped_lock lock(mDisplayLock);
+ return pacesetterDisplayLocked()
+ .transform([](const Display& display) {
+ return display.targeterPtr->target().expectedPresentTime();
+ })
+ .value_or(TimePoint());
+ }
// 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 expectedVsyncTime, 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);
- // Notifies the scheduler post composition. Returns if recomposite is needed.
- bool onPostComposition(nsecs_t presentTime);
+ // Notifies the scheduler once the composition is presented. Returns if recomposite is needed.
+ bool onCompositionPresented(nsecs_t presentTime);
// Notifies the scheduler when the display size has changed. Called from SF's main thread
void onActiveDisplayAreaChanged(uint32_t displayArea);
- size_t getEventThreadConnectionCount(ConnectionHandle handle);
-
- std::unique_ptr<VSyncSource> 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);
- void setGameModeRefreshRateForUid(FrameRateOverride);
+ // Stores the frame rate override that a game should run at set by game interventions.
+ // FrameRateOverride.refreshRateHz == 0 means no preference.
+ void setGameModeFrameRateForUid(FrameRateOverride) EXCLUDES(mDisplayLock);
+
+ // Stores the frame rate override that a game should run rat set by default game frame rate.
+ // FrameRateOverride.refreshRateHz == 0 means no preference, game default game frame rate is not
+ // enabled.
+ //
+ // "ro.surface_flinger.game_default_frame_rate_override" sets the frame rate value,
+ // "persist.graphics.game_default_frame_rate.enabled" controls whether this feature is enabled.
+ void setGameDefaultFrameRateForUid(FrameRateOverride) EXCLUDES(mDisplayLock);
+
+ void updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
+
+ void setSmallAreaDetectionThreshold(int32_t appId, float threshold);
+
+ // Returns true if the dirty area is less than threshold.
+ bool isSmallDirtyArea(int32_t appId, uint32_t dirtyArea);
// Retrieves the overridden refresh rate for a given uid.
- std::optional<Fps> getFrameRateOverride(uid_t uid) const EXCLUDES(mRefreshRateConfigsLock);
+ std::optional<Fps> 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;
+ }
+
+ Fps getNextFrameInterval(PhysicalDisplayId, TimePoint currentExpectedPresentTime) const
+ EXCLUDES(mDisplayLock);
+
// Returns the framerate of the layer with the given sequence ID
float getLayerFramerate(nsecs_t now, int32_t id) const {
return mLayerHistory.getLayerFramerate(now, id);
}
+ bool updateFrameRateOverrides(GlobalSignals, Fps displayRefreshRate) EXCLUDES(mPolicyLock);
+
+ // Returns true if the small dirty detection is enabled for the appId.
+ bool supportSmallDirtyDetection(int32_t appId) {
+ return mFeatures.test(Feature::kSmallDirtyContentDetection) &&
+ mSmallAreaDetectionAllowMappings.getThresholdForAppId(appId).has_value();
+ }
+
+ // Injects a delay that is a fraction of the predicted frame duration for the next frame.
+ void injectPacesetterDelay(float frameDurationFraction) REQUIRES(kMainThreadContext) {
+ mPacesetterFrameDurationFractionToSkip = frameDurationFraction;
+ }
+
private:
friend class TestableScheduler;
@@ -245,20 +351,50 @@ private:
enum class TimerState { Reset, Expired };
enum class TouchState { Inactive, Active };
+ // impl::MessageQueue overrides:
+ void onFrameSignal(ICompositor&, VsyncId, TimePoint expectedVsyncTime) override
+ REQUIRES(kMainThreadContext, mDisplayLock);
+
// Create a connection on the given EventThread.
ConnectionHandle createConnection(std::unique_ptr<EventThread>);
- sp<EventThreadConnection> createConnectionInternal(
- EventThread*, ISurfaceComposer::EventRegistrationFlags eventRegistration = {});
// 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;
+ // VsyncSchedule delegate.
+ void onHardwareVsyncRequest(PhysicalDisplayId, bool enable);
+
+ void resyncToHardwareVsyncLocked(PhysicalDisplayId, bool allowToEnable,
+ DisplayModePtr modePtr = nullptr)
+ 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<PhysicalDisplayId> 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<VsyncSchedule> promotePacesetterDisplayLocked(
+ std::optional<PhysicalDisplayId> pacesetterIdOpt = std::nullopt)
+ REQUIRES(kMainThreadContext, mDisplayLock);
+ void applyNewVsyncSchedule(std::shared_ptr<VsyncSchedule>) 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 +403,47 @@ private:
template <typename S, typename T>
GlobalSignals applyPolicy(S Policy::*, T&&) EXCLUDES(mPolicyLock);
- // Returns the display mode that fulfills the policy, and the signals that were considered.
- std::pair<DisplayModePtr, GlobalSignals> 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<RefreshRateConfigs> holdRefreshRateConfigs() const
- EXCLUDES(mRefreshRateConfigsLock) {
- std::scoped_lock lock(mRefreshRateConfigsLock);
- return mRefreshRateConfigs;
- }
+ using DisplayModeChoiceMap = ui::PhysicalDisplayMap<PhysicalDisplayId, DisplayModeChoice>;
+
+ // See mDisplayLock for thread safety.
+ DisplayModeChoiceMap chooseDisplayModes() const
+ REQUIRES(mPolicyLock, mDisplayLock, kMainThreadContext);
+
+ GlobalSignals makeGlobalSignals() const REQUIRES(mPolicyLock);
+
+ bool updateFrameRateOverridesLocked(GlobalSignals, Fps displayRefreshRate)
+ REQUIRES(mPolicyLock);
+ void updateAttachedChoreographers(const surfaceflinger::frontend::LayerHierarchy&,
+ Fps displayRefreshRate);
+ int updateAttachedChoreographersInternal(const surfaceflinger::frontend::LayerHierarchy&,
+ Fps displayRefreshRate, int parentDivisor);
+ void updateAttachedChoreographersFrameRate(const surfaceflinger::frontend::RequestedLayerState&,
+ Fps fps) EXCLUDES(mChoreographerLock);
+
+ void dispatchCachedReportedMode() REQUIRES(mPolicyLock) EXCLUDES(mDisplayLock);
+
+ // IEventThreadCallback overrides
+ bool throttleVsync(TimePoint, uid_t) override;
+ Period getVsyncPeriod(uid_t) override EXCLUDES(mDisplayLock);
+ void resync() override EXCLUDES(mDisplayLock);
// Stores EventThread associated with a given VSyncSource, and an initial EventThreadConnection.
struct Connection {
@@ -294,31 +455,108 @@ private:
mutable std::mutex mConnectionsLock;
std::unordered_map<ConnectionHandle, Connection> 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<nsecs_t> mLastResyncTime = 0;
const FeatureFlags mFeatures;
- std::optional<VsyncSchedule> mVsyncSchedule;
+
+ // Shifts the VSYNC phase during certain transactions and refresh rate changes.
+ const sp<VsyncModulator> mVsyncModulator;
// Used to choose refresh rate if content detection is enabled.
LayerHistory mLayerHistory;
// Timer used to monitor touch events.
- std::optional<OneShotTimer> mTouchTimer;
+ ftl::Optional<OneShotTimer> mTouchTimer;
// Timer used to monitor display power mode.
- std::optional<OneShotTimer> mDisplayPowerTimer;
+ ftl::Optional<OneShotTimer> mDisplayPowerTimer;
+
+ // Injected delay prior to compositing, for simulating jank.
+ float mPacesetterFrameDurationFractionToSkip GUARDED_BY(kMainThreadContext) = 0.f;
ISchedulerCallback& mSchedulerCallback;
+ IVsyncTrackerCallback& mVsyncTrackerCallback;
+
+ // 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;
+
+ using FrameTargeterPtr = std::unique_ptr<FrameTargeter>;
+
+ struct Display {
+ Display(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+ VsyncSchedulePtr schedulePtr, FeatureFlags features)
+ : displayId(displayId),
+ selectorPtr(std::move(selectorPtr)),
+ schedulePtr(std::move(schedulePtr)),
+ targeterPtr(std::make_unique<
+ FrameTargeter>(displayId,
+ features.test(Feature::kBackpressureGpuComposition))) {}
+
+ const PhysicalDisplayId displayId;
+
+ // Effectively const except in move constructor.
+ RefreshRateSelectorPtr selectorPtr;
+ VsyncSchedulePtr schedulePtr;
+ FrameTargeterPtr targeterPtr;
+
+ hal::PowerMode powerMode = hal::PowerMode::OFF;
+ };
+
+ using DisplayRef = std::reference_wrapper<Display>;
+ using ConstDisplayRef = std::reference_wrapper<const Display>;
+
+ ui::PhysicalDisplayMap<PhysicalDisplayId, Display> mDisplays GUARDED_BY(mDisplayLock)
+ GUARDED_BY(kMainThreadContext);
+
+ ftl::Optional<PhysicalDisplayId> mPacesetterDisplayId GUARDED_BY(mDisplayLock)
+ GUARDED_BY(kMainThreadContext);
+
+ ftl::Optional<DisplayRef> pacesetterDisplayLocked() REQUIRES(mDisplayLock) {
+ return static_cast<const Scheduler*>(this)->pacesetterDisplayLocked().transform(
+ [](const Display& display) { return std::ref(const_cast<Display&>(display)); });
+ }
+
+ ftl::Optional<ConstDisplayRef> pacesetterDisplayLocked() const REQUIRES(mDisplayLock) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ return mPacesetterDisplayId.and_then([this](PhysicalDisplayId pacesetterId)
+ REQUIRES(mDisplayLock, kMainThreadContext) {
+ return mDisplays.get(pacesetterId);
+ });
+ }
+
+ // The pacesetter must exist as a precondition.
+ ftl::NonNull<const Display*> pacesetterPtrLocked() const REQUIRES(mDisplayLock) {
+ return ftl::as_non_null(&pacesetterDisplayLocked()->get());
+ }
+
+ RefreshRateSelectorPtr pacesetterSelectorPtr() const EXCLUDES(mDisplayLock) {
+ std::scoped_lock lock(mDisplayLock);
+ return pacesetterSelectorPtrLocked();
+ }
+
+ RefreshRateSelectorPtr pacesetterSelectorPtrLocked() const REQUIRES(mDisplayLock) {
+ return pacesetterDisplayLocked()
+ .transform([](const Display& display) { return display.selectorPtr; })
+ .or_else([] { return std::optional<RefreshRateSelectorPtr>(nullptr); })
+ .value();
+ }
+
+ ConstVsyncSchedulePtr getVsyncScheduleLocked(
+ std::optional<PhysicalDisplayId> = std::nullopt) const REQUIRES(mDisplayLock);
+
+ VsyncSchedulePtr getVsyncScheduleLocked(std::optional<PhysicalDisplayId> idOpt = std::nullopt)
+ REQUIRES(mDisplayLock) {
+ return std::const_pointer_cast<VsyncSchedule>(
+ static_cast<const Scheduler*>(this)->getVsyncScheduleLocked(idOpt));
+ }
+
struct Policy {
// Policy for choosing the display mode.
LayerHistory::Summary contentRequirements;
@@ -328,19 +566,26 @@ private:
hal::PowerMode displayPowerMode = hal::PowerMode::ON;
// Chosen display mode.
- DisplayModePtr mode;
+ ftl::Optional<FrameRateMode> modeOpt;
struct ModeChangedParams {
ConnectionHandle handle;
- DisplayModePtr mode;
+ FrameRateMode mode;
};
// Parameters for latest dispatch of mode change event.
std::optional<ModeChangedParams> cachedModeChangedParams;
} mPolicy GUARDED_BY(mPolicyLock);
- mutable std::mutex mRefreshRateConfigsLock;
- std::shared_ptr<RefreshRateConfigs> mRefreshRateConfigs GUARDED_BY(mRefreshRateConfigsLock);
+ std::mutex mChoreographerLock;
+
+ struct AttachedChoreographers {
+ Fps frameRate;
+ std::unordered_set<wp<EventThreadConnection>, WpHash> connections;
+ };
+ // Map keyed by layer ID (sequence) to choreographer connections.
+ std::unordered_map<int32_t, AttachedChoreographers> mAttachedChoreographers
+ GUARDED_BY(mChoreographerLock);
std::mutex mVsyncTimelineLock;
std::optional<hal::VsyncPeriodChangeTimeline> mLastVsyncPeriodChangeTimeline
@@ -348,9 +593,7 @@ private:
static constexpr std::chrono::nanoseconds MAX_VSYNC_APPLIED_TIME = 200ms;
FrameRateOverrideMappings mFrameRateOverrideMappings;
-
- // Keeps track of whether the screen is acquired for debug
- std::atomic<bool> mScreenAcquired = false;
+ SmallAreaDetectionAllowMappings mSmallAreaDetectionAllowMappings;
};
} // namespace scheduler
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
new file mode 100644
index 0000000000..7510ebf2df
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 <sys/types.h>
+
+#include "SmallAreaDetectionAllowMappings.h"
+
+namespace android::scheduler {
+void SmallAreaDetectionAllowMappings::update(
+ std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
+ std::lock_guard lock(mLock);
+ mMap.clear();
+ for (std::pair<int32_t, float> row : appIdThresholdMappings) {
+ if (!isValidThreshold(row.second)) continue;
+
+ mMap.emplace(row.first, row.second);
+ }
+}
+
+void SmallAreaDetectionAllowMappings::setThresholdForAppId(int32_t appId, float threshold) {
+ if (!isValidThreshold(threshold)) return;
+
+ std::lock_guard lock(mLock);
+ mMap.emplace(appId, threshold);
+}
+
+std::optional<float> SmallAreaDetectionAllowMappings::getThresholdForAppId(int32_t appId) {
+ std::lock_guard lock(mLock);
+ const auto iter = mMap.find(appId);
+ if (iter != mMap.end()) {
+ return iter->second;
+ }
+ return std::nullopt;
+}
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
new file mode 100644
index 0000000000..4ec5e3b5e4
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/SmallAreaDetectionAllowMappings.h
@@ -0,0 +1,39 @@
+/*
+ * 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 <android-base/thread_annotations.h>
+#include <sys/types.h>
+#include <optional>
+#include <unordered_map>
+#include <vector>
+
+namespace android::scheduler {
+class SmallAreaDetectionAllowMappings {
+ using AppIdThresholdMap = std::unordered_map<int32_t, float>;
+
+public:
+ void update(std::vector<std::pair<int32_t, float>>& appIdThresholdMappings);
+ void setThresholdForAppId(int32_t appId, float threshold) EXCLUDES(mLock);
+ std::optional<float> getThresholdForAppId(int32_t uid) EXCLUDES(mLock);
+
+private:
+ static bool isValidThreshold(float threshold) { return threshold >= 0.0f && threshold <= 1.0f; }
+ mutable std::mutex mLock;
+ AppIdThresholdMap mMap GUARDED_BY(mLock);
+};
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatch.h b/services/surfaceflinger/Scheduler/VSyncDispatch.h
index 2bfe204a33..c3a952f689 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>, 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<VSyncDispatch> mDispatch;
- VSyncDispatch::CallbackToken mToken;
- bool mValidToken;
+ friend class VSyncCallbackRegistrationTest;
+
+ std::shared_ptr<VSyncDispatch> mDispatch;
+ std::optional<VSyncDispatch::CallbackToken> mToken;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
index 27f43115c1..ef30887037 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.cpp
@@ -21,12 +21,17 @@
#include <android-base/stringprintf.h>
#include <ftl/concat.h>
#include <utils/Trace.h>
+#include <log/log_main.h>
#include <scheduler/TimeKeeper.h>
+#include <common/FlagManager.h>
#include "VSyncDispatchTimerQueue.h"
#include "VSyncTracker.h"
+#undef LOG_TAG
+#define LOG_TAG "VSyncDispatch"
+
namespace android::scheduler {
using base::StringAppendF;
@@ -96,23 +101,25 @@ ScheduleResult VSyncDispatchTimerQueueEntry::schedule(VSyncDispatch::ScheduleTim
mArmedInfo && (nextVsyncTime > (mArmedInfo->mActualVsyncTime + mMinVsyncDistance));
bool const wouldSkipAWakeup =
mArmedInfo && ((nextWakeupTime > (mArmedInfo->mActualWakeupTime + mMinVsyncDistance)));
- if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
- return getExpectedCallbackTime(nextVsyncTime, timing);
- }
-
- bool const alreadyDispatchedForVsync = mLastDispatchTime &&
- ((*mLastDispatchTime + mMinVsyncDistance) >= nextVsyncTime &&
- (*mLastDispatchTime - mMinVsyncDistance) <= nextVsyncTime);
- if (alreadyDispatchedForVsync) {
- nextVsyncTime =
- tracker.nextAnticipatedVSyncTimeFrom(*mLastDispatchTime + mMinVsyncDistance);
+ if (FlagManager::getInstance().dont_skip_on_early()) {
+ if (wouldSkipAVsyncTarget || wouldSkipAWakeup) {
+ nextVsyncTime = mArmedInfo->mActualVsyncTime;
+ } else {
+ nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime);
+ }
+ nextWakeupTime = std::max(now, nextVsyncTime - timing.workDuration - timing.readyDuration);
+ } else {
+ if (wouldSkipAVsyncTarget && wouldSkipAWakeup) {
+ return getExpectedCallbackTime(nextVsyncTime, timing);
+ }
+ nextVsyncTime = adjustVsyncIfNeeded(tracker, nextVsyncTime);
nextWakeupTime = nextVsyncTime - timing.workDuration - timing.readyDuration;
}
auto const nextReadyTime = nextVsyncTime - timing.readyDuration;
mScheduleTiming = timing;
mArmedInfo = {nextWakeupTime, nextVsyncTime, nextReadyTime};
- return getExpectedCallbackTime(nextVsyncTime, timing);
+ return nextWakeupTime;
}
void VSyncDispatchTimerQueueEntry::addPendingWorkloadUpdate(VSyncDispatch::ScheduleTiming timing) {
@@ -123,6 +130,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 +162,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 +228,20 @@ void VSyncDispatchTimerQueueEntry::dump(std::string& result) const {
}
VSyncDispatchTimerQueue::VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper> 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 +272,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 +364,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 +451,48 @@ void VSyncDispatchTimerQueue::dump(std::string& result) const {
}
}
-VSyncCallbackRegistration::VSyncCallbackRegistration(VSyncDispatch& dispatch,
+VSyncCallbackRegistration::VSyncCallbackRegistration(std::shared_ptr<VSyncDispatch> 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 4923031098..e0fb8f9f86 100644
--- a/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
+++ b/services/surfaceflinger/Scheduler/VSyncDispatchTimerQueue.h
@@ -26,11 +26,11 @@
#include <android-base/thread_annotations.h>
#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<TimeKeeper>, VSyncTracker&, nsecs_t timerSlack,
- nsecs_t minVsyncDistance);
+ VSyncDispatchTimerQueue(std::unique_ptr<TimeKeeper>, 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,14 +144,16 @@ private:
void rearmTimerSkippingUpdateFor(nsecs_t now, CallbackMap::iterator const& skipUpdate)
REQUIRES(mMutex);
void cancelTimer() REQUIRES(mMutex);
+ ScheduleResult scheduleLocked(CallbackToken, ScheduleTiming) REQUIRES(mMutex);
+
+ std::mutex mutable mMutex;
static constexpr nsecs_t kInvalidTime = std::numeric_limits<int64_t>::max();
std::unique_ptr<TimeKeeper> const mTimeKeeper;
- VSyncTracker& mTracker;
+ VsyncSchedule::TrackerPtr mTracker;
nsecs_t const mTimerSlack;
nsecs_t const mMinVsyncDistance;
- std::mutex mutable mMutex;
size_t mCallbackToken GUARDED_BY(mMutex) = 0;
CallbackMap mCallbacks GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
index 77782e9c72..0d79a390a7 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.cpp
@@ -29,12 +29,14 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
+#include <common/FlagManager.h>
#include <cutils/compiler.h>
#include <cutils/properties.h>
+#include <ftl/concat.h>
+#include <gui/TraceUtils.h>
#include <utils/Log.h>
-#include <utils/Trace.h>
-#include "RefreshRateConfigs.h"
+#include "RefreshRateSelector.h"
#include "VSyncPredictor.h"
namespace android::scheduler {
@@ -45,35 +47,48 @@ static auto constexpr kMaxPercent = 100u;
VSyncPredictor::~VSyncPredictor() = default;
-VSyncPredictor::VSyncPredictor(nsecs_t idealPeriod, size_t historySize,
- size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent)
- : mTraceOn(property_get_bool("debug.sf.vsp_trace", true)),
+VSyncPredictor::VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+ size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
+ IVsyncTrackerCallback& callback)
+ : mId(modePtr->getPhysicalDisplayId()),
+ mTraceOn(property_get_bool("debug.sf.vsp_trace", false)),
kHistorySize(historySize),
kMinimumSamplesForPrediction(minimumSamplesForPrediction),
kOutlierTolerancePercent(std::min(outlierTolerancePercent, kMaxPercent)),
- mIdealPeriod(idealPeriod) {
+ mVsyncTrackerCallback(callback),
+ mDisplayModePtr(modePtr) {
resetModel();
}
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();
}
+nsecs_t VSyncPredictor::idealPeriod() const {
+ return mDisplayModePtr->getVsyncRate().getPeriodNsecs();
+}
+
bool VSyncPredictor::validate(nsecs_t timestamp) const {
if (mLastTimestampIndex < 0 || mTimestamps.empty()) {
return true;
}
- auto const aValidTimestamp = mTimestamps[mLastTimestampIndex];
- auto const percent = (timestamp - aValidTimestamp) % mIdealPeriod * kMaxPercent / mIdealPeriod;
+ const auto aValidTimestamp = mTimestamps[mLastTimestampIndex];
+ const auto percent =
+ (timestamp - aValidTimestamp) % idealPeriod() * kMaxPercent / idealPeriod();
if (percent >= kOutlierTolerancePercent &&
percent <= (kMaxPercent - kOutlierTolerancePercent)) {
+ ATRACE_FORMAT_INSTANT("timestamp is not aligned with model");
return false;
}
@@ -81,9 +96,10 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const {
[timestamp](nsecs_t a, nsecs_t b) {
return std::abs(timestamp - a) < std::abs(timestamp - b);
});
- const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / mIdealPeriod;
+ const auto distancePercent = std::abs(*iter - timestamp) * kMaxPercent / idealPeriod();
if (distancePercent < kOutlierTolerancePercent) {
// duplicate timestamp
+ ATRACE_FORMAT_INSTANT("duplicate timestamp");
return false;
}
return true;
@@ -91,10 +107,29 @@ bool VSyncPredictor::validate(nsecs_t timestamp) const {
nsecs_t VSyncPredictor::currentPeriod() const {
std::lock_guard lock(mMutex);
- return mRateMap.find(mIdealPeriod)->second.slope;
+ return mRateMap.find(idealPeriod())->second.slope;
+}
+
+Period VSyncPredictor::minFramePeriod() const {
+ if (!FlagManager::getInstance().vrr_config()) {
+ return Period::fromNs(currentPeriod());
+ }
+
+ std::lock_guard lock(mMutex);
+ return minFramePeriodLocked();
+}
+
+Period VSyncPredictor::minFramePeriodLocked() const {
+ const auto idealPeakRefreshPeriod = mDisplayModePtr->getPeakFps().getPeriodNsecs();
+ const auto numPeriods = static_cast<int>(std::round(static_cast<float>(idealPeakRefreshPeriod) /
+ static_cast<float>(idealPeriod())));
+ const auto slope = mRateMap.find(idealPeriod())->second.slope;
+ return Period::fromNs(slope * numPeriods);
}
bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
+ ATRACE_CALL();
+
std::lock_guard lock(mMutex);
if (!validate(timestamp)) {
@@ -113,6 +148,8 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
} else {
mKnownTimestamp = timestamp;
}
+ ATRACE_FORMAT_INSTANT("timestamp rejected. mKnownTimestamp was %.2fms ago",
+ (systemTime() - *mKnownTimestamp) / 1e6f);
return false;
}
@@ -124,9 +161,11 @@ 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};
+ mRateMap[idealPeriod()] = {idealPeriod(), 0};
return true;
}
@@ -150,7 +189,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
// Normalizing to the oldest timestamp cuts down on error in calculating the intercept.
const auto oldestTS = *std::min_element(mTimestamps.begin(), mTimestamps.end());
- auto it = mRateMap.find(mIdealPeriod);
+ auto it = mRateMap.find(idealPeriod());
auto const currentPeriod = it->second.slope;
// The mean of the ordinals must be precise for the intercept calculation, so scale them up for
@@ -161,13 +200,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;
}
@@ -188,7 +227,7 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
}
if (CC_UNLIKELY(bottom == 0)) {
- it->second = {mIdealPeriod, 0};
+ it->second = {idealPeriod(), 0};
clearTimestamps();
return false;
}
@@ -196,9 +235,9 @@ bool VSyncPredictor::addVsyncTimestamp(nsecs_t timestamp) {
nsecs_t const anticipatedPeriod = top * kScalingFactor / bottom;
nsecs_t const intercept = meanTS - (anticipatedPeriod * meanOrdinal / kScalingFactor);
- auto const percent = std::abs(anticipatedPeriod - mIdealPeriod) * kMaxPercent / mIdealPeriod;
+ auto const percent = std::abs(anticipatedPeriod - idealPeriod()) * kMaxPercent / idealPeriod();
if (percent >= kOutlierTolerancePercent) {
- it->second = {mIdealPeriod, 0};
+ it->second = {idealPeriod(), 0};
clearTimestamps();
return false;
}
@@ -208,19 +247,30 @@ 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<int64_t>(std::round((vsync - lastVsyncTime) / static_cast<float>(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;
+ auto const numPeriodsOut = ((timePoint - knownTimestamp) / idealPeriod()) + 1;
+ return knownTimestamp + numPeriodsOut * idealPeriod();
}
auto const oldest = *std::min_element(mTimestamps.begin(), mTimestamps.end());
@@ -230,7 +280,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 +301,54 @@ 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 (!mRenderRateOpt) return 0;
+ const auto divisor =
+ RefreshRateSelector::getFrameRateDivisor(Fps::fromPeriodNsecs(idealPeriod()),
+ *mRenderRateOpt);
+ if (divisor <= 1) return 0;
+
+ int mod = mLastVsyncSequence->seq % divisor;
+ if (mod == 0) return 0;
+
+ // This is actually a bug fix, but guarded with vrr_config since we found it with this
+ // config
+ if (FlagManager::getInstance().vrr_config()) {
+ if (mod < 0) mod += divisor;
+ }
+
+ return divisor - mod;
+ }();
+
+ if (renderRatePhase == 0) {
+ const auto vsyncTime = mLastVsyncSequence->vsyncTime;
+ if (FlagManager::getInstance().vrr_config()) {
+ const auto vsyncTimePoint = TimePoint::fromNs(vsyncTime);
+ ATRACE_FORMAT("%s InPhase vsyncIn %.2fms", __func__,
+ ticks<std::milli, float>(vsyncTimePoint - TimePoint::now()));
+ const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
+ mVsyncTrackerCallback.onVsyncGenerated(vsyncTimePoint, mDisplayModePtr, renderRate);
+ }
+ return vsyncTime;
+ }
+
+ auto const [slope, intercept] = getVSyncPredictionModelLocked();
+ const auto approximateNextVsync = mLastVsyncSequence->vsyncTime + slope * renderRatePhase;
+ const auto nextAnticipatedVsyncTime =
+ nextAnticipatedVSyncTimeFromLocked(approximateNextVsync - slope / 2);
+ if (FlagManager::getInstance().vrr_config()) {
+ const auto nextAnticipatedVsyncTimePoint = TimePoint::fromNs(nextAnticipatedVsyncTime);
+ ATRACE_FORMAT("%s outOfPhase vsyncIn %.2fms", __func__,
+ ticks<std::milli, float>(nextAnticipatedVsyncTimePoint - TimePoint::now()));
+ const Fps renderRate = mRenderRateOpt ? *mRenderRateOpt : mDisplayModePtr->getPeakFps();
+ mVsyncTrackerCallback.onVsyncGenerated(nextAnticipatedVsyncTimePoint, mDisplayModePtr,
+ renderRate);
+ }
+ return nextAnticipatedVsyncTime;
}
/*
@@ -263,81 +360,169 @@ 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(idealPeriod()),
+ frameRate);
+ return isVSyncInPhaseLocked(timePoint, static_cast<unsigned>(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<std::milli, float>(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 period = mRateMap[idealPeriod()].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<VsyncError> vsyncs(static_cast<size_t>(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<float>(vsync - knownVsync) / (period * divisor);
- const auto error = std::abs(std::round(numPeriods) - numPeriods);
- vsyncs[i] = {vsync, error};
- point = vsync + 1;
- }
-
- const auto minVsyncError = std::min_element(vsyncs.begin(), vsyncs.end());
- mRateDivisorKnownTimestampMap[dividedPeriod] = minVsyncError->vsyncTimestamp;
- return std::abs(minVsyncError->vsyncTimestamp - timePoint) < period / 2;
+ const auto vsyncSequence = getVsyncSequenceLocked(justBeforeTimePoint);
+ ATRACE_FORMAT_INSTANT("vsync in: %.2f sequence: %" PRId64,
+ getTimePointIn(now, vsyncSequence.vsyncTime), vsyncSequence.seq);
+ return vsyncSequence.seq % divisor == 0;
}
-VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
+void VSyncPredictor::setRenderRate(Fps renderRate) {
+ ATRACE_FORMAT("%s %s", __func__, to_string(renderRate).c_str());
+ ALOGV("%s %s: RenderRate %s ", __func__, to_string(mId).c_str(), to_string(renderRate).c_str());
std::lock_guard lock(mMutex);
- const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
- return {model.slope, model.intercept};
+ mRenderRateOpt = renderRate;
}
-VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
- return mRateMap.find(mIdealPeriod)->second;
-}
+void VSyncPredictor::setDisplayModePtr(ftl::NonNull<DisplayModePtr> modePtr) {
+ LOG_ALWAYS_FATAL_IF(mId != modePtr->getPhysicalDisplayId(),
+ "mode does not belong to the display");
+ ATRACE_FORMAT("%s %s", __func__, to_string(*modePtr).c_str());
+ const auto timeout = modePtr->getVrrConfig()
+ ? modePtr->getVrrConfig()->notifyExpectedPresentConfig
+ : std::nullopt;
+ ALOGV("%s %s: DisplayMode %s notifyExpectedPresentTimeout %s", __func__, to_string(mId).c_str(),
+ to_string(*modePtr).c_str(),
+ timeout ? std::to_string(timeout->notifyExpectedPresentTimeoutNs).c_str() : "N/A");
+ std::lock_guard lock(mMutex);
-void VSyncPredictor::setPeriod(nsecs_t period) {
- ATRACE_CALL();
+ mDisplayModePtr = modePtr;
+ traceInt64("VSP-setPeriod", modePtr->getVsyncRate().getPeriodNsecs());
- std::lock_guard lock(mMutex);
static constexpr size_t kSizeLimit = 30;
if (CC_UNLIKELY(mRateMap.size() == kSizeLimit)) {
mRateMap.erase(mRateMap.begin());
}
- mIdealPeriod = period;
- if (mRateMap.find(period) == mRateMap.end()) {
- mRateMap[mIdealPeriod] = {period, 0};
+ if (mRateMap.find(idealPeriod()) == mRateMap.end()) {
+ mRateMap[idealPeriod()] = {idealPeriod(), 0};
}
clearTimestamps();
}
+void VSyncPredictor::ensureMinFrameDurationIsKept(TimePoint expectedPresentTime,
+ TimePoint lastConfirmedPresentTime) {
+ const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+ const auto threshold = currentPeriod / 2;
+ const auto minFramePeriod = minFramePeriodLocked().ns();
+
+ auto prev = lastConfirmedPresentTime.ns();
+ for (auto& current : mPastExpectedPresentTimes) {
+ if (CC_UNLIKELY(mTraceOn)) {
+ ATRACE_FORMAT_INSTANT("current %.2f past last signaled fence",
+ static_cast<float>(current.ns() - lastConfirmedPresentTime.ns()) /
+ 1e6f);
+ }
+
+ const auto minPeriodViolation = current.ns() - prev + threshold < minFramePeriod;
+ if (minPeriodViolation) {
+ ATRACE_NAME("minPeriodViolation");
+ current = TimePoint::fromNs(prev + minFramePeriod);
+ prev = current.ns();
+ } else {
+ break;
+ }
+ }
+
+ if (!mPastExpectedPresentTimes.empty()) {
+ const auto phase = Duration(mPastExpectedPresentTimes.back() - expectedPresentTime);
+ if (phase > 0ns) {
+ if (mLastVsyncSequence) {
+ mLastVsyncSequence->vsyncTime += phase.ns();
+ }
+ }
+ }
+}
+
+void VSyncPredictor::onFrameBegin(TimePoint expectedPresentTime,
+ TimePoint lastConfirmedPresentTime) {
+ ATRACE_CALL();
+ std::lock_guard lock(mMutex);
+
+ if (!mDisplayModePtr->getVrrConfig()) return;
+
+ if (CC_UNLIKELY(mTraceOn)) {
+ ATRACE_FORMAT_INSTANT("vsync is %.2f past last signaled fence",
+ static_cast<float>(expectedPresentTime.ns() -
+ lastConfirmedPresentTime.ns()) /
+ 1e6f);
+ }
+ mPastExpectedPresentTimes.push_back(expectedPresentTime);
+
+ const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+ const auto threshold = currentPeriod / 2;
+
+ const auto minFramePeriod = minFramePeriodLocked().ns();
+ while (!mPastExpectedPresentTimes.empty()) {
+ const auto front = mPastExpectedPresentTimes.front().ns();
+ const bool frontIsLastConfirmed =
+ std::abs(front - lastConfirmedPresentTime.ns()) < threshold;
+ const bool frontIsBeforeConfirmed =
+ front < lastConfirmedPresentTime.ns() - minFramePeriod + threshold;
+ if (frontIsLastConfirmed || frontIsBeforeConfirmed) {
+ if (CC_UNLIKELY(mTraceOn)) {
+ ATRACE_FORMAT_INSTANT("Discarding old vsync - %.2f before last signaled fence",
+ static_cast<float>(lastConfirmedPresentTime.ns() -
+ mPastExpectedPresentTimes.front().ns()) /
+ 1e6f);
+ }
+ mPastExpectedPresentTimes.pop_front();
+ } else {
+ break;
+ }
+ }
+
+ ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+}
+
+void VSyncPredictor::onFrameMissed(TimePoint expectedPresentTime) {
+ ATRACE_CALL();
+
+ std::lock_guard lock(mMutex);
+ if (!mDisplayModePtr->getVrrConfig()) return;
+
+ // We don't know when the frame is going to be presented, so we assume it missed one vsync
+ const auto currentPeriod = mRateMap.find(idealPeriod())->second.slope;
+ const auto lastConfirmedPresentTime =
+ TimePoint::fromNs(expectedPresentTime.ns() + currentPeriod);
+
+ ensureMinFrameDurationIsKept(expectedPresentTime, lastConfirmedPresentTime);
+}
+
+VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModel() const {
+ std::lock_guard lock(mMutex);
+ const auto model = VSyncPredictor::getVSyncPredictionModelLocked();
+ return {model.slope, model.intercept};
+}
+
+VSyncPredictor::Model VSyncPredictor::getVSyncPredictionModelLocked() const {
+ return mRateMap.find(idealPeriod())->second;
+}
+
void VSyncPredictor::clearTimestamps() {
+ ATRACE_CALL();
+
if (!mTimestamps.empty()) {
auto const maxRb = *std::max_element(mTimestamps.begin(), mTimestamps.end());
if (mKnownTimestamp) {
@@ -358,18 +543,18 @@ bool VSyncPredictor::needsMoreSamples() const {
void VSyncPredictor::resetModel() {
std::lock_guard lock(mMutex);
- mRateMap[mIdealPeriod] = {mIdealPeriod, 0};
+ mRateMap[idealPeriod()] = {idealPeriod(), 0};
clearTimestamps();
}
void VSyncPredictor::dump(std::string& result) const {
std::lock_guard lock(mMutex);
- StringAppendF(&result, "\tmIdealPeriod=%.2f\n", mIdealPeriod / 1e6f);
+ StringAppendF(&result, "\tmDisplayModePtr=%s\n", to_string(*mDisplayModePtr).c_str());
StringAppendF(&result, "\tRefresh Rate Map:\n");
- for (const auto& [idealPeriod, periodInterceptTuple] : mRateMap) {
+ for (const auto& [period, periodInterceptTuple] : mRateMap) {
StringAppendF(&result,
"\t\tFor ideal period %.2fms: period = %.2fms, intercept = %" PRId64 "\n",
- idealPeriod / 1e6f, periodInterceptTuple.slope / 1e6f,
+ period / 1e6f, periodInterceptTuple.slope / 1e6f,
periodInterceptTuple.intercept);
}
}
diff --git a/services/surfaceflinger/Scheduler/VSyncPredictor.h b/services/surfaceflinger/Scheduler/VSyncPredictor.h
index 3181102663..72a343112a 100644
--- a/services/surfaceflinger/Scheduler/VSyncPredictor.h
+++ b/services/surfaceflinger/Scheduler/VSyncPredictor.h
@@ -16,11 +16,13 @@
#pragma once
+#include <deque>
#include <mutex>
#include <unordered_map>
#include <vector>
#include <android-base/thread_annotations.h>
+#include <ui/DisplayId.h>
#include "VSyncTracker.h"
@@ -29,30 +31,25 @@ namespace android::scheduler {
class VSyncPredictor : public VSyncTracker {
public:
/*
- * \param [in] idealPeriod The initial ideal period to use.
+ * \param [in] PhysicalDisplayid The display this corresponds to.
+ * \param [in] modePtr The initial display mode
* \param [in] historySize The internal amount of entries to store in the model.
* \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.
+ * \param [in] IVsyncTrackerCallback The callback for the VSyncTracker.
*/
- VSyncPredictor(nsecs_t idealPeriod, size_t historySize, size_t minimumSamplesForPrediction,
- uint32_t outlierTolerancePercent);
+ VSyncPredictor(ftl::NonNull<DisplayModePtr> modePtr, size_t historySize,
+ size_t minimumSamplesForPrediction, uint32_t outlierTolerancePercent,
+ IVsyncTrackerCallback&);
~VSyncPredictor();
bool addVsyncTimestamp(nsecs_t timestamp) final EXCLUDES(mMutex);
nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final EXCLUDES(mMutex);
nsecs_t currentPeriod() const final EXCLUDES(mMutex);
+ Period minFramePeriod() const final EXCLUDES(mMutex);
void resetModel() final EXCLUDES(mMutex);
- /*
- * Inform the model that the period is anticipated to change to a new value.
- * model will use the period parameter to predict vsync events until enough
- * timestamps with the new period have been collected.
- *
- * \param [in] period The new period that should be used.
- */
- void setPeriod(nsecs_t period) final EXCLUDES(mMutex);
-
/* Query if the model is in need of more samples to make a prediction.
* \return True, if model would benefit from more samples, False if not.
*/
@@ -67,6 +64,14 @@ public:
bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const final EXCLUDES(mMutex);
+ void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final EXCLUDES(mMutex);
+
+ void setRenderRate(Fps) final EXCLUDES(mMutex);
+
+ void onFrameBegin(TimePoint expectedPresentTime, TimePoint lastConfirmedPresentTime) final
+ EXCLUDES(mMutex);
+ void onFrameMissed(TimePoint expectedPresentTime) final EXCLUDES(mMutex);
+
void dump(std::string& result) const final EXCLUDES(mMutex);
private:
@@ -74,32 +79,47 @@ 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);
+ Period minFramePeriodLocked() const REQUIRES(mMutex);
+ void ensureMinFrameDurationIsKept(TimePoint, TimePoint) REQUIRES(mMutex);
+
+ struct VsyncSequence {
+ nsecs_t vsyncTime;
+ int64_t seq;
+ };
+ VsyncSequence getVsyncSequenceLocked(nsecs_t timestamp) const REQUIRES(mMutex);
+ nsecs_t idealPeriod() const REQUIRES(mMutex);
+
+ bool const mTraceOn;
+ size_t const kHistorySize;
+ size_t const kMinimumSamplesForPrediction;
+ size_t const kOutlierTolerancePercent;
+ IVsyncTrackerCallback& mVsyncTrackerCallback;
+ std::mutex mutable mMutex;
- nsecs_t mIdealPeriod GUARDED_BY(mMutex);
std::optional<nsecs_t> mKnownTimestamp GUARDED_BY(mMutex);
// Map between ideal vsync period and the calculated model
std::unordered_map<nsecs_t, Model> mutable mRateMap GUARDED_BY(mMutex);
- // Map between the divided vsync period and the last known vsync timestamp
- std::unordered_map<nsecs_t, nsecs_t> mutable mRateDivisorKnownTimestampMap GUARDED_BY(mMutex);
-
size_t mLastTimestampIndex GUARDED_BY(mMutex) = 0;
std::vector<nsecs_t> mTimestamps GUARDED_BY(mMutex);
+
+ ftl::NonNull<DisplayModePtr> mDisplayModePtr GUARDED_BY(mMutex);
+ std::optional<Fps> mRenderRateOpt GUARDED_BY(mMutex);
+
+ mutable std::optional<VsyncSequence> mLastVsyncSequence GUARDED_BY(mMutex);
+
+ std::deque<TimePoint> mPastExpectedPresentTimes GUARDED_BY(mMutex);
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.cpp b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
index e23945dc8a..186a2d6740 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.cpp
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.cpp
@@ -21,6 +21,8 @@
#include <assert.h>
#include <cutils/properties.h>
+#include <ftl/concat.h>
+#include <gui/TraceUtils.h>
#include <log/log.h>
#include <utils/Trace.h>
@@ -39,17 +41,20 @@ nsecs_t SystemClock::now() const {
return systemTime(SYSTEM_TIME_MONOTONIC);
}
-VSyncReactor::VSyncReactor(std::unique_ptr<Clock> clock, VSyncTracker& tracker,
- size_t pendingFenceLimit, bool supportKernelIdleTimer)
- : mClock(std::move(clock)),
+VSyncReactor::VSyncReactor(PhysicalDisplayId id, std::unique_ptr<Clock> 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;
bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
+ ATRACE_CALL();
+
if (!fence) {
return false;
}
@@ -61,6 +66,8 @@ bool VSyncReactor::addPresentFence(std::shared_ptr<FenceTime> fence) {
std::lock_guard lock(mMutex);
if (mExternalIgnoreFences || mInternalIgnoreFences) {
+ ATRACE_FORMAT_INSTANT("mExternalIgnoreFences=%d mInternalIgnoreFences=%d",
+ mExternalIgnoreFences, mInternalIgnoreFences);
return true;
}
@@ -113,32 +120,34 @@ void VSyncReactor::updateIgnorePresentFencesInternal() {
}
}
-void VSyncReactor::startPeriodTransitionInternal(nsecs_t newPeriod) {
- ATRACE_CALL();
+void VSyncReactor::startPeriodTransitionInternal(ftl::NonNull<DisplayModePtr> modePtr) {
+ ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
mPeriodConfirmationInProgress = true;
- mPeriodTransitioningTo = newPeriod;
+ mModePtrTransitioningTo = modePtr.get();
mMoreSamplesNeeded = true;
setIgnorePresentFencesInternal(true);
}
void VSyncReactor::endPeriodTransition() {
- ATRACE_CALL();
- mPeriodTransitioningTo.reset();
+ ATRACE_FORMAT("%s %" PRIu64, __func__, mId.value);
+ mModePtrTransitioningTo.reset();
mPeriodConfirmationInProgress = false;
mLastHwVsync.reset();
}
-void VSyncReactor::startPeriodTransition(nsecs_t period) {
- ATRACE_INT64("VSR-setPeriod", period);
+void VSyncReactor::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bool force) {
+ ATRACE_INT64(ftl::Concat("VSR-", __func__, " ", mId.value).c_str(),
+ modePtr->getVsyncRate().getPeriodNsecs());
std::lock_guard lock(mMutex);
mLastHwVsync.reset();
- if (!mSupportKernelIdleTimer && period == mTracker.currentPeriod()) {
+ if (!mSupportKernelIdleTimer &&
+ modePtr->getVsyncRate().getPeriodNsecs() == mTracker.currentPeriod() && !force) {
endPeriodTransition();
setIgnorePresentFencesInternal(false);
mMoreSamplesNeeded = false;
} else {
- startPeriodTransitionInternal(period);
+ startPeriodTransitionInternal(modePtr);
}
}
@@ -156,14 +165,16 @@ bool VSyncReactor::periodConfirmed(nsecs_t vsync_timestamp, std::optional<nsecs_
return false;
}
- const bool periodIsChanging =
- mPeriodTransitioningTo && (*mPeriodTransitioningTo != mTracker.currentPeriod());
+ const std::optional<Period> newPeriod = mModePtrTransitioningTo
+ ? mModePtrTransitioningTo->getVsyncRate().getPeriod()
+ : std::optional<Period>{};
+ const bool periodIsChanging = newPeriod && (newPeriod->ns() != mTracker.currentPeriod());
if (mSupportKernelIdleTimer && !periodIsChanging) {
// Clear out the Composer-provided period and use the allowance logic below
HwcVsyncPeriod = {};
}
- auto const period = mPeriodTransitioningTo ? *mPeriodTransitioningTo : mTracker.currentPeriod();
+ auto const period = newPeriod ? newPeriod->ns() : mTracker.currentPeriod();
static constexpr int allowancePercent = 10;
static constexpr std::ratio<allowancePercent, 100> allowancePercentRatio;
auto const allowance = period * allowancePercentRatio.num / allowancePercentRatio.den;
@@ -181,9 +192,9 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t>
std::lock_guard lock(mMutex);
if (periodConfirmed(timestamp, hwcVsyncPeriod)) {
- ATRACE_NAME("VSR: period confirmed");
- if (mPeriodTransitioningTo) {
- mTracker.setPeriod(*mPeriodTransitioningTo);
+ ATRACE_FORMAT("VSR %" PRIu64 ": period confirmed", mId.value);
+ if (mModePtrTransitioningTo) {
+ mTracker.setDisplayModePtr(ftl::as_non_null(mModePtrTransitioningTo));
*periodFlushed = true;
}
@@ -195,12 +206,12 @@ bool VSyncReactor::addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t>
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();
@@ -225,10 +236,11 @@ void VSyncReactor::dump(std::string& result) const {
mInternalIgnoreFences, mExternalIgnoreFences);
StringAppendF(&result, "mMoreSamplesNeeded=%d mPeriodConfirmationInProgress=%d\n",
mMoreSamplesNeeded, mPeriodConfirmationInProgress);
- if (mPeriodTransitioningTo) {
- StringAppendF(&result, "mPeriodTransitioningTo=%" PRId64 "\n", *mPeriodTransitioningTo);
+ if (mModePtrTransitioningTo) {
+ StringAppendF(&result, "mModePtrTransitioningTo=%s\n",
+ to_string(*mModePtrTransitioningTo).c_str());
} else {
- StringAppendF(&result, "mPeriodTransitioningTo=nullptr\n");
+ StringAppendF(&result, "mModePtrTransitioningTo=nullptr\n");
}
if (mLastHwVsync) {
diff --git a/services/surfaceflinger/Scheduler/VSyncReactor.h b/services/surfaceflinger/Scheduler/VSyncReactor.h
index 4501487392..2415a66a64 100644
--- a/services/surfaceflinger/Scheduler/VSyncReactor.h
+++ b/services/surfaceflinger/Scheduler/VSyncReactor.h
@@ -22,10 +22,12 @@
#include <vector>
#include <android-base/thread_annotations.h>
+#include <ui/DisplayId.h>
#include <ui/FenceTime.h>
#include <scheduler/TimeKeeper.h>
+#include "VSyncTracker.h"
#include "VsyncController.h"
namespace android::scheduler {
@@ -37,14 +39,14 @@ class VSyncTracker;
// TODO (b/145217110): consider renaming.
class VSyncReactor : public VsyncController {
public:
- VSyncReactor(std::unique_ptr<Clock> clock, VSyncTracker& tracker, size_t pendingFenceLimit,
- bool supportKernelIdleTimer);
+ VSyncReactor(PhysicalDisplayId, std::unique_ptr<Clock> clock, VSyncTracker& tracker,
+ size_t pendingFenceLimit, bool supportKernelIdleTimer);
~VSyncReactor();
bool addPresentFence(std::shared_ptr<FenceTime>) final;
void setIgnorePresentFences(bool ignore) final;
- void startPeriodTransition(nsecs_t period) final;
+ void onDisplayModeChanged(ftl::NonNull<DisplayModePtr>, bool force) final;
bool addHwVsyncTimestamp(nsecs_t timestamp, std::optional<nsecs_t> hwcVsyncPeriod,
bool* periodFlushed) final;
@@ -56,11 +58,12 @@ public:
private:
void setIgnorePresentFencesInternal(bool ignore) REQUIRES(mMutex);
void updateIgnorePresentFencesInternal() REQUIRES(mMutex);
- void startPeriodTransitionInternal(nsecs_t newPeriod) REQUIRES(mMutex);
+ void startPeriodTransitionInternal(ftl::NonNull<DisplayModePtr>) REQUIRES(mMutex);
void endPeriodTransition() REQUIRES(mMutex);
bool periodConfirmed(nsecs_t vsync_timestamp, std::optional<nsecs_t> hwcVsyncPeriod)
REQUIRES(mMutex);
+ const PhysicalDisplayId mId;
std::unique_ptr<Clock> const mClock;
VSyncTracker& mTracker;
size_t const mPendingLimit;
@@ -72,7 +75,7 @@ private:
bool mMoreSamplesNeeded GUARDED_BY(mMutex) = false;
bool mPeriodConfirmationInProgress GUARDED_BY(mMutex) = false;
- std::optional<nsecs_t> mPeriodTransitioningTo GUARDED_BY(mMutex);
+ DisplayModePtr mModePtrTransitioningTo GUARDED_BY(mMutex);
std::optional<nsecs_t> mLastHwVsync GUARDED_BY(mMutex);
hal::PowerMode mDisplayPowerMode GUARDED_BY(mMutex) = hal::PowerMode::ON;
diff --git a/services/surfaceflinger/Scheduler/VSyncTracker.h b/services/surfaceflinger/Scheduler/VSyncTracker.h
index 76315d2b5b..1ed863cf7d 100644
--- a/services/surfaceflinger/Scheduler/VSyncTracker.h
+++ b/services/surfaceflinger/Scheduler/VSyncTracker.h
@@ -16,13 +16,22 @@
#pragma once
+#include <ui/DisplayId.h>
#include <utils/Timers.h>
#include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
#include "VSyncDispatch.h"
namespace android::scheduler {
+
+struct IVsyncTrackerCallback {
+ virtual ~IVsyncTrackerCallback() = default;
+ virtual void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+ Fps renderRate) = 0;
+};
+
/*
* VSyncTracker is an interface for providing estimates on future Vsync signal times based on
* historical vsync timing data.
@@ -60,11 +69,9 @@ public:
virtual nsecs_t currentPeriod() const = 0;
/*
- * Inform the tracker that the period is changing and the tracker needs to recalibrate itself.
- *
- * \param [in] period The period that the system is changing into.
+ * The minimal period frames can be displayed.
*/
- virtual void setPeriod(nsecs_t period) = 0;
+ virtual Period minFramePeriod() const = 0;
/* Inform the tracker that the samples it has are not accurate for prediction. */
virtual void resetModel() = 0;
@@ -79,6 +86,32 @@ public:
*/
virtual bool isVSyncInPhase(nsecs_t timePoint, Fps frameRate) const = 0;
+ /*
+ * Sets the active mode of the display which includes the vsync period and other VRR attributes.
+ * This will inform the tracker that the period is changing and the tracker needs to recalibrate
+ * itself.
+ *
+ * \param [in] DisplayModePtr The display mode the tracker will use.
+ */
+ virtual void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) = 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 onFrameBegin(TimePoint expectedPresentTime,
+ TimePoint lastConfirmedPresentTime) = 0;
+
+ virtual void onFrameMissed(TimePoint expectedPresentTime) = 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 ff316515b6..6ae10f3d31 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 02ebd70272..b6cb373b7e 100644
--- a/services/surfaceflinger/Scheduler/VsyncConfiguration.h
+++ b/services/surfaceflinger/Scheduler/VsyncConfiguration.h
@@ -20,12 +20,12 @@
#include <optional>
#include <string>
+#include <android-base/thread_annotations.h>
#include <ftl/small_map.h>
#include <utils/Timers.h>
#include <scheduler/Fps.h>
-
-#include "VsyncModulator.h"
+#include <scheduler/VsyncConfig.h>
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;
@@ -145,7 +143,7 @@ private:
*/
class WorkDuration : public VsyncConfiguration {
public:
- explicit WorkDuration(Fps currentRefrshRate);
+ explicit WorkDuration(Fps currentRefreshRate);
protected:
// Used for unit tests
@@ -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 726a420649..807a7fba53 100644
--- a/services/surfaceflinger/Scheduler/VsyncController.h
+++ b/services/surfaceflinger/Scheduler/VsyncController.h
@@ -22,6 +22,7 @@
#include <DisplayHardware/HWComposer.h>
#include <DisplayHardware/Hal.h>
+#include <scheduler/FrameRateMode.h>
#include <ui/FenceTime.h>
#include <utils/Mutex.h>
#include <utils/RefBase.h>
@@ -59,12 +60,14 @@ public:
bool* periodFlushed) = 0;
/*
- * Inform the controller that the period is changing and the controller needs to recalibrate
- * itself. The controller will end the period transition internally.
+ * Inform the controller that the display mode is changing and the controller needs to
+ * recalibrate itself to the new vsync period. The controller will end the period transition
+ * internally.
*
- * \param [in] period The period that the system is changing into.
+ * \param [in] DisplayModePtr The new mode the display is changing to.
+ * \param [in] force True to recalibrate even if period matches the existing period.
*/
- virtual void startPeriodTransition(nsecs_t period) = 0;
+ virtual void onDisplayModeChanged(ftl::NonNull<DisplayModePtr>, 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 be57b2acd7..586357f50a 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<std::mutex> 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<DeathRecipient>::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<DeathRecipient>::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<std::mutex> 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<std::mutex> 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<IBinder>& who) {
static_cast<void>(updateVsyncConfigLocked());
}
-bool VsyncModulator::isVsyncConfigDefault() const {
+bool VsyncModulator::isVsyncConfigEarly() const {
std::lock_guard<std::mutex> 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 537cae1d7c..be0d3348b5 100644
--- a/services/surfaceflinger/Scheduler/VsyncModulator.h
+++ b/services/surfaceflinger/Scheduler/VsyncModulator.h
@@ -25,19 +25,13 @@
#include <binder/IBinder.h>
#include <utils/Timers.h>
+#include <scheduler/TransactionSchedule.h>
+#include <scheduler/VsyncConfig.h>
+
#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<VsyncConfig>;
- 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<IBinder>&) override EXCLUDES(mMutex);
diff --git a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
index 3a918a1660..db6a1871a7 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.cpp
@@ -16,11 +16,16 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <common/FlagManager.h>
+
+#include <ftl/fake_guard.h>
+#include <gui/TraceUtils.h>
#include <scheduler/Fps.h>
#include <scheduler/Timer.h>
#include "VsyncSchedule.h"
+#include "Utils/Dumper.h"
#include "VSyncDispatchTimerQueue.h"
#include "VSyncPredictor.h"
#include "VSyncReactor.h"
@@ -39,8 +44,8 @@ class VsyncSchedule::PredictedVsyncTracer {
}
public:
- explicit PredictedVsyncTracer(VsyncDispatch& dispatch)
- : mRegistration(dispatch, makeVsyncCallback(), __func__) {
+ explicit PredictedVsyncTracer(std::shared_ptr<VsyncDispatch> dispatch)
+ : mRegistration(std::move(dispatch), makeVsyncCallback(), __func__) {
schedule();
}
@@ -51,24 +56,54 @@ 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<PredictedVsyncTracer>(*mDispatch);
- }
-}
-
-VsyncSchedule::VsyncSchedule(TrackerPtr tracker, DispatchPtr dispatch, ControllerPtr controller)
- : mTracker(std::move(tracker)),
+VsyncSchedule::VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags features,
+ RequestHardwareVsync requestHardwareVsync,
+ IVsyncTrackerCallback& callback)
+ : mId(modePtr->getPhysicalDisplayId()),
+ mRequestHardwareVsync(std::move(requestHardwareVsync)),
+ mTracker(createTracker(modePtr, callback)),
+ mDispatch(createDispatch(mTracker)),
+ mController(createController(modePtr->getPhysicalDisplayId(), *mTracker, features)),
+ mTracer(features.test(Feature::kTracePredictedVsync)
+ ? std::make_unique<PredictedVsyncTracer>(mDispatch)
+ : nullptr) {}
+
+VsyncSchedule::VsyncSchedule(PhysicalDisplayId id, TrackerPtr tracker, DispatchPtr dispatch,
+ ControllerPtr controller, RequestHardwareVsync requestHardwareVsync)
+ : mId(id),
+ mRequestHardwareVsync(std::move(requestHardwareVsync)),
+ 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());
+}
+
+Period VsyncSchedule::minFramePeriod() const {
+ if (FlagManager::getInstance().vrr_config()) {
+ return mTracker->minFramePeriod();
+ }
+ return period();
+}
+
+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<std::mutex> 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 +111,112 @@ void VsyncSchedule::dump(std::string& out) const {
mDispatch->dump(out);
}
-VsyncSchedule::TrackerPtr VsyncSchedule::createTracker() {
+VsyncSchedule::TrackerPtr VsyncSchedule::createTracker(ftl::NonNull<DisplayModePtr> modePtr,
+ IVsyncTrackerCallback& callback) {
// 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<VSyncPredictor>(kInitialPeriod, kHistorySize, kMinSamplesForPrediction,
- kDiscardOutlierPercent);
+ return std::make_unique<VSyncPredictor>(modePtr, kHistorySize, kMinSamplesForPrediction,
+ kDiscardOutlierPercent, callback);
}
-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<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+ return std::make_unique<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), 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<VSyncReactor>(std::make_unique<SystemClock>(), tracker,
+ auto reactor = std::make_unique<VSyncReactor>(id, std::make_unique<SystemClock>(), tracker,
kMaxPendingFences, hasKernelIdleTimer);
reactor->setIgnorePresentFences(!features.test(Feature::kPresentFences));
return reactor;
}
+void VsyncSchedule::onDisplayModeChanged(ftl::NonNull<DisplayModePtr> modePtr, bool force) {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ mController->onDisplayModeChanged(modePtr, force);
+ enableHardwareVsyncLocked();
+}
+
+bool VsyncSchedule::addResyncSample(TimePoint timestamp, ftl::Optional<Period> hwcVsyncPeriod) {
+ bool needsHwVsync = false;
+ bool periodFlushed = false;
+ {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ if (mHwVsyncState == HwVsyncState::Enabled) {
+ needsHwVsync = mController->addHwVsyncTimestamp(timestamp.ns(),
+ hwcVsyncPeriod.transform(&Period::ns),
+ &periodFlushed);
+ }
+ }
+ if (needsHwVsync) {
+ enableHardwareVsync();
+ } else {
+ constexpr bool kDisallow = false;
+ disableHardwareVsync(kDisallow);
+ }
+ return periodFlushed;
+}
+
+void VsyncSchedule::enableHardwareVsync() {
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ enableHardwareVsyncLocked();
+}
+
+void VsyncSchedule::enableHardwareVsyncLocked() {
+ ATRACE_CALL();
+ if (mHwVsyncState == HwVsyncState::Disabled) {
+ getTracker().resetModel();
+ mRequestHardwareVsync(mId, true);
+ mHwVsyncState = HwVsyncState::Enabled;
+ }
+}
+
+void VsyncSchedule::disableHardwareVsync(bool disallow) {
+ ATRACE_CALL();
+ std::lock_guard<std::mutex> lock(mHwVsyncLock);
+ switch (mHwVsyncState) {
+ case HwVsyncState::Enabled:
+ mRequestHardwareVsync(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<std::mutex> 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 0d9b114875..722ea0b836 100644
--- a/services/surfaceflinger/Scheduler/VsyncSchedule.h
+++ b/services/surfaceflinger/Scheduler/VsyncSchedule.h
@@ -16,10 +16,31 @@
#pragma once
+#include <functional>
#include <memory>
#include <string>
+#include <android-base/thread_annotations.h>
+#include <ThreadContext.h>
+#include <ftl/enum.h>
+#include <ftl/optional.h>
+#include <ui/DisplayId.h>
+
#include <scheduler/Features.h>
+#include <scheduler/IVsyncSource.h>
+#include <scheduler/Time.h>
+
+#include "ThreadContext.h"
+#include "VSyncTracker.h"
+
+namespace android {
+class EventThreadTest;
+class VsyncScheduleTest;
+}
+
+namespace android::fuzz {
+class SchedulerFuzzer;
+}
namespace android::scheduler {
@@ -32,44 +53,116 @@ using VsyncDispatch = VSyncDispatch;
using VsyncTracker = VSyncTracker;
// Schedule that synchronizes to hardware VSYNC of a physical display.
-class VsyncSchedule {
+class VsyncSchedule final : public IVsyncSource {
public:
- explicit VsyncSchedule(FeatureFlags);
- VsyncSchedule(VsyncSchedule&&);
+ using RequestHardwareVsync = std::function<void(PhysicalDisplayId, bool enabled)>;
+
+ VsyncSchedule(ftl::NonNull<DisplayModePtr> modePtr, FeatureFlags, RequestHardwareVsync,
+ IVsyncTrackerCallback&);
~VsyncSchedule();
+ // IVsyncSource overrides:
+ Period period() const override;
+ TimePoint vsyncDeadlineAfter(TimePoint) const override;
+ Period minFramePeriod() const override;
+
+ // Inform the schedule that the display mode changed the schedule needs to recalibrate
+ // itself to the new vsync period. The schedule will end the period transition internally.
+ // This will enable hardware VSYNCs in order to calibrate.
+ //
+ // \param [in] DisplayModePtr The mode that the display is changing to.
+ // \param [in] force True to force a transition even if it is not a
+ // change.
+ void onDisplayModeChanged(ftl::NonNull<DisplayModePtr>, 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(TimePoint timestamp, ftl::Optional<Period> 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<VsyncDispatch>;
+ using TrackerPtr = std::shared_ptr<VsyncTracker>;
+
// 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() 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(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);
+
+ void setPendingHardwareVsyncState(bool enabled) REQUIRES(kMainThreadContext);
- using TrackerPtr = std::unique_ptr<VsyncTracker>;
- using DispatchPtr = std::unique_ptr<VsyncDispatch>;
+ bool getPendingHardwareVsyncState() const REQUIRES(kMainThreadContext);
+
+protected:
using ControllerPtr = std::unique_ptr<VsyncController>;
+ static void NoOpRequestHardwareVsync(PhysicalDisplayId, bool) {}
+
// For tests.
- VsyncSchedule(TrackerPtr, DispatchPtr, ControllerPtr);
+ VsyncSchedule(PhysicalDisplayId, TrackerPtr, DispatchPtr, ControllerPtr,
+ RequestHardwareVsync = NoOpRequestHardwareVsync);
+
+private:
+ friend class TestableScheduler;
+ friend class android::EventThreadTest;
+ friend class android::VsyncScheduleTest;
+ friend class android::fuzz::SchedulerFuzzer;
+
+ static TrackerPtr createTracker(ftl::NonNull<DisplayModePtr> modePtr, IVsyncTrackerCallback&);
+ static DispatchPtr createDispatch(TrackerPtr);
+ static ControllerPtr createController(PhysicalDisplayId, VsyncTracker&, FeatureFlags);
+
+ void enableHardwareVsyncLocked() 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<PredictedVsyncTracer>;
- // Effectively const except in move constructor.
- TrackerPtr mTracker;
- DispatchPtr mDispatch;
- ControllerPtr mController;
- TracerPtr mTracer;
+ const PhysicalDisplayId mId;
+ const RequestHardwareVsync mRequestHardwareVsync;
+ const TrackerPtr mTracker;
+ const DispatchPtr mDispatch;
+ const ControllerPtr mController;
+ const TracerPtr mTracer;
};
} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Features.h b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
index b3a6a606c3..7c72ac6afc 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Features.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Features.h
@@ -23,10 +23,12 @@
namespace android::scheduler {
enum class Feature : std::uint8_t {
- kPresentFences = 0b1,
- kKernelIdleTimer = 0b10,
- kContentDetection = 0b100,
- kTracePredictedVsync = 0b1000,
+ kPresentFences = 1 << 0,
+ kKernelIdleTimer = 1 << 1,
+ kContentDetection = 1 << 2,
+ kTracePredictedVsync = 1 << 3,
+ kBackpressureGpuComposition = 1 << 4,
+ kSmallDirtyContentDetection = 1 << 5,
};
using FeatureFlags = ftl::Flags<Feature>;
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
index bd4f40989d..2806450c5f 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Fps.h
@@ -23,7 +23,8 @@
#include <type_traits>
#include <android-base/stringprintf.h>
-#include <utils/Timers.h>
+#include <ftl/enum.h>
+#include <scheduler/Time.h>
namespace android {
@@ -52,6 +53,7 @@ public:
constexpr float getValue() const { return mFrequency; }
int getIntValue() const { return static_cast<int>(std::round(mFrequency)); }
+ constexpr Period getPeriod() const { return Period::fromNs(mPeriod); }
constexpr nsecs_t getPeriodNsecs() const { return mPeriod; }
private:
@@ -66,6 +68,29 @@ struct FpsRange {
Fps max = Fps::fromValue(std::numeric_limits<float>::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;
+};
+
+// The frame rate category of a Layer.
+enum class FrameRateCategory : int32_t {
+ Default,
+ NoPreference,
+ Low,
+ Normal,
+ High,
+
+ ftl_last = High
};
static_assert(std::is_trivially_copyable_v<Fps>);
@@ -127,13 +152,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<unsigned>(std::ceil(lhs.getValue() / rhs.getValue()));
+}
+
} // namespace fps_approx_ops
+constexpr Fps operator/(Fps fps, unsigned divisor) {
+ return Fps::fromPeriodNsecs(fps.getPeriodNsecs() * static_cast<nsecs_t>(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 +202,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 0000000000..59a6df23fd
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameRateMode.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 <ftl/non_null.h>
+#include <scheduler/Fps.h>
+
+// TODO(b/241285191): Pull this to <ui/DisplayMode.h>
+#include "DisplayHardware/DisplayMode.h"
+
+namespace android::scheduler {
+
+struct FrameRateMode {
+ Fps fps; // The render frame rate, which is a divisor of modePtr->getFps().
+ ftl::NonNull<DisplayModePtr> 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->getPeakFps()) + "(" +
+ to_string(mode.modePtr->getVsyncRate()) + "))";
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
new file mode 100644
index 0000000000..70d4846070
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/FrameTargeter.h
@@ -0,0 +1,150 @@
+/*
+ * 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 <array>
+#include <atomic>
+#include <memory>
+
+#include <ui/DisplayId.h>
+#include <ui/Fence.h>
+#include <ui/FenceTime.h>
+
+#include <scheduler/Time.h>
+#include <scheduler/VsyncId.h>
+#include <scheduler/interface/CompositeResult.h>
+
+// TODO(b/185536303): Pull to FTL.
+#include "../../../TracedOrdinal.h"
+#include "../../../Utils/Dumper.h"
+
+namespace android::scheduler {
+
+struct IVsyncSource;
+
+// Read-only interface to the metrics computed by FrameTargeter for the latest frame.
+class FrameTarget {
+public:
+ VsyncId vsyncId() const { return mVsyncId; }
+
+ // The time when the frame actually began, as opposed to when it had been scheduled to begin.
+ TimePoint frameBeginTime() const { return mFrameBeginTime; }
+
+ // Relative to when the frame actually began, as opposed to when it had been scheduled to begin.
+ Duration expectedFrameDuration() const { return mExpectedPresentTime - mFrameBeginTime; }
+
+ TimePoint expectedPresentTime() const { return mExpectedPresentTime; }
+
+ // The time of the VSYNC that preceded this frame. See `presentFenceForPastVsync` for details.
+ TimePoint pastVsyncTime(Period minFramePeriod) const;
+
+ // Equivalent to `pastVsyncTime` unless running N VSYNCs ahead.
+ TimePoint previousFrameVsyncTime(Period minFramePeriod) const {
+ return mExpectedPresentTime - minFramePeriod;
+ }
+
+ // The present fence for the frame that had targeted the most recent VSYNC before this frame.
+ // If the target VSYNC for any given frame is more than `vsyncPeriod` in the future, then the
+ // VSYNC of at least one previous frame has not yet passed. In other words, this is NOT the
+ // `presentFenceForPreviousFrame` if running N VSYNCs ahead, but the one that should have been
+ // signaled by now (unless that frame missed).
+ const FenceTimePtr& presentFenceForPastVsync(Period minFramePeriod) const;
+
+ // Equivalent to `presentFenceForPastVsync` unless running N VSYNCs ahead.
+ const FenceTimePtr& presentFenceForPreviousFrame() const {
+ return mPresentFences.front().fenceTime;
+ }
+
+ bool wouldPresentEarly(Period minFramePeriod) const;
+
+ bool isFramePending() const { return mFramePending; }
+ bool didMissFrame() const { return mFrameMissed; }
+ bool didMissHwcFrame() const { return mHwcFrameMissed && !mGpuFrameMissed; }
+
+protected:
+ explicit FrameTarget(const std::string& displayLabel);
+ ~FrameTarget() = default;
+
+ VsyncId mVsyncId;
+ TimePoint mFrameBeginTime;
+ TimePoint mExpectedPresentTime;
+
+ TracedOrdinal<bool> mFramePending;
+ TracedOrdinal<bool> mFrameMissed;
+ TracedOrdinal<bool> mHwcFrameMissed;
+ TracedOrdinal<bool> mGpuFrameMissed;
+
+ struct FenceWithFenceTime {
+ sp<Fence> fence = Fence::NO_FENCE;
+ FenceTimePtr fenceTime = FenceTime::NO_FENCE;
+ };
+ std::array<FenceWithFenceTime, 2> mPresentFences;
+
+private:
+ template <int N>
+ inline bool targetsVsyncsAhead(Period minFramePeriod) const {
+ static_assert(N > 1);
+ return expectedFrameDuration() > (N - 1) * minFramePeriod;
+ }
+};
+
+// Computes a display's per-frame metrics about past/upcoming targeting of present deadlines.
+class FrameTargeter final : private FrameTarget {
+public:
+ FrameTargeter(PhysicalDisplayId displayId, bool backpressureGpuComposition)
+ : FrameTarget(to_string(displayId)),
+ mBackpressureGpuComposition(backpressureGpuComposition) {}
+
+ const FrameTarget& target() const { return *this; }
+
+ struct BeginFrameArgs {
+ TimePoint frameBeginTime;
+ VsyncId vsyncId;
+ TimePoint expectedVsyncTime;
+ Duration sfWorkDuration;
+ };
+
+ void beginFrame(const BeginFrameArgs&, const IVsyncSource&);
+
+ // TODO(b/241285191): Merge with FrameTargeter::endFrame.
+ FenceTimePtr setPresentFence(sp<Fence>);
+
+ void endFrame(const CompositeResult&);
+
+ void dump(utils::Dumper&) const;
+
+private:
+ friend class FrameTargeterTest;
+
+ // For tests.
+ using IsFencePendingFuncPtr = bool (*)(const FenceTimePtr&, int graceTimeMs);
+ void beginFrame(const BeginFrameArgs&, const IVsyncSource&, IsFencePendingFuncPtr);
+ FenceTimePtr setPresentFence(sp<Fence>, FenceTimePtr);
+
+ static bool isFencePending(const FenceTimePtr&, int graceTimeMs);
+
+ const bool mBackpressureGpuComposition;
+
+ TimePoint mScheduledPresentTime;
+ CompositionCoverageFlags mCompositionCoverage;
+
+ std::atomic_uint mFrameMissedCount = 0;
+ std::atomic_uint mHwcFrameMissedCount = 0;
+ std::atomic_uint mGpuFrameMissedCount = 0;
+};
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
new file mode 100644
index 0000000000..0154060f36
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/IVsyncSource.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <scheduler/Time.h>
+
+namespace android::scheduler {
+
+struct IVsyncSource {
+ virtual Period period() const = 0;
+ virtual TimePoint vsyncDeadlineAfter(TimePoint) const = 0;
+ virtual Period minFramePeriod() const = 0;
+
+protected:
+ ~IVsyncSource() = default;
+};
+
+} // 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 0000000000..23ae83fc4b
--- /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 <memory>
+#include <queue>
+#include <utility>
+
+#include <scheduler/Time.h>
+
+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<FenceTime> presentFenceTime);
+
+private:
+ struct PendingFrame {
+ PendingFrame(TimePoint compositeTime, std::shared_ptr<FenceTime> presentFenceTime)
+ : compositeTime(compositeTime), presentFenceTime(std::move(presentFenceTime)) {}
+
+ const TimePoint compositeTime;
+ const std::shared_ptr<FenceTime> presentFenceTime;
+ };
+
+ std::queue<PendingFrame> 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 0000000000..ba1459a562
--- /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 <chrono>
+#include <string>
+
+#include <android-base/stringprintf.h>
+#include <utils/Timers.h>
+
+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 <typename R, typename P>
+ constexpr Duration(std::chrono::duration<R, P> 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<std::ratio<1>>(d); // Integer seconds.
+// const auto f = ticks<std::milli, float>(d); // Floating-point milliseconds.
+//
+template <typename Period, typename Rep = Duration::rep>
+constexpr Rep ticks(Duration d) {
+ using D = std::chrono::duration<Rep, Period>;
+ return std::chrono::duration_cast<D>(d).count();
+}
+
+inline std::string to_string(Duration d) {
+ return base::StringPrintf("%.3f ms", ticks<std::milli, float>(d));
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/Timer.h b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
index 58ad6cb991..67f7abe4ff 100644
--- a/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/Timer.h
@@ -60,6 +60,7 @@ private:
void reset() EXCLUDES(mMutex);
void cleanup() REQUIRES(mMutex);
void setDebugState(DebugState) EXCLUDES(mMutex);
+ void setCallback(std::function<void()>&&) REQUIRES(mMutex);
int mTimerFd = -1;
@@ -71,6 +72,7 @@ private:
void endDispatch();
mutable std::mutex mMutex;
+
std::function<void()> mCallback GUARDED_BY(mMutex);
bool mExpectingCallback GUARDED_BY(mMutex) = false;
DebugState mDebugState GUARDED_BY(mMutex);
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h b/services/surfaceflinger/Scheduler/include/scheduler/TransactionSchedule.h
new file mode 100644
index 0000000000..6fc44dde23
--- /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 0000000000..47d95a8a9a
--- /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 <chrono>
+
+#include <utils/Timers.h>
+
+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/libs/renderengine/include/renderengine/Framebuffer.h b/services/surfaceflinger/Scheduler/include/scheduler/VsyncId.h
index 65111278e0..6ca4e85041 100644
--- a/libs/renderengine/include/renderengine/Framebuffer.h
+++ b/services/surfaceflinger/Scheduler/include/scheduler/VsyncId.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 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.
@@ -18,18 +18,17 @@
#include <cstdint>
-struct ANativeWindowBuffer;
+#include <ftl/mixins.h>
namespace android {
-namespace renderengine {
-class Framebuffer {
-public:
- virtual ~Framebuffer() = default;
-
- virtual bool setNativeWindowBuffer(ANativeWindowBuffer* nativeBuffer, bool isProtected,
- const bool useFramebufferCache) = 0;
+// Sequential frame identifier, also known as FrameTimeline token.
+//
+// TODO(b/241285191): Pull to <gui/FrameTimelineInfo.h> and use VsyncId over int64_t everywhere.
+struct VsyncId : ftl::DefaultConstructible<VsyncId, int64_t, -1>,
+ ftl::Incrementable<VsyncId>,
+ ftl::Equatable<VsyncId> {
+ using DefaultConstructible::DefaultConstructible;
};
-} // namespace renderengine
} // namespace android
diff --git a/libs/renderengine/mock/Image.cpp b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
index 57f4346f8f..87c704ece3 100644
--- a/libs/renderengine/mock/Image.cpp
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositeResult.h
@@ -1,5 +1,5 @@
/*
- * Copyright 2018 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.
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-#include <renderengine/mock/Image.h>
+#pragma once
+
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
+#include <scheduler/interface/CompositionCoverage.h>
namespace android {
-namespace renderengine {
-namespace mock {
-// The Google Mock documentation recommends explicit non-header instantiations
-// for better compile time performance.
-Image::Image() = default;
-Image::~Image() = default;
+struct CompositeResult {
+ CompositionCoverageFlags compositionCoverage;
+};
+
+using CompositeResultsPerDisplay = ui::PhysicalDisplayMap<PhysicalDisplayId, CompositeResult>;
-} // namespace mock
-} // namespace renderengine
} // namespace android
diff --git a/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
new file mode 100644
index 0000000000..767462dfce
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/CompositionCoverage.h
@@ -0,0 +1,49 @@
+/*
+ * 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 <cstdint>
+
+#include <ftl/flags.h>
+#include <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
+namespace android {
+
+// Whether composition was covered by HWC and/or GPU.
+enum class CompositionCoverage : std::uint8_t {
+ Hwc = 1 << 0,
+
+ // 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<CompositionCoverage>;
+
+using CompositionCoveragePerDisplay = ui::DisplayMap<DisplayId, CompositionCoverageFlags>;
+
+inline CompositionCoverageFlags multiDisplayUnion(const CompositionCoveragePerDisplay& displays) {
+ CompositionCoverageFlags coverage;
+ for (const auto& [id, flags] : displays) {
+ coverage |= flags;
+ }
+ return coverage;
+}
+
+} // 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 0000000000..12ee36e084
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/include/scheduler/interface/ICompositor.h
@@ -0,0 +1,57 @@
+/*
+ * 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 <ui/DisplayId.h>
+#include <ui/DisplayMap.h>
+
+#include <scheduler/Time.h>
+#include <scheduler/VsyncId.h>
+#include <scheduler/interface/CompositeResult.h>
+
+namespace android {
+namespace scheduler {
+
+class FrameTarget;
+class FrameTargeter;
+
+using FrameTargets = ui::PhysicalDisplayMap<PhysicalDisplayId, const scheduler::FrameTarget*>;
+using FrameTargeters = ui::PhysicalDisplayMap<PhysicalDisplayId, scheduler::FrameTargeter*>;
+
+} // namespace scheduler
+
+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(PhysicalDisplayId pacesetterId, const scheduler::FrameTargets&) = 0;
+
+ // Composites a frame for each display. CompositionEngine performs GPU and/or HAL composition
+ // via RenderEngine and the Composer HAL, respectively.
+ virtual CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+ const scheduler::FrameTargeters&) = 0;
+
+ // Samples the composited frame via RegionSamplingThread.
+ virtual void sample() = 0;
+
+protected:
+ ~ICompositor() = default;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp b/services/surfaceflinger/Scheduler/src/FrameTargeter.cpp
new file mode 100644
index 0000000000..e80372be31
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/src/FrameTargeter.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 <gui/TraceUtils.h>
+
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/IVsyncSource.h>
+
+namespace android::scheduler {
+
+FrameTarget::FrameTarget(const std::string& displayLabel)
+ : mFramePending("PrevFramePending " + displayLabel, false),
+ mFrameMissed("PrevFrameMissed " + displayLabel, false),
+ mHwcFrameMissed("PrevHwcFrameMissed " + displayLabel, false),
+ mGpuFrameMissed("PrevGpuFrameMissed " + displayLabel, false) {}
+
+TimePoint FrameTarget::pastVsyncTime(Period minFramePeriod) const {
+ // TODO(b/267315508): Generalize to N VSYNCs.
+ const int shift = static_cast<int>(targetsVsyncsAhead<2>(minFramePeriod));
+ return mExpectedPresentTime - Period::fromNs(minFramePeriod.ns() << shift);
+}
+
+const FenceTimePtr& FrameTarget::presentFenceForPastVsync(Period minFramePeriod) const {
+ // TODO(b/267315508): Generalize to N VSYNCs.
+ const size_t i = static_cast<size_t>(targetsVsyncsAhead<2>(minFramePeriod));
+ return mPresentFences[i].fenceTime;
+}
+
+bool FrameTarget::wouldPresentEarly(Period minFramePeriod) const {
+ // TODO(b/241285475): Since this is called during `composite`, the calls to `targetsVsyncsAhead`
+ // should use `TimePoint::now()` in case of delays since `mFrameBeginTime`.
+
+ // TODO(b/267315508): Generalize to N VSYNCs.
+ if (targetsVsyncsAhead<3>(minFramePeriod)) {
+ return true;
+ }
+
+ const auto fence = presentFenceForPastVsync(minFramePeriod);
+ return fence->isValid() && fence->getSignalTime() != Fence::SIGNAL_TIME_PENDING;
+}
+
+void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& vsyncSource) {
+ return beginFrame(args, vsyncSource, &FrameTargeter::isFencePending);
+}
+
+void FrameTargeter::beginFrame(const BeginFrameArgs& args, const IVsyncSource& vsyncSource,
+ IsFencePendingFuncPtr isFencePendingFuncPtr) {
+ mVsyncId = args.vsyncId;
+ mFrameBeginTime = args.frameBeginTime;
+
+ // The `expectedVsyncTime`, which was predicted when this frame was scheduled, is normally in
+ // the future relative to `frameBeginTime`, but may not be for delayed frames. Adjust
+ // `mExpectedPresentTime` accordingly, but not `mScheduledPresentTime`.
+ const TimePoint lastScheduledPresentTime = mScheduledPresentTime;
+ mScheduledPresentTime = args.expectedVsyncTime;
+
+ const Period vsyncPeriod = vsyncSource.period();
+ const Period minFramePeriod = vsyncSource.minFramePeriod();
+
+ // 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 (args.expectedVsyncTime >= args.frameBeginTime) {
+ mExpectedPresentTime = args.expectedVsyncTime;
+ } else {
+ mExpectedPresentTime = vsyncSource.vsyncDeadlineAfter(args.frameBeginTime);
+ if (args.sfWorkDuration > vsyncPeriod) {
+ // Inflate the expected present time if we're targeting the next VSYNC.
+ mExpectedPresentTime += vsyncPeriod;
+ }
+ }
+
+ ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, ftl::to_underlying(args.vsyncId),
+ ticks<std::milli, float>(mExpectedPresentTime - TimePoint::now()),
+ mExpectedPresentTime == args.expectedVsyncTime ? "" : " (adjusted)");
+
+ const FenceTimePtr& pastPresentFence = presentFenceForPastVsync(minFramePeriod);
+
+ // In cases where the present fence is about to fire, give it a small grace period instead of
+ // giving up on the frame.
+ //
+ // TODO(b/280667110): The grace period should depend on `sfWorkDuration` and `vsyncPeriod` being
+ // approximately equal, not whether backpressure propagation is enabled.
+ const int graceTimeForPresentFenceMs = static_cast<int>(
+ mBackpressureGpuComposition || !mCompositionCoverage.test(CompositionCoverage::Gpu));
+
+ // Pending frames may trigger backpressure propagation.
+ const auto& isFencePending = *isFencePendingFuncPtr;
+ mFramePending = pastPresentFence != FenceTime::NO_FENCE &&
+ isFencePending(pastPresentFence, graceTimeForPresentFenceMs);
+
+ // A frame is missed if the prior frame is still pending. If no longer pending, then we still
+ // count the frame as missed if the predicted present time was further in the past than when the
+ // fence actually fired. 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.
+ mFrameMissed = mFramePending || [&] {
+ const nsecs_t pastPresentTime = pastPresentFence->getSignalTime();
+ if (pastPresentTime < 0) return false;
+ const nsecs_t frameMissedSlop = vsyncPeriod.ns() / 2;
+ return lastScheduledPresentTime.ns() < pastPresentTime - frameMissedSlop;
+ }();
+
+ mHwcFrameMissed = mFrameMissed && mCompositionCoverage.test(CompositionCoverage::Hwc);
+ mGpuFrameMissed = mFrameMissed && mCompositionCoverage.test(CompositionCoverage::Gpu);
+
+ if (mFrameMissed) mFrameMissedCount++;
+ if (mHwcFrameMissed) mHwcFrameMissedCount++;
+ if (mGpuFrameMissed) mGpuFrameMissedCount++;
+}
+
+void FrameTargeter::endFrame(const CompositeResult& result) {
+ mCompositionCoverage = result.compositionCoverage;
+}
+
+FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence) {
+ auto presentFenceTime = std::make_shared<FenceTime>(presentFence);
+ return setPresentFence(std::move(presentFence), std::move(presentFenceTime));
+}
+
+FenceTimePtr FrameTargeter::setPresentFence(sp<Fence> presentFence, FenceTimePtr presentFenceTime) {
+ mPresentFences[1] = mPresentFences[0];
+ mPresentFences[0] = {std::move(presentFence), presentFenceTime};
+ return presentFenceTime;
+}
+
+void FrameTargeter::dump(utils::Dumper& dumper) const {
+ // There are scripts and tests that expect this (rather than "name=value") format.
+ dumper.dump({}, "Total missed frame count: " + std::to_string(mFrameMissedCount));
+ dumper.dump({}, "HWC missed frame count: " + std::to_string(mHwcFrameMissedCount));
+ dumper.dump({}, "GPU missed frame count: " + std::to_string(mGpuFrameMissedCount));
+}
+
+bool FrameTargeter::isFencePending(const FenceTimePtr& fence, int graceTimeMs) {
+ ATRACE_CALL();
+ const status_t status = fence->wait(graceTimeMs);
+
+ // This is the same as Fence::Status::Unsignaled, but it saves a call to getStatus,
+ // which calls wait(0) again internally.
+ return status == -ETIME;
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp b/services/surfaceflinger/Scheduler/src/PresentLatencyTracker.cpp
new file mode 100644
index 0000000000..8f3e0818f8
--- /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 <scheduler/PresentLatencyTracker.h>
+
+#include <cutils/compiler.h>
+#include <log/log.h>
+#include <ui/FenceTime.h>
+
+namespace android::scheduler {
+
+Duration PresentLatencyTracker::trackPendingFrame(TimePoint compositeTime,
+ std::shared_ptr<FenceTime> 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/src/Timer.cpp b/services/surfaceflinger/Scheduler/src/Timer.cpp
index a4cf57fbaa..09e8a1ebe0 100644
--- a/services/surfaceflinger/Scheduler/src/Timer.cpp
+++ b/services/surfaceflinger/Scheduler/src/Timer.cpp
@@ -93,8 +93,8 @@ void Timer::cleanup() {
close(mPipes[kWritePipe]);
mPipes[kWritePipe] = -1;
}
- mExpectingCallback = false;
- mCallback = {};
+
+ setCallback({});
}
void Timer::endDispatch() {
@@ -112,8 +112,7 @@ void Timer::alarmAt(std::function<void()> callback, nsecs_t time) {
static constexpr int ns_per_s =
std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
- mCallback = std::move(callback);
- mExpectingCallback = true;
+ setCallback(std::move(callback));
struct itimerspec old_timer;
struct itimerspec new_timer {
@@ -142,6 +141,8 @@ void Timer::alarmCancel() {
if (timerfd_settime(mTimerFd, 0, &new_timer, &old_timer)) {
ALOGW("Failed to disarm timerfd");
}
+
+ setCallback({});
}
void Timer::threadMain() {
@@ -231,6 +232,11 @@ void Timer::setDebugState(DebugState state) {
mDebugState = state;
}
+void Timer::setCallback(std::function<void()>&& callback) {
+ mExpectingCallback = bool(callback);
+ mCallback = std::move(callback);
+}
+
void Timer::dump(std::string& result) const {
std::lock_guard lock(mMutex);
result.append("\t\tDebugState: ");
diff --git a/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
new file mode 100644
index 0000000000..c883385755
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/tests/FrameTargeterTest.cpp
@@ -0,0 +1,338 @@
+/*
+ * 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 <ftl/optional.h>
+#include <gtest/gtest.h>
+
+#include <common/test/FlagUtils.h>
+#include <scheduler/Fps.h>
+#include <scheduler/FrameTargeter.h>
+#include <scheduler/IVsyncSource.h>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+using namespace std::chrono_literals;
+
+namespace android::scheduler {
+namespace {
+
+struct VsyncSource final : IVsyncSource {
+ VsyncSource(Period period, Period minFramePeriod, TimePoint deadline)
+ : vsyncPeriod(period), framePeriod(minFramePeriod), vsyncDeadline(deadline) {}
+
+ const Period vsyncPeriod;
+ const Period framePeriod;
+ const TimePoint vsyncDeadline;
+
+ Period period() const override { return vsyncPeriod; }
+ TimePoint vsyncDeadlineAfter(TimePoint) const override { return vsyncDeadline; }
+ Period minFramePeriod() const override { return framePeriod; }
+};
+
+} // namespace
+
+class FrameTargeterTest : public testing::Test {
+public:
+ const auto& target() const { return mTargeter.target(); }
+
+ struct Frame {
+ Frame(FrameTargeterTest* testPtr, VsyncId vsyncId, TimePoint& frameBeginTime,
+ Duration frameDuration, Fps refreshRate, Fps peakRefreshRate,
+ FrameTargeter::IsFencePendingFuncPtr isFencePendingFuncPtr = Frame::fenceSignaled,
+ const ftl::Optional<VsyncSource>& vsyncSourceOpt = std::nullopt)
+ : testPtr(testPtr),
+ frameBeginTime(frameBeginTime),
+ period(refreshRate.getPeriod()),
+ minFramePeriod(peakRefreshRate.getPeriod()) {
+ const FrameTargeter::BeginFrameArgs args{.frameBeginTime = frameBeginTime,
+ .vsyncId = vsyncId,
+ .expectedVsyncTime =
+ frameBeginTime + frameDuration,
+ .sfWorkDuration = 10ms};
+
+ testPtr->mTargeter.beginFrame(args,
+ vsyncSourceOpt
+ .or_else([&] {
+ return std::make_optional(
+ VsyncSource(period, period,
+ args.expectedVsyncTime));
+ })
+ .value(),
+ isFencePendingFuncPtr);
+ }
+
+ FenceTimePtr end(CompositionCoverage coverage = CompositionCoverage::Hwc) {
+ if (ended) return nullptr;
+ ended = true;
+
+ auto [fence, fenceTime] = testPtr->mFenceMap.makePendingFenceForTest();
+ testPtr->mTargeter.setPresentFence(std::move(fence), fenceTime);
+
+ testPtr->mTargeter.endFrame({.compositionCoverage = coverage});
+ return fenceTime;
+ }
+
+ ~Frame() {
+ end();
+ frameBeginTime += period;
+ }
+
+ static bool fencePending(const FenceTimePtr&, int) { return true; }
+ static bool fenceSignaled(const FenceTimePtr&, int) { return false; }
+
+ FrameTargeterTest* const testPtr;
+
+ TimePoint& frameBeginTime;
+ const Period period;
+ const Period minFramePeriod;
+
+ bool ended = false;
+ };
+
+private:
+ FenceToFenceTimeMap mFenceMap;
+
+ static constexpr bool kBackpressureGpuComposition = true;
+ FrameTargeter mTargeter{PhysicalDisplayId::fromPort(13), kBackpressureGpuComposition};
+};
+
+TEST_F(FrameTargeterTest, targetsFrames) {
+ VsyncId vsyncId{42};
+ {
+ TimePoint frameBeginTime(989ms);
+ const Frame frame(this, vsyncId++, frameBeginTime, 10ms, 60_Hz, 60_Hz);
+
+ EXPECT_EQ(target().vsyncId(), VsyncId{42});
+ EXPECT_EQ(target().frameBeginTime(), TimePoint(989ms));
+ EXPECT_EQ(target().expectedPresentTime(), TimePoint(999ms));
+ EXPECT_EQ(target().expectedFrameDuration(), 10ms);
+ }
+ {
+ TimePoint frameBeginTime(1100ms);
+ const Frame frame(this, vsyncId++, frameBeginTime, 11ms, 60_Hz, 60_Hz);
+
+ EXPECT_EQ(target().vsyncId(), VsyncId{43});
+ EXPECT_EQ(target().frameBeginTime(), TimePoint(1100ms));
+ EXPECT_EQ(target().expectedPresentTime(), TimePoint(1111ms));
+ EXPECT_EQ(target().expectedFrameDuration(), 11ms);
+ }
+}
+
+TEST_F(FrameTargeterTest, inflatesExpectedPresentTime) {
+ // Negative such that `expectedVsyncTime` is in the past.
+ constexpr Duration kFrameDuration = -3ms;
+ TimePoint frameBeginTime(777ms);
+
+ constexpr Fps kRefreshRate = 120_Hz;
+ const VsyncSource vsyncSource(kRefreshRate.getPeriod(), kRefreshRate.getPeriod(),
+ frameBeginTime + 5ms);
+ const Frame frame(this, VsyncId{123}, frameBeginTime, kFrameDuration, kRefreshRate,
+ kRefreshRate, Frame::fenceSignaled, vsyncSource);
+
+ EXPECT_EQ(target().expectedPresentTime(), vsyncSource.vsyncDeadline + vsyncSource.vsyncPeriod);
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsync) {
+ VsyncId vsyncId{111};
+ TimePoint frameBeginTime(1000ms);
+ constexpr Fps kRefreshRate = 60_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+ constexpr Duration kFrameDuration = 13ms;
+
+ for (int n = 5; n-- > 0;) {
+ Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+ const auto fence = frame.end();
+
+ EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - kPeriod);
+ EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), fence);
+ }
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAhead) {
+ VsyncId vsyncId{222};
+ TimePoint frameBeginTime(2000ms);
+ constexpr Fps kRefreshRate = 120_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+ constexpr Duration kFrameDuration = 10ms;
+
+ FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+ for (int n = 5; n-- > 0;) {
+ Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate, kRefreshRate);
+ const auto fence = frame.end();
+
+ EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
+ EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
+
+ previousFence = fence;
+ }
+}
+
+TEST_F(FrameTargeterTest, recallsPastVsyncTwoVsyncsAheadVrr) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+
+ VsyncId vsyncId{222};
+ TimePoint frameBeginTime(2000ms);
+ constexpr Fps kRefreshRate = 120_Hz;
+ constexpr Fps kPeakRefreshRate = 240_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+ constexpr Duration kFrameDuration = 10ms;
+
+ FenceTimePtr previousFence = FenceTime::NO_FENCE;
+
+ for (int n = 5; n-- > 0;) {
+ Frame frame(this, vsyncId++, frameBeginTime, kFrameDuration, kRefreshRate,
+ kPeakRefreshRate);
+ const auto fence = frame.end();
+
+ EXPECT_EQ(target().pastVsyncTime(kPeriod), frameBeginTime + kFrameDuration - 2 * kPeriod);
+ EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), previousFence);
+
+ previousFence = fence;
+ }
+}
+
+TEST_F(FrameTargeterTest, doesNotDetectEarlyPresentIfNoFence) {
+ constexpr Period kPeriod = (60_Hz).getPeriod();
+ EXPECT_EQ(target().presentFenceForPastVsync(kPeriod), FenceTime::NO_FENCE);
+ EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresent) {
+ VsyncId vsyncId{333};
+ TimePoint frameBeginTime(3000ms);
+ constexpr Fps kRefreshRate = 60_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+ // The target is not early while past present fences are pending.
+ for (int n = 3; n-- > 0;) {
+ const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+ }
+
+ // The target is early if the past present fence was signaled.
+ Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ const auto fence = frame.end();
+ fence->signalForTest(frameBeginTime.ns());
+
+ EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentTwoVsyncsAhead) {
+ VsyncId vsyncId{444};
+ TimePoint frameBeginTime(4000ms);
+ constexpr Fps kRefreshRate = 120_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+ // The target is not early while past present fences are pending.
+ for (int n = 3; n-- > 0;) {
+ const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+ }
+
+ Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ const auto fence = frame.end();
+ fence->signalForTest(frameBeginTime.ns());
+
+ // The target is two VSYNCs ahead, so the past present fence is still pending.
+ EXPECT_FALSE(target().wouldPresentEarly(kPeriod));
+
+ { const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate); }
+
+ // The target is early if the past present fence was signaled.
+ EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsEarlyPresentThreeVsyncsAhead) {
+ TimePoint frameBeginTime(5000ms);
+ constexpr Fps kRefreshRate = 144_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+ const Frame frame(this, VsyncId{555}, frameBeginTime, 16ms, kRefreshRate, kRefreshRate);
+
+ // The target is more than two VSYNCs ahead, but present fences are not tracked that far back.
+ EXPECT_TRUE(target().wouldPresentEarly(kPeriod));
+}
+
+TEST_F(FrameTargeterTest, detectsMissedFrames) {
+ VsyncId vsyncId{555};
+ TimePoint frameBeginTime(5000ms);
+ constexpr Fps kRefreshRate = 60_Hz;
+ constexpr Period kPeriod = kRefreshRate.getPeriod();
+
+ EXPECT_FALSE(target().isFramePending());
+ EXPECT_FALSE(target().didMissFrame());
+ EXPECT_FALSE(target().didMissHwcFrame());
+
+ {
+ const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ EXPECT_FALSE(target().isFramePending());
+
+ // The frame did not miss if the past present fence is invalid.
+ EXPECT_FALSE(target().didMissFrame());
+ EXPECT_FALSE(target().didMissHwcFrame());
+ }
+ {
+ Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate,
+ Frame::fencePending);
+ EXPECT_TRUE(target().isFramePending());
+
+ // The frame missed if the past present fence is pending.
+ EXPECT_TRUE(target().didMissFrame());
+ EXPECT_TRUE(target().didMissHwcFrame());
+
+ frame.end(CompositionCoverage::Gpu);
+ }
+ {
+ const Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate,
+ Frame::fencePending);
+ EXPECT_TRUE(target().isFramePending());
+
+ // The GPU frame missed if the past present fence is pending.
+ EXPECT_TRUE(target().didMissFrame());
+ EXPECT_FALSE(target().didMissHwcFrame());
+ }
+ {
+ Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ EXPECT_FALSE(target().isFramePending());
+
+ const auto fence = frame.end();
+ const auto expectedPresentTime = target().expectedPresentTime();
+ fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2 + 1);
+ }
+ {
+ Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ EXPECT_FALSE(target().isFramePending());
+
+ const auto fence = frame.end();
+ const auto expectedPresentTime = target().expectedPresentTime();
+ fence->signalForTest(expectedPresentTime.ns() + kPeriod.ns() / 2);
+
+ // The frame missed if the past present fence was signaled but not within slop.
+ EXPECT_TRUE(target().didMissFrame());
+ EXPECT_TRUE(target().didMissHwcFrame());
+ }
+ {
+ Frame frame(this, vsyncId++, frameBeginTime, 10ms, kRefreshRate, kRefreshRate);
+ EXPECT_FALSE(target().isFramePending());
+
+ // The frame did not miss if the past present fence was signaled within slop.
+ EXPECT_FALSE(target().didMissFrame());
+ EXPECT_FALSE(target().didMissHwcFrame());
+ }
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
new file mode 100644
index 0000000000..df2ea83112
--- /dev/null
+++ b/services/surfaceflinger/Scheduler/tests/PresentLatencyTrackerTest.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 <gtest/gtest.h>
+
+#include <algorithm>
+#include <array>
+
+#include <scheduler/PresentLatencyTracker.h>
+#include <ui/FenceTime.h>
+
+namespace android::scheduler {
+
+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] = fenceMap.makePendingFenceForTest();
+ 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<FenceToFenceTimeMap::FencePair, PresentLatencyTracker::kMaxPendingFrames> fences;
+ std::generate(fences.begin(), fences.end(),
+ [&fenceMap] { return fenceMap.makePendingFenceForTest(); });
+
+ // 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<nsecs_t>(i));
+ }
+
+ const auto fence = fenceMap.makePendingFenceForTest();
+
+ // ...then the present latency is measured using the latest frame.
+ constexpr Duration kPresentLatency = Duration::fromNs(static_cast<nsecs_t>(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 0000000000..dd03366bcc
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureOutput.cpp
@@ -0,0 +1,132 @@
+/*
+ * 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 "ui/Rotation.h"
+
+#include <compositionengine/CompositionEngine.h>
+#include <compositionengine/DisplayColorProfileCreationArgs.h>
+#include <compositionengine/impl/DisplayColorProfile.h>
+#include <ui/Rotation.h>
+
+namespace android {
+
+std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs args) {
+ std::shared_ptr<ScreenCaptureOutput> output = compositionengine::impl::createOutputTemplated<
+ ScreenCaptureOutput, compositionengine::CompositionEngine, const RenderArea&,
+ const compositionengine::Output::ColorProfile&,
+ bool>(args.compositionEngine, args.renderArea, args.colorProfile, args.regionSampling,
+ args.dimInGammaSpaceForEnhancedScreenshots);
+ output->editState().isSecure = args.renderArea.isSecure();
+ output->editState().isProtected = args.isProtected;
+ output->setCompositionEnabled(true);
+ output->setLayerFilter({args.layerStack});
+ output->setRenderSurface(std::make_unique<ScreenCaptureRenderSurface>(std::move(args.buffer)));
+ output->setDisplayBrightness(args.sdrWhitePointNits, args.displayBrightnessNits);
+ output->editState().clientTargetBrightness = args.targetBrightness;
+ output->editState().treat170mAsSrgb = args.treat170mAsSrgb;
+
+ output->setDisplayColorProfile(std::make_unique<compositionengine::impl::DisplayColorProfile>(
+ compositionengine::DisplayColorProfileCreationArgsBuilder()
+ .setHasWideColorGamut(true)
+ .Build()));
+
+ const Rect& sourceCrop = args.renderArea.getSourceCrop();
+ const ui::Rotation orientation = ui::ROTATION_0;
+ output->setDisplaySize({sourceCrop.getWidth(), sourceCrop.getHeight()});
+ output->setProjection(orientation, sourceCrop,
+ {args.renderArea.getReqWidth(), args.renderArea.getReqHeight()});
+
+ {
+ 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, bool dimInGammaSpaceForEnhancedScreenshots)
+ : mRenderArea(renderArea),
+ mColorProfile(colorProfile),
+ mRegionSampling(regionSampling),
+ mDimInGammaSpaceForEnhancedScreenshots(dimInGammaSpaceForEnhancedScreenshots) {}
+
+void ScreenCaptureOutput::updateColorProfile(const compositionengine::CompositionRefreshArgs&) {
+ auto& outputState = editState();
+ outputState.dataspace = mColorProfile.dataspace;
+ outputState.renderIntent = mColorProfile.renderIntent;
+}
+
+renderengine::DisplaySettings ScreenCaptureOutput::generateClientCompositionDisplaySettings(
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer) const {
+ auto clientCompositionDisplay =
+ compositionengine::impl::Output::generateClientCompositionDisplaySettings(buffer);
+ clientCompositionDisplay.clip = mRenderArea.getSourceCrop();
+
+ auto renderIntent = static_cast<ui::RenderIntent>(clientCompositionDisplay.renderIntent);
+ if (mDimInGammaSpaceForEnhancedScreenshots && renderIntent != ui::RenderIntent::COLORIMETRIC &&
+ renderIntent != ui::RenderIntent::TONE_MAP_COLORIMETRIC) {
+ clientCompositionDisplay.dimmingStage =
+ aidl::android::hardware::graphics::composer3::DimmingStage::GAMMA_OETF;
+ }
+
+ return clientCompositionDisplay;
+}
+
+std::vector<compositionengine::LayerFE::LayerSettings>
+ScreenCaptureOutput::generateClientCompositionRequests(
+ bool supportsProtectedContent, ui::Dataspace outputDataspace,
+ std::vector<compositionengine::LayerFE*>& 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<int32_t>(ui::Dataspace::TRANSFER_HLG) &&
+ transfer != static_cast<int32_t>(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<float>(sourceCrop.left), static_cast<float>(sourceCrop.top),
+ static_cast<float>(sourceCrop.right), static_cast<float>(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 0000000000..069f458bdb
--- /dev/null
+++ b/services/surfaceflinger/ScreenCaptureOutput.h
@@ -0,0 +1,74 @@
+/*
+ * 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 <compositionengine/DisplayColorProfile.h>
+#include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/Output.h>
+#include <ui/Rect.h>
+
+#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<renderengine::ExternalTexture> buffer;
+ float sdrWhitePointNits;
+ float displayBrightnessNits;
+ // Counterintuitively, when targetBrightness > 1.0 then dim the scene.
+ float targetBrightness;
+ bool regionSampling;
+ bool treat170mAsSrgb;
+ bool dimInGammaSpaceForEnhancedScreenshots;
+ bool isProtected = false;
+};
+
+// 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, bool dimInGammaSpaceForEnhancedScreenshots);
+
+ void updateColorProfile(const compositionengine::CompositionRefreshArgs&) override;
+
+ std::vector<compositionengine::LayerFE::LayerSettings> generateClientCompositionRequests(
+ bool supportsProtectedContent, ui::Dataspace outputDataspace,
+ std::vector<compositionengine::LayerFE*>& outLayerFEs) override;
+
+protected:
+ bool getSkipColorTransform() const override { return false; }
+ renderengine::DisplaySettings generateClientCompositionDisplaySettings(
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer) const override;
+
+private:
+ const RenderArea& mRenderArea;
+ const compositionengine::Output::ColorProfile& mColorProfile;
+ const bool mRegionSampling;
+ const bool mDimInGammaSpaceForEnhancedScreenshots;
+};
+
+std::shared_ptr<ScreenCaptureOutput> createScreenCaptureOutput(ScreenCaptureOutputArgs);
+
+} // namespace android
diff --git a/services/surfaceflinger/ScreenCaptureRenderSurface.h b/services/surfaceflinger/ScreenCaptureRenderSurface.h
new file mode 100644
index 0000000000..50ba9bff3f
--- /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 <memory>
+
+#include <compositionengine/RenderSurface.h>
+#include <renderengine/impl/ExternalTexture.h>
+#include <ui/Fence.h>
+#include <ui/Size.h>
+
+namespace android {
+
+// ScreenCaptureRenderSurface is a RenderSurface that returns a preallocated buffer used by
+// ScreenCaptureOutput.
+class ScreenCaptureRenderSurface : public compositionengine::RenderSurface {
+public:
+ ScreenCaptureRenderSurface(std::shared_ptr<renderengine::ExternalTexture> buffer)
+ : mBuffer(std::move(buffer)){};
+
+ std::shared_ptr<renderengine::ExternalTexture> dequeueBuffer(
+ base::unique_fd* /* bufferFence */) override {
+ return mBuffer;
+ }
+
+ void queueBuffer(base::unique_fd readyFence, float) override {
+ mRenderFence = sp<Fence>::make(readyFence.release());
+ }
+
+ const sp<Fence>& 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<renderengine::ExternalTexture> mBuffer;
+
+ sp<Fence> mRenderFence = Fence::NO_FENCE;
+
+ ui::Size mSize;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlinger.cpp b/services/surfaceflinger/SurfaceFlinger.cpp
index 76c7b9e1a5..b980a65255 100644
--- a/services/surfaceflinger/SurfaceFlinger.cpp
+++ b/services/surfaceflinger/SurfaceFlinger.cpp
@@ -24,16 +24,17 @@
#include "SurfaceFlinger.h"
+#include <aidl/android/hardware/power/Boost.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include <android/configuration.h>
#include <android/gui/IDisplayEventConnection.h>
+#include <android/gui/StaticDisplayInfo.h>
#include <android/hardware/configstore/1.0/ISurfaceFlingerConfigs.h>
#include <android/hardware/configstore/1.1/ISurfaceFlingerConfigs.h>
#include <android/hardware/configstore/1.1/types.h>
-#include <android/hardware/power/Boost.h>
#include <android/native_window.h>
#include <android/os/IInputFlinger.h>
#include <binder/IPCThreadState.h>
@@ -43,18 +44,23 @@
#include <compositionengine/CompositionRefreshArgs.h>
#include <compositionengine/Display.h>
#include <compositionengine/DisplayColorProfile.h>
+#include <compositionengine/DisplayColorProfileCreationArgs.h>
#include <compositionengine/DisplayCreationArgs.h>
#include <compositionengine/LayerFECompositionState.h>
#include <compositionengine/OutputLayer.h>
#include <compositionengine/RenderSurface.h>
+#include <compositionengine/impl/DisplayColorProfile.h>
#include <compositionengine/impl/OutputCompositionState.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
#include <configstore/Utils.h>
#include <cutils/compiler.h>
#include <cutils/properties.h>
+#include <ftl/algorithm.h>
+#include <ftl/concat.h>
#include <ftl/fake_guard.h>
#include <ftl/future.h>
-#include <ftl/small_map.h>
+#include <ftl/unit.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/BufferQueue.h>
#include <gui/DebugEGLImageTracker.h>
#include <gui/IProducerListener.h>
@@ -62,18 +68,20 @@
#include <gui/LayerMetadata.h>
#include <gui/LayerState.h>
#include <gui/Surface.h>
+#include <gui/SurfaceComposerClient.h>
#include <gui/TraceUtils.h>
#include <hidl/ServiceManagement.h>
#include <layerproto/LayerProtoParser.h>
+#include <linux/sched/types.h>
#include <log/log.h>
#include <private/android_filesystem_config.h>
#include <private/gui/SyncFeatures.h>
#include <processgroup/processgroup.h>
#include <renderengine/RenderEngine.h>
#include <renderengine/impl/ExternalTexture.h>
+#include <scheduler/FrameTargeter.h>
#include <sys/types.h>
#include <ui/ColorSpace.h>
-#include <ui/DataspaceUtils.h>
#include <ui/DebugUtils.h>
#include <ui/DisplayId.h>
#include <ui/DisplayMode.h>
@@ -81,34 +89,39 @@
#include <ui/DisplayState.h>
#include <ui/DynamicDisplayInfo.h>
#include <ui/GraphicBufferAllocator.h>
+#include <ui/HdrRenderTypeUtils.h>
+#include <ui/LayerStack.h>
#include <ui/PixelFormat.h>
#include <ui/StaticDisplayInfo.h>
+#include <unistd.h>
#include <utils/StopWatch.h>
#include <utils/String16.h>
#include <utils/String8.h>
#include <utils/Timers.h>
#include <utils/misc.h>
-
#include <algorithm>
#include <cerrno>
#include <cinttypes>
#include <cmath>
#include <cstdint>
+#include <filesystem>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
+#include <string>
#include <type_traits>
#include <unordered_map>
+#include <vector>
+#include <common/FlagManager.h>
+#include <gui/LayerStatePermissions.h>
+#include <gui/SchedulingPolicy.h>
#include <ui/DisplayIdentification.h>
#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 "DisplayDevice.h"
#include "DisplayHardware/ComposerHal.h"
#include "DisplayHardware/FramebufferSurface.h"
@@ -117,33 +130,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/LayerLog.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 <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
@@ -154,13 +168,20 @@
#define NO_THREAD_SAFETY_ANALYSIS \
_Pragma("GCC error \"Prefer <ftl/fake_guard.h> or MutexUtils.h helpers.\"")
-namespace android {
+// 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 +190,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 +261,111 @@ 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.peakRefreshRate);
+ return outMode.resolution.getWidth() >= FOUR_K_WIDTH &&
+ outMode.resolution.getHeight() >= FOUR_K_HEIGHT && refreshRate > 30_Hz;
+}
+
+void excludeDolbyVisionIf4k30Present(const std::vector<ui::Hdr>& 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<ui::Hdr> 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> surfaceControl) {
+ if (!surfaceControl) {
+ return UNASSIGNED_LAYER_ID;
+ }
+ return LayerHandle::getLayerId(surfaceControl->getHandle());
+}
+
+/**
+ * Returns true if the file at path exists and is newer than duration.
+ */
+bool fileNewerThan(const std::string& path, std::chrono::minutes duration) {
+ using Clock = std::filesystem::file_time_type::clock;
+ std::error_code error;
+ std::filesystem::file_time_type updateTime = std::filesystem::last_write_time(path, error);
+ if (error) {
+ return false;
+ }
+ return duration > (Clock::now() - updateTime);
+}
+
+bool isFrameIntervalOnCadence(TimePoint expectedPresentTime, TimePoint lastExpectedPresentTimestamp,
+ Fps lastFrameInterval, Period timeout, Duration threshold) {
+ if (lastFrameInterval.getPeriodNsecs() == 0) {
+ return false;
+ }
+
+ const auto expectedPresentTimeDeltaNs =
+ expectedPresentTime.ns() - lastExpectedPresentTimestamp.ns();
+
+ if (expectedPresentTimeDeltaNs > timeout.ns()) {
+ return false;
+ }
+
+ const auto expectedPresentPeriods = static_cast<nsecs_t>(
+ std::round(static_cast<float>(expectedPresentTimeDeltaNs) /
+ static_cast<float>(lastFrameInterval.getPeriodNsecs())));
+ const auto calculatedPeriodsOutNs = lastFrameInterval.getPeriodNsecs() * expectedPresentPeriods;
+ const auto calculatedExpectedPresentTimeNs =
+ lastExpectedPresentTimestamp.ns() + calculatedPeriodsOutNs;
+ const auto presentTimeDelta =
+ std::abs(expectedPresentTime.ns() - calculatedExpectedPresentTimeNs);
+ return presentTimeDelta < threshold.ns();
+}
+
+bool isExpectedPresentWithinTimeout(TimePoint expectedPresentTime,
+ TimePoint lastExpectedPresentTimestamp,
+ std::optional<Period> timeoutOpt, Duration threshold) {
+ if (!timeoutOpt) {
+ // Always within timeout if timeoutOpt is absent and don't send hint
+ // for the timeout
+ return true;
+ }
+
+ if (timeoutOpt->ns() == 0) {
+ // Always outside timeout if timeoutOpt is 0 and always send
+ // the hint for the timeout.
+ return false;
+ }
+
+ if (expectedPresentTime.ns() < lastExpectedPresentTimestamp.ns() + timeoutOpt->ns()) {
+ return true;
+ }
+
+ // Check if within the threshold as it can be just outside the timeout
+ return std::abs(expectedPresentTime.ns() -
+ (lastExpectedPresentTimestamp.ns() + timeoutOpt->ns())) < threshold.ns();
+}
} // namespace anonymous
// ---------------------------------------------------------------------------
@@ -270,6 +378,7 @@ 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";
@@ -278,9 +387,9 @@ int64_t SurfaceFlinger::dispSyncPresentTimeOffset;
bool SurfaceFlinger::useHwcForRgbToYuv;
bool SurfaceFlinger::hasSyncFramework;
int64_t SurfaceFlinger::maxFrameBufferAcquiredBuffers;
+int64_t SurfaceFlinger::minAcquiredBuffers = 1;
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,41 +411,35 @@ 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<impl::TimeStats>()),
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<TunnelModeEnabledReporter>::make()),
mEmulatedDisplayDensity(getDensityFromProperty("qemu.sf.lcd_density", false)),
mInternalDisplayDensity(
getDensityFromProperty("ro.sf.lcd_density", !mEmulatedDisplayDensity)),
mPowerAdvisor(std::make_unique<Hwc2::impl::PowerAdvisor>(*this)),
- mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make(*this)) {
+ mWindowInfosListenerInvoker(sp<WindowInfosListenerInvoker>::make()) {
ALOGI("Using HWComposer service: %s", mHwcServiceName.c_str());
}
SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipInitialization) {
+ ATRACE_CALL();
ALOGI("SurfaceFlinger is starting");
hasSyncFramework = running_without_sync_framework(true);
@@ -346,15 +449,17 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI
useHwcForRgbToYuv = force_hwc_copy_for_virtual_displays(false);
maxFrameBufferAcquiredBuffers = max_frame_buffer_acquired_buffers(2);
+ minAcquiredBuffers =
+ SurfaceFlingerProperties::min_acquired_buffers().value_or(minAcquiredBuffers);
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<ui::Dataspace>(default_composition_dataspace(Dataspace::V0_SRGB));
mWideColorGamutCompositionDataspace = static_cast<ui::Dataspace>(wcg_composition_dataspace(
- hasWideColorDisplay ? Dataspace::DISPLAY_P3 : Dataspace::V0_SRGB));
+ mSupportsWideColor ? Dataspace::DISPLAY_P3 : Dataspace::V0_SRGB));
defaultCompositionDataspace = mDefaultCompositionDataspace;
wideColorGamutCompositionDataspace = mWideColorGamutCompositionDataspace;
defaultCompositionPixelFormat = static_cast<ui::PixelFormat>(
@@ -362,9 +467,6 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI
wideColorGamutCompositionPixelFormat =
static_cast<ui::PixelFormat>(wcg_composition_pixel_format(ui::PixelFormat::RGBA_8888));
- mColorSpaceAgnosticDataspace =
- static_cast<ui::Dataspace>(color_space_agnostic_dataspace(Dataspace::UNKNOWN));
-
mLayerCachingEnabled = [] {
const bool enable =
android::sysprop::SurfaceFlingerProperties::enable_layer_caching().value_or(false);
@@ -378,32 +480,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 +513,12 @@ SurfaceFlinger::SurfaceFlinger(Factory& factory) : SurfaceFlinger(factory, SkipI
property_get("debug.sf.treat_170m_as_sRGB", value, "0");
mTreat170mAsSrgb = atoi(value);
+ property_get("debug.sf.dim_in_gamma_in_enhanced_screenshots", value, 0);
+ mDimInGammaSpaceForEnhancedScreenshots = 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,25 +534,27 @@ 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();
+ mLayerTracing.setTransactionTracing(*mTransactionTracing);
}
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, true);
+ 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;
}
@@ -470,13 +568,13 @@ void SurfaceFlinger::binderDied(const wp<IBinder>&) {
// the window manager died on us. prepare its eulogy.
mBootFinished = false;
- // Sever the link to inputflinger since it's gone as well.
- static_cast<void>(mScheduler->schedule([=] { mInputFlinger = nullptr; }));
+ static_cast<void>(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 +582,8 @@ void SurfaceFlinger::run() {
mScheduler->run();
}
-sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() {
- const sp<Client> client = new Client(this);
- return client->initCheck() == NO_ERROR ? client : nullptr;
-}
-
-sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secure) {
+sp<IBinder> 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 +598,7 @@ sp<IBinder> 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<IBinder>::fromExisting(this));
flinger->setTransactionFlags(eDisplayTransactionNeeded);
}
public:
@@ -513,15 +607,18 @@ sp<IBinder> SurfaceFlinger::createDisplay(const String8& displayName, bool secur
}
};
- sp<BBinder> token = new DisplayToken(this);
+ sp<BBinder> token = sp<DisplayToken>::make(sp<SurfaceFlinger>::fromExisting(this));
Mutex::Autolock _l(mStateLock);
// Display ID is assigned when virtual display is allocated by HWC.
DisplayDeviceState state;
state.isSecure = secure;
+ // Set display as protected when marked as secure to ensure no behavior change
+ // TODO (b/314820005): separate as a different arg when creating the display.
+ state.isProtected = secure;
state.displayName = displayName;
+ state.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
mCurrentState.displays.add(token, state);
- mInterceptor->saveDisplayCreation(state);
return token;
}
@@ -539,7 +636,6 @@ void SurfaceFlinger::destroyDisplay(const sp<IBinder>& displayToken) {
ALOGE("%s: Invalid operation on physical display", __func__);
return;
}
- mInterceptor->saveDisplayDeletion(state.sequenceId);
mCurrentState.displays.removeItemsAt(index);
setTransactionFlags(eDisplayTransactionNeeded);
}
@@ -591,12 +687,12 @@ void SurfaceFlinger::releaseVirtualDisplay(VirtualDisplayId displayId) {
std::vector<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdsLocked() const {
std::vector<PhysicalDisplayId> 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 +701,10 @@ std::vector<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdsLocked() con
return displayIds;
}
-status_t SurfaceFlinger::getPrimaryPhysicalDisplayId(PhysicalDisplayId* id) const {
- Mutex::Autolock lock(mStateLock);
- *id = getPrimaryDisplayIdLocked();
- return NO_ERROR;
+std::optional<PhysicalDisplayId> SurfaceFlinger::getPhysicalDisplayIdLocked(
+ const sp<display::DisplayToken>& displayToken) const {
+ return ftl::find_if(mPhysicalDisplays, PhysicalDisplay::hasToken(displayToken))
+ .transform(&ftl::to_key<PhysicalDisplays>);
}
sp<IBinder> SurfaceFlinger::getPhysicalDisplayToken(PhysicalDisplayId displayId) const {
@@ -616,20 +712,12 @@ sp<IBinder> SurfaceFlinger::getPhysicalDisplayToken(PhysicalDisplayId displayId)
return getPhysicalDisplayTokenLocked(displayId);
}
-status_t SurfaceFlinger::getColorManagement(bool* outGetColorManagement) const {
- if (!outGetColorManagement) {
- return BAD_VALUE;
- }
- *outGetColorManagement = useColorManagement;
- return NO_ERROR;
-}
-
HWComposer& SurfaceFlinger::getHwComposer() const {
return mCompositionEngine->getHwComposer();
}
renderengine::RenderEngine& SurfaceFlinger::getRenderEngine() const {
- return mCompositionEngine->getRenderEngine();
+ return *mRenderEngine;
}
compositionengine::CompositionEngine& SurfaceFlinger::getCompositionEngine() const {
@@ -642,6 +730,7 @@ void SurfaceFlinger::bootFinished() {
return;
}
mBootFinished = true;
+ FlagManager::getMutableInstance().markBootCompleted();
if (mStartPropertySetThread->join() != NO_ERROR) {
ALOGE("Join StartPropertySetThread failed!");
}
@@ -655,13 +744,13 @@ void SurfaceFlinger::bootFinished() {
mFrameTracer->initialize();
mFrameTimeline->onBootFinished();
- getRenderEngine().setEnableTracing(mFlagManager.use_skia_tracing());
+ getRenderEngine().setEnableTracing(FlagManager::getInstance().use_skia_tracing());
// wait patiently for the window manager death
const String16 name("window");
- mWindowManager = defaultServiceManager()->getService(name);
+ mWindowManager = defaultServiceManager()->waitForService(name);
if (mWindowManager != 0) {
- mWindowManager->linkToDeath(static_cast<IBinder::DeathRecipient*>(this));
+ mWindowManager->linkToDeath(sp<IBinder::DeathRecipient>::fromExisting(this));
}
// stop boot animation
@@ -673,9 +762,9 @@ void SurfaceFlinger::bootFinished() {
LOG_EVENT_LONG(LOGTAG_SF_STOP_BOOTANIM,
ns2ms(systemTime(SYSTEM_TIME_MONOTONIC)));
- sp<IBinder> input(defaultServiceManager()->getService(String16("inputflinger")));
+ sp<IBinder> input(defaultServiceManager()->waitForService(String16("inputflinger")));
- static_cast<void>(mScheduler->schedule([=] {
+ static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(kMainThreadContext) {
if (input == nullptr) {
ALOGE("Failed to link to input service");
} else {
@@ -683,81 +772,48 @@ void SurfaceFlinger::bootFinished() {
}
readPersistentProperties();
+ const bool hintSessionEnabled = FlagManager::getInstance().use_adpf_cpu_hint();
+ mPowerAdvisor->enablePowerHintSession(hintSessionEnabled);
+ const bool hintSessionUsed = mPowerAdvisor->usePowerHintSession();
+ // Ordering is important here, as onBootFinished signals to PowerAdvisor that concurrency
+ // is safe because its variables are initialized.
mPowerAdvisor->onBootFinished();
- const bool powerHintEnabled = mFlagManager.use_adpf_cpu_hint();
- mPowerAdvisor->enablePowerHint(powerHintEnabled);
- const bool powerHintUsed = mPowerAdvisor->usePowerHintSession();
ALOGD("Power hint is %s",
- powerHintUsed ? "supported" : (powerHintEnabled ? "unsupported" : "disabled"));
- if (powerHintUsed) {
+ hintSessionUsed ? "supported" : (hintSessionEnabled ? "unsupported" : "disabled"));
+ if (hintSessionUsed) {
std::optional<pid_t> renderEngineTid = getRenderEngine().getRenderEngineTid();
std::vector<int32_t> tidList;
tidList.emplace_back(gettid());
if (renderEngineTid.has_value()) {
tidList.emplace_back(*renderEngineTid);
}
- if (!mPowerAdvisor->startPowerHintSession(tidList)) {
+ if (!mPowerAdvisor->startPowerHintSession(std::move(tidList))) {
ALOGW("Cannot start power hint session");
}
}
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);
}
}));
}
-uint32_t SurfaceFlinger::getNewTexture() {
- {
- std::lock_guard lock(mTexturePoolMutex);
- if (!mTexturePool.empty()) {
- uint32_t name = mTexturePool.back();
- mTexturePool.pop_back();
- ATRACE_INT("TexturePoolSize", mTexturePool.size());
- return name;
- }
-
- // The pool was too small, so increase it for the future
- ++mTexturePoolSize;
- }
-
- // The pool was empty, so we need to get a new texture name directly using a
- // blocking call to the main thread
- auto genTextures = [this] {
- uint32_t name = 0;
- getRenderEngine().genTextures(1, &name);
- return name;
- };
- if (std::this_thread::get_id() == mMainThreadId) {
- return genTextures();
- } else {
- return mScheduler->schedule(genTextures).get();
- }
-}
-
-void SurfaceFlinger::deleteTextureAsync(uint32_t texture) {
- std::lock_guard lock(mTexturePoolMutex);
- // We don't change the pool size, so the fix-up logic in postComposition will decide whether
- // to actually delete this or not based on mTexturePoolSize
- mTexturePool.push_back(texture);
- ATRACE_INT("TexturePoolSize", mTexturePool.size());
-}
-
static std::optional<renderengine::RenderEngine::RenderEngineType>
chooseRenderEngineTypeViaSysProp() {
char prop[PROPERTY_VALUE_MAX];
- property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "");
+ property_get(PROPERTY_DEBUG_RENDERENGINE_BACKEND, prop, "skiaglthreaded");
- if (strcmp(prop, "gles") == 0) {
- return renderengine::RenderEngine::RenderEngineType::GLES;
- } else if (strcmp(prop, "threaded") == 0) {
- return renderengine::RenderEngine::RenderEngineType::THREADED;
- } else if (strcmp(prop, "skiagl") == 0) {
+ if (strcmp(prop, "skiagl") == 0) {
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 +822,12 @@ 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) {
+ ATRACE_CALL();
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.
@@ -777,7 +835,6 @@ void SurfaceFlinger::init() {
auto builder = renderengine::RenderEngineCreationArgs::Builder()
.setPixelFormat(static_cast<int32_t>(defaultCompositionPixelFormat))
.setImageCacheSize(maxFrameBufferAcquiredBuffers)
- .setUseColorManagerment(useColorManagement)
.setEnableProtectedContext(enable_protected_contents(false))
.setPrecacheToneMapperShaderOnly(false)
.setSupportsBackgroundBlur(mSupportsBlur)
@@ -788,7 +845,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,38 +866,72 @@ 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<const DisplayDevice> 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);
+
+ mLayerTracing.setTakeLayersSnapshotProtoFunction([&](uint32_t traceFlags) {
+ auto snapshot = perfetto::protos::LayersSnapshotProto{};
+ mScheduler
+ ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+ snapshot = takeLayersSnapshotProto(traceFlags, TimePoint::now(),
+ mLastCommittedVsyncId, true);
+ })
+ .wait();
+ return snapshot;
+ });
+
+ // 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<void>(mScheduler->schedule(
+ [this]() FTL_FAKE_GUARD(kMainThreadContext) { initializeDisplays(); }));
mPowerAdvisor->init();
- char primeShaderCache[PROPERTY_VALUE_MAX];
- property_get("service.sf.prime_shader_cache", primeShaderCache, "1");
- if (atoi(primeShaderCache)) {
+ if (base::GetBoolProperty("service.sf.prime_shader_cache"s, true)) {
if (setSchedFifo(false) != NO_ERROR) {
ALOGW("Can't set SCHED_OTHER for primeCache");
}
- mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache();
+ bool shouldPrimeUltraHDR =
+ base::GetBoolProperty("ro.surface_flinger.prime_shader_cache.ultrahdr"s, false);
+ mRenderEnginePrimeCacheFuture = getRenderEngine().primeCache(shouldPrimeUltraHDR);
if (setSchedFifo(true) != NO_ERROR) {
- ALOGW("Can't set SCHED_OTHER for primeCache");
+ ALOGW("Can't set SCHED_FIFO after primeCache");
}
}
- onActiveDisplaySizeChanged(display);
-
// Inform native graphics APIs whether the present timestamp is supported:
const bool presentFenceReliable =
@@ -850,9 +942,32 @@ void SurfaceFlinger::init() {
ALOGE("Run StartPropertySetThread failed!");
}
+ initTransactionTraceWriter();
ALOGV("Done initializing");
}
+void SurfaceFlinger::initTransactionTraceWriter() {
+ if (!mTransactionTracing) {
+ return;
+ }
+ TransactionTraceWriter::getInstance().setWriterFunction(
+ [&](const std::string& filename, bool overwrite) {
+ auto writeFn = [&]() {
+ if (!overwrite && fileNewerThan(filename, std::chrono::minutes{10})) {
+ ALOGD("TransactionTraceWriter: file=%s already exists", filename.c_str());
+ return;
+ }
+ mTransactionTracing->flush();
+ mTransactionTracing->writeToFile(filename);
+ };
+ if (std::this_thread::get_id() == mMainThreadId) {
+ writeFn();
+ } else {
+ mScheduler->schedule(writeFn).get();
+ }
+ });
+}
+
void SurfaceFlinger::readPersistentProperties() {
Mutex::Autolock _l(mStateLock);
@@ -866,8 +981,8 @@ void SurfaceFlinger::readPersistentProperties() {
property_get("persist.sys.sf.native_mode", value, "0");
mDisplayColorSetting = static_cast<DisplayColorSetting>(atoi(value));
- property_get("persist.sys.sf.color_mode", value, "0");
- mForceColorMode = static_cast<ColorMode>(atoi(value));
+ mForceColorMode =
+ static_cast<ui::ColorMode>(base::GetIntProperty("persist.sys.sf.color_mode"s, 0));
}
void SurfaceFlinger::startBootAnim() {
@@ -882,17 +997,6 @@ void SurfaceFlinger::startBootAnim() {
// ----------------------------------------------------------------------------
-bool SurfaceFlinger::authenticateSurfaceTexture(
- const sp<IGraphicBufferProducer>& bufferProducer) const {
- Mutex::Autolock _l(mStateLock);
- return authenticateSurfaceTextureLocked(bufferProducer);
-}
-
-bool SurfaceFlinger::authenticateSurfaceTextureLocked(
- const sp<IGraphicBufferProducer>& /* bufferProducer */) const {
- return false;
-}
-
status_t SurfaceFlinger::getSupportedFrameTimestamps(
std::vector<FrameEvent>* outSupported) const {
*outSupported = {
@@ -936,24 +1040,24 @@ status_t SurfaceFlinger::getDisplayState(const sp<IBinder>& displayToken, ui::Di
return NO_ERROR;
}
-status_t SurfaceFlinger::getStaticDisplayInfo(const sp<IBinder>& 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<PhysicalDisplayId>(static_cast<uint64_t>(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 +1069,19 @@ status_t SurfaceFlinger::getStaticDisplayInfo(const sp<IBinder>& 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<IBinder>& 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<DisplayDevice>& 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<int32_t>(id.value());
@@ -1013,11 +1099,12 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp<IBinder>& displayToken,
outMode.xDpi = xDpi;
outMode.yDpi = yDpi;
- const nsecs_t period = mode->getVsyncPeriod();
- outMode.refreshRate = Fps::fromPeriodNsecs(period).getValue();
+ const auto peakFps = mode->getPeakFps();
+ outMode.peakRefreshRate = peakFps.getValue();
+ outMode.vsyncRate = mode->getVsyncRate().getValue();
- const auto vsyncConfigSet =
- mVsyncConfiguration->getConfigsForRefreshRate(Fps::fromValue(outMode.refreshRate));
+ const auto vsyncConfigSet = mVsyncConfiguration->getConfigsForRefreshRate(
+ Fps::fromValue(outMode.peakRefreshRate));
outMode.appVsyncOffset = vsyncConfigSet.late.appOffset;
outMode.sfVsyncOffset = vsyncConfigSet.late.sfOffset;
outMode.group = mode->getGroup();
@@ -1034,218 +1121,326 @@ status_t SurfaceFlinger::getDynamicDisplayInfo(const sp<IBinder>& 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;
-
+ outMode.presentationDeadline = peakFps.getPeriodNsecs() - 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<ui::DisplayModeId>(-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<PhysicalDisplayId>(static_cast<uint64_t>(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<IBinder>&, DisplayStatInfo* stats) {
- if (!stats) {
+status_t SurfaceFlinger::getDynamicDisplayInfoFromToken(const sp<IBinder>& 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<PhysicalDisplays>)
+ .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) {
- ATRACE_CALL();
+status_t SurfaceFlinger::getDisplayStats(const sp<IBinder>& displayToken,
+ DisplayStatInfo* outStats) {
+ if (!outStats) {
+ return BAD_VALUE;
+ }
- if (!info.mode) {
- ALOGW("requested display mode is null");
- return;
+ std::optional<PhysicalDisplayId> 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;
+ }
}
- auto display = getDisplayDeviceLocked(info.mode->getPhysicalDisplayId());
+
+ 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;
+ }
+ outStats->vsyncTime = schedule->vsyncDeadlineAfter(TimePoint::now()).ns();
+ outStats->vsyncPeriod = schedule->period().ns();
+ return NO_ERROR;
+}
+
+void SurfaceFlinger::setDesiredMode(display::DisplayModeRequest&& request, bool force) {
+ const auto displayId = request.mode.modePtr->getPhysicalDisplayId();
+ ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+
+ const auto display = getDisplayDeviceLocked(displayId);
if (!display) {
ALOGW("%s: display is no longer valid", __func__);
return;
}
- if (display->setDesiredActiveMode(info)) {
- scheduleComposite(FrameHint::kNone);
+ const auto mode = request.mode;
+ const bool emitEvent = request.emitEvent;
+
+ switch (display->setDesiredMode(std::move(request), force)) {
+ case DisplayDevice::DesiredModeAction::InitiateDisplayModeSwitch:
+ // DisplayDevice::setDesiredMode updated the render rate, so inform Scheduler.
+ 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.get());
- // 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(info.mode->getFps());
- mScheduler->setModeChangePending(true);
+ if (displayId == mActiveDisplayId) {
+ updatePhaseConfiguration(mode.fps);
+ }
+
+ mScheduler->setModeChangePending(true);
+ break;
+ case DisplayDevice::DesiredModeAction::InitiateRenderRateSwitch:
+ mScheduler->setRenderRate(displayId, mode.fps);
+
+ if (displayId == mActiveDisplayId) {
+ updatePhaseConfiguration(mode.fps);
+ mRefreshRateStats->setRefreshRate(mode.fps);
+ }
+
+ if (emitEvent) {
+ dispatchDisplayModeChangeEvent(displayId, mode);
+ }
+ break;
+ case DisplayDevice::DesiredModeAction::None:
+ break;
}
}
-status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<IBinder>& displayToken, int modeId) {
+status_t SurfaceFlinger::setActiveModeFromBackdoor(const sp<display::DisplayToken>& displayToken,
+ DisplayModeId modeId, Fps minFps, Fps maxFps) {
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<PhysicalDisplays>)
+ .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->getPeakFps(); });
- 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;
+ const FpsRange physical = {fps, fps};
+ const FpsRange render = {minFps.isValid() ? minFps : fps, maxFps.isValid() ? maxFps : fps};
+ const FpsRanges ranges = {physical, render};
+
// 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, ranges, ranges,
+ allowGroupSwitching};
+
+ return setDesiredDisplayModeSpecsInternal(display, policy);
});
return future.get();
}
-void SurfaceFlinger::updateInternalStateWithChangedMode() {
- ATRACE_CALL();
-
- const auto display = getDefaultDisplayDeviceLocked();
- if (!display) {
- return;
- }
+void SurfaceFlinger::finalizeDisplayModeChange(DisplayDevice& display) {
+ const auto displayId = display.getPhysicalId();
+ ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
- const auto upcomingModeInfo =
- FTL_FAKE_GUARD(kMainThreadContext, display->getUpcomingActiveMode());
-
- if (!upcomingModeInfo.mode) {
+ const auto pendingModeOpt = display.getPendingMode();
+ if (!pendingModeOpt) {
// 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()) {
- auto& state = mCurrentState.displays.editValueFor(display->getDisplayToken());
+ const auto& activeMode = pendingModeOpt->mode;
+
+ if (display.getActiveMode().modePtr->getResolution() != activeMode.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 = activeMode.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());
+ display.finalizeModeChange(activeMode.modePtr->getId(), activeMode.modePtr->getVsyncRate(),
+ activeMode.fps);
- const Fps refreshRate = upcomingModeInfo.mode->getFps();
- mRefreshRateStats->setRefreshRate(refreshRate);
- updatePhaseConfiguration(refreshRate);
+ if (displayId == mActiveDisplayId) {
+ mRefreshRateStats->setRefreshRate(activeMode.fps);
+ updatePhaseConfiguration(activeMode.fps);
+ }
- if (upcomingModeInfo.event != DisplayModeEvent::None) {
- mScheduler->onPrimaryDisplayModeChanged(mAppConnectionHandle, upcomingModeInfo.mode);
+ if (pendingModeOpt->emitEvent) {
+ dispatchDisplayModeChangeEvent(displayId, activeMode);
}
}
-void SurfaceFlinger::clearDesiredActiveModeState(const sp<DisplayDevice>& display) {
- display->clearDesiredActiveModeState();
- if (isDisplayActiveLocked(display)) {
+void SurfaceFlinger::dropModeRequest(const sp<DisplayDevice>& display) {
+ display->clearDesiredMode();
+ if (display->getPhysicalId() == mActiveDisplayId) {
+ // TODO(b/255635711): Check for pending mode changes on other displays.
mScheduler->setModeChangePending(false);
}
}
-void SurfaceFlinger::desiredActiveModeChangeDone(const sp<DisplayDevice>& display) {
- const auto refreshRate = display->getDesiredActiveMode()->mode->getFps();
- clearDesiredActiveModeState(display);
- mScheduler->resyncToHardwareVsync(true, refreshRate);
- updatePhaseConfiguration(refreshRate);
+void SurfaceFlinger::applyActiveMode(const sp<DisplayDevice>& display) {
+ const auto activeModeOpt = display->getDesiredMode();
+ auto activeModePtr = activeModeOpt->mode.modePtr;
+ const auto displayId = activeModePtr->getPhysicalDisplayId();
+ const auto renderFps = activeModeOpt->mode.fps;
+
+ dropModeRequest(display);
+
+ constexpr bool kAllowToEnable = true;
+ mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, std::move(activeModePtr).take());
+ mScheduler->setRenderRate(displayId, renderFps);
+
+ if (displayId == mActiveDisplayId) {
+ updatePhaseConfiguration(renderFps);
+ }
}
-void SurfaceFlinger::setActiveModeInHwcIfNeeded() {
+void SurfaceFlinger::initiateDisplayModeChanges() {
ATRACE_CALL();
std::optional<PhysicalDisplayId> displayToUpdateImmediately;
- for (const auto& iter : mDisplays) {
- const auto& display = iter.second;
- if (!display || !display->isInternal()) {
- continue;
- }
+ for (const auto& [id, physical] : mPhysicalDisplays) {
+ 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
+ auto desiredModeOpt = display->getDesiredMode();
+ if (!desiredModeOpt) {
continue;
}
- if (!isDisplayActiveLocked(display)) {
- // display is no longer the active display, so abort the mode change
- clearDesiredActiveModeState(display);
+ if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
+ dropModeRequest(display);
continue;
}
- const auto desiredMode = display->getMode(desiredActiveMode->mode->getId());
- if (!desiredMode) {
+ const auto desiredModeId = desiredModeOpt->mode.modePtr->getId();
+ const auto displayModePtrOpt = physical.snapshot().displayModes().get(desiredModeId);
+
+ if (!displayModePtrOpt) {
ALOGW("Desired display mode is no longer supported. Mode ID = %d",
- desiredActiveMode->mode->getId().value());
- clearDesiredActiveModeState(display);
+ desiredModeId.value());
+ dropModeRequest(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()->getVsyncRate()).c_str(),
to_string(display->getId()).c_str());
- if (display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) {
- // we are already in the requested mode, there is nothing left to do
- desiredActiveModeChangeDone(display);
+ if (display->getActiveMode() == desiredModeOpt->mode) {
+ applyActiveMode(display);
continue;
}
// Desired active mode was set, it is different than the mode currently in use, however
// allowed modes might have changed by the time we process the refresh.
// Make sure the desired mode is still allowed
- const auto displayModeAllowed =
- display->refreshRateConfigs().isModeAllowed(desiredActiveMode->mode->getId());
- if (!displayModeAllowed) {
- clearDesiredActiveModeState(display);
+ if (!display->refreshRateSelector().isModeAllowed(desiredModeOpt->mode)) {
+ dropModeRequest(display);
continue;
}
@@ -1255,37 +1450,30 @@ void SurfaceFlinger::setActiveModeInHwcIfNeeded() {
constraints.seamlessRequired = false;
hal::VsyncPeriodChangeTimeline outTimeline;
- const auto status = FTL_FAKE_GUARD(kMainThreadContext,
- display->initiateModeChange(*desiredActiveMode,
- constraints, &outTimeline));
-
- if (status != NO_ERROR) {
- // initiateModeChange may fail if a hotplug event is just about
- // to be sent. We just log the error in this case.
- ALOGW("initiateModeChange failed: %d", status);
+ if (!display->initiateModeChange(std::move(*desiredModeOpt), constraints, outTimeline)) {
continue;
}
+
+ display->refreshRateSelector().onModeChangeInitiated();
mScheduler->onNewVsyncPeriodChangeTimeline(outTimeline);
if (outTimeline.refreshRequired) {
scheduleComposite(FrameHint::kNone);
- mSetActiveModePending = true;
} else {
- // Updating the internal state should be done outside the loop,
- // because it can recreate a DisplayDevice and modify mDisplays
- // which will invalidate the iterator.
+ // TODO(b/255635711): Remove `displayToUpdateImmediately` to `finalizeDisplayModeChange`
+ // for all displays. This was only needed when the loop iterated over `mDisplays` rather
+ // than `mPhysicalDisplays`.
displayToUpdateImmediately = display->getPhysicalId();
}
}
if (displayToUpdateImmediately) {
- updateInternalStateWithChangedMode();
-
const auto display = getDisplayDeviceLocked(*displayToUpdateImmediately);
- const auto desiredActiveMode = display->getDesiredActiveMode();
- if (desiredActiveMode &&
- display->getActiveMode()->getId() == desiredActiveMode->mode->getId()) {
- desiredActiveModeChangeDone(display);
+ finalizeDisplayModeChange(*display);
+
+ const auto desiredModeOpt = display->getDesiredMode();
+ if (desiredModeOpt && display->getActiveMode() == desiredModeOpt->mode) {
+ applyActiveMode(display);
}
}
}
@@ -1305,22 +1493,6 @@ void SurfaceFlinger::disableExpensiveRendering() {
future.wait();
}
-std::vector<ColorMode> 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<IBinder>& displayToken,
ui::DisplayPrimaries& primaries) {
if (!displayToken) {
@@ -1329,13 +1501,13 @@ status_t SurfaceFlinger::getDisplayNativePrimaries(const sp<IBinder>& 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<PhysicalDisplays>);
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,36 +1516,37 @@ status_t SurfaceFlinger::getDisplayNativePrimaries(const sp<IBinder>& displayTok
return NO_ERROR;
}
-status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& displayToken, ColorMode mode) {
+status_t SurfaceFlinger::setActiveColorMode(const sp<IBinder>& 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<PhysicalDisplays>)
+ .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;
}
display->getCompositionDisplay()->setColorProfile(
- {mode, Dataspace::UNKNOWN, RenderIntent::COLORIMETRIC, Dataspace::UNKNOWN});
+ {mode, Dataspace::UNKNOWN, RenderIntent::COLORIMETRIC});
return NO_ERROR;
});
@@ -1391,30 +1564,67 @@ status_t SurfaceFlinger::getBootDisplayModeSupport(bool* outSupport) const {
return NO_ERROR;
}
-status_t SurfaceFlinger::setBootDisplayMode(const sp<IBinder>& 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<int32_t> pixelFormats;
+ pixelFormats.reserve(combination.pixelFormats.size());
+ std::transform(combination.pixelFormats.cbegin(), combination.pixelFormats.cend(),
+ std::back_inserter(pixelFormats),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ std::vector<int32_t> standards;
+ standards.reserve(combination.standards.size());
+ std::transform(combination.standards.cbegin(), combination.standards.cend(),
+ std::back_inserter(standards),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ std::vector<int32_t> transfers;
+ transfers.reserve(combination.transfers.size());
+ std::transform(combination.transfers.cbegin(), combination.transfers.cend(),
+ std::back_inserter(transfers),
+ [](const auto& val) { return static_cast<int32_t>(val); });
+ std::vector<int32_t> ranges;
+ ranges.reserve(combination.ranges.size());
+ std::transform(combination.ranges.cbegin(), combination.ranges.cend(),
+ std::back_inserter(ranges),
+ [](const auto& val) { return static_cast<int32_t>(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<display::DisplayToken>& 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<PhysicalDisplays>)
+ .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 +1642,92 @@ status_t SurfaceFlinger::clearBootDisplayMode(const sp<IBinder>& displayToken) {
return future.get();
}
+status_t SurfaceFlinger::getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* 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<int>(capability.sourceType);
+ tempCapability.outputType = static_cast<int>(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<AidlHdrConversionStrategy::Tag::passthrough>(
+ hdrConversionStrategy.get<GuiHdrConversionStrategyTag::passthrough>());
+ status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy,
+ &aidlPreferredHdrOutputType);
+ *outPreferredHdrOutputType = static_cast<int32_t>(aidlPreferredHdrOutputType);
+ return status;
+ }
+ case GuiHdrConversionStrategyTag::autoAllowedHdrTypes: {
+ auto autoHdrTypes =
+ hdrConversionStrategy
+ .get<GuiHdrConversionStrategyTag::autoAllowedHdrTypes>();
+ std::vector<aidl::android::hardware::graphics::common::Hdr> aidlAutoHdrTypes;
+ for (auto type : autoHdrTypes) {
+ aidlAutoHdrTypes.push_back(
+ static_cast<aidl::android::hardware::graphics::common::Hdr>(type));
+ }
+ aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::autoAllowedHdrTypes>(
+ aidlAutoHdrTypes);
+ status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy,
+ &aidlPreferredHdrOutputType);
+ *outPreferredHdrOutputType = static_cast<int32_t>(aidlPreferredHdrOutputType);
+ return status;
+ }
+ case GuiHdrConversionStrategyTag::forceHdrConversion: {
+ auto forceHdrConversion =
+ hdrConversionStrategy
+ .get<GuiHdrConversionStrategyTag::forceHdrConversion>();
+ aidlConversionStrategy.set<AidlHdrConversionStrategy::Tag::forceHdrConversion>(
+ static_cast<aidl::android::hardware::graphics::common::Hdr>(
+ forceHdrConversion));
+ status = getHwComposer().setHdrConversionStrategy(aidlConversionStrategy,
+ &aidlPreferredHdrOutputType);
+ *outPreferredHdrOutputType = static_cast<int32_t>(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<IBinder>& displayToken, bool on) {
const char* const whence = __func__;
static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
@@ -1455,18 +1751,6 @@ void SurfaceFlinger::setGameContentType(const sp<IBinder>& 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<IBinder>& displayToken,
const std::vector<ui::Hdr>& hdrTypes) {
Mutex::Autolock lock(mStateLock);
@@ -1482,7 +1766,8 @@ status_t SurfaceFlinger::overrideHdrTypes(const sp<IBinder>& 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<uint8_t>* pulledData,
+ bool* success) {
*success = mTimeStats->onPullAtom(atomId, pulledData);
return NO_ERROR;
}
@@ -1557,34 +1842,11 @@ status_t SurfaceFlinger::isWideColorDisplay(const sp<IBinder>& 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<LayerDebugInfo>* outLayers) {
+status_t SurfaceFlinger::getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) {
outLayers->clear();
auto future = mScheduler->schedule([=] {
const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
@@ -1615,8 +1877,12 @@ status_t SurfaceFlinger::addRegionSamplingListener(const Rect& samplingArea,
return BAD_VALUE;
}
- const wp<Layer> 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<Layer> stopLayer = LayerHandle::getLayer(stopLayerHandle);
+ mRegionSamplingThread->addListener(samplingArea,
+ stopLayer ? stopLayer->getSequence() : UNASSIGNED_LAYER_ID,
+ listener);
return NO_ERROR;
}
@@ -1681,17 +1947,6 @@ status_t SurfaceFlinger::getDisplayBrightnessSupport(const sp<IBinder>& displayT
return NO_ERROR;
}
-bool SurfaceFlinger::hasVisibleHdrLayer(const sp<DisplayDevice>& 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<IBinder>& displayToken,
const gui::DisplayBrightness& brightness) {
if (!displayToken) {
@@ -1711,10 +1966,28 @@ status_t SurfaceFlinger::setDisplayBrightness(const sp<IBinder>& displayToken,
float currentDimmingRatio =
compositionDisplay->editState().sdrWhitePointNits /
compositionDisplay->editState().displayBrightnessNits;
- compositionDisplay->setDisplayBrightness(brightness.sdrWhitePointNits,
- brightness.displayBrightnessNits);
+ static constexpr float kDimmingThreshold = 0.02f;
+ if (brightness.sdrWhitePointNits == 0.f ||
+ abs(brightness.sdrWhitePointNits - brightness.displayBrightnessNits) /
+ brightness.sdrWhitePointNits >=
+ kDimmingThreshold) {
+ // to optimize, skip brightness setter if the brightness difference ratio
+ // is lower than threshold
+ compositionDisplay
+ ->setDisplayBrightness(brightness.sdrWhitePointNits,
+ brightness.displayBrightnessNits);
+ } else {
+ compositionDisplay->setDisplayBrightness(brightness.sdrWhitePointNits,
+ brightness.sdrWhitePointNits);
+ }
+
FTL_FAKE_GUARD(kMainThreadContext,
display->stageBrightness(brightness.displayBrightness));
+ float currentHdrSdrRatio =
+ compositionDisplay->editState().displayBrightnessNits /
+ compositionDisplay->editState().sdrWhitePointNits;
+ FTL_FAKE_GUARD(kMainThreadContext,
+ display->updateHdrSdrRatioOverlayRatio(currentHdrSdrRatio));
if (brightness.sdrWhitePointNits / brightness.displayBrightnessNits !=
currentDimmingRatio) {
@@ -1786,7 +2059,7 @@ status_t SurfaceFlinger::removeHdrLayerInfoListener(
}
status_t SurfaceFlinger::notifyPowerBoost(int32_t boostId) {
- using hardware::power::Boost;
+ using aidl::android::hardware::power::Boost;
Boost powerBoost = static_cast<Boost>(boostId);
if (powerBoost == Boost::INTERACTION) {
@@ -1816,19 +2089,21 @@ status_t SurfaceFlinger::getDisplayDecorationSupport(
// ----------------------------------------------------------------------------
sp<IDisplayEventConnection> SurfaceFlinger::createDisplayEventConnection(
- ISurfaceComposer::VsyncSource vsyncSource,
- ISurfaceComposer::EventRegistrationFlags eventRegistration) {
+ gui::ISurfaceComposer::VsyncSource vsyncSource, EventRegistrationFlags eventRegistration,
+ const sp<IBinder>& 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 +2131,53 @@ nsecs_t SurfaceFlinger::getVsyncPeriodFromHWC() const {
void SurfaceFlinger::onComposerHalVsync(hal::HWDisplayId hwcDisplayId, int64_t timestamp,
std::optional<hal::VsyncPeriodNanos> vsyncPeriod) {
- const std::string tracePeriod = [vsyncPeriod]() {
- if (ATRACE_ENABLED() && vsyncPeriod) {
- std::stringstream ss;
- ss << "(" << *vsyncPeriod << ")";
- return ss.str();
+ if (FlagManager::getInstance().connected_display()) {
+ // use ~0 instead of -1 as AidlComposerHal.cpp passes the param as unsigned int32
+ if (mIsHotplugErrViaNegVsync && timestamp < 0 && vsyncPeriod.has_value() &&
+ vsyncPeriod.value() == ~0) {
+ int hotplugErrorCode = static_cast<int32_t>(-timestamp);
+ ALOGD("SurfaceFlinger got hotplugErrorCode=%d", hotplugErrorCode);
+ mScheduler->onHotplugConnectionError(mAppConnectionHandle, hotplugErrorCode);
+ return;
}
- return std::string();
- }();
- ATRACE_FORMAT("onComposerHalVsync%s", tracePeriod.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;
- }
+ ATRACE_NAME(vsyncPeriod
+ ? ftl::Concat(__func__, ' ', hwcDisplayId, ' ', *vsyncPeriod, "ns").c_str()
+ : ftl::Concat(__func__, ' ', hwcDisplayId).c_str());
- 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);
+ Mutex::Autolock lock(mStateLock);
+ 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<std::mutex> 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);
+void SurfaceFlinger::onComposerHalHotplugEvent(hal::HWDisplayId hwcDisplayId,
+ DisplayHotplugEvent event) {
+ if (event == DisplayHotplugEvent::CONNECTED || event == DisplayHotplugEvent::DISCONNECTED) {
+ hal::Connection connection = (event == DisplayHotplugEvent::CONNECTED)
+ ? hal::Connection::CONNECTED
+ : hal::Connection::DISCONNECTED;
+ {
+ std::lock_guard<std::mutex> lock(mHotplugMutex);
+ mPendingHotplugEvents.push_back(HotplugEvent{hwcDisplayId, connection});
+ }
- mPendingHotplugEvents.emplace_back(HotplugEvent{hwcDisplayId, connection});
+ if (mScheduler) {
+ mScheduler->scheduleConfigure();
+ }
- if (std::this_thread::get_id() == mMainThreadId) {
- // Process all pending hot plug events immediately if we are on the main thread.
- processDisplayHotplugEventsLocked();
+ return;
}
- setTransactionFlags(eDisplayTransactionNeeded);
+ if (FlagManager::getInstance().hotplug2()) {
+ ALOGD("SurfaceFlinger got hotplug event=%d", static_cast<int32_t>(event));
+ // TODO(b/311403559): use enum type instead of int
+ mScheduler->onHotplugConnectionError(mAppConnectionHandle, static_cast<int32_t>(event));
+ }
}
void SurfaceFlinger::onComposerHalVsyncPeriodTimingChanged(
@@ -1943,237 +2205,424 @@ 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 char* const whence = __func__;
+ static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
+ const Fps fps = Fps::fromPeriodNsecs(getHwComposer().getComposer()->isVrrSupported()
+ ? data.refreshPeriodNanos
+ : data.vsyncPeriodNanos);
+ ATRACE_FORMAT("%s Fps %d", whence, fps.getIntValue());
+ const auto display = getDisplayDeviceLocked(*displayId);
+ FTL_FAKE_GUARD(kMainThreadContext,
+ display->updateRefreshRateOverlayRate(fps, display->getActiveMode().fps,
+ /* setByHwc */ true));
+ }));
+ }
+}
- // On main thread to avoid race conditions with display power state.
- static_cast<void>(mScheduler->schedule([=]() FTL_FAKE_GUARD(mStateLock) {
- mHWCVsyncPendingState = enabled ? hal::Vsync::ENABLE : hal::Vsync::DISABLE;
+void SurfaceFlinger::configure() {
+ Mutex::Autolock lock(mStateLock);
+ if (configureLocked()) {
+ setTransactionFlags(eDisplayTransactionNeeded);
+ }
+}
- if (const auto display = getDefaultDisplayDeviceLocked();
- display && display->isPoweredOn()) {
- setHWCVsyncEnabled(display->getPhysicalId(), mHWCVsyncPendingState);
+bool SurfaceFlinger::updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs,
+ bool flushTransactions,
+ bool& outTransactionsAreEmpty) {
+ ATRACE_CALL();
+ frontend::Update update;
+ if (flushTransactions) {
+ update = flushLifecycleUpdates();
+ if (mTransactionTracing) {
+ mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId), frameTimeNs,
+ update, mFrontEndDisplayInfos,
+ mFrontEndDisplayInfosChanged);
}
- }));
+ }
+
+ bool needsTraversal = false;
+ if (flushTransactions) {
+ needsTraversal |= commitMirrorDisplays(vsyncId);
+ needsTraversal |= commitCreatedLayers(vsyncId, update.layerCreatedStates);
+ needsTraversal |= applyTransactions(update.transactions, vsyncId);
+ }
+ outTransactionsAreEmpty = !needsTraversal;
+ const bool shouldCommit = (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
+ if (shouldCommit) {
+ commitTransactionsLegacy();
+ }
+
+ bool mustComposite = latchBuffers() || shouldCommit;
+ updateLayerGeometry();
+ return mustComposite;
}
-SurfaceFlinger::FenceWithFenceTime SurfaceFlinger::previousFrameFence() {
- const auto now = systemTime();
- const auto vsyncPeriod = mScheduler->getDisplayStatInfo(now).vsyncPeriod;
- const bool expectedPresentTimeIsTheNextVsync = mExpectedPresentTime - now <= vsyncPeriod;
+void SurfaceFlinger::updateLayerHistory(nsecs_t now) {
+ for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+ using Changes = frontend::RequestedLayerState::Changes;
+ if (snapshot->path.isClone()) {
+ continue;
+ }
+
+ const bool updateSmallDirty = FlagManager::getInstance().enable_small_area_detection() &&
+ ((snapshot->clientChanges & layer_state_t::eSurfaceDamageRegionChanged) ||
+ snapshot->changes.any(Changes::Geometry));
+
+ const bool hasChanges =
+ snapshot->changes.any(Changes::FrameRate | Changes::Buffer | Changes::Animation |
+ Changes::Geometry | Changes::Visibility) ||
+ (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) !=
+ 0;
+
+ if (!updateSmallDirty && !hasChanges) {
+ continue;
+ }
+
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+ "Couldn't find layer object for %s",
+ snapshot->getDebugString().c_str());
+
+ if (updateSmallDirty) {
+ // Update small dirty flag while surface damage region or geometry changed
+ it->second->setIsSmallDirty(snapshot.get());
+ }
+
+ if (!hasChanges) {
+ continue;
+ }
+
+ const auto layerProps = scheduler::LayerProps{
+ .visible = snapshot->isVisible,
+ .bounds = snapshot->geomLayerBounds,
+ .transform = snapshot->geomLayerTransform,
+ .setFrameRateVote = snapshot->frameRate,
+ .frameRateSelectionPriority = snapshot->frameRateSelectionPriority,
+ .isSmallDirty = snapshot->isSmallDirty,
+ .isFrontBuffered = snapshot->isFrontBuffered(),
+ };
+
+ if (snapshot->changes.any(Changes::Geometry | Changes::Visibility)) {
+ mScheduler->setLayerProperties(snapshot->sequence, layerProps);
+ }
+
+ if (snapshot->clientChanges & layer_state_t::eDefaultFrameRateCompatibilityChanged) {
+ mScheduler->setDefaultFrameRateCompatibility(snapshot->sequence,
+ snapshot->defaultFrameRateCompatibility);
+ }
+
+ if (snapshot->changes.test(Changes::Animation)) {
+ it->second->recordLayerHistoryAnimationTx(layerProps, now);
+ }
- size_t shift = 0;
- if (!expectedPresentTimeIsTheNextVsync) {
- shift = static_cast<size_t>((mExpectedPresentTime - now) / vsyncPeriod);
- if (shift >= mPreviousPresentFences.size()) {
- shift = mPreviousPresentFences.size() - 1;
+ if (snapshot->changes.test(Changes::FrameRate)) {
+ it->second->setFrameRateForLayerTree(snapshot->frameRate, layerProps, now);
+ }
+
+ if (snapshot->changes.test(Changes::Buffer)) {
+ it->second->recordLayerHistoryBufferUpdate(layerProps, now);
}
}
- ATRACE_FORMAT("previousFrameFence shift=%zu", shift);
- return mPreviousPresentFences[shift];
}
-bool SurfaceFlinger::previousFramePending(int graceTimeMs) {
+bool SurfaceFlinger::updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs,
+ bool flushTransactions, bool& outTransactionsAreEmpty) {
+ using Changes = frontend::RequestedLayerState::Changes;
ATRACE_CALL();
- const std::shared_ptr<FenceTime>& fence = previousFrameFence().fenceTime;
+ frontend::Update update;
+ if (flushTransactions) {
+ 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, collect the transactions
+ // before committing the created layers.
+ // 3. Transactions can only be flushed after adding layers, since the layer can be a newly
+ // created one
+ mTransactionHandler.collectTransactions();
+ {
+ // TODO(b/238781169) lockless queue this and keep order.
+ std::scoped_lock<std::mutex> 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();
+ }
+
+ mLayerLifecycleManager.addLayers(std::move(update.newLayers));
+ update.transactions = mTransactionHandler.flushTransactions();
+ if (mTransactionTracing) {
+ mTransactionTracing->addCommittedTransactions(ftl::to_underlying(vsyncId), frameTimeNs,
+ update, mFrontEndDisplayInfos,
+ mFrontEndDisplayInfosChanged);
+ }
+ mLayerLifecycleManager.applyTransactions(update.transactions);
+ mLayerLifecycleManager.onHandlesDestroyed(update.destroyedHandles);
+ for (auto& legacyLayer : update.layerCreatedStates) {
+ sp<Layer> 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());
+ }
+ }
- if (fence == FenceTime::NO_FENCE) {
- return false;
+ // Keep a copy of the drawing state (that is going to be overwritten
+ // by commitTransactionsLocked) outside of mStateLock so that the side
+ // effects of the State assignment don't happen with mStateLock held,
+ // which can cause deadlocks.
+ State drawingState(mDrawingState);
+ Mutex::Autolock lock(mStateLock);
+ bool mustComposite = false;
+ mustComposite |= applyAndCommitDisplayTransactionStatesLocked(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(),
+ .skipRoundCornersWhenProtected =
+ !getRenderEngine().supportsProtectedContent()};
+ 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 | Changes::Geometry)) {
+ mVisibleRegionsDirty = true;
+ }
+ if (mLayerLifecycleManager.getGlobalChanges().any(Changes::Hierarchy | Changes::FrameRate)) {
+ // The frame rate of attached choreographers can only change as a result of a
+ // FrameRate change (including when Hierarchy changes).
+ mUpdateAttachedChoreographer = true;
}
+ outTransactionsAreEmpty = mLayerLifecycleManager.getGlobalChanges().get() == 0;
+ mustComposite |= mLayerLifecycleManager.getGlobalChanges().get() != 0;
- const status_t status = fence->wait(graceTimeMs);
- // This is the same as Fence::Status::Unsignaled, but it saves a getStatus() call,
- // which calls wait(0) again internally
- return status == -ETIME;
-}
+ bool newDataLatched = false;
+ if (!mLegacyFrontEndEnabled) {
+ ATRACE_NAME("DisplayCallbackAndStatsUpdates");
+ mustComposite |= applyTransactionsLocked(update.transactions, vsyncId);
+ traverseLegacyLayers([&](Layer* layer) { layer->commitTransaction(); });
+ const nsecs_t latchTime = systemTime();
+ bool unused = false;
+
+ for (auto& layer : mLayerLifecycleManager.getLayers()) {
+ if (layer->changes.test(frontend::RequestedLayerState::Changes::Created) &&
+ layer->bgColorLayer) {
+ sp<Layer> bgColorLayer = getFactory().createEffectLayer(
+ LayerCreationArgs(this, nullptr, layer->name,
+ ISurfaceComposerClient::eFXSurfaceEffect, LayerMetadata(),
+ std::make_optional(layer->id), true));
+ mLegacyLayers[bgColorLayer->sequence] = bgColorLayer;
+ }
+ const bool willReleaseBufferOnLatch = layer->willReleaseBufferOnLatch();
+
+ auto it = mLegacyLayers.find(layer->id);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+ "Couldnt find layer object for %s",
+ layer->getDebugString().c_str());
+ if (!layer->hasReadyFrame() && !willReleaseBufferOnLatch) {
+ if (!it->second->hasBuffer()) {
+ // The last latch time is used to classify a missed frame as buffer stuffing
+ // instead of a missed frame. This is used to identify scenarios where we
+ // could not latch a buffer or apply a transaction due to backpressure.
+ // We only update the latch time for buffer less layers here, the latch time
+ // is updated for buffer layers when the buffer is latched.
+ it->second->updateLastLatchTime(latchTime);
+ }
+ continue;
+ }
+
+ const bool bgColorOnly =
+ !layer->externalTexture && (layer->bgColorLayerId != UNASSIGNED_LAYER_ID);
+ if (willReleaseBufferOnLatch) {
+ mLayersWithBuffersRemoved.emplace(it->second);
+ }
+ it->second->latchBufferImpl(unused, latchTime, bgColorOnly);
+ newDataLatched = true;
-nsecs_t SurfaceFlinger::previousFramePresentTime() {
- const std::shared_ptr<FenceTime>& fence = previousFrameFence().fenceTime;
+ mLayersWithQueuedFrames.emplace(it->second);
+ mLayersIdsWithQueuedFrames.emplace(it->second->sequence);
+ }
- if (fence == FenceTime::NO_FENCE) {
- return Fence::SIGNAL_TIME_INVALID;
- }
+ updateLayerHistory(latchTime);
+ mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+ if (mLayersIdsWithQueuedFrames.find(snapshot.path.id) ==
+ mLayersIdsWithQueuedFrames.end())
+ return;
+ Region visibleReg;
+ visibleReg.set(snapshot.transformedBoundsWithoutTransparentRegion);
+ invalidateLayerStack(snapshot.outputFilter, visibleReg);
+ });
- return fence->getSignalTime();
-}
+ for (auto& destroyedLayer : mLayerLifecycleManager.getDestroyedLayers()) {
+ mLegacyLayers.erase(destroyedLayer->id);
+ }
-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;
-}
+ {
+ ATRACE_NAME("LLM:commitChanges");
+ mLayerLifecycleManager.commitChanges();
+ }
-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);
+ // enter boot animation on first buffer latch
+ if (CC_UNLIKELY(mBootStage == BootStage::BOOTLOADER && newDataLatched)) {
+ ALOGI("Enter boot animation");
+ mBootStage = BootStage::BOOTANIMATION;
+ }
+ }
+ mustComposite |= (getTransactionFlags() & ~eTransactionFlushNeeded) || newDataLatched;
+ if (mustComposite && !mLegacyFrontEndEnabled) {
+ commitTransactions();
}
- const nsecs_t lastScheduledPresentTime = mScheduledPresentTime;
- mScheduledPresentTime = expectedVsyncTime;
+ return mustComposite;
+}
- const auto vsyncIn = [&] {
- if (!ATRACE_ENABLED()) return 0.f;
- return (mExpectedPresentTime - systemTime()) / 1e6f;
- }();
- ATRACE_FORMAT("%s %" PRId64 " vsyncIn %.2fms%s", __func__, vsyncId, vsyncIn,
- mExpectedPresentTime == expectedVsyncTime ? "" : " (adjusted)");
-
- // When Backpressure propagation is enabled we want to give a small grace period
- // 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;
-
- // Pending frames may trigger backpressure propagation.
- const TracedOrdinal<bool> framePending = {"PrevFramePending",
- previousFramePending(graceTimeForPresentFenceMs)};
-
- // Frame missed counts for metrics tracking.
- // A frame is missed if the prior frame is still pending. If no longer pending,
- // then we still count the frame as missed if the predicted present time
- // was further in the past than when the fence actually fired.
-
- // 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 TracedOrdinal<bool> frameMissed = {"PrevFrameMissed",
- framePending ||
- (previousPresentTime >= 0 &&
- (lastScheduledPresentTime <
- previousPresentTime - frameMissedSlop))};
- const TracedOrdinal<bool> hwcFrameMissed = {"PrevHwcFrameMissed",
- mHadDeviceComposition && frameMissed};
- const TracedOrdinal<bool> gpuFrameMissed = {"PrevGpuFrameMissed",
- mHadClientComposition && frameMissed};
-
- if (frameMissed) {
- mFrameMissedCount++;
+bool SurfaceFlinger::commit(PhysicalDisplayId pacesetterId,
+ const scheduler::FrameTargets& frameTargets) {
+ const scheduler::FrameTarget& pacesetterFrameTarget = *frameTargets.get(pacesetterId)->get();
+
+ const VsyncId vsyncId = pacesetterFrameTarget.vsyncId();
+ ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
+
+ if (pacesetterFrameTarget.didMissFrame()) {
mTimeStats->incrementMissedFrames();
}
- if (hwcFrameMissed) {
- mHwcFrameMissedCount++;
- }
+ // If a mode set is pending and the fence hasn't fired yet, wait for the next commit.
+ if (std::any_of(frameTargets.begin(), frameTargets.end(),
+ [this](const auto& pair) FTL_FAKE_GUARD(mStateLock)
+ FTL_FAKE_GUARD(kMainThreadContext) {
+ if (!pair.second->isFramePending()) return false;
+
+ if (const auto display = getDisplayDeviceLocked(pair.first)) {
+ return display->isModeSetPending();
+ }
- if (gpuFrameMissed) {
- mGpuFrameMissedCount++;
+ return false;
+ })) {
+ mScheduler->scheduleFrame();
+ return 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) {
- if (framePending) {
- mScheduler->scheduleFrame();
- return false;
- }
+ {
+ Mutex::Autolock lock(mStateLock);
- // We received the present fence from the HWC, so we assume it successfully updated
- // the mode, hence we update SF.
- mSetActiveModePending = false;
- {
- Mutex::Autolock lock(mStateLock);
- updateInternalStateWithChangedMode();
+ for (const auto [id, target] : frameTargets) {
+ // TODO(b/241285876): This is `nullptr` when the DisplayDevice is about to be removed in
+ // this commit, since the PhysicalDisplay has already been removed. Rather than checking
+ // for `nullptr` below, change Scheduler::onFrameSignal to filter out the FrameTarget of
+ // the removed display.
+ const auto display = getDisplayDeviceLocked(id);
+
+ if (display && display->isModeSetPending()) {
+ finalizeDisplayModeChange(*display);
+ }
}
}
- if (framePending) {
- if ((hwcFrameMissed && !gpuFrameMissed) || mPropagateBackpressureClientComposition) {
+ if (pacesetterFrameTarget.isFramePending()) {
+ if (mBackpressureGpuComposition || pacesetterFrameTarget.didMissHwcFrame()) {
+ if (FlagManager::getInstance().vrr_config()) {
+ mScheduler->getVsyncSchedule()->getTracker().onFrameMissed(
+ pacesetterFrameTarget.expectedPresentTime());
+ }
scheduleCommit(FrameHint::kNone);
return false;
}
}
+ const Period vsyncPeriod = mScheduler->getVsyncSchedule()->period();
+
// 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);
+ mPowerAdvisor->setCommitStart(pacesetterFrameTarget.frameBeginTime());
+ mPowerAdvisor->setExpectedPresentTime(pacesetterFrameTarget.expectedPresentTime());
- // 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 - pacesetterFrameTarget.expectedFrameDuration();
+
+ mPowerAdvisor->setFrameDelay(frameDelay);
+ mPowerAdvisor->setTotalFrameTargetWorkDuration(idealSfWorkDuration);
- if (mTracingEnabledChanged) {
- mLayerTracingEnabled = mLayerTracing.isEnabled();
- mTracingEnabledChanged = false;
+ const auto& display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
+ const Period idealVsyncPeriod = display->getActiveMode().fps.getPeriod();
+ mPowerAdvisor->updateTargetWorkDuration(idealVsyncPeriod);
}
- if (mRefreshRateOverlaySpinner) {
+ if (mRefreshRateOverlaySpinner || mHdrSdrRatioOverlay) {
Mutex::Autolock lock(mStateLock);
if (const auto display = getDefaultDisplayDeviceLocked()) {
- display->animateRefreshRateOverlay();
+ display->animateOverlay();
}
}
// Composite if transactions were committed, or if requested by HWC.
bool mustComposite = mMustComposite.exchange(false);
{
- mFrameTimeline->setSfWakeUp(vsyncId, frameTime, Fps::fromPeriodNsecs(stats.vsyncPeriod));
+ mFrameTimeline->setSfWakeUp(ftl::to_underlying(vsyncId),
+ pacesetterFrameTarget.frameBeginTime().ns(),
+ Fps::fromPeriodNsecs(vsyncPeriod.ns()),
+ mScheduler->getPacesetterRefreshRate());
- 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<TransactionState> transactions = flushTransactions();
- needsTraversal |= commitCreatedLayers();
- needsTraversal |= applyTransactions(transactions, vsyncId);
+ const bool flushTransactions = clearTransactionFlags(eTransactionFlushNeeded);
+ bool transactionsAreEmpty;
+ if (mLegacyFrontEndEnabled) {
+ mustComposite |=
+ updateLayerSnapshotsLegacy(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
+ flushTransactions, transactionsAreEmpty);
}
-
- const bool shouldCommit =
- (getTransactionFlags() & ~eTransactionFlushNeeded) || needsTraversal;
- if (shouldCommit) {
- commitTransactions();
+ if (mLayerLifecycleManagerEnabled) {
+ mustComposite |=
+ updateLayerSnapshots(vsyncId, pacesetterFrameTarget.frameBeginTime().ns(),
+ 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,57 +2630,106 @@ bool SurfaceFlinger::commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expected
// Hold mStateLock as chooseRefreshRateForContent promotes wp<Layer> to sp<Layer>
// and may eventually call to ~Layer() if it holds the last reference
{
- Mutex::Autolock _l(mStateLock);
- mScheduler->chooseRefreshRateForContent();
- setActiveModeInHwcIfNeeded();
+ bool updateAttachedChoreographer = mUpdateAttachedChoreographer;
+ mUpdateAttachedChoreographer = false;
+
+ Mutex::Autolock lock(mStateLock);
+ mScheduler->chooseRefreshRateForContent(mLayerLifecycleManagerEnabled
+ ? &mLayerHierarchyBuilder.getHierarchy()
+ : nullptr,
+ updateAttachedChoreographer);
+ initiateDisplayModeChanges();
}
updateCursorAsync();
- updateInputFlinger();
-
- if (mLayerTracingEnabled && !mLayerTracing.flagIsSet(LayerTracing::TRACE_COMPOSITION)) {
- // This will block and tracing should only be enabled for debugging.
- mLayerTracing.notify(mVisibleRegionsDirty, frameTime);
+ if (!mustComposite) {
+ updateInputFlinger(vsyncId, pacesetterFrameTarget.frameBeginTime());
}
+ doActiveLayersTracingIfNeeded(false, mVisibleRegionsDirty,
+ pacesetterFrameTarget.frameBeginTime(), vsyncId);
+
+ mLastCommittedVsyncId = vsyncId;
persistDisplayBrightness(mustComposite);
return mustComposite && CC_LIKELY(mBootStage != BootStage::BOOTLOADER);
}
-void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId)
- FTL_FAKE_GUARD(kMainThreadContext) {
- ATRACE_FORMAT("%s %" PRId64, __func__, vsyncId);
+CompositeResultsPerDisplay SurfaceFlinger::composite(
+ PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters& frameTargeters) {
+ const scheduler::FrameTarget& pacesetterTarget =
+ frameTargeters.get(pacesetterId)->get()->target();
+
+ const VsyncId vsyncId = pacesetterTarget.vsyncId();
+ ATRACE_NAME(ftl::Concat(__func__, ' ', ftl::to_underlying(vsyncId)).c_str());
compositionengine::CompositionRefreshArgs refreshArgs;
+ refreshArgs.powerCallback = this;
const auto& displays = FTL_FAKE_GUARD(mStateLock, mDisplays);
refreshArgs.outputs.reserve(displays.size());
+
+ // Add outputs for physical displays.
+ for (const auto& [id, targeter] : frameTargeters) {
+ ftl::FakeGuard guard(mStateLock);
+
+ if (const auto display = getCompositionDisplayLocked(id)) {
+ refreshArgs.outputs.push_back(display);
+ }
+ }
+
std::vector<DisplayId> displayIds;
for (const auto& [_, display] : displays) {
- refreshArgs.outputs.push_back(display->getCompositionDisplay());
displayIds.push_back(display->getId());
+ display->tracePowerMode();
+
+ // Add outputs for virtual displays.
+ if (display->isVirtual()) {
+ const Fps refreshRate = display->getAdjustedRefreshRate();
+
+ if (!refreshRate.isValid() ||
+ mScheduler->isVsyncInPhase(pacesetterTarget.frameBeginTime(), refreshRate)) {
+ refreshArgs.outputs.push_back(display->getCompositionDisplay());
+ }
+ }
}
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())
refreshArgs.layersWithQueuedFrames.push_back(layerFE);
}
- refreshArgs.outputColorSetting = useColorManagement
- ? mDisplayColorSetting
- : compositionengine::OutputColorSetting::kUnmanaged;
- refreshArgs.colorSpaceAgnosticDataspace = mColorSpaceAgnosticDataspace;
+ refreshArgs.outputColorSetting = mDisplayColorSetting;
refreshArgs.forceOutputColorMode = mForceColorMode;
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 +2743,152 @@ 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;
- refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
- refreshArgs.expectedPresentTime = expectedPresentTime;
+ const Period minFramePeriod = mScheduler->getVsyncSchedule()->minFramePeriod();
+ if (!getHwComposer().getComposer()->isSupported(
+ Hwc2::Composer::OptionalFeature::ExpectedPresentTime) &&
+ pacesetterTarget.wouldPresentEarly(minFramePeriod)) {
+ const auto hwcMinWorkDuration = mVsyncConfiguration->getCurrentConfigs().hwcMinWorkDuration;
+
+ // TODO(b/255601557): Calculate and pass per-display values for each FrameTarget.
+ refreshArgs.earliestPresentTime =
+ pacesetterTarget.previousFrameVsyncTime(minFramePeriod) - hwcMinWorkDuration;
+ }
+
+ const TimePoint expectedPresentTime = pacesetterTarget.expectedPresentTime();
+ // TODO(b/255601557) Update frameInterval per display
+ refreshArgs.frameInterval = mScheduler->getNextFrameInterval(pacesetterId, expectedPresentTime);
+ refreshArgs.scheduledFrameTime = mScheduler->getScheduledFrameTime();
+ refreshArgs.expectedPresentTime = expectedPresentTime.ns();
+ refreshArgs.hasTrustedPresentationListener = mNumTrustedPresentationListeners > 0;
+ {
+ auto& notifyExpectedPresentData = mNotifyExpectedPresentMap[pacesetterId];
+ auto lastExpectedPresentTimestamp = TimePoint::fromNs(
+ notifyExpectedPresentData.lastExpectedPresentTimestamp.load().ns());
+ if (expectedPresentTime > lastExpectedPresentTimestamp) {
+ // If the values are not same, then hint is sent with newer value.
+ // And because composition always follows the notifyExpectedPresentIfRequired, we can
+ // skip updating the lastExpectedPresentTimestamp in this case.
+ notifyExpectedPresentData.lastExpectedPresentTimestamp
+ .compare_exchange_weak(lastExpectedPresentTimestamp, expectedPresentTime);
+ }
+ }
// Store the present time just before calling to the composition engine so we could notify
// the scheduler.
const auto presentTime = systemTime();
+ constexpr bool kCursorOnly = false;
+ const auto layers = moveSnapshotsToCompositionArgs(refreshArgs, kCursorOnly);
+
+ if (mLayerLifecycleManagerEnabled && !mVisibleRegionsDirty) {
+ for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
+ auto compositionDisplay = display->getCompositionDisplay();
+ if (!compositionDisplay->getState().isEnabled) continue;
+ for (auto outputLayer : compositionDisplay->getOutputLayersOrderedByZ()) {
+ if (outputLayer->getLayerFE().getCompositionState() == nullptr) {
+ // This is unexpected but instead of crashing, capture traces to disk
+ // and recover gracefully by forcing CE to rebuild layer stack.
+ ALOGE("Output layer %s for display %s %" PRIu64 " has a null "
+ "snapshot. Forcing mVisibleRegionsDirty",
+ outputLayer->getLayerFE().getDebugName(),
+ compositionDisplay->getName().c_str(), compositionDisplay->getId().value);
+
+ TransactionTraceWriter::getInstance().invoke(__func__, /* overwrite= */ false);
+ mVisibleRegionsDirty = true;
+ refreshArgs.updatingOutputGeometryThisFrame = mVisibleRegionsDirty;
+ refreshArgs.updatingGeometryThisFrame = mVisibleRegionsDirty;
+ }
+ }
+ }
+ }
+
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(pacesetterTarget.frameBeginTime().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 = pacesetterTarget.presentFenceForPreviousFrame();
+
+ mPowerAdvisor->setSfPresentTiming(TimePoint::fromNs(previousPresentFence->getSignalTime()),
+ TimePoint::now());
+ mPowerAdvisor->reportActualWorkDuration();
}
- if (mScheduler->onPostComposition(presentTime)) {
+ if (mScheduler->onCompositionPresented(presentTime)) {
scheduleComposite(FrameHint::kNone);
}
- postFrame();
- postComposition();
+ onCompositionPresented(pacesetterId, frameTargeters, presentTime);
- const bool prevFrameHadClientComposition = mHadClientComposition;
+ const bool hadGpuComposited =
+ multiDisplayUnion(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;
+ CompositionCoverageFlags& flags =
+ mCompositionCoverage.try_emplace(display->getId()).first->second;
+
+ if (state.usesDeviceComposition) {
+ flags |= CompositionCoverage::Hwc;
+ }
+
+ if (state.reusedClientComposition) {
+ flags |= CompositionCoverage::GpuReuse;
+ } else if (state.usesClientComposition) {
+ flags |= 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 auto coverage = multiDisplayUnion(mCompositionCoverage);
+ const bool hasGpuComposited = coverage.test(CompositionCoverage::Gpu);
+
+ clientCompositionRecord.hadClientComposition = hasGpuComposited;
+ clientCompositionRecord.reused = coverage.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 =
+ coverage.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);
- }
+ mLayersIdsWithQueuedFrames.clear();
+ doActiveLayersTracingIfNeeded(true, mVisibleRegionsDirty, pacesetterTarget.frameBeginTime(),
+ vsyncId);
- mVisibleRegionsWereDirtyThisFrame = mVisibleRegionsDirty; // Cache value for use in post-comp
+ updateInputFlinger(vsyncId, pacesetterTarget.frameBeginTime());
+
+ if (mVisibleRegionsDirty) mHdrLayerInfoChanged = true;
mVisibleRegionsDirty = false;
if (mCompositionEngine->needsAnotherUpdate()) {
@@ -2315,8 +2896,19 @@ void SurfaceFlinger::composite(nsecs_t frameTime, int64_t vsyncId)
}
if (mPowerHintSessionEnabled) {
- mPowerAdvisor->setCompositeEnd(systemTime());
+ mPowerAdvisor->setCompositeEnd(TimePoint::now());
+ }
+
+ CompositeResultsPerDisplay resultsPerDisplay;
+
+ // Filter out virtual displays.
+ for (const auto& [id, coverage] : mCompositionCoverage) {
+ if (const auto idOpt = PhysicalDisplayId::tryCast(id)) {
+ resultsPerDisplay.try_emplace(*idOpt, CompositeResult{coverage});
+ }
}
+
+ return resultsPerDisplay;
}
void SurfaceFlinger::updateLayerGeometry() {
@@ -2329,87 +2921,39 @@ 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<FenceTime>& 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();
}
+ // RANGE_EXTENDED layer may identify themselves as being "HDR"
+ // via a desired hdr/sdr ratio
+ auto pixelFormat = snapshot.buffer
+ ? std::make_optional(static_cast<ui::PixelFormat>(snapshot.buffer->getPixelFormat()))
+ : std::nullopt;
- 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;
- }
-
- // 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<std::mutex> 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 (getHdrRenderType(snapshot.dataspace, pixelFormat, snapshot.desiredHdrSdrRatio) !=
+ HdrRenderType::SDR) {
+ return true;
}
- if (mIgnoreHdrCameraLayers) {
- auto buffer = layer->getBuffer();
- if (buffer && (buffer->getUsage() & GRALLOC_USAGE_HW_CAMERA_WRITE) != 0) {
- 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;
}
- return true;
+ return false;
}
ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId,
@@ -2418,7 +2962,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 +2993,105 @@ ui::Rotation SurfaceFlinger::getPhysicalDisplayOrientation(DisplayId displayId,
return ui::ROTATION_0;
}
-void SurfaceFlinger::postComposition() {
+void SurfaceFlinger::onCompositionPresented(PhysicalDisplayId pacesetterId,
+ const scheduler::FrameTargeters& frameTargeters,
+ nsecs_t presentStartTime) {
ATRACE_CALL();
- ALOGV("postComposition");
- const auto* display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked()).get();
+ ui::PhysicalDisplayMap<PhysicalDisplayId, std::shared_ptr<FenceTime>> presentFences;
+ ui::PhysicalDisplayMap<PhysicalDisplayId, const sp<Fence>> gpuCompositionDoneFences;
- std::shared_ptr<FenceTime> glCompositionDoneFenceTime;
- if (display && display->getCompositionDisplay()->getState().usesClientComposition) {
- glCompositionDoneFenceTime =
- std::make_shared<FenceTime>(display->getCompositionDisplay()
- ->getRenderSurface()
- ->getClientTargetAcquireFence());
- } else {
- glCompositionDoneFenceTime = FenceTime::NO_FENCE;
- }
+ for (const auto& [id, targeter] : frameTargeters) {
+ auto presentFence = getHwComposer().getPresentFence(id);
+
+ if (id == pacesetterId) {
+ mTransactionCallbackInvoker.addPresentFence(presentFence);
+ }
- for (size_t i = mPreviousPresentFences.size()-1; i >= 1; i--) {
- mPreviousPresentFences[i] = mPreviousPresentFences[i-1];
+ if (auto fenceTime = targeter->setPresentFence(std::move(presentFence));
+ fenceTime->isValid()) {
+ presentFences.try_emplace(id, std::move(fenceTime));
+ }
+
+ ftl::FakeGuard guard(mStateLock);
+ if (const auto display = getCompositionDisplayLocked(id);
+ display && display->getState().usesClientComposition) {
+ gpuCompositionDoneFences
+ .try_emplace(id, display->getRenderSurface()->getClientTargetAcquireFence());
+ }
}
- mPreviousPresentFences[0].fence =
- display ? getHwComposer().getPresentFence(display->getPhysicalId()) : Fence::NO_FENCE;
- mPreviousPresentFences[0].fenceTime =
- std::make_shared<FenceTime>(mPreviousPresentFences[0].fence);
+ const auto pacesetterDisplay = FTL_FAKE_GUARD(mStateLock, getDisplayDeviceLocked(pacesetterId));
- nsecs_t now = systemTime();
+ std::shared_ptr<FenceTime> pacesetterPresentFenceTime =
+ presentFences.get(pacesetterId)
+ .transform([](const FenceTimePtr& ptr) { return ptr; })
+ .value_or(FenceTime::NO_FENCE);
+
+ std::shared_ptr<FenceTime> pacesetterGpuCompositionDoneFenceTime =
+ gpuCompositionDoneFences.get(pacesetterId)
+ .transform([](sp<Fence> fence) {
+ return std::make_shared<FenceTime>(std::move(fence));
+ })
+ .value_or(FenceTime::NO_FENCE);
+
+ 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(), pacesetterPresentFenceTime,
+ pacesetterGpuCompositionDoneFenceTime);
// 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)
+ ? Duration::zero()
+ : mPresentLatencyTracker.trackPendingFrame(compositeTime, pacesetterPresentFenceTime);
+
+ 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());
+
+ ui::DisplayMap<ui::LayerStack, const DisplayDevice*> layerStackToDisplay;
{
- std::lock_guard<std::mutex> 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<ui::LayerStack> 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<FenceResult>(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->onCompositionPresented(pacesetterDisplay.get(),
+ pacesetterGpuCompositionDoneFenceTime,
+ pacesetterPresentFenceTime, compositorTiming);
+ layer->releasePendingBuffer(presentTime.ns());
}
std::vector<std::pair<std::shared_ptr<compositionengine::Display>, sp<HdrLayerInfoReporter>>>
@@ -2524,114 +3118,116 @@ 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 auto* outputLayer =
- compositionDisplay->getOutputLayerForLayer(layerFe);
- if (outputLayer) {
- info.numberOfHdrLayers++;
- const auto displayFrame = outputLayer->getState().displayFrame;
- const int32_t area = displayFrame.width() * displayFrame.height();
- if (area > maxArea) {
- maxArea = area;
- info.maxW = displayFrame.width();
- info.maxH = displayFrame.height();
+ auto updateInfoFn =
+ [&](const std::shared_ptr<compositionengine::Display>& compositionDisplay,
+ const frontend::LayerSnapshot& snapshot, const sp<LayerFE>& layerFe) {
+ 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<float>::infinity()
+ : snapshot.desiredHdrSdrRatio;
+ info.mergeDesiredRatio(desiredHdrSdrRatio);
+ info.numberOfHdrLayers++;
+ const auto displayFrame = outputLayer->getState().displayFrame;
+ const int32_t area =
+ displayFrame.width() * displayFrame.height();
+ if (area > maxArea) {
+ maxArea = area;
+ info.maxW = displayFrame.width();
+ info.maxH = displayFrame.height();
+ }
+ }
}
}
- }
- }
- });
+ };
+
+ if (mLayerLifecycleManagerEnabled) {
+ mLayerSnapshotBuilder.forEachVisibleSnapshot(
+ [&, compositionDisplay = compositionDisplay](
+ std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+ "Couldnt find layer object for %s",
+ snapshot->getDebugString().c_str());
+ auto& legacyLayer = it->second;
+ sp<LayerFE> layerFe =
+ legacyLayer->getCompositionEngineLayerFE(snapshot->path);
+
+ updateInfoFn(compositionDisplay, *snapshot, layerFe);
+ });
+ } else {
+ mDrawingState.traverse([&, compositionDisplay = compositionDisplay](Layer* layer) {
+ const auto layerFe = layer->getCompositionEngineLayerFE();
+ const frontend::LayerSnapshot& snapshot = *layer->getLayerSnapshot();
+ updateInfoFn(compositionDisplay, snapshot, layerFe);
+ });
+ }
listener->dispatchHdrLayerInfo(info);
}
}
- mSomeDataspaceChanged = false;
- mVisibleRegionsWereDirtyThisFrame = false;
+ mHdrLayerInfoChanged = false;
- mTransactionCallbackInvoker.addPresentFence(mPreviousPresentFences[0].fence);
mTransactionCallbackInvoker.sendCallbacks(false /* onCommitOnly */);
mTransactionCallbackInvoker.clearCompletedTransactions();
- if (display && display->isInternal() && display->getPowerMode() == hal::PowerMode::ON &&
- mPreviousPresentFences[0].fenceTime->isValid()) {
- mScheduler->addPresentFence(mPreviousPresentFences[0].fenceTime);
- }
+ mTimeStats->incrementTotalFrames();
+ mTimeStats->setPresentFenceGlobal(pacesetterPresentFenceTime);
- const bool isDisplayConnected =
- display && getHwComposer().isConnected(display->getPhysicalId());
+ for (auto&& [id, presentFence] : presentFences) {
+ ftl::FakeGuard guard(mStateLock);
+ const bool isInternalDisplay =
+ mPhysicalDisplays.get(id).transform(&PhysicalDisplay::isInternal).value_or(false);
- if (!hasSyncFramework) {
- if (isDisplayConnected && display->isPoweredOn()) {
- mScheduler->enableHardwareVsync();
+ if (isInternalDisplay) {
+ mScheduler->addPresentFence(id, std::move(presentFence));
}
}
- if (mAnimCompositionPending) {
- mAnimCompositionPending = false;
+ const bool hasPacesetterDisplay =
+ pacesetterDisplay && getHwComposer().isConnected(pacesetterId);
- 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 (hasPacesetterDisplay && pacesetterDisplay->isPoweredOn()) {
+ mScheduler->enableHardwareVsync(pacesetterId);
}
- 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 (hasPacesetterDisplay && !pacesetterDisplay->isPoweredOn()) {
getRenderEngine().cleanupPostRender();
return;
}
- nsecs_t currentTime = systemTime();
- if (mHasPoweredOff) {
- mHasPoweredOff = false;
- } else {
- nsecs_t elapsedTime = currentTime - getBE().mLastSwapTime;
- size_t numPeriods = static_cast<size_t>(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();
- {
- std::lock_guard lock(mTexturePoolMutex);
- if (mTexturePool.size() < mTexturePoolSize) {
- const size_t refillCount = mTexturePoolSize - mTexturePool.size();
- const size_t offset = mTexturePool.size();
- mTexturePool.resize(mTexturePoolSize);
- getRenderEngine().genTextures(refillCount, mTexturePool.data() + offset);
- ATRACE_INT("TexturePoolSize", mTexturePool.size());
- } else if (mTexturePool.size() > mTexturePoolSize) {
- const size_t deleteCount = mTexturePool.size() - mTexturePoolSize;
- const size_t offset = mTexturePoolSize;
- getRenderEngine().deleteTextures(deleteCount, mTexturePool.data() + offset);
- mTexturePool.resize(mTexturePoolSize);
- ATRACE_INT("TexturePoolSize", mTexturePool.size());
- }
+ 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<const DisplayDevice*> 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(presentStartTime),
+ false);
+ });
}
// Even though ATRACE_INT64 already checks if tracing is enabled, it doesn't prevent the
@@ -2640,6 +3236,8 @@ void SurfaceFlinger::postComposition() {
// 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,17 +3270,20 @@ 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();
+ mDebugInTransaction = systemTime();
+
+ // Here we're guaranteed that some transaction flags are set
+ // so we can call commitTransactionsLocked unconditionally.
+ // We clear the flags with mStateLock held to guarantee that
+ // mCurrentState won't change until the transaction is committed.
+ mScheduler->modulateVsync({}, &VsyncModulator::onTransactionCommit);
+ commitTransactionsLocked(clearTransactionFlags(eTransactionMask));
+ mDebugInTransaction = 0;
}
-void SurfaceFlinger::commitTransactions() {
+void SurfaceFlinger::commitTransactionsLegacy() {
ATRACE_CALL();
// Keep a copy of the drawing state (that is going to be overwritten
@@ -2698,7 +3299,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;
@@ -2712,12 +3313,13 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
int attempt = 0;
constexpr int kMaxAttempts = 3;
do {
- hwcModes = getHwComposer().getModes(displayId);
+ hwcModes = getHwComposer().getModes(displayId,
+ scheduler::RefreshRateSelector::kMinSupportedFrameRate
+ .getPeriodNsecs());
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 +3327,21 @@ std::pair<DisplayModes, DisplayModePtr> 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<ui::DisplayModeId>(-1),
[](ui::DisplayModeId max, const auto& pair) {
@@ -2750,6 +3357,7 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
.setPhysicalDisplayId(displayId)
.setResolution({hwcMode.width, hwcMode.height})
.setVsyncPeriod(hwcMode.vsyncPeriod)
+ .setVrrConfig(hwcMode.vrrConfig)
.setDpiX(hwcMode.dpiX)
.setDpiY(hwcMode.dpiY)
.setGroup(hwcMode.configGroup)
@@ -2772,120 +3380,165 @@ std::pair<DisplayModes, DisplayModePtr> SurfaceFlinger::loadDisplayModes(
return {modes, activeMode};
}
-void SurfaceFlinger::processDisplayHotplugEventsLocked() {
- for (const auto& event : mPendingHotplugEvents) {
- std::optional<DisplayIdentificationInfo> info =
- getHwComposer().onHotplug(event.hwcDisplayId, event.connection);
+bool SurfaceFlinger::configureLocked() {
+ std::vector<HotplugEvent> events;
+ {
+ std::lock_guard<std::mutex> lock(mHotplugMutex);
+ events = std::move(mPendingHotplugEvents);
+ }
- if (!info) {
- continue;
+ 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 (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<IBinder> 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> 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<IBinder> token = sp<BBinder>::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.isProtected = true;
+ 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);
}
+void SurfaceFlinger::dispatchDisplayModeChangeEvent(PhysicalDisplayId displayId,
+ const scheduler::FrameRateMode& mode) {
+ // TODO(b/255635821): Merge code paths and move to Scheduler.
+ const auto onDisplayModeChanged = displayId == mActiveDisplayId
+ ? &scheduler::Scheduler::onPrimaryDisplayModeChanged
+ : &scheduler::Scheduler::onNonPrimaryDisplayModeChanged;
+
+ ((*mScheduler).*onDisplayModeChanged)(mAppConnectionHandle, mode);
+}
+
sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
const wp<IBinder>& displayToken,
std::shared_ptr<compositionengine::Display> compositionDisplay,
const DisplayDeviceState& state,
const sp<compositionengine::DisplaySurface>& displaySurface,
const sp<IGraphicBufferProducer>& producer) {
- DisplayDeviceCreationArgs creationArgs(this, getHwComposer(), displayToken, compositionDisplay);
+ DisplayDeviceCreationArgs creationArgs(sp<SurfaceFlinger>::fromExisting(this), getHwComposer(),
+ displayToken, compositionDisplay);
creationArgs.sequenceId = state.sequenceId;
creationArgs.isSecure = state.isSecure;
+ creationArgs.isProtected = state.isProtected;
creationArgs.displaySurface = displaySurface;
creationArgs.hasWideColorGamut = false;
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<scheduler::RefreshRateConfigs>(creationArgs.supportedModes,
- creationArgs.activeModeId, config);
- }
-
- if (const auto id = PhysicalDisplayId::tryCast(compositionDisplay->getId())) {
- creationArgs.isPrimary = id == getPrimaryDisplayIdLocked();
-
- if (useColorManagement) {
- std::vector<ColorMode> modes = getHwComposer().getColorModes(*id);
- for (ColorMode colorMode : modes) {
- if (isWideColorMode(colorMode)) {
- creationArgs.hasWideColorGamut = true;
- }
- std::vector<RenderIntent> renderIntents =
- getHwComposer().getRenderIntents(*id, colorMode);
- creationArgs.hwcColorModes.emplace(colorMode, renderIntents);
- }
- }
+ creationArgs.refreshRateSelector =
+ mPhysicalDisplays.get(physical->id)
+ .transform(&PhysicalDisplay::snapshotRef)
+ .transform([&](const display::DisplaySnapshot& snapshot) {
+ return std::make_shared<
+ scheduler::RefreshRateSelector>(snapshot.displayModes(),
+ creationArgs.activeModeId,
+ config);
+ })
+ .value_or(nullptr);
+
+ creationArgs.isPrimary = physical->id == getPrimaryDisplayIdLocked();
+
+ mPhysicalDisplays.get(physical->id)
+ .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));
+ }
+ }));
}
if (const auto id = HalDisplayId::tryCast(compositionDisplay->getId())) {
@@ -2912,27 +3565,28 @@ sp<DisplayDevice> SurfaceFlinger::setupNewDisplayDeviceInternal(
creationArgs.initialPowerMode =
state.isVirtual() ? std::make_optional(hal::PowerMode::ON) : std::nullopt;
+ creationArgs.requestedRefreshRate = state.requestedRefreshRate;
+
sp<DisplayDevice> 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);
+ RenderIntent::COLORIMETRIC});
+
+ if (const auto& physical = state.physical) {
+ const auto& mode = *physical->activeMode;
+ display->setActiveMode(mode.getId(), mode.getVsyncRate(), mode.getVsyncRate());
}
- display->setLayerStack(state.layerStack);
+ display->setLayerFilter(makeLayerFilterForDisplay(display->getId(), state.layerStack));
display->setProjection(state.orientation, state.layerStackSpaceRect,
state.orientedDisplaySpaceRect);
display->setDisplayName(state.displayName);
@@ -2973,6 +3627,7 @@ void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& displayToken,
builder.setPixels(resolution);
builder.setIsSecure(state.isSecure);
+ builder.setIsProtected(state.isProtected);
builder.setPowerAdvisor(mPowerAdvisor.get());
builder.setName(state.displayName);
auto compositionDisplay = getCompositionEngine().createDisplay(builder.build());
@@ -3008,11 +3663,22 @@ void SurfaceFlinger::processDisplayAdded(const wp<IBinder>& 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 +3693,7 @@ void SurfaceFlinger::processDisplayRemoved(const wp<IBinder>& displayToken) {
releaseVirtualDisplay(display->getVirtualId());
} else {
dispatchDisplayHotplugEvent(display->getPhysicalId(), false);
+ mScheduler->unregisterDisplay(display->getPhysicalId());
}
}
@@ -3057,8 +3724,6 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken,
// Recreate the DisplayDevice if the surface or sequence ID changed.
if (currentBinder != drawingBinder || currentState.sequenceId != drawingState.sequenceId) {
- getRenderEngine().cleanFramebufferCache();
-
if (const auto display = getDisplayDeviceLocked(displayToken)) {
display->disconnect();
if (display->isVirtual()) {
@@ -3080,7 +3745,7 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& displayToken,
// TODO(b/175678251) Call a listener instead.
if (currentState.physical->hwcDisplayId == getHwComposer().getPrimaryHwcDisplayId()) {
- updateInternalDisplayVsyncLocked(display);
+ resetPhaseConfiguration(display->getActiveMode().fps);
}
}
return;
@@ -3088,7 +3753,8 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& 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 +3764,28 @@ void SurfaceFlinger::processDisplayChanged(const wp<IBinder>& 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<DisplayDevice>& 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 +3798,7 @@ void SurfaceFlinger::processDisplayChangesLocked() {
const KeyedVector<wp<IBinder>, 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 +3834,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 +3892,18 @@ 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.
+ layer->skipReportingTransformHint();
+ } else {
+ layer->updateTransformHint(hintDisplay->getTransformHint());
}
-
- layer->updateTransformHint(hintDisplay->getTransformHint());
});
}
@@ -3230,6 +3911,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,52 +3919,77 @@ 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<Layer>::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<WindowInfo> windowInfos;
std::vector<DisplayInfo> 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<int32_t> visibleWindowIds;
+ for (WindowInfo& windowInfo : windowInfos) {
+ if (!windowInfo.inputConfig.test(WindowInfo::InputConfig::NOT_VISIBLE)) {
+ visibleWindowIds.insert(windowInfo.id);
+ }
+ }
+ bool visibleWindowsChanged = false;
+ if (visibleWindowIds != mVisibleWindowIds) {
+ visibleWindowsChanged = true;
+ mVisibleWindowIds = std::move(visibleWindowIds);
}
+
BackgroundExecutor::getInstance().sendCallbacks({[updateWindowInfo,
windowInfos = std::move(windowInfos),
displayInfos = std::move(displayInfos),
inputWindowCommands =
std::move(mInputWindowCommands),
- inputFlinger = mInputFlinger, this]() {
+ inputFlinger = mInputFlinger, this,
+ visibleWindowsChanged, vsyncId, frameTime]() {
ATRACE_NAME("BackgroundExecutor::updateInputFlinger");
if (updateWindowInfo) {
- mWindowInfosListenerInvoker->windowInfosChanged(windowInfos, displayInfos,
- inputWindowCommands.syncInputWindows);
- } else if (inputWindowCommands.syncInputWindows) {
- // If the caller requested to sync input windows, but there are no
- // changes to input windows, notify immediately.
- windowInfosReported();
+ mWindowInfosListenerInvoker
+ ->windowInfosChanged(gui::WindowInfosUpdate{std::move(windowInfos),
+ std::move(displayInfos),
+ ftl::to_underlying(vsyncId),
+ frameTime.ns()},
+ std::move(
+ inputWindowCommands.windowInfosReportedListeners),
+ /* forceImmediateCall= */ visibleWindowsChanged ||
+ !inputWindowCommands.focusRequests.empty());
+ } 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);
@@ -3314,7 +4021,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);
}
@@ -3323,47 +4030,32 @@ void SurfaceFlinger::persistDisplayBrightness(bool needsComposite) {
void SurfaceFlinger::buildWindowInfos(std::vector<WindowInfo>& outWindowInfos,
std::vector<DisplayInfo>& outDisplayInfos) {
- ftl::SmallMap<ui::LayerStack, DisplayDevice::InputInfo, 4> 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;
+ 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};
+ });
- 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{})));
- });
+ 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);
}
}
@@ -3376,28 +4068,51 @@ void SurfaceFlinger::updateCursorAsync() {
}
}
+ constexpr bool kCursorOnly = true;
+ const auto layers = moveSnapshotsToCompositionArgs(refreshArgs, kCursorOnly);
mCompositionEngine->updateCursorAsync(refreshArgs);
+ moveSnapshotsFromCompositionArgs(refreshArgs, layers);
+}
+
+void SurfaceFlinger::requestHardwareVsync(PhysicalDisplayId displayId, bool enable) {
+ getHwComposer().setVsyncEnabled(displayId, enable ? hal::Vsync::ENABLE : hal::Vsync::DISABLE);
}
-void SurfaceFlinger::requestDisplayMode(DisplayModePtr mode, DisplayModeEvent event) {
+void SurfaceFlinger::requestDisplayModes(std::vector<display::DisplayModeRequest> 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->refreshRateConfigs().isModeAllowed(mode->getId())) {
- ALOGV("Skipping disallowed mode %d", mode->getId().value());
- return;
- }
+ const auto displayId = modePtr->getPhysicalDisplayId();
+ const auto display = getDisplayDeviceLocked(displayId);
- setDesiredActiveMode({std::move(mode), event});
+ if (!display) continue;
+
+ if (ftl::FakeGuard guard(kMainThreadContext);
+ !shouldApplyRefreshRateSelectorPolicy(*display)) {
+ ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
+ continue;
+ }
+
+ if (display->refreshRateSelector().isModeAllowed(request.mode)) {
+ setDesiredMode(std::move(request));
+ } else {
+ ALOGV("%s: Mode %d is disallowed for display %s", __func__, modePtr->getId().value(),
+ to_string(displayId).c_str());
+ }
+ }
}
void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
@@ -3409,27 +4124,103 @@ void SurfaceFlinger::triggerOnFrameRateOverridesChanged() {
mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
}
-void SurfaceFlinger::initScheduler(const sp<DisplayDevice>& 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;
+void SurfaceFlinger::notifyCpuLoadUp() {
+ mPowerAdvisor->notifyCpuLoadUp();
+}
+
+void SurfaceFlinger::onChoreographerAttached() {
+ ATRACE_CALL();
+ if (mLayerLifecycleManagerEnabled) {
+ mUpdateAttachedChoreographer = true;
+ scheduleCommit(FrameHint::kNone);
}
- const auto currRefreshRate = display->getActiveMode()->getFps();
- mRefreshRateStats = std::make_unique<scheduler::RefreshRateStats>(*mTimeStats, currRefreshRate,
- hal::PowerMode::OFF);
+}
+
+void SurfaceFlinger::onVsyncGenerated(TimePoint expectedPresentTime,
+ ftl::NonNull<DisplayModePtr> modePtr, Fps renderRate) {
+ const auto vsyncPeriod = modePtr->getVsyncRate().getPeriod();
+ const auto timeoutOpt = [&]() -> std::optional<Period> {
+ const auto vrrConfig = modePtr->getVrrConfig();
+ if (!vrrConfig) return std::nullopt;
+
+ const auto notifyExpectedPresentConfig =
+ modePtr->getVrrConfig()->notifyExpectedPresentConfig;
+ if (!notifyExpectedPresentConfig) return std::nullopt;
+ return Period::fromNs(notifyExpectedPresentConfig->notifyExpectedPresentTimeoutNs);
+ }();
+
+ notifyExpectedPresentIfRequired(modePtr->getPhysicalDisplayId(), vsyncPeriod,
+ expectedPresentTime, renderRate, timeoutOpt);
+}
+
+void SurfaceFlinger::notifyExpectedPresentIfRequired(PhysicalDisplayId displayId,
+ Period vsyncPeriod,
+ TimePoint expectedPresentTime,
+ Fps frameInterval,
+ std::optional<Period> timeoutOpt) {
+ {
+ auto& data = mNotifyExpectedPresentMap[displayId];
+ const auto lastExpectedPresentTimestamp = data.lastExpectedPresentTimestamp.load();
+ const auto lastFrameInterval = data.lastFrameInterval;
+ data.lastFrameInterval = frameInterval;
+ const auto threshold = Duration::fromNs(vsyncPeriod.ns() / 2);
+
+ const constexpr nsecs_t kOneSecondNs =
+ std::chrono::duration_cast<std::chrono::nanoseconds>(1s).count();
+ const auto timeout = Period::fromNs(timeoutOpt && timeoutOpt->ns() > 0 ? timeoutOpt->ns()
+ : kOneSecondNs);
+ const bool frameIntervalIsOnCadence =
+ isFrameIntervalOnCadence(expectedPresentTime, lastExpectedPresentTimestamp,
+ lastFrameInterval, timeout, threshold);
+
+ const bool expectedPresentWithinTimeout =
+ isExpectedPresentWithinTimeout(expectedPresentTime, lastExpectedPresentTimestamp,
+ timeoutOpt, threshold);
+
+ using fps_approx_ops::operator!=;
+ if (frameIntervalIsOnCadence && frameInterval != lastFrameInterval) {
+ data.lastExpectedPresentTimestamp = expectedPresentTime;
+ }
+
+ if (expectedPresentWithinTimeout && frameIntervalIsOnCadence) {
+ return;
+ }
+ data.lastExpectedPresentTimestamp = expectedPresentTime;
+ }
+
+ const char* const whence = __func__;
+ static_cast<void>(mScheduler->schedule([=, this]() FTL_FAKE_GUARD(kMainThreadContext) {
+ const auto status = getHwComposer().notifyExpectedPresent(displayId, expectedPresentTime,
+ frameInterval);
+ if (status != NO_ERROR) {
+ ALOGE("%s failed to notifyExpectedPresentHint for display %" PRId64, whence,
+ displayId.value);
+ }
+ }));
+}
- mVsyncConfiguration = getFactory().createVsyncConfiguration(currRefreshRate);
- mVsyncModulator = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
+void SurfaceFlinger::initScheduler(const sp<const DisplayDevice>& display) {
+ using namespace scheduler;
- using Feature = scheduler::Feature;
- scheduler::FeatureFlags features;
+ LOG_ALWAYS_FATAL_IF(mScheduler);
- if (sysprop::use_content_detection_for_refresh_rate(false)) {
+ const auto activeMode = display->refreshRateSelector().getActiveMode();
+ const Fps activeRefreshRate = activeMode.fps;
+ mRefreshRateStats =
+ std::make_unique<RefreshRateStats>(*mTimeStats, activeRefreshRate, hal::PowerMode::OFF);
+
+ mVsyncConfiguration = getFactory().createVsyncConfiguration(activeRefreshRate);
+
+ FeatureFlags features;
+
+ const auto defaultContentDetectionValue =
+ FlagManager::getInstance().enable_fro_dependent_features() &&
+ sysprop::enable_frame_rate_override(true);
+ if (sysprop::use_content_detection_for_refresh_rate(defaultContentDetectionValue)) {
features |= Feature::kContentDetection;
+ if (FlagManager::getInstance().enable_small_area_detection()) {
+ features |= Feature::kSmallDirtyContentDetection;
+ }
}
if (base::GetBoolProperty("debug.sf.show_predicted_vsync"s, false)) {
features |= Feature::kTracePredictedVsync;
@@ -3438,68 +4229,54 @@ void SurfaceFlinger::initScheduler(const sp<DisplayDevice>& display) {
!getHwComposer().hasCapability(Capability::PRESENT_FENCE_IS_NOT_RELIABLE)) {
features |= Feature::kPresentFences;
}
+ if (display->refreshRateSelector().kernelIdleTimerController()) {
+ features |= Feature::kKernelIdleTimer;
+ }
+ if (mBackpressureGpuComposition) {
+ features |= Feature::kBackpressureGpuComposition;
+ }
- mScheduler = std::make_unique<scheduler::Scheduler>(static_cast<ICompositor&>(*this),
- static_cast<ISchedulerCallback&>(*this),
- features);
- {
- auto configs = display->holdRefreshRateConfigs();
- if (configs->kernelIdleTimerController().has_value()) {
- features |= Feature::kKernelIdleTimer;
- }
+ auto modulatorPtr = sp<VsyncModulator>::make(mVsyncConfiguration->getCurrentConfigs());
- mScheduler->createVsyncSchedule(features);
- mScheduler->setRefreshRateConfigs(std::move(configs));
+ mScheduler = std::make_unique<Scheduler>(static_cast<ICompositor&>(*this),
+ static_cast<ISchedulerCallback&>(*this), features,
+ std::move(modulatorPtr),
+ static_cast<IVsyncTrackerCallback&>(*this));
+ mScheduler->registerDisplay(display->getPhysicalId(), display->holdRefreshRateSelector());
+ if (FlagManager::getInstance().vrr_config()) {
+ mScheduler->setRenderRate(display->getPhysicalId(), activeMode.fps);
}
- setVsyncEnabled(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<RegionSamplingThread>::make(*this,
+ RegionSamplingThread::EnvironmentTimingTunables());
+ mFpsReporter = sp<FpsReporter>::make(*mFrameTimeline, *this);
-void SurfaceFlinger::updatePhaseConfiguration(const Fps& refreshRate) {
- mVsyncConfiguration->setRefreshRateFps(refreshRate);
- setVsyncConfig(mVsyncModulator->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs()),
- refreshRate.getPeriodNsecs());
+ mIsHotplugErrViaNegVsync =
+ base::GetBoolProperty("debug.sf.hwc_hotplug_error_via_neg_vsync"s, false);
}
-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);
+void SurfaceFlinger::updatePhaseConfiguration(Fps refreshRate) {
+ mVsyncConfiguration->setRefreshRateFps(refreshRate);
+ mScheduler->setVsyncConfigSet(mVsyncConfiguration->getCurrentConfigs(),
+ refreshRate.getPeriod());
}
void SurfaceFlinger::doCommitTransactions() {
@@ -3532,10 +4309,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;
@@ -3547,8 +4320,24 @@ void SurfaceFlinger::doCommitTransactions() {
}
commitOffscreenLayers();
- if (mNumClones > 0) {
- mDrawingState.traverse([&](Layer* layer) { layer->updateMirrorInfo(); });
+ if (mLayerMirrorRoots.size() > 0) {
+ std::deque<Layer*> pendingUpdates;
+ pendingUpdates.insert(pendingUpdates.end(), mLayerMirrorRoots.begin(),
+ mLayerMirrorRoots.end());
+ std::vector<Layer*> 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({});
+ }
}
}
@@ -3563,10 +4352,10 @@ void SurfaceFlinger::commitOffscreenLayers() {
}
}
-void SurfaceFlinger::invalidateLayerStack(const sp<const Layer>& 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);
}
}
@@ -3581,8 +4370,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:
@@ -3600,16 +4387,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<Layer>::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;
@@ -3628,7 +4418,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;
}
@@ -3651,7 +4444,7 @@ bool SurfaceFlinger::latchBuffers() {
mBootStage = BootStage::BOOTANIMATION;
}
- if (mNumClones > 0) {
+ if (mLayerMirrorRoots.size() > 0) {
mDrawingState.traverse([&](Layer* layer) { layer->updateCloneBufferInfo(); });
}
@@ -3659,44 +4452,81 @@ bool SurfaceFlinger::latchBuffers() {
return !mLayersWithQueuedFrames.empty() && newDataLatched;
}
-status_t SurfaceFlinger::addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
+status_t SurfaceFlinger::addClientLayer(LayerCreationArgs& args, const sp<IBinder>& handle,
const sp<Layer>& layer, const wp<Layer>& 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<void>(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<Layer> parent = sp<Layer>::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%s", layer->getName().c_str(),
+ (layer->isHandleAlive() ? "handleAlive" : ""));
+ }
+ });
+ }
+ });
+
+ 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<std::mutex> 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<std::mutex> lock(mCreatedLayersLock);
+ mCreatedLayers.emplace_back(layer, parent, args.addToRoot);
+ mNewLayers.emplace_back(std::make_unique<frontend::RequestedLayerState>(args));
+ args.mirrorLayerHandle.clear();
+ args.parentHandle.clear();
+ mNewLayerArgs.emplace_back(std::move(args));
}
setTransactionFlags(eTransactionNeeded);
@@ -3708,217 +4538,283 @@ 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<IBinder>& 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<sp<IBinder>, SpHash<IBinder>>&
- 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) {
+ const auto& transaction = *flushState.transaction;
+
+ const TimePoint desiredPresentTime = TimePoint::fromNs(transaction.desiredPresentTime);
+ const TimePoint expectedPresentTime = mScheduler->expectedPresentTimeForPacesetter();
+
+ using TransactionReadiness = TransactionHandler::TransactionReadiness;
+
+ // 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 >= expectedPresentTime &&
+ desiredPresentTime < expectedPresentTime + 1s) {
+ ATRACE_FORMAT("not current desiredPresentTime: %" PRId64 " expectedPresentTime: %" PRId64,
+ desiredPresentTime, expectedPresentTime);
+ return TransactionReadiness::NotReady;
}
- return false;
-}
+ if (!mScheduler->isVsyncValid(expectedPresentTime, transaction.originUid)) {
+ ATRACE_FORMAT("!isVsyncValid expectedPresentTime: %" PRId64 " uid: %d", expectedPresentTime,
+ transaction.originUid);
+ return TransactionReadiness::NotReady;
+ }
-int SurfaceFlinger::flushUnsignaledPendingTransactionQueues(
- std::vector<TransactionState>& transactions,
- std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
- std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions) {
- return flushPendingTransactionQueues(transactions, bufferLayersReadyToPresent,
- applyTokensWithUnsignaledTransactions,
- /*tryApplyUnsignaled*/ true);
-}
-
-int SurfaceFlinger::flushPendingTransactionQueues(
- std::vector<TransactionState>& transactions,
- std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
- std::unordered_set<sp<IBinder>, SpHash<IBinder>>& 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(expectedPresentTime, VsyncId{transaction.frameTimelineInfo.vsyncId})) {
+ ATRACE_FORMAT("frameIsEarly vsyncId: %" PRId64 " expectedPresentTime: %" PRId64,
+ transaction.frameTimelineInfo.vsyncId, expectedPresentTime);
+ return TransactionReadiness::NotReady;
+ }
- 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<int>(ready));
- if (ready == TransactionReadiness::NotReady) {
- setTransactionFlags(eTransactionFlushNeeded);
- break;
+ return TransactionReadiness::Ready;
+}
+
+TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheckLegacy(
+ const TransactionHandler::TransactionFlushState& flushState) {
+ using TransactionReadiness = TransactionHandler::TransactionReadiness;
+ auto ready = TransactionReadiness::Ready;
+ flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
+ resolvedState) -> bool {
+ sp<Layer> layer = LayerHandle::getLayer(resolvedState.state.surface);
+
+ const auto& transaction = *flushState.transaction;
+ const auto& s = resolvedState.state;
+ // 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,
+ resolvedState.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;
}
- 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<uint64_t>::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 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;
}
- if (transactionQueue.empty()) {
- it = mPendingTransactionQueues.erase(it);
- mTransactionQueueCV.broadcast();
- } else {
- it = std::next(it, 1);
+ const bool acquireFenceAvailable = s.bufferData &&
+ s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+ s.bufferData->acquireFence;
+ const bool fenceSignaled = !acquireFenceAvailable ||
+ s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
+ if (!fenceSignaled) {
+ // check fence status
+ const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
+ flushState.firstTransaction) &&
+ layer->isSimpleBufferUpdate(s);
+
+ 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,
+ {.pid = layer->getOwnerPid(),
+ .layerId = static_cast<uint32_t>(
+ layer->getSequence()),
+ .layerName = layer->getDebugName(),
+ .bufferId = s.bufferData->getId(),
+ .frameNumber = s.bufferData->frameNumber});
+ }
+ ATRACE_FORMAT("fence unsignaled %s", layer->getDebugName());
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+ }
}
- }
- return transactionsPendingBarrier;
-}
+ return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
+ });
+ return ready;
+}
+
+TransactionHandler::TransactionReadiness SurfaceFlinger::transactionReadyBufferCheck(
+ const TransactionHandler::TransactionFlushState& flushState) {
+ using TransactionReadiness = TransactionHandler::TransactionReadiness;
+ auto ready = TransactionReadiness::Ready;
+ flushState.transaction->traverseStatesWithBuffersWhileTrue([&](const ResolvedComposerState&
+ resolvedState) -> bool {
+ const frontend::RequestedLayerState* layer =
+ mLayerLifecycleManager.getLayerFromId(resolvedState.layerId);
+ const auto& transaction = *flushState.transaction;
+ const auto& s = resolvedState.state;
+ // 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->barrierProducerId > s.bufferData->producerId) {
+ if (s.bufferData->releaseBufferListener) {
+ uint32_t currentMaxAcquiredBufferCount =
+ getMaxAcquiredBufferCountForCurrentRefreshRate(layer->ownerUid.val());
+ ATRACE_FORMAT_INSTANT("callReleaseBufferCallback %s - %" PRIu64,
+ layer->name.c_str(), s.bufferData->frameNumber);
+ s.bufferData->releaseBufferListener
+ ->onReleaseBuffer({resolvedState.externalTexture->getBuffer()->getId(),
+ s.bufferData->frameNumber},
+ s.bufferData->acquireFence
+ ? s.bufferData->acquireFence
+ : Fence::NO_FENCE,
+ currentMaxAcquiredBufferCount);
+ }
-std::vector<TransactionState> 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<TransactionState> transactions;
- // Layer handles that have transactions with buffers that are ready to be applied.
- std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>> bufferLayersReadyToPresent;
- std::unordered_set<sp<IBinder>, SpHash<IBinder>> 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;
- }
+ // 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->name.c_str(),
+ layer->barrierProducerId, s.bufferData->producerId);
+ return TraverseBuffersReturnValues::DELETE_AND_CONTINUE_TRAVERSAL;
+ }
- return transactionIsReadyToBeApplied(transaction, transaction.frameTimelineInfo,
- transaction.isAutoTimestamp,
- transaction.desiredPresentTime,
- transaction.originUid, transaction.states,
- bufferLayersReadyToPresent,
- transactions.size(),
- /*tryApplyUnsignaled*/ false);
- }();
- ATRACE_INT("TransactionReadiness", static_cast<int>(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<uint64_t>::max();
- }
- });
- transactions.emplace_back(std::move(transaction));
+ if (layer->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->name.c_str(), layer->barrierFrameNumber,
+ s.bufferData->barrierFrameNumber);
+ ready = TransactionReadiness::NotReadyBarrier;
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
}
- 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);
- }
+ // 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->name.c_str());
+ ready = TransactionReadiness::NotReady;
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
+ }
- // 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);
+ const bool acquireFenceAvailable = s.bufferData &&
+ s.bufferData->flags.test(BufferData::BufferDataChange::fenceChanged) &&
+ s.bufferData->acquireFence;
+ const bool fenceSignaled = !acquireFenceAvailable ||
+ s.bufferData->acquireFence->getStatus() != Fence::Status::Unsignaled;
+ if (!fenceSignaled) {
+ // check fence status
+ const bool allowLatchUnsignaled = shouldLatchUnsignaled(s, transaction.states.size(),
+ flushState.firstTransaction) &&
+ layer->isSimpleBufferUpdate(s);
+ if (allowLatchUnsignaled) {
+ ATRACE_FORMAT("fence unsignaled try allowLatchUnsignaled %s", layer->name.c_str());
+ 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,
+ {.pid = layer->ownerPid.val(),
+ .layerId = layer->id,
+ .layerName = layer->name,
+ .bufferId = s.bufferData->getId(),
+ .frameNumber = s.bufferData->frameNumber});
+ }
+ ATRACE_FORMAT("fence unsignaled %s", layer->name.c_str());
+ return TraverseBuffersReturnValues::STOP_TRAVERSAL;
}
}
+ return TraverseBuffersReturnValues::CONTINUE_TRAVERSAL;
+ });
+ return ready;
+}
+
+void SurfaceFlinger::addTransactionReadyFilters() {
+ mTransactionHandler.addTransactionReadyFilter(
+ std::bind(&SurfaceFlinger::transactionReadyTimelineCheck, this, std::placeholders::_1));
+ if (mLayerLifecycleManagerEnabled) {
+ mTransactionHandler.addTransactionReadyFilter(
+ std::bind(&SurfaceFlinger::transactionReadyBufferCheck, this,
+ std::placeholders::_1));
+ } else {
+ mTransactionHandler.addTransactionReadyFilter(
+ std::bind(&SurfaceFlinger::transactionReadyBufferCheckLegacy, this,
+ std::placeholders::_1));
}
- return transactions;
}
-// for test only
-bool SurfaceFlinger::flushTransactionQueues(int64_t vsyncId) {
- std::vector<TransactionState> transactions = flushTransactions();
+// For tests only
+bool SurfaceFlinger::flushTransactionQueues(VsyncId vsyncId) {
+ mTransactionHandler.collectTransactions();
+ std::vector<TransactionState> transactions = mTransactionHandler.flushTransactions();
return applyTransactions(transactions, vsyncId);
}
bool SurfaceFlinger::applyTransactions(std::vector<TransactionState>& transactions,
- int64_t vsyncId) {
- Mutex::Autolock _l(mStateLock);
+ VsyncId vsyncId) {
+ Mutex::Autolock lock(mStateLock);
return applyTransactionsLocked(transactions, vsyncId);
}
bool SurfaceFlinger::applyTransactionsLocked(std::vector<TransactionState>& transactions,
- int64_t vsyncId) {
+ VsyncId vsyncId) {
bool needsTraversal = false;
// Now apply all transactions.
for (auto& transaction : transactions) {
@@ -3927,205 +4823,179 @@ bool SurfaceFlinger::applyTransactionsLocked(std::vector<TransactionState>& 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(ftl::to_underlying(vsyncId));
+ 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()->minFramePeriod() / 2;
+
+ return predictedPresentTime >= expectedPresentTime &&
+ predictedPresentTime - expectedPresentTime >= earlyLatchVsyncThreshold;
}
-bool SurfaceFlinger::shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t& state,
- size_t numStates, size_t totalTXapplied) const {
+
+bool SurfaceFlinger::shouldLatchUnsignaled(const layer_state_t& state, size_t numStates,
+ bool firstTransaction) const {
if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Disabled) {
- ALOGV("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
+ ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::Disabled)", __func__);
return false;
}
- if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::Always) {
- ALOGV("%s: true (LatchUnsignaledConfig::Always)", __func__);
- return true;
- }
-
// We only want to latch unsignaled when a single layer is updated in this
// transaction (i.e. not a blast sync transaction).
if (numStates != 1) {
- ALOGV("%s: false (numStates=%zu)", __func__, numStates);
+ ATRACE_FORMAT_INSTANT("%s: false (numStates=%zu)", __func__, numStates);
return false;
}
if (enableLatchUnsignaledConfig == LatchUnsignaledConfig::AutoSingleLayer) {
- if (totalTXapplied > 0) {
- ALOGV("%s: false (LatchUnsignaledConfig::AutoSingleLayer; totalTXapplied=%zu)",
- __func__, totalTXapplied);
+ if (!firstTransaction) {
+ ATRACE_FORMAT_INSTANT("%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)",
- __func__);
+ if (mScheduler->vsyncModulator().isVsyncConfigEarly()) {
+ ATRACE_FORMAT_INSTANT("%s: false (LatchUnsignaledConfig::AutoSingleLayer; "
+ "isVsyncConfigEarly)",
+ __func__);
return false;
}
}
- if (!layer->simpleBufferUpdate(state)) {
- ALOGV("%s: false (!simpleBufferUpdate)", __func__);
- return false;
- }
-
- ALOGV("%s: true", __func__);
return true;
}
-auto SurfaceFlinger::transactionIsReadyToBeApplied(TransactionState& transaction,
- const FrameTimelineInfo& info, bool isAutoTimestamp, int64_t desiredPresentTime,
- uid_t originUid, const Vector<ComposerState>& states,
- const std::unordered_map<
- sp<IBinder>, uint64_t, SpHash<IBinder>>& 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<ComposerState>& states,
+ const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+ InputWindowCommands inputWindowCommands, int64_t desiredPresentTime, bool isAutoTimestamp,
+ const std::vector<client_cache_t>& uncacheBuffers, bool hasListenerCallbacks,
+ const std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId,
+ const std::vector<uint64_t>& 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> 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<uint64_t> uncacheBufferIds;
+ uncacheBufferIds.reserve(uncacheBuffers.size());
+ for (const auto& uncacheBuffer : uncacheBuffers) {
+ sp<GraphicBuffer> buffer = ClientCache::getInstance().erase(uncacheBuffer);
+ if (buffer != nullptr) {
+ uncacheBufferIds.push_back(buffer->getId());
+ }
+ }
+
+ std::vector<ResolvedComposerState> 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> 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);
+ if (resolvedState.externalTexture) {
+ resolvedState.state.bufferData->buffer = resolvedState.externalTexture->getBuffer();
+ }
+ 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<IBinder>& 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<CountDownLatch>(
- (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;
@@ -4133,105 +5003,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<ComposerState>& states,
- const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
- const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& 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<ComposerState>& states,
- const Vector<DisplayState>& displays, uint32_t flags,
+ std::vector<ResolvedComposerState>& states,
+ Vector<DisplayState>& 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<uint64_t>& uncacheBufferIds,
+ const int64_t postTime, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& listenerCallbacks,
int originPid, int originUid, uint64_t transactionId) {
uint32_t transactionFlags = 0;
- for (const DisplayState& display : displays) {
- 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
@@ -4240,52 +5030,78 @@ bool SurfaceFlinger::applyTransactionState(const FrameTimelineInfo& frameTimelin
for (const auto& listener : listenerCallbacks) {
mTransactionCallbackInvoker.addEmptyTransaction(listener);
}
-
+ nsecs_t now = systemTime();
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(),
+ .isFrontBuffered = layer->isFrontBuffered(),
+ };
+ layer->recordLayerHistoryAnimationTx(layerProps, now);
}
}
}
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::applyAndCommitDisplayTransactionStatesLocked(
+ std::vector<TransactionState>& transactions) {
+ 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;
@@ -4293,10 +5109,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;
@@ -4369,11 +5191,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<ListenerCallbacks> filteredListeners;
for (auto& listener : s.listeners) {
@@ -4397,18 +5218,23 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime
uint32_t flags = 0;
sp<Layer> 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<CallbackHandle>::make(listener,
+ callbackIds,
+ s.surface),
+ std::vector<JankData>());
}
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) {
@@ -4459,18 +5285,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)) {
@@ -4478,7 +5297,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;
}
}
@@ -4502,6 +5321,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,
@@ -4522,8 +5346,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))
@@ -4535,9 +5359,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;
}
@@ -4545,7 +5366,8 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime
if (layer->setApi(s.api)) flags |= eTraversalNeeded;
}
if (what & layer_state_t::eSidebandStreamChanged) {
- if (layer->setSidebandStream(s.sidebandStream)) flags |= eTraversalNeeded;
+ if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+ flags |= eTraversalNeeded;
}
if (what & layer_state_t::eInputInfoChanged) {
layer->setInputInfo(*s.windowInfoHandle->getInfo());
@@ -4553,16 +5375,20 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime
}
std::optional<nsecs_t> 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>(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)) {
@@ -4572,6 +5398,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;
@@ -4583,9 +5417,23 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime
const auto strategy =
Layer::FrameRate::convertChangeFrameRateStrategy(s.changeFrameRateStrategy);
- if (layer->setFrameRate(
- Layer::FrameRate(Fps::fromValue(s.frameRate), compatibility, strategy))) {
- flags |= eTraversalNeeded;
+ if (layer->setFrameRate(Layer::FrameRate::FrameRateVote(Fps::fromValue(s.frameRate),
+ compatibility, strategy))) {
+ flags |= eTraversalNeeded;
+ }
+ }
+ if (what & layer_state_t::eFrameRateCategoryChanged) {
+ const FrameRateCategory category = Layer::FrameRate::convertCategory(s.frameRateCategory);
+ if (layer->setFrameRateCategory(category, s.frameRateCategorySmoothSwitchOnly)) {
+ flags |= eTraversalNeeded;
+ }
+ }
+ if (what & layer_state_t::eFrameRateSelectionStrategyChanged) {
+ const scheduler::LayerInfo::FrameRateSelectionStrategy strategy =
+ scheduler::LayerInfo::convertFrameRateSelectionStrategy(
+ s.frameRateSelectionStrategy);
+ if (layer->setFrameRateSelectionStrategy(strategy)) {
+ flags |= eTraversalNeeded;
}
}
if (what & layer_state_t::eFixedTransformHintChanged) {
@@ -4599,6 +5447,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;
@@ -4622,7 +5483,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
@@ -4645,24 +5506,172 @@ uint32_t SurfaceFlinger::setClientStateLocked(const FrameTimelineInfo& frameTime
std::vector<sp<CallbackHandle>> 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<CallbackHandle>::make(listener, callbackIds, s.surface));
}
}
if (what & layer_state_t::eBufferChanged) {
- std::shared_ptr<renderengine::ExternalTexture> 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;
+ 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<ListenerCallbacks> 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> 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<CallbackHandle>::make(listener,
+ callbackIds,
+ s.surface),
+ std::vector<JankData>());
+ }
+ return 0;
+ }
+ if (what & layer_state_t::eProducerDisconnect) {
+ layer->onDisconnect();
+ }
+ std::optional<nsecs_t> dequeueBufferTimestamp;
+ if (what & layer_state_t::eMetadataChanged) {
+ dequeueBufferTimestamp = s.metadata.getInt64(gui::METADATA_DEQUEUE_TIME);
+ }
+
+ std::vector<sp<CallbackHandle>> callbackHandles;
+ if ((what & layer_state_t::eHasListenerCallbacksChanged) && (!filteredListeners.empty())) {
+ for (auto& [listener, callbackIds] : filteredListeners) {
+ callbackHandles.emplace_back(
+ sp<CallbackHandle>::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::eBufferTransformChanged) {
+ if (layer->setTransform(s.bufferTransform)) flags |= eTraversalNeeded;
+ }
+ if (what & layer_state_t::eTransformToDisplayInverseChanged) {
+ if (layer->setTransformToDisplayInverse(s.transformToDisplayInverse))
+ flags |= eTraversalNeeded;
+ }
+ if (what & layer_state_t::eCropChanged) {
+ if (layer->setCrop(s.crop)) flags |= eTraversalNeeded;
+ }
+ if (what & layer_state_t::eSidebandStreamChanged) {
+ if (layer->setSidebandStream(s.sidebandStream, frameTimelineInfo, postTime))
+ flags |= eTraversalNeeded;
+ }
+ if (what & layer_state_t::eDataspaceChanged) {
+ if (layer->setDataspace(s.dataspace)) flags |= eTraversalNeeded;
+ }
+ if (what & layer_state_t::eExtendedRangeBrightnessChanged) {
+ if (layer->setExtendedRangeBrightness(s.currentHdrSdrRatio, s.desiredHdrSdrRatio)) {
+ flags |= eTraversalNeeded;
+ }
+ }
+ if (what & layer_state_t::eBufferChanged) {
+ std::optional<ui::Transform::RotationFlags> 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;
}
@@ -4672,65 +5681,104 @@ uint32_t SurfaceFlinger::addInputWindowCommands(const InputWindowCommands& input
}
status_t SurfaceFlinger::mirrorLayer(const LayerCreationArgs& args,
- const sp<IBinder>& mirrorFromHandle, sp<IBinder>* outHandle,
- int32_t* outLayerId) {
+ const sp<IBinder>& mirrorFromHandle,
+ gui::CreateSurfaceResult& outResult) {
if (!mirrorFromHandle) {
return NAME_NOT_FOUND;
}
sp<Layer> mirrorLayer;
sp<Layer> 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<IBinder>* outHandle,
- const sp<IBinder>& parentHandle, int32_t* outLayerId,
- const sp<Layer>& 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<Layer> 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<std::mutex> 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> 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<int32_t>* 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;
@@ -4740,76 +5788,29 @@ status_t SurfaceFlinger::createLayer(LayerCreationArgs& args, sp<IBinder>* outHa
return result;
}
- bool addToRoot = args.addToRoot && callingThreadHasUnscopedSurfaceFlingerAccess();
- wp<Layer> 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<Layer> 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<Layer> 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<int32_t>(outTransformHint);
+ outResult.layerId = layer->sequence;
+ outResult.layerName = String16(layer->getDebugName());
return result;
}
-status_t SurfaceFlinger::createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format,
- sp<IBinder>* handle,
- sp<IGraphicBufferProducer>* gbp,
- sp<Layer>* 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<BufferQueueLayer> 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<IBinder>* handle,
sp<Layer>* outLayer) {
- args.textureName = getNewTexture();
*outLayer = getFactory().createBufferStateLayer(args);
*handle = (*outLayer)->getHandle();
return NO_ERROR;
@@ -4822,41 +5823,47 @@ status_t SurfaceFlinger::createEffectLayer(const LayerCreationArgs& args, sp<IBi
return NO_ERROR;
}
-status_t SurfaceFlinger::createContainerLayer(const LayerCreationArgs& args, sp<IBinder>* handle,
- sp<Layer>* outLayer) {
- *outLayer = getFactory().createContainerLayer(args);
- *handle = (*outLayer)->getHandle();
- return NO_ERROR;
-}
-
void SurfaceFlinger::markLayerPendingRemovalLocked(const sp<Layer>& layer) {
mLayersPendingRemoval.add(layer);
mLayersRemoved = true;
setTransactionFlags(eTransactionNeeded);
}
-void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer) {
+void SurfaceFlinger::onHandleDestroyed(BBinder* handle, sp<Layer>& layer, uint32_t layerId) {
+ {
+ std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
+ mDestroyedHandles.emplace_back(layerId, layer->getDebugName());
+ }
+
+ mTransactionHandler.onLayerDestroyed(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<IBinder> 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<int>(getuid());
+ const uint64_t transactionId = (static_cast<uint64_t>(mPid) << 32) | mUniqueTransactionId++;
+ state.id = transactionId;
+
// reset screen orientation and use primary layer stack
- Vector<ComposerState> state;
- Vector<DisplayState> displays;
DisplayState d;
d.what = DisplayState::eDisplayProjectionChanged |
DisplayState::eLayerStackChanged;
@@ -4867,30 +5874,22 @@ void SurfaceFlinger::onInitializeDisplays() {
d.layerStackSpaceRect.makeInvalid();
d.width = 0;
d.height = 0;
- displays.add(d);
+ state.displays.add(d);
- nsecs_t now = systemTime();
-
- 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<TransactionState> 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 {
+ Mutex::Autolock lock(mStateLock);
+ applyAndCommitDisplayTransactionStatesLocked(transactions);
+ }
-void SurfaceFlinger::initializeDisplays() {
- // Async since we may be called from the main thread.
- static_cast<void>(
- mScheduler->schedule([this]() FTL_FAKE_GUARD(mStateLock) { onInitializeDisplays(); }));
+ {
+ ftl::FakeGuard guard(mStateLock);
+ setPowerModeInternal(display, hal::PowerMode::ON);
+ }
}
void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal::PowerMode mode) {
@@ -4902,77 +5901,129 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& display, hal:
const auto displayId = display->getPhysicalId();
ALOGD("Setting power mode %d on display %s", mode, to_string(displayId).c_str());
- std::optional<hal::PowerMode> 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<int32_t>(mode));
- }
- const auto refreshRate = display->refreshRateConfigs().getActiveMode()->getFps();
- if (*currentMode == hal::PowerMode::OFF) {
+ const auto activeMode = display->refreshRateSelector().getActiveMode().modePtr;
+ 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 (mode != hal::PowerMode::DOZE_SUSPEND &&
+ (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
+ const bool enable =
+ mScheduler->getVsyncSchedule(displayId)->getPendingHardwareVsyncState();
+ requestHardwareVsync(displayId, enable);
+
+ if (displayId == mActiveDisplayId) {
+ mScheduler->enableSyntheticVsync(false);
+ }
+
+ constexpr bool kAllowToEnable = true;
+ mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, activeMode.get());
}
mVisibleRegionsDirty = true;
- mHasPoweredOff = true;
scheduleComposite(FrameHint::kActive);
} else if (mode == hal::PowerMode::OFF) {
+ const bool currentModeNotDozeSuspend = (*currentModeOpt != hal::PowerMode::DOZE_SUSPEND);
// 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 (displayId == mActiveDisplayId) {
+ if (const auto display = getActivatableDisplay()) {
+ onActiveDisplayChangedLocked(activeDisplay.get(), *display);
+ } else {
+ 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 (currentModeNotDozeSuspend) {
+ if (!FlagManager::getInstance().multithreaded_present()) {
+ mScheduler->disableHardwareVsync(displayId, true);
+ }
+ mScheduler->enableSyntheticVsync();
+ }
+ }
}
- if (isDisplayActiveLocked(display) && *currentMode != hal::PowerMode::DOZE_SUSPEND) {
- mScheduler->disableHardwareVsync(true);
- mScheduler->onScreenReleased(mAppConnectionHandle);
+ if (currentModeNotDozeSuspend && FlagManager::getInstance().multithreaded_present()) {
+ constexpr bool kDisallow = true;
+ mScheduler->disableHardwareVsync(displayId, kDisallow);
}
- // Make sure HWVsync is disabled before turning off the display
- setHWCVsyncEnabled(displayId, hal::Vsync::DISABLE);
-
+ // We must disable VSYNC *before* turning off the display. The call to
+ // disableHardwareVsync, above, schedules a task to turn it off after
+ // this method returns. But by that point, the display is OFF, so the
+ // call just updates the pending state, without actually disabling
+ // VSYNC.
+ requestHardwareVsync(displayId, false);
getHwComposer().setPowerMode(displayId, mode);
+
mVisibleRegionsDirty = true;
// from this point on, SF will stop drawing on this display
} 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 (*currentModeOpt == hal::PowerMode::DOZE_SUSPEND &&
+ (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present())) {
+ if (displayId == mActiveDisplayId) {
+ ALOGI("Force repainting for DOZE_SUSPEND -> DOZE or ON.");
+ mVisibleRegionsDirty = true;
+ scheduleRepaint();
+ mScheduler->enableSyntheticVsync(false);
+ }
+ constexpr bool kAllowToEnable = true;
+ mScheduler->resyncToHardwareVsync(displayId, kAllowToEnable, activeMode.get());
}
} else if (mode == hal::PowerMode::DOZE_SUSPEND) {
// Leave display going to doze
- if (isDisplayActiveLocked(display)) {
- mScheduler->disableHardwareVsync(true);
- mScheduler->onScreenReleased(mAppConnectionHandle);
+ if (displayId == mActiveDisplayId || FlagManager::getInstance().multithreaded_present()) {
+ constexpr bool kDisallow = true;
+ mScheduler->disableHardwareVsync(displayId, kDisallow);
+ }
+ if (displayId == mActiveDisplayId) {
+ mScheduler->enableSyntheticVsync();
}
getHwComposer().setPowerMode(displayId, mode);
} else {
@@ -4980,17 +6031,19 @@ void SurfaceFlinger::setPowerModeInternal(const sp<DisplayDevice>& 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<IBinder>& 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,
@@ -5016,91 +6069,65 @@ status_t SurfaceFlinger::doDump(int fd, const DumpArgs& args, bool asProto) {
!PermissionCache::checkPermission(sDump, pid, uid)) {
StringAppendF(&result, "Permission Denial: can't dump SurfaceFlinger from pid=%d, uid=%d\n",
pid, uid);
- } else {
- static const std::unordered_map<std::string, Dumper> dumpers = {
- {"--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)},
- {"--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)},
- {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
- {"--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]));
-
- // Traversal of drawing state must happen on the main thread.
- // Otherwise, SortedVector may have shared ownership during concurrent
- // traversals, which can result in use-after-frees.
- std::string compositionLayers;
- mScheduler
- ->schedule([&] {
- StringAppendF(&compositionLayers, "Composition layers\n");
- mDrawingState.traverseInZOrder([&](Layer* layer) {
- auto* compositionState = layer->getCompositionState();
- if (!compositionState || !compositionState->isVisible) return;
-
- android::base::StringAppendF(&compositionLayers, "* Layer %p (%s)\n", layer,
- layer->getDebugName() ? layer->getDebugName()
- : "<unknown>");
- compositionState->dump(compositionLayers);
- });
- })
- .get();
-
- bool dumpLayers = true;
- {
- TimedLock lock(mStateLock, s2ns(1), __func__);
- if (!lock.locked()) {
- StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
- strerror(-lock.status), lock.status);
- }
+ write(fd, result.c_str(), result.size());
+ return NO_ERROR;
+ }
- if (const auto it = dumpers.find(flag); it != dumpers.end()) {
- (it->second)(args, asProto, result);
- dumpLayers = false;
- } else if (!asProto) {
- dumpAllLocked(args, compositionLayers, result);
- }
- }
+ if (asProto && args.empty()) {
+ perfetto::protos::LayersTraceFileProto traceFileProto =
+ mLayerTracing.createTraceFileProto();
+ perfetto::protos::LayersSnapshotProto* layersTrace = traceFileProto.add_entry();
+ perfetto::protos::LayersProto layersProto = dumpProtoFromMainThread();
+ layersTrace->mutable_layers()->Swap(&layersProto);
+ auto displayProtos = dumpDisplayProto();
+ layersTrace->mutable_displays()->Swap(&displayProtos);
+ result.append(traceFileProto.SerializeAsString());
+ write(fd, result.c_str(), result.size());
+ return NO_ERROR;
+ }
- if (dumpLayers) {
- LayersTraceFileProto traceFileProto = mLayerTracing.createTraceFileProto();
- LayersTraceProto* layersTrace = traceFileProto.add_entry();
- LayersProto layersProto = dumpProtoFromMainThread();
- layersTrace->mutable_layers()->Swap(&layersProto);
- dumpDisplayProto(*layersTrace);
+ static const std::unordered_map<std::string, Dumper> dumpers = {
+ {"--comp-displays"s, dumper(&SurfaceFlinger::dumpCompositionDisplays)},
+ {"--display-id"s, dumper(&SurfaceFlinger::dumpDisplayIdentificationData)},
+ {"--displays"s, dumper(&SurfaceFlinger::dumpDisplays)},
+ {"--edid"s, argsDumper(&SurfaceFlinger::dumpRawDisplayIdentificationData)},
+ {"--events"s, dumper(&SurfaceFlinger::dumpEvents)},
+ {"--frametimeline"s, argsDumper(&SurfaceFlinger::dumpFrameTimeline)},
+ {"--frontend"s, mainThreadDumper(&SurfaceFlinger::dumpFrontEnd)},
+ {"--hdrinfo"s, dumper(&SurfaceFlinger::dumpHdrInfo)},
+ {"--hwclayers"s, mainThreadDumper(&SurfaceFlinger::dumpHwcLayersMinidump)},
+ {"--latency"s, argsDumper(&SurfaceFlinger::dumpStatsLocked)},
+ {"--latency-clear"s, argsDumper(&SurfaceFlinger::clearStatsLocked)},
+ {"--list"s, dumper(&SurfaceFlinger::listLayersLocked)},
+ {"--planner"s, argsDumper(&SurfaceFlinger::dumpPlannerInfo)},
+ {"--scheduler"s, dumper(&SurfaceFlinger::dumpScheduler)},
+ {"--timestats"s, protoDumper(&SurfaceFlinger::dumpTimeStats)},
+ {"--vsync"s, dumper(&SurfaceFlinger::dumpVsync)},
+ {"--wide-color"s, dumper(&SurfaceFlinger::dumpWideColorInfo)},
+ };
- if (asProto) {
- result.append(traceFileProto.SerializeAsString());
- } else {
- // Dump info that we need to access from the main thread
- const auto layerTree = LayerProtoParser::generateLayerTree(layersTrace->layers());
- result.append(LayerProtoParser::layerTreeToString(layerTree));
- result.append("\n");
- dumpOffscreenLayers(result);
- }
- }
+ const auto flag = args.empty() ? ""s : std::string(String8(args[0]));
+ if (const auto it = dumpers.find(flag); it != dumpers.end()) {
+ (it->second)(args, asProto, result);
+ write(fd, result.c_str(), result.size());
+ return NO_ERROR;
}
+
+ // Traversal of drawing state must happen on the main thread.
+ // Otherwise, SortedVector may have shared ownership during concurrent
+ // traversals, which can result in use-after-frees.
+ std::string compositionLayers;
+ mScheduler
+ ->schedule([&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+ dumpVisibleFrontEnd(compositionLayers);
+ })
+ .get();
+ dumpAll(args, compositionLayers, result);
write(fd, result.c_str(), result.size());
return NO_ERROR;
}
status_t SurfaceFlinger::dumpCritical(int fd, const DumpArgs&, bool asProto) {
- if (asProto) {
- mLayerTracing.writeToFile();
- if (mTransactionTracing) {
- mTransactionTracing->writeToFile();
- }
- }
-
return doDump(fd, DumpArgs(), asProto);
}
@@ -5111,17 +6138,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.string()) {
- 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&) {
@@ -5129,12 +6153,10 @@ void SurfaceFlinger::clearStatsLocked(const DumpArgs& args, std::string&) {
const auto name = clearAll ? String8() : String8(args[1]);
mCurrentState.traverse([&](Layer* layer) {
- if (clearAll || layer->getName() == name.string()) {
+ if (clearAll || layer->getName() == name.c_str()) {
layer->clearFrameStats();
}
});
-
- mAnimFrameTracker.clearStats();
}
void SurfaceFlinger::dumpTimeStats(const DumpArgs& args, bool asProto, std::string& result) const {
@@ -5145,12 +6167,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("<win-anim>");
+ ATRACE_CALL();
+ mDrawingState.traverse([&](Layer* layer) { layer->logFrameStats(); });
}
void SurfaceFlinger::appendSfConfigString(std::string& result) const {
@@ -5166,24 +6189,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 {
@@ -5193,21 +6223,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<float>(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<float>(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);
@@ -5216,9 +6231,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);
+ }
}
}
@@ -5273,47 +6304,156 @@ 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 uses color management: %d\n", useColorManagement);
+ StringAppendF(&result, "Device supports wide color: %d\n", mSupportsWideColor);
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<ColorMode> 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>& layer : mDrawingState.layersSortedByZ) {
- layer->writeToProto(layersProto, traceFlags);
+void SurfaceFlinger::dumpHdrInfo(std::string& result) const {
+ for (const auto& [displayId, listener] : mHdrLayerInfoListeners) {
+ StringAppendF(&result, "HDR events for display %" PRIu64 "\n", displayId.value);
+ listener->dump(result);
+ result.append("\n");
+ }
+}
+
+void SurfaceFlinger::dumpFrontEnd(std::string& result) {
+ std::ostringstream out;
+ out << "\nComposition list\n";
+ ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+ for (const auto& snapshot : mLayerSnapshotBuilder.getSnapshots()) {
+ if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+ lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+ out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+ }
+ out << " " << *snapshot << "\n";
}
- return layersProto;
+ out << "\nInput list\n";
+ lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+ mLayerSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+ if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+ lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+ out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+ }
+ out << " " << snapshot << "\n";
+ });
+
+ out << "\nLayer Hierarchy\n"
+ << mLayerHierarchyBuilder.getHierarchy().dump() << "\nOffscreen Hierarchy\n"
+ << mLayerHierarchyBuilder.getOffscreenHierarchy().dump() << "\n\n";
+ result.append(out.str());
}
-void SurfaceFlinger::dumpDisplayProto(LayersTraceProto& layersTraceProto) const {
+void SurfaceFlinger::dumpVisibleFrontEnd(std::string& result) {
+ if (!mLayerLifecycleManagerEnabled) {
+ StringAppendF(&result, "Composition layers\n");
+ mDrawingState.traverseInZOrder([&](Layer* layer) {
+ auto* compositionState = layer->getCompositionState();
+ if (!compositionState || !compositionState->isVisible) return;
+ android::base::StringAppendF(&result, "* Layer %p (%s)\n", layer,
+ layer->getDebugName() ? layer->getDebugName()
+ : "<unknown>");
+ compositionState->dump(result);
+ });
+
+ StringAppendF(&result, "Offscreen Layers\n");
+ for (Layer* offscreenLayer : mOffscreenLayers) {
+ offscreenLayer->traverse(LayerVector::StateSet::Drawing,
+ [&](Layer* layer) { layer->dumpOffscreenDebugInfo(result); });
+ }
+ } else {
+ std::ostringstream out;
+ out << "\nComposition list\n";
+ ui::LayerStack lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+ mLayerSnapshotBuilder.forEachVisibleSnapshot(
+ [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+ if (snapshot->hasSomethingToDraw()) {
+ if (lastPrintedLayerStackHeader != snapshot->outputFilter.layerStack) {
+ lastPrintedLayerStackHeader = snapshot->outputFilter.layerStack;
+ out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+ }
+ out << " " << *snapshot << "\n";
+ }
+ });
+
+ out << "\nInput list\n";
+ lastPrintedLayerStackHeader = ui::INVALID_LAYER_STACK;
+ mLayerSnapshotBuilder.forEachInputSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+ if (lastPrintedLayerStackHeader != snapshot.outputFilter.layerStack) {
+ lastPrintedLayerStackHeader = snapshot.outputFilter.layerStack;
+ out << "LayerStack=" << lastPrintedLayerStackHeader.id << "\n";
+ }
+ out << " " << snapshot << "\n";
+ });
+
+ out << "\nLayer Hierarchy\n"
+ << mLayerHierarchyBuilder.getHierarchy() << "\nOffscreen Hierarchy\n"
+ << mLayerHierarchyBuilder.getOffscreenHierarchy() << "\n\n";
+ result = out.str();
+ dumpHwcLayersMinidump(result);
+ }
+}
+
+perfetto::protos::LayersProto SurfaceFlinger::dumpDrawingStateProto(uint32_t traceFlags) const {
+ std::unordered_set<uint64_t> 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) {
+ perfetto::protos::LayersProto layersProto;
+ for (const sp<Layer>& layer : mDrawingState.layersSortedByZ) {
+ if (stackIdsToSkip.find(layer->getLayerStack().id) != stackIdsToSkip.end()) {
+ continue;
+ }
+ layer->writeToProto(layersProto, traceFlags);
+ }
+ return layersProto;
+ }
+
+ return LayerProtoFromSnapshotGenerator(mLayerSnapshotBuilder, mFrontEndDisplayInfos,
+ mLegacyLayers, traceFlags)
+ .generate(mLayerHierarchyBuilder.getHierarchy());
+}
+
+google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto>
+SurfaceFlinger::dumpDisplayProto() const {
+ google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> displays;
for (const auto& [_, display] : FTL_FAKE_GUARD(mStateLock, mDisplays)) {
- DisplayProto* displayProto = layersTraceProto.add_displays();
+ perfetto::protos::DisplayProto* displayProto = displays.Add();
displayProto->set_id(display->getId().value);
displayProto->set_name(display->getDisplayName());
displayProto->set_layer_stack(display->getLayerStack().id);
+
+ if (!display->isVirtual()) {
+ const auto dpi = display->refreshRateSelector().getActiveMode().modePtr->getDpi();
+ displayProto->set_dpi_x(dpi.x);
+ displayProto->set_dpi_y(dpi.y);
+ }
+
LayerProtoHelper::writeSizeToProto(display->getWidth(), display->getHeight(),
[&]() { return displayProto->mutable_size(); });
LayerProtoHelper::writeToProto(display->getLayerStackSpaceRect(), [&]() {
@@ -5323,16 +6463,18 @@ void SurfaceFlinger::dumpDisplayProto(LayersTraceProto& layersTraceProto) const
displayProto->mutable_transform());
displayProto->set_is_virtual(display->isVirtual());
}
+ return displays;
}
void SurfaceFlinger::dumpHwc(std::string& result) const {
getHwComposer().dump(result);
}
-void SurfaceFlinger::dumpOffscreenLayersProto(LayersProto& layersProto, uint32_t traceFlags) const {
+void SurfaceFlinger::dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
+ uint32_t traceFlags) const {
// Add a fake invisible root layer to the proto output and parent all the offscreen layers to
// it.
- LayerProto* rootProto = layersProto.add_layers();
+ perfetto::protos::LayerProto* rootProto = layersProto.add_layers();
const int32_t offscreenRootLayerId = INT32_MAX - 2;
rootProto->set_id(offscreenRootLayerId);
rootProto->set_name("Offscreen Root");
@@ -5343,12 +6485,12 @@ void SurfaceFlinger::dumpOffscreenLayersProto(LayersProto& layersProto, uint32_t
rootProto->add_children(offscreenLayer->sequence);
// Add layer
- LayerProto* layerProto = offscreenLayer->writeToProto(layersProto, traceFlags);
+ auto* layerProto = offscreenLayer->writeToProto(layersProto, traceFlags);
layerProto->set_parent(offscreenRootLayerId);
}
}
-LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
+perfetto::protos::LayersProto SurfaceFlinger::dumpProtoFromMainThread(uint32_t traceFlags) {
return mScheduler->schedule([=] { return dumpDrawingStateProto(traceFlags); }).get();
}
@@ -5357,7 +6499,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;
});
@@ -5366,8 +6508,61 @@ void SurfaceFlinger::dumpOffscreenLayers(std::string& result) {
result.append(future.get());
}
-void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
- std::string& result) const {
+void SurfaceFlinger::dumpHwcLayersMinidumpLockedLegacy(std::string& result) const {
+ for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, 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->miniDumpLegacy(result, ref); });
+ result.append("\n");
+ }
+}
+
+void SurfaceFlinger::dumpHwcLayersMinidump(std::string& result) const {
+ if (!mLayerLifecycleManagerEnabled) {
+ return dumpHwcLayersMinidumpLockedLegacy(result);
+ }
+ for (const auto& [token, display] : FTL_FAKE_GUARD(mStateLock, 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;
+ mLayerSnapshotBuilder.forEachVisibleSnapshot([&](const frontend::LayerSnapshot& snapshot) {
+ if (!snapshot.hasSomethingToDraw() ||
+ ref.getLayerStack() != snapshot.outputFilter.layerStack) {
+ return;
+ }
+ auto it = mLegacyLayers.find(snapshot.sequence);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+ "Couldnt find layer object for %s",
+ snapshot.getDebugString().c_str());
+ it->second->miniDump(result, snapshot, ref);
+ });
+ result.append("\n");
+ }
+}
+
+void SurfaceFlinger::dumpAll(const DumpArgs& args, const std::string& compositionLayers,
+ std::string& result) const {
+ TimedLock lock(mStateLock, s2ns(1), __func__);
+ if (!lock.locked()) {
+ StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
+ strerror(-lock.status), lock.status);
+ }
+
const bool colorize = !args.empty() && args[0] == String16("--color");
Colorizer colorizer(colorize);
@@ -5392,6 +6587,8 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp
result.append("\nWide-Color information:\n");
dumpWideColorInfo(result);
+ dumpHdrInfo(result);
+
colorizer.bold(result);
result.append("Sync configuration: ");
colorizer.reset(result);
@@ -5401,21 +6598,19 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp
colorizer.bold(result);
result.append("Scheduler:\n");
colorizer.reset(result);
- dumpVSync(result);
+ dumpScheduler(result);
+ dumpEvents(result);
+ dumpVsync(result);
result.append("\n");
- dumpStaticScreenStats(result);
- result.append("\n");
-
- StringAppendF(&result, "Total missed frame count: %u\n", mFrameMissedCount.load());
- StringAppendF(&result, "HWC missed frame count: %u\n", mHwcFrameMissedCount.load());
- StringAppendF(&result, "GPU missed frame count: %u\n\n", mGpuFrameMissedCount.load());
-
/*
* Dump the visible layer list
*/
colorizer.bold(result);
- StringAppendF(&result, "Visible layers (count = %zu)\n", mNumLayers.load());
+ StringAppendF(&result, "SurfaceFlinger New Frontend Enabled:%s\n",
+ mLayerLifecycleManagerEnabled ? "true" : "false");
+ StringAppendF(&result, "Active Layers - layers with client handles (count = %zu)\n",
+ mNumLayers.load());
colorizer.reset(result);
result.append(compositionLayers);
@@ -5449,17 +6644,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->getVsyncRate());
- const auto dpi = activeMode->getDpi();
+ const auto dpi = activeModePtr->getDpi();
xDpi = base::StringPrintf("%.2f", dpi.x);
yDpi = base::StringPrintf("%.2f", dpi.y);
} else {
@@ -5476,11 +6669,6 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp
StringAppendF(&result, " transaction time: %f us\n", inTransactionDuration / 1000.0);
- /*
- * Tracing state
- */
- mLayerTracing.dump(result);
-
result.append("\nTransaction tracing: ");
if (mTransactionTracing) {
result.append("enabled\n");
@@ -5490,22 +6678,8 @@ 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");
+ if (mLegacyFrontEndEnabled) {
+ dumpHwcLayersMinidumpLockedLegacy(result);
}
{
@@ -5534,10 +6708,19 @@ void SurfaceFlinger::dumpAllLocked(const DumpArgs& args, const std::string& comp
/*
* Dump flag/property manager state
*/
- mFlagManager.dump(result);
+ FlagManager::getInstance().dump(result);
result.append(mTimeStats->miniDump());
result.append("\n");
+
+ result.append("Window Infos:\n");
+ auto windowInfosDebug = mWindowInfosListenerInvoker->getDebugInfo();
+ StringAppendF(&result, " max send vsync id: %" PRId64 "\n",
+ ftl::to_underlying(windowInfosDebug.maxSendDelayVsyncId));
+ 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) {
@@ -5569,29 +6752,11 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) {
#pragma clang diagnostic push
#pragma clang diagnostic error "-Wswitch-enum"
switch (static_cast<ISurfaceComposerTag>(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.
@@ -5604,100 +6769,39 @@ 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:
+ case GET_SCHEDULING_POLICY:
// 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:
@@ -5705,12 +6809,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;
}
@@ -5723,9 +6858,9 @@ status_t SurfaceFlinger::CheckTransactCodeCredentials(uint32_t code) {
code == IBinder::SYSPROPS_TRANSACTION) {
return OK;
}
- // Numbers from 1000 to 1042 are currently used for backdoors. The code
+ // Numbers from 1000 to 1045 are currently used for backdoors. The code
// in onTransact verifies that the user is root, and has access to use SF.
- if (code >= 1000 && code <= 1042) {
+ if (code >= 1000 && code <= 1045) {
ALOGV("Accessing SurfaceFlinger through backdoor code: %u", code);
return OK;
}
@@ -5758,28 +6893,20 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
case 1001:
return NAME_NOT_FOUND;
case 1002: // Toggle flashing on surface damage.
- if (const int delay = data.readInt32(); delay > 0) {
- mDebugFlashDelay = delay;
- } else {
- mDebugFlashDelay = mDebugFlashDelay ? 0 : 1;
- }
- scheduleRepaint();
+ sfdo_setDebugFlash(data.readInt32());
return NO_ERROR;
case 1004: // Force composite ahead of next VSYNC.
case 1006:
- scheduleComposite(FrameHint::kActive);
+ sfdo_scheduleComposite();
return NO_ERROR;
case 1005: { // Force commit ahead of next VSYNC.
- Mutex::Autolock lock(mStateLock);
- setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded |
- eTraversalNeeded);
+ sfdo_scheduleCommit();
return NO_ERROR;
}
case 1007: // Unused.
return NAME_NOT_FOUND;
case 1008: // Toggle forced GPU composition.
- mDebugDisableHWC = data.readInt32() != 0;
- scheduleRepaint();
+ sfdo_forceClientComposition(data.readInt32() != 0);
return NO_ERROR;
case 1009: // Toggle use of transform hint.
mDebugDisableTransformHint = data.readInt32() != 0;
@@ -5792,15 +6919,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
@@ -5871,17 +6991,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;
@@ -5896,12 +7007,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<DisplayColorSetting>(data.readInt32());
- if (data.readInt32(&colorMode) == NO_ERROR) {
- mForceColorMode = static_cast<ColorMode>(colorMode);
+
+ if (int32_t colorMode; data.readInt32(&colorMode) == NO_ERROR) {
+ mForceColorMode = static_cast<ui::ColorMode>(colorMode);
}
scheduleRepaint();
return NO_ERROR;
@@ -5910,37 +7020,13 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
case 1024: {
return NAME_NOT_FOUND;
}
- case 1025: { // Set layer tracing
- n = data.readInt32();
- bool tracingEnabledChanged;
- if (n == 1) {
- int64_t fixedStartingTime = data.readInt64();
- ALOGD("LayerTracing enabled");
- tracingEnabledChanged = mLayerTracing.enable();
- if (tracingEnabledChanged) {
- int64_t startingTime =
- (fixedStartingTime) ? fixedStartingTime : systemTime();
- mScheduler
- ->schedule([&]() FTL_FAKE_GUARD(mStateLock) {
- mLayerTracing.notify("start", startingTime);
- })
- .wait();
- }
- } else if (n == 2) {
- std::string filename = std::string(data.readCString());
- ALOGD("LayerTracing disabled. Trace wrote to %s", filename.c_str());
- tracingEnabledChanged = mLayerTracing.disable(filename.c_str());
- } else {
- ALOGD("LayerTracing disabled");
- tracingEnabledChanged = mLayerTracing.disable();
- }
- mTracingEnabledChanged = tracingEnabledChanged;
- reply->writeInt32(NO_ERROR);
- return NO_ERROR;
+ // Deprecated, use perfetto to start/stop the layer tracing
+ case 1025: {
+ return NAME_NOT_FOUND;
}
- case 1026: { // Get layer tracing status
- reply->writeBool(mLayerTracing.isEnabled());
- return NO_ERROR;
+ // Deprecated, execute "adb shell perfetto --query" to see the ongoing tracing sessions
+ case 1026: {
+ return NAME_NOT_FOUND;
}
// Is a DisplayColorSetting supported?
case 1027: {
@@ -5952,8 +7038,6 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
DisplayColorSetting setting = static_cast<DisplayColorSetting>(data.readInt32());
switch (setting) {
case DisplayColorSetting::kManaged:
- reply->writeBool(useColorManagement);
- break;
case DisplayColorSetting::kUnmanaged:
reply->writeBool(true);
break;
@@ -5970,23 +7054,14 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
case 1028: { // Unused.
return NAME_NOT_FOUND;
}
- // Set buffer size for SF tracing (value in KB)
+ // Deprecated, use perfetto to set the active layer tracing buffer size
case 1029: {
- n = data.readInt32();
- if (n <= 0 || n > MAX_TRACING_MEMORY) {
- ALOGW("Invalid buffer size: %d KB", n);
- reply->writeInt32(BAD_VALUE);
- return BAD_VALUE;
- }
-
- ALOGD("Updating trace buffer to %d KB", n);
- mLayerTracing.setBufferSize(n * 1024);
- reply->writeInt32(NO_ERROR);
- return NO_ERROR;
+ return NAME_NOT_FOUND;
}
// Is device color managed?
case 1030: {
- reply->writeBool(useColorManagement);
+ // ColorDisplayManager stil calls this
+ reply->writeBool(true);
return NO_ERROR;
}
// Override default composition data space
@@ -6021,33 +7096,27 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
}
return NO_ERROR;
}
- // Set trace flags
+ // Deprecated, use perfetto to set layer trace flags
case 1033: {
- n = data.readUint32();
- ALOGD("Updating trace flags to 0x%x", n);
- mLayerTracing.setTraceFlags(n);
- reply->writeInt32(NO_ERROR);
- return NO_ERROR;
+ return NAME_NOT_FOUND;
}
case 1034: {
- auto future = mScheduler->schedule([&] {
- switch (n = data.readInt32()) {
- case 0:
- case 1:
- FTL_FAKE_GUARD(mStateLock,
- enableRefreshRateOverlay(static_cast<bool>(n)));
- break;
- default: {
- reply->writeBool(
- FTL_FAKE_GUARD(mStateLock, isRefreshRateOverlayEnabled()));
- }
- }
- });
-
- future.wait();
+ n = data.readInt32();
+ if (n == 0 || n == 1) {
+ sfdo_enableRefreshRateOverlay(static_cast<bool>(n));
+ } else {
+ Mutex::Autolock lock(mStateLock);
+ reply->writeBool(isRefreshRateOverlayEnabled());
+ }
return NO_ERROR;
}
case 1035: {
+ // Parameters:
+ // - (required) i32 mode id.
+ // - (optional) i64 display id. Using default display if not provided.
+ // - (optional) f min render rate. Using mode's fps is not provided.
+ // - (optional) f max render rate. Using mode's fps is not provided.
+
const int modeId = data.readInt32();
const auto display = [&]() -> sp<IBinder> {
@@ -6064,8 +7133,21 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
return nullptr;
}();
+ const auto getFps = [&] {
+ float value;
+ if (data.readFloat(&value) == NO_ERROR) {
+ return Fps::fromValue(value);
+ }
+
+ return Fps();
+ };
+
+ const auto minFps = getFps();
+ const auto maxFps = getFps();
+
mDebugDisplayModeSetByBackdoor = false;
- const status_t result = setActiveModeFromBackdoor(display, modeId);
+ const status_t result =
+ setActiveModeFromBackdoor(display, DisplayModeId{modeId}, minFps, maxFps);
mDebugDisplayModeSetByBackdoor = result == NO_ERROR;
return result;
}
@@ -6075,7 +7157,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());
@@ -6085,24 +7167,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();
}
@@ -6113,7 +7193,7 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
const hal::HWDisplayId hwcId =
(Mutex::Autolock(mStateLock), getHwComposer().getPrimaryHwcDisplayId());
- onComposerHalHotplug(hwcId, hal::Connection::CONNECTED);
+ onComposerHalHotplugEvent(hwcId, DisplayHotplugEvent::CONNECTED);
return NO_ERROR;
}
// Modify the max number of display frames stored within FrameTimeline
@@ -6178,13 +7258,16 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
}
case 1041: { // Transaction tracing
if (mTransactionTracing) {
- if (data.readInt32()) {
+ int arg = data.readInt32();
+ if (arg == -1) {
+ mScheduler->schedule([&]() { mTransactionTracing.reset(); }).get();
+ } else if (arg > 0) {
// Transaction tracing is always running but allow the user to temporarily
// increase the buffer when actively debugging.
mTransactionTracing->setBufferSize(
- TransactionTracing::ACTIVE_TRACING_BUFFER_SIZE);
+ TransactionTracing::LEGACY_ACTIVE_TRACING_BUFFER_SIZE);
} else {
- mTransactionTracing->writeToFile();
+ TransactionTraceWriter::getInstance().invoke("", /* overwrite= */ true);
mTransactionTracing->setBufferSize(
TransactionTracing::CONTINUOUS_TRACING_BUFFER_SIZE);
}
@@ -6192,16 +7275,131 @@ status_t SurfaceFlinger::onTransact(uint32_t code, const Parcel& data, Parcel* r
reply->writeInt32(NO_ERROR);
return NO_ERROR;
}
- case 1042: { // Write layers trace or transaction trace to file
+ case 1042: { // Write transaction trace to file
if (mTransactionTracing) {
mTransactionTracing->writeToFile();
}
- if (mLayerTracingEnabled) {
- mLayerTracing.writeToFile();
- }
reply->writeInt32(NO_ERROR);
return NO_ERROR;
}
+ // hdr sdr ratio overlay
+ case 1043: {
+ auto future = mScheduler->schedule(
+ [&]() FTL_FAKE_GUARD(mStateLock) FTL_FAKE_GUARD(kMainThreadContext) {
+ n = data.readInt32();
+ mHdrSdrRatioOverlay = n != 0;
+ switch (n) {
+ case 0:
+ case 1:
+ enableHdrSdrRatioOverlay(mHdrSdrRatioOverlay);
+ break;
+ default:
+ reply->writeBool(isHdrSdrRatioOverlayEnabled());
+ }
+ });
+ future.wait();
+ return NO_ERROR;
+ }
+
+ case 1044: { // Enable/Disable mirroring from one display to another
+ /*
+ * Mirror one display onto another.
+ * Ensure the source and destination displays are on.
+ * Commands:
+ * 0: Mirror one display to another
+ * 1: Disable mirroring to a previously mirrored display
+ * 2: Disable mirroring on previously mirrored displays
+ *
+ * Ex:
+ * Get the display ids:
+ * adb shell dumpsys SurfaceFlinger --display-id
+ * Mirror first display to the second:
+ * adb shell service call SurfaceFlinger 1044 i64 0 i64 4619827677550801152 i64
+ * 4619827677550801153
+ * Stop mirroring:
+ * adb shell service call SurfaceFlinger 1044 i64 1
+ */
+
+ int64_t arg0 = data.readInt64();
+
+ switch (arg0) {
+ case 0: {
+ // Mirror arg1 to arg2
+ int64_t arg1 = data.readInt64();
+ int64_t arg2 = data.readInt64();
+ // Enable mirroring for one display
+ const auto display1id = DisplayId::fromValue(arg1);
+ auto mirrorRoot = SurfaceComposerClient::getDefault()->mirrorDisplay(
+ display1id.value());
+ auto id2 = DisplayId::fromValue<PhysicalDisplayId>(arg2);
+ const auto token2 = getPhysicalDisplayToken(*id2);
+ ui::LayerStack layerStack;
+ {
+ Mutex::Autolock lock(mStateLock);
+ sp<DisplayDevice> display = getDisplayDeviceLocked(token2);
+ layerStack = display->getLayerStack();
+ }
+ SurfaceComposerClient::Transaction t;
+ t.setDisplayLayerStack(token2, layerStack);
+ t.setLayer(mirrorRoot, INT_MAX); // Top-most layer
+ t.setLayerStack(mirrorRoot, layerStack);
+ t.apply();
+
+ mMirrorMapForDebug.emplace_or_replace(arg2, mirrorRoot);
+ break;
+ }
+
+ case 1: {
+ // Disable mirroring for arg1
+ int64_t arg1 = data.readInt64();
+ mMirrorMapForDebug.erase(arg1);
+ break;
+ }
+
+ case 2: {
+ // Disable mirroring for all displays
+ mMirrorMapForDebug.clear();
+ break;
+ }
+
+ default:
+ return BAD_VALUE;
+ }
+ return NO_ERROR;
+ }
+ // Inject jank
+ // First argument is a float that describes the fraction of frame duration to jank by.
+ // Second argument is a delay in ms for triggering the jank. This is useful for working
+ // with tools that steal the adb connection. This argument is optional.
+ case 1045: {
+ if (FlagManager::getInstance().vrr_config()) {
+ float jankAmount = data.readFloat();
+ int32_t jankDelayMs = 0;
+ if (data.readInt32(&jankDelayMs) != NO_ERROR) {
+ jankDelayMs = 0;
+ }
+
+ const auto jankDelayDuration = Duration(std::chrono::milliseconds(jankDelayMs));
+
+ const bool jankAmountValid = jankAmount > 0.0 && jankAmount < 100.0;
+
+ if (!jankAmountValid) {
+ ALOGD("Ignoring invalid jank amount: %f", jankAmount);
+ reply->writeInt32(BAD_VALUE);
+ return BAD_VALUE;
+ }
+
+ (void)mScheduler->scheduleDelayed(
+ [&, jankAmount]() FTL_FAKE_GUARD(kMainThreadContext) {
+ mScheduler->injectPacesetterDelay(jankAmount);
+ scheduleComposite(FrameHint::kActive);
+ },
+ jankDelayDuration.ns());
+ reply->writeInt32(NO_ERROR);
+ return NO_ERROR;
+ }
+ return err;
+ }
}
}
return err;
@@ -6213,7 +7411,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<void>(mScheduler->schedule([=] {
const auto display = FTL_FAKE_GUARD(mStateLock, getDefaultDisplayDeviceLocked());
if (!display) {
@@ -6222,14 +7420,14 @@ void SurfaceFlinger::kernelTimerChanged(bool expired) {
}
if (!display->isRefreshRateOverlayEnabled()) return;
- const auto desiredActiveMode = display->getDesiredActiveMode();
- const std::optional<DisplayModeId> desiredModeId = desiredActiveMode
- ? std::make_optional(desiredActiveMode->mode->getId())
- : std::nullopt;
+ const auto desiredModeIdOpt =
+ display->getDesiredMode().transform([](const display::DisplayModeRequest& request) {
+ return request.mode.modePtr->getId();
+ });
const bool timerExpired = mKernelIdleTimerEnabled && expired;
- if (display->onKernelTimerChanged(desiredModeId, timerExpired)) {
+ if (display->onKernelTimerChanged(desiredModeIdOpt, timerExpired)) {
mScheduler->scheduleFrame();
}
}));
@@ -6277,7 +7475,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) {
@@ -6288,12 +7486,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> 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:
@@ -6309,7 +7507,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;
@@ -6331,18 +7529,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();
@@ -6399,20 +7585,6 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) {
return NO_ERROR;
}
- // Currently, there is no wrapper in bionic: b/183240349.
- struct sched_attr {
- uint32_t size;
- uint32_t sched_policy;
- uint64_t sched_flags;
- int32_t sched_nice;
- uint32_t sched_priority;
- uint64_t sched_runtime;
- uint64_t sched_deadline;
- uint64_t sched_period;
- uint32_t sched_util_min;
- uint32_t sched_util_max;
- };
-
sched_attr attr = {};
attr.size = sizeof(attr);
@@ -6427,25 +7599,72 @@ status_t SurfaceFlinger::setSchedAttr(bool enabled) {
return NO_ERROR;
}
-status_t SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
- const sp<IScreenCaptureListener>& captureListener) {
+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
+
+static void invokeScreenCaptureError(const status_t status,
+ const sp<IScreenCaptureListener>& captureListener) {
+ ScreenCaptureResults captureResults;
+ captureResults.fenceResult = base::unexpected(status);
+ captureListener->onScreenCaptureCompleted(captureResults);
+}
+
+void SurfaceFlinger::captureDisplay(const DisplayCaptureArgs& args,
+ const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
status_t validate = validateScreenshotPermissions(args);
if (validate != OK) {
- return validate;
+ invokeScreenCaptureError(validate, captureListener);
+ return;
}
- if (!args.displayToken) return BAD_VALUE;
+ if (!args.displayToken) {
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
+ }
+
+ if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+ ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+ invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
+ return;
+ }
wp<const DisplayDevice> displayWeak;
ui::LayerStack layerStack;
ui::Size reqSize(args.width, args.height);
- ui::Dataspace dataspace;
+ std::unordered_set<uint32_t> excludeLayerIds;
{
Mutex::Autolock lock(mStateLock);
sp<DisplayDevice> display = getDisplayDeviceLocked(args.displayToken);
- if (!display) return NAME_NOT_FOUND;
+ if (!display) {
+ invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+ return;
+ }
displayWeak = display;
layerStack = display->getLayerStack();
@@ -6454,109 +7673,131 @@ 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");
+ invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+ return;
+ }
}
}
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.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,
- args.pixelFormat, args.allowProtected, args.grayscale,
- captureListener);
- return fenceStatus(future.get());
+ captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
+ args.allowProtected, args.grayscale, captureListener);
}
-status_t SurfaceFlinger::captureDisplay(DisplayId displayId,
- const sp<IScreenCaptureListener>& captureListener) {
+void SurfaceFlinger::captureDisplay(DisplayId displayId, const CaptureArgs& args,
+ const sp<IScreenCaptureListener>& captureListener) {
ui::LayerStack layerStack;
wp<const DisplayDevice> displayWeak;
ui::Size size;
- ui::Dataspace dataspace;
{
Mutex::Autolock lock(mStateLock);
const auto display = getDisplayDeviceLocked(displayId);
if (!display) {
- return NAME_NOT_FOUND;
+ invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+ return;
}
displayWeak = display;
layerStack = display->getLayerStack();
size = display->getLayerStackSpaceRect().getSize();
+ }
+
+ size.width *= args.frameScaleX;
+ size.height *= args.frameScaleY;
- dataspace =
- pickDataspaceFromColorMode(display->getCompositionDisplay()->getState().colorMode);
+ // We could query a real value for this but it'll be a long, long time until we support
+ // displays that need upwards of 1GB per buffer so...
+ constexpr auto kMaxTextureSize = 16384;
+ if (size.width <= 0 || size.height <= 0 || size.width >= kMaxTextureSize ||
+ size.height >= kMaxTextureSize) {
+ ALOGE("capture display resolved to invalid size %d x %d", size.width, size.height);
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
}
RenderAreaFuture renderAreaFuture = ftl::defer([=] {
- return DisplayRenderArea::create(displayWeak, Rect(), size, dataspace,
- false /* useIdentityTransform */,
+ return DisplayRenderArea::create(displayWeak, Rect(), size, args.dataspace,
+ args.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");
- return BAD_VALUE;
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
}
constexpr bool kAllowProtected = false;
constexpr bool kGrayscale = false;
- auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, size,
- ui::PixelFormat::RGBA_8888, kAllowProtected, kGrayscale,
- captureListener);
- return fenceStatus(future.get());
+ captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, size, args.pixelFormat,
+ kAllowProtected, kGrayscale, captureListener);
}
-status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
- const sp<IScreenCaptureListener>& captureListener) {
+void SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
+ const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
status_t validate = validateScreenshotPermissions(args);
if (validate != OK) {
- return validate;
+ invokeScreenCaptureError(validate, captureListener);
+ return;
}
ui::Size reqSize;
sp<Layer> parent;
Rect crop(args.sourceCrop);
- std::unordered_set<sp<Layer>, SpHash<Layer>> excludeLayers;
- ui::Dataspace dataspace;
+ std::unordered_set<uint32_t> excludeLayerIds;
+ ui::Dataspace dataspace = args.dataspace;
- // Call this before holding mStateLock to avoid any deadlocking.
- bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
+ if (args.captureSecureLayers && !hasCaptureBlackoutContentPermission()) {
+ ALOGE("Attempting to capture secure layers without CAPTURE_BLACKOUT_CONTENT");
+ invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
+ return;
+ }
{
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;
- }
-
- if (!canCaptureBlackoutContent &&
- parent->getDrawingState().flags & layer_state_t::eLayerSecure) {
- ALOGW("Attempting to capture secure layer: PERMISSION_DENIED");
- return PERMISSION_DENIED;
+ invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+ return;
}
Rect parentSourceBounds = parent->getCroppedBufferSize(parent->getDrawingState());
@@ -6573,85 +7814,112 @@ status_t SurfaceFlinger::captureLayers(const LayerCaptureArgs& args,
if (crop.isEmpty() || args.frameScaleX <= 0.0f || args.frameScaleY <= 0.0f) {
// Error out if the layer has no source bounds (i.e. they are boundless) and a source
// crop was not specified, or an invalid frame scale was provided.
- return BAD_VALUE;
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
}
reqSize = ui::Size(crop.width() * args.frameScaleX, crop.height() * args.frameScaleY);
for (const auto& handle : args.excludeHandles) {
- sp<Layer> 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;
+ invokeScreenCaptureError(NAME_NOT_FOUND, captureListener);
+ return;
}
}
-
- // 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
if (reqSize.width <= 0 || reqSize.height <= 0) {
ALOGW("Failed to captureLayes: crop or scale too small");
- return BAD_VALUE;
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
}
- Rect layerStackSpaceRect(0, 0, reqSize.width, reqSize.height);
bool childrenOnly = args.childrenOnly;
RenderAreaFuture renderAreaFuture = ftl::defer([=]() -> std::unique_ptr<RenderArea> {
+ 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<LayerRenderArea>(*this, parent, crop, reqSize, dataspace,
- childrenOnly, layerStackSpaceRect,
- args.captureSecureLayers);
+ childrenOnly, args.captureSecureLayers,
+ layerTransform, layerBufferSize,
+ args.hintForSeamlessTransition);
});
+ GetLayerSnapshotsFunction getLayerSnapshots;
+ if (mLayerLifecycleManagerEnabled) {
+ std::optional<FloatRect> 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<Layer> 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<Layer>::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;
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
}
- auto future = captureScreenCommon(std::move(renderAreaFuture), traverseLayers, reqSize,
- args.pixelFormat, args.allowProtected, args.grayscale,
- captureListener);
- return fenceStatus(future.get());
+ captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, reqSize, args.pixelFormat,
+ args.allowProtected, args.grayscale, captureListener);
}
-ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
- RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
- ui::Size bufferSize, ui::PixelFormat reqPixelFormat, bool allowProtected, bool grayscale,
- const sp<IScreenCaptureListener>& captureListener) {
+void SurfaceFlinger::captureScreenCommon(RenderAreaFuture renderAreaFuture,
+ GetLayerSnapshotsFunction getLayerSnapshots,
+ ui::Size bufferSize, ui::PixelFormat reqPixelFormat,
+ bool allowProtected, bool grayscale,
+ const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
if (exceedsMaxRenderTargetSize(bufferSize.getWidth(), bufferSize.getHeight())) {
ALOGE("Attempted to capture screen with size (%" PRId32 ", %" PRId32
") that exceeds render target size limit.",
bufferSize.getWidth(), bufferSize.getHeight());
- return ftl::yield<FenceResult>(base::unexpected(BAD_VALUE)).share();
+ invokeScreenCaptureError(BAD_VALUE, captureListener);
+ return;
}
// Loop over all visible layers to see whether there's any protected layer. A protected layer is
@@ -6661,22 +7929,24 @@ ftl::SharedFuture<FenceResult> 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 bool isProtected = hasProtectedLayer && allowProtected && supportsProtected;
const uint32_t usage = GRALLOC_USAGE_HW_COMPOSER | GRALLOC_USAGE_HW_RENDER |
GRALLOC_USAGE_HW_TEXTURE |
- (hasProtectedLayer && allowProtected && supportsProtected
- ? GRALLOC_USAGE_PROTECTED
- : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
+ (isProtected ? GRALLOC_USAGE_PROTECTED
+ : GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
sp<GraphicBuffer> buffer =
getFactory().createGraphicBuffer(bufferSize.getWidth(), bufferSize.getHeight(),
static_cast<android_pixel_format>(reqPixelFormat),
@@ -6688,60 +7958,59 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
// Otherwise an irreponsible process may cause an SF crash by allocating
// too much.
ALOGE("%s: Buffer failed to allocate: %d", __func__, bufferStatus);
- return ftl::yield<FenceResult>(base::unexpected(bufferStatus)).share();
+ invokeScreenCaptureError(bufferStatus, captureListener);
+ return;
}
const std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
renderengine::impl::ExternalTexture>(buffer, getRenderEngine(),
renderengine::impl::ExternalTexture::Usage::
WRITEABLE);
- return captureScreenCommon(std::move(renderAreaFuture), traverseLayers, texture,
- false /* regionSampling */, grayscale, captureListener);
+ auto fence = captureScreenCommon(std::move(renderAreaFuture), getLayerSnapshots, texture,
+ false /* regionSampling */, grayscale, isProtected,
+ captureListener);
+ fence.get();
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
- RenderAreaFuture renderAreaFuture, TraverseLayersFunction traverseLayers,
+ RenderAreaFuture renderAreaFuture, GetLayerSnapshotsFunction getLayerSnapshots,
const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
- bool grayscale, const sp<IScreenCaptureListener>& captureListener) {
+ bool grayscale, bool isProtected, const sp<IScreenCaptureListener>& captureListener) {
ATRACE_CALL();
- bool canCaptureBlackoutContent = hasCaptureBlackoutContentPermission();
-
- auto future = mScheduler->schedule([=, renderAreaFuture = std::move(renderAreaFuture)]() mutable
- -> ftl::SharedFuture<FenceResult> {
- ScreenCaptureResults captureResults;
- std::unique_ptr<RenderArea> renderArea = renderAreaFuture.get();
- if (!renderArea) {
- ALOGW("Skipping screen capture because of invalid render area.");
- captureResults.result = NO_MEMORY;
- captureListener->onScreenCaptureCompleted(captureResults);
- return ftl::yield<FenceResult>(base::unexpected(NO_ERROR)).share();
- }
+ auto future = mScheduler->schedule(
+ [=, renderAreaFuture = std::move(renderAreaFuture)]() FTL_FAKE_GUARD(
+ kMainThreadContext) mutable -> ftl::SharedFuture<FenceResult> {
+ ScreenCaptureResults captureResults;
+ std::shared_ptr<RenderArea> 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<FenceResult>(base::unexpected(NO_ERROR)).share();
+ }
- ftl::SharedFuture<FenceResult> renderFuture;
- renderArea->render([&] {
- renderFuture =
- renderScreenImpl(*renderArea, traverseLayers, buffer, canCaptureBlackoutContent,
- regionSampling, grayscale, captureResults);
- });
+ ftl::SharedFuture<FenceResult> renderFuture;
+ renderArea->render([&]() FTL_FAKE_GUARD(kMainThreadContext) {
+ renderFuture =
+ renderScreenImpl(renderArea, getLayerSnapshots, buffer, regionSampling,
+ grayscale, isProtected, captureResults);
+ });
- if (captureListener) {
- // TODO: The future returned by std::async blocks the main thread. Return a chain of
- // futures to the Binder thread instead.
- 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<FenceResult>(base::unexpected(NO_ERROR)).share();
- }
// Flatten nested futures.
auto chain = ftl::Future(std::move(future)).then([](ftl::SharedFuture<FenceResult> future) {
@@ -6752,167 +8021,192 @@ ftl::SharedFuture<FenceResult> SurfaceFlinger::captureScreenCommon(
}
ftl::SharedFuture<FenceResult> SurfaceFlinger::renderScreenImpl(
- const RenderArea& renderArea, TraverseLayersFunction traverseLayers,
- const std::shared_ptr<renderengine::ExternalTexture>& buffer,
- bool canCaptureBlackoutContent, bool regionSampling, bool grayscale,
- ScreenCaptureResults& captureResults) {
+ std::shared_ptr<const RenderArea> renderArea, GetLayerSnapshotsFunction getLayerSnapshots,
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer, bool regionSampling,
+ bool grayscale, bool isProtected, 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();
- // We allow the system server to take screenshots of secure layers for
- // use in situations like the Screen-rotation animation and place
- // the impetus on WindowManager to not persist them.
- if (captureResults.capturedSecureLayers && !canCaptureBlackoutContent) {
- ALOGW("FB is protected: PERMISSION_DENIED");
- return ftl::yield<FenceResult>(base::unexpected(PERMISSION_DENIED)).share();
+ 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();
}
- 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<compositionengine::LayerFE::LayerSettings> 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<aidl::android::hardware::graphics::composer3::RenderIntent>(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<Layer*> 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();
+ }
- };
- std::vector<compositionengine::LayerFE::LayerSettings> 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;
+ 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;
+
+ // TODO(b/298219334): Clean this up once we verify this doesn't break anything
+ static constexpr bool kScreenshotsDontDim = true;
+
+ if (kScreenshotsDontDim && !captureResults.capturedHdrLayers) {
+ displayBrightnessNits = sdrWhitePointNits;
+ } else {
+ displayBrightnessNits = state.displayBrightnessNits;
+ // Only clamp the display brightness if this is not a seamless transition. Otherwise
+ // for seamless transitions it's important to match the current display state as the
+ // buffer will be shown under these same conditions, and we want to avoid any
+ // flickers
+ if (sdrWhitePointNits > 1.0f && !renderArea->getHintForSeamlessTransition()) {
+ // Restrict the amount of HDR "headroom" in the screenshot to avoid over-dimming
+ // the SDR portion. 2.0 chosen by experimentation
+ constexpr float kMaxScreenshotHeadroom = 2.0f;
+ displayBrightnessNits = std::min(sdrWhitePointNits * kMaxScreenshotHeadroom,
+ displayBrightnessNits);
}
- captureResults.capturedHdrLayers |= isHdrLayer(layer);
}
- clientCompositionLayers.insert(clientCompositionLayers.end(),
- std::make_move_iterator(results.begin()),
- std::make_move_iterator(results.end()));
- renderedLayers.push_back(layer);
+ // Screenshots leaving the device should be colorimetric
+ if (requestedDataspace == ui::Dataspace::UNKNOWN &&
+ renderArea->getHintForSeamlessTransition()) {
+ renderIntent = state.renderIntent;
+ }
}
+ }
- });
+ captureResults.buffer = capturedBuffer->getBuffer();
+
+ ui::LayerStack layerStack{ui::DEFAULT_LAYER_STACK};
+ if (!layers.empty()) {
+ const sp<LayerFE>& layerFE = layers.back().second;
+ layerStack = layerFE->getCompositionState()->outputFilter.layerStack;
+ }
+
+ auto copyLayerFEs = [&layers]() {
+ std::vector<sp<compositionengine::LayerFE>> layerFEs;
+ layerFEs.reserve(layers.size());
+ for (const auto& [_, layerFE] : layers) {
+ layerFEs.push_back(layerFE);
+ }
+ return layerFEs;
+ };
- std::vector<renderengine::LayerSettings> clientRenderEngineLayers;
- clientRenderEngineLayers.reserve(clientCompositionLayers.size());
- std::transform(clientCompositionLayers.begin(), clientCompositionLayers.end(),
- std::back_inserter(clientRenderEngineLayers),
- [](compositionengine::LayerFE::LayerSettings& settings)
- -> renderengine::LayerSettings { return settings; });
+ auto present = [this, buffer = capturedBuffer, dataspace = captureResults.capturedDataspace,
+ sdrWhitePointNits, displayBrightnessNits, grayscale, isProtected,
+ layerFEs = copyLayerFEs(), layerStack, regionSampling,
+ renderArea = std::move(renderArea), renderIntent]() -> FenceResult {
+ std::unique_ptr<compositionengine::CompositionEngine> 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;
+ }
+ }
- // 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);
+ // Screenshots leaving the device must not dim in gamma space.
+ const bool dimInGammaSpaceForEnhancedScreenshots = mDimInGammaSpaceForEnhancedScreenshots &&
+ renderArea->getHintForSeamlessTransition();
+
+ std::shared_ptr<ScreenCaptureOutput> output = createScreenCaptureOutput(
+ ScreenCaptureOutputArgs{.compositionEngine = *compositionEngine,
+ .colorProfile = colorProfile,
+ .renderArea = *renderArea,
+ .layerStack = layerStack,
+ .buffer = std::move(buffer),
+ .sdrWhitePointNits = sdrWhitePointNits,
+ .displayBrightnessNits = displayBrightnessNits,
+ .targetBrightness = targetBrightness,
+ .regionSampling = regionSampling,
+ .treat170mAsSrgb = mTreat170mAsSrgb,
+ .dimInGammaSpaceForEnhancedScreenshots =
+ dimInGammaSpaceForEnhancedScreenshots,
+ .isProtected = isProtected});
+
+ 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);
- constexpr bool kUseFramebufferCache = false;
- auto chain =
- ftl::Future(getRenderEngine().drawLayers(clientCompositionDisplay,
- clientRenderEngineLayers, buffer,
- kUseFramebufferCache, std::move(bufferFence)))
- .then(&toFenceResult);
+ return output->getRenderSurface()->getClientTargetAcquireFence();
+ };
- const auto future = chain.share();
- for (auto* layer : renderedLayers) {
- layer->onLayerDisplayed(future);
- }
+ // 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::SKIA_GL_THREADED;
+ }();
+ auto presentFuture = renderEngineIsThreaded ? ftl::defer(std::move(present)).share()
+ : ftl::yield(present()).share();
- // Always switch back to unprotected context.
- getRenderEngine().useProtectedContext(false);
+ 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);
+ }
- 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);
+ }
}
// ---------------------------------------------------------------------------
@@ -6930,6 +8224,7 @@ void SurfaceFlinger::State::traverseInReverseZOrder(const LayerVector::Visitor&
}
void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const int32_t uid,
+ std::unordered_set<uint32_t> 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.
@@ -6948,14 +8243,45 @@ void SurfaceFlinger::traverseLayersInLayerStack(ui::LayerStack layerStack, const
if (uid != CaptureArgs::UNSET_UID && layer->getOwnerUid() != uid) {
return;
}
+
+ if (!excludeLayerIds.empty()) {
+ auto p = sp<Layer>::fromExisting(layer);
+ while (p != nullptr) {
+ if (excludeLayerIds.count(p->sequence) != 0) {
+ return;
+ }
+ p = p->getParent();
+ }
+ }
+
visitor(layer);
});
}
}
+ftl::Optional<scheduler::FrameRateMode> 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->getPeakFps(), ftl::as_non_null(modePtr)};
+ });
+}
+
status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal(
const sp<DisplayDevice>& display,
- const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy) {
+ const scheduler::RefreshRateSelector::PolicyVariant& policy) {
+ const auto displayId = display->getPhysicalId();
+ ATRACE_NAME(ftl::Concat(__func__, ' ', displayId.value).c_str());
+
Mutex::Autolock lock(mStateLock);
if (mDebugDisplayModeSetByBackdoor) {
@@ -6963,64 +8289,121 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecsInternal(
return NO_ERROR;
}
- const status_t setPolicyResult = display->setRefreshRatePolicy(policy, overridePolicy);
- if (setPolicyResult < 0) {
- return BAD_VALUE;
+ 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 (setPolicyResult == scheduler::RefreshRateConfigs::CURRENT_POLICY_UNCHANGED) {
+
+ if (!shouldApplyRefreshRateSelectorPolicy(*display)) {
+ ALOGV("%s(%s): Skipped applying policy", __func__, to_string(displayId).c_str());
return NO_ERROR;
}
- scheduler::RefreshRateConfigs::Policy currentPolicy =
- display->refreshRateConfigs().getCurrentPolicy();
+ return applyRefreshRateSelectorPolicy(displayId, selector);
+}
+
+bool SurfaceFlinger::shouldApplyRefreshRateSelectorPolicy(const DisplayDevice& display) const {
+ if (display.isPoweredOn() || mPhysicalDisplays.size() == 1) return true;
+
+ LOG_ALWAYS_FATAL_IF(display.isVirtual());
+ const auto displayId = display.getPhysicalId();
+
+ // The display is powered off, and this is a multi-display device. If the display is the
+ // inactive internal display of a dual-display foldable, then the policy will be applied
+ // when it becomes active upon powering on.
+ //
+ // TODO(b/255635711): Remove this function (i.e. returning `false` as a special case) once
+ // concurrent mode setting across multiple (potentially powered off) displays is supported.
+ //
+ return displayId == mActiveDisplayId ||
+ !mPhysicalDisplays.get(displayId)
+ .transform(&PhysicalDisplay::isInternal)
+ .value_or(false);
+}
+status_t SurfaceFlinger::applyRefreshRateSelectorPolicy(
+ PhysicalDisplayId displayId, const scheduler::RefreshRateSelector& selector, bool force) {
+ 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());
+ const Fps preferredFps = preferredMode.fps;
+ ALOGV("Switching to Scheduler preferred mode %d (%s)", preferredModeId.value(),
+ to_string(preferredFps).c_str());
- if (display->refreshRateConfigs().isModeAllowed(preferredDisplayMode->getId())) {
- ALOGV("switching to Scheduler preferred display mode %d",
- preferredDisplayMode->getId().value());
- setDesiredActiveMode({preferredDisplayMode, DisplayModeEvent::Changed});
- } 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;
+ }
+
+ setDesiredMode({std::move(preferredMode), .emitEvent = true}, force);
+
+ // Update the frameRateOverride list as the display render rate might have changed
+ if (mScheduler->updateFrameRateOverrides(scheduler::GlobalSignals{}, preferredFps)) {
+ triggerOnFrameRateOverridesChanged();
}
return NO_ERROR;
}
-status_t SurfaceFlinger::setDesiredDisplayModeSpecs(
- const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode, bool allowGroupSwitching,
- float primaryRefreshRateMin, float primaryRefreshRateMax, float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) {
+namespace {
+FpsRange translate(const gui::DisplayModeSpecs::RefreshRateRanges::RefreshRateRange& aidlRange) {
+ return FpsRange{Fps::fromValue(aidlRange.min), Fps::fromValue(aidlRange.max)};
+}
+
+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<IBinder>& 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",
@@ -7030,16 +8413,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);
}
});
@@ -7047,16 +8425,10 @@ status_t SurfaceFlinger::setDesiredDisplayModeSpecs(
}
status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& 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;
}
@@ -7070,21 +8442,15 @@ status_t SurfaceFlinger::getDesiredDisplayModeSpecs(const sp<IBinder>& 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<Layer> SurfaceFlinger::fromHandle(const sp<IBinder>& handle) const {
- return Layer::fromHandle(handle);
-}
-
void SurfaceFlinger::onLayerFirstRef(Layer* layer) {
mNumLayers++;
if (!layer->isRemovedFromCurrentState()) {
@@ -7101,6 +8467,7 @@ void SurfaceFlinger::onLayerDestroyed(Layer* layer) {
if (mTransactionTracing) {
mTransactionTracing->onLayerRemoved(layer->getSequence());
}
+ mScheduler->onLayerDestroyed(layer);
}
void SurfaceFlinger::onLayerUpdate() {
@@ -7147,94 +8514,79 @@ const std::unordered_map<std::string, uint32_t>& SurfaceFlinger::getGenericLayer
// on the work to remove the table in that bug rather than adding more to
// it.
static const std::unordered_map<std::string, uint32_t> 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<IGraphicBufferProducer>& surface, float frameRate,
- int8_t compatibility, int8_t changeFrameRateStrategy) {
- if (!ValidateFrameRate(frameRate, compatibility, changeFrameRateStrategy,
- "SurfaceFlinger::setFrameRate")) {
- return BAD_VALUE;
- }
-
- static_cast<void>(mScheduler->schedule([=] {
- Mutex::Autolock lock(mStateLock);
- if (authenticateSurfaceTextureLocked(surface)) {
- sp<Layer> layer = (static_cast<MonitoredProducer*>(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) {
+status_t SurfaceFlinger::setGameModeFrameRateOverride(uid_t uid, float frameRate) {
PhysicalDisplayId displayId = [&]() {
Mutex::Autolock lock(mStateLock);
return getDefaultDisplayDeviceLocked()->getPhysicalId();
}();
- mScheduler->setGameModeRefreshRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
+ mScheduler->setGameModeFrameRateForUid(FrameRateOverride{static_cast<uid_t>(uid), frameRate});
mScheduler->onFrameRateOverridesChanged(mAppConnectionHandle, displayId);
return NO_ERROR;
}
-status_t SurfaceFlinger::setFrameTimelineInfo(const sp<IGraphicBufferProducer>& 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;
+status_t SurfaceFlinger::setGameDefaultFrameRateOverride(uid_t uid, float frameRate) {
+ if (FlagManager::getInstance().game_default_frame_rate()) {
+ mScheduler->setGameDefaultFrameRateForUid(
+ FrameRateOverride{static_cast<uid_t>(uid), frameRate});
}
+ return NO_ERROR;
+}
- sp<Layer> layer = (static_cast<MonitoredProducer*>(surface.get()))->getLayer();
- if (layer == nullptr) {
- ALOGE("Attempt to set frame timeline info on a layer that no longer exists");
- return BAD_VALUE;
- }
+status_t SurfaceFlinger::updateSmallAreaDetection(
+ std::vector<std::pair<int32_t, float>>& appIdThresholdMappings) {
+ mScheduler->updateSmallAreaDetection(appIdThresholdMappings);
+ return NO_ERROR;
+}
- layer->setFrameTimelineInfoForBuffer(frameTimelineInfo);
+status_t SurfaceFlinger::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+ mScheduler->setSmallAreaDetectionThreshold(appId, threshold);
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 ||
+ FlagManager::getInstance().refresh_rate_overlay_on_external_display()) {
+ if (const auto device = getDisplayDeviceLocked(id)) {
+ const auto enableOverlay = [&](const bool setByHwc) FTL_FAKE_GUARD(
+ kMainThreadContext) {
+ device->enableRefreshRateOverlay(enable, setByHwc, mRefreshRateOverlaySpinner,
+ mRefreshRateOverlayRenderRate,
+ mRefreshRateOverlayShowInMiddle);
+ };
+ enableOverlay(setByHwc);
+ if (setByHwc) {
+ const auto status =
+ getHwComposer().setRefreshRateChangedCallbackDebugEnabled(id, enable);
+ if (status != NO_ERROR) {
+ ALOGE("Error updating the refresh rate changed callback debug enabled");
+ enableOverlay(/*setByHwc*/ false);
+ }
+ }
+ }
}
}
}
-status_t SurfaceFlinger::addTransactionTraceListener(
- const sp<gui::ITransactionTraceListener>& listener) {
- if (!listener) {
- return BAD_VALUE;
+void SurfaceFlinger::enableHdrSdrRatioOverlay(bool enable) {
+ for (const auto& [id, display] : mPhysicalDisplays) {
+ if (display.snapshot().connectionType() == ui::DisplayConnectionType::Internal) {
+ if (const auto device = getDisplayDeviceLocked(id)) {
+ device->enableHdrSdrRatioOverlay(enable);
+ }
+ }
}
-
- mInterceptor->addTransactionTraceListener(listener);
-
- return NO_ERROR;
}
-int SurfaceFlinger::getGPUContextPriority() {
+int SurfaceFlinger::getGpuContextPriority() {
return getRenderEngine().getContextPriority();
}
@@ -7244,7 +8596,7 @@ int SurfaceFlinger::calculateMaxAcquiredBufferCount(Fps refreshRate,
if (presentLatency.count() % refreshRate.getPeriodNsecs()) {
pipelineDepth++;
}
- return std::max(1ll, pipelineDepth - 1);
+ return std::max(minAcquiredBuffers, static_cast<int64_t>(pipelineDepth - 1));
}
status_t SurfaceFlinger::getMaxAcquiredBufferCount(int* buffers) const {
@@ -7252,7 +8604,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;
}
}
@@ -7267,7 +8619,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;
}
}
@@ -7280,12 +8632,13 @@ 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> layer = state.layer.promote();
if (!layer) {
ALOGD("Layer was destroyed soon after creation %p", state.layer.unsafe_get());
return;
}
+ MUTEX_ALIAS(mStateLock, layer->mFlinger->mStateLock);
sp<Layer> parent;
bool addToRoot = state.addToRoot;
@@ -7309,9 +8662,19 @@ void SurfaceFlinger::handleLayerCreatedLocked(const LayerCreatedState& state) {
parent->addChild(layer);
}
- layer->updateTransformHint(mActiveDisplayTransformHint);
+ ui::LayerStack layerStack = layer->getLayerStack(LayerVector::StateSet::Current);
+ sp<const DisplayDevice> 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() {
@@ -7322,38 +8685,64 @@ void SurfaceFlinger::sample() {
mRegionSamplingThread->onCompositionComplete(mScheduler->getScheduledFrameTime());
}
-void SurfaceFlinger::onActiveDisplaySizeChanged(const sp<DisplayDevice>& 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());
+}
+
+sp<DisplayDevice> SurfaceFlinger::getActivatableDisplay() const {
+ if (mPhysicalDisplays.size() == 1) return nullptr;
+
+ // TODO(b/255635821): Choose the pacesetter display, considering both internal and external
+ // displays. For now, pick the other internal display, assuming a dual-display foldable.
+ return findDisplay([this](const DisplayDevice& display) REQUIRES(mStateLock) {
+ const auto idOpt = PhysicalDisplayId::tryCast(display.getId());
+ return idOpt && *idOpt != mActiveDisplayId && display.isPoweredOn() &&
+ mPhysicalDisplays.get(*idOpt)
+ .transform(&PhysicalDisplay::isInternal)
+ .value_or(false);
+ });
}
-void SurfaceFlinger::onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay) {
+void SurfaceFlinger::onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr,
+ const DisplayDevice& activeDisplay) {
ATRACE_CALL();
- if (const auto display = getDisplayDeviceLocked(mActiveDisplayToken)) {
- display->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
- }
+ // For the first display activated during boot, there is no need to force setDesiredMode,
+ // because DM is about to send its policy via setDesiredDisplayModeSpecs.
+ bool forceApplyPolicy = false;
- if (!activeDisplay) {
- ALOGE("%s: activeDisplay is null", __func__);
- return;
+ if (inactiveDisplayPtr) {
+ inactiveDisplayPtr->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(false);
+ forceApplyPolicy = true;
}
- mActiveDisplayToken = activeDisplay->getDisplayToken();
- activeDisplay->getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
- updateInternalDisplayVsyncLocked(activeDisplay);
+
+ mActiveDisplayId = activeDisplay.getPhysicalId();
+ activeDisplay.getCompositionDisplay()->setLayerCachingTexturePoolEnabled(true);
+
+ resetPhaseConfiguration(activeDisplay.getActiveMode().fps);
+
+ // TODO(b/255635711): Check for pending mode changes on other displays.
mScheduler->setModeChangePending(false);
- mScheduler->setRefreshRateConfigs(activeDisplay->holdRefreshRateConfigs());
+
+ mScheduler->setPacesetterDisplay(mActiveDisplayId);
+
onActiveDisplaySizeChanged(activeDisplay);
- mActiveDisplayTransformHint = activeDisplay->getTransformHint();
+ mActiveDisplayTransformHint = activeDisplay.getTransformHint();
+ sActiveDisplayRotationFlags = ui::Transform::toRotationFlags(activeDisplay.getOrientation());
- // Update the kernel timer for the current active display, since the policy
- // for this display might have changed when it was not the active display.
- toggleKernelIdleTimer();
+ // The policy of the new active/pacesetter display may have changed while it was inactive. In
+ // that case, its preferred mode has not been propagated to HWC (via setDesiredMode). In either
+ // case, the Scheduler's cachedModeChangedParams must be initialized to the newly active mode,
+ // and the kernel idle timer of the newly active display must be toggled.
+ applyRefreshRateSelectorPolicy(mActiveDisplayId, activeDisplay.refreshRateSelector(),
+ forceApplyPolicy);
}
-status_t SurfaceFlinger::addWindowInfosListener(
- const sp<IWindowInfosListener>& windowInfosListener) const {
- mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener);
+status_t SurfaceFlinger::addWindowInfosListener(const sp<IWindowInfosListener>& windowInfosListener,
+ gui::WindowInfosListenerInfo* outInfo) {
+ mWindowInfosListenerInvoker->addWindowInfosListener(windowInfosListener, outInfo);
+ setTransactionFlags(eInputInfoUpdateNeeded);
return NO_ERROR;
}
@@ -7363,77 +8752,514 @@ status_t SurfaceFlinger::removeWindowInfosListener(
return NO_ERROR;
}
+status_t SurfaceFlinger::getStalledTransactionInfo(
+ int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result) {
+ result = mTransactionHandler.getStalledTransactionInfo(pid);
+ return NO_ERROR;
+}
+
std::shared_ptr<renderengine::ExternalTexture> SurfaceFlinger::getExternalTextureFromBufferData(
- const BufferData& bufferData, const char* layerName) const {
- bool cacheIdChanged = bufferData.flags.test(BufferData::BufferDataChange::cachedBufferChanged);
- bool bufferSizeExceedsLimit = false;
- std::shared_ptr<renderengine::ExternalTexture> 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<LayerCreatedState> 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<uint32_t>(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<MirrorDisplayState> mirrorDisplays;
{
- std::scoped_lock<std::mutex> lock(mCreatedLayersLock);
- createdLayers = std::move(mCreatedLayers);
- mCreatedLayers.clear();
- if (createdLayers.size() == 0) {
+ std::scoped_lock<std::mutex> lock(mMirrorDisplayLock);
+ mirrorDisplays = std::move(mMirrorDisplays);
+ mMirrorDisplays.clear();
+ if (mirrorDisplays.size() == 0) {
return false;
}
}
+ sp<IBinder> 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<Layer> rootMirrorLayer = LayerHandle::getLayer(mirrorDisplay.rootHandle);
+ ssize_t idx = mCurrentState.layersSortedByZ.indexOf(rootMirrorLayer);
+ bool ret = rootMirrorLayer->setLayerStack(ui::LayerStack::fromValue(-1));
+ if (idx >= 0 && ret) {
+ mCurrentState.layersSortedByZ.removeAt(idx);
+ mCurrentState.layersSortedByZ.add(rootMirrorLayer);
+ }
+
+ 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<Layer> 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<LayerCreatedState>& 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<Layer*> 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,
+ const std::vector<std::pair<Layer*, LayerFE*>>& layers) {
+ if (mLayerLifecycleManagerEnabled) {
+ std::vector<std::unique_ptr<frontend::LayerSnapshot>>& 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<std::pair<Layer*, LayerFE*>> SurfaceFlinger::moveSnapshotsToCompositionArgs(
+ compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly) {
+ std::vector<std::pair<Layer*, LayerFE*>> layers;
+ if (mLayerLifecycleManagerEnabled) {
+ nsecs_t currentTime = systemTime();
+ mLayerSnapshotBuilder.forEachVisibleSnapshot(
+ [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+ if (cursorOnly &&
+ snapshot->compositionType !=
+ aidl::android::hardware::graphics::composer3::Composition::CURSOR) {
+ return;
+ }
+
+ if (!snapshot->hasSomethingToDraw()) {
+ return;
+ }
+
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_IF(it == mLegacyLayers.end(),
+ "Couldnt find layer object for %s",
+ snapshot->getDebugString().c_str());
+ auto& legacyLayer = it->second;
+ sp<LayerFE> 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> 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<Layer>::fromExisting(layer));
+ }
+ }
+ }
+
+ return layers;
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(
+ std::optional<ui::LayerStack> layerStack, uint32_t uid,
+ std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
+ snapshotFilterFn) {
+ return [&, layerStack, uid]() {
+ std::vector<std::pair<Layer*, sp<LayerFE>>> layers;
+ bool stopTraversal = false;
+ mLayerSnapshotBuilder.forEachVisibleSnapshot(
+ [&](std::unique_ptr<frontend::LayerSnapshot>& snapshot) {
+ if (stopTraversal) {
+ return;
+ }
+ if (layerStack && snapshot->outputFilter.layerStack != *layerStack) {
+ return;
+ }
+ if (uid != CaptureArgs::UNSET_UID && snapshot->uid != gui::Uid(uid)) {
+ return;
+ }
+ if (!snapshot->hasSomethingToDraw()) {
+ return;
+ }
+ if (snapshotFilterFn && !snapshotFilterFn(*snapshot, stopTraversal)) {
+ return;
+ }
+
+ auto it = mLegacyLayers.find(snapshot->sequence);
+ LLOG_ALWAYS_FATAL_WITH_TRACE_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> layerFE = getFactory().createLayerFE(snapshot->name);
+ layerFE->mSnapshot = std::make_unique<frontend::LayerSnapshot>(*snapshot);
+ layers.emplace_back(legacyLayer, std::move(layerFE));
+ });
+
+ return layers;
+ };
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(std::optional<ui::LayerStack> layerStack,
+ uint32_t uid,
+ std::unordered_set<uint32_t> excludeLayerIds) {
+ return [&, layerStack, uid, excludeLayerIds = std::move(excludeLayerIds)]() {
+ if (excludeLayerIds.empty()) {
+ auto getLayerSnapshotsFn =
+ getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
+ std::vector<std::pair<Layer*, sp<LayerFE>>> 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(),
+ .skipRoundCornersWhenProtected =
+ !getRenderEngine().supportsProtectedContent()};
+ mLayerSnapshotBuilder.update(args);
+
+ auto getLayerSnapshotsFn =
+ getLayerSnapshotsForScreenshots(layerStack, uid, /*snapshotFilterFn=*/nullptr);
+ std::vector<std::pair<Layer*, sp<LayerFE>>> layers = getLayerSnapshotsFn();
+
+ args.excludeLayerIds.clear();
+ mLayerSnapshotBuilder.update(args);
+
+ return layers;
+ };
+}
+
+std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>
+SurfaceFlinger::getLayerSnapshotsForScreenshots(uint32_t rootLayerId, uint32_t uid,
+ std::unordered_set<uint32_t> excludeLayerIds,
+ bool childrenOnly,
+ const std::optional<FloatRect>& 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(),
+ .skipRoundCornersWhenProtected =
+ !getRenderEngine().supportsProtectedContent()};
+ // The layer may not exist if it was just created and a screenshot was requested immediately
+ // after. In this case, the hierarchy will be empty so we will not render any layers.
+ args.rootSnapshot.isSecure = mLayerLifecycleManager.getLayerFromId(rootLayerId) &&
+ mLayerLifecycleManager.isLayerSecure(rootLayerId);
+ mLayerSnapshotBuilder.update(args);
+
+ auto getLayerSnapshotsFn =
+ getLayerSnapshotsForScreenshots({}, uid, /*snapshotFilterFn=*/nullptr);
+ std::vector<std::pair<Layer*, sp<LayerFE>>> 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.
+ mTransactionHandler.collectTransactions();
+ update.transactions = mTransactionHandler.flushTransactions();
+ {
+ // TODO(b/238781169) lockless queue this and keep order.
+ std::scoped_lock<std::mutex> 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::doActiveLayersTracingIfNeeded(bool isCompositionComputed,
+ bool visibleRegionDirty, TimePoint time,
+ VsyncId vsyncId) {
+ if (!mLayerTracing.isActiveTracingStarted()) {
+ return;
+ }
+ if (isCompositionComputed !=
+ mLayerTracing.isActiveTracingFlagSet(LayerTracing::Flag::TRACE_COMPOSITION)) {
+ return;
+ }
+ if (!visibleRegionDirty &&
+ !mLayerTracing.isActiveTracingFlagSet(LayerTracing::Flag::TRACE_BUFFERS)) {
+ return;
+ }
+ auto snapshot = takeLayersSnapshotProto(mLayerTracing.getActiveTracingFlags(), time, vsyncId,
+ visibleRegionDirty);
+ mLayerTracing.addProtoSnapshotToOstream(std::move(snapshot), LayerTracing::Mode::MODE_ACTIVE);
+}
+
+perfetto::protos::LayersSnapshotProto SurfaceFlinger::takeLayersSnapshotProto(
+ uint32_t traceFlags, TimePoint time, VsyncId vsyncId, bool visibleRegionDirty) {
+ ATRACE_CALL();
+ perfetto::protos::LayersSnapshotProto snapshot;
+ snapshot.set_elapsed_realtime_nanos(time.ns());
+ snapshot.set_vsync_id(ftl::to_underlying(vsyncId));
+ snapshot.set_where(visibleRegionDirty ? "visibleRegionsDirty" : "bufferLatched");
+ snapshot.set_excludes_composition_state((traceFlags & LayerTracing::Flag::TRACE_COMPOSITION) ==
+ 0);
+
+ auto layers = dumpDrawingStateProto(traceFlags);
+ if (traceFlags & LayerTracing::Flag::TRACE_EXTRA) {
+ dumpOffscreenLayersProto(layers);
+ }
+ *snapshot.mutable_layers() = std::move(layers);
+
+ if (traceFlags & LayerTracing::Flag::TRACE_HWC) {
+ std::string hwcDump;
+ dumpHwc(hwcDump);
+ snapshot.set_hwc_blob(std::move(hwcDump));
+ }
+
+ *snapshot.mutable_displays() = dumpDisplayProto();
+
+ return snapshot;
+}
+
+// sfdo functions
+
+void SurfaceFlinger::sfdo_enableRefreshRateOverlay(bool active) {
+ auto future = mScheduler->schedule(
+ [&]() FTL_FAKE_GUARD(mStateLock)
+ FTL_FAKE_GUARD(kMainThreadContext) { enableRefreshRateOverlay(active); });
+ future.wait();
+}
+
+void SurfaceFlinger::sfdo_setDebugFlash(int delay) {
+ if (delay > 0) {
+ mDebugFlashDelay = delay;
+ } else {
+ mDebugFlashDelay = mDebugFlashDelay ? 0 : 1;
+ }
+ scheduleRepaint();
+}
+
+void SurfaceFlinger::sfdo_scheduleComposite() {
+ scheduleComposite(SurfaceFlinger::FrameHint::kActive);
+}
+
+void SurfaceFlinger::sfdo_scheduleCommit() {
+ Mutex::Autolock lock(mStateLock);
+ setTransactionFlags(eTransactionNeeded | eDisplayTransactionNeeded | eTraversalNeeded);
+}
+
+void SurfaceFlinger::sfdo_forceClientComposition(bool enabled) {
+ mDebugDisableHWC = enabled;
+ scheduleRepaint();
}
// 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<IBinder>& layerHandle, sp<IDisplayEventConnection>* outConnection) {
+ sp<IDisplayEventConnection> 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<gui::ISurfaceComposerClient>* outClient) {
+ const sp<Client> client = sp<Client>::make(mFlinger);
+ if (client->initCheck() == NO_ERROR) {
+ *outClient = client;
+ if (FlagManager::getInstance().misc1()) {
+ const int policy = SCHED_FIFO;
+ client->setMinSchedulerPolicy(policy, sched_get_priority_min(policy));
+ }
+ return binder::Status::ok();
+ } else {
+ *outClient = nullptr;
+ return binderStatusFromStatusT(BAD_VALUE);
+ }
+}
+
binder::Status SurfaceComposerAIDL::createDisplay(const std::string& displayName, bool secure,
+ float requestedRefreshRate,
sp<IBinder>* 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<IBinder>& 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<int64_t>* outDisplayIds) {
@@ -7447,22 +9273,12 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayIds(std::vector<int64_t>*
return binder::Status::ok();
}
-binder::Status SurfaceComposerAIDL::getPrimaryPhysicalDisplayId(int64_t* outDisplayId) {
+binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId,
+ sp<IBinder>* outDisplay) {
status_t status = checkAccessPermission();
if (status != OK) {
- return binder::Status::fromStatusT(status);
- }
-
- PhysicalDisplayId id;
- status = mFlinger->getPrimaryPhysicalDisplayId(&id);
- if (status == NO_ERROR) {
- *outDisplayId = id.value;
+ return binderStatusFromStatusT(status);
}
- return binder::Status::fromStatusT(status);
-}
-
-binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId,
- sp<IBinder>* outDisplay) {
const auto id = DisplayId::fromValue<PhysicalDisplayId>(static_cast<uint64_t>(displayId));
*outDisplay = mFlinger->getPhysicalDisplayToken(*id);
return binder::Status::ok();
@@ -7470,12 +9286,25 @@ binder::Status SurfaceComposerAIDL::getPhysicalDisplayToken(int64_t displayId,
binder::Status SurfaceComposerAIDL::setPowerMode(const sp<IBinder>& 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<FrameEvent>* 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<IBinder>& display,
gui::DisplayStatInfo* outStatInfo) {
DisplayStatInfo statInfo;
@@ -7484,7 +9313,7 @@ binder::Status SurfaceComposerAIDL::getDisplayStats(const sp<IBinder>& display,
outStatInfo->vsyncTime = static_cast<long>(statInfo.vsyncTime);
outStatInfo->vsyncPeriod = static_cast<long>(statInfo.vsyncPeriod);
}
- return binder::Status::fromStatusT(status);
+ return binderStatusFromStatusT(status);
}
binder::Status SurfaceComposerAIDL::getDisplayState(const sp<IBinder>& display,
@@ -7497,112 +9326,716 @@ binder::Status SurfaceComposerAIDL::getDisplayState(const sp<IBinder>& 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<gui::DisplayConnectionType>(info.connectionType);
+ outInfo->density = info.density;
+ outInfo->secure = info.secure;
+ outInfo->installOrientation = static_cast<gui::Rotation>(info.installOrientation);
+
+ if (const std::optional<DeviceProductInfo> dpi = info.deviceProductInfo) {
+ gui::DeviceProductInfo dinfo;
+ dinfo.name = std::move(dpi->name);
+ dinfo.manufacturerPnpId = std::vector<uint8_t>(dpi->manufacturerPnpId.begin(),
+ dpi->manufacturerPnpId.end());
+ dinfo.productId = dpi->productId;
+ dinfo.relativeAddress =
+ std::vector<uint8_t>(dpi->relativeAddress.begin(), dpi->relativeAddress.end());
+ if (const auto* model =
+ std::get_if<DeviceProductInfo::ModelYear>(&dpi->manufactureOrModelDate)) {
+ gui::DeviceProductInfo::ModelYear modelYear;
+ modelYear.year = model->year;
+ dinfo.manufactureOrModelDate.set<Tag::modelYear>(modelYear);
+ } else if (const auto* manufacture = std::get_if<DeviceProductInfo::ManufactureYear>(
+ &dpi->manufactureOrModelDate)) {
+ gui::DeviceProductInfo::ManufactureYear date;
+ date.modelYear.year = manufacture->year;
+ dinfo.manufactureOrModelDate.set<Tag::manufactureYear>(date);
+ } else if (const auto* manufacture =
+ std::get_if<DeviceProductInfo::ManufactureWeekAndYear>(
+ &dpi->manufactureOrModelDate)) {
+ gui::DeviceProductInfo::ManufactureWeekAndYear date;
+ date.manufactureYear.modelYear.year = manufacture->year;
+ date.week = manufacture->week;
+ dinfo.manufactureOrModelDate.set<Tag::manufactureWeekAndYear>(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.peakRefreshRate = mode.peakRefreshRate;
+ outMode.vsyncRate = mode.vsyncRate;
+ 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<int32_t>(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<int32_t>(cmode));
+ }
+
+ outInfo->activeColorMode = static_cast<int32_t>(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<int32_t>(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<IBinder>& 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<IBinder>& 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<IBinder>& display, int colorMode) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->setActiveColorMode(display, static_cast<ui::ColorMode>(colorMode));
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setBootDisplayMode(const sp<IBinder>& 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<IBinder>& 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);
+}
+
+binder::Status SurfaceComposerAIDL::getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* hdrConversionCapabilities) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->getHdrConversionCapabilities(hdrConversionCapabilities);
+ }
+ return binderStatusFromStatusT(status);
+}
- status = mFlinger->getBootDisplayModeSupport(outMode);
- return binder::Status::fromStatusT(status);
+binder::Status SurfaceComposerAIDL::setHdrConversionStrategy(
+ const gui::HdrConversionStrategy& hdrConversionStrategy,
+ int32_t* outPreferredHdrOutputType) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->setHdrConversionStrategy(hdrConversionStrategy,
+ outPreferredHdrOutputType);
+ }
+ return binderStatusFromStatusT(status);
}
-binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp<IBinder>& display, bool on) {
+binder::Status SurfaceComposerAIDL::getHdrOutputConversionSupport(bool* outMode) {
status_t status = checkAccessPermission();
- if (status != OK) return binder::Status::fromStatusT(status);
+ if (status == OK) {
+ status = mFlinger->getHdrOutputConversionSupport(outMode);
+ }
+ return binderStatusFromStatusT(status);
+}
+binder::Status SurfaceComposerAIDL::setAutoLowLatencyMode(const sp<IBinder>& 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<IBinder>& 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();
}
binder::Status SurfaceComposerAIDL::captureDisplay(
const DisplayCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
- status_t status = mFlinger->captureDisplay(args, captureListener);
- return binder::Status::fromStatusT(status);
+ mFlinger->captureDisplay(args, captureListener);
+ return binderStatusFromStatusT(NO_ERROR);
}
binder::Status SurfaceComposerAIDL::captureDisplayById(
- int64_t displayId, const sp<IScreenCaptureListener>& captureListener) {
- status_t status;
+ int64_t displayId, const CaptureArgs& args,
+ const sp<IScreenCaptureListener>& captureListener) {
+ // status_t status;
IPCThreadState* ipc = IPCThreadState::self();
const int uid = ipc->getCallingUid();
if (uid == AID_ROOT || uid == AID_GRAPHICS || uid == AID_SYSTEM || uid == AID_SHELL) {
std::optional<DisplayId> id = DisplayId::fromValue(static_cast<uint64_t>(displayId));
- status = mFlinger->captureDisplay(*id, captureListener);
+ mFlinger->captureDisplay(*id, args, captureListener);
} else {
- status = PERMISSION_DENIED;
+ invokeScreenCaptureError(PERMISSION_DENIED, captureListener);
}
- return binder::Status::fromStatusT(status);
+ return binderStatusFromStatusT(NO_ERROR);
}
binder::Status SurfaceComposerAIDL::captureLayers(
const LayerCaptureArgs& args, const sp<IScreenCaptureListener>& captureListener) {
- status_t status = mFlinger->captureLayers(args, captureListener);
- return binder::Status::fromStatusT(status);
+ mFlinger->captureLayers(args, captureListener);
+ return binderStatusFromStatusT(NO_ERROR);
+}
+
+binder::Status SurfaceComposerAIDL::overrideHdrTypes(const sp<IBinder>& display,
+ const std::vector<int32_t>& 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<ui::Hdr> hdrTypesVector;
+ for (int32_t i : hdrTypes) {
+ hdrTypesVector.push_back(static_cast<ui::Hdr>(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<gui::LayerDebugInfo>* 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::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<int32_t>(dataspace);
+ outPref->defaultPixelFormat = static_cast<int32_t>(pixelFormat);
+ outPref->wideColorGamutDataspace = static_cast<int32_t>(wideColorGamutDataspace);
+ outPref->wideColorGamutPixelFormat = static_cast<int32_t>(wideColorGamutPixelFormat);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getDisplayedContentSamplingAttributes(
+ const sp<IBinder>& 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<int32_t>(format);
+ outAttrs->dataspace = static_cast<int32_t>(dataspace);
+ outAttrs->componentMask = static_cast<int8_t>(componentMask);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setDisplayContentSamplingEnabled(const sp<IBinder>& display,
+ bool enable,
+ int8_t componentMask,
+ int64_t maxFrames) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->setDisplayContentSamplingEnabled(display, enable,
+ static_cast<uint8_t>(componentMask),
+ static_cast<uint64_t>(maxFrames));
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getDisplayedContentSample(const sp<IBinder>& 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<uint64_t>(maxFrames),
+ static_cast<uint64_t>(timestamp), &stats);
+ if (status == NO_ERROR) {
+ // convert from ui::DisplayedFrameStats to gui::DisplayedFrameStats
+ outStats->numFrames = static_cast<int64_t>(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<int64_t>(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<int64_t>(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<int64_t>(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<int64_t>(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<IBinder>& 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<IBinder>& stopLayerHandle,
+ const sp<gui::IRegionSamplingListener>& 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<gui::IRegionSamplingListener>& listener) {
+ status_t status = checkReadFrameBufferPermission();
+ if (status == OK) {
+ status = mFlinger->removeRegionSamplingListener(listener);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::addFpsListener(int32_t taskId,
+ const sp<gui::IFpsListener>& listener) {
+ status_t status = checkReadFrameBufferPermission();
+ if (status == OK) {
+ status = mFlinger->addFpsListener(taskId, listener);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::removeFpsListener(const sp<gui::IFpsListener>& listener) {
+ status_t status = checkReadFrameBufferPermission();
+ if (status == OK) {
+ status = mFlinger->removeFpsListener(listener);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::addTunnelModeEnabledListener(
+ const sp<gui::ITunnelModeEnabledListener>& listener) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->addTunnelModeEnabledListener(listener);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::removeTunnelModeEnabledListener(
+ const sp<gui::ITunnelModeEnabledListener>& listener) {
+ status_t status = checkAccessPermission();
+ if (status == OK) {
+ status = mFlinger->removeTunnelModeEnabledListener(listener);
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setDesiredDisplayModeSpecs(const sp<IBinder>& 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<IBinder>& 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<IBinder>& displayToken,
bool* outSupport) {
status_t status = mFlinger->getDisplayBrightnessSupport(displayToken, outSupport);
- return binder::Status::fromStatusT(status);
+ return binderStatusFromStatusT(status);
}
binder::Status SurfaceComposerAIDL::setDisplayBrightness(const sp<IBinder>& 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<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& 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<IBinder>& displayToken, const sp<gui::IHdrLayerInfoListener>& 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<IBinder>& displayToken, std::optional<gui::DisplayDecorationSupport>* outSupport) {
+ std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport> 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<int32_t>(support->format);
+ outSupport->value().alphaInterpretation =
+ static_cast<int32_t>(support->alphaInterpretation);
+ }
+
+ return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::setGameModeFrameRateOverride(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->setGameModeFrameRateOverride(uid, frameRate);
+ } else {
+ ALOGE("setGameModeFrameRateOverride() permission denied for uid: %d", c_uid);
+ status = PERMISSION_DENIED;
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setGameDefaultFrameRateOverride(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->setGameDefaultFrameRateOverride(uid, frameRate);
+ } else {
+ ALOGE("setGameDefaultFrameRateOverride() permission denied for uid: %d", c_uid);
+ status = PERMISSION_DENIED;
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::enableRefreshRateOverlay(bool active) {
+ mFlinger->sfdo_enableRefreshRateOverlay(active);
+ return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::setDebugFlash(int delay) {
+ mFlinger->sfdo_setDebugFlash(delay);
+ return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::scheduleComposite() {
+ mFlinger->sfdo_scheduleComposite();
+ return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::scheduleCommit() {
+ mFlinger->sfdo_scheduleCommit();
+ return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::forceClientComposition(bool enabled) {
+ mFlinger->sfdo_forceClientComposition(enabled);
+ return binder::Status::ok();
+}
+
+binder::Status SurfaceComposerAIDL::updateSmallAreaDetection(const std::vector<int32_t>& appIds,
+ const std::vector<float>& thresholds) {
+ status_t status;
+ const int c_uid = IPCThreadState::self()->getCallingUid();
+ if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
+ if (appIds.size() != thresholds.size()) return binderStatusFromStatusT(BAD_VALUE);
+
+ std::vector<std::pair<int32_t, float>> mappings;
+ const size_t size = appIds.size();
+ mappings.reserve(size);
+ for (int i = 0; i < size; i++) {
+ auto row = std::make_pair(appIds[i], thresholds[i]);
+ mappings.push_back(row);
+ }
+ status = mFlinger->updateSmallAreaDetection(mappings);
+ } else {
+ ALOGE("updateSmallAreaDetection() permission denied for uid: %d", c_uid);
+ status = PERMISSION_DENIED;
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::setSmallAreaDetectionThreshold(int32_t appId, float threshold) {
+ status_t status;
+ const int c_uid = IPCThreadState::self()->getCallingUid();
+ if (c_uid == AID_ROOT || c_uid == AID_SYSTEM) {
+ status = mFlinger->setSmallAreaDetectionThreshold(appId, threshold);
+ } else {
+ ALOGE("setSmallAreaDetectionThreshold() 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<gui::IWindowInfosListener>& 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<gui::IWindowInfosListener>& 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);
+}
+
+binder::Status SurfaceComposerAIDL::getStalledTransactionInfo(
+ int pid, std::optional<gui::StalledTransactionInfo>* outInfo) {
+ const int callingPid = IPCThreadState::self()->getCallingPid();
+ const int callingUid = IPCThreadState::self()->getCallingUid();
+ if (!checkPermission(sAccessSurfaceFlinger, callingPid, callingUid)) {
+ return binderStatusFromStatusT(PERMISSION_DENIED);
+ }
+
+ std::optional<TransactionHandler::StalledTransactionInfo> stalledTransactionInfo;
+ status_t status = mFlinger->getStalledTransactionInfo(pid, stalledTransactionInfo);
+ if (stalledTransactionInfo) {
+ gui::StalledTransactionInfo result;
+ result.layerName = String16{stalledTransactionInfo->layerName.c_str()},
+ result.bufferId = stalledTransactionInfo->bufferId,
+ result.frameNumber = stalledTransactionInfo->frameNumber,
+ outInfo->emplace(std::move(result));
+ } else {
+ outInfo->reset();
+ }
+ return binderStatusFromStatusT(status);
+}
+
+binder::Status SurfaceComposerAIDL::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ return gui::getSchedulingPolicy(outPolicy);
}
status_t SurfaceComposerAIDL::checkAccessPermission(bool usePermissionCache) {
@@ -7627,6 +10060,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<void>(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 0f711afa51..61360a70df 100644
--- a/services/surfaceflinger/SurfaceFlinger.h
+++ b/services/surfaceflinger/SurfaceFlinger.h
@@ -21,25 +21,31 @@
* NOTE: Make sure this file doesn't include anything from <gl/ > or <gl2/ >
*/
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
#include <android-base/thread_annotations.h>
#include <android/gui/BnSurfaceComposer.h>
#include <android/gui/DisplayStatInfo.h>
#include <android/gui/DisplayState.h>
+#include <android/gui/ISurfaceComposerClient.h>
#include <cutils/atomic.h>
#include <cutils/compiler.h>
+#include <ftl/algorithm.h>
#include <ftl/future.h>
-#include <ftl/small_map.h>
+#include <ftl/non_null.h>
#include <gui/BufferQueue.h>
+#include <gui/CompositorTiming.h>
#include <gui/FrameTimestamps.h>
#include <gui/ISurfaceComposer.h>
-#include <gui/ISurfaceComposerClient.h>
#include <gui/ITransactionCompletedListener.h>
+#include <gui/LayerDebugInfo.h>
#include <gui/LayerState.h>
#include <layerproto/LayerProtoHeader.h>
#include <math/mat4.h>
#include <renderengine/LayerSettings.h>
#include <serviceutils/PriorityDumper.h>
#include <system/graphics.h>
+#include <ui/DisplayMap.h>
#include <ui/FenceTime.h>
#include <ui/PixelFormat.h>
#include <ui/Size.h>
@@ -50,26 +56,36 @@
#include <utils/Trace.h>
#include <utils/threads.h>
-#include <compositionengine/FenceResult.h>
#include <compositionengine/OutputColorSetting.h>
#include <scheduler/Fps.h>
-
-#include "ClientCache.h"
+#include <scheduler/PresentLatencyTracker.h>
+#include <scheduler/Time.h>
+#include <scheduler/TransactionSchedule.h>
+#include <scheduler/interface/CompositionCoverage.h>
+#include <scheduler/interface/ICompositor.h>
+#include <ui/FenceResult.h>
+
+#include <common/FlagManager.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 "MutexUtils.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 +106,17 @@
#include <unordered_map>
#include <unordered_set>
#include <utility>
+#include <vector>
#include <aidl/android/hardware/graphics/common/DisplayDecorationSupport.h>
+#include <aidl/android/hardware/graphics/common/DisplayHotplugEvent.h>
+#include <aidl/android/hardware/graphics/composer3/RefreshRateChangedDebugData.h>
+#include "Client.h"
using namespace android::surfaceflinger;
namespace android {
-class Client;
class EventThread;
class FlagManager;
class FpsReporter;
@@ -115,6 +134,9 @@ class FrameTracer;
class ScreenCapturer;
class WindowInfosListenerInvoker;
+using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
+using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
+using frontend::TransactionHandler;
using gui::CaptureArgs;
using gui::DisplayCaptureArgs;
using gui::IRegionSamplingListener;
@@ -146,7 +168,8 @@ enum {
eDisplayTransactionNeeded = 0x04,
eTransformHintUpdateNeeded = 0x08,
eTransactionFlushNeeded = 0x10,
- eTransactionMask = 0x1f,
+ eInputInfoUpdateNeeded = 0x20,
+ eTransactionMask = 0x3f,
};
// Latch Unsignaled buffer behaviours
@@ -157,39 +180,28 @@ 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<FenceTime> display = FenceTime::NO_FENCE;
- };
- std::queue<CompositePresentTime> mCompositePresentTimes;
-
- static const size_t NUM_BUCKETS = 8; // < 1-7, 7+
- nsecs_t mFrameBuckets[NUM_BUCKETS] = {};
- nsecs_t mTotalTime = 0;
- std::atomic<nsecs_t> mLastSwapTime = 0;
-};
-
class SurfaceFlinger : public BnSurfaceComposer,
public PriorityDumper,
private IBinder::DeathRecipient,
private HWC2::ComposerCallback,
private ICompositor,
- private scheduler::ISchedulerCallback {
+ private scheduler::ISchedulerCallback,
+ private compositionengine::ICEPowerCallback,
+ private scheduler::IVsyncTrackerCallback {
public:
struct SkipInitializationTag {};
@@ -204,29 +216,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;
@@ -244,18 +233,15 @@ public:
// FramebufferSurface
static int64_t maxFrameBufferAcquiredBuffers;
+ // Controls the minimum acquired buffers SurfaceFlinger will suggest via
+ // ISurfaceComposer.getMaxAcquiredBufferCount().
+ static int64_t minAcquiredBuffers;
+
// Controls the maximum width and height in pixels that the graphics pipeline can support for
// GPU fallback composition. For example, 8k devices with 4k GPUs, or 4k devices with 2k GPUs.
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;
-
static bool useContextPriority;
// The data space and pixel format that SurfaceFlinger expects hardware composer
@@ -280,9 +266,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 };
@@ -300,18 +283,8 @@ public:
// The CompositionEngine encapsulates all composition related interfaces and actions.
compositionengine::CompositionEngine& getCompositionEngine() const;
- // Obtains a name from the texture pool, or, if the pool is empty, posts a
- // synchronous message to the main thread to obtain one on the fly
- uint32_t getNewTexture();
-
- // utility function to delete a texture on the main thread
- void deleteTextureAsync(uint32_t texture);
-
renderengine::RenderEngine& getRenderEngine() const;
- bool authenticateSurfaceTextureLocked(
- const sp<IGraphicBufferProducer>& bufferProducer) const;
-
void onLayerFirstRef(Layer*);
void onLayerDestroyed(Layer*);
void onLayerUpdate();
@@ -319,23 +292,20 @@ public:
void removeHierarchyFromOffscreenLayers(Layer* layer);
void removeFromOffscreenLayers(Layer* layer);
- // TODO: Remove atomic if move dtor to main thread CL lands
- std::atomic<uint32_t> 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>& layer, uint32_t layerId);
+
+ std::vector<Layer*> 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<Layer> fromHandle(const sp<IBinder>& 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 +323,28 @@ public:
// on this behavior to increase contrast for some media sources.
bool mTreat170mAsSrgb = false;
+ // If true, then screenshots with an enhanced render intent will dim in gamma space.
+ // The purpose is to ensure that screenshots appear correct during system animations for devices
+ // that require that dimming must occur in gamma space.
+ bool mDimInGammaSpaceForEnhancedScreenshots = 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 +353,7 @@ protected:
REQUIRES(mStateLock);
virtual std::shared_ptr<renderengine::ExternalTexture> 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 <typename Predicate>
@@ -375,34 +367,27 @@ 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<void(const LayerVector::Visitor&)>;
+ using GetLayerSnapshotsFunction = std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()>;
using RenderAreaFuture = ftl::Future<std::unique_ptr<RenderArea>>;
using DumpArgs = Vector<String16>;
using Dumper = std::function<void(const DumpArgs&, bool asProto, std::string&)>;
- // 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,12 +407,25 @@ private:
const LayerVector::StateSet stateSet = LayerVector::StateSet::Invalid;
LayerVector layersSortedByZ;
- DefaultKeyedVector< wp<IBinder>, DisplayDeviceState> displays;
+
+ // TODO(b/241285876): Replace deprecated DefaultKeyedVector with ftl::SmallMap.
+ DefaultKeyedVector<wp<IBinder>, DisplayDeviceState> displays;
+
+ std::optional<size_t> 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;
- renderengine::ShadowSettings globalShadowSettings;
+ ShadowSettings globalShadowSettings;
void traverse(const LayerVector::Visitor& visitor) const;
void traverseInZOrder(const LayerVector::Visitor& visitor) const;
@@ -471,58 +469,65 @@ 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 <typename F, std::enable_if_t<!std::is_member_function_pointer_v<F>>* = nullptr>
static Dumper dumper(F&& dump) {
using namespace std::placeholders;
return std::bind(std::forward<F>(dump), _3);
}
+ Dumper lockedDumper(Dumper dump) {
+ return [this, dump](const DumpArgs& args, bool asProto, std::string& result) -> void {
+ TimedLock lock(mStateLock, s2ns(1), __func__);
+ if (!lock.locked()) {
+ base::StringAppendF(&result, "Dumping without lock after timeout: %s (%d)\n",
+ strerror(-lock.status), lock.status);
+ }
+ dump(args, asProto, result);
+ };
+ }
+
template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
Dumper dumper(F dump) {
using namespace std::placeholders;
- return std::bind(dump, this, _3);
+ return lockedDumper(std::bind(dump, this, _3));
}
template <typename F>
Dumper argsDumper(F dump) {
using namespace std::placeholders;
- return std::bind(dump, this, _1, _3);
+ return lockedDumper(std::bind(dump, this, _1, _3));
}
template <typename F>
Dumper protoDumper(F dump) {
using namespace std::placeholders;
- return std::bind(dump, this, _1, _2, _3);
+ return lockedDumper(std::bind(dump, this, _1, _2, _3));
}
- template <typename... Args,
- typename Handler = VsyncModulator::VsyncConfigOpt (VsyncModulator::*)(Args...)>
- void modulateVsync(Handler handler, Args... args) {
- if (const auto config = (*mVsyncModulator.*handler)(args...)) {
- const auto vsyncPeriod = mScheduler->getVsyncPeriodFromRefreshRateConfigs();
- setVsyncConfig(*config, vsyncPeriod);
- }
+ template <typename F, std::enable_if_t<std::is_member_function_pointer_v<F>>* = nullptr>
+ Dumper mainThreadDumper(F dump) {
+ using namespace std::placeholders;
+ Dumper dumper = std::bind(dump, this, _3);
+ return [this, dumper](const DumpArgs& args, bool asProto, std::string& result) -> void {
+ mScheduler
+ ->schedule(
+ [&args, asProto, &result, dumper]() FTL_FAKE_GUARD(kMainThreadContext)
+ FTL_FAKE_GUARD(mStateLock) { dumper(args, asProto, result); })
+ .get();
+ };
}
- 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<String16>& args) override { return priorityDump(fd, args); }
@@ -530,97 +535,85 @@ private:
EXCLUDES(mStateLock);
// Implements ISurfaceComposer
- sp<ISurfaceComposerClient> createConnection() override;
- sp<IBinder> createDisplay(const String8& displayName, bool secure);
+ sp<IBinder> createDisplay(const String8& displayName, bool secure,
+ float requestedRefreshRate = 0.0f);
void destroyDisplay(const sp<IBinder>& displayToken);
std::vector<PhysicalDisplayId> getPhysicalDisplayIds() const EXCLUDES(mStateLock) {
Mutex::Autolock lock(mStateLock);
return getPhysicalDisplayIdsLocked();
}
- status_t getPrimaryPhysicalDisplayId(PhysicalDisplayId*) const EXCLUDES(mStateLock);
sp<IBinder> getPhysicalDisplayToken(PhysicalDisplayId displayId) const;
- status_t setTransactionState(const FrameTimelineInfo& frameTimelineInfo,
- const Vector<ComposerState>& state,
- const Vector<DisplayState>& displays, uint32_t flags,
- const sp<IBinder>& applyToken,
- const InputWindowCommands& inputWindowCommands,
- int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- const std::vector<ListenerCallbacks>& listenerCallbacks,
- uint64_t transactionId) override;
- void bootFinished() override;
- bool authenticateSurfaceTexture(
- const sp<IGraphicBufferProducer>& bufferProducer) const override;
- status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const override;
+ status_t setTransactionState(
+ const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& state,
+ const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+ InputWindowCommands inputWindowCommands, int64_t desiredPresentTime,
+ bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks, const std::vector<ListenerCallbacks>& listenerCallbacks,
+ uint64_t transactionId, const std::vector<uint64_t>& mergedTransactionIds) override;
+ void bootFinished();
+ virtual status_t getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) const;
sp<IDisplayEventConnection> createDisplayEventConnection(
- ISurfaceComposer::VsyncSource vsyncSource = eVsyncSourceApp,
- ISurfaceComposer::EventRegistrationFlags eventRegistration = {}) override;
+ gui::ISurfaceComposer::VsyncSource vsyncSource =
+ gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
+ EventRegistrationFlags eventRegistration = {},
+ const sp<IBinder>& layerHandle = nullptr);
- status_t captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
- status_t captureDisplay(DisplayId, const sp<IScreenCaptureListener>&);
- status_t captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
+ void captureDisplay(const DisplayCaptureArgs&, const sp<IScreenCaptureListener>&);
+ void captureDisplay(DisplayId, const CaptureArgs&, const sp<IScreenCaptureListener>&);
+ void captureLayers(const LayerCaptureArgs&, const sp<IScreenCaptureListener>&);
status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* stats);
status_t getDisplayState(const sp<IBinder>& displayToken, ui::DisplayState*)
EXCLUDES(mStateLock);
- status_t getStaticDisplayInfo(const sp<IBinder>& displayToken, ui::StaticDisplayInfo*)
- EXCLUDES(mStateLock) override;
- status_t getDynamicDisplayInfo(const sp<IBinder>& displayToken, ui::DynamicDisplayInfo*)
- EXCLUDES(mStateLock) override;
- status_t getDisplayNativePrimaries(const sp<IBinder>& displayToken,
- ui::DisplayPrimaries&) override;
- status_t setActiveColorMode(const sp<IBinder>& 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<IBinder>& displayToken,
+ ui::DynamicDisplayInfo*) EXCLUDES(mStateLock);
+ void getDynamicDisplayInfoInternal(ui::DynamicDisplayInfo*&, const sp<DisplayDevice>&,
+ const display::DisplaySnapshot&);
+ status_t getDisplayNativePrimaries(const sp<IBinder>& displayToken, ui::DisplayPrimaries&);
+ status_t setActiveColorMode(const sp<IBinder>& displayToken, ui::ColorMode colorMode);
status_t getBootDisplayModeSupport(bool* outSupport) const;
- status_t setBootDisplayMode(const sp<IBinder>& displayToken, ui::DisplayModeId id) override;
+ status_t setBootDisplayMode(const sp<display::DisplayToken>&, DisplayModeId);
+ status_t getOverlaySupport(gui::OverlayProperties* outProperties) const;
status_t clearBootDisplayMode(const sp<IBinder>& displayToken);
+ status_t getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>* hdrConversionCapaabilities) const;
+ status_t setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy,
+ int32_t*);
+ status_t getHdrOutputConversionSupport(bool* outSupport) const;
void setAutoLowLatencyMode(const sp<IBinder>& displayToken, bool on);
void setGameContentType(const sp<IBinder>& displayToken, bool on);
void setPowerMode(const sp<IBinder>& displayToken, int mode);
- status_t clearAnimationFrameStats() override;
- status_t getAnimationFrameStats(FrameStats* outStats) const override;
status_t overrideHdrTypes(const sp<IBinder>& displayToken,
- const std::vector<ui::Hdr>& 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<LayerDebugInfo>* outLayers) override;
- status_t getColorManagement(bool* outGetColorManagement) const override;
+ const std::vector<ui::Hdr>& hdrTypes);
+ status_t onPullAtom(const int32_t atomId, std::vector<uint8_t>* pulledData, bool* success);
+ status_t getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers);
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<IBinder>& displayToken,
ui::PixelFormat* outFormat,
ui::Dataspace* outDataspace,
- uint8_t* outComponentMask) const override;
+ uint8_t* outComponentMask) const;
status_t setDisplayContentSamplingEnabled(const sp<IBinder>& displayToken, bool enable,
- uint8_t componentMask, uint64_t maxFrames) override;
+ uint8_t componentMask, uint64_t maxFrames);
status_t getDisplayedContentSample(const sp<IBinder>& 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<IBinder>& displayToken, bool* outIsWideColorDisplay) const;
status_t addRegionSamplingListener(const Rect& samplingArea, const sp<IBinder>& stopLayerHandle,
- const sp<IRegionSamplingListener>& listener) override;
- status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener) override;
- status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) override;
- status_t removeFpsListener(const sp<gui::IFpsListener>& listener) override;
- status_t addTunnelModeEnabledListener(
- const sp<gui::ITunnelModeEnabledListener>& listener) override;
- status_t removeTunnelModeEnabledListener(
- const sp<gui::ITunnelModeEnabledListener>& listener) override;
+ const sp<IRegionSamplingListener>& listener);
+ status_t removeRegionSamplingListener(const sp<IRegionSamplingListener>& listener);
+ status_t addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener);
+ status_t removeFpsListener(const sp<gui::IFpsListener>& listener);
+ status_t addTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener);
+ status_t removeTunnelModeEnabledListener(const sp<gui::ITunnelModeEnabledListener>& listener);
status_t setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId displayModeId, bool allowGroupSwitching,
- float primaryRefreshRateMin, float primaryRefreshRateMax,
- float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) override;
- status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
- ui::DisplayModeId* outDefaultMode,
- bool* outAllowGroupSwitching,
- float* outPrimaryRefreshRateMin,
- float* outPrimaryRefreshRateMax,
- float* outAppRequestRefreshRateMin,
- float* outAppRequestRefreshRateMax) override;
+ const gui::DisplayModeSpecs&);
+ status_t getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, gui::DisplayModeSpecs*);
status_t getDisplayBrightnessSupport(const sp<IBinder>& displayToken, bool* outSupport) const;
status_t setDisplayBrightness(const sp<IBinder>& displayToken,
const gui::DisplayBrightness& brightness);
@@ -630,71 +623,80 @@ private:
const sp<gui::IHdrLayerInfoListener>& 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<IBinder>& displayToken,
std::optional<aidl::android::hardware::graphics::common::DisplayDecorationSupport>*
- outSupport) const override;
+ outSupport) const;
status_t setFrameRate(const sp<IGraphicBufferProducer>& surface, float frameRate,
- int8_t compatibility, int8_t changeFrameRateStrategy) override;
+ int8_t compatibility, int8_t changeFrameRateStrategy);
status_t setFrameTimelineInfo(const sp<IGraphicBufferProducer>& surface,
- const FrameTimelineInfo& frameTimelineInfo) override;
+ const gui::FrameTimelineInfo& frameTimelineInfo);
+
+ status_t setGameModeFrameRateOverride(uid_t uid, float frameRate);
+
+ status_t setGameDefaultFrameRateOverride(uid_t uid, float frameRate);
- status_t setOverrideFrameRate(uid_t uid, float frameRate) override;
+ status_t updateSmallAreaDetection(std::vector<std::pair<int32_t, float>>& uidThresholdMappings);
- status_t addTransactionTraceListener(
- const sp<gui::ITransactionTraceListener>& listener) override;
+ status_t setSmallAreaDetectionThreshold(int32_t appId, float threshold);
- int getGPUContextPriority() override;
+ int getGpuContextPriority();
- status_t getMaxAcquiredBufferCount(int* buffers) const override;
+ status_t getMaxAcquiredBufferCount(int* buffers) const;
- status_t addWindowInfosListener(
- const sp<gui::IWindowInfosListener>& windowInfosListener) const override;
+ status_t addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener,
+ gui::WindowInfosListenerInfo* outResult);
status_t removeWindowInfosListener(
- const sp<gui::IWindowInfosListener>& windowInfosListener) const override;
+ const sp<gui::IWindowInfosListener>& windowInfosListener) const;
+
+ status_t getStalledTransactionInfo(
+ int pid, std::optional<TransactionHandler::StalledTransactionInfo>& result);
// Implements IBinder::DeathRecipient.
void binderDied(const wp<IBinder>& who) override;
// HWC2::ComposerCallback overrides:
- void onComposerHalVsync(hal::HWDisplayId, int64_t timestamp,
+ void onComposerHalVsync(hal::HWDisplayId, nsecs_t timestamp,
std::optional<hal::VsyncPeriodNanos>) override;
- void onComposerHalHotplug(hal::HWDisplayId, hal::Connection) override;
+ void onComposerHalHotplugEvent(hal::HWDisplayId, DisplayHotplugEvent) override;
void onComposerHalRefresh(hal::HWDisplayId) override;
void onComposerHalVsyncPeriodTimingChanged(hal::HWDisplayId,
const hal::VsyncPeriodChangeTimeline&) override;
void onComposerHalSeamlessPossible(hal::HWDisplayId) override;
void onComposerHalVsyncIdle(hal::HWDisplayId) override;
+ void onRefreshRateChangedDebug(const RefreshRateChangedDebugData&) override;
// ICompositor overrides:
+ void configure() override REQUIRES(kMainThreadContext);
+ bool commit(PhysicalDisplayId pacesetterId, const scheduler::FrameTargets&) override
+ REQUIRES(kMainThreadContext);
+ CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+ const scheduler::FrameTargeters&) override
+ REQUIRES(kMainThreadContext);
- // 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 sample() override;
- /*
- * ISchedulerCallback
- */
-
- // 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.
+ // ISchedulerCallback overrides:
+ void requestHardwareVsync(PhysicalDisplayId, bool) override;
+ void requestDisplayModes(std::vector<display::DisplayModeRequest>) override;
void kernelTimerChanged(bool expired) override;
- // Called when the frame rate override list changed to trigger an event.
void triggerOnFrameRateOverridesChanged() override;
+ void onChoreographerAttached() override;
+
+ // ICEPowerCallback overrides:
+ void notifyCpuLoadUp() override;
+
+ // IVsyncTrackerCallback overrides
+ void onVsyncGenerated(TimePoint expectedPresentTime, ftl::NonNull<DisplayModePtr>,
+ Fps renderRate) 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::optional<KernelIdleTimerController>, std::chrono::milliseconds>
@@ -708,90 +710,124 @@ 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;
+ // Show hdr sdr ratio overlay
+ bool mHdrSdrRatioOverlay = false;
+
+ void setDesiredMode(display::DisplayModeRequest&&, bool force = false) REQUIRES(mStateLock);
+
+ status_t setActiveModeFromBackdoor(const sp<display::DisplayToken>&, DisplayModeId, Fps minFps,
+ Fps maxFps);
+
+ void initiateDisplayModeChanges() REQUIRES(mStateLock, kMainThreadContext);
+ void finalizeDisplayModeChange(DisplayDevice&) REQUIRES(mStateLock, kMainThreadContext);
+
+ // TODO(b/241285191): Replace DisplayDevice with DisplayModeRequest, and move to Scheduler.
+ void dropModeRequest(const sp<DisplayDevice>&) REQUIRES(mStateLock);
+ void applyActiveMode(const sp<DisplayDevice>&) REQUIRES(mStateLock);
- // 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) REQUIRES(mStateLock);
- status_t setActiveModeFromBackdoor(const sp<IBinder>& displayToken, int id);
- // Sets the active mode and a new refresh rate in SF.
- void updateInternalStateWithChangedMode() REQUIRES(mStateLock);
- // Calls to setActiveMode on the main thread if there is a pending mode change
- // that needs to be applied.
- void setActiveModeInHwcIfNeeded() REQUIRES(mStateLock);
- void clearDesiredActiveModeState(const sp<DisplayDevice>&) REQUIRES(mStateLock);
- // Called when active mode is no longer is progress
- void desiredActiveModeChangeDone(const sp<DisplayDevice>&) REQUIRES(mStateLock);
// Called on the main thread in response to setPowerMode()
void setPowerModeInternal(const sp<DisplayDevice>& 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<DisplayDevice>& 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<scheduler::FrameRateMode> getPreferredDisplayMode(
+ PhysicalDisplayId, DisplayModeId defaultModeId) const REQUIRES(mStateLock);
- // Sets the desired display mode specs.
status_t setDesiredDisplayModeSpecsInternal(
- const sp<DisplayDevice>& display,
- const std::optional<scheduler::RefreshRateConfigs::Policy>& policy, bool overridePolicy)
- EXCLUDES(mStateLock);
-
- void commitTransactions() EXCLUDES(mStateLock);
- void commitTransactionsLocked(uint32_t transactionFlags) REQUIRES(mStateLock);
+ const sp<DisplayDevice>&, const scheduler::RefreshRateSelector::PolicyVariant&)
+ EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
+
+ bool shouldApplyRefreshRateSelectorPolicy(const DisplayDevice&) const
+ REQUIRES(mStateLock, kMainThreadContext);
+
+ // TODO(b/241285191): Look up RefreshRateSelector on Scheduler to remove redundant parameter.
+ status_t applyRefreshRateSelectorPolicy(PhysicalDisplayId,
+ const scheduler::RefreshRateSelector&,
+ bool force = false)
+ REQUIRES(mStateLock, kMainThreadContext);
+
+ void commitTransactionsLegacy() EXCLUDES(mStateLock) REQUIRES(kMainThreadContext);
+ void commitTransactions() REQUIRES(kMainThreadContext, mStateLock);
+ 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<std::pair<Layer*, LayerFE*>> moveSnapshotsToCompositionArgs(
+ compositionengine::CompositionRefreshArgs& refreshArgs, bool cursorOnly);
+ void moveSnapshotsFromCompositionArgs(compositionengine::CompositionRefreshArgs& refreshArgs,
+ const std::vector<std::pair<Layer*, LayerFE*>>& layers);
+ // Return true if we must composite this frame
+ bool updateLayerSnapshotsLegacy(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
+ bool& out) REQUIRES(kMainThreadContext);
+ // Return true if we must composite this frame
+ bool updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
+ bool& out) REQUIRES(kMainThreadContext);
+ void updateLayerHistory(nsecs_t now);
+ frontend::Update flushLifecycleUpdates() REQUIRES(kMainThreadContext);
+
+ void updateInputFlinger(VsyncId vsyncId, TimePoint frameTime);
void persistDisplayBrightness(bool needsComposite) REQUIRES(kMainThreadContext);
void buildWindowInfos(std::vector<gui::WindowInfo>& outWindowInfos,
std::vector<gui::DisplayInfo>& outDisplayInfos);
void commitInputWindowCommands() REQUIRES(mStateLock);
void updateCursorAsync();
- void initScheduler(const sp<DisplayDevice>& display) REQUIRES(mStateLock);
- void updatePhaseConfiguration(const Fps&) REQUIRES(mStateLock);
- void setVsyncConfig(const VsyncModulator::VsyncConfig&, nsecs_t vsyncPeriod);
+ void initScheduler(const sp<const DisplayDevice>&) REQUIRES(kMainThreadContext, mStateLock);
+ void resetPhaseConfiguration(Fps) REQUIRES(mStateLock, kMainThreadContext);
+ void updatePhaseConfiguration(Fps) REQUIRES(mStateLock);
/*
* Transactions
*/
- bool applyTransactionState(const FrameTimelineInfo& info, Vector<ComposerState>& state,
- const Vector<DisplayState>& displays, uint32_t flags,
+ bool applyTransactionState(const FrameTimelineInfo& info,
+ std::vector<ResolvedComposerState>& state,
+ Vector<DisplayState>& 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<uint64_t>& uncacheBufferIds,
+ const int64_t postTime, bool hasListenerCallbacks,
const std::vector<ListenerCallbacks>& 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<TransactionState> flushTransactions();
+ bool applyTransactions(std::vector<TransactionState>&, VsyncId) REQUIRES(kMainThreadContext);
+ bool applyAndCommitDisplayTransactionStatesLocked(std::vector<TransactionState>& transactions)
+ REQUIRES(kMainThreadContext, mStateLock);
// Returns true if there is at least one transaction that needs to be flushed
bool transactionFlushNeeded();
-
- int flushPendingTransactionQueues(
- std::vector<TransactionState>& transactions,
- std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
- std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions,
- bool tryApplyUnsignaled) REQUIRES(mStateLock, mQueueLock);
-
- int flushUnsignaledPendingTransactionQueues(
- std::vector<TransactionState>& transactions,
- std::unordered_map<sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
- std::unordered_set<sp<IBinder>, SpHash<IBinder>>& applyTokensWithUnsignaledTransactions)
- REQUIRES(mStateLock, mQueueLock);
-
- uint32_t setClientStateLocked(const FrameTimelineInfo&, ComposerState&,
+ void addTransactionReadyFilters();
+ TransactionHandler::TransactionReadiness transactionReadyTimelineCheck(
+ const TransactionHandler::TransactionFlushState& flushState)
+ REQUIRES(kMainThreadContext);
+ TransactionHandler::TransactionReadiness transactionReadyBufferCheckLegacy(
+ 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.
@@ -804,41 +840,19 @@ 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<ComposerState>& states,
- const std::unordered_map<
- sp<IBinder>, uint64_t, SpHash<IBinder>>& bufferLayersReadyToPresent,
- size_t totalTXapplied, bool tryApplyUnsignaled) const REQUIRES(mStateLock);
static LatchUnsignaledConfig getLatchUnsignaledConfig();
- bool shouldLatchUnsignaled(const sp<Layer>& layer, const layer_state_t&, size_t numStates,
- size_t totalTXapplied) const;
- bool stopTransactionProcessing(const std::unordered_set<sp<IBinder>, SpHash<IBinder>>&
- applyTokensWithUnsignaledTransactions) const;
- bool applyTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId);
- bool applyTransactionsLocked(std::vector<TransactionState>& transactions, int64_t vsyncId)
+ bool shouldLatchUnsignaled(const layer_state_t&, size_t numStates, bool firstTransaction) const;
+ bool applyTransactionsLocked(std::vector<TransactionState>& 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<IBinder>* outHandle,
- const sp<IBinder>& parentHandle, int32_t* outLayerId,
- const sp<Layer>& parentLayer = nullptr,
- uint32_t* outTransformHint = nullptr);
-
- status_t createBufferQueueLayer(LayerCreationArgs& args, PixelFormat& format,
- sp<IBinder>* outHandle, sp<IGraphicBufferProducer>* outGbp,
- sp<Layer>* outLayer);
+ status_t createLayer(LayerCreationArgs& args, gui::CreateSurfaceResult& outResult);
status_t createBufferStateLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
sp<Layer>* outLayer);
@@ -846,21 +860,17 @@ private:
status_t createEffectLayer(const LayerCreationArgs& args, sp<IBinder>* outHandle,
sp<Layer>* outLayer);
- status_t createContainerLayer(const LayerCreationArgs& args, sp<IBinder>* outHandle,
- sp<Layer>* outLayer);
-
status_t mirrorLayer(const LayerCreationArgs& args, const sp<IBinder>& mirrorFromHandle,
- sp<IBinder>* 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>& layer);
- void markLayerPendingRemovalLocked(const sp<Layer>& layer);
+ void markLayerPendingRemovalLocked(const sp<Layer>& layer) REQUIRES(mStateLock);
// add a layer to SurfaceFlinger
- status_t addClientLayer(const sp<Client>& client, const sp<IBinder>& handle,
- const sp<Layer>& lbc, const wp<Layer>& parentLayer, bool addToRoot,
+ status_t addClientLayer(LayerCreationArgs& args, const sp<IBinder>& handle,
+ const sp<Layer>& layer, const wp<Layer>& parentLayer,
uint32_t* outTransformHint);
// Traverse through all the layers and compute and cache its bounds.
@@ -869,22 +879,24 @@ private:
// Boot animation, on/off animations and screen capture
void startBootAnim();
- ftl::SharedFuture<FenceResult> captureScreenCommon(RenderAreaFuture, TraverseLayersFunction,
- ui::Size bufferSize, ui::PixelFormat,
- bool allowProtected, bool grayscale,
- const sp<IScreenCaptureListener>&);
+ void captureScreenCommon(RenderAreaFuture, GetLayerSnapshotsFunction, ui::Size bufferSize,
+ ui::PixelFormat, bool allowProtected, bool grayscale,
+ const sp<IScreenCaptureListener>&);
ftl::SharedFuture<FenceResult> captureScreenCommon(
- RenderAreaFuture, TraverseLayersFunction,
+ RenderAreaFuture, GetLayerSnapshotsFunction,
const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
- bool grayscale, const sp<IScreenCaptureListener>&);
+ bool grayscale, bool isProtected, const sp<IScreenCaptureListener>&);
ftl::SharedFuture<FenceResult> renderScreenImpl(
- const RenderArea&, TraverseLayersFunction,
- const std::shared_ptr<renderengine::ExternalTexture>&, bool canCaptureBlackoutContent,
- bool regionSampling, bool grayscale, ScreenCaptureResults&) EXCLUDES(mStateLock);
+ std::shared_ptr<const RenderArea>, GetLayerSnapshotsFunction,
+ const std::shared_ptr<renderengine::ExternalTexture>&, bool regionSampling,
+ bool grayscale, bool isProtected, 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<uint32_t> excludeLayerIds,
+ const LayerVector::Visitor&);
void readPersistentProperties();
@@ -893,8 +905,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<const DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) const
REQUIRES(mStateLock) {
@@ -902,8 +915,9 @@ private:
}
sp<DisplayDevice> getDisplayDeviceLocked(const wp<IBinder>& displayToken) REQUIRES(mStateLock) {
- const sp<DisplayDevice> nullDisplay;
- return mDisplays.get(displayToken).value_or(std::cref(nullDisplay));
+ return mDisplays.get(displayToken)
+ .or_else(ftl::static_ref<sp<DisplayDevice>>([] { return nullptr; }))
+ .value();
}
sp<const DisplayDevice> getDisplayDeviceLocked(PhysicalDisplayId id) const
@@ -923,6 +937,14 @@ private:
return findDisplay([id](const auto& display) { return display.getId() == id; });
}
+ std::shared_ptr<compositionengine::Display> getCompositionDisplayLocked(DisplayId id) const
+ REQUIRES(mStateLock) {
+ if (const auto display = getDisplayDeviceLocked(id)) {
+ return display->getCompositionDisplay();
+ }
+ return nullptr;
+ }
+
// Returns the primary display or (for foldables) the active display, assuming that the inner
// and outer displays have mutually exclusive power states.
sp<const DisplayDevice> getDefaultDisplayDeviceLocked() const REQUIRES(mStateLock) {
@@ -930,12 +952,12 @@ private:
}
sp<DisplayDevice> 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<const DisplayDevice> getDefaultDisplayDevice() const EXCLUDES(mStateLock) {
@@ -943,11 +965,27 @@ private:
return getDefaultDisplayDeviceLocked();
}
+ using DisplayDeviceAndSnapshot =
+ std::pair<sp<DisplayDevice>, display::PhysicalDisplay::SnapshotRef>;
+
+ // Combinator for ftl::Optional<PhysicalDisplay>::and_then.
+ auto getDisplayDeviceAndSnapshot() REQUIRES(mStateLock) {
+ return [this](const display::PhysicalDisplay& display) REQUIRES(
+ mStateLock) -> ftl::Optional<DisplayDeviceAndSnapshot> {
+ 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 <typename Predicate>
sp<DisplayDevice> findDisplay(Predicate p) const REQUIRES(mStateLock) {
const auto it = std::find_if(mDisplays.begin(), mDisplays.end(),
- [&](const auto& pair) { return p(*pair.second); });
+ [&](const auto& pair)
+ REQUIRES(mStateLock) { return p(*pair.second); });
return it == mDisplays.end() ? nullptr : it->second;
}
@@ -956,10 +994,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<const Layer>& layer, const Region& dirty);
+ void invalidateLayerStack(const ui::LayerFilter& layerFilter, const Region& dirty);
- bool isDisplayActiveLocked(const sp<const DisplayDevice>& 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)};
}
/*
@@ -976,14 +1019,8 @@ private:
/*
* Compositing
*/
- void postComposition();
- void getCompositorTiming(CompositorTiming* compositorTiming);
- void updateCompositorTiming(const DisplayStatInfo& stats, nsecs_t compositeTime,
- std::shared_ptr<FenceTime>& presentFenceTime);
- void setCompositorTimingSnapped(const DisplayStatInfo& stats,
- nsecs_t compositeToPresentLatency);
-
- void postFrame() REQUIRES(kMainThreadContext);
+ void onCompositionPresented(PhysicalDisplayId pacesetterId, const scheduler::FrameTargeters&,
+ nsecs_t presentStartTime) REQUIRES(kMainThreadContext);
/*
* Display management
@@ -991,75 +1028,54 @@ private:
std::pair<DisplayModes, DisplayModePtr> 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<DisplayDevice> setupNewDisplayDeviceInternal(
const wp<IBinder>& displayToken,
std::shared_ptr<compositionengine::Display> compositionDisplay,
const DisplayDeviceState& state,
const sp<compositionengine::DisplaySurface>& displaySurface,
const sp<IGraphicBufferProducer>& producer) REQUIRES(mStateLock);
- void processDisplayChangesLocked() REQUIRES(mStateLock);
- void processDisplayRemoved(const wp<IBinder>& displayToken) REQUIRES(mStateLock);
+ void processDisplayChangesLocked() REQUIRES(mStateLock, kMainThreadContext);
+ void processDisplayRemoved(const wp<IBinder>& displayToken)
+ REQUIRES(mStateLock, kMainThreadContext);
void processDisplayChanged(const wp<IBinder>& 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);
+ void dispatchDisplayHotplugEvent(PhysicalDisplayId, bool connected);
+ void dispatchDisplayModeChangeEvent(PhysicalDisplayId, const scheduler::FrameRateMode&)
+ REQUIRES(mStateLock);
/*
* VSYNC
*/
nsecs_t getVsyncPeriodFromHWC() const REQUIRES(mStateLock);
- void setHWCVsyncEnabled(PhysicalDisplayId id, hal::Vsync enabled) {
- mLastHWCVsyncState = enabled;
- getHwComposer().setVsyncEnabled(id, enabled);
- }
-
- struct FenceWithFenceTime {
- sp<Fence> fence = Fence::NO_FENCE;
- std::shared_ptr<FenceTime> fenceTime = FenceTime::NO_FENCE;
- };
-
- // Gets the fence for the previous frame.
- // Must be called on the main thread.
- FenceWithFenceTime previousFrameFence();
-
- // 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);
-
- // 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();
-
- // 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;
-
/*
* Display identification
*/
- sp<IBinder> getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const
+ sp<display::DisplayToken> getPhysicalDisplayTokenLocked(PhysicalDisplayId displayId) const
REQUIRES(mStateLock) {
- const sp<IBinder> 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<sp<display::DisplayToken>>(nullptr); })
+ .value();
}
std::optional<PhysicalDisplayId> getPhysicalDisplayIdLocked(
- const sp<IBinder>& displayToken) const REQUIRES(mStateLock) {
- for (const auto& [id, token] : mPhysicalDisplayTokens) {
- if (token == displayToken) {
- return id;
- }
- }
- return {};
- }
+ const sp<display::DisplayToken>&) const REQUIRES(mStateLock);
// Returns the first display connected at boot.
//
@@ -1082,15 +1098,22 @@ private:
VirtualDisplayId acquireVirtualDisplay(ui::Size, ui::PixelFormat) REQUIRES(mStateLock);
void releaseVirtualDisplay(VirtualDisplayId);
- void onActiveDisplayChangedLocked(const sp<DisplayDevice>& activeDisplay) REQUIRES(mStateLock);
+ // Returns a display other than `mActiveDisplayId` that can be activated, if any.
+ sp<DisplayDevice> getActivatableDisplay() const REQUIRES(mStateLock, kMainThreadContext);
+
+ void onActiveDisplayChangedLocked(const DisplayDevice* inactiveDisplayPtr,
+ const DisplayDevice& activeDisplay)
+ REQUIRES(mStateLock, kMainThreadContext);
- void onActiveDisplaySizeChanged(const sp<DisplayDevice>& activeDisplay);
+ void onActiveDisplaySizeChanged(const DisplayDevice&);
/*
* Debugging & dumpsys
*/
- void dumpAllLocked(const DumpArgs& args, const std::string& compositionLayers,
- std::string& result) const REQUIRES(mStateLock);
+ void dumpAll(const DumpArgs& args, const std::string& compositionLayers,
+ std::string& result) const EXCLUDES(mStateLock);
+ void dumpHwcLayersMinidump(std::string& result) const REQUIRES(mStateLock, kMainThreadContext);
+ void dumpHwcLayersMinidumpLockedLegacy(std::string& result) const REQUIRES(mStateLock);
void appendSfConfigString(std::string& result) const;
void listLayersLocked(std::string& result) const;
@@ -1098,26 +1121,35 @@ 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);
void dumpDisplayIdentificationData(std::string& result) const REQUIRES(mStateLock);
void dumpRawDisplayIdentificationData(const DumpArgs&, std::string& result) const;
void dumpWideColorInfo(std::string& result) const REQUIRES(mStateLock);
+ void dumpHdrInfo(std::string& result) const REQUIRES(mStateLock);
+ void dumpFrontEnd(std::string& result) REQUIRES(kMainThreadContext);
+ void dumpVisibleFrontEnd(std::string& result) REQUIRES(mStateLock, kMainThreadContext);
- LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
- void dumpOffscreenLayersProto(LayersProto& layersProto,
+ perfetto::protos::LayersProto dumpDrawingStateProto(uint32_t traceFlags) const;
+ void dumpOffscreenLayersProto(perfetto::protos::LayersProto& layersProto,
uint32_t traceFlags = LayerTracing::TRACE_ALL) const;
- void dumpDisplayProto(LayersTraceProto& layersTraceProto) const;
+ google::protobuf::RepeatedPtrField<perfetto::protos::DisplayProto> dumpDisplayProto() const;
+ void doActiveLayersTracingIfNeeded(bool isCompositionComputed, bool visibleRegionDirty,
+ TimePoint, VsyncId) REQUIRES(kMainThreadContext);
+ perfetto::protos::LayersSnapshotProto takeLayersSnapshotProto(uint32_t flags, TimePoint,
+ VsyncId, bool visibleRegionDirty)
+ REQUIRES(kMainThreadContext);
// Dumps state from HW Composer
void dumpHwc(std::string& result) const;
- LayersProto dumpProtoFromMainThread(uint32_t traceFlags = LayerTracing::TRACE_ALL)
- EXCLUDES(mStateLock);
+ perfetto::protos::LayersProto dumpProtoFromMainThread(
+ uint32_t traceFlags = LayerTracing::TRACE_ALL) EXCLUDES(mStateLock);
void dumpOffscreenLayers(std::string& result) EXCLUDES(mStateLock);
void dumpPlannerInfo(const DumpArgs& args, std::string& result) const REQUIRES(mStateLock);
@@ -1138,52 +1170,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<std::string, uint32_t>& getGenericLayerMetadataKeyMap() const;
- /*
- * Misc
- */
- std::vector<ui::ColorMode> getDisplayColorModes(const DisplayDevice&) REQUIRES(mStateLock);
-
static int calculateMaxAcquiredBufferCount(Fps refreshRate,
std::chrono::nanoseconds presentLatency);
int getMaxAcquiredBufferCountForRefreshRate(Fps refreshRate) const;
- void updateInternalDisplayVsyncLocked(const sp<DisplayDevice>& 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;
+ void initTransactionTraceWriter();
sp<StartPropertySetThread> mStartPropertySetThread;
surfaceflinger::Factory& mFactory;
pid_t mPid;
std::future<void> 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<int32_t> mTransactionFlags = 0;
- std::vector<std::shared_ptr<CountDownLatch>> mTransactionCommittedSignals;
- bool mAnimTransactionPending = false;
std::atomic<uint32_t> mUniqueTransactionId = 1;
SortedVector<sp<Layer>> 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<uint64_t> 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 =
@@ -1198,7 +1230,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
@@ -1206,9 +1237,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;
@@ -1217,44 +1247,53 @@ 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 mUpdateAttachedChoreographer = 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<sp<Layer>, SpHash<Layer>> mLayersWithQueuedFrames;
+ std::unordered_set<sp<Layer>, SpHash<Layer>> mLayersWithBuffersRemoved;
+ std::unordered_set<uint32_t> mLayersIdsWithQueuedFrames;
+
// Tracks layers that need to update a display's dirty region.
std::vector<sp<Layer>> mLayersPendingRefresh;
- // size should be longest sf-duration / shortest vsync period and round up
- std::array<FenceWithFenceTime, 5> 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<sp<Layer>> mPreviouslyComposedLayers;
BootStage mBootStage = BootStage::BOOTLOADER;
- std::vector<HotplugEvent> mPendingHotplugEvents GUARDED_BY(mStateLock);
+ struct HotplugEvent {
+ hal::HWDisplayId hwcDisplayId;
+ hal::Connection connection = hal::Connection::INVALID;
+ };
+
+ bool mIsHotplugErrViaNegVsync = false;
+
+ std::mutex mHotplugMutex;
+ std::vector<HotplugEvent> 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<wp<IBinder>, const sp<DisplayDevice>, 5> mDisplays GUARDED_BY(mStateLock);
- ftl::SmallMap<PhysicalDisplayId, const sp<IBinder>, 3> mPhysicalDisplayTokens
- GUARDED_BY(mStateLock);
+ ui::DisplayMap<wp<IBinder>, const sp<DisplayDevice>> 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<PhysicalDisplayId> mActiveDisplayId GUARDED_BY(mStateLock);
struct {
DisplayIdGenerator<GpuVirtualDisplayId> gpu;
@@ -1268,51 +1307,22 @@ private:
std::atomic_bool mForceFullDamage = false;
bool mLayerCachingEnabled = false;
- bool mPropagateBackpressureClientComposition = false;
- sp<SurfaceInterceptor> mInterceptor;
-
- LayerTracing mLayerTracing{*this};
- bool mLayerTracingEnabled = false;
+ bool mBackpressureGpuComposition = false;
+ LayerTracing mLayerTracing;
std::optional<TransactionTracing> mTransactionTracing;
- std::atomic<bool> mTracingEnabledChanged = false;
const std::shared_ptr<TimeStats> mTimeStats;
const std::unique_ptr<FrameTracer> mFrameTracer;
const std::unique_ptr<frametimeline::FrameTimeline> 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<uint32_t> mFrameMissedCount = 0;
- std::atomic<uint32_t> mHwcFrameMissedCount = 0;
- std::atomic<uint32_t> 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).
- std::mutex mTexturePoolMutex;
- uint32_t mTexturePoolSize = 0;
- std::vector<uint32_t> mTexturePool;
-
- mutable Mutex mQueueLock;
- Condition mTransactionQueueCV;
- std::unordered_map<sp<IBinder>, std::queue<TransactionState>, IListenerHash>
- mPendingTransactionQueues GUARDED_BY(mQueueLock);
- std::deque<TransactionState> mTransactionQueue GUARDED_BY(mQueueLock);
- /*
- * Feature prototyping
- */
-
- // Static screen stats
- bool mHasPoweredOff = false;
-
std::atomic<size_t> mNumLayers = 0;
// to linkToDeath
@@ -1335,21 +1345,26 @@ 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<renderengine::RenderEngine> mRenderEngine;
+ std::atomic<int> mNumTrustedPresentationListeners = 0;
+
std::unique_ptr<compositionengine::CompositionEngine> mCompositionEngine;
+
+ CompositionCoveragePerDisplay 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
*/
@@ -1360,18 +1375,8 @@ private:
// Stores phase offsets configured per refresh rate.
std::unique_ptr<scheduler::VsyncConfiguration> mVsyncConfiguration;
- // Optional to defer construction until PhaseConfiguration is created.
- sp<VsyncModulator> mVsyncModulator;
-
std::unique_ptr<scheduler::RefreshRateStats> mRefreshRateStats;
-
- std::atomic<nsecs_t> mExpectedPresentTime = 0;
- nsecs_t mScheduledPresentTime = 0;
- hal::Vsync mHWCVsyncPendingState = hal::Vsync::DISABLE;
- hal::Vsync mLastHWCVsyncState = hal::Vsync::DISABLE;
-
- // below flags are set by main thread only
- bool mSetActiveModePending = false;
+ scheduler::PresentLatencyTracker mPresentLatencyTracker GUARDED_BY(kMainThreadContext);
bool mLumaSampling = true;
sp<RegionSamplingThread> mRegionSamplingThread;
@@ -1388,7 +1393,9 @@ private:
std::unique_ptr<Hwc2::PowerAdvisor> mPowerAdvisor;
- void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock);
+ void enableRefreshRateOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
+
+ void enableHdrSdrRatioOverlay(bool enable) REQUIRES(mStateLock, kMainThreadContext);
// Flag used to set override desired display mode from backdoor
bool mDebugDisplayModeSetByBackdoor = false;
@@ -1403,39 +1410,55 @@ private:
std::unordered_map<DisplayId, sp<HdrLayerInfoReporter>> mHdrLayerInfoListeners
GUARDED_BY(mStateLock);
+
mutable std::mutex mCreatedLayersLock;
- struct LayerCreatedState {
- LayerCreatedState(const wp<Layer>& layer, const wp<Layer> parent, bool addToRoot)
- : layer(layer), initialParent(parent), addToRoot(addToRoot) {}
- wp<Layer> 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<Layer> 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<LayerCreatedState> mCreatedLayers GUARDED_BY(mCreatedLayersLock);
- bool commitCreatedLayers();
- void handleLayerCreatedLocked(const LayerCreatedState& state) REQUIRES(mStateLock);
+ bool commitCreatedLayers(VsyncId, std::vector<LayerCreatedState>& createdLayers);
+ void handleLayerCreatedLocked(const LayerCreatedState&, VsyncId) REQUIRES(mStateLock);
+
+ mutable std::mutex mMirrorDisplayLock;
+ struct MirrorDisplayState {
+ MirrorDisplayState(ui::LayerStack layerStack, sp<IBinder>& rootHandle,
+ const sp<Client>& client)
+ : layerStack(layerStack), rootHandle(rootHandle), client(client) {}
+
+ ui::LayerStack layerStack;
+ sp<IBinder> rootHandle;
+ const sp<Client> client;
+ };
+ std::vector<MirrorDisplayState> mMirrorDisplays GUARDED_BY(mMirrorDisplayLock);
+ bool commitMirrorDisplays(VsyncId);
std::atomic<ui::Transform::RotationFlags> 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<IBinder> mActiveDisplayToken GUARDED_BY(mStateLock);
+ bool isHdrSdrRatioOverlayEnabled() const REQUIRES(mStateLock) {
+ return hasDisplay(
+ [](const auto& display) { return display.isHdrSdrRatioOverlayEnabled(); });
+ }
+ std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+ std::optional<ui::LayerStack> layerStack, uint32_t uid,
+ std::function<bool(const frontend::LayerSnapshot&, bool& outStopTraversal)>
+ snapshotFilterFn);
+ std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+ std::optional<ui::LayerStack> layerStack, uint32_t uid,
+ std::unordered_set<uint32_t> excludeLayerIds);
+ std::function<std::vector<std::pair<Layer*, sp<LayerFE>>>()> getLayerSnapshotsForScreenshots(
+ uint32_t rootLayerId, uint32_t uid, std::unordered_set<uint32_t> excludeLayerIds,
+ bool childrenOnly, const std::optional<FloatRect>& optionalParentCrop);
const sp<WindowInfosListenerInvoker> mWindowInfosListenerInvoker;
- FlagManager mFlagManager;
-
// returns the framerate of the layer with the given sequence ID
float getLayerFramerate(nsecs_t now, int32_t id) const {
return mScheduler->getLayerFramerate(now, id);
@@ -1443,42 +1466,138 @@ private:
bool mPowerHintSessionEnabled;
- struct {
- bool late = false;
- bool early = false;
- } mPowerHintSessionMode;
+ bool mLayerLifecycleManagerEnabled = false;
+ bool mLegacyFrontEndEnabled = true;
+
+ frontend::LayerLifecycleManager mLayerLifecycleManager;
+ frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
+ frontend::LayerSnapshotBuilder mLayerSnapshotBuilder;
+
+ std::vector<std::pair<uint32_t, std::string>> mDestroyedHandles;
+ std::vector<std::unique_ptr<frontend::RequestedLayerState>> mNewLayers;
+ std::vector<LayerCreationArgs> mNewLayerArgs;
+ // These classes do not store any client state but help with managing transaction callbacks
+ // and stats.
+ std::unordered_map<uint32_t, sp<Layer>> mLegacyLayers;
+
+ TransactionHandler mTransactionHandler;
+ ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> mFrontEndDisplayInfos;
+ bool mFrontEndDisplayInfosChanged = false;
+
+ // WindowInfo ids visible during the last commit.
+ std::unordered_set<int32_t> mVisibleWindowIds;
+
+ // Mirroring
+ // Map of displayid to mirrorRoot
+ ftl::SmallMap<int64_t, sp<SurfaceControl>, 3> mMirrorMapForDebug;
+
+ // NotifyExpectedPresentHint
+ struct NotifyExpectedPresentData {
+ // lastExpectedPresentTimestamp is read and write from multiple threads such as
+ // main thread, EventThread, MessageQueue. And is atomic for that reason.
+ std::atomic<TimePoint> lastExpectedPresentTimestamp{};
+ Fps lastFrameInterval{};
+ };
+ std::unordered_map<PhysicalDisplayId, NotifyExpectedPresentData> mNotifyExpectedPresentMap;
- nsecs_t mAnimationTransactionTimeout = s2ns(5);
+ void notifyExpectedPresentIfRequired(PhysicalDisplayId, Period vsyncPeriod,
+ TimePoint expectedPresentTime, Fps frameInterval,
+ std::optional<Period> timeoutOpt);
- friend class SurfaceComposerAIDL;
+ void sfdo_enableRefreshRateOverlay(bool active);
+ void sfdo_setDebugFlash(int delay);
+ void sfdo_scheduleComposite();
+ void sfdo_scheduleCommit();
+ void sfdo_forceClientComposition(bool enabled);
};
class SurfaceComposerAIDL : public gui::BnSurfaceComposer {
public:
- SurfaceComposerAIDL(sp<SurfaceFlinger> sf) { mFlinger = sf; }
-
+ SurfaceComposerAIDL(sp<SurfaceFlinger> sf) : mFlinger(std::move(sf)) {}
+
+ binder::Status bootFinished() override;
+ binder::Status createDisplayEventConnection(
+ VsyncSource vsyncSource, EventRegistration eventRegistration,
+ const sp<IBinder>& layerHandle,
+ sp<gui::IDisplayEventConnection>* outConnection) override;
+ binder::Status createConnection(sp<gui::ISurfaceComposerClient>* outClient) override;
binder::Status createDisplay(const std::string& displayName, bool secure,
- sp<IBinder>* outDisplay) override;
+ float requestedRefreshRate, sp<IBinder>* outDisplay) override;
binder::Status destroyDisplay(const sp<IBinder>& display) override;
binder::Status getPhysicalDisplayIds(std::vector<int64_t>* outDisplayIds) override;
- binder::Status getPrimaryPhysicalDisplayId(int64_t* outDisplayId) override;
binder::Status getPhysicalDisplayToken(int64_t displayId, sp<IBinder>* outDisplay) override;
binder::Status setPowerMode(const sp<IBinder>& display, int mode) override;
+ binder::Status getSupportedFrameTimestamps(std::vector<FrameEvent>* outSupported) override;
binder::Status getDisplayStats(const sp<IBinder>& display,
gui::DisplayStatInfo* outStatInfo) override;
binder::Status getDisplayState(const sp<IBinder>& 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<IBinder>& display,
+ gui::DynamicDisplayInfo* outInfo) override;
+ binder::Status getDisplayNativePrimaries(const sp<IBinder>& display,
+ gui::DisplayPrimaries* outPrimaries) override;
+ binder::Status setActiveColorMode(const sp<IBinder>& display, int colorMode) override;
+ binder::Status setBootDisplayMode(const sp<IBinder>& display, int displayModeId) override;
binder::Status clearBootDisplayMode(const sp<IBinder>& display) override;
binder::Status getBootDisplayModeSupport(bool* outMode) override;
+ binder::Status getOverlaySupport(gui::OverlayProperties* outProperties) override;
+ binder::Status getHdrConversionCapabilities(
+ std::vector<gui::HdrConversionCapability>*) override;
+ binder::Status setHdrConversionStrategy(const gui::HdrConversionStrategy& hdrConversionStrategy,
+ int32_t*) override;
+ binder::Status getHdrOutputConversionSupport(bool* outSupport) override;
binder::Status setAutoLowLatencyMode(const sp<IBinder>& display, bool on) override;
binder::Status setGameContentType(const sp<IBinder>& display, bool on) override;
binder::Status captureDisplay(const DisplayCaptureArgs&,
const sp<IScreenCaptureListener>&) override;
- binder::Status captureDisplayById(int64_t, const sp<IScreenCaptureListener>&) override;
+ binder::Status captureDisplayById(int64_t, const CaptureArgs&,
+ const sp<IScreenCaptureListener>&) override;
binder::Status captureLayers(const LayerCaptureArgs&,
const sp<IScreenCaptureListener>&) 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<IBinder>& display,
+ const std::vector<int32_t>& hdrTypes) override;
+ binder::Status onPullAtom(int32_t atomId, gui::PullAtomData* outPullData) override;
+ binder::Status getLayerDebugInfo(std::vector<gui::LayerDebugInfo>* outLayers) override;
+ binder::Status getCompositionPreference(gui::CompositionPreference* outPref) override;
+ binder::Status getDisplayedContentSamplingAttributes(
+ const sp<IBinder>& display, gui::ContentSamplingAttributes* outAttrs) override;
+ binder::Status setDisplayContentSamplingEnabled(const sp<IBinder>& display, bool enable,
+ int8_t componentMask,
+ int64_t maxFrames) override;
+ binder::Status getDisplayedContentSample(const sp<IBinder>& display, int64_t maxFrames,
+ int64_t timestamp,
+ gui::DisplayedFrameStats* outStats) override;
+ binder::Status getProtectedContentSupport(bool* outSupporte) override;
binder::Status isWideColorDisplay(const sp<IBinder>& token,
bool* outIsWideColorDisplay) override;
+ binder::Status addRegionSamplingListener(
+ const gui::ARect& samplingArea, const sp<IBinder>& stopLayerHandle,
+ const sp<gui::IRegionSamplingListener>& listener) override;
+ binder::Status removeRegionSamplingListener(
+ const sp<gui::IRegionSamplingListener>& listener) override;
+ binder::Status addFpsListener(int32_t taskId, const sp<gui::IFpsListener>& listener) override;
+ binder::Status removeFpsListener(const sp<gui::IFpsListener>& listener) override;
+ binder::Status addTunnelModeEnabledListener(
+ const sp<gui::ITunnelModeEnabledListener>& listener) override;
+ binder::Status removeTunnelModeEnabledListener(
+ const sp<gui::ITunnelModeEnabledListener>& listener) override;
+ binder::Status setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs&) override;
+ binder::Status getDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ gui::DisplayModeSpecs* outSpecs) override;
binder::Status getDisplayBrightnessSupport(const sp<IBinder>& displayToken,
bool* outSupport) override;
binder::Status setDisplayBrightness(const sp<IBinder>& displayToken,
@@ -1488,12 +1607,41 @@ public:
binder::Status removeHdrLayerInfoListener(
const sp<IBinder>& displayToken,
const sp<gui::IHdrLayerInfoListener>& 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<IBinder>& displayToken,
+ std::optional<gui::DisplayDecorationSupport>* outSupport) override;
+ binder::Status setGameModeFrameRateOverride(int32_t uid, float frameRate) override;
+ binder::Status setGameDefaultFrameRateOverride(int32_t uid, float frameRate) override;
+ binder::Status enableRefreshRateOverlay(bool active) override;
+ binder::Status setDebugFlash(int delay) override;
+ binder::Status scheduleComposite() override;
+ binder::Status scheduleCommit() override;
+ binder::Status forceClientComposition(bool enabled) override;
+ binder::Status updateSmallAreaDetection(const std::vector<int32_t>& appIds,
+ const std::vector<float>& thresholds) override;
+ binder::Status setSmallAreaDetectionThreshold(int32_t appId, float threshold) override;
+ binder::Status getGpuContextPriority(int32_t* outPriority) override;
+ binder::Status getMaxAcquiredBufferCount(int32_t* buffers) override;
+ binder::Status addWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener,
+ gui::WindowInfosListenerInfo* outInfo) override;
+ binder::Status removeWindowInfosListener(
+ const sp<gui::IWindowInfosListener>& windowInfosListener) override;
+ binder::Status getStalledTransactionInfo(
+ int pid, std::optional<gui::StalledTransactionInfo>* outInfo) override;
+ binder::Status getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) 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<SurfaceFlinger> mFlinger;
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
index b81b445dad..7e6894d3f7 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.cpp
@@ -22,22 +22,16 @@
#include <cutils/properties.h>
#include <ui/GraphicBuffer.h>
-#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<scheduler::VsyncConfiguration> DefaultFactory::createVsyncConfig
}
}
-sp<SurfaceInterceptor> DefaultFactory::createSurfaceInterceptor() {
- return new android::impl::SurfaceInterceptor();
-}
-
sp<StartPropertySetThread> DefaultFactory::createStartPropertySetThread(
bool timestampPropertyValue) {
- return new StartPropertySetThread(timestampPropertyValue);
+ return sp<StartPropertySetThread>::make(timestampPropertyValue);
}
sp<DisplayDevice> DefaultFactory::createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) {
- return new DisplayDevice(creationArgs);
+ return sp<DisplayDevice>::make(creationArgs);
}
sp<GraphicBuffer> 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<GraphicBuffer>::make(width, height, format, layerCount, usage, requestorName);
}
void DefaultFactory::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
@@ -84,18 +74,6 @@ void DefaultFactory::createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
BufferQueue::createBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
}
-sp<IGraphicBufferProducer> DefaultFactory::createMonitoredProducer(
- const sp<IGraphicBufferProducer>& producer, const sp<SurfaceFlinger>& flinger,
- const wp<Layer>& layer) {
- return new MonitoredProducer(producer, flinger, layer);
-}
-
-sp<BufferLayerConsumer> DefaultFactory::createBufferLayerConsumer(
- const sp<IGraphicBufferConsumer>& consumer, renderengine::RenderEngine& renderEngine,
- uint32_t textureName, Layer* layer) {
- return new BufferLayerConsumer(consumer, renderEngine, textureName, layer);
-}
-
std::unique_ptr<surfaceflinger::NativeWindowSurface> DefaultFactory::createNativeWindowSurface(
const sp<IGraphicBufferProducer>& producer) {
return surfaceflinger::impl::createNativeWindowSurface(producer);
@@ -105,20 +83,16 @@ std::unique_ptr<compositionengine::CompositionEngine> DefaultFactory::createComp
return compositionengine::impl::createCompositionEngine();
}
-sp<ContainerLayer> DefaultFactory::createContainerLayer(const LayerCreationArgs& args) {
- return new ContainerLayer(args);
-}
-
-sp<BufferQueueLayer> DefaultFactory::createBufferQueueLayer(const LayerCreationArgs& args) {
- return new BufferQueueLayer(args);
+sp<Layer> DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) {
+ return sp<Layer>::make(args);
}
-sp<BufferStateLayer> DefaultFactory::createBufferStateLayer(const LayerCreationArgs& args) {
- return new BufferStateLayer(args);
+sp<Layer> DefaultFactory::createEffectLayer(const LayerCreationArgs& args) {
+ return sp<Layer>::make(args);
}
-sp<EffectLayer> DefaultFactory::createEffectLayer(const LayerCreationArgs& args) {
- return new EffectLayer(args);
+sp<LayerFE> DefaultFactory::createLayerFE(const std::string& layerName) {
+ return sp<LayerFE>::make(layerName);
}
std::unique_ptr<FrameTracer> DefaultFactory::createFrameTracer() {
diff --git a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
index 501629d4da..2c6de0e113 100644
--- a/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
+++ b/services/surfaceflinger/SurfaceFlingerDefaultFactory.h
@@ -29,7 +29,6 @@ public:
std::unique_ptr<HWComposer> createHWComposer(const std::string& serviceName) override;
std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
Fps currentRefreshRate) override;
- sp<SurfaceInterceptor> createSurfaceInterceptor() override;
sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override;
sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs&) override;
sp<GraphicBuffer> createGraphicBuffer(uint32_t width, uint32_t height, PixelFormat format,
@@ -38,19 +37,12 @@ public:
void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
bool consumerIsSurfaceFlinger) override;
- sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer>&,
- const sp<SurfaceFlinger>&,
- const wp<Layer>&) override;
- sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer>&,
- renderengine::RenderEngine&, uint32_t tex,
- Layer*) override;
std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
const sp<IGraphicBufferProducer>&) override;
std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override;
- sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs& args) override;
- sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs& args) override;
- sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) override;
- sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) override;
+ sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) override;
+ sp<Layer> createEffectLayer(const LayerCreationArgs& args) override;
+ sp<LayerFE> createLayerFE(const std::string& layerName) override;
std::unique_ptr<FrameTracer> createFrameTracer() override;
std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) override;
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.cpp b/services/surfaceflinger/SurfaceFlingerFactory.cpp
index 3997b04f5f..7bd6cf69f3 100644
--- a/services/surfaceflinger/SurfaceFlingerFactory.cpp
+++ b/services/surfaceflinger/SurfaceFlingerFactory.cpp
@@ -26,7 +26,7 @@ namespace android::surfaceflinger {
sp<SurfaceFlinger> createSurfaceFlinger() {
static DefaultFactory factory;
- return new SurfaceFlinger(factory);
+ return sp<SurfaceFlinger>::make(factory);
}
} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/SurfaceFlingerFactory.h b/services/surfaceflinger/SurfaceFlingerFactory.h
index 6153e8e354..f310c4ac53 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<HWComposer> createHWComposer(const std::string& serviceName) = 0;
virtual std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
Fps currentRefreshRate) = 0;
- virtual sp<SurfaceInterceptor> createSurfaceInterceptor() = 0;
virtual sp<StartPropertySetThread> createStartPropertySetThread(
bool timestampPropertyValue) = 0;
@@ -86,22 +80,15 @@ public:
virtual void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
sp<IGraphicBufferConsumer>* outConsumer,
bool consumerIsSurfaceFlinger) = 0;
- virtual sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer>&,
- const sp<SurfaceFlinger>&,
- const wp<Layer>&) = 0;
- virtual sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer>&,
- renderengine::RenderEngine&,
- uint32_t tex, Layer*) = 0;
virtual std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
const sp<IGraphicBufferProducer>&) = 0;
virtual std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() = 0;
- virtual sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs& args) = 0;
- virtual sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs& args) = 0;
- virtual sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) = 0;
- virtual sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) = 0;
+ virtual sp<Layer> createBufferStateLayer(const LayerCreationArgs& args) = 0;
+ virtual sp<Layer> createEffectLayer(const LayerCreationArgs& args) = 0;
+ virtual sp<LayerFE> createLayerFE(const std::string& layerName) = 0;
virtual std::unique_ptr<FrameTracer> createFrameTracer() = 0;
virtual std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid) = 0;
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.cpp b/services/surfaceflinger/SurfaceFlingerProperties.cpp
index 20fa091730..a7650783b4 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.cpp
+++ b/services/surfaceflinger/SurfaceFlingerProperties.cpp
@@ -227,14 +227,6 @@ int32_t wcg_composition_pixel_format(PixelFormat defaultValue) {
return static_cast<int32_t>(defaultValue);
}
-int64_t color_space_agnostic_dataspace(Dataspace defaultValue) {
- auto temp = SurfaceFlingerProperties::color_space_agnostic_dataspace();
- if (temp.has_value()) {
- return *temp;
- }
- return static_cast<int64_t>(defaultValue);
-}
-
bool refresh_rate_switching(bool defaultValue) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -375,5 +367,13 @@ 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);
+}
+
+int32_t game_default_frame_rate_override(int32_t defaultValue) {
+ return SurfaceFlingerProperties::game_default_frame_rate_override().value_or(defaultValue);
+}
+
} // namespace sysprop
} // namespace android
diff --git a/services/surfaceflinger/SurfaceFlingerProperties.h b/services/surfaceflinger/SurfaceFlingerProperties.h
index 080feee686..65ebe2af21 100644
--- a/services/surfaceflinger/SurfaceFlingerProperties.h
+++ b/services/surfaceflinger/SurfaceFlingerProperties.h
@@ -71,9 +71,6 @@ int64_t wcg_composition_dataspace(
int32_t wcg_composition_pixel_format(
android::hardware::graphics::common::V1_2::PixelFormat defaultValue);
-int64_t color_space_agnostic_dataspace(
- android::hardware::graphics::common::V1_2::Dataspace defaultValue);
-
bool refresh_rate_switching(bool defaultValue);
int32_t set_idle_timer_ms(int32_t defaultValue);
@@ -102,6 +99,10 @@ bool enable_sdr_dimming(bool defaultValue);
bool ignore_hdr_camera_layers(bool defaultValue);
+bool clear_slots_with_set_layer_buffer(bool defaultValue);
+
+int32_t game_default_frame_rate_override(int32_t 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 0782fef8ea..0000000000
--- 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 <fstream>
-
-#include <android-base/file.h>
-#include <log/log.h>
-#include <utils/Trace.h>
-
-namespace android {
-
-// ----------------------------------------------------------------------------
-// TODO(marissaw): add new layer state values to SurfaceInterceptor
-
-SurfaceInterceptor::~SurfaceInterceptor() = default;
-
-namespace impl {
-
-void SurfaceInterceptor::addTransactionTraceListener(
- const sp<gui::ITransactionTraceListener>& listener) {
- sp<IBinder> 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<IBinder>& who) {
- std::scoped_lock lock(mListenersMutex);
- mTraceToggledListeners.erase(who);
-}
-
-void SurfaceInterceptor::enable(const SortedVector<sp<Layer>>& layers,
- const DefaultKeyedVector< wp<IBinder>, 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<std::mutex> 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<std::mutex> 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<IBinder>, 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<sp<Layer>>& 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<const Layer>& 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<const Layer> SurfaceInterceptor::getLayer(const wp<IBinder>& weakHandle) const {
- sp<IBinder> handle = weakHandle.promote();
- return Layer::fromHandle(handle).promote();
-}
-
-int32_t SurfaceInterceptor::getLayerId(const sp<const Layer>& layer) const {
- return layer->sequence;
-}
-
-int32_t SurfaceInterceptor::getLayerIdFromWeakRef(const wp<const Layer>& layer) const {
- if (layer == nullptr) {
- return -1;
- }
- auto strongLayer = layer.promote();
- return strongLayer == nullptr ? -1 : getLayerId(strongLayer);
-}
-
-int32_t SurfaceInterceptor::getLayerIdFromHandle(const sp<IBinder>& handle) const {
- if (handle == nullptr) {
- return -1;
- }
- const sp<const Layer> 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<BlurRegion>& 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<const Layer> 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<ComposerState>& stateUpdates,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays,
- const Vector<DisplayState>& 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<const Layer>& 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<const Layer>& 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<const IGraphicBufferProducer>& 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().string());
- }
- 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<ComposerState>& stateUpdates,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays,
- const Vector<DisplayState>& 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<std::mutex> protoGuard(mTraceMutex);
- addTransactionLocked(createTraceIncrementLocked(), stateUpdates, displays, changedDisplays,
- flags, originPid, originUid, transactionId);
-}
-
-void SurfaceInterceptor::saveSurfaceCreation(const sp<const Layer>& layer) {
- if (!mEnabled || layer == nullptr) {
- return;
- }
- ATRACE_CALL();
- std::lock_guard<std::mutex> protoGuard(mTraceMutex);
- addSurfaceCreationLocked(createTraceIncrementLocked(), layer);
-}
-
-void SurfaceInterceptor::saveSurfaceDeletion(const sp<const Layer>& layer) {
- if (!mEnabled || layer == nullptr) {
- return;
- }
- ATRACE_CALL();
- std::lock_guard<std::mutex> 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<std::mutex> protoGuard(mTraceMutex);
- addBufferUpdateLocked(createTraceIncrementLocked(), layerId, width, height, frameNumber);
-}
-
-void SurfaceInterceptor::saveVSyncEvent(nsecs_t timestamp) {
- if (!mEnabled) {
- return;
- }
- std::lock_guard<std::mutex> protoGuard(mTraceMutex);
- addVSyncUpdateLocked(createTraceIncrementLocked(), timestamp);
-}
-
-void SurfaceInterceptor::saveDisplayCreation(const DisplayDeviceState& info) {
- if (!mEnabled) {
- return;
- }
- ATRACE_CALL();
- std::lock_guard<std::mutex> protoGuard(mTraceMutex);
- addDisplayCreationLocked(createTraceIncrementLocked(), info);
-}
-
-void SurfaceInterceptor::saveDisplayDeletion(int32_t sequenceId) {
- if (!mEnabled) {
- return;
- }
- ATRACE_CALL();
- std::lock_guard<std::mutex> protoGuard(mTraceMutex);
- addDisplayDeletionLocked(createTraceIncrementLocked(), sequenceId);
-}
-
-void SurfaceInterceptor::savePowerModeUpdate(int32_t sequenceId, int32_t mode) {
- if (!mEnabled) {
- return;
- }
- ATRACE_CALL();
- std::lock_guard<std::mutex> 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 970c3e5c27..0000000000
--- 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 <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
-
-#include <mutex>
-
-#include <binder/IBinder.h>
-
-#include <gui/LayerState.h>
-
-#include <utils/KeyedVector.h>
-#include <utils/SortedVector.h>
-#include <utils/StrongPointer.h>
-#include <utils/Vector.h>
-
-#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<sp<Layer>>& layers,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays) = 0;
- virtual void disable() = 0;
- virtual bool isEnabled() = 0;
-
- virtual void addTransactionTraceListener(
- const sp<gui::ITransactionTraceListener>& listener) = 0;
- virtual void binderDied(const wp<IBinder>& who) = 0;
-
- // Intercept display and surface transactions
- virtual void saveTransaction(
- const Vector<ComposerState>& stateUpdates,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays,
- const Vector<DisplayState>& changedDisplays, uint32_t flags, int originPid,
- int originUid, uint64_t transactionId) = 0;
-
- // Intercept surface data
- virtual void saveSurfaceCreation(const sp<const Layer>& layer) = 0;
- virtual void saveSurfaceDeletion(const sp<const Layer>& 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<sp<Layer>>& layers,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays) override;
- void disable() override;
- bool isEnabled() override;
-
- void addTransactionTraceListener(const sp<gui::ITransactionTraceListener>& listener) override;
- void binderDied(const wp<IBinder>& who) override;
-
- // Intercept display and surface transactions
- void saveTransaction(const Vector<ComposerState>& stateUpdates,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays,
- const Vector<DisplayState>& changedDisplays, uint32_t flags, int originPid,
- int originUid, uint64_t transactionId) override;
-
- // Intercept surface data
- void saveSurfaceCreation(const sp<const Layer>& layer) override;
- void saveSurfaceDeletion(const sp<const Layer>& 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<IBinder>, DisplayDeviceState>& displays);
- void saveExistingSurfacesLocked(const SortedVector<sp<Layer>>& layers);
- void addInitialSurfaceStateLocked(Increment* increment, const sp<const Layer>& layer);
- void addInitialDisplayStateLocked(Increment* increment, const DisplayDeviceState& display);
-
- status_t writeProtoFileLocked();
- const sp<const Layer> getLayer(const wp<IBinder>& weakHandle) const;
- int32_t getLayerId(const sp<const Layer>& layer) const;
- int32_t getLayerIdFromWeakRef(const wp<const Layer>& layer) const;
- int32_t getLayerIdFromHandle(const sp<IBinder>& weakHandle) const;
-
- Increment* createTraceIncrementLocked();
- void addSurfaceCreationLocked(Increment* increment, const sp<const Layer>& layer);
- void addSurfaceDeletionLocked(Increment* increment, const sp<const Layer>& 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<BlurRegion>& effectRegions);
- void addSurfaceChangesLocked(Transaction* transaction, const layer_state_t& state);
- void addTransactionLocked(Increment* increment, const Vector<ComposerState>& stateUpdates,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>& displays,
- const Vector<DisplayState>& 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<const IGraphicBufferProducer>& 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<wp<IBinder>, sp<gui::ITransactionTraceListener>> 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 cab33ae379..3b2bbb0cf3 100644
--- a/services/surfaceflinger/TEST_MAPPING
+++ b/services/surfaceflinger/TEST_MAPPING
@@ -1,10 +1,47 @@
{
+ "imports": [
+ {
+ "path": "frameworks/native/libs/gui"
+ },
+ {
+ "path": "frameworks/native/services/inputflinger"
+ }
+ ],
"presubmit": [
{
"name": "libsurfaceflinger_unittest"
},
{
"name": "libcompositionengine_test"
+ },
+ {
+ "name": "libscheduler_test"
+ },
+ {
+ "name": "CtsGraphicsTestCases",
+ // flaky on mixed gsi builds
+ "options": [
+ {
+ "exclude-filter": "android.graphics.cts.CameraGpuTest#testCameraImageCaptureAndRendering"
+ },
+ {
+ "exclude-filter": "android.graphics.cts.AnimatorLeakTest#testPauseResume"
+ }
+ ]
+ },
+ {
+ "name": "CtsSurfaceControlTests"
+ },
+ {
+ "name": "CtsSurfaceControlTestsStaging"
+ }
+ ],
+ "hwasan-presubmit": [
+ {
+ "name": "libscheduler_test"
+ },
+ {
+ "name": "libsurfaceflinger_unittest"
}
]
}
diff --git a/services/surfaceflinger/TimeStats/Android.bp b/services/surfaceflinger/TimeStats/Android.bp
index 4686eed54c..c3141be9db 100644
--- a/services/surfaceflinger/TimeStats/Android.bp
+++ b/services/surfaceflinger/TimeStats/Android.bp
@@ -7,14 +7,9 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library {
- name: "libtimestats",
- srcs: [
- "TimeStats.cpp",
- ],
- header_libs: [
- "libscheduler_headers",
- ],
+cc_defaults {
+ name: "libtimestats_deps",
+
shared_libs: [
"android.hardware.graphics.composer@2.4",
"libbase",
@@ -22,17 +17,34 @@ cc_library {
"liblog",
"libprotobuf-cpp-lite",
"libtimestats_atoms_proto",
- "libtimestats_proto",
"libui",
"libutils",
],
+
+ static_libs: [
+ "libtimestats_proto",
+ ],
+
+ export_static_lib_headers: [
+ "libtimestats_proto",
+ ],
+}
+
+cc_library {
+ name: "libtimestats",
+ defaults: [
+ "libtimestats_deps",
+ ],
+ srcs: [
+ "TimeStats.cpp",
+ ],
+ header_libs: [
+ "libscheduler_headers",
+ ],
export_include_dirs: ["."],
export_header_lib_headers: [
"libscheduler_headers",
],
- export_shared_lib_headers: [
- "libtimestats_proto",
- ],
cppflags: [
"-Wall",
"-Werror",
diff --git a/services/surfaceflinger/TimeStats/TimeStats.cpp b/services/surfaceflinger/TimeStats/TimeStats.cpp
index e5a9dd47c3..368cb41779 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.cpp
+++ b/services/surfaceflinger/TimeStats/TimeStats.cpp
@@ -27,6 +27,7 @@
#include <algorithm>
#include <chrono>
+#include <cmath>
#include <unordered_map>
#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<uint8_t>* pulledData) {
std::lock_guard<std::mutex> lock(mMutex);
if (mTimeStats.statsStartLegacy == 0) {
@@ -103,7 +106,8 @@ bool TimeStats::populateGlobalAtom(std::string* pulledData) {
atom->set_client_composition_frames(mTimeStats.clientCompositionFramesLegacy);
atom->set_display_on_millis(mTimeStats.displayOnTimeLegacy);
atom->set_animation_millis(mTimeStats.presentToPresentLegacy.totalTime());
- atom->set_event_connection_count(mTimeStats.displayEventConnectionsCountLegacy);
+ // Deprecated
+ atom->set_event_connection_count(0);
*atom->mutable_frame_duration() =
histogramToProto(mTimeStats.frameDurationLegacy.hist, mMaxPulledHistogramBuckets);
*atom->mutable_render_engine_timing() =
@@ -136,10 +140,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<uint8_t>* pulledData) {
std::lock_guard<std::mutex> lock(mMutex);
std::vector<TimeStatsHelper::TimeStatsLayer*> dumpStats;
@@ -177,6 +182,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 +238,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 +255,7 @@ TimeStats::TimeStats(std::optional<size_t> maxPulledLayers,
}
}
-bool TimeStats::onPullAtom(const int atomId, std::string* pulledData) {
+bool TimeStats::onPullAtom(const int atomId, std::vector<uint8_t>* pulledData) {
bool success = false;
if (atomId == 10062) { // SURFACEFLINGER_STATS_GLOBAL_INFO
success = populateGlobalAtom(pulledData);
@@ -345,16 +357,6 @@ void TimeStats::incrementRefreshRateSwitches() {
mTimeStats.refreshRateSwitchesLegacy++;
}
-void TimeStats::recordDisplayEventConnectionCount(int32_t count) {
- if (!mEnabled.load()) return;
-
- ATRACE_CALL();
-
- std::lock_guard<std::mutex> lock(mMutex);
- mTimeStats.displayEventConnectionsCountLegacy =
- std::max(mTimeStats.displayEventConnectionsCountLegacy, count);
-}
-
static int32_t toMs(nsecs_t nanos) {
int64_t millis =
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::nanoseconds(nanos))
@@ -448,6 +450,7 @@ void TimeStats::flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayR
LayerRecord& layerRecord = mTimeStatsTracker[layerId];
TimeRecord& prevTimeRecord = layerRecord.prevTimeRecord;
+ std::optional<int32_t>& prevPresentToPresentMs = layerRecord.prevPresentToPresentMs;
std::deque<TimeRecord>& timeRecords = layerRecord.timeRecords;
const int32_t refreshRateBucket =
clampToNearestBucket(displayRefreshRate, REFRESH_RATE_BUCKET_WIDTH);
@@ -525,6 +528,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();
@@ -1054,7 +1063,6 @@ void TimeStats::clearGlobalLocked() {
mTimeStats.compositionStrategyPredictedLegacy = 0;
mTimeStats.compositionStrategyPredictionSucceededLegacy = 0;
mTimeStats.refreshRateSwitchesLegacy = 0;
- mTimeStats.displayEventConnectionsCountLegacy = 0;
mTimeStats.displayOnTimeLegacy = 0;
mTimeStats.presentToPresentLegacy.hist.clear();
mTimeStats.frameDurationLegacy.hist.clear();
diff --git a/services/surfaceflinger/TimeStats/TimeStats.h b/services/surfaceflinger/TimeStats/TimeStats.h
index 7a159b8eb7..0c227d4168 100644
--- a/services/surfaceflinger/TimeStats/TimeStats.h
+++ b/services/surfaceflinger/TimeStats/TimeStats.h
@@ -34,6 +34,8 @@
#include <scheduler/Fps.h>
+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<uint8_t>* pulledData) = 0;
virtual void parseArgs(bool asProto, const Vector<String16>& args, std::string& result) = 0;
virtual bool isEnabled() = 0;
@@ -55,9 +57,6 @@ public:
virtual void incrementMissedFrames() = 0;
// Increments the number of times the display refresh rate changed.
virtual void incrementRefreshRateSwitches() = 0;
- // Records the most up-to-date count of display event connections.
- // The stored count will be the maximum ever recoded.
- virtual void recordDisplayEventConnectionCount(int32_t count) = 0;
// Records the start and end times for a frame.
// The start time is the same as the beginning of a SurfaceFlinger
@@ -217,6 +216,7 @@ class TimeStats : public android::TimeStats {
uint32_t lateAcquireFrames = 0;
uint32_t badDesiredPresentFrames = 0;
TimeRecord prevTimeRecord;
+ std::optional<int32_t> prevPresentToPresentMs;
std::deque<TimeRecord> timeRecords;
};
@@ -242,7 +242,7 @@ public:
TimeStats(std::optional<size_t> maxPulledLayers,
std::optional<size_t> maxPulledHistogramBuckets);
- bool onPullAtom(const int atomId, std::string* pulledData) override;
+ bool onPullAtom(const int atomId, std::vector<uint8_t>* pulledData) override;
void parseArgs(bool asProto, const Vector<String16>& args, std::string& result) override;
bool isEnabled() override;
std::string miniDump() override;
@@ -250,7 +250,6 @@ public:
void incrementTotalFrames() override;
void incrementMissedFrames() override;
void incrementRefreshRateSwitches() override;
- void recordDisplayEventConnectionCount(int32_t count) override;
void recordFrameDuration(nsecs_t startTime, nsecs_t endTime) override;
void recordRenderEngineDuration(nsecs_t startTime, nsecs_t endTime) override;
@@ -290,8 +289,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<uint8_t>* pulledData);
+ bool populateLayerAtom(std::vector<uint8_t>* pulledData);
bool recordReadyLocked(int32_t layerId, TimeRecord* timeRecord);
void flushAvailableRecordsToStatsLocked(int32_t layerId, Fps displayRefreshRate,
std::optional<Fps> renderRate, SetFrameRateVote,
diff --git a/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto b/services/surfaceflinger/TimeStats/timestatsatomsproto/timestats_atoms.proto
index e45757ddfd..8615947db5 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/TimeStatsHelper.cpp b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
index cf1ca65972..cbbcb91469 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
+++ b/services/surfaceflinger/TimeStats/timestatsproto/TimeStatsHelper.cpp
@@ -115,7 +115,7 @@ std::string TimeStatsHelper::TimeStatsLayer::toString() const {
StringAppendF(&result, "badDesiredPresentFrames = %d\n", badDesiredPresentFrames);
result.append("Jank payload for this layer:\n");
result.append(jankPayload.toString());
- result.append("SetFrateRate vote for this layer:\n");
+ result.append("SetFrameRate vote for this layer:\n");
result.append(setFrameRateVote.toString());
const auto iter = deltas.find("present2present");
if (iter != deltas.end()) {
diff --git a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
index 237ae8d761..9e97f0db71 100644
--- a/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
+++ b/services/surfaceflinger/TimeStats/timestatsproto/include/timestatsproto/TimeStatsHelper.h
@@ -24,6 +24,9 @@
#include <unordered_map>
#include <vector>
+using android::gui::GameMode;
+using android::gui::LayerMetadata;
+
namespace android {
namespace surfaceflinger {
@@ -172,7 +175,6 @@ public:
int32_t clientCompositionReusedFramesLegacy = 0;
int32_t refreshRateSwitchesLegacy = 0;
int32_t compositionStrategyChangesLegacy = 0;
- int32_t displayEventConnectionsCountLegacy = 0;
int64_t displayOnTimeLegacy = 0;
Histogram presentToPresentLegacy;
Histogram frameDurationLegacy;
diff --git a/services/surfaceflinger/TracedOrdinal.h b/services/surfaceflinger/TracedOrdinal.h
index 558b3be273..1adc3a531d 100644
--- a/services/surfaceflinger/TracedOrdinal.h
+++ b/services/surfaceflinger/TracedOrdinal.h
@@ -24,16 +24,24 @@
#include <cutils/compiler.h>
#include <utils/Trace.h>
-namespace std {
+namespace android {
+
+namespace {
template <class Rep, class Period>
bool signbit(std::chrono::duration<Rep, Period> v) {
- return signbit(std::chrono::duration_cast<std::chrono::nanoseconds>(v).count());
+ return std::signbit(std::chrono::duration_cast<std::chrono::nanoseconds>(v).count());
}
-} // namespace std
-namespace android {
+template <typename Enum, typename std::enable_if<std::is_enum<Enum>::value>::type* = nullptr>
+bool signbit(Enum e) {
+ return std::signbit(static_cast<typename std::underlying_type<Enum>::type>(e));
+}
+
+template <typename T, typename std::enable_if<!std::is_enum<T>::value>::type* = nullptr>
+bool signbit(T t) {
+ return std::signbit(t);
+}
-namespace {
template <typename T>
int64_t to_int64(T v) {
return int64_t(v);
@@ -49,14 +57,12 @@ template <typename T>
class TracedOrdinal {
public:
static_assert(std::is_same<bool, T>() || (std::is_signed<T>() && std::is_integral<T>()) ||
- std::is_same<std::chrono::nanoseconds, T>(),
+ std::is_same<std::chrono::nanoseconds, T>() || std::is_enum<T>(),
"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/LayerDataSource.cpp b/services/surfaceflinger/Tracing/LayerDataSource.cpp
new file mode 100644
index 0000000000..ed1e2ec89c
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LayerDataSource.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerTracing"
+
+#include "LayerDataSource.h"
+
+#include <log/log.h>
+#include <perfetto/config/android/surfaceflinger_layers_config.pbzero.h>
+
+namespace android {
+
+void LayerDataSource::Initialize(LayerTracing& layerTracing) {
+ mLayerTracing.store(&layerTracing);
+
+ auto args = perfetto::TracingInitArgs{};
+ args.backends = perfetto::kSystemBackend;
+ // We are tracing ~50kb/entry and the default shmem buffer size (256kb) could be overrun.
+ // A shmem buffer overrun typically just stalls layer tracing, however when the stall
+ // lasts for too long perfetto assumes there is a deadlock and aborts surfaceflinger.
+ args.shmem_size_hint_kb = 1024;
+ perfetto::Tracing::Initialize(args);
+
+ perfetto::DataSourceDescriptor descriptor;
+ descriptor.set_name(android::LayerDataSource::kName);
+ LayerDataSource::Register(descriptor);
+}
+
+void LayerDataSource::UnregisterLayerTracing() {
+ mLayerTracing.store(nullptr);
+}
+
+void LayerDataSource::OnSetup(const LayerDataSource::SetupArgs& args) {
+ const auto configRaw = args.config->surfaceflinger_layers_config_raw();
+ const auto config = perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Decoder{configRaw};
+
+ if (config.has_mode() && config.mode() != LayerTracing::Mode::MODE_UNSPECIFIED) {
+ mMode = static_cast<LayerTracing::Mode>(config.mode());
+ } else {
+ mMode = LayerTracing::Mode::MODE_GENERATED_BUGREPORT_ONLY;
+ ALOGD("Received config with unspecified 'mode'."
+ " Using 'MODE_GENERATED_BUGREPORT_ONLY' as default");
+ }
+
+ mFlags = 0;
+ for (auto it = config.trace_flags(); it; ++it) {
+ mFlags |= static_cast<uint32_t>(*it);
+ }
+}
+
+void LayerDataSource::OnStart(const LayerDataSource::StartArgs&) {
+ ALOGD("Received OnStart event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+ if (auto* p = mLayerTracing.load()) {
+ p->onStart(mMode, mFlags);
+ }
+}
+
+void LayerDataSource::OnFlush(const LayerDataSource::FlushArgs& args) {
+ ALOGD("Received OnFlush event"
+ " (mode = 0x%02x, flags = 0x%02x, reason = 0x%" PRIx64 ", clone_target = 0x%0" PRIx64 ")",
+ mMode, mFlags, args.flush_flags.reason(), args.flush_flags.clone_target());
+
+ bool isBugreport = args.flush_flags.reason() == perfetto::FlushFlags::Reason::kTraceClone &&
+ args.flush_flags.clone_target() == perfetto::FlushFlags::CloneTarget::kBugreport;
+
+ if (auto* p = mLayerTracing.load()) {
+ p->onFlush(mMode, mFlags, isBugreport);
+ }
+}
+
+void LayerDataSource::OnStop(const LayerDataSource::StopArgs&) {
+ ALOGD("Received OnStop event (mode = 0x%02x, flags = 0x%02x)", mMode, mFlags);
+ if (auto* p = mLayerTracing.load()) {
+ p->onStop(mMode);
+ }
+}
+
+LayerTracing::Mode LayerDataSource::GetMode() const {
+ return mMode;
+}
+
+std::atomic<LayerTracing*> LayerDataSource::mLayerTracing = nullptr;
+
+} // namespace android
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::LayerDataSource);
diff --git a/services/surfaceflinger/Tracing/LayerDataSource.h b/services/surfaceflinger/Tracing/LayerDataSource.h
new file mode 100644
index 0000000000..7944092993
--- /dev/null
+++ b/services/surfaceflinger/Tracing/LayerDataSource.h
@@ -0,0 +1,77 @@
+/*
+ * 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 "LayerTracing.h"
+
+#include <perfetto/tracing.h>
+
+#include <atomic>
+
+namespace android {
+
+/*
+ * Thread local storage used for fast (lock free) read of data source's properties.
+ *
+ */
+struct LayerDataSourceTlsState {
+ template <typename TraceContext>
+ explicit LayerDataSourceTlsState(const TraceContext& trace_context) {
+ auto dataSource = trace_context.GetDataSourceLocked();
+ mMode = dataSource.valid()
+ ? dataSource->GetMode()
+ : perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Mode::MODE_GENERATED;
+ }
+
+ LayerTracing::Mode mMode;
+};
+
+struct LayerDataSourceTraits : public perfetto::DefaultDataSourceTraits {
+ using TlsStateType = LayerDataSourceTlsState;
+};
+
+/*
+ * Defines the Perfetto custom data source 'android.surfaceflinger.layers'.
+ *
+ * Registers the data source with Perfetto, listens to Perfetto events (setup/start/flush/stop)
+ * and writes trace packets to Perfetto.
+ *
+ */
+class LayerDataSource : public perfetto::DataSource<LayerDataSource, LayerDataSourceTraits> {
+public:
+ static void Initialize(LayerTracing&);
+ static void UnregisterLayerTracing();
+ void OnSetup(const SetupArgs&) override;
+ void OnStart(const StartArgs&) override;
+ void OnFlush(const FlushArgs&) override;
+ void OnStop(const StopArgs&) override;
+ LayerTracing::Mode GetMode() const;
+
+ static constexpr auto* kName = "android.surfaceflinger.layers";
+ static constexpr perfetto::BufferExhaustedPolicy kBufferExhaustedPolicy =
+ perfetto::BufferExhaustedPolicy::kStall;
+ static constexpr bool kRequiresCallbacksUnderLock = false;
+
+private:
+ static std::atomic<LayerTracing*> mLayerTracing;
+ LayerTracing::Mode mMode;
+ std::uint32_t mFlags;
+};
+
+} // namespace android
+
+PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(android::LayerDataSource);
diff --git a/services/surfaceflinger/Tracing/LayerTracing.cpp b/services/surfaceflinger/Tracing/LayerTracing.cpp
index 49554c7dd8..41bcdf05c3 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.cpp
+++ b/services/surfaceflinger/Tracing/LayerTracing.cpp
@@ -18,118 +18,211 @@
#define LOG_TAG "LayerTracing"
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
-#include <SurfaceFlinger.h>
-#include <android-base/stringprintf.h>
+#include "LayerTracing.h"
+
+#include "LayerDataSource.h"
+#include "Tracing/tools/LayerTraceGenerator.h"
+#include "TransactionTracing.h"
+
#include <log/log.h>
-#include <utils/SystemClock.h>
+#include <perfetto/tracing.h>
+#include <utils/Timers.h>
#include <utils/Trace.h>
-#include "LayerTracing.h"
-#include "RingBuffer.h"
-
namespace android {
-LayerTracing::LayerTracing(SurfaceFlinger& flinger) : mFlinger(flinger) {
- mBuffer = std::make_unique<RingBuffer<LayersTraceFileProto, LayersTraceProto>>();
+LayerTracing::LayerTracing() {
+ mTakeLayersSnapshotProto = [](uint32_t) { return perfetto::protos::LayersSnapshotProto{}; };
+ LayerDataSource::Initialize(*this);
+}
+
+LayerTracing::LayerTracing(std::ostream& outStream) : LayerTracing() {
+ mOutStream = std::ref(outStream);
}
-LayerTracing::~LayerTracing() = default;
+LayerTracing::~LayerTracing() {
+ LayerDataSource::UnregisterLayerTracing();
+}
-bool LayerTracing::enable() {
- std::scoped_lock lock(mTraceLock);
- if (mEnabled) {
- return false;
+void LayerTracing::setTakeLayersSnapshotProtoFunction(
+ const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>& callback) {
+ mTakeLayersSnapshotProto = callback;
+}
+
+void LayerTracing::setTransactionTracing(TransactionTracing& transactionTracing) {
+ mTransactionTracing = &transactionTracing;
+}
+
+void LayerTracing::onStart(Mode mode, uint32_t flags) {
+ switch (mode) {
+ case Mode::MODE_ACTIVE: {
+ mActiveTracingFlags.store(flags);
+ mIsActiveTracingStarted.store(true);
+ ALOGV("Starting active tracing (waiting for initial snapshot)");
+ // It might take a while before a layers change occurs and a "spontaneous" snapshot is
+ // taken. Let's manually take a snapshot, so that the trace's first entry will contain
+ // the current layers state.
+ addProtoSnapshotToOstream(mTakeLayersSnapshotProto(flags), Mode::MODE_ACTIVE);
+ ALOGD("Started active tracing (traced initial snapshot)");
+ break;
+ }
+ case Mode::MODE_GENERATED: {
+ // This tracing mode processes the buffer of transactions (owned by TransactionTracing),
+ // generates layers snapshots and writes them to perfetto. This happens every time an
+ // OnFlush event is received.
+ ALOGD("Started generated tracing (waiting for OnFlush event to generated layers)");
+ break;
+ }
+ case Mode::MODE_GENERATED_BUGREPORT_ONLY: {
+ // Same as MODE_GENERATED, but only when the received OnFlush event is due to a
+ // bugreport being taken. This mode exists because the generated layers trace is very
+ // large (hundreds of MB), hence we want to include it only in bugreports and not in
+ // field uploads.
+ //
+ // Note that perfetto communicates only whether the OnFlush event is due to a bugreport
+ // or not, hence we need an additional "bugreport only" tracing mode.
+ // If perfetto had communicated when the OnFlush is due to a field upload, then we could
+ // have had a single "generated" tracing mode that would have been a noop in case of
+ // field uploads.
+ ALOGD("Started 'generated bugreport only' tracing"
+ " (waiting for bugreport's OnFlush event to generate layers)");
+ break;
+ }
+ case Mode::MODE_DUMP: {
+ auto snapshot = mTakeLayersSnapshotProto(flags);
+ addProtoSnapshotToOstream(std::move(snapshot), Mode::MODE_DUMP);
+ ALOGD("Started dump tracing (dumped single snapshot)");
+ break;
+ }
+ default: {
+ ALOGE("Started unknown tracing mode (0x%02x)", mode);
+ }
}
- mBuffer->setSize(mBufferSizeInBytes);
- mEnabled = true;
- return true;
}
-bool LayerTracing::disable(std::string filename) {
- std::scoped_lock lock(mTraceLock);
- if (!mEnabled) {
- return false;
+void LayerTracing::onFlush(Mode mode, uint32_t flags, bool isBugreport) {
+ // In "generated" mode process the buffer of transactions (owned by TransactionTracing),
+ // generate layers snapshots and write them to perfetto.
+ if (mode != Mode::MODE_GENERATED && mode != Mode::MODE_GENERATED_BUGREPORT_ONLY) {
+ ALOGD("Skipping layers trace generation (not a 'generated' tracing session)");
+ return;
}
- mEnabled = false;
- LayersTraceFileProto fileProto = createTraceFileProto();
- mBuffer->writeToFile(fileProto, filename);
- mBuffer->reset();
- return true;
+
+ // In "generated bugreport only" mode skip the layers snapshot generation
+ // if the perfetto's OnFlush event is not due to a bugreport being taken.
+ if (mode == Mode::MODE_GENERATED_BUGREPORT_ONLY && !isBugreport) {
+ ALOGD("Skipping layers trace generation (not a bugreport OnFlush event)");
+ return;
+ }
+
+ if (!mTransactionTracing) {
+ ALOGD("Skipping layers trace generation (transactions tracing disabled)");
+ return;
+ }
+
+ auto transactionTrace = mTransactionTracing->writeToProto();
+ LayerTraceGenerator{}.generate(transactionTrace, flags, *this);
+ ALOGD("Flushed generated tracing");
}
-bool LayerTracing::isEnabled() const {
- std::scoped_lock lock(mTraceLock);
- return mEnabled;
+void LayerTracing::onStop(Mode mode) {
+ if (mode == Mode::MODE_ACTIVE) {
+ mIsActiveTracingStarted.store(false);
+ ALOGD("Stopped active tracing");
+ }
}
-status_t LayerTracing::writeToFile() {
- std::scoped_lock lock(mTraceLock);
- if (!mEnabled) {
- return STATUS_OK;
+void LayerTracing::addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot,
+ Mode mode) {
+ ATRACE_CALL();
+ if (mOutStream) {
+ writeSnapshotToStream(std::move(snapshot));
+ } else {
+ writeSnapshotToPerfetto(snapshot, mode);
}
- LayersTraceFileProto fileProto = createTraceFileProto();
- return mBuffer->writeToFile(fileProto, FILE_NAME);
}
-void LayerTracing::setTraceFlags(uint32_t flags) {
- std::scoped_lock lock(mTraceLock);
- mFlags = flags;
+bool LayerTracing::isActiveTracingStarted() const {
+ return mIsActiveTracingStarted.load();
}
-void LayerTracing::setBufferSize(size_t bufferSizeInBytes) {
- std::scoped_lock lock(mTraceLock);
- mBufferSizeInBytes = bufferSizeInBytes;
+uint32_t LayerTracing::getActiveTracingFlags() const {
+ return mActiveTracingFlags.load();
}
-bool LayerTracing::flagIsSet(uint32_t flags) const {
- return (mFlags & flags) == flags;
+bool LayerTracing::isActiveTracingFlagSet(Flag flag) const {
+ return (mActiveTracingFlags.load() & flag) != 0;
}
-LayersTraceFileProto LayerTracing::createTraceFileProto() const {
- LayersTraceFileProto fileProto;
- fileProto.set_magic_number(uint64_t(LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H) << 32 |
- LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
+perfetto::protos::LayersTraceFileProto LayerTracing::createTraceFileProto() {
+ perfetto::protos::LayersTraceFileProto fileProto;
+ fileProto.set_magic_number(
+ static_cast<uint64_t>(perfetto::protos::LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_H)
+ << 32 |
+ perfetto::protos::LayersTraceFileProto_MagicNumber_MAGIC_NUMBER_L);
+ auto timeOffsetNs = static_cast<uint64_t>(systemTime(SYSTEM_TIME_REALTIME) -
+ systemTime(SYSTEM_TIME_MONOTONIC));
+ fileProto.set_real_to_elapsed_time_offset_nanos(timeOffsetNs);
return fileProto;
}
-void LayerTracing::dump(std::string& result) const {
- std::scoped_lock lock(mTraceLock);
- base::StringAppendF(&result, "Tracing state: %s\n", mEnabled ? "enabled" : "disabled");
- mBuffer->dump(result);
+void LayerTracing::writeSnapshotToStream(perfetto::protos::LayersSnapshotProto&& snapshot) const {
+ auto fileProto = createTraceFileProto();
+ *fileProto.add_entry() = std::move(snapshot);
+ mOutStream->get() << fileProto.SerializeAsString();
}
-void LayerTracing::notify(bool visibleRegionDirty, int64_t time) {
- std::scoped_lock lock(mTraceLock);
- if (!mEnabled) {
- return;
- }
+void LayerTracing::writeSnapshotToPerfetto(const perfetto::protos::LayersSnapshotProto& snapshot,
+ Mode srcMode) {
+ const auto snapshotBytes = snapshot.SerializeAsString();
+
+ LayerDataSource::Trace([&](LayerDataSource::TraceContext context) {
+ auto dstMode = context.GetCustomTlsState()->mMode;
+ if (srcMode == Mode::MODE_GENERATED) {
+ // Layers snapshots produced by LayerTraceGenerator have srcMode == MODE_GENERATED
+ // and should be written to tracing sessions with MODE_GENERATED
+ // or MODE_GENERATED_BUGREPORT_ONLY.
+ if (dstMode != Mode::MODE_GENERATED && dstMode != Mode::MODE_GENERATED_BUGREPORT_ONLY) {
+ return;
+ }
+ } else if (srcMode != dstMode) {
+ return;
+ }
+
+ if (!checkAndUpdateLastVsyncIdWrittenToPerfetto(srcMode, snapshot.vsync_id())) {
+ return;
+ }
+ {
+ auto packet = context.NewTracePacket();
+ packet->set_timestamp(static_cast<uint64_t>(snapshot.elapsed_realtime_nanos()));
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ auto* snapshotProto = packet->set_surfaceflinger_layers_snapshot();
+ snapshotProto->AppendRawProtoBytes(snapshotBytes.data(), snapshotBytes.size());
+ }
+ {
+ // TODO (b/162206162): remove empty packet when perfetto bug is fixed.
+ // It is currently needed in order not to lose the last trace entry.
+ context.NewTracePacket();
+ }
+ });
+}
- if (!visibleRegionDirty && !flagIsSet(LayerTracing::TRACE_BUFFERS)) {
- return;
+bool LayerTracing::checkAndUpdateLastVsyncIdWrittenToPerfetto(Mode mode, std::int64_t vsyncId) {
+ // In some situations (e.g. two bugreports taken shortly one after the other) the generated
+ // sequence of layers snapshots might overlap. Here we check the snapshot's vsyncid to make
+ // sure that in generated tracing mode a given snapshot is written only once to perfetto.
+ if (mode != Mode::MODE_GENERATED && mode != Mode::MODE_GENERATED_BUGREPORT_ONLY) {
+ return true;
}
- ATRACE_CALL();
- LayersTraceProto entry;
- 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);
+ auto lastVsyncId = mLastVsyncIdWrittenToPerfetto.load();
+ while (lastVsyncId < vsyncId) {
+ if (mLastVsyncIdWrittenToPerfetto.compare_exchange_strong(lastVsyncId, vsyncId)) {
+ return true;
+ }
}
- 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);
- mBuffer->emplace(std::move(entry));
+ return false;
}
} // namespace android
diff --git a/services/surfaceflinger/Tracing/LayerTracing.h b/services/surfaceflinger/Tracing/LayerTracing.h
index 88a19ecdf1..2895ba7026 100644
--- a/services/surfaceflinger/Tracing/LayerTracing.h
+++ b/services/surfaceflinger/Tracing/LayerTracing.h
@@ -16,62 +16,120 @@
#pragma once
-#include <android-base/thread_annotations.h>
#include <layerproto/LayerProtoHeader.h>
-#include <utils/Errors.h>
-#include <utils/StrongPointer.h>
-#include <utils/Timers.h>
-#include <memory>
-#include <mutex>
-
-using namespace android::surfaceflinger;
+#include <atomic>
+#include <functional>
+#include <optional>
+#include <ostream>
namespace android {
-template <typename FileProto, typename EntryProto>
-class RingBuffer;
-
-class SurfaceFlinger;
+class TransactionTracing;
/*
* LayerTracing records layer states during surface flinging. Manages tracing state and
* configuration.
+ *
+ * The traced data can then be collected with Perfetto.
+ *
+ * The Perfetto custom data source LayerDataSource is registered with perfetto. The data source
+ * is used to listen to perfetto events (setup, start, stop, flush) and to write trace packets
+ * to perfetto.
+ *
+ * The user can configure/start/stop tracing via /system/bin/perfetto.
+ *
+ * Tracing can operate in the following modes.
+ *
+ * ACTIVE mode:
+ * A layers snapshot is taken and written to perfetto for each vsyncid commit.
+ *
+ * GENERATED mode:
+ * Listens to the perfetto 'flush' event (e.g. when a bugreport is taken).
+ * When a 'flush' event is received, the ring buffer of transactions (hold by TransactionTracing)
+ * is processed by LayerTraceGenerator, a sequence of layers snapshots is generated
+ * and written to perfetto.
+ *
+ * DUMP mode:
+ * When the 'start' event is received a single layers snapshot is taken
+ * and written to perfetto.
+ *
+ *
+ * E.g. start active mode tracing
+ * (replace mode value with MODE_DUMP, MODE_GENERATED or MODE_GENERATED_BUGREPORT_ONLY to enable
+ * different tracing modes):
+ *
+ adb shell -t perfetto \
+ -c - --txt \
+ -o /data/misc/perfetto-traces/trace \
+ <<EOF
+ unique_session_name: "surfaceflinger_layers_active"
+ buffers: {
+ size_kb: 63488
+ fill_policy: RING_BUFFER
+ }
+ data_sources: {
+ config {
+ name: "android.surfaceflinger.layers"
+ surfaceflinger_layers_config: {
+ mode: MODE_ACTIVE
+ trace_flags: TRACE_FLAG_INPUT
+ trace_flags: TRACE_FLAG_COMPOSITION
+ trace_flags: TRACE_FLAG_HWC
+ trace_flags: TRACE_FLAG_BUFFERS
+ trace_flags: TRACE_FLAG_VIRTUAL_DISPLAYS
+ }
+ }
+ }
+EOF
+ *
*/
class LayerTracing {
public:
- LayerTracing(SurfaceFlinger& flinger);
- ~LayerTracing();
- bool enable();
- bool disable(std::string filename = FILE_NAME);
- bool isEnabled() const;
- status_t writeToFile();
- LayersTraceFileProto createTraceFileProto() const;
- void notify(bool visibleRegionDirty, int64_t time);
+ using Mode = perfetto::protos::pbzero::SurfaceFlingerLayersConfig::Mode;
- enum : uint32_t {
+ enum Flag : uint32_t {
TRACE_INPUT = 1 << 1,
TRACE_COMPOSITION = 1 << 2,
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;
- void setBufferSize(size_t bufferSizeInBytes);
- void dump(std::string&) const;
+
+ LayerTracing();
+ LayerTracing(std::ostream&);
+ ~LayerTracing();
+ void setTakeLayersSnapshotProtoFunction(
+ const std::function<perfetto::protos::LayersSnapshotProto(uint32_t)>&);
+ void setTransactionTracing(TransactionTracing&);
+
+ // Start event from perfetto data source
+ void onStart(Mode mode, uint32_t flags);
+ // Flush event from perfetto data source
+ void onFlush(Mode mode, uint32_t flags, bool isBugreport);
+ // Stop event from perfetto data source
+ void onStop(Mode mode);
+
+ void addProtoSnapshotToOstream(perfetto::protos::LayersSnapshotProto&& snapshot, Mode mode);
+ bool isActiveTracingStarted() const;
+ uint32_t getActiveTracingFlags() const;
+ bool isActiveTracingFlagSet(Flag flag) const;
+ static perfetto::protos::LayersTraceFileProto createTraceFileProto();
private:
- static constexpr auto FILE_NAME = "/data/misc/wmtrace/layers_trace.winscope";
+ void writeSnapshotToStream(perfetto::protos::LayersSnapshotProto&& snapshot) const;
+ void writeSnapshotToPerfetto(const perfetto::protos::LayersSnapshotProto& snapshot, Mode mode);
+ bool checkAndUpdateLastVsyncIdWrittenToPerfetto(Mode mode, std::int64_t vsyncId);
+
+ std::function<perfetto::protos::LayersSnapshotProto(uint32_t)> mTakeLayersSnapshotProto;
+ TransactionTracing* mTransactionTracing;
- SurfaceFlinger& mFlinger;
- uint32_t mFlags = TRACE_INPUT;
- mutable std::mutex mTraceLock;
- bool mEnabled GUARDED_BY(mTraceLock) = false;
- std::unique_ptr<RingBuffer<LayersTraceFileProto, LayersTraceProto>> mBuffer
- GUARDED_BY(mTraceLock);
- size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = 20 * 1024 * 1024;
+ std::atomic<bool> mIsActiveTracingStarted{false};
+ std::atomic<uint32_t> mActiveTracingFlags{0};
+ std::atomic<std::int64_t> mLastVsyncIdWrittenToPerfetto{-1};
+ std::optional<std::reference_wrapper<std::ostream>> mOutStream;
};
} // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionDataSource.cpp b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
new file mode 100644
index 0000000000..6c9ed301ff
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionDataSource.cpp
@@ -0,0 +1,82 @@
+/*
+ * 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 "TransactionDataSource.h"
+#include "TransactionTracing.h"
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTracing"
+
+#include <log/log.h>
+
+namespace android {
+
+void TransactionDataSource::Initialize(TransactionTracing& transactionTracing) {
+ mTransactionTracing.store(&transactionTracing);
+
+ auto args = perfetto::TracingInitArgs{};
+ args.backends = perfetto::kSystemBackend;
+ perfetto::Tracing::Initialize(args);
+
+ perfetto::DataSourceDescriptor descriptor;
+ descriptor.set_name(kName);
+ TransactionDataSource::Register(descriptor);
+}
+
+void TransactionDataSource::UnregisterTransactionTracing() {
+ mTransactionTracing.store(nullptr);
+}
+
+void TransactionDataSource::OnSetup(const TransactionDataSource::SetupArgs& args) {
+ const auto configRaw = args.config->surfaceflinger_transactions_config_raw();
+ const auto config =
+ perfetto::protos::pbzero::SurfaceFlingerTransactionsConfig::Decoder{configRaw};
+
+ if (config.has_mode() && config.mode() != TransactionTracing::Mode::MODE_UNSPECIFIED) {
+ mMode = static_cast<TransactionTracing::Mode>(config.mode());
+ } else {
+ mMode = TransactionTracing::Mode::MODE_CONTINUOUS;
+ ALOGD("Received config with unspecified 'mode'. Using 'CONTINUOUS' as default");
+ }
+}
+
+void TransactionDataSource::OnStart(const StartArgs&) {
+ ALOGD("Received OnStart event");
+ if (auto* p = mTransactionTracing.load()) {
+ p->onStart(mMode);
+ }
+}
+
+void TransactionDataSource::OnFlush(const FlushArgs&) {
+ ALOGD("Received OnFlush event");
+ if (auto* p = mTransactionTracing.load()) {
+ p->onFlush(mMode);
+ }
+}
+
+void TransactionDataSource::OnStop(const StopArgs&) {
+ ALOGD("Received OnStop event");
+}
+
+TransactionTracing::Mode TransactionDataSource::GetMode() const {
+ return mMode;
+}
+
+std::atomic<TransactionTracing*> TransactionDataSource::mTransactionTracing = nullptr;
+
+} // namespace android
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(android::TransactionDataSource);
diff --git a/services/surfaceflinger/Tracing/TransactionDataSource.h b/services/surfaceflinger/Tracing/TransactionDataSource.h
new file mode 100644
index 0000000000..be8373b4ac
--- /dev/null
+++ b/services/surfaceflinger/Tracing/TransactionDataSource.h
@@ -0,0 +1,74 @@
+/*
+ * 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 "TransactionTracing.h"
+
+#include <perfetto/tracing.h>
+
+namespace android {
+
+/*
+ * Thread local storage used for fast (lock free) read of data source's properties.
+ *
+ */
+struct TransactionDataSourceTlsState {
+ template <typename TraceContext>
+ explicit TransactionDataSourceTlsState(const TraceContext& trace_context) {
+ auto dataSource = trace_context.GetDataSourceLocked();
+ mMode = dataSource.valid() ? dataSource->GetMode()
+ : TransactionTracing::Mode::MODE_CONTINUOUS;
+ }
+
+ TransactionTracing::Mode mMode;
+};
+
+struct TransactionDataSourceTraits : public perfetto::DefaultDataSourceTraits {
+ using TlsStateType = TransactionDataSourceTlsState;
+};
+
+/*
+ * Defines the Perfetto custom data source 'android.surfaceflinger.transactions'.
+ *
+ * Registers the data source with Perfetto, listens to Perfetto events (setup/start/flush/stop)
+ * and writes trace packets to Perfetto.
+ *
+ */
+class TransactionDataSource
+ : public perfetto::DataSource<TransactionDataSource, TransactionDataSourceTraits> {
+public:
+ static void Initialize(TransactionTracing&);
+ static void UnregisterTransactionTracing();
+ void OnSetup(const SetupArgs&) override;
+ void OnStart(const StartArgs&) override;
+ void OnFlush(const FlushArgs&) override;
+ void OnStop(const StopArgs&) override;
+ TransactionTracing::Mode GetMode() const;
+
+ static constexpr auto* kName = "android.surfaceflinger.transactions";
+ static constexpr perfetto::BufferExhaustedPolicy kBufferExhaustedPolicy =
+ perfetto::BufferExhaustedPolicy::kStall;
+ static constexpr bool kRequiresCallbacksUnderLock = false;
+
+private:
+ static std::atomic<TransactionTracing*> mTransactionTracing;
+ TransactionTracing::Mode mMode;
+};
+
+} // namespace android
+
+PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(android::TransactionDataSource);
diff --git a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
index a73eccf0e5..2dc89b55ba 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.cpp
@@ -15,15 +15,44 @@
*/
#include <gui/SurfaceComposerClient.h>
+#include <ui/Fence.h>
#include <ui/Rect.h>
+#include "FrontEnd/LayerCreationArgs.h"
#include "LayerProtoHelper.h"
#include "TransactionProtoParser.h"
+#include "TransactionState.h"
+#include "gui/LayerState.h"
namespace android::surfaceflinger {
-proto::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
- proto::TransactionState proto;
+class FakeExternalTexture : public renderengine::ExternalTexture {
+ const sp<GraphicBuffer> 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<GraphicBuffer>& 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;
+};
+
+perfetto::protos::TransactionState TransactionProtoParser::toProto(const TransactionState& t) {
+ perfetto::protos::TransactionState proto;
proto.set_pid(t.originPid);
proto.set_uid(t.originUid);
proto.set_vsync_id(t.frameTimelineInfo.vsyncId);
@@ -33,51 +62,40 @@ proto::TransactionState TransactionProtoParser::toProto(const TransactionState&
proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(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<int32_t>(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<int32_t>(t.mergedTransactionIds.size()));
+ for (auto& mergedTransactionId : t.mergedTransactionIds) {
+ proto.mutable_merged_transaction_ids()->Add(mergedTransactionId);
+ }
+
return proto;
}
-proto::TransactionState TransactionProtoParser::toProto(
- const std::map<int32_t /* layerId */, TracingLayerState>& states) {
- proto::TransactionState proto;
+perfetto::protos::TransactionState TransactionProtoParser::toProto(
+ const std::map<uint32_t /* layerId */, TracingLayerState>& states) {
+ perfetto::protos::TransactionState proto;
proto.mutable_layer_changes()->Reserve(static_cast<int32_t>(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<proto::LayerState_BufferData_PixelFormat>(state.pixelFormat));
- bufferProto->set_usage(state.bufferUsage);
- }
+ perfetto::protos::LayerState layerProto = toProto(state);
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 proto;
- if (layer.surface) {
- proto.set_layer_id(mMapper->getLayerId(layer.surface));
- } else {
- proto.set_layer_id(layer.layerId);
- }
-
+perfetto::protos::LayerState TransactionProtoParser::toProto(
+ const ResolvedComposerState& resolvedComposerState) {
+ perfetto::protos::LayerState proto;
+ 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);
}
@@ -99,7 +114,7 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) {
proto.set_mask(layer.mask);
}
if (layer.what & layer_state_t::eMatrixChanged) {
- proto::LayerState_Matrix22* matrixProto = proto.mutable_matrix();
+ perfetto::protos::LayerState_Matrix22* matrixProto = proto.mutable_matrix();
matrixProto->set_dsdx(layer.matrix.dsdx);
matrixProto->set_dsdy(layer.matrix.dsdy);
matrixProto->set_dtdx(layer.matrix.dtdx);
@@ -113,11 +128,11 @@ 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) {
- proto::LayerState_Color3* colorProto = proto.mutable_color();
+ perfetto::protos::LayerState_Color3* colorProto = proto.mutable_color();
colorProto->set_r(layer.color.r);
colorProto->set_g(layer.color.g);
colorProto->set_b(layer.color.b);
@@ -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);
@@ -135,28 +150,15 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) {
LayerProtoHelper::writeToProto(layer.crop, proto.mutable_crop());
}
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());
- bufferProto->set_pixel_format(static_cast<proto::LayerState_BufferData_PixelFormat>(
- 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);
+ perfetto::protos::LayerState_BufferData* bufferProto = proto.mutable_buffer_data();
+ 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<proto::LayerState_BufferData_PixelFormat>(pixelFormat));
- bufferProto->set_usage(usage);
+ static_cast<perfetto::protos::LayerState_BufferData_PixelFormat>(
+ resolvedComposerState.externalTexture->getPixelFormat()));
+ bufferProto->set_usage(resolvedComposerState.externalTexture->getUsage());
}
bufferProto->set_frame_number(layer.bufferData->frameNumber);
bufferProto->set_flags(layer.bufferData->flags.get());
@@ -180,26 +182,22 @@ 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);
}
if (layer.what & layer_state_t::eInputInfoChanged) {
if (layer.windowInfoHandle) {
const gui::WindowInfo* inputInfo = layer.windowInfoHandle->getInfo();
- proto::LayerState_WindowInfo* windowInfoProto = proto.mutable_window_info_handle();
+ perfetto::protos::LayerState_WindowInfo* windowInfoProto =
+ proto.mutable_window_info_handle();
windowInfoProto->set_layout_params_flags(inputInfo->layoutParamsFlags.get());
windowInfoProto->set_layout_params_type(
static_cast<int32_t>(inputInfo->layoutParamsType));
+ windowInfoProto->set_input_config(inputInfo->inputConfig.get());
LayerProtoHelper::writeToProto(inputInfo->touchableRegion,
windowInfoProto->mutable_touchable_region());
windowInfoProto->set_surface_inset(inputInfo->surfaceInset);
@@ -208,7 +206,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();
+ perfetto::protos::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 +215,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<int32_t>(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);
+ perfetto::protos::LayerState_Color3* colorProto = proto.mutable_color();
+ 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);
@@ -260,13 +257,13 @@ proto::LayerState TransactionProtoParser::toProto(const layer_state_t& layer) {
}
if (layer.what & layer_state_t::eDropInputModeChanged) {
proto.set_drop_input_mode(
- static_cast<proto::LayerState_DropInputMode>(layer.dropInputMode));
+ static_cast<perfetto::protos::LayerState_DropInputMode>(layer.dropInputMode));
}
return proto;
}
-proto::DisplayState TransactionProtoParser::toProto(const DisplayState& display) {
- proto::DisplayState proto;
+perfetto::protos::DisplayState TransactionProtoParser::toProto(const DisplayState& display) {
+ perfetto::protos::DisplayState proto;
proto.set_what(display.what);
proto.set_id(mMapper->getDisplayId(display.token));
@@ -290,17 +287,20 @@ proto::DisplayState TransactionProtoParser::toProto(const DisplayState& display)
return proto;
}
-proto::LayerCreationArgs TransactionProtoParser::toProto(const TracingLayerCreationArgs& args) {
- proto::LayerCreationArgs proto;
- proto.set_layer_id(args.layerId);
+perfetto::protos::LayerCreationArgs TransactionProtoParser::toProto(const LayerCreationArgs& args) {
+ perfetto::protos::LayerCreationArgs proto;
+ 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;
}
-TransactionState TransactionProtoParser::fromProto(const proto::TransactionState& proto) {
+TransactionState TransactionProtoParser::fromProto(
+ const perfetto::protos::TransactionState& proto) {
TransactionState t;
t.originPid = proto.pid();
t.originUid = proto.uid();
@@ -312,10 +312,10 @@ TransactionState TransactionProtoParser::fromProto(const proto::TransactionState
int32_t layerCount = proto.layer_changes_size();
t.states.reserve(static_cast<size_t>(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();
@@ -326,47 +326,48 @@ TransactionState TransactionProtoParser::fromProto(const proto::TransactionState
return t;
}
-void TransactionProtoParser::fromProto(const proto::LayerCreationArgs& proto,
- TracingLayerCreationArgs& outArgs) {
- outArgs.layerId = proto.layer_id();
+void TransactionProtoParser::fromProto(const perfetto::protos::LayerCreationArgs& proto,
+ 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,
+void TransactionProtoParser::mergeFromProto(const perfetto::protos::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<int32_t>(proto.parent_id());
+ outState.parentId = resolvedComposerState.parentId;
}
if (state.what & layer_state_t::eRelativeLayerChanged) {
- outState.relativeParentId = static_cast<int32_t>(proto.relative_parent_id());
+ outState.relativeParentId = resolvedComposerState.relativeParentId;
}
if (state.what & layer_state_t::eInputInfoChanged) {
- outState.inputCropId = static_cast<int32_t>(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 perfetto::protos::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 +376,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();
}
@@ -387,7 +384,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta
layer.mask = proto.mask();
}
if (proto.what() & layer_state_t::eMatrixChanged) {
- const proto::LayerState_Matrix22& matrixProto = proto.matrix();
+ const perfetto::protos::LayerState_Matrix22& matrixProto = proto.matrix();
layer.matrix.dsdx = matrixProto.dsdx();
layer.matrix.dsdy = matrixProto.dsdy();
layer.matrix.dtdx = matrixProto.dtdx();
@@ -401,11 +398,11 @@ 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) {
- const proto::LayerState_Color3& colorProto = proto.color();
+ const perfetto::protos::LayerState_Color3& colorProto = proto.color();
layer.color.r = colorProto.r();
layer.color.g = colorProto.g();
layer.color.b = colorProto.b();
@@ -413,8 +410,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();
@@ -423,11 +420,17 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta
LayerProtoHelper::readFromProto(proto.crop(), layer.crop);
}
if (proto.what() & layer_state_t::eBufferChanged) {
- const proto::LayerState_BufferData& bufferProto = proto.buffer_data();
+ const perfetto::protos::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<fake::BufferData>(bufferProto.buffer_id(), bufferProto.width(),
+ bufferProto.height(), bufferProto.pixel_format(),
+ bufferProto.usage());
+ resolvedComposerState.externalTexture =
+ std::make_shared<FakeExternalTexture>(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<BufferData::BufferDataChange>(bufferProto.flags());
layer.bufferData->cachedBuffer.id = bufferProto.cached_buffer_id();
@@ -451,32 +454,16 @@ 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<int32_t>(layerId)),
- nullptr, static_cast<int32_t>(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<int32_t>(layerId)),
- nullptr, static_cast<int32_t>(layerId));
- }
+ resolvedComposerState.relativeParentId = proto.relative_parent_id();
layer.z = proto.z();
}
if ((proto.what() & layer_state_t::eInputInfoChanged) && proto.has_window_info_handle()) {
gui::WindowInfo inputInfo;
- const proto::LayerState_WindowInfo& windowInfoProto = proto.window_info_handle();
+ const perfetto::protos::LayerState_WindowInfo& windowInfoProto = proto.window_info_handle();
inputInfo.layoutParamsFlags =
static_cast<gui::WindowInfo::Flag>(windowInfoProto.layout_params_flags());
@@ -484,30 +471,27 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta
static_cast<gui::WindowInfo::Type>(windowInfoProto.layout_params_type());
LayerProtoHelper::readFromProto(windowInfoProto.touchable_region(),
inputInfo.touchableRegion);
+ inputInfo.inputConfig =
+ ftl::Flags<gui::WindowInfo::InputConfig>(windowInfoProto.input_config());
inputInfo.surfaceInset = windowInfoProto.surface_inset();
- inputInfo.setInputConfig(gui::WindowInfo::InputConfig::NOT_FOCUSABLE,
- !windowInfoProto.focusable());
- 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 perfetto::protos::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<int32_t>(layerId));
+ resolvedComposerState.touchCropId = windowInfoProto.crop_layer_id();
+
layer.windowInfoHandle = sp<gui::WindowInfoHandle>::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<ui::Dataspace>(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();
+ const perfetto::protos::LayerState_Color3& colorProto = proto.color();
+ 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();
@@ -544,7 +528,7 @@ void TransactionProtoParser::fromProto(const proto::LayerState& proto, layer_sta
}
}
-DisplayState TransactionProtoParser::fromProto(const proto::DisplayState& proto) {
+DisplayState TransactionProtoParser::fromProto(const perfetto::protos::DisplayState& proto) {
DisplayState display;
display.what = proto.what();
display.token = mMapper->getDisplayHandle(proto.id());
@@ -569,4 +553,63 @@ DisplayState TransactionProtoParser::fromProto(const proto::DisplayState& proto)
return display;
}
+void asProto(perfetto::protos::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());
+}
+
+perfetto::protos::DisplayInfo TransactionProtoParser::toProto(
+ const frontend::DisplayInfo& displayInfo, uint32_t layerStack) {
+ perfetto::protos::DisplayInfo proto;
+ proto.set_layer_stack(layerStack);
+ proto.set_display_id(displayInfo.info.displayId);
+ proto.set_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 perfetto::protos::Transform& proto) {
+ outTransform.set(proto.dsdx(), proto.dtdx(), proto.dtdy(), proto.dsdy());
+ outTransform.set(proto.tx(), proto.ty());
+}
+
+frontend::DisplayInfo TransactionProtoParser::fromProto(
+ const perfetto::protos::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<perfetto::protos::DisplayInfo>& proto,
+ frontend::DisplayInfos& outDisplayInfos) {
+ outDisplayInfos.clear();
+ for (const perfetto::protos::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 872a901b21..b3ab71cfb5 100644
--- a/services/surfaceflinger/Tracing/TransactionProtoParser.h
+++ b/services/surfaceflinger/Tracing/TransactionProtoParser.h
@@ -15,61 +15,19 @@
*/
#pragma once
+#include <gui/fake/BufferData.h>
#include <layerproto/TransactionProto.h>
#include <utils/RefBase.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,42 +37,32 @@ public:
class FlingerDataMapper {
public:
virtual ~FlingerDataMapper() = default;
- virtual sp<IBinder> getLayerHandle(int32_t /* layerId */) const { return nullptr; }
- virtual int64_t getLayerId(const sp<IBinder>& /* layerHandle */) const { return -1; }
- virtual int64_t getLayerId(BBinder* /* layerHandle */) const { return -1; }
virtual sp<IBinder> getDisplayHandle(int32_t /* displayId */) const { return nullptr; }
virtual int32_t getDisplayId(const sp<IBinder>& /* displayHandle */) const { return -1; }
- virtual std::shared_ptr<BufferData> getGraphicData(uint64_t bufferId, uint32_t width,
- uint32_t height, int32_t pixelFormat,
- uint64_t usage) const {
- return std::make_shared<BufferDataStub>(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<FlingerDataMapper> provider)
: mMapper(std::move(provider)) {}
- proto::TransactionState toProto(const TransactionState&);
- proto::TransactionState toProto(const std::map<int32_t /* layerId */, TracingLayerState>&);
- proto::LayerCreationArgs toProto(const TracingLayerCreationArgs& args);
+ perfetto::protos::TransactionState toProto(const TransactionState&);
+ perfetto::protos::TransactionState toProto(
+ const std::map<uint32_t /* layerId */, TracingLayerState>&);
+ perfetto::protos::LayerCreationArgs toProto(const LayerCreationArgs& args);
+ perfetto::protos::LayerState toProto(const ResolvedComposerState&);
+ static perfetto::protos::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);
+ TransactionState fromProto(const perfetto::protos::TransactionState&);
+ void mergeFromProto(const perfetto::protos::LayerState&, TracingLayerState& outState);
+ void fromProto(const perfetto::protos::LayerCreationArgs&, LayerCreationArgs& outArgs);
std::unique_ptr<FlingerDataMapper> mMapper;
+ static frontend::DisplayInfo fromProto(const perfetto::protos::DisplayInfo&);
+ static void fromProto(const google::protobuf::RepeatedPtrField<perfetto::protos::DisplayInfo>&,
+ frontend::DisplayInfos& outDisplayInfos);
private:
- proto::LayerState toProto(const layer_state_t&);
- proto::DisplayState toProto(const DisplayState&);
- void fromProto(const proto::LayerState&, layer_state_t& out);
- DisplayState fromProto(const proto::DisplayState&);
-
+ perfetto::protos::DisplayState toProto(const DisplayState&);
+ void fromProto(const perfetto::protos::LayerState&, ResolvedComposerState& out);
+ DisplayState fromProto(const perfetto::protos::DisplayState&);
};
} // namespace android::surfaceflinger
diff --git a/services/surfaceflinger/Tracing/RingBuffer.h b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
index 7e38c55840..7d1d3fd7f2 100644
--- a/services/surfaceflinger/Tracing/RingBuffer.h
+++ b/services/surfaceflinger/Tracing/TransactionRingBuffer.h
@@ -24,6 +24,7 @@
#include <utils/Timers.h>
#include <utils/Trace.h>
#include <chrono>
+#include <fstream>
#include <queue>
namespace android {
@@ -31,7 +32,7 @@ namespace android {
class SurfaceFlinger;
template <typename FileProto, typename EntryProto>
-class RingBuffer {
+class TransactionRingBuffer {
public:
size_t size() const { return mSizeInBytes; }
size_t used() const { return mUsedInBytes; }
@@ -46,7 +47,7 @@ public:
mUsedInBytes = 0U;
}
- void writeToProto(FileProto& fileProto) {
+ void writeToProto(FileProto& fileProto) const {
fileProto.mutable_entry()->Reserve(static_cast<int>(mStorage.size()) +
fileProto.entry().size());
for (const std::string& entry : mStorage) {
@@ -55,7 +56,7 @@ public:
}
}
- status_t writeToFile(FileProto& fileProto, std::string filename) {
+ status_t appendToStream(FileProto& fileProto, std::ofstream& out) {
ATRACE_CALL();
writeToProto(fileProto);
std::string output;
@@ -64,12 +65,7 @@ public:
return UNKNOWN_ERROR;
}
- // -rw-r--r--
- const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
- if (!android::base::WriteStringToFile(output, filename, mode, getuid(), getgid(), true)) {
- ALOGE("Could not save the proto file %s", filename.c_str());
- return PERMISSION_DENIED;
- }
+ out << output;
return NO_ERROR;
}
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.cpp b/services/surfaceflinger/Tracing/TransactionTracing.cpp
index 6381758c7b..696f3489bf 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.cpp
+++ b/services/surfaceflinger/Tracing/TransactionTracing.cpp
@@ -16,93 +16,37 @@
#undef LOG_TAG
#define LOG_TAG "TransactionTracing"
-#define ATRACE_TAG ATRACE_TAG_GRAPHICS
#include <android-base/stringprintf.h>
#include <log/log.h>
#include <utils/SystemClock.h>
-#include <utils/Trace.h>
-#include "ClientCache.h"
+#include "Client.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "TransactionDataSource.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<IBinder>& layerHandle) const {
- if (layerHandle == nullptr) {
- return -1;
- }
-
- return reinterpret_cast<int64_t>(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<renderengine::ExternalTexture> 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<BBinder* /* layerHandle */, int32_t /* layerId */>& mLayerHandles;
-
-public:
- FlingerDataMapper(std::unordered_map<BBinder* /* handle */, int32_t /* id */>& layerHandles)
- : mLayerHandles(layerHandles) {}
-
- int64_t getLayerId(const sp<IBinder>& 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;
- }
-};
+ANDROID_SINGLETON_STATIC_INSTANCE(android::TransactionTraceWriter)
TransactionTracing::TransactionTracing()
- : mProtoParser(std::make_unique<FlingerDataMapper>(mLayerHandles)),
- mLockfreeProtoParser(std::make_unique<FlatDataMapper>()) {
+ : mProtoParser(std::make_unique<TransactionProtoParser::FlingerDataMapper>()) {
std::scoped_lock lock(mTraceLock);
- mBuffer.setSize(mBufferSizeInBytes);
+ mBuffer.setSize(CONTINUOUS_TRACING_BUFFER_SIZE);
+
mStartingTimestamp = systemTime();
+
{
std::scoped_lock lock(mMainThreadLock);
mThread = std::thread(&TransactionTracing::loop, this);
}
+
+ TransactionDataSource::Initialize(*this);
}
TransactionTracing::~TransactionTracing() {
+ TransactionDataSource::UnregisterTransactionTracing();
std::thread thread;
{
std::scoped_lock lock(mMainThreadLock);
@@ -113,140 +57,204 @@ TransactionTracing::~TransactionTracing() {
if (thread.joinable()) {
thread.join();
}
+}
+
+void TransactionTracing::onStart(TransactionTracing::Mode mode) {
+ // In "active" mode write the ring buffer (starting state + following sequence of transactions)
+ // to perfetto when tracing starts (only once).
+ if (mode != Mode::MODE_ACTIVE) {
+ return;
+ }
+
+ writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_ACTIVE);
- writeToFile();
+ ALOGD("Started active mode tracing (wrote initial transactions ring buffer to perfetto)");
}
-status_t TransactionTracing::writeToFile(std::string filename) {
- std::scoped_lock lock(mTraceLock);
- proto::TransactionTraceFile fileProto = createTraceFileProto();
- addStartingStateToProtoLocked(fileProto);
- return mBuffer.writeToFile(fileProto, filename);
+void TransactionTracing::onFlush(TransactionTracing::Mode mode) {
+ // In "continuous" mode write the ring buffer (starting state + following sequence of
+ // transactions) to perfetto when a "flush" event is received (bugreport is taken or tracing is
+ // stopped).
+ if (mode != Mode::MODE_CONTINUOUS) {
+ return;
+ }
+
+ writeRingBufferToPerfetto(TransactionTracing::Mode::MODE_CONTINUOUS);
+
+ ALOGD("Flushed continuous mode tracing (wrote transactions ring buffer to perfetto");
+}
+
+void TransactionTracing::writeRingBufferToPerfetto(TransactionTracing::Mode mode) {
+ // Write the ring buffer (starting state + following sequence of transactions) to perfetto
+ // tracing sessions with the specified mode.
+ const auto fileProto = writeToProto();
+
+ TransactionDataSource::Trace([&](TransactionDataSource::TraceContext context) {
+ // Write packets only to tracing sessions with specified mode
+ if (context.GetCustomTlsState()->mMode != mode) {
+ return;
+ }
+ for (const auto& entryProto : fileProto.entry()) {
+ const auto entryBytes = entryProto.SerializeAsString();
+
+ auto packet = context.NewTracePacket();
+ packet->set_timestamp(static_cast<uint64_t>(entryProto.elapsed_realtime_nanos()));
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+
+ auto* transactionsProto = packet->set_surfaceflinger_transactions();
+ transactionsProto->AppendRawProtoBytes(entryBytes.data(), entryBytes.size());
+ }
+ {
+ // TODO (b/162206162): remove empty packet when perfetto bug is fixed.
+ // It is currently needed in order not to lose the last trace entry.
+ context.NewTracePacket();
+ }
+ });
+}
+
+status_t TransactionTracing::writeToFile(const std::string& filename) {
+ auto fileProto = writeToProto();
+
+ std::string output;
+ if (!fileProto.SerializeToString(&output)) {
+ ALOGE("Could not serialize proto.");
+ return UNKNOWN_ERROR;
+ }
+
+ // -rw-r--r--
+ const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ if (!android::base::WriteStringToFile(output, filename, mode, getuid(), getgid(), true)) {
+ ALOGE("Could not save the proto file %s", filename.c_str());
+ return PERMISSION_DENIED;
+ }
+
+ return NO_ERROR;
+}
+
+perfetto::protos::TransactionTraceFile TransactionTracing::writeToProto() {
+ std::scoped_lock<std::mutex> lock(mTraceLock);
+ perfetto::protos::TransactionTraceFile fileProto = createTraceFileProto();
+ const auto startingStateProto = createStartingStateProtoLocked();
+ if (startingStateProto) {
+ *fileProto.add_entry() = std::move(*startingStateProto);
+ }
+ mBuffer.writeToProto(fileProto);
+ return fileProto;
}
void TransactionTracing::setBufferSize(size_t bufferSizeInBytes) {
std::scoped_lock lock(mTraceLock);
- mBufferSizeInBytes = bufferSizeInBytes;
- mBuffer.setSize(mBufferSizeInBytes);
+ mBuffer.setSize(bufferSizeInBytes);
}
-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);
+perfetto::protos::TransactionTraceFile TransactionTracing::createTraceFileProto() const {
+ perfetto::protos::TransactionTraceFile proto;
+ proto.set_magic_number(
+ uint64_t(perfetto::protos::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_H) << 32 |
+ perfetto::protos::TransactionTraceFile_MagicNumber_MAGIC_NUMBER_L);
+ auto timeOffsetNs = static_cast<uint64_t>(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));
+ perfetto::protos::TransactionState* state =
+ new perfetto::protos::TransactionState(mProtoParser.toProto(transaction));
mTransactionQueue.push(state);
}
-void TransactionTracing::addCommittedTransactions(std::vector<TransactionState>& 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 frontend::DisplayInfos& 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 (auto& [handle, _] : newUpdate.destroyedHandles) {
+ update.destroyedLayerHandles.push_back(handle);
+ }
+ mPendingUpdates.emplace_back(update);
tryPushToTracingThread();
+ mLastUpdatedVsyncId = vsyncId;
}
void TransactionTracing::loop() {
while (true) {
- std::vector<CommittedTransactions> committedTransactions;
- std::vector<int32_t> removedLayers;
+ std::vector<CommittedUpdates> committedUpdates;
+ std::vector<uint32_t> destroyedLayers;
{
std::unique_lock<std::mutex> 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>& committedTransactions,
- const std::vector<int32_t>& removedLayers) {
- ATRACE_CALL();
+void TransactionTracing::addEntry(const std::vector<CommittedUpdates>& committedUpdates,
+ const std::vector<uint32_t>& destroyedLayers) {
std::scoped_lock lock(mTraceLock);
std::vector<std::string> removedEntries;
- proto::TransactionTraceEntry entryProto;
+ perfetto::protos::TransactionTraceEntry entryProto;
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<BBinder*>(layer->layer_id())));
- if ((layer->what() & layer_state_t::eReparent) && layer->parent_id() != -1) {
- layer->set_parent_id(
- mProtoParser.mMapper->getLayerId(reinterpret_cast<BBinder*>(
- 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<BBinder*>(
- 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<BBinder*>(
- 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<int32_t>(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<int32_t>(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<int32_t>(removedLayers.size()));
- for (auto& removedLayer : removedLayers) {
- entryProto.mutable_removed_layers()->Add(removedLayer);
+
+ entryProto.mutable_destroyed_layers()->Reserve(
+ static_cast<int32_t>(destroyedLayers.size()));
+ for (auto& destroyedLayer : destroyedLayers) {
+ entryProto.mutable_destroyed_layers()->Add(destroyedLayer);
}
- mCreatedLayers.clear();
entryProto.mutable_transactions()->Reserve(
- static_cast<int32_t>(entry.transactionIds.size()));
- for (const uint64_t& id : entry.transactionIds) {
+ static_cast<int32_t>(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,23 +264,55 @@ void TransactionTracing::addEntry(const std::vector<CommittedTransactions>& comm
}
}
+ entryProto.mutable_destroyed_layer_handles()->Reserve(
+ static_cast<int32_t>(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<int32_t>(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();
+
+ TransactionDataSource::Trace([&](TransactionDataSource::TraceContext context) {
+ // In "active" mode write each committed transaction to perfetto.
+ // Note: the starting state is written (once) when the perfetto "start" event is
+ // received.
+ if (context.GetCustomTlsState()->mMode != Mode::MODE_ACTIVE) {
+ return;
+ }
+ {
+ auto packet = context.NewTracePacket();
+ packet->set_timestamp(static_cast<uint64_t>(entryProto.elapsed_realtime_nanos()));
+ packet->set_timestamp_clock_id(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC);
+ auto* transactions = packet->set_surfaceflinger_transactions();
+ transactions->AppendRawProtoBytes(serializedProto.data(), serializedProto.size());
+ }
+ {
+ // TODO (b/162206162): remove empty packet when perfetto bug is fixed.
+ // It is currently needed in order not to lose the last trace entry.
+ context.NewTracePacket();
+ }
+ });
+
std::vector<std::string> entries = mBuffer.emplace(std::move(serializedProto));
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<int32_t>(mRemovedLayerHandles.size()));
- for (auto& handle : mRemovedLayerHandles) {
- entryProto.mutable_removed_layer_handles()->Add(handle);
- }
- mRemovedLayerHandles.clear();
+ entryProto.Clear();
}
- proto::TransactionTraceEntry removedEntryProto;
+ perfetto::protos::TransactionTraceEntry removedEntryProto;
for (const std::string& removedEntry : removedEntries) {
removedEntryProto.ParseFromString(removedEntry);
updateStartingStateLocked(removedEntryProto);
@@ -281,71 +321,46 @@ void TransactionTracing::addEntry(const std::vector<CommittedTransactions>& comm
mTransactionsAddedToBufferCv.notify_one();
}
-void TransactionTracing::flush(int64_t vsyncId) {
- while (!mPendingTransactions.empty() || !mPendingRemovedLayers.empty()) {
- tryPushToTracingThread();
+void TransactionTracing::flush() {
+ {
+ std::scoped_lock lock(mMainThreadLock);
+ // Collect any pending transactions and wait for transactions to be added to
+ 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();
}
std::unique_lock<std::mutex> lock(mTraceLock);
base::ScopedLockAssertion assumeLocked(mTraceLock);
- mTransactionsAddedToBufferCv.wait(lock, [&]() REQUIRES(mTraceLock) {
- proto::TransactionTraceEntry entry;
- if (mBuffer.used() > 0) {
- entry.ParseFromString(mBuffer.back());
- }
- return mBuffer.used() > 0 && entry.vsync_id() >= 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));
+ mTransactionsAddedToBufferCv.wait_for(lock, std::chrono::milliseconds(100),
+ [&]() REQUIRES(mTraceLock) {
+ perfetto::protos::TransactionTraceEntry entry;
+ if (mBuffer.used() > 0) {
+ entry.ParseFromString(mBuffer.back());
+ }
+ return mBuffer.used() > 0 &&
+ entry.vsync_id() >= mLastUpdatedVsyncId;
+ });
}
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 {
@@ -354,61 +369,78 @@ void TransactionTracing::tryPushToTracingThread() {
}
void TransactionTracing::updateStartingStateLocked(
- const proto::TransactionTraceEntry& removedEntry) {
+ const perfetto::protos::TransactionTraceEntry& removedEntry) {
mStartingTimestamp = removedEntry.elapsed_realtime_nanos();
// Keep track of layer starting state so we can reconstruct the layer state as we purge
// transactions from the buffer.
- for (const proto::LayerCreationArgs& addedLayer : removedEntry.added_layers()) {
+ for (const perfetto::protos::LayerCreationArgs& addedLayer : removedEntry.added_layers()) {
TracingLayerState& startingState = mStartingStates[addedLayer.layer_id()];
startingState.layerId = addedLayer.layer_id();
mProtoParser.fromProto(addedLayer, startingState.args);
}
// 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());
+ for (const perfetto::protos::TransactionState& transaction : removedEntry.transactions()) {
+ for (const perfetto::protos::LayerState& layerState : transaction.layer_changes()) {
+ 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);
}
}
-void TransactionTracing::addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) {
- if (mStartingStates.size() == 0) {
- return;
+std::optional<perfetto::protos::TransactionTraceEntry>
+TransactionTracing::createStartingStateProtoLocked() {
+ if (mStartingStates.empty()) {
+ return std::nullopt;
}
- proto::TransactionTraceEntry* entryProto = proto.add_entry();
- entryProto->set_elapsed_realtime_nanos(mStartingTimestamp);
- entryProto->set_vsync_id(0);
+ perfetto::protos::TransactionTraceEntry entryProto;
+ entryProto.set_elapsed_realtime_nanos(mStartingTimestamp);
+ entryProto.set_vsync_id(0);
- entryProto->mutable_added_layers()->Reserve(static_cast<int32_t>(mStartingStates.size()));
+ entryProto.mutable_added_layers()->Reserve(static_cast<int32_t>(mStartingStates.size()));
for (auto& [layerId, state] : mStartingStates) {
- entryProto->mutable_added_layers()->Add(mProtoParser.toProto(state.args));
+ entryProto.mutable_added_layers()->Add(mProtoParser.toProto(state.args));
}
- proto::TransactionState transactionProto = mProtoParser.toProto(mStartingStates);
+ perfetto::protos::TransactionState transactionProto = mProtoParser.toProto(mStartingStates);
transactionProto.set_vsync_id(0);
transactionProto.set_post_time(mStartingTimestamp);
- entryProto->mutable_transactions()->Add(std::move(transactionProto));
-}
+ entryProto.mutable_transactions()->Add(std::move(transactionProto));
-proto::TransactionTraceFile TransactionTracing::writeToProto() {
- std::scoped_lock<std::mutex> lock(mTraceLock);
- proto::TransactionTraceFile proto = createTraceFileProto();
- addStartingStateToProtoLocked(proto);
- mBuffer.writeToProto(proto);
- return proto;
+ entryProto.mutable_destroyed_layer_handles()->Reserve(
+ static_cast<int32_t>(mRemovedLayerHandlesAtStart.size()));
+ for (const uint32_t destroyedLayerHandleId : mRemovedLayerHandlesAtStart) {
+ entryProto.mutable_destroyed_layer_handles()->Add(destroyedLayerHandleId);
+ }
+
+ entryProto.mutable_displays()->Reserve(static_cast<int32_t>(mStartingDisplayInfos.size()));
+ for (auto& [layerStack, displayInfo] : mStartingDisplayInfos) {
+ entryProto.mutable_displays()->Add(mProtoParser.toProto(displayInfo, layerStack.id));
+ }
+ entryProto.set_displays_changed(true);
+
+ return entryProto;
}
} // namespace android
diff --git a/services/surfaceflinger/Tracing/TransactionTracing.h b/services/surfaceflinger/Tracing/TransactionTracing.h
index 4c291f960f..cb96e05680 100644
--- a/services/surfaceflinger/Tracing/TransactionTracing.h
+++ b/services/surfaceflinger/Tracing/TransactionTracing.h
@@ -19,15 +19,20 @@
#include <android-base/thread_annotations.h>
#include <layerproto/TransactionProto.h>
#include <utils/Errors.h>
+#include <utils/Singleton.h>
#include <utils/Timers.h>
-#include <memory>
#include <mutex>
+#include <optional>
+#include <set>
#include <thread>
-#include "RingBuffer.h"
+#include "FrontEnd/DisplayInfo.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/Update.h"
#include "LocklessStack.h"
#include "TransactionProtoParser.h"
+#include "TransactionRingBuffer.h"
using namespace android::surfaceflinger;
@@ -37,7 +42,7 @@ class SurfaceFlinger;
class TransactionTracingTest;
/*
- * Records all committed transactions into a ring bufffer.
+ * Records all committed transactions into a ring buffer.
*
* Transactions come in via the binder thread. They are serialized to proto
* and stored in a map using the transaction id as key. Main thread will
@@ -45,51 +50,134 @@ class TransactionTracingTest;
* the tracing thread. The tracing thread will then wake up and add the
* committed transactions to the ring buffer.
*
- * When generating SF dump state, we will flush the buffer to a file which
- * will then be included in the bugreport.
+ * The traced data can then be collected via:
+ * - Perfetto (preferred).
+ * - File system, after triggering the disk write through SF backdoor. This is legacy and is going
+ * to be phased out.
+ *
+ * The Perfetto custom data source TransactionDataSource is registered with perfetto and is used
+ * to listen to perfetto events (setup, start, stop, flush) and to write trace packets to perfetto.
+ *
+ * The user can configure/start/stop tracing via /system/bin/perfetto.
+ *
+ * Tracing can operate in the following modes.
+ *
+ * ACTIVE mode:
+ * The transactions ring buffer (starting state + following committed transactions) is written
+ * (only once) to perfetto when the 'start' event is received.
+ * Transactions are then written to perfetto each time they are committed.
+ * On the receiver side, the data source is to be configured to periodically
+ * flush data to disk providing virtually infinite storage.
+ *
+ * CONTINUOUS mode:
+ * Listens to the perfetto 'flush' event (e.g. when a bugreport is taken).
+ * When a 'flush' event is received, the ring buffer of transactions (starting state + following
+ * committed transactions) is written to perfetto. On the receiver side, the data source is to be
+ * configured with a dedicated buffer large enough to store all the flushed data.
+ *
+ *
+ * E.g. start active mode tracing:
+ *
+ adb shell perfetto \
+ -c - --txt \
+ -o /data/misc/perfetto-traces/trace \
+ <<EOF
+ unique_session_name: "surfaceflinger_transactions_active"
+ buffers: {
+ size_kb: 1024
+ fill_policy: RING_BUFFER
+ }
+ data_sources: {
+ config {
+ name: "android.surfaceflinger.transactions"
+ surfaceflinger_transactions_config: {
+ mode: MODE_ACTIVE
+ }
+ }
+ }
+ write_into_file: true
+ file_write_period_ms: 100
+ EOF
+ *
+ *
+ * E.g. start continuous mode tracing:
+ *
+ adb shell perfetto \
+ -c - --txt \
+ -o /data/misc/perfetto-traces/trace \
+ <<EOF
+ unique_session_name: "surfaceflinger_transactions_continuous"
+ buffers: {
+ size_kb: 1024
+ fill_policy: RING_BUFFER
+ }
+ data_sources: {
+ config {
+ name: "android.surfaceflinger.transactions"
+ surfaceflinger_transactions_config: {
+ mode: MODE_CONTINUOUS
+ }
+ }
+ }
+ EOF
*
*/
class TransactionTracing {
public:
+ using Mode = perfetto::protos::pbzero::SurfaceFlingerTransactionsConfig::Mode;
+
TransactionTracing();
~TransactionTracing();
+ // Start event from perfetto data source
+ void onStart(Mode mode);
+ // Flush event from perfetto data source
+ void onFlush(Mode mode);
+
void addQueuedTransaction(const TransactionState&);
- void addCommittedTransactions(std::vector<TransactionState>& transactions, int64_t vsyncId);
- status_t writeToFile(std::string filename = FILE_NAME);
+ void addCommittedTransactions(int64_t vsyncId, nsecs_t commitTime, frontend::Update& update,
+ const frontend::DisplayInfos&, bool displayInfoChanged);
+ status_t writeToFile(const std::string& filename = FILE_PATH);
+ // Return buffer contents as trace file proto
+ perfetto::protos::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
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;
+ // Wait until all the committed transactions for the specified vsync id are added to the buffer.
+ void flush() EXCLUDES(mMainThreadLock);
+
static constexpr auto CONTINUOUS_TRACING_BUFFER_SIZE = 512 * 1024;
- static constexpr auto ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
+ static constexpr auto LEGACY_ACTIVE_TRACING_BUFFER_SIZE = 100 * 1024 * 1024;
+ // version 1 - switching to support new frontend
+ static constexpr auto TRACING_VERSION = 1;
private:
+ friend class TransactionTraceWriter;
friend class TransactionTracingTest;
+ friend class SurfaceFlinger;
- static constexpr auto FILE_NAME = "/data/misc/wmtrace/transactions_trace.winscope";
+ static constexpr auto DIR_NAME = "/data/misc/wmtrace/";
+ static constexpr auto FILE_NAME = "transactions_trace.winscope";
+ static constexpr auto FILE_PATH = "/data/misc/wmtrace/transactions_trace.winscope";
+ static std::string getFilePath(const std::string& prefix) {
+ return DIR_NAME + prefix + FILE_NAME;
+ }
mutable std::mutex mTraceLock;
- RingBuffer<proto::TransactionTraceFile, proto::TransactionTraceEntry> mBuffer
- GUARDED_BY(mTraceLock);
- size_t mBufferSizeInBytes GUARDED_BY(mTraceLock) = CONTINUOUS_TRACING_BUFFER_SIZE;
- std::unordered_map<uint64_t, proto::TransactionState> mQueuedTransactions
+ TransactionRingBuffer<perfetto::protos::TransactionTraceFile,
+ perfetto::protos::TransactionTraceEntry>
+ mBuffer GUARDED_BY(mTraceLock);
+ std::unordered_map<uint64_t, perfetto::protos::TransactionState> mQueuedTransactions
GUARDED_BY(mTraceLock);
- LocklessStack<proto::TransactionState> mTransactionQueue;
+ LocklessStack<perfetto::protos::TransactionState> mTransactionQueue;
nsecs_t mStartingTimestamp GUARDED_BY(mTraceLock);
- std::vector<proto::LayerCreationArgs> mCreatedLayers GUARDED_BY(mTraceLock);
- std::unordered_map<BBinder* /* layerHandle */, int32_t /* layerId */> mLayerHandles
+ std::unordered_map<int, perfetto::protos::LayerCreationArgs> mCreatedLayers
GUARDED_BY(mTraceLock);
- std::vector<int32_t /* layerId */> mRemovedLayerHandles GUARDED_BY(mTraceLock);
- std::map<int32_t /* layerId */, TracingLayerState> 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::map<uint32_t /* layerId */, TracingLayerState> mStartingStates GUARDED_BY(mTraceLock);
+ frontend::DisplayInfos mStartingDisplayInfos GUARDED_BY(mTraceLock);
+
+ std::set<uint32_t /* layerId */> 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,31 +186,52 @@ private:
bool mDone GUARDED_BY(mMainThreadLock) = false;
std::condition_variable mTransactionsAvailableCv;
std::condition_variable mTransactionsAddedToBufferCv;
- struct CommittedTransactions {
+ struct CommittedUpdates {
std::vector<uint64_t> transactionIds;
+ std::vector<LayerCreationArgs> createdLayers;
+ std::vector<uint32_t> destroyedLayerHandles;
+ bool displayInfoChanged;
+ frontend::DisplayInfos displayInfos;
int64_t vsyncId;
int64_t timestamp;
};
- std::vector<CommittedTransactions> mCommittedTransactions GUARDED_BY(mMainThreadLock);
- std::vector<CommittedTransactions> mPendingTransactions; // only accessed by main thread
+ std::vector<CommittedUpdates> mUpdates GUARDED_BY(mMainThreadLock);
+ std::vector<CommittedUpdates> mPendingUpdates; // only accessed by main thread
- std::vector<int32_t /* layerId */> mRemovedLayers GUARDED_BY(mMainThreadLock);
- std::vector<int32_t /* layerId */> mPendingRemovedLayers; // only accessed by main thread
+ std::vector<uint32_t /* layerId */> mDestroyedLayers GUARDED_BY(mMainThreadLock);
+ std::vector<uint32_t /* layerId */> mPendingDestroyedLayers; // only accessed by main thread
+ int64_t mLastUpdatedVsyncId = -1;
- proto::TransactionTraceFile createTraceFileProto() const;
+ void writeRingBufferToPerfetto(TransactionTracing::Mode mode);
+ perfetto::protos::TransactionTraceFile createTraceFileProto() const;
void loop();
- void addEntry(const std::vector<CommittedTransactions>& committedTransactions,
- const std::vector<int32_t>& removedLayers) EXCLUDES(mTraceLock);
+ void addEntry(const std::vector<CommittedUpdates>& committedTransactions,
+ const std::vector<uint32_t>& removedLayers) EXCLUDES(mTraceLock);
int32_t getLayerIdLocked(const sp<IBinder>& layerHandle) REQUIRES(mTraceLock);
void tryPushToTracingThread() EXCLUDES(mMainThreadLock);
- void addStartingStateToProtoLocked(proto::TransactionTraceFile& proto) REQUIRES(mTraceLock);
- void updateStartingStateLocked(const proto::TransactionTraceEntry& entry) REQUIRES(mTraceLock);
+ std::optional<perfetto::protos::TransactionTraceEntry> createStartingStateProtoLocked()
+ REQUIRES(mTraceLock);
+ void updateStartingStateLocked(const perfetto::protos::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);
- // Return buffer contents as trace file proto
- proto::TransactionTraceFile writeToProto() EXCLUDES(mMainThreadLock);
+class TransactionTraceWriter : public Singleton<TransactionTraceWriter> {
+ friend class Singleton<TransactionTracing>;
+ std::function<void(const std::string& prefix, bool overwrite)> mWriterFunction =
+ [](const std::string&, bool) {};
+
+public:
+ void setWriterFunction(
+ std::function<void(const std::string& filename, bool overwrite)> function) {
+ mWriterFunction = std::move(function);
+ }
+ void invoke(const std::string& prefix, bool overwrite) {
+ mWriterFunction(TransactionTracing::getFilePath(prefix), overwrite);
+ }
+ /* pass in a complete file path for testing */
+ void invokeForTest(const std::string& filename, bool overwrite) {
+ mWriterFunction(filename, overwrite);
+ }
};
} // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/Android.bp b/services/surfaceflinger/Tracing/tools/Android.bp
index e8fe734a8f..2ff09c3f4c 100644
--- a/services/surfaceflinger/Tracing/tools/Android.bp
+++ b/services/surfaceflinger/Tracing/tools/Android.bp
@@ -25,13 +25,12 @@ cc_binary {
name: "layertracegenerator",
defaults: [
"libsurfaceflinger_mocks_defaults",
+ "librenderengine_deps",
"surfaceflinger_defaults",
- "skia_renderengine_deps",
],
srcs: [
":libsurfaceflinger_sources",
":libsurfaceflinger_mock_sources",
- ":layertracegenerator_sources",
"main.cpp",
],
static_libs: [
@@ -41,15 +40,3 @@ cc_binary {
"libsurfaceflinger_mocks_headers",
],
}
-
-filegroup {
- name: "layertracegenerator_sources",
- srcs: [
- "LayerTraceGenerator.cpp",
- ],
-}
-
-cc_library_headers {
- name: "layertracegenerator_headers",
- export_include_dirs: ["."],
-}
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
index cf44effe50..c2d1954ee5 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.cpp
@@ -16,268 +16,158 @@
#undef LOG_TAG
#define LOG_TAG "LayerTraceGenerator"
+//#define LOG_NDEBUG 0
-#include <TestableSurfaceFlinger.h>
#include <Tracing/TransactionProtoParser.h>
-#include <binder/IPCThreadState.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
#include <gui/LayerState.h>
#include <log/log.h>
-#include <mock/MockEventThread.h>
#include <renderengine/ExternalTexture.h>
-#include <renderengine/mock/FakeExternalTexture.h>
-#include <renderengine/mock/RenderEngine.h>
#include <utils/String16.h>
+#include <filesystem>
+#include <fstream>
+#include <ios>
#include <string>
+#include <vector>
+#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 {
+using namespace ftl::flag_operators;
-class Factory final : public surfaceflinger::Factory {
-public:
- ~Factory() = default;
-
- std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
-
- std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
- Fps /*currentRefreshRate*/) override {
- return std::make_unique<scheduler::FakePhaseOffsets>();
- }
-
- sp<SurfaceInterceptor> createSurfaceInterceptor() override {
- return new android::impl::SurfaceInterceptor();
- }
-
- sp<StartPropertySetThread> createStartPropertySetThread(
- bool /* timestampPropertyValue */) override {
- return nullptr;
- }
-
- sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& /* creationArgs */) override {
- return nullptr;
- }
-
- sp<GraphicBuffer> createGraphicBuffer(uint32_t /* width */, uint32_t /* height */,
- PixelFormat /* format */, uint32_t /* layerCount */,
- uint64_t /* usage */,
- std::string /* requestorName */) override {
- return nullptr;
- }
-
- void createBufferQueue(sp<IGraphicBufferProducer>* /* outProducer */,
- sp<IGraphicBufferConsumer>* /* outConsumer */,
- bool /* consumerIsSurfaceFlinger */) override {}
-
- sp<IGraphicBufferProducer> createMonitoredProducer(
- const sp<IGraphicBufferProducer>& /* producer */,
- const sp<SurfaceFlinger>& /* flinger */, const wp<Layer>& /* layer */) override {
- return nullptr;
- }
-
- sp<BufferLayerConsumer> createBufferLayerConsumer(
- const sp<IGraphicBufferConsumer>& /* consumer */,
- renderengine::RenderEngine& /* renderEngine */, uint32_t /* textureName */,
- Layer* /* layer */) override {
- return nullptr;
- }
-
- std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
- const sp<IGraphicBufferProducer>& /* producer */) override {
- return nullptr;
- }
-
- std::unique_ptr<compositionengine::CompositionEngine> createCompositionEngine() override {
- return compositionengine::impl::createCompositionEngine();
- }
-
- sp<ContainerLayer> createContainerLayer(const LayerCreationArgs& args) {
- return sp<ContainerLayer>::make(args);
- }
-
- sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs& args) {
- return new BufferStateLayer(args);
- }
-
- sp<EffectLayer> createEffectLayer(const LayerCreationArgs& args) {
- return new EffectLayer(args);
- }
-
- sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs&) override {
- return nullptr;
- }
-
- std::unique_ptr<FrameTracer> createFrameTracer() override {
- return std::make_unique<testing::NiceMock<mock::FrameTracer>>();
- }
-
- std::unique_ptr<frametimeline::FrameTimeline> createFrameTimeline(
- std::shared_ptr<TimeStats> timeStats, pid_t surfaceFlingerPid = 0) override {
- return std::make_unique<testing::NiceMock<mock::FrameTimeline>>(timeStats,
- surfaceFlingerPid);
- }
-};
-
-class MockSurfaceFlinger : public SurfaceFlinger {
-public:
- MockSurfaceFlinger(Factory& factory)
- : SurfaceFlinger(factory, SurfaceFlinger::SkipInitialization) {}
- std::shared_ptr<renderengine::ExternalTexture> getExternalTextureFromBufferData(
- const BufferData& bufferData, const char* /* layerName */) const override {
- return std::make_shared<renderengine::mock::FakeExternalTexture>(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<int32_t /*layerId*/, sp<IBinder> /* handle */> mLayerHandles;
- sp<IBinder> 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;
- }
-};
-
-bool LayerTraceGenerator::generate(const proto::TransactionTraceFile& traceFile,
- const char* outputLayersTracePath) {
+bool LayerTraceGenerator::generate(const perfetto::protos::TransactionTraceFile& traceFile,
+ std::uint32_t traceFlags, LayerTracing& layerTracing,
+ bool onlyLastEntry) {
if (traceFile.entry_size() == 0) {
+ ALOGD("Trace file is empty");
return false;
}
- Factory mFactory;
- sp<MockSurfaceFlinger> flinger = new MockSurfaceFlinger(mFactory);
- TestableSurfaceFlinger mFlinger(flinger);
- mFlinger.setupRenderEngine(
- std::make_unique<testing::NiceMock<renderengine::mock::RenderEngine>>());
- mock::VsyncController* mVsyncController = new testing::NiceMock<mock::VsyncController>();
- mock::VSyncTracker* mVSyncTracker = new testing::NiceMock<mock::VSyncTracker>();
- mock::EventThread* mEventThread = new testing::NiceMock<mock::EventThread>();
- mock::EventThread* mSFEventThread = new testing::NiceMock<mock::EventThread>();
- mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
- std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker),
- std::unique_ptr<EventThread>(mEventThread),
- std::unique_ptr<EventThread>(mSFEventThread),
- TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
- TestableSurfaceFlinger::kOneDisplayMode, true /* useNiceMock */);
+ TransactionProtoParser parser(std::make_unique<TransactionProtoParser::FlingerDataMapper>());
- Hwc2::mock::Composer* mComposer = new testing::NiceMock<Hwc2::mock::Composer>();
- mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
- mFlinger.mutableMaxRenderTargetSize() = 16384;
+ // frontend
+ frontend::LayerLifecycleManager lifecycleManager;
+ frontend::LayerHierarchyBuilder hierarchyBuilder{{}};
+ frontend::LayerSnapshotBuilder snapshotBuilder;
+ ui::DisplayMap<ui::LayerStack, frontend::DisplayInfo> displayInfos;
- flinger->setLayerTracingFlags(LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS);
- flinger->startLayerTracing(traceFile.entry(0).elapsed_realtime_nanos());
- std::unique_ptr<TraceGenFlingerDataMapper> mapper =
- std::make_unique<TraceGenFlingerDataMapper>();
- TraceGenFlingerDataMapper* dataMapper = mapper.get();
- TransactionProtoParser parser(std::move(mapper));
+ 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);
- nsecs_t frameTime;
- int64_t vsyncId;
ALOGD("Generating %d transactions...", traceFile.entry_size());
for (int i = 0; i < traceFile.entry_size(); i++) {
- proto::TransactionTraceEntry entry = traceFile.entry(i);
+ // parse proto
+ perfetto::protos::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<std::unique_ptr<frontend::RequestedLayerState>> 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<IBinder> outHandle;
- int32_t outLayerId;
- LayerCreationArgs args(mFlinger.flinger(), nullptr /* client */, tracingArgs.name,
- tracingArgs.flags, LayerMetadata());
- args.sequence = std::make_optional<int32_t>(tracingArgs.layerId);
-
- if (tracingArgs.mirrorFromId == -1) {
- sp<IBinder> 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<IBinder> 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<frontend::RequestedLayerState>(args));
}
+ std::vector<TransactionState> 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<BBinder>::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<std::pair<uint32_t, std::string>> 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);
+ }
- flinger->stopLayerTracing(outputLayersTracePath);
- ALOGD("End of generating trace file. File written to %s", outputLayersTracePath);
- dataMapper->mLayerHandles.clear();
+ // 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());
+ }
+
+ 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();
+
+ auto layersProto =
+ LayerProtoFromSnapshotGenerator(snapshotBuilder, displayInfos, {}, traceFlags)
+ .generate(hierarchyBuilder.getHierarchy());
+ auto displayProtos = LayerProtoHelper::writeDisplayInfoToProto(displayInfos);
+ if (!onlyLastEntry || (i == traceFile.entry_size() - 1)) {
+ perfetto::protos::LayersSnapshotProto snapshotProto{};
+ snapshotProto.set_vsync_id(entry.vsync_id());
+ snapshotProto.set_elapsed_realtime_nanos(entry.elapsed_realtime_nanos());
+ snapshotProto.set_where(visibleRegionsDirty ? "visibleRegionsDirty" : "bufferLatched");
+ *snapshotProto.mutable_layers() = std::move(layersProto);
+ if ((traceFlags & LayerTracing::TRACE_COMPOSITION) == 0) {
+ snapshotProto.set_excludes_composition_state(true);
+ }
+ *snapshotProto.mutable_displays() = std::move(displayProtos);
+
+ layerTracing.addProtoSnapshotToOstream(std::move(snapshotProto),
+ LayerTracing::Mode::MODE_GENERATED);
+ }
+ }
+ ALOGD("End of generating trace file");
return true;
}
-} // namespace android \ No newline at end of file
+} // namespace android
diff --git a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
index ee1ea6ce8b..e4d02ca5ce 100644
--- a/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
+++ b/services/surfaceflinger/Tracing/tools/LayerTraceGenerator.h
@@ -18,9 +18,18 @@
#include <Tracing/TransactionTracing.h>
+#include <functional>
+#include <optional>
+#include <ostream>
+#include <string>
+
namespace android {
+
+class LayerTracing;
+
class LayerTraceGenerator {
public:
- bool generate(const proto::TransactionTraceFile&, const char* outputLayersTracePath);
+ bool generate(const perfetto::protos::TransactionTraceFile&, std::uint32_t traceFlags,
+ LayerTracing& layerTracing, bool onlyLastEntry = false);
};
-} // 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 f3cf42d7ab..18022b1697 100644
--- a/services/surfaceflinger/Tracing/tools/main.cpp
+++ b/services/surfaceflinger/Tracing/tools/main.cpp
@@ -21,14 +21,15 @@
#include <iostream>
#include <string>
+#include <Tracing/LayerTracing.h>
#include "LayerTraceGenerator.h"
using namespace android;
int main(int argc, char** argv) {
- if (argc > 3) {
+ if (argc > 4) {
std::cout << "Usage: " << argv[0]
- << " [transaction-trace-path] [output-layers-trace-path]\n";
+ << " [transaction-trace-path] [output-layers-trace-path] [--last-entry-only]\n";
return -1;
}
@@ -41,20 +42,39 @@ int main(int argc, char** argv) {
return -1;
}
- proto::TransactionTraceFile transactionTraceFile;
+ perfetto::protos::TransactionTraceFile transactionTraceFile;
if (!transactionTraceFile.ParseFromIstream(&input)) {
std::cout << "Error: Failed to parse " << transactionTracePath;
return -1;
}
- const char* outputLayersTracePath =
+ const auto* outputLayersTracePath =
(argc == 3) ? argv[2] : "/data/misc/wmtrace/layers_trace.winscope";
- ;
+ auto outStream = std::ofstream{outputLayersTracePath, std::ios::binary | std::ios::out};
+
+ auto layerTracing = LayerTracing{outStream};
+
+ const bool generateLastEntryOnly =
+ argc >= 4 && std::string_view(argv[3]) == "--last-entry-only";
+
+ auto traceFlags = LayerTracing::Flag::TRACE_INPUT | LayerTracing::Flag::TRACE_BUFFERS;
+
ALOGD("Generating %s...", outputLayersTracePath);
std::cout << "Generating " << outputLayersTracePath << "\n";
- if (!LayerTraceGenerator().generate(transactionTraceFile, outputLayersTracePath)) {
- std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath;
+
+ if (!LayerTraceGenerator().generate(transactionTraceFile, traceFlags, layerTracing,
+ generateLastEntryOnly)) {
+ std::cout << "Error: Failed to generate layers trace " << outputLayersTracePath << "\n";
return -1;
}
+
+ // Set output file permissions (-rw-r--r--)
+ outStream.close();
+ const mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
+ if (chmod(outputLayersTracePath, mode) != 0) {
+ std::cout << "Error: Failed to set permissions of " << outputLayersTracePath << "\n";
+ return -1;
+ }
+
return 0;
-} \ No newline at end of file
+}
diff --git a/services/surfaceflinger/Tracing/tools/run.sh b/services/surfaceflinger/Tracing/tools/run.sh
index baa93f1a1c..307a4d8338 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 d2c2e29a2f..6a155c17df 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<CallbackHandle>& handle) {
- return addCallbackHandle(handle, std::vector<JankData>());
-}
-
status_t TransactionCallbackInvoker::findOrCreateTransactionStats(
const sp<IBinder>& listener, const std::vector<CallbackId>& callbackIds,
TransactionStats** outTransactionStats) {
@@ -137,7 +132,6 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>&
sp<Fence> 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<CallbackHandle>&
snprintf(fenceName, 32, "%.28s", handle->name.c_str());
sp<Fence> 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,13 +151,14 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>&
// 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,
+ FrameEventHistoryStats eventStats(handle->frameNumber, handle->previousFrameNumber,
handle->gpuCompositionDoneFence->getSnapshot().fence,
handle->compositorTiming, handle->refreshStartTime,
handle->dequeueReadyTime);
@@ -178,8 +172,8 @@ status_t TransactionCallbackInvoker::addCallbackHandle(const sp<CallbackHandle>&
return NO_ERROR;
}
-void TransactionCallbackInvoker::addPresentFence(const sp<Fence>& presentFence) {
- mPresentFence = presentFence;
+void TransactionCallbackInvoker::addPresentFence(sp<Fence> presentFence) {
+ mPresentFence = std::move(presentFence);
}
void TransactionCallbackInvoker::sendCallbacks(bool onCommitOnly) {
diff --git a/services/surfaceflinger/TransactionCallbackInvoker.h b/services/surfaceflinger/TransactionCallbackInvoker.h
index 81d79f0777..245398f2f4 100644
--- a/services/surfaceflinger/TransactionCallbackInvoker.h
+++ b/services/surfaceflinger/TransactionCallbackInvoker.h
@@ -19,6 +19,7 @@
#include <condition_variable>
#include <deque>
#include <mutex>
+#include <optional>
#include <queue>
#include <thread>
#include <unordered_map>
@@ -26,10 +27,10 @@
#include <android-base/thread_annotations.h>
#include <binder/IBinder.h>
-#include <compositionengine/FenceResult.h>
#include <ftl/future.h>
#include <gui/ITransactionCompletedListener.h>
#include <ui/Fence.h>
+#include <ui/FenceResult.h>
namespace android {
@@ -48,13 +49,14 @@ public:
std::vector<ftl::SharedFuture<FenceResult>> previousReleaseFences;
std::variant<nsecs_t, sp<Fence>> acquireTimeOrFence = -1;
nsecs_t latchTime = -1;
- uint32_t transformHint = 0;
+ std::optional<uint32_t> transformHint = std::nullopt;
uint32_t currentMaxAcquiredBufferCount = 0;
std::shared_ptr<FenceTime> gpuCompositionDoneFence{FenceTime::NO_FENCE};
CompositorTiming compositorTiming;
nsecs_t refreshStartTime = 0;
nsecs_t dequeueReadyTime = 0;
uint64_t frameNumber = 0;
+ uint64_t previousFrameNumber = 0;
ReleaseCallbackId previousReleaseCallbackId = ReleaseCallbackId::INVALID_ID;
};
@@ -65,12 +67,9 @@ public:
status_t addOnCommitCallbackHandles(const std::deque<sp<CallbackHandle>>& handles,
std::deque<sp<CallbackHandle>>& outRemainingHandles);
- // Adds the Transaction CallbackHandle from a layer that does not need to be relatched and
- // presented this frame.
- status_t registerUnpresentedCallbackHandle(const sp<CallbackHandle>& handle);
void addEmptyTransaction(const ListenerCallbacks& listenerCallbacks);
- void addPresentFence(const sp<Fence>& presentFence);
+ void addPresentFence(sp<Fence>);
void sendCallbacks(bool onCommitOnly);
void clearCompletedTransactions() {
diff --git a/services/surfaceflinger/TransactionState.h b/services/surfaceflinger/TransactionState.h
index 900d566942..31cd2d7eb6 100644
--- a/services/surfaceflinger/TransactionState.h
+++ b/services/surfaceflinger/TransactionState.h
@@ -20,60 +20,96 @@
#include <memory>
#include <mutex>
#include <vector>
+#include "FrontEnd/LayerCreationArgs.h"
+#include "renderengine/ExternalTexture.h"
#include <gui/LayerState.h>
#include <system/window.h>
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<renderengine::ExternalTexture> 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<ComposerState>& composerStates,
+ std::vector<ResolvedComposerState>& composerStates,
const Vector<DisplayState>& displayStates, uint32_t transactionFlags,
const sp<IBinder>& applyToken, const InputWindowCommands& inputWindowCommands,
int64_t desiredPresentTime, bool isAutoTimestamp,
- const client_cache_t& uncacheBuffer, int64_t postTime, uint32_t permissions,
+ std::vector<uint64_t> uncacheBufferIds, int64_t postTime,
bool hasListenerCallbacks, std::vector<ListenerCallbacks> listenerCallbacks,
- int originPid, int originUid, uint64_t transactionId)
+ int originPid, int originUid, uint64_t transactionId,
+ std::vector<uint64_t> 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 <typename Visitor>
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 <typename Visitor>
+ 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);
+ 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<ComposerState> states;
+ std::vector<ResolvedComposerState> states;
Vector<DisplayState> displays;
uint32_t flags;
sp<IBinder> applyToken;
InputWindowCommands inputWindowCommands;
int64_t desiredPresentTime;
bool isAutoTimestamp;
- client_cache_t buffer;
+ std::vector<uint64_t> uncacheBufferIds;
int64_t postTime;
- uint32_t permissions;
bool hasListenerCallbacks;
std::vector<ListenerCallbacks> listenerCallbacks;
int originPid;
int originUid;
uint64_t id;
- std::shared_ptr<CountDownLatch> 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<std::mutex> 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<std::mutex> 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<uint64_t> mergedTransactionIds;
};
} // namespace android
diff --git a/services/surfaceflinger/TunnelModeEnabledReporter.cpp b/services/surfaceflinger/TunnelModeEnabledReporter.cpp
index 4497cafa58..bc9b870075 100644
--- a/services/surfaceflinger/TunnelModeEnabledReporter.cpp
+++ b/services/surfaceflinger/TunnelModeEnabledReporter.cpp
@@ -59,7 +59,7 @@ void TunnelModeEnabledReporter::binderDied(const wp<IBinder>& who) {
void TunnelModeEnabledReporter::addListener(const sp<gui::ITunnelModeEnabledListener>& listener) {
sp<IBinder> asBinder = IInterface::asBinder(listener);
- asBinder->linkToDeath(this);
+ asBinder->linkToDeath(sp<DeathRecipient>::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 0000000000..ee942177e5
--- /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 <string>
+#include <string_view>
+
+#include <ftl/optional.h>
+
+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 <compositionengine/impl/DumpHelpers.h>
+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<const std::string_view&>(value));
+ }
+
+ void dump(std::string_view name, bool value) {
+ using namespace std::string_view_literals;
+ dump(name, value ? "true"sv : "false"sv);
+ }
+
+ template <typename T>
+ void dump(std::string_view name, const std::optional<T>& opt) {
+ if (opt) {
+ dump(name, *opt);
+ } else {
+ using namespace std::string_view_literals;
+ dump(name, "nullopt"sv);
+ }
+ }
+
+ template <typename T>
+ void dump(std::string_view name, const ftl::Optional<T>& opt) {
+ dump(name, static_cast<const std::optional<T>&>(opt));
+ }
+
+ template <typename T, typename... Ts>
+ 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> indent;
+ };
+
+private:
+ std::string& mOut;
+ int mIndent = 0;
+};
+
+} // namespace android::utils
diff --git a/services/surfaceflinger/Utils/OverlayUtils.h b/services/surfaceflinger/Utils/OverlayUtils.h
new file mode 100644
index 0000000000..0ef0be1101
--- /dev/null
+++ b/services/surfaceflinger/Utils/OverlayUtils.h
@@ -0,0 +1,146 @@
+/**
+ * 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_NDEBUG 0
+#pragma once
+
+#include "BackgroundExecutor.h"
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wconversion"
+#include <SkCanvas.h>
+#include <SkPaint.h>
+#pragma clang diagnostic pop
+
+#include <gui/SurfaceComposerClient.h>
+#include <utils/StrongPointer.h>
+
+namespace android {
+
+inline constexpr int kDigitWidth = 64;
+inline constexpr int kDigitHeight = 100;
+inline constexpr int kDigitSpace = 16;
+
+// HdrSdrRatioOverlay re-uses this value though it doesn't really need such amount buffer.
+// for output good-looking and code conciseness.
+inline constexpr int kMaxDigits = /*displayFps*/ 3 + /*renderFps*/ 3 + /*spinner*/ 1;
+inline constexpr int kBufferWidth = kMaxDigits * kDigitWidth + (kMaxDigits - 1) * kDigitSpace;
+inline constexpr int kBufferHeight = kDigitHeight;
+
+class SurfaceControl;
+
+// Helper class to delete the SurfaceControl on a helper thread as
+// SurfaceControl assumes its destruction happens without SurfaceFlinger::mStateLock held.
+class SurfaceControlHolder {
+public:
+ explicit SurfaceControlHolder(sp<SurfaceControl> sc) : mSurfaceControl(std::move(sc)){};
+
+ ~SurfaceControlHolder() {
+ // Hand the sp<SurfaceControl> to the helper thread to release the last
+ // reference. This makes sure that the SurfaceControl is destructed without
+ // SurfaceFlinger::mStateLock held.
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[sc = std::move(mSurfaceControl)]() mutable { sc.clear(); }});
+ }
+
+ static std::unique_ptr<SurfaceControlHolder> createSurfaceControlHolder(const String8& name) {
+ sp<SurfaceControl> surfaceControl =
+ SurfaceComposerClient::getDefault()
+ ->createSurface(name, kBufferWidth, kBufferHeight, PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState);
+ return std::make_unique<SurfaceControlHolder>(std::move(surfaceControl));
+ }
+
+ const sp<SurfaceControl>& get() const { return mSurfaceControl; }
+
+private:
+ sp<SurfaceControl> mSurfaceControl;
+};
+
+// Helper class to draw digit and decimal point.
+class SegmentDrawer {
+public:
+ enum class Segment {
+ Upper,
+ UpperLeft,
+ UpperRight,
+ Middle,
+ LowerLeft,
+ LowerRight,
+ Bottom,
+ DecimalPoint
+ };
+ static void drawSegment(Segment segment, int left, SkColor color, SkCanvas& canvas) {
+ const SkRect rect = [&]() {
+ switch (segment) {
+ case Segment::Upper:
+ return SkRect::MakeLTRB(left, 0, left + kDigitWidth, kDigitSpace);
+ case Segment::UpperLeft:
+ return SkRect::MakeLTRB(left, 0, left + kDigitSpace, kDigitHeight / 2.);
+ case Segment::UpperRight:
+ return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, 0, left + kDigitWidth,
+ kDigitHeight / 2.);
+ case Segment::Middle:
+ return SkRect::MakeLTRB(left, kDigitHeight / 2. - kDigitSpace / 2.,
+ left + kDigitWidth,
+ kDigitHeight / 2. + kDigitSpace / 2.);
+ case Segment::LowerLeft:
+ return SkRect::MakeLTRB(left, kDigitHeight / 2., left + kDigitSpace,
+ kDigitHeight);
+ case Segment::LowerRight:
+ return SkRect::MakeLTRB(left + kDigitWidth - kDigitSpace, kDigitHeight / 2.,
+ left + kDigitWidth, kDigitHeight);
+ case Segment::Bottom:
+ return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitWidth,
+ kDigitHeight);
+ case Segment::DecimalPoint:
+ return SkRect::MakeLTRB(left, kDigitHeight - kDigitSpace, left + kDigitSpace,
+ kDigitHeight);
+ }
+ }();
+
+ SkPaint paint;
+ paint.setColor(color);
+ paint.setBlendMode(SkBlendMode::kSrc);
+ canvas.drawRect(rect, paint);
+ }
+
+ static void drawDigit(int digit, int left, SkColor color, SkCanvas& canvas) {
+ if (digit < 0 || digit > 9) return;
+
+ if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 7 ||
+ digit == 8 || digit == 9)
+ drawSegment(Segment::Upper, left, color, canvas);
+ if (digit == 0 || digit == 4 || digit == 5 || digit == 6 || digit == 8 || digit == 9)
+ drawSegment(Segment::UpperLeft, left, color, canvas);
+ if (digit == 0 || digit == 1 || digit == 2 || digit == 3 || digit == 4 || digit == 7 ||
+ digit == 8 || digit == 9)
+ drawSegment(Segment::UpperRight, left, color, canvas);
+ if (digit == 2 || digit == 3 || digit == 4 || digit == 5 || digit == 6 || digit == 8 ||
+ digit == 9)
+ drawSegment(Segment::Middle, left, color, canvas);
+ if (digit == 0 || digit == 2 || digit == 6 || digit == 8)
+ drawSegment(Segment::LowerLeft, left, color, canvas);
+ if (digit == 0 || digit == 1 || digit == 3 || digit == 4 || digit == 5 || digit == 6 ||
+ digit == 7 || digit == 8 || digit == 9)
+ drawSegment(Segment::LowerRight, left, color, canvas);
+ if (digit == 0 || digit == 2 || digit == 3 || digit == 5 || digit == 6 || digit == 8 ||
+ digit == 9)
+ drawSegment(Segment::Bottom, left, color, canvas);
+ }
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/Utils/RingBuffer.h b/services/surfaceflinger/Utils/RingBuffer.h
new file mode 100644
index 0000000000..198e7b2cf1
--- /dev/null
+++ b/services/surfaceflinger/Utils/RingBuffer.h
@@ -0,0 +1,68 @@
+/*
+ * 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 <stddef.h>
+#include <array>
+
+namespace android::utils {
+
+template <class T, size_t SIZE>
+class RingBuffer {
+ RingBuffer(const RingBuffer&) = delete;
+ void operator=(const RingBuffer&) = delete;
+
+public:
+ RingBuffer() = default;
+ ~RingBuffer() = default;
+
+ constexpr size_t capacity() const { return SIZE; }
+
+ size_t size() const { return mCount; }
+
+ T& next() {
+ mHead = static_cast<size_t>(mHead + 1) % SIZE;
+ if (mCount < SIZE) {
+ mCount++;
+ }
+ return mBuffer[static_cast<size_t>(mHead)];
+ }
+
+ T& front() { return (*this)[0]; }
+
+ T& back() { return (*this)[size() - 1]; }
+
+ T& operator[](size_t index) {
+ return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
+ }
+
+ const T& operator[](size_t index) const {
+ return mBuffer[(static_cast<size_t>(mHead + 1) + index) % mCount];
+ }
+
+ void clear() {
+ mCount = 0;
+ mHead = -1;
+ }
+
+private:
+ std::array<T, SIZE> mBuffer;
+ int mHead = -1;
+ size_t mCount = 0;
+};
+
+} // namespace android::utils
diff --git a/services/surfaceflinger/WindowInfosListenerInvoker.cpp b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
index 30b9d8f1cb..effbfdb896 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.cpp
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.cpp
@@ -14,80 +14,193 @@
* limitations under the License.
*/
-#include <ftl/small_vector.h>
+#include <android/gui/BnWindowInfosPublisher.h>
+#include <android/gui/IWindowInfosPublisher.h>
+#include <android/gui/WindowInfosListenerInfo.h>
#include <gui/ISurfaceComposer.h>
+#include <gui/TraceUtils.h>
+#include <gui/WindowInfosUpdate.h>
+#include <scheduler/Time.h>
-#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) : mInvoker(invoker) {}
-
- binder::Status onWindowInfosReported() override {
- mInvoker.windowInfosReported();
- return binder::Status::ok();
- }
-
- WindowInfosListenerInvoker& mInvoker;
-};
-
-WindowInfosListenerInvoker::WindowInfosListenerInvoker(SurfaceFlinger& flinger)
- : mFlinger(flinger),
- mWindowInfosReportedListener(sp<WindowInfosReportedListener>::make(*this)) {}
-
-void WindowInfosListenerInvoker::addWindowInfosListener(sp<IWindowInfosListener> listener) {
- sp<IBinder> asBinder = IInterface::asBinder(listener);
- asBinder->linkToDeath(this);
-
- std::scoped_lock lock(mListenersMutex);
- mWindowInfosListeners.try_emplace(asBinder, std::move(listener));
+void WindowInfosListenerInvoker::addWindowInfosListener(sp<IWindowInfosListener> listener,
+ gui::WindowInfosListenerInfo* outInfo) {
+ int64_t listenerId = mNextListenerId++;
+ outInfo->listenerId = listenerId;
+ outInfo->windowInfosPublisher = sp<gui::IWindowInfosPublisher>::fromExisting(this);
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[this, listener = std::move(listener), listenerId]() {
+ ATRACE_NAME("WindowInfosListenerInvoker::addWindowInfosListener");
+ sp<IBinder> asBinder = IInterface::asBinder(listener);
+ asBinder->linkToDeath(sp<DeathRecipient>::fromExisting(this));
+ mWindowInfosListeners.try_emplace(asBinder,
+ std::make_pair(listenerId, std::move(listener)));
+ }});
}
void WindowInfosListenerInvoker::removeWindowInfosListener(
const sp<IWindowInfosListener>& listener) {
- sp<IBinder> asBinder = IInterface::asBinder(listener);
-
- std::scoped_lock lock(mListenersMutex);
- asBinder->unlinkToDeath(this);
- mWindowInfosListeners.erase(asBinder);
+ BackgroundExecutor::getInstance().sendCallbacks({[this, listener]() {
+ ATRACE_NAME("WindowInfosListenerInvoker::removeWindowInfosListener");
+ sp<IBinder> asBinder = IInterface::asBinder(listener);
+ asBinder->unlinkToDeath(sp<DeathRecipient>::fromExisting(this));
+ eraseListenerAndAckMessages(asBinder);
+ }});
}
void WindowInfosListenerInvoker::binderDied(const wp<IBinder>& who) {
- std::scoped_lock lock(mListenersMutex);
- mWindowInfosListeners.erase(who);
+ BackgroundExecutor::getInstance().sendCallbacks({[this, who]() {
+ ATRACE_NAME("WindowInfosListenerInvoker::binderDied");
+ eraseListenerAndAckMessages(who);
+ }});
}
-void WindowInfosListenerInvoker::windowInfosChanged(const std::vector<WindowInfo>& windowInfos,
- const std::vector<DisplayInfo>& displayInfos,
- bool shouldSync) {
- ftl::SmallVector<const sp<IWindowInfosListener>, kStaticCapacity> windowInfosListeners;
- {
- std::scoped_lock lock(mListenersMutex);
- for (const auto& [_, listener] : mWindowInfosListeners) {
- windowInfosListeners.push_back(listener);
+void WindowInfosListenerInvoker::eraseListenerAndAckMessages(const wp<IBinder>& binder) {
+ auto it = mWindowInfosListeners.find(binder);
+ int64_t listenerId = it->second.first;
+ mWindowInfosListeners.erase(binder);
+
+ std::vector<int64_t> vsyncIds;
+ for (auto& [vsyncId, state] : mUnackedState) {
+ if (std::find(state.unackedListenerIds.begin(), state.unackedListenerIds.end(),
+ listenerId) != state.unackedListenerIds.end()) {
+ vsyncIds.push_back(vsyncId);
}
}
- mCallbacksPending = windowInfosListeners.size();
+ 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,
+ };
+ }
+
+ // 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;
+ }
+
+ if (mDelayedUpdate) {
+ mDelayedUpdate.reset();
+ }
+
+ if (CC_UNLIKELY(mWindowInfosListeners.empty())) {
+ mReportedListeners.merge(reportedListeners);
+ mDelayInfo.reset();
+ return;
+ }
+
+ 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);
+ }
- for (const auto& listener : windowInfosListeners) {
- listener->onWindowInfosChanged(windowInfos, displayInfos,
- shouldSync ? mWindowInfosReportedListener : nullptr);
+ mDelayInfo.reset();
+ updateMaxSendDelay();
+
+ // 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::windowInfosReported() {
- mCallbacksPending--;
- if (mCallbacksPending == 0) {
- mFlinger.windowInfosReported();
+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::updateMaxSendDelay() {
+ if (!mDelayInfo) {
+ return;
}
+ nsecs_t delay = TimePoint::now().ns() - mDelayInfo->frameTime;
+ if (delay > mDebugInfo.maxSendDelayDuration) {
+ mDebugInfo.maxSendDelayDuration = delay;
+ mDebugInfo.maxSendDelayVsyncId = VsyncId{mDelayInfo->vsyncId};
+ }
+}
+
+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;
+ }
+
+ auto& state = it->second;
+ state.unackedListenerIds.unstable_erase(std::find(state.unackedListenerIds.begin(),
+ state.unackedListenerIds.end(),
+ listenerId));
+ if (!state.unackedListenerIds.empty()) {
+ return;
+ }
+
+ WindowInfosReportedListenerSet reportedListeners{std::move(state.reportedListeners)};
+ mUnackedState.erase(vsyncId);
+
+ for (const auto& reportedListener : reportedListeners) {
+ sp<IBinder> asBinder = IInterface::asBinder(reportedListener);
+ if (asBinder->isBinderAlive()) {
+ reportedListener->onWindowInfosReported();
+ }
+ }
+
+ if (!mDelayedUpdate || !mUnackedState.empty()) {
+ return;
+ }
+ 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 d8d8d0f570..261fd0ff3b 100644
--- a/services/surfaceflinger/WindowInfosListenerInvoker.h
+++ b/services/surfaceflinger/WindowInfosListenerInvoker.h
@@ -16,43 +16,72 @@
#pragma once
-#include <android/gui/BnWindowInfosReportedListener.h>
+#include <optional>
+#include <unordered_set>
+
+#include <android/gui/BnWindowInfosPublisher.h>
#include <android/gui/IWindowInfosListener.h>
#include <android/gui/IWindowInfosReportedListener.h>
#include <binder/IBinder.h>
#include <ftl/small_map.h>
+#include <ftl/small_vector.h>
+#include <gui/SpHash.h>
#include <utils/Mutex.h>
+#include "scheduler/VsyncId.h"
+
namespace android {
-class SurfaceFlinger;
+using WindowInfosReportedListenerSet =
+ std::unordered_set<sp<gui::IWindowInfosReportedListener>,
+ gui::SpHash<gui::IWindowInfosReportedListener>>;
-class WindowInfosListenerInvoker : public IBinder::DeathRecipient {
+class WindowInfosListenerInvoker : public gui::BnWindowInfosPublisher,
+ public IBinder::DeathRecipient {
public:
- explicit WindowInfosListenerInvoker(SurfaceFlinger&);
-
- void addWindowInfosListener(sp<gui::IWindowInfosListener>);
+ void addWindowInfosListener(sp<gui::IWindowInfosListener>, gui::WindowInfosListenerInfo*);
void removeWindowInfosListener(const sp<gui::IWindowInfosListener>& windowInfosListener);
- void windowInfosChanged(const std::vector<gui::WindowInfo>&,
- const std::vector<gui::DisplayInfo>&, bool shouldSync);
+ 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<IBinder>& who) override;
private:
- struct WindowInfosReportedListener;
- void windowInfosReported();
+ static constexpr size_t kStaticCapacity = 3;
+ std::atomic<int64_t> mNextListenerId{0};
+ ftl::SmallMap<wp<IBinder>, const std::pair<int64_t, sp<gui::IWindowInfosListener>>,
+ kStaticCapacity>
+ mWindowInfosListeners;
- SurfaceFlinger& mFlinger;
- std::mutex mListenersMutex;
+ std::optional<gui::WindowInfosUpdate> mDelayedUpdate;
+ WindowInfosReportedListenerSet mReportedListeners;
+ void eraseListenerAndAckMessages(const wp<IBinder>&);
- static constexpr size_t kStaticCapacity = 3;
- ftl::SmallMap<wp<IBinder>, const sp<gui::IWindowInfosListener>, kStaticCapacity>
- mWindowInfosListeners GUARDED_BY(mListenersMutex);
+ struct UnackedState {
+ ftl::SmallVector<int64_t, kStaticCapacity> unackedListenerIds;
+ WindowInfosReportedListenerSet reportedListeners;
+ };
+ ftl::SmallMap<int64_t /* vsyncId */, UnackedState, 5> mUnackedState;
- sp<gui::IWindowInfosReportedListener> mWindowInfosReportedListener;
- std::atomic<size_t> mCallbacksPending{0};
+ DebugInfo mDebugInfo;
+ struct DelayInfo {
+ int64_t vsyncId;
+ nsecs_t frameTime;
+ };
+ std::optional<DelayInfo> mDelayInfo;
+ void updateMaxSendDelay();
};
} // namespace android
diff --git a/services/surfaceflinger/common/Android.bp b/services/surfaceflinger/common/Android.bp
new file mode 100644
index 0000000000..5ef22b590b
--- /dev/null
+++ b/services/surfaceflinger/common/Android.bp
@@ -0,0 +1,48 @@
+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: "libsurfaceflinger_common_defaults",
+ defaults: [
+ "android.hardware.graphics.composer3-ndk_shared",
+ "surfaceflinger_defaults",
+ ],
+ shared_libs: [
+ "libSurfaceFlingerProp",
+ "server_configurable_flags",
+ ],
+ static_libs: [
+ "librenderengine",
+ ],
+ srcs: [
+ "FlagManager.cpp",
+ ],
+ local_include_dirs: ["include"],
+ export_include_dirs: ["include"],
+}
+
+cc_library_static {
+ name: "libsurfaceflinger_common",
+ defaults: [
+ "libsurfaceflinger_common_defaults",
+ ],
+ static_libs: [
+ "libsurfaceflingerflags",
+ ],
+}
+
+cc_library_static {
+ name: "libsurfaceflinger_common_test",
+ defaults: [
+ "libsurfaceflinger_common_defaults",
+ ],
+ static_libs: [
+ "libsurfaceflingerflags_test",
+ ],
+}
diff --git a/services/surfaceflinger/common/FlagManager.cpp b/services/surfaceflinger/common/FlagManager.cpp
new file mode 100644
index 0000000000..cb1faee6b9
--- /dev/null
+++ b/services/surfaceflinger/common/FlagManager.cpp
@@ -0,0 +1,217 @@
+/*
+ * 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.
+ */
+
+#include <common/FlagManager.h>
+
+#include <SurfaceFlingerProperties.sysprop.h>
+#include <android-base/parsebool.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+#include <renderengine/RenderEngine.h>
+#include <server_configurable_flags/get_flags.h>
+#include <cinttypes>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+namespace android {
+using namespace com::android::graphics::surfaceflinger;
+
+static constexpr const char* kExperimentNamespace = "surface_flinger_native_boot";
+
+std::unique_ptr<FlagManager> FlagManager::mInstance;
+std::once_flag FlagManager::mOnce;
+
+FlagManager::FlagManager(ConstructorTag) {}
+FlagManager::~FlagManager() = default;
+
+namespace {
+std::optional<bool> parseBool(const char* str) {
+ base::ParseBoolResult parseResult = base::ParseBool(str);
+ switch (parseResult) {
+ case base::ParseBoolResult::kTrue:
+ return std::make_optional(true);
+ case base::ParseBoolResult::kFalse:
+ return std::make_optional(false);
+ case base::ParseBoolResult::kError:
+ return std::nullopt;
+ }
+}
+
+bool getFlagValue(std::function<bool()> getter, std::optional<bool> overrideValue) {
+ if (overrideValue.has_value()) {
+ return *overrideValue;
+ }
+
+ return getter();
+}
+
+} // namespace
+
+const FlagManager& FlagManager::getInstance() {
+ return getMutableInstance();
+}
+
+FlagManager& FlagManager::getMutableInstance() {
+ std::call_once(mOnce, [&] {
+ LOG_ALWAYS_FATAL_IF(mInstance, "Instance already created");
+ mInstance = std::make_unique<FlagManager>(ConstructorTag{});
+ });
+
+ return *mInstance;
+}
+
+void FlagManager::markBootCompleted() {
+ mBootCompleted = true;
+}
+
+void FlagManager::setUnitTestMode() {
+ mUnitTestMode = true;
+
+ // Also set boot completed as we don't really care about it in unit testing
+ mBootCompleted = true;
+}
+
+void FlagManager::dumpFlag(std::string& result, bool readonly, const char* name,
+ std::function<bool()> getter) const {
+ if (readonly || mBootCompleted) {
+ base::StringAppendF(&result, "%s: %s\n", name, getter() ? "true" : "false");
+ } else {
+ base::StringAppendF(&result, "%s: in progress (still booting)\n", name);
+ }
+}
+
+void FlagManager::dump(std::string& result) const {
+#define DUMP_FLAG_INTERVAL(name, readonly) \
+ dumpFlag(result, (readonly), #name, std::bind(&FlagManager::name, this))
+#define DUMP_SERVER_FLAG(name) DUMP_FLAG_INTERVAL(name, false)
+#define DUMP_READ_ONLY_FLAG(name) DUMP_FLAG_INTERVAL(name, true)
+
+ base::StringAppendF(&result, "FlagManager values: \n");
+
+ /// Legacy server flags ///
+ DUMP_SERVER_FLAG(use_adpf_cpu_hint);
+ DUMP_SERVER_FLAG(use_skia_tracing);
+
+ /// Trunk stable server flags ///
+ DUMP_SERVER_FLAG(late_boot_misc2);
+ DUMP_SERVER_FLAG(dont_skip_on_early);
+ DUMP_SERVER_FLAG(refresh_rate_overlay_on_external_display);
+
+ /// Trunk stable readonly flags ///
+ DUMP_READ_ONLY_FLAG(connected_display);
+ DUMP_READ_ONLY_FLAG(enable_small_area_detection);
+ DUMP_READ_ONLY_FLAG(misc1);
+ DUMP_READ_ONLY_FLAG(vrr_config);
+ DUMP_READ_ONLY_FLAG(hotplug2);
+ DUMP_READ_ONLY_FLAG(hdcp_level_hal);
+ DUMP_READ_ONLY_FLAG(multithreaded_present);
+ DUMP_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace);
+ DUMP_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency);
+ DUMP_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved);
+ DUMP_READ_ONLY_FLAG(enable_fro_dependent_features);
+ DUMP_READ_ONLY_FLAG(display_protected);
+ DUMP_READ_ONLY_FLAG(fp16_client_target);
+ DUMP_READ_ONLY_FLAG(game_default_frame_rate);
+ DUMP_READ_ONLY_FLAG(enable_layer_command_batching);
+#undef DUMP_READ_ONLY_FLAG
+#undef DUMP_SERVER_FLAG
+#undef DUMP_FLAG_INTERVAL
+}
+
+std::optional<bool> FlagManager::getBoolProperty(const char* property) const {
+ return parseBool(base::GetProperty(property, "").c_str());
+}
+
+bool FlagManager::getServerConfigurableFlag(const char* experimentFlagName) const {
+ const auto value = server_configurable_flags::GetServerConfigurableFlag(kExperimentNamespace,
+ experimentFlagName, "");
+ const auto res = parseBool(value.c_str());
+ return res.has_value() && res.value();
+}
+
+#define FLAG_MANAGER_LEGACY_SERVER_FLAG(name, syspropOverride, serverFlagName) \
+ bool FlagManager::name() const { \
+ LOG_ALWAYS_FATAL_IF(!mBootCompleted, \
+ "Can't read %s before boot completed as it is server writable", \
+ __func__); \
+ const auto debugOverride = getBoolProperty(syspropOverride); \
+ if (debugOverride.has_value()) return debugOverride.value(); \
+ return getServerConfigurableFlag(serverFlagName); \
+ }
+
+#define FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, checkForBootCompleted) \
+ bool FlagManager::name() const { \
+ if (checkForBootCompleted) { \
+ LOG_ALWAYS_FATAL_IF(!mBootCompleted, \
+ "Can't read %s before boot completed as it is server writable", \
+ __func__); \
+ } \
+ static const std::optional<bool> debugOverride = getBoolProperty(syspropOverride); \
+ static const bool value = getFlagValue([] { return flags::name(); }, debugOverride); \
+ if (mUnitTestMode) { \
+ /* \
+ * When testing, we don't want to rely on the cached `value` or the debugOverride. \
+ */ \
+ return flags::name(); \
+ } \
+ return value; \
+ }
+
+#define FLAG_MANAGER_SERVER_FLAG(name, syspropOverride) \
+ FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, true)
+
+#define FLAG_MANAGER_READ_ONLY_FLAG(name, syspropOverride) \
+ FLAG_MANAGER_FLAG_INTERNAL(name, syspropOverride, false)
+
+/// Legacy server flags ///
+FLAG_MANAGER_LEGACY_SERVER_FLAG(test_flag, "", "")
+FLAG_MANAGER_LEGACY_SERVER_FLAG(use_adpf_cpu_hint, "debug.sf.enable_adpf_cpu_hint",
+ "AdpfFeature__adpf_cpu_hint")
+FLAG_MANAGER_LEGACY_SERVER_FLAG(use_skia_tracing, PROPERTY_SKIA_ATRACE_ENABLED,
+ "SkiaTracingFeature__use_skia_tracing")
+
+/// Trunk stable readonly flags ///
+FLAG_MANAGER_READ_ONLY_FLAG(connected_display, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_small_area_detection, "")
+FLAG_MANAGER_READ_ONLY_FLAG(misc1, "")
+FLAG_MANAGER_READ_ONLY_FLAG(vrr_config, "debug.sf.enable_vrr_config")
+FLAG_MANAGER_READ_ONLY_FLAG(hotplug2, "")
+FLAG_MANAGER_READ_ONLY_FLAG(hdcp_level_hal, "")
+FLAG_MANAGER_READ_ONLY_FLAG(multithreaded_present, "debug.sf.multithreaded_present")
+FLAG_MANAGER_READ_ONLY_FLAG(add_sf_skipped_frames_to_trace, "")
+FLAG_MANAGER_READ_ONLY_FLAG(use_known_refresh_rate_for_fps_consistency, "")
+FLAG_MANAGER_READ_ONLY_FLAG(cache_if_source_crop_layer_only_moved,
+ "debug.sf.cache_source_crop_only_moved")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_fro_dependent_features, "")
+FLAG_MANAGER_READ_ONLY_FLAG(display_protected, "")
+FLAG_MANAGER_READ_ONLY_FLAG(fp16_client_target, "debug.sf.fp16_client_target")
+FLAG_MANAGER_READ_ONLY_FLAG(game_default_frame_rate, "")
+FLAG_MANAGER_READ_ONLY_FLAG(enable_layer_command_batching, "")
+
+/// Trunk stable server flags ///
+FLAG_MANAGER_SERVER_FLAG(late_boot_misc2, "")
+FLAG_MANAGER_SERVER_FLAG(refresh_rate_overlay_on_external_display, "")
+
+/// Exceptions ///
+bool FlagManager::dont_skip_on_early() const {
+ // Even though this is a server writable flag, we do call it before boot completed, but that's
+ // fine since the decision is done per frame. We can't do caching though.
+ return flags::dont_skip_on_early();
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/common/include/common/FlagManager.h b/services/surfaceflinger/common/include/common/FlagManager.h
new file mode 100644
index 0000000000..8b5983d2c4
--- /dev/null
+++ b/services/surfaceflinger/common/include/common/FlagManager.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <cstdint>
+#include <functional>
+#include <mutex>
+#include <optional>
+#include <string>
+
+namespace android {
+// Manages flags for SurfaceFlinger, including default values, system properties, and Mendel
+// experiment configuration values. Can be called from any thread.
+class FlagManager {
+private:
+ // Effectively making the constructor private, while allowing std::make_unique to work
+ struct ConstructorTag {};
+
+public:
+ static const FlagManager& getInstance();
+ static FlagManager& getMutableInstance();
+
+ FlagManager(ConstructorTag);
+ virtual ~FlagManager();
+
+ void markBootCompleted();
+ void dump(std::string& result) const;
+
+ void setUnitTestMode();
+
+ /// Legacy server flags ///
+ bool test_flag() const;
+ bool use_adpf_cpu_hint() const;
+ bool use_skia_tracing() const;
+
+ /// Trunk stable server flags ///
+ bool late_boot_misc2() const;
+ bool dont_skip_on_early() const;
+ bool refresh_rate_overlay_on_external_display() const;
+
+ /// Trunk stable readonly flags ///
+ bool connected_display() const;
+ bool enable_small_area_detection() const;
+ bool misc1() const;
+ bool vrr_config() const;
+ bool hotplug2() const;
+ bool hdcp_level_hal() const;
+ bool multithreaded_present() const;
+ bool add_sf_skipped_frames_to_trace() const;
+ bool use_known_refresh_rate_for_fps_consistency() const;
+ bool cache_if_source_crop_layer_only_moved() const;
+ bool enable_fro_dependent_features() const;
+ bool display_protected() const;
+ bool fp16_client_target() const;
+ bool game_default_frame_rate() const;
+ bool enable_layer_command_batching() const;
+
+protected:
+ // overridden for unit tests
+ virtual std::optional<bool> getBoolProperty(const char*) const;
+ virtual bool getServerConfigurableFlag(const char*) const;
+
+private:
+ friend class TestableFlagManager;
+
+ FlagManager(const FlagManager&) = delete;
+
+ void dumpFlag(std::string& result, bool readonly, const char* name,
+ std::function<bool()> getter) const;
+
+ std::atomic_bool mBootCompleted = false;
+ std::atomic_bool mUnitTestMode = false;
+
+ static std::unique_ptr<FlagManager> mInstance;
+ static std::once_flag mOnce;
+};
+} // namespace android
diff --git a/libs/renderengine/gl/GLVertexBuffer.h b/services/surfaceflinger/common/include/common/test/FlagUtils.h
index c0fd0c1b04..550c70d98f 100644
--- a/libs/renderengine/gl/GLVertexBuffer.h
+++ b/services/surfaceflinger/common/include/common/test/FlagUtils.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,34 +16,25 @@
#pragma once
-#include <cstdint>
+#include <common/FlagManager.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <GLES2/gl2.h>
-
-struct ANativeWindowBuffer;
+#define SET_FLAG_FOR_TEST(name, value) TestFlagSetter _testflag_((name), (name), (value))
namespace android {
-namespace renderengine {
-namespace gl {
-
-class GLESRenderEngine;
-
-class GLVertexBuffer {
+class TestFlagSetter {
public:
- explicit GLVertexBuffer();
- ~GLVertexBuffer();
+ TestFlagSetter(bool (*getter)(), void((*setter)(bool)), bool flagValue) {
+ FlagManager::getMutableInstance().setUnitTestMode();
+
+ const bool initialValue = getter();
+ setter(flagValue);
+ mResetFlagValue = [=] { setter(initialValue); };
+ }
- void allocateBuffers(const GLfloat data[], const GLuint size);
- uint32_t getBufferName() const { return mBufferName; }
- void bind() const;
- void unbind() const;
+ ~TestFlagSetter() { mResetFlagValue(); }
private:
- uint32_t mBufferName;
+ std::function<void()> mResetFlagValue;
};
-} // namespace gl
-} // namespace renderengine
} // namespace android
diff --git a/services/surfaceflinger/fuzzer/Android.bp b/services/surfaceflinger/fuzzer/Android.bp
index b0b6bf14ab..ab3b3528dd 100644
--- a/services/surfaceflinger/fuzzer/Android.bp
+++ b/services/surfaceflinger/fuzzer/Android.bp
@@ -31,6 +31,7 @@ cc_defaults {
],
static_libs: [
"android.hardware.graphics.composer@2.1-resources",
+ "libc++fs",
"libgmock",
"libgui_mocks",
"libgmock_ndk",
@@ -38,6 +39,7 @@ cc_defaults {
"libgtest_ndk_c++",
"libgmock_main_ndk",
"librenderengine_mocks",
+ "libsurfaceflinger_common",
"perfetto_trace_protos",
"libcompositionengine_mocks",
"perfetto_trace_protos",
@@ -69,12 +71,21 @@ cc_defaults {
"-Wno-unused-result",
"-Wno-conversion",
"-Wno-sign-compare",
+ "-Wno-unused-function",
],
fuzz_config: {
cc: [
- "android-media-fuzzing-reports@google.com",
+ "android-cogs-eng@google.com",
],
- componentid: 155276,
+ componentid: 1075131,
+ hotlists: [
+ "4593311",
+ ],
+ description: "The fuzzer targets the APIs of libsurfaceflinger library",
+ vector: "local_no_privileges_required",
+ service_privilege: "privileged",
+ users: "multi_user",
+ fuzzed_code_usage: "shipped",
},
}
@@ -127,3 +138,28 @@ cc_fuzz {
"surfaceflinger_layer_fuzzer.cpp",
],
}
+
+cc_fuzz {
+ name: "surfaceflinger_frametracer_fuzzer",
+ defaults: [
+ "surfaceflinger_fuzz_defaults",
+ ],
+ srcs: [
+ "surfaceflinger_frametracer_fuzzer.cpp",
+ ],
+}
+
+cc_fuzz {
+ name: "surfaceflinger_service_fuzzer",
+ defaults: [
+ "surfaceflinger_fuzz_defaults",
+ "service_fuzzer_defaults",
+ "fuzzer_disable_leaks",
+ ],
+ srcs: [
+ "surfaceflinger_service_fuzzer.cpp",
+ ],
+ fuzz_config: {
+ triage_assignee: "waghpawan@google.com",
+ },
+}
diff --git a/services/surfaceflinger/fuzzer/README.md b/services/surfaceflinger/fuzzer/README.md
index 78a7596ef3..a06c41b139 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)
# <a name="SurfaceFlinger"></a> 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
```
+
+# <a name="FrameTracer"></a> 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 a605a2fd9b..68237c8dd6 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<uint8_t>()));
};
void process();
@@ -162,16 +163,22 @@ private:
void DisplayHardwareFuzzer::validateDisplay(Hwc2::AidlComposer* composer, Display display) {
uint32_t outNumTypes, outNumRequests;
- composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
- &outNumRequests);
+ const auto frameIntervalRange =
+ mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
+ Fps::fromValue(120).getPeriodNsecs());
+ composer->validateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
+ &outNumTypes, &outNumRequests);
}
void DisplayHardwareFuzzer::presentOrValidateDisplay(Hwc2::AidlComposer* composer,
Display display) {
int32_t outPresentFence;
uint32_t outNumTypes, outNumRequests, state;
- composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), &outNumTypes,
- &outNumRequests, &outPresentFence, &state);
+ const auto frameIntervalRange =
+ mFdp.ConsumeIntegralInRange<int32_t>(Fps::fromValue(1).getPeriodNsecs(),
+ Fps::fromValue(120).getPeriodNsecs());
+ composer->presentOrValidateDisplay(display, mFdp.ConsumeIntegral<nsecs_t>(), frameIntervalRange,
+ &outNumTypes, &outNumRequests, &outPresentFence, &state);
}
void DisplayHardwareFuzzer::setOutputBuffer(Hwc2::AidlComposer* composer, Display display) {
@@ -221,8 +228,11 @@ void DisplayHardwareFuzzer::getDeviceCompositionChanges(HalDisplayId halDisplayI
std::optional<impl::HWComposer::DeviceRequestedChanges> outChanges;
mHwc.getDeviceCompositionChanges(halDisplayID,
mFdp.ConsumeBool() /*frameUsesClientComposition*/,
- std::chrono::steady_clock::now(), FenceTime::NO_FENCE,
- mFdp.ConsumeIntegral<nsecs_t>(), &outChanges);
+ std::chrono::steady_clock::now(),
+ mFdp.ConsumeIntegral<nsecs_t>(),
+ Fps::fromValue(
+ mFdp.ConsumeFloatingPointInRange<float>(1.f, 120.f)),
+ &outChanges);
}
void DisplayHardwareFuzzer::getDisplayedContentSamplingAttributes(HalDisplayId halDisplayID) {
@@ -276,7 +286,7 @@ void DisplayHardwareFuzzer::invokeAidlComposer() {
composer.setClientTarget(display, mFdp.ConsumeIntegral<uint32_t>(), sp<GraphicBuffer>(),
mFdp.ConsumeIntegral<int32_t>(), mFdp.PickValueInArray(kDataspaces),
- {});
+ {}, mFdp.ConsumeFloatingPoint<float>());
composer.setColorMode(display, mFdp.PickValueInArray(kColormodes),
mFdp.PickValueInArray(kRenderIntents));
@@ -325,8 +335,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,12 +489,12 @@ void DisplayHardwareFuzzer::invokeFrameBufferSurface() {
BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
sp<FramebufferSurface> surface =
- new FramebufferSurface(mHwc, mPhysicalDisplayId, bqConsumer, getFuzzedSize() /*size*/,
- getFuzzedSize() /*maxSize*/);
+ sp<FramebufferSurface>::make(mHwc, mPhysicalDisplayId, bqConsumer,
+ getFuzzedSize() /*size*/, getFuzzedSize() /*maxSize*/);
surface->beginFrame(mFdp.ConsumeBool());
surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
- surface->advanceFrame();
+ surface->advanceFrame(mFdp.ConsumeFloatingPoint<float>());
surface->onFrameCommitted();
String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
surface->dumpAsString(result);
@@ -497,15 +506,15 @@ void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() {
DisplayIdGenerator<HalVirtualDisplayId> mGenerator;
VirtualDisplayId VirtualDisplayId = mGenerator.generateId().value();
- sp<SurfaceComposerClient> mClient = new SurfaceComposerClient();
+ sp<SurfaceComposerClient> mClient = sp<SurfaceComposerClient>::make();
sp<SurfaceControl> mSurfaceControl =
mClient->createSurface(String8("TestSurface"), 100, 100, PIXEL_FORMAT_RGBA_8888,
ISurfaceComposerClient::eFXSurfaceBufferState,
/*parent*/ nullptr);
- sp<BLASTBufferQueue> mBlastBufferQueueAdapter =
- new BLASTBufferQueue("TestBLASTBufferQueue", mSurfaceControl, 100, 100,
- PIXEL_FORMAT_RGBA_8888);
+ auto mBlastBufferQueueAdapter =
+ sp<BLASTBufferQueue>::make("TestBLASTBufferQueue", mSurfaceControl, 100, 100,
+ PIXEL_FORMAT_RGBA_8888);
sp<IGraphicBufferProducer> sink = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
sp<IGraphicBufferProducer> bqProducer = mBlastBufferQueueAdapter->getIGraphicBufferProducer();
@@ -513,15 +522,15 @@ void DisplayHardwareFuzzer::invokeVirtualDisplaySurface() {
BufferQueue::createBufferQueue(&bqProducer, &bqConsumer);
BufferQueue::createBufferQueue(&sink, &bqConsumer);
- sp<VirtualDisplaySurface> surface =
- new VirtualDisplaySurface(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer,
- mFdp.ConsumeRandomLengthString().c_str() /*name*/);
+ auto surface =
+ sp<VirtualDisplaySurface>::make(mHwc, VirtualDisplayId, sink, bqProducer, bqConsumer,
+ mFdp.ConsumeRandomLengthString().c_str() /*name*/);
surface->beginFrame(mFdp.ConsumeBool());
surface->prepareFrame(mFdp.PickValueInArray(kCompositionTypes));
surface->resizeBuffers(getFuzzedSize());
surface->getClientTargetAcquireFence();
- surface->advanceFrame();
+ surface->advanceFrame(mFdp.ConsumeFloatingPoint<float>());
surface->onFrameCommitted();
String8 result = String8(mFdp.ConsumeRandomLengthString().c_str());
surface->dumpAsString(result);
@@ -552,10 +561,10 @@ void DisplayHardwareFuzzer::invokeComposer() {
getDeviceCompositionChanges(halDisplayID);
mHwc.setClientTarget(halDisplayID, mFdp.ConsumeIntegral<uint32_t>(), Fence::NO_FENCE,
- sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces));
+ sp<GraphicBuffer>::make(), mFdp.PickValueInArray(kDataspaces),
+ mFdp.ConsumeFloatingPoint<float>());
- 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 +574,7 @@ void DisplayHardwareFuzzer::invokeComposer() {
mHwc.getLayerReleaseFence(halDisplayID, layer);
- mHwc.setOutputBuffer(halVirtualDisplayId, sp<Fence>::make().get(), sp<GraphicBuffer>::make());
+ mHwc.setOutputBuffer(halVirtualDisplayId, sp<Fence>::make(), sp<GraphicBuffer>::make());
mHwc.clearReleaseFences(halDisplayID);
@@ -598,7 +607,7 @@ void DisplayHardwareFuzzer::invokeComposer() {
mFdp.ConsumeBool() ? hal::Vsync::ENABLE : hal::Vsync::DISABLE);
mHwc.isConnected(mPhysicalDisplayId);
- mHwc.getModes(mPhysicalDisplayId);
+ mHwc.getModes(mPhysicalDisplayId, mFdp.ConsumeIntegral<int32_t>());
mHwc.getActiveMode(mPhysicalDisplayId);
mHwc.getColorModes(mPhysicalDisplayId);
mHwc.hasCapability(mFdp.PickValueInArray(kCapability));
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
index 6a6e3db733..b2dc20e216 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_displayhardware_fuzzer_utils.h
@@ -41,6 +41,8 @@ class SurfaceComposerClient;
namespace android::hardware::graphics::composer::hal {
+using aidl::android::hardware::graphics::common::DisplayHotplugEvent;
+using aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::HWC2::ComposerCallback;
@@ -51,7 +53,9 @@ public:
: mCallback(callback), mVsyncSwitchingSupported(vsyncSwitchingSupported) {}
Return<void> onHotplug(HWDisplayId display, Connection connection) override {
- mCallback->onComposerHalHotplug(display, connection);
+ const auto event = connection == Connection::CONNECTED ? DisplayHotplugEvent::CONNECTED
+ : DisplayHotplugEvent::DISCONNECTED;
+ mCallback->onComposerHalHotplugEvent(display, event);
return Void();
}
@@ -93,12 +97,13 @@ private:
struct TestHWC2ComposerCallback : public HWC2::ComposerCallback {
virtual ~TestHWC2ComposerCallback() = default;
- void onComposerHalHotplug(HWDisplayId, Connection){};
+ void onComposerHalHotplugEvent(HWDisplayId, DisplayHotplugEvent) {}
void onComposerHalRefresh(HWDisplayId) {}
void onComposerHalVsync(HWDisplayId, int64_t, std::optional<VsyncPeriodNanos>) {}
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 0000000000..ce8d47e71c
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_frametracer_fuzzer.cpp
@@ -0,0 +1,147 @@
+/*
+ * 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 <FrameTracer/FrameTracer.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <perfetto/trace/trace.pb.h>
+
+namespace android::fuzz {
+
+using namespace google::protobuf;
+
+constexpr size_t kMaxStringSize = 256;
+constexpr size_t kMinLayerIds = 1;
+constexpr size_t kMaxLayerIds = 10;
+constexpr int32_t kMinRange = 0;
+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<android::FrameTracer>();
+ }
+ ~FrameTracerFuzzer() { mFrameTracer.reset(); }
+ void process();
+
+private:
+ void traceTimestamp();
+ void traceTimestamp(std::vector<int32_t> layerIds, size_t numLayerIds);
+ void traceFence(std::vector<int32_t> layerIds, size_t numLayerIds);
+ std::unique_ptr<perfetto::TracingSession> getTracingSessionForTest();
+ std::unique_ptr<android::FrameTracer> mFrameTracer = nullptr;
+ std::vector<int32_t> generateLayerIds(size_t numLayerIds);
+ android::FenceToFenceTimeMap mFenceFactory;
+ FuzzedDataProvider mFdp;
+};
+
+std::unique_ptr<perfetto::TracingSession> FrameTracerFuzzer::getTracingSessionForTest() {
+ perfetto::TraceConfig cfg;
+ cfg.set_duration_ms(mFdp.ConsumeIntegralInRange<int32_t>(kMinRange, kConfigDuration));
+ cfg.add_buffers()->set_size_kb(mFdp.ConsumeIntegralInRange<int32_t>(kMinRange, 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<int32_t> FrameTracerFuzzer::generateLayerIds(size_t numLayerIds) {
+ std::vector<int32_t> layerIds;
+ for (size_t i = 0; i < numLayerIds; ++i) {
+ layerIds.push_back(mFdp.ConsumeIntegral<int32_t>());
+ }
+ return layerIds;
+}
+
+void FrameTracerFuzzer::traceTimestamp(std::vector<int32_t> layerIds, size_t numLayerIds) {
+ uint32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
+ android::FrameTracer::FrameEvent::BufferEventType type = static_cast<
+ android::FrameTracer::FrameEvent::BufferEventType>(
+ mFdp.ConsumeIntegralInRange<uint32_t>(android::FrameTracer::FrameEvent::UNSPECIFIED,
+ android::FrameTracer::FrameEvent::CANCEL));
+ mFrameTracer->traceTimestamp(layerId, mFdp.ConsumeIntegral<uint64_t>() /*bufferID*/,
+ mFdp.ConsumeIntegral<uint64_t>() /*frameNumber*/,
+ mFdp.ConsumeIntegral<nsecs_t>() /*timestamp*/, type,
+ mFdp.ConsumeIntegral<nsecs_t>() /*duration*/);
+}
+
+void FrameTracerFuzzer::traceFence(std::vector<int32_t> layerIds, size_t numLayerIds) {
+ const nsecs_t signalTime =
+ mFdp.ConsumeBool() ? android::Fence::SIGNAL_TIME_PENDING : systemTime();
+ const nsecs_t startTime = (signalTime == android::Fence::SIGNAL_TIME_PENDING)
+ ? signalTime - kTimeOffset
+ : signalTime + kTimeOffset;
+ auto fence = mFenceFactory.createFenceTimeForTest(android::Fence::NO_FENCE);
+ mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, signalTime);
+ int32_t layerId = layerIds.at(mFdp.ConsumeIntegralInRange<size_t>(0, numLayerIds - 1));
+ mFrameTracer->traceFence(layerId, mFdp.ConsumeIntegral<uint64_t>() /*bufferID*/,
+ mFdp.ConsumeIntegral<uint64_t>() /*frameNumber*/, fence,
+ android::FrameTracer::FrameEvent::ACQUIRE_FENCE, startTime);
+}
+
+void FrameTracerFuzzer::process() {
+ std::vector<int32_t> layerIds =
+ generateLayerIds(mFdp.ConsumeIntegralInRange<size_t>(kMinLayerIds, kMaxLayerIds));
+
+ std::unique_ptr<perfetto::TracingSession> tracingSession;
+ while (mFdp.remaining_bytes()) {
+ auto invokeFrametracerAPI = mFdp.PickValueInArray<const std::function<void()>>({
+ [&]() { mFrameTracer->registerDataSource(); },
+ [&]() {
+ if (tracingSession) {
+ tracingSession->StopBlocking();
+ }
+ tracingSession = getTracingSessionForTest();
+ tracingSession->StartBlocking();
+ },
+ [&]() { traceTimestamp(layerIds, layerIds.size()); },
+ [&]() { traceFence(layerIds, layerIds.size()); },
+ [&]() {
+ for (auto it = layerIds.begin(); it != layerIds.end(); ++it) {
+ mFrameTracer->traceNewLayer(*it /*layerId*/,
+ mFdp.ConsumeRandomLengthString(
+ kMaxStringSize) /*layerName*/);
+ }
+ },
+ [&]() { mFenceFactory.signalAllForTest(android::Fence::NO_FENCE, systemTime()); },
+ });
+ invokeFrametracerAPI();
+ }
+
+ 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 f25043cd6d..d13189e439 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzer.cpp
@@ -24,7 +24,6 @@
namespace android::fuzz {
static constexpr LatchUnsignaledConfig kLatchUnsignaledConfig[] = {
- LatchUnsignaledConfig::Always,
LatchUnsignaledConfig::AutoSingleLayer,
LatchUnsignaledConfig::Disabled,
};
@@ -95,6 +94,7 @@ static constexpr BnSurfaceComposer::ISurfaceComposerTag kSurfaceComposerTags[]{
BnSurfaceComposer::REMOVE_TUNNEL_MODE_ENABLED_LISTENER,
BnSurfaceComposer::ADD_WINDOW_INFOS_LISTENER,
BnSurfaceComposer::REMOVE_WINDOW_INFOS_LISTENER,
+ BnSurfaceComposer::GET_SCHEDULING_POLICY,
};
static constexpr uint32_t kMinCode = 1000;
@@ -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<SurfaceFlinger>::fromExisting(mTestableFlinger.flinger());
};
void process(const uint8_t *data, size_t size);
@@ -130,7 +130,7 @@ void SurfaceFlingerFuzzer::invokeFlinger() {
mFlinger->maxFrameBufferAcquiredBuffers = mFdp.ConsumeIntegral<int64_t>();
mFlinger->maxGraphicsWidth = mFdp.ConsumeIntegral<uint32_t>();
mFlinger->maxGraphicsHeight = mFdp.ConsumeIntegral<uint32_t>();
- mFlinger->hasWideColorDisplay = mFdp.ConsumeBool();
+ mTestableFlinger.mutableSupportsWideColor() = mFdp.ConsumeBool();
mFlinger->useContextPriority = mFdp.ConsumeBool();
mFlinger->defaultCompositionDataspace = mFdp.PickValueInArray(kDataspaces);
@@ -145,13 +145,9 @@ void SurfaceFlingerFuzzer::invokeFlinger() {
mFlinger->scheduleRepaint();
mFlinger->scheduleSample();
- uint32_t texture = mFlinger->getNewTexture();
- mFlinger->deleteTextureAsync(texture);
-
sp<IBinder> handle = defaultServiceManager()->checkService(
String16(mFdp.ConsumeRandomLengthString().c_str()));
- mFlinger->fromHandle(handle);
- mFlinger->windowInfosReported();
+ LayerHandle::getLayer(handle);
mFlinger->disableExpensiveRendering();
}
@@ -186,11 +182,12 @@ void SurfaceFlingerFuzzer::setTransactionState() {
bool hasListenerCallbacks = mFdp.ConsumeBool();
std::vector<ListenerCallbacks> listenerCallbacks{};
uint64_t transactionId = mFdp.ConsumeIntegral<uint64_t>();
+ std::vector<uint64_t> mergedTransactionIds{};
mTestableFlinger.setTransactionState(FrameTimelineInfo{}, states, displays, flags, applyToken,
InputWindowCommands{}, desiredPresentTime, isAutoTimestamp,
- {}, hasListenerCallbacks, listenerCallbacks,
- transactionId);
+ {}, hasListenerCallbacks, listenerCallbacks, transactionId,
+ mergedTransactionIds);
}
void SurfaceFlingerFuzzer::setDisplayStateLocked() {
@@ -238,7 +235,8 @@ void SurfaceFlingerFuzzer::process(const uint8_t *data, size_t size) {
mTestableFlinger.enableHalVirtualDisplays(mFdp.ConsumeBool());
- mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral<uint32_t>());
+ FTL_FAKE_GUARD(kMainThreadContext,
+ mTestableFlinger.commitTransactionsLocked(mFdp.ConsumeIntegral<uint32_t>()));
mTestableFlinger.notifyPowerBoost(mFdp.ConsumeIntegral<int32_t>());
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
index 867a1985bd..81fbc847a3 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_fuzzers_utils.h
@@ -30,19 +30,16 @@
#include <ui/DisplayStatInfo.h>
#include <ui/DynamicDisplayInfo.h>
-#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<int64_t>()};
+}
+
+inline TimePoint getFuzzedTimePoint(FuzzedDataProvider& fdp) {
+ return TimePoint::fromNs(fdp.ConsumeIntegral<nsecs_t>());
+}
+
+inline Duration getFuzzedDuration(FuzzedDataProvider& fdp) {
+ return Duration::fromNs(fdp.ConsumeIntegral<nsecs_t>());
+}
+
+inline FloatRect getFuzzedFloatRect(FuzzedDataProvider* fdp) {
return FloatRect(fdp->ConsumeFloatingPoint<float>() /*left*/,
fdp->ConsumeFloatingPoint<float>() /*right*/,
fdp->ConsumeFloatingPoint<float>() /*top*/,
fdp->ConsumeFloatingPoint<float>() /*bottom*/);
}
-HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider *fdp) {
+inline HdrMetadata getFuzzedHdrMetadata(FuzzedDataProvider* fdp) {
HdrMetadata hdrMetadata;
if (fdp->ConsumeBool()) {
hdrMetadata.cta8613.maxContentLightLevel = fdp->ConsumeFloatingPoint<float>();
@@ -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,35 @@ namespace scheduler {
class TestableScheduler : public Scheduler, private ICompositor {
public:
- TestableScheduler(const std::shared_ptr<scheduler::RefreshRateConfigs> &refreshRateConfigs,
- ISchedulerCallback &callback)
+ TestableScheduler(const std::shared_ptr<scheduler::RefreshRateSelector>& selectorPtr,
+ sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback,
+ IVsyncTrackerCallback& vsyncTrackerCallback)
: TestableScheduler(std::make_unique<android::mock::VsyncController>(),
- std::make_unique<android::mock::VSyncTracker>(), refreshRateConfigs,
- callback) {}
+ std::make_shared<android::mock::VSyncTracker>(), selectorPtr,
+ std::move(modulatorPtr), callback, vsyncTrackerCallback) {}
TestableScheduler(std::unique_ptr<VsyncController> controller,
- std::unique_ptr<VSyncTracker> tracker,
- std::shared_ptr<RefreshRateConfigs> 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<RefreshRateSelector> selectorPtr,
+ sp<VsyncModulator> modulatorPtr, ISchedulerCallback& callback,
+ IVsyncTrackerCallback& vsyncTrackerCallback)
+ : Scheduler(*this, callback, Feature::kContentDetection, std::move(modulatorPtr),
+ vsyncTrackerCallback) {
+ const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
+ registerDisplayInternal(displayId, std::move(selectorPtr),
+ std::shared_ptr<VsyncSchedule>(
+ new VsyncSchedule(displayId, std::move(tracker),
+ std::make_shared<FuzzImplVSyncDispatch>(),
+ std::move(controller))));
}
ConnectionHandle createConnection(std::unique_ptr<EventThread> 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,19 +280,25 @@ 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(PhysicalDisplayId, const scheduler::FrameTargets&) override { return false; }
+ CompositeResultsPerDisplay composite(PhysicalDisplayId,
+ const scheduler::FrameTargeters&) override {
+ return {};
+ }
void sample() override {}
// MessageQueue overrides:
void scheduleFrame() override {}
- void postMessage(sp<MessageHandler>&&) override {}
+ void postMessage(sp<MessageHandler>&& handler) override { handler->handleMessage(Message()); }
};
} // namespace scheduler
@@ -286,13 +306,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<HWComposer> createHWComposer(const std::string &) override { return nullptr; }
+ std::unique_ptr<HWComposer> createHWComposer(const std::string&) override { return nullptr; }
- std::unique_ptr<MessageQueue> createMessageQueue(ICompositor &compositor) {
- return std::make_unique<android::impl::MessageQueue>(compositor);
+ std::unique_ptr<MessageQueue> createMessageQueue(ICompositor& compositor) {
+ return std::make_unique<NoOpMessageQueue>(compositor);
}
std::unique_ptr<scheduler::VsyncConfiguration> createVsyncConfiguration(
@@ -301,27 +326,23 @@ public:
}
std::unique_ptr<scheduler::Scheduler> createScheduler(
- const std::shared_ptr<scheduler::RefreshRateConfigs> &,
- scheduler::ISchedulerCallback &) {
+ const std::shared_ptr<scheduler::RefreshRateSelector>&,
+ scheduler::ISchedulerCallback&) {
return nullptr;
}
- sp<SurfaceInterceptor> createSurfaceInterceptor() override {
- return new android::impl::SurfaceInterceptor();
- }
-
sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
- return new StartPropertySetThread(timestampPropertyValue);
+ return sp<StartPropertySetThread>::make(timestampPropertyValue);
}
sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs &creationArgs) override {
- return new DisplayDevice(creationArgs);
+ return sp<DisplayDevice>::make(creationArgs);
}
sp<GraphicBuffer> 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<GraphicBuffer>::make(width, height, format, layerCount, usage, requestorName);
}
void createBufferQueue(sp<IGraphicBufferProducer> *outProducer,
@@ -334,18 +355,6 @@ public:
mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
}
- sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer> &producer,
- const sp<SurfaceFlinger> &flinger,
- const wp<Layer> &layer) override {
- return new MonitoredProducer(producer, flinger, layer);
- }
-
- sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer> &consumer,
- renderengine::RenderEngine &renderEngine,
- uint32_t textureName, Layer *layer) override {
- return new BufferLayerConsumer(consumer, renderEngine, textureName, layer);
- }
-
std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
const sp<IGraphicBufferProducer> &producer) override {
if (!mCreateNativeWindowSurface) return nullptr;
@@ -356,20 +365,14 @@ public:
return compositionengine::impl::createCompositionEngine();
}
- sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs &) override {
- return nullptr;
- }
+ sp<Layer> createBufferStateLayer(const LayerCreationArgs &) override { return nullptr; }
- sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs &) override {
- return nullptr;
+ sp<Layer> createEffectLayer(const LayerCreationArgs &args) override {
+ return sp<Layer>::make(args);
}
- sp<EffectLayer> createEffectLayer(const LayerCreationArgs &args) override {
- return new EffectLayer(args);
- }
-
- sp<ContainerLayer> createContainerLayer(const LayerCreationArgs &args) override {
- return new ContainerLayer(args);
+ sp<LayerFE> createLayerFE(const std::string &layerName) override {
+ return sp<LayerFE>::make(layerName);
}
std::unique_ptr<FrameTracer> createFrameTracer() override {
@@ -400,16 +403,16 @@ public:
} // namespace surfaceflinger::test
// TODO(b/189053744) : Create a common test/mock library for surfaceflinger
-class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback {
+class TestableSurfaceFlinger final : private scheduler::ISchedulerCallback,
+ private scheduler::IVsyncTrackerCallback {
public:
using HotplugEvent = SurfaceFlinger::HotplugEvent;
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 +433,7 @@ public:
void onPullAtom(FuzzedDataProvider *fdp) {
const int32_t atomId = fdp->ConsumeIntegral<uint8_t>();
- std::string pulledData = fdp->ConsumeRandomLengthString().c_str();
+ std::vector<uint8_t> pulledData = fdp->ConsumeRemainingBytes<uint8_t>();
bool success = fdp->ConsumeBool();
mFlinger->onPullAtom(atomId, &pulledData, &success);
}
@@ -447,21 +450,19 @@ 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<nsecs_t>())));
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<uint32_t>());
+ perfetto::protos::LayersProto layersProto =
+ mFlinger->dumpDrawingStateProto(fdp->ConsumeIntegral<uint32_t>());
mFlinger->dumpOffscreenLayersProto(layersProto);
- LayersTraceProto layersTraceProto{};
- mFlinger->dumpDisplayProto(layersTraceProto);
+ mFlinger->dumpDisplayProto();
result = fdp->ConsumeRandomLengthString().c_str();
mFlinger->dumpHwc(result);
@@ -469,10 +470,6 @@ public:
mFlinger->calculateColorMatrix(fdp->ConsumeFloatingPoint<float>());
mFlinger->updateColorMatrixLocked();
mFlinger->CheckTransactCodeCredentials(fdp->ConsumeIntegral<uint32_t>());
-
- const CountDownLatch transactionCommittedSignal(fdp->ConsumeIntegral<uint32_t>());
- mFlinger->waitForSynchronousTransaction(transactionCommittedSignal);
- mFlinger->signalSynchronousTransactions(fdp->ConsumeIntegral<uint32_t>());
}
void getCompositionPreference() {
@@ -485,74 +482,58 @@ public:
&outWideColorGamutPixelFormat);
}
- void overrideHdrTypes(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+ void overrideHdrTypes(const sp<IBinder>& display, FuzzedDataProvider* fdp) {
std::vector<ui::Hdr> hdrTypes;
hdrTypes.push_back(fdp->PickValueInArray(kHdrTypes));
mFlinger->overrideHdrTypes(display, hdrTypes);
}
- void getDisplayedContentSample(sp<IBinder> &display, FuzzedDataProvider *fdp) {
+ void getDisplayedContentSample(const sp<IBinder>& display, FuzzedDataProvider* fdp) {
DisplayedFrameStats outDisplayedFrameStats;
mFlinger->getDisplayedContentSample(display, fdp->ConsumeIntegral<uint64_t>(),
fdp->ConsumeIntegral<uint64_t>(),
&outDisplayedFrameStats);
}
- void getDisplayStats(sp<IBinder> &display) {
+ void getDisplayStats(const sp<IBinder>& display) {
android::DisplayStatInfo stats;
mFlinger->getDisplayStats(display, &stats);
}
- void getDisplayState(sp<IBinder> &display) {
+ void getDisplayState(const sp<IBinder>& display) {
ui::DisplayState displayState;
mFlinger->getDisplayState(display, &displayState);
}
- void getStaticDisplayInfo(sp<IBinder> &display) {
+ void getStaticDisplayInfo(int64_t displayId) {
ui::StaticDisplayInfo staticDisplayInfo;
- mFlinger->getStaticDisplayInfo(display, &staticDisplayInfo);
+ mFlinger->getStaticDisplayInfo(displayId, &staticDisplayInfo);
}
- void getDynamicDisplayInfo(sp<IBinder> &display) {
+ void getDynamicDisplayInfo(int64_t displayId) {
android::ui::DynamicDisplayInfo dynamicDisplayInfo;
- mFlinger->getDynamicDisplayInfo(display, &dynamicDisplayInfo);
+ mFlinger->getDynamicDisplayInfoFromId(displayId, &dynamicDisplayInfo);
}
- void getDisplayNativePrimaries(sp<IBinder> &display) {
+ void getDisplayNativePrimaries(const sp<IBinder>& display) {
android::ui::DisplayPrimaries displayPrimaries;
mFlinger->getDisplayNativePrimaries(display, displayPrimaries);
}
- void getDesiredDisplayModeSpecs(sp<IBinder> &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<nsecs_t>());
- }
-
- void updateCompositorTiming(FuzzedDataProvider *fdp) {
- std::shared_ptr<FenceTime> presentFenceTime = FenceTime::NO_FENCE;
- mFlinger->updateCompositorTiming({}, fdp->ConsumeIntegral<nsecs_t>(), presentFenceTime);
+ void getDesiredDisplayModeSpecs(const sp<IBinder>& display) {
+ gui::DisplayModeSpecs _;
+ mFlinger->getDesiredDisplayModeSpecs(display, &_);
}
- void getCompositorTiming() {
- CompositorTiming compositorTiming;
- mFlinger->getCompositorTiming(&compositorTiming);
+ // TODO(b/248317436): extend to cover all displays for multi-display devices
+ static std::optional<PhysicalDisplayId> getFirstDisplayId() {
+ std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ if (ids.empty()) return {};
+ return ids.front();
}
- sp<IBinder> fuzzBoot(FuzzedDataProvider *fdp) {
+ std::pair<sp<IBinder>, PhysicalDisplayId> fuzzBoot(FuzzedDataProvider* fdp) {
mFlinger->callingThreadHasUnscopedSurfaceFlingerAccess(fdp->ConsumeBool());
- mFlinger->createConnection();
+ const sp<Client> client = sp<Client>::make(mFlinger);
DisplayIdGenerator<HalVirtualDisplayId> kGenerator;
HalVirtualDisplayId halVirtualDisplayId = kGenerator.generateId().value();
@@ -561,14 +542,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<uint8_t>()));
mFlinger->getHwComposer().allocatePhysicalDisplay(kHwDisplayId, physicalDisplayId);
sp<IBinder> display =
mFlinger->createDisplay(String8(fdp->ConsumeRandomLengthString().c_str()),
fdp->ConsumeBool());
- onInitializeDisplays();
+ initializeDisplays();
mFlinger->getPhysicalDisplayToken(physicalDisplayId);
mFlinger->mStartPropertySetThread =
@@ -576,36 +558,32 @@ public:
mFlinger->bootFinished();
- return display;
+ return {display, physicalDisplayId};
}
void fuzzSurfaceFlinger(const uint8_t *data, size_t size) {
FuzzedDataProvider mFdp(data, size);
- sp<IBinder> display = fuzzBoot(&mFdp);
+ const auto [display, displayId] = fuzzBoot(&mFdp);
sp<IGraphicBufferProducer> bufferProducer = sp<mock::GraphicBufferProducer>::make();
- mFlinger->authenticateSurfaceTexture(bufferProducer.get());
mFlinger->createDisplayEventConnection();
getDisplayStats(display);
getDisplayState(display);
- getStaticDisplayInfo(display);
- getDynamicDisplayInfo(display);
+ getStaticDisplayInfo(displayId.value);
+ getDynamicDisplayInfo(displayId.value);
getDisplayNativePrimaries(display);
mFlinger->setAutoLowLatencyMode(display, mFdp.ConsumeBool());
mFlinger->setGameContentType(display, mFdp.ConsumeBool());
mFlinger->setPowerMode(display, mFdp.ConsumeIntegral<int>());
- mFlinger->clearAnimationFrameStats();
overrideHdrTypes(display, &mFdp);
onPullAtom(&mFdp);
- mFlinger->injectVSync(mFdp.ConsumeIntegral<nsecs_t>());
-
getCompositionPreference();
getDisplayedContentSample(display, &mFdp);
getDesiredDisplayModeSpecs(display);
@@ -620,19 +598,31 @@ public:
mFlinger->binderDied(display);
mFlinger->onFirstRef();
- mFlinger->commitTransactions();
- mFlinger->updateInputFlinger();
+ mFlinger->updateInputFlinger(VsyncId{}, TimePoint{});
mFlinger->updateCursorAsync();
- setVsyncConfig(&mFdp);
+ mutableScheduler().setVsyncConfig({.sfOffset = mFdp.ConsumeIntegral<nsecs_t>(),
+ .appOffset = mFdp.ConsumeIntegral<nsecs_t>(),
+ .sfWorkDuration = getFuzzedDuration(mFdp),
+ .appWorkDuration = getFuzzedDuration(mFdp)},
+ getFuzzedDuration(mFdp));
+
+ {
+ ftl::FakeGuard guard(kMainThreadContext);
- mFlinger->flushTransactionQueues(0);
+ mFlinger->commitTransactionsLegacy();
+ mFlinger->flushTransactionQueues(getFuzzedVsyncId(mFdp));
+
+ scheduler::FrameTargeter frameTargeter(displayId, mFdp.ConsumeBool());
+ mFlinger->onCompositionPresented(displayId, ftl::init::map(displayId, &frameTargeter),
+ mFdp.ConsumeIntegral<nsecs_t>());
+ }
mFlinger->setTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
mFlinger->clearTransactionFlags(mFdp.ConsumeIntegral<uint32_t>());
mFlinger->commitOffscreenLayers();
- mFlinger->frameIsEarly(mFdp.ConsumeIntegral<nsecs_t>(), mFdp.ConsumeIntegral<int64_t>());
+ mFlinger->frameIsEarly(getFuzzedTimePoint(mFdp), getFuzzedVsyncId(mFdp));
mFlinger->computeLayerBounds();
mFlinger->startBootAnim();
@@ -643,16 +633,6 @@ public:
mFlinger->getMaxAcquiredBufferCountForCurrentRefreshRate(mFdp.ConsumeIntegral<uid_t>());
- mFlinger->postComposition();
-
- getCompositorTiming();
-
- updateCompositorTiming(&mFdp);
-
- mFlinger->setCompositorTimingSnapped({}, mFdp.ConsumeIntegral<nsecs_t>());
- FTL_FAKE_GUARD(kMainThreadContext, mFlinger->postFrame());
- mFlinger->calculateExpectedPresentTime({});
-
mFlinger->enableHalVirtualDisplays(mFdp.ConsumeBool());
fuzzDumpsysAndDebug(&mFdp);
@@ -661,7 +641,8 @@ public:
}
void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
- mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+ mFlinger->mRenderEngine = std::move(renderEngine);
+ mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
}
void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
@@ -675,10 +656,11 @@ public:
// The ISchedulerCallback argument can be nullptr for a no-op implementation.
void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
- std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
+ std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
std::unique_ptr<EventThread> appEventThread,
std::unique_ptr<EventThread> sfEventThread,
- scheduler::ISchedulerCallback *callback = nullptr,
+ scheduler::ISchedulerCallback* callback = nullptr,
+ scheduler::IVsyncTrackerCallback* vsyncTrackerCallback = nullptr,
bool hasMultipleModes = false) {
constexpr DisplayModeId kModeId60{0};
DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
@@ -688,18 +670,21 @@ public:
modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
}
- mRefreshRateConfigs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
- const auto fps = mRefreshRateConfigs->getActiveMode()->getFps();
+ mRefreshRateSelector = std::make_shared<scheduler::RefreshRateSelector>(modes, kModeId60);
+ const auto fps = mRefreshRateSelector->getActiveMode().modePtr->getVsyncRate();
mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
- mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
- mFlinger->mVsyncConfiguration->getCurrentConfigs());
+
mFlinger->mRefreshRateStats =
std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
hal::PowerMode::OFF);
+ auto modulatorPtr = sp<scheduler::VsyncModulator>::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),
+ *(vsyncTrackerCallback ?: this));
mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
@@ -740,9 +725,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 +743,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<ComposerState> &states,
- const Vector<DisplayState> &displays, uint32_t flags, const sp<IBinder> &applyToken,
- const InputWindowCommands &inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t &uncacheBuffer, bool hasListenerCallbacks,
- std::vector<ListenerCallbacks> &listenerCallbacks, uint64_t transactionId) {
+ const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
+ const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
+ const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
+ bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
+ uint64_t transactionId, const std::vector<uint64_t>& 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 +781,38 @@ 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<IBinder> &handle) { return mFlinger->fromHandle(handle); }
+ auto fromHandle(const sp<IBinder> &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<HWComposer>());
- mFlinger->mCompositionEngine->setRenderEngine(
- std::unique_ptr<renderengine::RenderEngine>());
+ mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
+ mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
}
private:
- void setVsyncEnabled(bool) override {}
- void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {}
+ void requestHardwareVsync(PhysicalDisplayId, bool) override {}
+ void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
void kernelTimerChanged(bool) override {}
void triggerOnFrameRateOverridesChanged() override {}
+ void onChoreographerAttached() override {}
+
+ // IVsyncTrackerCallback overrides
+ void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
surfaceflinger::test::Factory mFactory;
- sp<SurfaceFlinger> mFlinger = new SurfaceFlinger(mFactory, SurfaceFlinger::SkipInitialization);
+ sp<SurfaceFlinger> mFlinger =
+ sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
scheduler::TestableScheduler *mScheduler = nullptr;
- std::shared_ptr<scheduler::RefreshRateConfigs> mRefreshRateConfigs;
+ std::shared_ptr<scheduler::RefreshRateSelector> mRefreshRateSelector;
};
} // namespace android
diff --git a/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_layer_fuzzer.cpp
index 34cf90628a..7aae3c4a1a 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 <BufferStateLayer.h>
#include <Client.h>
#include <DisplayDevice.h>
-#include <EffectLayer.h>
-#include <LayerRejecter.h>
#include <LayerRenderArea.h>
-#include <MonitoredProducer.h>
#include <ftl/future.h>
#include <fuzzer/FuzzedDataProvider.h>
#include <gui/IProducerListener.h>
@@ -29,6 +25,7 @@
#include <gui/WindowInfo.h>
#include <renderengine/mock/FakeExternalTexture.h>
#include <ui/DisplayStatInfo.h>
+#include <ui/Transform.h>
#include <FuzzableDataspaces.h>
#include <surfaceflinger_fuzzers_utils.h>
@@ -46,6 +43,7 @@ public:
void invokeEffectLayer();
LayerCreationArgs createLayerCreationArgs(TestableSurfaceFlinger* flinger, sp<Client> client);
Rect getFuzzedRect();
+ ui::Transform getFuzzedTransform();
FrameTimelineInfo getFuzzedFrameTimelineInfo();
private:
@@ -58,9 +56,17 @@ Rect LayerFuzzer::getFuzzedRect() {
mFdp.ConsumeIntegral<int32_t>() /*bottom*/);
}
+ui::Transform LayerFuzzer::getFuzzedTransform() {
+ return ui::Transform(mFdp.ConsumeIntegral<int32_t>() /*orientation*/,
+ mFdp.ConsumeIntegral<int32_t>() /*width*/,
+ mFdp.ConsumeIntegral<int32_t>() /*height*/);
+}
+
FrameTimelineInfo LayerFuzzer::getFuzzedFrameTimelineInfo() {
- return FrameTimelineInfo{.vsyncId = mFdp.ConsumeIntegral<int64_t>(),
- .inputEventId = mFdp.ConsumeIntegral<int32_t>()};
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = mFdp.ConsumeIntegral<int64_t>();
+ ftInfo.inputEventId = mFdp.ConsumeIntegral<int32_t>();
+ return ftInfo;
}
LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* flinger,
@@ -77,15 +83,15 @@ LayerCreationArgs LayerFuzzer::createLayerCreationArgs(TestableSurfaceFlinger* f
void LayerFuzzer::invokeEffectLayer() {
TestableSurfaceFlinger flinger;
- sp<Client> client = sp<Client>::make(flinger.flinger());
+ sp<Client> client = sp<Client>::make(sp<SurfaceFlinger>::fromExisting(flinger.flinger()));
const LayerCreationArgs layerCreationArgs = createLayerCreationArgs(&flinger, client);
- sp<EffectLayer> effectLayer = sp<EffectLayer>::make(layerCreationArgs);
+ sp<Layer> effectLayer = sp<Layer>::make(layerCreationArgs);
effectLayer->setColor({(mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*x*/,
mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*y*/,
mFdp.ConsumeFloatingPointInRange<float>(0, 255) /*z*/)});
effectLayer->setDataspace(mFdp.PickValueInArray(kDataspaces));
- sp<EffectLayer> parent = sp<EffectLayer>::make(layerCreationArgs);
+ sp<Layer> parent = sp<Layer>::make(layerCreationArgs);
effectLayer->setChildrenDrawingParent(parent);
const FrameTimelineInfo frameInfo = getFuzzedFrameTimelineInfo();
@@ -100,7 +106,7 @@ void LayerFuzzer::invokeEffectLayer() {
effectLayer->addSurfaceFramePresentedForBuffer(surfaceFrame,
mFdp.ConsumeIntegral<int64_t>() /*acquireTime*/,
mFdp.ConsumeIntegral<int64_t>() /*currentTime*/);
- effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1);
+ effectLayer->addSurfaceFrameDroppedForBuffer(surfaceFrame1, mFdp.ConsumeIntegral<nsecs_t>());
parent.clear();
client.clear();
@@ -109,24 +115,25 @@ void LayerFuzzer::invokeEffectLayer() {
void LayerFuzzer::invokeBufferStateLayer() {
TestableSurfaceFlinger flinger;
- sp<Client> client = sp<Client>::make(flinger.flinger());
- sp<BufferStateLayer> layer =
- sp<BufferStateLayer>::make(createLayerCreationArgs(&flinger, client));
+ sp<Client> client = sp<Client>::make(sp<SurfaceFlinger>::fromExisting(flinger.flinger()));
+ sp<Layer> layer = sp<Layer>::make(createLayerCreationArgs(&flinger, client));
sp<Fence> fence = sp<Fence>::make();
const std::shared_ptr<FenceTime> fenceTime = std::make_shared<FenceTime>(fence);
- const CompositorTiming compositor = {mFdp.ConsumeIntegral<int64_t>(),
- mFdp.ConsumeIntegral<int64_t>(),
- mFdp.ConsumeIntegral<int64_t>()};
+ const CompositorTiming compositorTiming(mFdp.ConsumeIntegral<int64_t>(),
+ mFdp.ConsumeIntegral<int64_t>(),
+ mFdp.ConsumeIntegral<int64_t>(),
+ mFdp.ConsumeIntegral<int64_t>());
- layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share());
- layer->onLayerDisplayed(
- ftl::yield<FenceResult>(base::unexpected(mFdp.ConsumeIntegral<status_t>())).share());
+ layer->onLayerDisplayed(ftl::yield<FenceResult>(fence).share(),
+ ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
+ layer->onLayerDisplayed(ftl::yield<FenceResult>(
+ base::unexpected(mFdp.ConsumeIntegral<status_t>()))
+ .share(),
+ ui::LayerStack::fromValue(mFdp.ConsumeIntegral<uint32_t>()));
layer->releasePendingBuffer(mFdp.ConsumeIntegral<int64_t>());
- layer->finalizeFrameEventHistory(fenceTime, compositor);
- layer->onPostComposition(nullptr, fenceTime, fenceTime, compositor);
- layer->isBufferDue(mFdp.ConsumeIntegral<int64_t>());
+ layer->onCompositionPresented(nullptr, fenceTime, fenceTime, compositorTiming);
layer->setTransform(mFdp.ConsumeIntegral<uint32_t>());
layer->setTransformToDisplayInverse(mFdp.ConsumeBool());
@@ -146,14 +153,14 @@ void LayerFuzzer::invokeBufferStateLayer() {
native_handle_t* testHandle = native_handle_create(0, 1);
const bool ownsHandle = mFdp.ConsumeBool();
sp<NativeHandle> nativeHandle = sp<NativeHandle>::make(testHandle, ownsHandle);
- layer->setSidebandStream(nativeHandle);
+ layer->setSidebandStream(nativeHandle, getFuzzedFrameTimelineInfo(),
+ mFdp.ConsumeIntegral<nsecs_t>() /* postTime */);
layer->computeSourceBounds(getFuzzedFloatRect(&mFdp));
layer->fenceHasSignaled();
- layer->framePresentTimeIsCurrent(mFdp.ConsumeIntegral<int64_t>());
layer->onPreComposition(mFdp.ConsumeIntegral<int64_t>());
const std::vector<sp<CallbackHandle>> callbacks;
- layer->setTransactionCompletedListeners(callbacks);
+ layer->setTransactionCompletedListeners(callbacks, mFdp.ConsumeBool());
std::shared_ptr<renderengine::ExternalTexture> texture = std::make_shared<
renderengine::mock::FakeExternalTexture>(mFdp.ConsumeIntegral<uint32_t>(),
@@ -171,7 +178,8 @@ void LayerFuzzer::invokeBufferStateLayer() {
{mFdp.ConsumeIntegral<int32_t>(),
mFdp.ConsumeIntegral<int32_t>()} /*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 da60a6981b..b690d8d98e 100644
--- a/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_scheduler_fuzzer.cpp
@@ -19,12 +19,19 @@
#include <fuzzer/FuzzedDataProvider.h>
#include <processgroup/sched_policy.h>
-#include "Scheduler/DispSyncSource.h"
+#include <scheduler/IVsyncSource.h>
+#include <scheduler/PresentLatencyTracker.h>
+
#include "Scheduler/OneShotTimer.h"
+#include "Scheduler/RefreshRateSelector.h"
#include "Scheduler/VSyncDispatchTimerQueue.h"
#include "Scheduler/VSyncPredictor.h"
#include "Scheduler/VSyncReactor.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+
#include "surfaceflinger_fuzzers_utils.h"
#include "surfaceflinger_scheduler_fuzzer.h"
@@ -36,13 +43,15 @@ 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<scheduler::RefreshRateConfigs::LayerVoteType>();
+constexpr auto kLayerVoteTypes = ftl::enum_range<scheduler::RefreshRateSelector::LayerVoteType>();
+constexpr auto kCompositionCoverage = ftl::enum_range<CompositionCoverage>();
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 kDisplayId = PhysicalDisplayId::fromPort(42u);
template <typename T>
void dump(T* component, FuzzedDataProvider* fdp) {
@@ -50,19 +59,24 @@ void dump(T* component, FuzzedDataProvider* fdp) {
component->dump(res);
}
-class SchedulerFuzzer : private VSyncSource::Callback {
+inline sp<Fence> makeFakeFence() {
+ return sp<Fence>::make(memfd_create("fd", MFD_ALLOW_SEALING));
+}
+
+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 fuzzFrameTargeter();
void fuzzVSyncModulator();
void fuzzVSyncPredictor();
void fuzzVSyncReactor();
void fuzzLayerHistory();
- void fuzzDispSyncSource();
void fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch);
void fuzzVSyncDispatchTimerQueue();
void fuzzOneShotTimer();
@@ -71,8 +85,7 @@ private:
FuzzedDataProvider mFdp;
-protected:
- void onVSyncEvent(nsecs_t /* when */, VSyncSource::VSyncData) {}
+ std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
};
PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
@@ -85,45 +98,36 @@ PhysicalDisplayId SchedulerFuzzer::getPhysicalDisplayId() {
return displayId;
}
+struct EventThreadCallback : public IEventThreadCallback {
+ bool throttleVsync(TimePoint, uid_t) override { return false; }
+ Period getVsyncPeriod(uid_t) override { return kSyncPeriod; }
+ void resync() override {}
+};
+
void SchedulerFuzzer::fuzzEventThread() {
- const auto getVsyncPeriod = [](uid_t /* uid */) { return kSyncPeriod.count(); };
+ mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
+ new scheduler::VsyncSchedule(getPhysicalDisplayId(),
+ std::make_shared<mock::VSyncTracker>(),
+ std::make_shared<mock::VSyncDispatch>(), nullptr));
+ EventThreadCallback callback;
std::unique_ptr<android::impl::EventThread> thread = std::make_unique<
- android::impl::EventThread>(std::move(std::make_unique<FuzzImplVSyncSource>()), nullptr,
- nullptr, nullptr, getVsyncPeriod);
+ android::impl::EventThread>("fuzzer", mVsyncSchedule, nullptr, callback,
+ (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
+ (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
thread->onHotplugReceived(getPhysicalDisplayId(), mFdp.ConsumeBool());
sp<EventThreadConnection> connection =
- new EventThreadConnection(thread.get(), mFdp.ConsumeIntegral<uint16_t>(), nullptr,
- {} /*eventRegistration*/);
+ sp<EventThreadConnection>::make(thread.get(), mFdp.ConsumeIntegral<uint16_t>());
thread->requestNextVsync(connection);
thread->setVsyncRate(mFdp.ConsumeIntegral<uint32_t>() /*rate*/, connection);
thread->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
(std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
thread->registerDisplayEventConnection(connection);
- thread->onScreenAcquired();
- thread->onScreenReleased();
+ thread->enableSyntheticVsync(mFdp.ConsumeBool());
dump<android::impl::EventThread>(thread.get(), &mFdp);
}
-void SchedulerFuzzer::fuzzDispSyncSource() {
- std::unique_ptr<FuzzImplVSyncDispatch> vSyncDispatch =
- std::make_unique<FuzzImplVSyncDispatch>();
- std::unique_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_unique<FuzzImplVSyncTracker>();
- std::unique_ptr<scheduler::DispSyncSource> dispSyncSource = std::make_unique<
- scheduler::DispSyncSource>(*vSyncDispatch, *vSyncTracker,
- (std::chrono::nanoseconds)
- mFdp.ConsumeIntegral<uint64_t>() /*workDuration*/,
- (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>()
- /*readyDuration*/,
- mFdp.ConsumeBool(),
- mFdp.ConsumeRandomLengthString(kRandomStringLength).c_str());
- dispSyncSource->setVSyncEnabled(true);
- dispSyncSource->setCallback(this);
- dispSyncSource->setDuration((std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(), 0ns);
- dump<scheduler::DispSyncSource>(dispSyncSource.get(), &mFdp);
-}
-
void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* dispatch) {
scheduler::VSyncDispatch::CallbackToken tmp = dispatch->registerCallback(
[&](auto, auto, auto) {
@@ -142,7 +146,7 @@ void SchedulerFuzzer::fuzzCallbackToken(scheduler::VSyncDispatchTimerQueue* disp
}
void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() {
- FuzzImplVSyncTracker stubTracker{mFdp.ConsumeIntegral<nsecs_t>()};
+ auto stubTracker = std::make_shared<FuzzImplVSyncTracker>(mFdp.ConsumeIntegral<nsecs_t>());
scheduler::VSyncDispatchTimerQueue
mDispatch{std::make_unique<scheduler::ControllableClock>(), stubTracker,
mFdp.ConsumeIntegral<nsecs_t>() /*dispatchGroupThreshold*/,
@@ -155,17 +159,17 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() {
scheduler::VSyncDispatchTimerQueueEntry entry(
"fuzz", [](auto, auto, auto) {},
mFdp.ConsumeIntegral<nsecs_t>() /*vSyncMoveThreshold*/);
- entry.update(stubTracker, 0);
+ entry.update(*stubTracker, 0);
entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
.readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
.earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
- stubTracker, 0);
+ *stubTracker, 0);
entry.disarm();
entry.ensureNotRunning();
entry.schedule({.workDuration = mFdp.ConsumeIntegral<nsecs_t>(),
.readyDuration = mFdp.ConsumeIntegral<nsecs_t>(),
.earliestVsync = mFdp.ConsumeIntegral<nsecs_t>()},
- stubTracker, 0);
+ *stubTracker, 0);
auto const wakeup = entry.wakeupTime();
auto const ready = entry.readyTime();
entry.callback(entry.executing(), *wakeup, *ready);
@@ -175,15 +179,24 @@ void SchedulerFuzzer::fuzzVSyncDispatchTimerQueue() {
dump<scheduler::VSyncDispatchTimerQueueEntry>(&entry, &mFdp);
}
+struct VsyncTrackerCallback : public scheduler::IVsyncTrackerCallback {
+ void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override {}
+};
+
void SchedulerFuzzer::fuzzVSyncPredictor() {
uint16_t now = mFdp.ConsumeIntegral<uint16_t>();
uint16_t historySize = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
uint16_t minimumSamplesForPrediction = mFdp.ConsumeIntegralInRange<uint16_t>(1, UINT16_MAX);
- scheduler::VSyncPredictor tracker{mFdp.ConsumeIntegral<uint16_t>() /*period*/, historySize,
- minimumSamplesForPrediction,
- mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/};
+ nsecs_t idealPeriod = mFdp.ConsumeIntegralInRange<nsecs_t>(1, UINT32_MAX);
+ VsyncTrackerCallback callback;
+ const auto mode = ftl::as_non_null(
+ mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(idealPeriod)));
+ scheduler::VSyncPredictor tracker{mode, historySize, minimumSamplesForPrediction,
+ mFdp.ConsumeIntegral<uint32_t>() /*outlierTolerancePercent*/,
+ callback};
uint16_t period = mFdp.ConsumeIntegral<uint16_t>();
- tracker.setPeriod(period);
+ tracker.setDisplayModePtr(ftl::as_non_null(
+ mock::createDisplayMode(DisplayModeId(0), Fps::fromPeriodNsecs(period))));
for (uint16_t i = 0; i < minimumSamplesForPrediction; ++i) {
if (!tracker.needsMoreSamples()) {
break;
@@ -224,19 +237,19 @@ void SchedulerFuzzer::fuzzLayerHistory() {
nsecs_t time2 = time1;
uint8_t historySize = mFdp.ConsumeIntegral<uint8_t>();
- sp<FuzzImplLayer> layer1 = new FuzzImplLayer(flinger.flinger());
- sp<FuzzImplLayer> layer2 = new FuzzImplLayer(flinger.flinger());
+ sp<FuzzImplLayer> layer1 = sp<FuzzImplLayer>::make(flinger.flinger());
+ sp<FuzzImplLayer> layer2 = sp<FuzzImplLayer>::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<android::mock::EventThread>());
@@ -245,28 +258,35 @@ void SchedulerFuzzer::fuzzLayerHistory() {
scheduler->setDuration(handle, (std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>(),
(std::chrono::nanoseconds)mFdp.ConsumeIntegral<uint64_t>());
- dump<scheduler::TestableScheduler>(scheduler, &mFdp);
+ std::string result = mFdp.ConsumeRandomLengthString(kRandomStringLength);
+ utils::Dumper dumper(result);
+ scheduler->dump(dumper);
}
void SchedulerFuzzer::fuzzVSyncReactor() {
std::shared_ptr<FuzzImplVSyncTracker> vSyncTracker = std::make_shared<FuzzImplVSyncTracker>();
- scheduler::VSyncReactor reactor(std::make_unique<ClockWrapper>(
+ scheduler::VSyncReactor reactor(kDisplayId,
+ std::make_unique<ClockWrapper>(
std::make_shared<FuzzImplClock>()),
*vSyncTracker, mFdp.ConsumeIntegral<uint8_t>() /*pendingLimit*/,
false);
- reactor.startPeriodTransition(mFdp.ConsumeIntegral<nsecs_t>());
- bool periodFlushed = mFdp.ConsumeBool();
+ const auto mode = ftl::as_non_null(
+ mock::createDisplayMode(DisplayModeId(0),
+ Fps::fromPeriodNsecs(mFdp.ConsumeIntegral<nsecs_t>())));
+ reactor.onDisplayModeChanged(mode, 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<nsecs_t>() /*newPeriod*/, std::nullopt,
&periodFlushed);
- sp<Fence> fence = new Fence(memfd_create("fd", MFD_ALLOW_SEALING));
- std::shared_ptr<FenceTime> ft = std::make_shared<FenceTime>(fence);
+
+ const auto fence = std::make_shared<FenceTime>(makeFakeFence());
vSyncTracker->addVsyncTimestamp(mFdp.ConsumeIntegral<nsecs_t>());
FenceTime::Snapshot snap(mFdp.ConsumeIntegral<nsecs_t>());
- ft->applyTrustedSnapshot(snap);
+ fence->applyTrustedSnapshot(snap);
reactor.setIgnorePresentFences(mFdp.ConsumeBool());
- reactor.addPresentFence(ft);
+ reactor.addPresentFence(fence);
dump<scheduler::VSyncReactor>(&reactor, &mFdp);
}
@@ -288,17 +308,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<FuzzImplVsyncModulator> vSyncModulator =
sp<FuzzImplVsyncModulator>::make(offsets, scheduler::Now);
(void)vSyncModulator->setVsyncConfigSet(offsets);
@@ -319,14 +336,14 @@ void SchedulerFuzzer::fuzzRefreshRateSelection() {
LayerCreationArgs args(flinger.flinger(), client,
mFdp.ConsumeRandomLengthString(kRandomStringLength) /*name*/,
mFdp.ConsumeIntegral<uint16_t>() /*layerFlags*/, LayerMetadata());
- sp<Layer> layer = new BufferQueueLayer(args);
+ sp<Layer> layer = sp<Layer>::make(args);
layer->setFrameRateSelectionPriority(mFdp.ConsumeIntegral<int16_t>());
}
-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<uint16_t>(1, UINT16_MAX >> 1);
@@ -342,54 +359,107 @@ void SchedulerFuzzer::fuzzRefreshRateConfigs() {
Fps::fromValue(static_cast<float>(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<LayerRequirement> layers = {{.weight = mFdp.ConsumeFloatingPoint<float>()}};
- refreshRateConfigs.getBestRefreshRate(layers, globalSignals);
+ refreshRateSelector.getRankedFrameRates(layers, globalSignals);
layers[0].name = mFdp.ConsumeRandomLengthString(kRandomStringLength);
layers[0].ownerUid = mFdp.ConsumeIntegral<uint16_t>();
layers[0].desiredRefreshRate = Fps::fromValue(mFdp.ConsumeFloatingPoint<float>());
layers[0].vote = mFdp.PickValueInArray(kLayerVoteTypes.values);
auto frameRateOverrides =
- refreshRateConfigs.getFrameRateOverrides(layers,
- Fps::fromValue(
+ refreshRateSelector.getFrameRateOverrides(layers,
+ Fps::fromValue(
+ mFdp.ConsumeFloatingPoint<float>()),
+ globalSignals);
+
+ {
+ ftl::FakeGuard guard(kMainThreadContext);
+
+ refreshRateSelector.setPolicy(
+ RefreshRateSelector::
+ DisplayManagerPolicy{modeId,
+ {Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
+ Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())}});
+ refreshRateSelector.setPolicy(
+ RefreshRateSelector::OverridePolicy{modeId,
+ {Fps::fromValue(
mFdp.ConsumeFloatingPoint<float>()),
- globalSignals);
+ Fps::fromValue(
+ mFdp.ConsumeFloatingPoint<float>())}});
+ refreshRateSelector.setPolicy(RefreshRateSelector::NoOverridePolicy{});
- refreshRateConfigs.setDisplayManagerPolicy(
- {modeId,
- {Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
- Fps::fromValue(mFdp.ConsumeFloatingPoint<float>())}});
- refreshRateConfigs.setActiveModeId(modeId);
+ refreshRateSelector.setActiveMode(modeId,
+ Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
+ }
- RefreshRateConfigs::isFractionalPairOrMultiple(Fps::fromValue(
- mFdp.ConsumeFloatingPoint<float>()),
- Fps::fromValue(
- mFdp.ConsumeFloatingPoint<float>()));
- RefreshRateConfigs::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
- Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
+ RefreshRateSelector::isFractionalPairOrMultiple(Fps::fromValue(
+ mFdp.ConsumeFloatingPoint<float>()),
+ Fps::fromValue(
+ mFdp.ConsumeFloatingPoint<float>()));
+ RefreshRateSelector::getFrameRateDivisor(Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
+ Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()));
android::mock::TimeStats timeStats;
RefreshRateStats refreshRateStats(timeStats, Fps::fromValue(mFdp.ConsumeFloatingPoint<float>()),
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->getVsyncRate(); });
refreshRateStats.setRefreshRate(*fpsOpt);
refreshRateStats.setPowerMode(mFdp.PickValueInArray(kPowerModes));
}
+void SchedulerFuzzer::fuzzPresentLatencyTracker() {
+ scheduler::PresentLatencyTracker tracker;
+
+ int i = 5;
+ while (i-- > 0) {
+ tracker.trackPendingFrame(getFuzzedTimePoint(mFdp),
+ std::make_shared<FenceTime>(makeFakeFence()));
+ }
+}
+
+void SchedulerFuzzer::fuzzFrameTargeter() {
+ scheduler::FrameTargeter frameTargeter(kDisplayId, mFdp.ConsumeBool());
+
+ const struct VsyncSource final : scheduler::IVsyncSource {
+ explicit VsyncSource(FuzzedDataProvider& fuzzer) : fuzzer(fuzzer) {}
+ FuzzedDataProvider& fuzzer;
+
+ Period period() const { return getFuzzedDuration(fuzzer); }
+ TimePoint vsyncDeadlineAfter(TimePoint) const { return getFuzzedTimePoint(fuzzer); }
+ Period minFramePeriod() const { return period(); }
+ } vsyncSource{mFdp};
+
+ int i = 10;
+ while (i-- > 0) {
+ frameTargeter.beginFrame({.frameBeginTime = getFuzzedTimePoint(mFdp),
+ .vsyncId = getFuzzedVsyncId(mFdp),
+ .expectedVsyncTime = getFuzzedTimePoint(mFdp),
+ .sfWorkDuration = getFuzzedDuration(mFdp)},
+ vsyncSource);
+
+ frameTargeter.setPresentFence(makeFakeFence());
+
+ frameTargeter.endFrame(
+ {.compositionCoverage = mFdp.PickValueInArray(kCompositionCoverage.values)});
+ }
+}
+
void SchedulerFuzzer::process() {
fuzzRefreshRateSelection();
- fuzzRefreshRateConfigs();
+ fuzzRefreshRateSelector();
+ fuzzPresentLatencyTracker();
+ fuzzFrameTargeter();
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 1a49ead275..fa307e9bb4 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<Layer> 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<Layer> createClone(uint32_t /* mirrorRootId */) override { return nullptr; }
};
class FuzzImplVSyncTracker : public scheduler::VSyncTracker {
@@ -106,8 +89,7 @@ public:
nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t /* timePoint */) const override { return 1; }
nsecs_t currentPeriod() const override { return 1; }
-
- void setPeriod(nsecs_t /* period */) override {}
+ Period minFramePeriod() const override { return Period::fromNs(currentPeriod()); }
void resetModel() override {}
@@ -117,6 +99,8 @@ public:
return true;
}
+ void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) override {}
+
nsecs_t nextVSyncTime(nsecs_t timePoint) const {
if (timePoint % mPeriod == 0) {
return timePoint;
@@ -124,6 +108,12 @@ public:
return (timePoint - (timePoint % mPeriod) + mPeriod);
}
+ void setRenderRate(Fps) override {}
+
+ void onFrameBegin(TimePoint, TimePoint) override {}
+
+ void onFrameMissed(TimePoint) override {}
+
void dump(std::string& /* result */) const override {}
protected:
@@ -144,6 +134,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/fuzzer/surfaceflinger_service_fuzzer.cpp b/services/surfaceflinger/fuzzer/surfaceflinger_service_fuzzer.cpp
new file mode 100644
index 0000000000..849a896ce2
--- /dev/null
+++ b/services/surfaceflinger/fuzzer/surfaceflinger_service_fuzzer.cpp
@@ -0,0 +1,32 @@
+/*
+ * 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 <fuzzbinder/libbinder_driver.h>
+
+#include "SurfaceFlinger.h"
+#include "SurfaceFlingerDefaultFactory.h"
+
+using namespace android;
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ DefaultFactory factory;
+ sp<SurfaceFlinger> flinger = sp<SurfaceFlinger>::make(factory);
+ flinger->init();
+
+ sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
+ fuzzService({flinger, composerAIDL}, FuzzedDataProvider(data, size));
+ return 0;
+}
diff --git a/services/surfaceflinger/layerproto/Android.bp b/services/surfaceflinger/layerproto/Android.bp
index 973a4392a2..a4dc8a058e 100644
--- a/services/surfaceflinger/layerproto/Android.bp
+++ b/services/surfaceflinger/layerproto/Android.bp
@@ -13,7 +13,20 @@ cc_library {
srcs: [
"LayerProtoParser.cpp",
- "*.proto",
+ ],
+
+ static_libs: [
+ "libperfetto_client_experimental",
+ ],
+
+ whole_static_libs: [
+ // TODO(b/169779783): move into "static_libs" when the soong issue is fixed
+ "perfetto_trace_protos",
+ ],
+
+ export_static_lib_headers: [
+ "libperfetto_client_experimental",
+ "perfetto_trace_protos",
],
shared_libs: [
@@ -24,10 +37,6 @@ cc_library {
"libbase",
],
- proto: {
- export_proto_headers: true,
- },
-
cppflags: [
"-Werror",
"-Wno-unused-parameter",
@@ -42,21 +51,3 @@ cc_library {
"-Wno-undef",
],
}
-
-java_library_static {
- name: "layersprotosnano",
- host_supported: true,
- proto: {
- type: "nano",
- },
- srcs: ["*.proto"],
- sdk_version: "core_platform",
- target: {
- android: {
- jarjar_rules: "jarjar-rules.txt",
- },
- host: {
- static_libs: ["libprotobuf-java-nano"],
- },
- },
-}
diff --git a/services/surfaceflinger/layerproto/LayerProtoParser.cpp b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
index 854084e7f9..c3d0a40261 100644
--- a/services/surfaceflinger/layerproto/LayerProtoParser.cpp
+++ b/services/surfaceflinger/layerproto/LayerProtoParser.cpp
@@ -37,7 +37,8 @@ bool sortLayers(LayerProtoParser::Layer* lhs, const LayerProtoParser::Layer* rhs
return lhs->id < rhs->id;
}
-LayerProtoParser::LayerTree LayerProtoParser::generateLayerTree(const LayersProto& layersProto) {
+LayerProtoParser::LayerTree LayerProtoParser::generateLayerTree(
+ const perfetto::protos::LayersProto& layersProto) {
LayerTree layerTree;
layerTree.allLayers = generateLayerList(layersProto);
@@ -53,7 +54,7 @@ LayerProtoParser::LayerTree LayerProtoParser::generateLayerTree(const LayersProt
}
std::vector<LayerProtoParser::Layer> LayerProtoParser::generateLayerList(
- const LayersProto& layersProto) {
+ const perfetto::protos::LayersProto& layersProto) {
std::vector<Layer> layerList;
std::unordered_map<int32_t, Layer*> layerMap;
@@ -74,7 +75,8 @@ std::vector<LayerProtoParser::Layer> LayerProtoParser::generateLayerList(
return layerList;
}
-LayerProtoParser::Layer LayerProtoParser::generateLayer(const LayerProto& layerProto) {
+LayerProtoParser::Layer LayerProtoParser::generateLayer(
+ const perfetto::protos::LayerProto& layerProto) {
Layer layer;
layer.id = layerProto.id();
layer.name = layerProto.name();
@@ -120,17 +122,19 @@ LayerProtoParser::Layer LayerProtoParser::generateLayer(const LayerProto& layerP
return layer;
}
-LayerProtoParser::Region LayerProtoParser::generateRegion(const RegionProto& regionProto) {
+LayerProtoParser::Region LayerProtoParser::generateRegion(
+ const perfetto::protos::RegionProto& regionProto) {
LayerProtoParser::Region region;
for (int i = 0; i < regionProto.rect_size(); i++) {
- const RectProto& rectProto = regionProto.rect(i);
+ const perfetto::protos::RectProto& rectProto = regionProto.rect(i);
region.rects.push_back(generateRect(rectProto));
}
return region;
}
-LayerProtoParser::Rect LayerProtoParser::generateRect(const RectProto& rectProto) {
+LayerProtoParser::Rect LayerProtoParser::generateRect(
+ const perfetto::protos::RectProto& rectProto) {
LayerProtoParser::Rect rect;
rect.left = rectProto.left();
rect.top = rectProto.top();
@@ -140,7 +144,8 @@ LayerProtoParser::Rect LayerProtoParser::generateRect(const RectProto& rectProto
return rect;
}
-LayerProtoParser::FloatRect LayerProtoParser::generateFloatRect(const FloatRectProto& rectProto) {
+LayerProtoParser::FloatRect LayerProtoParser::generateFloatRect(
+ const perfetto::protos::FloatRectProto& rectProto) {
LayerProtoParser::FloatRect rect;
rect.left = rectProto.left();
rect.top = rectProto.top();
@@ -151,7 +156,7 @@ LayerProtoParser::FloatRect LayerProtoParser::generateFloatRect(const FloatRectP
}
LayerProtoParser::Transform LayerProtoParser::generateTransform(
- const TransformProto& transformProto) {
+ const perfetto::protos::TransformProto& transformProto) {
LayerProtoParser::Transform transform;
transform.dsdx = transformProto.dsdx();
transform.dtdx = transformProto.dtdx();
@@ -162,7 +167,7 @@ LayerProtoParser::Transform LayerProtoParser::generateTransform(
}
LayerProtoParser::ActiveBuffer LayerProtoParser::generateActiveBuffer(
- const ActiveBufferProto& activeBufferProto) {
+ const perfetto::protos::ActiveBufferProto& activeBufferProto) {
LayerProtoParser::ActiveBuffer activeBuffer;
activeBuffer.width = activeBufferProto.width();
activeBuffer.height = activeBufferProto.height();
@@ -172,7 +177,7 @@ LayerProtoParser::ActiveBuffer LayerProtoParser::generateActiveBuffer(
return activeBuffer;
}
-void LayerProtoParser::updateChildrenAndRelative(const LayerProto& layerProto,
+void LayerProtoParser::updateChildrenAndRelative(const perfetto::protos::LayerProto& layerProto,
std::unordered_map<int32_t, Layer*>& layerMap) {
auto currLayer = layerMap[layerProto.id()];
@@ -188,13 +193,13 @@ void LayerProtoParser::updateChildrenAndRelative(const LayerProto& layerProto,
}
}
- if (layerProto.parent() != -1) {
+ if (layerProto.has_parent()) {
if (layerMap.count(layerProto.parent()) > 0) {
currLayer->parent = layerMap[layerProto.parent()];
}
}
- if (layerProto.z_order_relative_of() != -1) {
+ if (layerProto.has_z_order_relative_of()) {
if (layerMap.count(layerProto.z_order_relative_of()) > 0) {
currLayer->zOrderRelativeOf = layerMap[layerProto.z_order_relative_of()];
}
diff --git a/services/surfaceflinger/layerproto/common.proto b/services/surfaceflinger/layerproto/common.proto
deleted file mode 100644
index a6d8d61600..0000000000
--- a/services/surfaceflinger/layerproto/common.proto
+++ /dev/null
@@ -1,91 +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.
- */
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-package android.surfaceflinger;
-
-message RegionProto {
- reserved 1; // Previously: uint64 id
- repeated RectProto rect = 2;
-}
-
-message RectProto {
- int32 left = 1;
- int32 top = 2;
- int32 right = 3;
- int32 bottom = 4;
-}
-
-message SizeProto {
- int32 w = 1;
- int32 h = 2;
-}
-
-message TransformProto {
- float dsdx = 1;
- float dtdx = 2;
- float dsdy = 3;
- float dtdy = 4;
- int32 type = 5;
-}
-
-message ColorProto {
- float r = 1;
- float g = 2;
- float b = 3;
- float a = 4;
-}
-
-message InputWindowInfoProto {
- uint32 layout_params_flags = 1;
- int32 layout_params_type = 2;
- RectProto frame = 3;
- RegionProto touchable_region = 4;
-
- int32 surface_inset = 5;
- bool visible = 6;
- bool can_receive_keys = 7 [deprecated = true];
- bool focusable = 8;
- bool has_wallpaper = 9;
-
- float global_scale_factor = 10;
- float window_x_scale = 11 [deprecated = true];
- float window_y_scale = 12 [deprecated = true];
-
- int32 crop_layer_id = 13;
- bool replace_touchable_region_with_crop = 14;
- RectProto touchable_region_crop = 15;
- TransformProto transform = 16;
-}
-
-message BlurRegion {
- uint32 blur_radius = 1;
- uint32 corner_radius_tl = 2;
- uint32 corner_radius_tr = 3;
- uint32 corner_radius_bl = 4;
- float corner_radius_br = 5;
- float alpha = 6;
- int32 left = 7;
- int32 top = 8;
- int32 right = 9;
- int32 bottom = 10;
-}
-
-message ColorTransformProto {
- // This will be a 4x4 matrix of float values
- repeated float val = 1;
-}
diff --git a/services/surfaceflinger/layerproto/display.proto b/services/surfaceflinger/layerproto/display.proto
deleted file mode 100644
index c8cd9266a7..0000000000
--- a/services/surfaceflinger/layerproto/display.proto
+++ /dev/null
@@ -1,38 +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.
- */
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
-
-package android.surfaceflinger;
-
-message DisplayProto {
- uint64 id = 1;
-
- string name = 2;
-
- uint32 layer_stack = 3;
-
- SizeProto size = 4;
-
- RectProto layer_stack_space_rect = 5;
-
- TransformProto transform = 6;
-
- bool is_virtual = 7;
-}
diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h
index f560562c94..4a2ef3ddf8 100644
--- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h
+++ b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoHeader.h
@@ -14,11 +14,14 @@
* limitations under the License.
*/
+#pragma once
+
// pragma is used here to disable the warnings emitted from the protobuf
// headers. By adding #pragma before including layer.pb.h, it supresses
// protobuf warnings, but allows the rest of the files to continuing using
// the current flags.
// This file should be included instead of directly including layer.b.h
#pragma GCC system_header
-#include <layers.pb.h>
-#include <layerstrace.pb.h>
+#include <perfetto/config/android/surfaceflinger_layers_config.pbzero.h>
+#include <perfetto/trace/android/surfaceflinger_layers.pb.h>
+#include <perfetto/trace/android/surfaceflinger_layers.pbzero.h>
diff --git a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
index 52503bad3a..79c3982dbd 100644
--- a/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
+++ b/services/surfaceflinger/layerproto/include/layerproto/LayerProtoParser.h
@@ -24,6 +24,8 @@
#include <unordered_map>
#include <vector>
+using android::gui::LayerMetadata;
+
namespace android {
namespace surfaceflinger {
@@ -129,19 +131,22 @@ public:
std::vector<Layer*> topLevelLayers;
};
- static LayerTree generateLayerTree(const LayersProto& layersProto);
+ static LayerTree generateLayerTree(const perfetto::protos::LayersProto& layersProto);
static std::string layerTreeToString(const LayerTree& layerTree);
private:
- static std::vector<Layer> generateLayerList(const LayersProto& layersProto);
- static LayerProtoParser::Layer generateLayer(const LayerProto& layerProto);
- static LayerProtoParser::Region generateRegion(const RegionProto& regionProto);
- static LayerProtoParser::Rect generateRect(const RectProto& rectProto);
- static LayerProtoParser::FloatRect generateFloatRect(const FloatRectProto& rectProto);
- static LayerProtoParser::Transform generateTransform(const TransformProto& transformProto);
+ static std::vector<Layer> generateLayerList(const perfetto::protos::LayersProto& layersProto);
+ static LayerProtoParser::Layer generateLayer(const perfetto::protos::LayerProto& layerProto);
+ static LayerProtoParser::Region generateRegion(
+ const perfetto::protos::RegionProto& regionProto);
+ static LayerProtoParser::Rect generateRect(const perfetto::protos::RectProto& rectProto);
+ static LayerProtoParser::FloatRect generateFloatRect(
+ const perfetto::protos::FloatRectProto& rectProto);
+ static LayerProtoParser::Transform generateTransform(
+ const perfetto::protos::TransformProto& transformProto);
static LayerProtoParser::ActiveBuffer generateActiveBuffer(
- const ActiveBufferProto& activeBufferProto);
- static void updateChildrenAndRelative(const LayerProto& layerProto,
+ const perfetto::protos::ActiveBufferProto& activeBufferProto);
+ static void updateChildrenAndRelative(const perfetto::protos::LayerProto& layerProto,
std::unordered_map<int32_t, Layer*>& layerMap);
static std::string layerToString(const LayerProtoParser::Layer* layer);
diff --git a/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h b/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
index 3e9ca52fff..ea80ad8f33 100644
--- a/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
+++ b/services/surfaceflinger/layerproto/include/layerproto/TransactionProto.h
@@ -14,7 +14,11 @@
* limitations under the License.
*/
+#pragma once
+
// disable the warnings emitted from the protobuf headers. This file should be included instead of
// directly including the generated header file
#pragma GCC system_header
-#include <transactions.pb.h>
+#include <perfetto/config/android/surfaceflinger_transactions_config.pbzero.h>
+#include <perfetto/trace/android/surfaceflinger_transactions.pb.h>
+#include <perfetto/trace/android/surfaceflinger_transactions.pbzero.h>
diff --git a/services/surfaceflinger/layerproto/jarjar-rules.txt b/services/surfaceflinger/layerproto/jarjar-rules.txt
deleted file mode 100644
index 40043a861c..0000000000
--- a/services/surfaceflinger/layerproto/jarjar-rules.txt
+++ /dev/null
@@ -1 +0,0 @@
-rule com.google.protobuf.nano.** com.android.framework.protobuf.nano.@1
diff --git a/services/surfaceflinger/layerproto/layers.proto b/services/surfaceflinger/layerproto/layers.proto
deleted file mode 100644
index 3598308338..0000000000
--- a/services/surfaceflinger/layerproto/layers.proto
+++ /dev/null
@@ -1,169 +0,0 @@
-// Definitions for SurfaceFlinger layers.
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
-
-package android.surfaceflinger;
-
-// Contains a list of all layers.
-message LayersProto {
- repeated LayerProto layers = 1;
-}
-
-// Must match definition in the IComposerClient HAL
-enum HwcCompositionType {
- // Invalid composition type
- INVALID = 0;
- // Layer was composited by the client into the client target buffer
- CLIENT = 1;
- // Layer was composited by the device through hardware overlays
- DEVICE = 2;
- // Layer was composited by the device using a color
- SOLID_COLOR = 3;
- // Similar to DEVICE, but the layer position may have been asynchronously set
- // through setCursorPosition
- CURSOR = 4;
- // Layer was composited by the device via a sideband stream.
- SIDEBAND = 5;
-}
-
-// Information about each layer.
-message LayerProto {
- // unique id per layer.
- int32 id = 1;
- // unique name per layer.
- string name = 2;
- // list of children this layer may have. May be empty.
- repeated int32 children = 3;
- // list of layers that are z order relative to this layer.
- repeated int32 relatives = 4;
- // The type of layer, ex Color, Layer
- string type = 5;
- RegionProto transparent_region = 6;
- RegionProto visible_region = 7;
- RegionProto damage_region = 8;
- uint32 layer_stack = 9;
- // The layer's z order. Can be z order in layer stack, relative to parent,
- // or relative to another layer specified in zOrderRelative.
- int32 z = 10;
- // The layer's position on the display.
- PositionProto position = 11;
- // The layer's requested position.
- PositionProto requested_position = 12;
- // The layer's size.
- SizeProto size = 13;
- // The layer's crop in it's own bounds.
- RectProto crop = 14;
- // The layer's crop in it's parent's bounds.
- RectProto final_crop = 15 [deprecated=true];
- bool is_opaque = 16;
- bool invalidate = 17;
- string dataspace = 18;
- string pixel_format = 19;
- // The layer's actual color.
- ColorProto color = 20;
- // The layer's requested color.
- ColorProto requested_color = 21;
- // Can be any combination of
- // hidden = 0x01
- // opaque = 0x02,
- // secure = 0x80,
- uint32 flags = 22;
- // The layer's actual transform
- TransformProto transform = 23;
- // The layer's requested transform.
- TransformProto requested_transform = 24;
- // The parent layer. This value can be null if there is no parent.
- int32 parent = 25;
- // The layer that this layer has a z order relative to. This value can be null.
- int32 z_order_relative_of = 26;
- // This value can be null if there's nothing to draw.
- ActiveBufferProto active_buffer = 27;
- // The number of frames available.
- int32 queued_frames = 28;
- bool refresh_pending = 29;
- // The layer's composer backend destination frame
- RectProto hwc_frame = 30;
- // The layer's composer backend source crop
- FloatRectProto hwc_crop = 31;
- // The layer's composer backend transform
- int32 hwc_transform = 32;
- int32 window_type = 33 [deprecated=true];
- int32 app_id = 34 [deprecated=true];
- // The layer's composition type
- HwcCompositionType hwc_composition_type = 35;
- // If it's a buffer layer, indicate if the content is protected
- bool is_protected = 36;
- // Current frame number being rendered.
- uint64 curr_frame = 37;
- // A list of barriers that the layer is waiting to update state.
- repeated BarrierLayerProto barrier_layer = 38;
- // If active_buffer is not null, record its transform.
- TransformProto buffer_transform = 39;
- int32 effective_scaling_mode = 40;
- // Layer's corner radius.
- float corner_radius = 41;
- // Metadata map. May be empty.
- map<int32, bytes> metadata = 42;
-
- TransformProto effective_transform = 43;
- FloatRectProto source_bounds = 44;
- FloatRectProto bounds = 45;
- FloatRectProto screen_bounds = 46;
-
- InputWindowInfoProto input_window_info = 47;
-
- // Crop used to draw the rounded corner.
- FloatRectProto corner_radius_crop = 48;
-
- // length of the shadow to draw around the layer, it may be set on the
- // layer or set by a parent layer.
- float shadow_radius = 49;
- ColorTransformProto color_transform = 50;
-
- bool is_relative_of = 51;
- // Layer's background blur radius in pixels.
- int32 background_blur_radius = 52;
-
- uint32 owner_uid = 53;
-
- // Regions of a layer, where blur should be applied.
- repeated BlurRegion blur_regions = 54;
-
- bool is_trusted_overlay = 55;
-
- // Corner radius explicitly set on layer rather than inherited
- float requested_corner_radius = 56;
-
- RectProto destination_frame = 57;
-}
-
-message PositionProto {
- float x = 1;
- float y = 2;
-}
-
-message FloatRectProto {
- float left = 1;
- float top = 2;
- float right = 3;
- float bottom = 4;
-}
-
-message ActiveBufferProto {
- uint32 width = 1;
- uint32 height = 2;
- uint32 stride = 3;
- int32 format = 4;
- uint64 usage = 5;
-}
-
-message BarrierLayerProto {
- // layer id the barrier is waiting on.
- int32 id = 1;
- // frame number the barrier is waiting on.
- uint64 frame_number = 2;
-}
-
diff --git a/services/surfaceflinger/layerproto/layerstrace.proto b/services/surfaceflinger/layerproto/layerstrace.proto
deleted file mode 100644
index 13647b669e..0000000000
--- a/services/surfaceflinger/layerproto/layerstrace.proto
+++ /dev/null
@@ -1,63 +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.
- */
-
-syntax = "proto2";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/layers.proto";
-import "frameworks/native/services/surfaceflinger/layerproto/display.proto";
-
-package android.surfaceflinger;
-
-/* represents a file full of surface flinger trace entries.
- Encoded, it should start with 0x4c 0x59 0x52 0x54 0x52 0x41 0x43 0x45 (.LYRTRACE), such
- that they can be easily identified. */
-message LayersTraceFileProto {
-
- /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
- (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
- constants into .proto files. */
- enum MagicNumber {
- INVALID = 0;
- MAGIC_NUMBER_L = 0x5452594c; /* LYRT (little-endian ASCII) */
- MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
- }
-
- optional fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
- repeated LayersTraceProto entry = 2;
-}
-
-/* one window manager trace entry. */
-message LayersTraceProto {
- /* required: elapsed realtime in nanos since boot of when this entry was logged */
- optional sfixed64 elapsed_realtime_nanos = 1;
-
- /* where the trace originated */
- optional string where = 2;
-
- optional LayersProto layers = 3;
-
- // Blob for the current HWC information for all layers, reported by dumpsys.
- optional string hwc_blob = 4;
-
- /* Includes state sent during composition like visible region and composition type. */
- optional bool excludes_composition_state = 5;
-
- /* Number of missed entries since the last entry was recorded. */
- optional uint32 missed_entries = 6;
-
- repeated DisplayProto displays = 7;
-}
diff --git a/services/surfaceflinger/layerproto/transactions.proto b/services/surfaceflinger/layerproto/transactions.proto
deleted file mode 100644
index 4f99b192a3..0000000000
--- a/services/surfaceflinger/layerproto/transactions.proto
+++ /dev/null
@@ -1,283 +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.
- */
-
-syntax = "proto3";
-option optimize_for = LITE_RUNTIME;
-
-import "frameworks/native/services/surfaceflinger/layerproto/common.proto";
-
-package android.surfaceflinger.proto;
-
-/* Represents a file full of surface flinger transactions.
- Encoded, it should start with 0x54 0x4E 0x58 0x54 0x52 0x41 0x43 0x45 (.TNXTRACE), such
- that they can be easily identified. */
-message TransactionTraceFile {
- /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
- (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
- constants into .proto files. */
- enum MagicNumber {
- INVALID = 0;
- MAGIC_NUMBER_L = 0x54584E54; /* TNXT (little-endian ASCII) */
- MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
- }
-
- fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
- repeated TransactionTraceEntry entry = 2;
-}
-
-message TransactionTraceEntry {
- int64 elapsed_realtime_nanos = 1;
- int64 vsync_id = 2;
- repeated TransactionState transactions = 3;
- repeated LayerCreationArgs added_layers = 4;
- repeated int32 removed_layers = 5;
- repeated DisplayState added_displays = 6;
- repeated int32 removed_displays = 7;
- repeated int32 removed_layer_handles = 8;
-}
-
-message LayerCreationArgs {
- int32 layer_id = 1;
- string name = 2;
- uint32 flags = 3;
- int32 parent_id = 4;
- int32 mirror_from_id = 5;
-}
-
-message TransactionState {
- int32 pid = 1;
- int32 uid = 2;
- int64 vsync_id = 3;
- int32 input_event_id = 4;
- int64 post_time = 5;
- uint64 transaction_id = 6;
- repeated LayerState layer_changes = 7;
- repeated DisplayState display_changes = 8;
-}
-
-// Keep insync with layer_state_t
-message LayerState {
- int64 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.
- enum ChangesLsb {
- eChangesLsbNone = 0;
- ePositionChanged = 0x00000001;
- eLayerChanged = 0x00000002;
- eSizeChanged = 0x00000004;
- eAlphaChanged = 0x00000008;
-
- eMatrixChanged = 0x00000010;
- eTransparentRegionChanged = 0x00000020;
- eFlagsChanged = 0x00000040;
- eLayerStackChanged = 0x00000080;
-
- eReleaseBufferListenerChanged = 0x00000400;
- eShadowRadiusChanged = 0x00000800;
-
- eBufferCropChanged = 0x00002000;
- eRelativeLayerChanged = 0x00004000;
- eReparent = 0x00008000;
-
- eColorChanged = 0x00010000;
- eDestroySurface = 0x00020000;
- eTransformChanged = 0x00040000;
- eTransformToDisplayInverseChanged = 0x00080000;
-
- eCropChanged = 0x00100000;
- eBufferChanged = 0x00200000;
- eAcquireFenceChanged = 0x00400000;
- eDataspaceChanged = 0x00800000;
-
- eHdrMetadataChanged = 0x01000000;
- eSurfaceDamageRegionChanged = 0x02000000;
- eApiChanged = 0x04000000;
- eSidebandStreamChanged = 0x08000000;
-
- eColorTransformChanged = 0x10000000;
- eHasListenerCallbacksChanged = 0x20000000;
- eInputInfoChanged = 0x40000000;
- eCornerRadiusChanged = -2147483648; // 0x80000000; (proto stores enums as signed int)
- };
- enum ChangesMsb {
- eChangesMsbNone = 0;
- eDestinationFrameChanged = 0x1;
- eCachedBufferChanged = 0x2;
- eBackgroundColorChanged = 0x4;
- eMetadataChanged = 0x8;
- eColorSpaceAgnosticChanged = 0x10;
- eFrameRateSelectionPriority = 0x20;
- eFrameRateChanged = 0x40;
- eBackgroundBlurRadiusChanged = 0x80;
- eProducerDisconnect = 0x100;
- eFixedTransformHintChanged = 0x200;
- eFrameNumberChanged = 0x400;
- eBlurRegionsChanged = 0x800;
- eAutoRefreshChanged = 0x1000;
- eStretchChanged = 0x2000;
- eTrustedOverlayChanged = 0x4000;
- eDropInputModeChanged = 0x8000;
- };
- uint64 what = 2;
- float x = 3;
- float y = 4;
- int32 z = 5;
- uint32 w = 6;
- uint32 h = 7;
- uint32 layer_stack = 8;
-
- enum Flags {
- eFlagsNone = 0;
- eLayerHidden = 0x01;
- eLayerOpaque = 0x02;
- eLayerSkipScreenshot = 0x40;
- eLayerSecure = 0x80;
- eEnableBackpressure = 0x100;
- eLayerIsDisplayDecoration = 0x200;
- };
- uint32 flags = 9;
- uint32 mask = 10;
-
- message Matrix22 {
- float dsdx = 1;
- float dtdx = 2;
- float dtdy = 3;
- float dsdy = 4;
- };
- Matrix22 matrix = 11;
- float corner_radius = 12;
- uint32 background_blur_radius = 13;
- int64 parent_id = 14;
- int64 relative_parent_id = 15;
-
- float alpha = 16;
- message Color3 {
- float r = 1;
- float g = 2;
- float b = 3;
- }
- Color3 color = 17;
- RegionProto transparent_region = 18;
- uint32 transform = 19;
- bool transform_to_display_inverse = 20;
- RectProto crop = 21;
-
- message BufferData {
- uint64 buffer_id = 1;
- uint32 width = 2;
- uint32 height = 3;
- uint64 frame_number = 4;
-
- enum BufferDataChange {
- BufferDataChangeNone = 0;
- fenceChanged = 0x01;
- frameNumberChanged = 0x02;
- cachedBufferChanged = 0x04;
- }
- uint32 flags = 5;
- uint64 cached_buffer_id = 6;
-
- enum PixelFormat {
- PIXEL_FORMAT_UNKNOWN = 0;
- PIXEL_FORMAT_CUSTOM = -4;
- PIXEL_FORMAT_TRANSLUCENT = -3;
- PIXEL_FORMAT_TRANSPARENT = -2;
- PIXEL_FORMAT_OPAQUE = -1;
- PIXEL_FORMAT_RGBA_8888 = 1;
- PIXEL_FORMAT_RGBX_8888 = 2;
- PIXEL_FORMAT_RGB_888 = 3;
- PIXEL_FORMAT_RGB_565 = 4;
- PIXEL_FORMAT_BGRA_8888 = 5;
- PIXEL_FORMAT_RGBA_5551 = 6;
- PIXEL_FORMAT_RGBA_4444 = 7;
- PIXEL_FORMAT_RGBA_FP16 = 22;
- PIXEL_FORMAT_RGBA_1010102 = 43;
- PIXEL_FORMAT_R_8 = 0x38;
- }
- PixelFormat pixel_format = 7;
- uint64 usage = 8;
- }
- BufferData buffer_data = 22;
- int32 api = 23;
- bool has_sideband_stream = 24;
- 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;
- RegionProto touchable_region = 3;
- int32 surface_inset = 4;
- bool focusable = 5;
- bool has_wallpaper = 6;
- float global_scale_factor = 7;
- int64 crop_layer_id = 8;
- bool replace_touchable_region_with_crop = 9;
- RectProto touchable_region_crop = 10;
- Transform transform = 11;
- }
- WindowInfo window_info_handle = 27;
- float bg_color_alpha = 28;
- int32 bg_color_dataspace = 29;
- bool color_space_agnostic = 30;
- float shadow_radius = 31;
- int32 frame_rate_selection_priority = 32;
- float frame_rate = 33;
- int32 frame_rate_compatibility = 34;
- int32 change_frame_rate_strategy = 35;
- uint32 fixed_transform_hint = 36;
- uint64 frame_number = 37;
- bool auto_refresh = 38;
- bool is_trusted_overlay = 39;
- RectProto buffer_crop = 40;
- RectProto destination_frame = 41;
-
- enum DropInputMode {
- NONE = 0;
- ALL = 1;
- OBSCURED = 2;
- };
- DropInputMode drop_input_mode = 42;
-}
-
-message DisplayState {
- enum Changes {
- eChangesNone = 0;
- eSurfaceChanged = 0x01;
- eLayerStackChanged = 0x02;
- eDisplayProjectionChanged = 0x04;
- eDisplaySizeChanged = 0x08;
- eFlagsChanged = 0x10;
- };
- int32 id = 1;
- uint32 what = 2;
- uint32 flags = 3;
- uint32 layer_stack = 4;
- uint32 orientation = 5;
- RectProto layer_stack_space_rect = 6;
- RectProto oriented_display_space_rect = 7;
- uint32 width = 8;
- uint32 height = 9;
-}
diff --git a/services/surfaceflinger/main_surfaceflinger.cpp b/services/surfaceflinger/main_surfaceflinger.cpp
index 883766b334..6c8972f1fb 100644
--- a/services/surfaceflinger/main_surfaceflinger.cpp
+++ b/services/surfaceflinger/main_surfaceflinger.cpp
@@ -29,6 +29,7 @@
#include <binder/IPCThreadState.h>
#include <binder/IServiceManager.h>
#include <binder/ProcessState.h>
+#include <common/FlagManager.h>
#include <configstore/Utils.h>
#include <displayservice/DisplayService.h>
#include <errno.h>
@@ -67,7 +68,7 @@ static void startDisplayService() {
using android::frameworks::displayservice::V1_0::implementation::DisplayService;
using android::frameworks::displayservice::V1_0::IDisplayService;
- sp<IDisplayService> displayservice = new DisplayService();
+ sp<IDisplayService> displayservice = sp<DisplayService>::make();
status_t err = displayservice->registerAsService();
// b/141930622
@@ -91,7 +92,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 +149,17 @@ int main(int, char**) {
IServiceManager::DUMP_FLAG_PRIORITY_CRITICAL | IServiceManager::DUMP_FLAG_PROTO);
// publish gui::ISurfaceComposer, the new AIDL interface
- sp<SurfaceComposerAIDL> composerAIDL = new SurfaceComposerAIDL(flinger);
+ sp<SurfaceComposerAIDL> composerAIDL = sp<SurfaceComposerAIDL>::make(flinger);
+ if (FlagManager::getInstance().misc1()) {
+ composerAIDL->setMinSchedulerPolicy(SCHED_FIFO, newPriority);
+ }
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/surfaceflinger_flags.aconfig b/services/surfaceflinger/surfaceflinger_flags.aconfig
new file mode 100644
index 0000000000..abe7ec708f
--- /dev/null
+++ b/services/surfaceflinger/surfaceflinger_flags.aconfig
@@ -0,0 +1,142 @@
+package: "com.android.graphics.surfaceflinger.flags"
+container: "system"
+
+flag {
+ name: "misc1"
+ namespace: "core_graphics"
+ description: "This flag controls minor miscellaneous SurfaceFlinger changes"
+ bug: "297389311"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "connected_display"
+ namespace: "core_graphics"
+ description: "Controls SurfaceFlinger support for Connected Displays in 24Q1"
+ bug: "278199093"
+ is_fixed_read_only: true
+}
+
+flag{
+ name: "late_boot_misc2"
+ namespace: "core_graphics"
+ description: "This flag controls minor miscellaneous SurfaceFlinger changes. Cannot be read before boot finished!"
+ bug: "297389311"
+}
+
+flag {
+ name: "vrr_config"
+ namespace: "core_graphics"
+ description: "Controls SurfaceFlinger support for VRR Configurations"
+ bug: "284845445"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_layer_command_batching"
+ namespace: "core_graphics"
+ description: "This flag controls batching on createLayer/destroyLayer command with executeCommand."
+ bug: "290685621"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "dont_skip_on_early"
+ namespace: "core_graphics"
+ description: "This flag is guarding the behaviour where SurfaceFlinger is trying to opportunistically present a frame when the configuration change from late to early"
+ bug: "273702768"
+}
+
+flag {
+ name: "multithreaded_present"
+ namespace: "core_graphics"
+ description: "Controls whether to offload present calls to another thread"
+ bug: "259132483"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_small_area_detection"
+ namespace: "core_graphics"
+ description: "Feature flag for SmallAreaDetection"
+ bug: "283055450"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "hotplug2"
+ namespace: "core_graphics"
+ description: "Feature flag for using hotplug2 HAL API"
+ bug: "303460805"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "hdcp_level_hal"
+ namespace: "core_graphics"
+ description: "Feature flag for adding a HAL API to commuicate hdcp levels"
+ bug: "285359126"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "add_sf_skipped_frames_to_trace"
+ namespace: "core_graphics"
+ description: "Add SurfaceFlinger dropped Frames to frame timeline"
+ bug: "273701290"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "refresh_rate_overlay_on_external_display"
+ namespace: "core_graphics"
+ description: "enable refresh rate indicator on the external display"
+ bug: "301647974"
+}
+
+flag {
+ name: "use_known_refresh_rate_for_fps_consistency"
+ namespace: "core_graphics"
+ description: "Whether to use the closest known refresh rate to determine the fps consistency."
+ bug: "299201319"
+}
+
+flag {
+ name: "cache_if_source_crop_layer_only_moved"
+ namespace: "core_graphics"
+ description: "do not flatten layers if source crop is only moved"
+ bug: "305718400"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "enable_fro_dependent_features"
+ namespace: "core_graphics"
+ description: "enable frame rate override dependent features by default"
+ bug: "314217419"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "display_protected"
+ namespace: "core_graphics"
+ description: "Introduce protected displays to specify whether they should render protected content"
+ bug: "301647974"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "fp16_client_target"
+ namespace: "core_graphics"
+ description: "Controls whether we render to fp16 client targets"
+ bug: "236745178"
+ is_fixed_read_only: true
+}
+
+flag {
+ name: "game_default_frame_rate"
+ namespace: "game"
+ description: "This flag guards the new behavior with the addition of Game Default Frame Rate feature."
+ bug: "286084594"
+ is_fixed_read_only: true
+}
diff --git a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
index bcbe21a483..0ad5ac9956 100644
--- a/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
+++ b/services/surfaceflinger/sysprop/SurfaceFlingerProperties.sysprop
@@ -199,6 +199,7 @@ prop {
# useColorManagement indicates whether SurfaceFlinger should manage color
# by switching to appropriate color mode automatically depending on the
# Dataspace of the surfaces on screen.
+# DEPRECATED: SurfaceFlinger is always color managed.
prop {
api_name: "use_color_management"
type: Boolean
@@ -207,7 +208,7 @@ prop {
prop_name: "ro.surface_flinger.use_color_management"
}
-# The following four propertiess define:
+# The following four properties define:
# Returns the default data space and pixel format that SurfaceFlinger
# expects to receive and output as well as the wide color gamut data space
# and pixel format for wide color gamut surfaces.
@@ -276,7 +277,7 @@ prop {
# The variable works only when useColorManagement is specified. If
# unspecified, the data space follows what SurfaceFlinger expects for
# surfaces when useColorManagement is specified.
-
+# DEPRECATED: do not use
prop {
api_name: "color_space_agnostic_dataspace"
type: Long
@@ -470,4 +471,39 @@ prop {
scope: Public
access: Readonly
prop_name: "ro.surface_flinger.ignore_hdr_camera_layers"
-} \ No newline at end of file
+}
+
+# Controls the minimum acquired buffers SurfaceFlinger will suggest via
+# ISurfaceComposer.getMaxAcquiredBufferCount().
+prop {
+ api_name: "min_acquired_buffers"
+ type: Long
+ scope: Public
+ access: Readonly
+ prop_name: "ro.surface_flinger.min_acquired_buffers"
+}
+
+# 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"
+}
+
+# Controls the default frame rate override of game applications. Ideally, game applications set
+# desired frame rate via setFrameRate() API. However, to cover the scenario when the game didn't
+# have a set frame rate, we introduce the default frame rate. The priority of this override is the
+# lowest among setFrameRate() and game intervention override.
+prop {
+ api_name: "game_default_frame_rate_override"
+ type: Integer
+ scope: Public
+ access: Readonly
+ prop_name: "ro.surface_flinger.game_default_frame_rate_override"
+}
diff --git a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
index 348a462038..00173009f5 100644
--- a/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
+++ b/services/surfaceflinger/sysprop/api/SurfaceFlingerProperties-current.txt
@@ -1,6 +1,10 @@
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
prop_name: "ro.surface_flinger.color_space_agnostic_dataspace"
@@ -61,6 +65,11 @@ props {
prop_name: "ro.surface_flinger.force_hwc_copy_for_virtual_displays"
}
prop {
+ api_name: "game_default_frame_rate_override"
+ type: Integer
+ prop_name: "ro.surface_flinger.game_default_frame_rate_override"
+ }
+ prop {
api_name: "has_HDR_display"
prop_name: "ro.surface_flinger.has_HDR_display"
}
@@ -93,6 +102,11 @@ props {
prop_name: "ro.surface_flinger.max_virtual_display_dimension"
}
prop {
+ api_name: "min_acquired_buffers"
+ type: Long
+ prop_name: "ro.surface_flinger.min_acquired_buffers"
+ }
+ prop {
api_name: "present_time_offset_from_vsync_ns"
type: Long
prop_name: "ro.surface_flinger.present_time_offset_from_vsync_ns"
diff --git a/services/surfaceflinger/tests/Android.bp b/services/surfaceflinger/tests/Android.bp
index 507601b4e0..5449aeb6bb 100644
--- a/services/surfaceflinger/tests/Android.bp
+++ b/services/surfaceflinger/tests/Android.bp
@@ -23,22 +23,28 @@ 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",
+ "Binder_test.cpp",
"BufferGenerator.cpp",
- "CommonTypes_test.cpp",
"Credentials_test.cpp",
"DereferenceSurfaceControl_test.cpp",
"DisplayConfigs_test.cpp",
"DisplayEventReceiver_test.cpp",
"EffectLayer_test.cpp",
+ "HdrSdrRatioOverlay_test.cpp",
"InvalidHandles_test.cpp",
+ "LayerBorder_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",
@@ -49,21 +55,19 @@ cc_test {
"ReleaseBufferCallback_test.cpp",
"ScreenCapture_test.cpp",
"SetFrameRate_test.cpp",
- "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",
+ "libsurfaceflingerflags",
],
shared_libs: [
- "android.hardware.graphics.common-V4-ndk",
"android.hardware.graphics.common@1.2",
"libandroid",
"libbase",
@@ -122,10 +126,12 @@ cc_test {
],
cpp_std: "experimental",
gnu_extensions: false,
+ data: [
+ ":SurfaceFlinger_test",
+ ],
}
subdirs = [
- "fakehwc",
"hwc2",
"unittests",
"utils",
diff --git a/services/surfaceflinger/tests/Binder_test.cpp b/services/surfaceflinger/tests/Binder_test.cpp
new file mode 100644
index 0000000000..3152973da6
--- /dev/null
+++ b/services/surfaceflinger/tests/Binder_test.cpp
@@ -0,0 +1,225 @@
+/*
+ * 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 <errno.h>
+#include <sched.h>
+
+#include <android/gui/ISurfaceComposer.h>
+#include <android/gui/ISurfaceComposerClient.h>
+#include <binder/IBinder.h>
+#include <binder/IServiceManager.h>
+#include <gtest/gtest.h>
+#include <gui/ISurfaceComposer.h>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+namespace android::test {
+using namespace com::android::graphics::surfaceflinger;
+
+class BinderTest : public ::testing::Test {
+protected:
+ BinderTest();
+
+ void SetUp() override;
+
+ void getSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+ void getNonAidlSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+ void getClientSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+ void getDisplayEventConnectionSchedulingPolicy(gui::SchedulingPolicy* outPolicy);
+
+private:
+ sp<gui::ISurfaceComposer> mISurfaceComposerAidl;
+ sp<ISurfaceComposer> mISurfaceComposer;
+ sp<gui::ISurfaceComposerClient> mISurfaceComposerClient;
+ sp<gui::IDisplayEventConnection> mConnection;
+};
+
+BinderTest::BinderTest() {
+ const String16 name("SurfaceFlingerAIDL");
+ mISurfaceComposerAidl = waitForService<gui::ISurfaceComposer>(String16("SurfaceFlingerAIDL"));
+ mISurfaceComposer = waitForService<ISurfaceComposer>(String16("SurfaceFlinger"));
+ mISurfaceComposerAidl->createConnection(&mISurfaceComposerClient);
+ mISurfaceComposerAidl
+ ->createDisplayEventConnection(gui::ISurfaceComposer::VsyncSource::eVsyncSourceApp,
+ gui::ISurfaceComposer::EventRegistration(0), {},
+ &mConnection);
+}
+
+void BinderTest::SetUp() {
+ ASSERT_TRUE(mISurfaceComposerAidl);
+ ASSERT_TRUE(mISurfaceComposer);
+ ASSERT_TRUE(mISurfaceComposerClient);
+ ASSERT_TRUE(mConnection);
+}
+
+void BinderTest::getSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ const auto status = mISurfaceComposerAidl->getSchedulingPolicy(outPolicy);
+ ASSERT_TRUE(status.isOk());
+}
+
+void BinderTest::getNonAidlSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ Parcel data, reply;
+ const status_t status =
+ IInterface::asBinder(mISurfaceComposer)
+ ->transact(BnSurfaceComposer::GET_SCHEDULING_POLICY, data, &reply);
+ ASSERT_EQ(OK, status);
+
+ outPolicy->policy = reply.readInt32();
+ outPolicy->priority = reply.readInt32();
+}
+
+void BinderTest::getClientSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ const auto status = mISurfaceComposerClient->getSchedulingPolicy(outPolicy);
+ ASSERT_TRUE(status.isOk());
+}
+
+void BinderTest::getDisplayEventConnectionSchedulingPolicy(gui::SchedulingPolicy* outPolicy) {
+ const auto status = mConnection->getSchedulingPolicy(outPolicy);
+ ASSERT_TRUE(status.isOk());
+}
+
+TEST_F(BinderTest, SchedulingPolicy) {
+ if (!flags::misc1()) GTEST_SKIP();
+
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTest, NonAidlSchedulingPolicy) {
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getNonAidlSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTest, ClientSchedulingPolicy) {
+ if (!flags::misc1()) GTEST_SKIP();
+
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getClientSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTest, DisplayEventConnectionSchedulingPolicy) {
+ if (!flags::misc1()) GTEST_SKIP();
+
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getDisplayEventConnectionSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+class BinderTestRtCaller : public BinderTest {
+protected:
+ void SetUp() override;
+ void TearDown() override;
+
+private:
+ int mOrigPolicy;
+ int mOrigPriority;
+};
+
+void BinderTestRtCaller::SetUp() {
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ mOrigPolicy = sched_getscheduler(0);
+ struct sched_param origSchedParam;
+ ASSERT_GE(0, sched_getparam(0, &origSchedParam)) << "errno: " << strerror(errno);
+ mOrigPriority = origSchedParam.sched_priority;
+
+ struct sched_param param;
+ param.sched_priority = priority;
+ ASSERT_GE(0, sched_setscheduler(0, policy, &param)) << "errno: " << strerror(errno);
+}
+
+void BinderTestRtCaller::TearDown() {
+ struct sched_param origSchedParam;
+ origSchedParam.sched_priority = mOrigPriority;
+ ASSERT_GE(0, sched_setscheduler(0, mOrigPolicy, &origSchedParam))
+ << "errno: " << strerror(errno);
+}
+
+TEST_F(BinderTestRtCaller, SchedulingPolicy) {
+ if (!flags::misc1()) GTEST_SKIP();
+
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTestRtCaller, NonAidlSchedulingPolicy) {
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getNonAidlSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTestRtCaller, ClientSchedulingPolicy) {
+ if (!flags::misc1()) GTEST_SKIP();
+
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getClientSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+TEST_F(BinderTestRtCaller, DisplayEventConnectionSchedulingPolicy) {
+ if (!flags::misc1()) GTEST_SKIP();
+
+ const int policy = SCHED_FIFO;
+ const int priority = sched_get_priority_min(policy);
+
+ gui::SchedulingPolicy sfPolicy;
+ ASSERT_NO_FATAL_FAILURE(getDisplayEventConnectionSchedulingPolicy(&sfPolicy));
+
+ ASSERT_EQ(policy, sfPolicy.policy & (~SCHED_RESET_ON_FORK));
+ ASSERT_EQ(priority, sfPolicy.priority);
+}
+
+} // namespace android::test
diff --git a/services/surfaceflinger/tests/BootDisplayMode_test.cpp b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
index d70908e390..4f41a81011 100644
--- a/services/surfaceflinger/tests/BootDisplayMode_test.cpp
+++ b/services/surfaceflinger/tests/BootDisplayMode_test.cpp
@@ -18,6 +18,7 @@
#include <gtest/gtest.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerService.h>
#include <private/gui/ComposerServiceAIDL.h>
@@ -25,28 +26,97 @@
namespace android {
-TEST(BootDisplayModeTest, setBootDisplayMode) {
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
- sp<gui::ISurfaceComposer> sf_aidl(ComposerServiceAIDL::getComposerService());
- auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
- bool bootModeSupport = false;
- binder::Status status = sf_aidl->getBootDisplayModeSupport(&bootModeSupport);
- ASSERT_NO_FATAL_FAILURE(status.transactionError());
- if (bootModeSupport) {
- ASSERT_EQ(NO_ERROR, sf->setBootDisplayMode(displayToken, 0));
+using gui::aidl_utils::statusTFromBinderStatus;
+
+struct BootDisplayModeTest : public ::testing::Test {
+protected:
+ void SetUp() override {
+ mSf = ComposerServiceAIDL::getComposerService();
+
+ const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ ASSERT_FALSE(ids.empty());
+ mDisplayToken = SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+ bool bootModeSupport = false;
+ binder::Status status = mSf->getBootDisplayModeSupport(&bootModeSupport);
+ ASSERT_NO_FATAL_FAILURE(statusTFromBinderStatus(status));
+
+ if (!bootModeSupport) {
+ GTEST_SKIP() << "Boot mode not supported";
+ }
+
+ gui::DynamicDisplayInfo info;
+ status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+ ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ mOldMode = info.preferredBootDisplayMode;
+ const auto newMode = [&]() -> std::optional<ui::DisplayModeId> {
+ for (const auto& mode : info.supportedDisplayModes) {
+ if (mode.id != mOldMode) {
+ return std::optional(mode.id);
+ }
+ }
+ return std::nullopt;
+ }();
+
+ if (!newMode) {
+ GTEST_SKIP() << "Only a single mode is supported";
+ }
+
+ mNewMode = *newMode;
}
-}
-TEST(BootDisplayModeTest, clearBootDisplayMode) {
- sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
- auto displayToken = SurfaceComposerClient::getInternalDisplayToken();
- bool bootModeSupport = false;
- binder::Status status = sf->getBootDisplayModeSupport(&bootModeSupport);
- ASSERT_NO_FATAL_FAILURE(status.transactionError());
- if (bootModeSupport) {
- status = sf->clearBootDisplayMode(displayToken);
- ASSERT_EQ(NO_ERROR, status.transactionError());
+ void TearDown() override {
+ binder::Status status = mSf->setBootDisplayMode(mDisplayToken, mOldMode);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+ gui::DynamicDisplayInfo info;
+ status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ EXPECT_EQ(mOldMode, info.preferredBootDisplayMode);
}
+
+ ui::DisplayModeId mOldMode;
+ ui::DisplayModeId mNewMode;
+ sp<gui::ISurfaceComposer> mSf;
+ sp<IBinder> mDisplayToken;
+};
+
+TEST_F(BootDisplayModeTest, setBootDisplayMode) {
+ // Set a new mode and check that it got applied
+ binder::Status status = mSf->setBootDisplayMode(mDisplayToken, mNewMode);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+ gui::DynamicDisplayInfo info;
+ status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ EXPECT_EQ(mNewMode, info.preferredBootDisplayMode);
+}
+
+TEST_F(BootDisplayModeTest, clearBootDisplayMode) {
+ // Clear once to figure out what the system default is
+ binder::Status status = mSf->clearBootDisplayMode(mDisplayToken);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+ gui::DynamicDisplayInfo info;
+ status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+ const ui::DisplayModeId systemMode = info.preferredBootDisplayMode;
+ const ui::DisplayModeId newMode = systemMode == mOldMode ? mNewMode : mOldMode;
+
+ // Now set a new mode and clear the boot mode again to figure out if the api worked.
+ status = mSf->setBootDisplayMode(mDisplayToken, newMode);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+ status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ EXPECT_EQ(newMode, info.preferredBootDisplayMode);
+
+ status = mSf->clearBootDisplayMode(mDisplayToken);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+
+ status = mSf->getDynamicDisplayInfoFromToken(mDisplayToken, &info);
+ EXPECT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ EXPECT_EQ(systemMode, info.preferredBootDisplayMode);
}
} // namespace android
diff --git a/services/surfaceflinger/tests/BufferGenerator.cpp b/services/surfaceflinger/tests/BufferGenerator.cpp
index 47a150dd35..d74bd55987 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<BufferItemConsumer>::make(consumer, GraphicBuffer::USAGE_HW_TEXTURE);
- mListener = new BufferListener(consumer, callback);
+ mListener = sp<BufferListener>::make(consumer, callback);
mBufferItemConsumer->setFrameAvailableListener(mListener);
- mSurface = new Surface(producer, true);
+ mSurface = sp<Surface>::make(producer, true);
}
/* Used by Egl manager. The surface is never displayed. */
@@ -364,7 +365,7 @@ status_t BufferGenerator::get(sp<GraphicBuffer>* outBuffer, sp<Fence>* outFence)
*outBuffer = mGraphicBuffer;
}
if (outFence) {
- *outFence = new Fence(mFence);
+ *outFence = sp<Fence>::make(mFence);
} else {
close(mFence);
}
diff --git a/services/surfaceflinger/tests/CommonTypes_test.cpp b/services/surfaceflinger/tests/CommonTypes_test.cpp
deleted file mode 100644
index 25b4615239..0000000000
--- a/services/surfaceflinger/tests/CommonTypes_test.cpp
+++ /dev/null
@@ -1,200 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-#include <aidl/android/hardware/graphics/common/BlendMode.h>
-#include <aidl/android/hardware/graphics/common/Dataspace.h>
-
-#include <android/data_space.h>
-#include <android/hardware/graphics/common/1.2/types.h>
-#include <android/hardware/graphics/composer/2.1/IComposerClient.h>
-
-using AidlBlendMode = aidl::android::hardware::graphics::common::BlendMode;
-using AidlDataspace = aidl::android::hardware::graphics::common::Dataspace;
-
-using HidlBlendMode = android::hardware::graphics::composer::V2_1::IComposerClient::BlendMode;
-using HidlDataspace = android::hardware::graphics::common::V1_2::Dataspace;
-
-static_assert(static_cast<uint32_t>(AidlBlendMode::INVALID) ==
- static_cast<uint32_t>(HidlBlendMode::INVALID));
-static_assert(static_cast<uint32_t>(AidlBlendMode::NONE) ==
- static_cast<uint32_t>(HidlBlendMode::NONE));
-static_assert(static_cast<uint32_t>(AidlBlendMode::PREMULTIPLIED) ==
- static_cast<uint32_t>(HidlBlendMode::PREMULTIPLIED));
-static_assert(static_cast<uint32_t>(AidlBlendMode::COVERAGE) ==
- static_cast<uint32_t>(HidlBlendMode::COVERAGE));
-
-static_assert(static_cast<uint32_t>(ADATASPACE_UNKNOWN) ==
- static_cast<uint32_t>(AidlDataspace::UNKNOWN));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB_LINEAR) ==
- static_cast<uint32_t>(AidlDataspace::SCRGB_LINEAR));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB) == static_cast<uint32_t>(AidlDataspace::SRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB) ==
- static_cast<uint32_t>(AidlDataspace::SCRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_DISPLAY_P3) ==
- static_cast<uint32_t>(AidlDataspace::DISPLAY_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020_PQ) ==
- static_cast<uint32_t>(AidlDataspace::BT2020_PQ));
-static_assert(static_cast<uint32_t>(ADATASPACE_ADOBE_RGB) ==
- static_cast<uint32_t>(AidlDataspace::ADOBE_RGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020) ==
- static_cast<uint32_t>(AidlDataspace::BT2020));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT709) ==
- static_cast<uint32_t>(AidlDataspace::BT709));
-static_assert(static_cast<uint32_t>(ADATASPACE_DCI_P3) ==
- static_cast<uint32_t>(AidlDataspace::DCI_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB_LINEAR) ==
- static_cast<uint32_t>(AidlDataspace::SRGB_LINEAR));
-
-static_assert(static_cast<uint32_t>(ADATASPACE_UNKNOWN) ==
- static_cast<uint32_t>(HidlDataspace::UNKNOWN));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::V0_SCRGB_LINEAR));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB) ==
- static_cast<uint32_t>(HidlDataspace::V0_SRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_SCRGB) ==
- static_cast<uint32_t>(HidlDataspace::V0_SCRGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_DISPLAY_P3) ==
- static_cast<uint32_t>(HidlDataspace::DISPLAY_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020_PQ) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_PQ));
-static_assert(static_cast<uint32_t>(ADATASPACE_ADOBE_RGB) ==
- static_cast<uint32_t>(HidlDataspace::ADOBE_RGB));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT2020) ==
- static_cast<uint32_t>(HidlDataspace::BT2020));
-static_assert(static_cast<uint32_t>(ADATASPACE_BT709) ==
- static_cast<uint32_t>(HidlDataspace::V0_BT709));
-static_assert(static_cast<uint32_t>(ADATASPACE_DCI_P3) ==
- static_cast<uint32_t>(HidlDataspace::DCI_P3));
-static_assert(static_cast<uint32_t>(ADATASPACE_SRGB_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::V0_SRGB_LINEAR));
-
-static_assert(static_cast<uint32_t>(AidlDataspace::UNKNOWN) ==
- static_cast<uint32_t>(HidlDataspace::UNKNOWN));
-static_assert(static_cast<uint32_t>(AidlDataspace::ARBITRARY) ==
- static_cast<uint32_t>(HidlDataspace::ARBITRARY));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_SHIFT) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_SHIFT));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_MASK) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_MASK));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_UNSPECIFIED) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_UNSPECIFIED));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT709) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT709));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_625) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_625));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_625_UNADJUSTED) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_625_UNADJUSTED));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_525) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_525));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT601_525_UNADJUSTED) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT601_525_UNADJUSTED));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT2020) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT2020));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT2020_CONSTANT_LUMINANCE) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT2020_CONSTANT_LUMINANCE));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_BT470M) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_BT470M));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_FILM) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_FILM));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_DCI_P3) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_DCI_P3));
-static_assert(static_cast<uint32_t>(AidlDataspace::STANDARD_ADOBE_RGB) ==
- static_cast<uint32_t>(HidlDataspace::STANDARD_ADOBE_RGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SHIFT) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_SHIFT));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_MASK) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_MASK));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_UNSPECIFIED) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_UNSPECIFIED));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SRGB) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_SRGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_SMPTE_170M) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_SMPTE_170M));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_2) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_2));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_6) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_6));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_GAMMA2_8) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_GAMMA2_8));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_ST2084) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_ST2084));
-static_assert(static_cast<uint32_t>(AidlDataspace::TRANSFER_HLG) ==
- static_cast<uint32_t>(HidlDataspace::TRANSFER_HLG));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_SHIFT) ==
- static_cast<uint32_t>(HidlDataspace::RANGE_SHIFT));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_MASK) ==
- static_cast<uint32_t>(HidlDataspace::RANGE_MASK));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_UNSPECIFIED) ==
- static_cast<uint32_t>(HidlDataspace::RANGE_UNSPECIFIED));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_FULL) ==
- static_cast<uint32_t>(HidlDataspace::RANGE_FULL));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_LIMITED) ==
- static_cast<uint32_t>(HidlDataspace::RANGE_LIMITED));
-static_assert(static_cast<uint32_t>(AidlDataspace::RANGE_EXTENDED) ==
- static_cast<uint32_t>(HidlDataspace::RANGE_EXTENDED));
-static_assert(static_cast<uint32_t>(AidlDataspace::SRGB_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::V0_SRGB_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::SCRGB_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::V0_SCRGB_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::SRGB) ==
- static_cast<uint32_t>(HidlDataspace::V0_SRGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::SCRGB) ==
- static_cast<uint32_t>(HidlDataspace::V0_SCRGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::JFIF) ==
- static_cast<uint32_t>(HidlDataspace::V0_JFIF));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT601_625) ==
- static_cast<uint32_t>(HidlDataspace::V0_BT601_625));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT601_525) ==
- static_cast<uint32_t>(HidlDataspace::V0_BT601_525));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT709) ==
- static_cast<uint32_t>(HidlDataspace::V0_BT709));
-static_assert(static_cast<uint32_t>(AidlDataspace::DCI_P3_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::DCI_P3_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::DCI_P3) ==
- static_cast<uint32_t>(HidlDataspace::DCI_P3));
-static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_P3_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::DISPLAY_P3_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_P3) ==
- static_cast<uint32_t>(HidlDataspace::DISPLAY_P3));
-static_assert(static_cast<uint32_t>(AidlDataspace::ADOBE_RGB) ==
- static_cast<uint32_t>(HidlDataspace::ADOBE_RGB));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_LINEAR) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_LINEAR));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020) ==
- static_cast<uint32_t>(HidlDataspace::BT2020));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_PQ) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_PQ));
-static_assert(static_cast<uint32_t>(AidlDataspace::DEPTH) ==
- static_cast<uint32_t>(HidlDataspace::DEPTH));
-static_assert(static_cast<uint32_t>(AidlDataspace::SENSOR) ==
- static_cast<uint32_t>(HidlDataspace::SENSOR));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_ITU));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU_PQ) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_ITU_PQ));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_ITU_HLG) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_ITU_HLG));
-static_assert(static_cast<uint32_t>(AidlDataspace::BT2020_HLG) ==
- static_cast<uint32_t>(HidlDataspace::BT2020_HLG));
-static_assert(static_cast<uint32_t>(AidlDataspace::DISPLAY_BT2020) ==
- static_cast<uint32_t>(HidlDataspace::DISPLAY_BT2020));
-static_assert(static_cast<uint32_t>(AidlDataspace::DYNAMIC_DEPTH) ==
- static_cast<uint32_t>(HidlDataspace::DYNAMIC_DEPTH));
-static_assert(static_cast<uint32_t>(AidlDataspace::JPEG_APP_SEGMENTS) ==
- static_cast<uint32_t>(HidlDataspace::JPEG_APP_SEGMENTS));
-static_assert(static_cast<uint32_t>(AidlDataspace::HEIF) ==
- static_cast<uint32_t>(HidlDataspace::HEIF));
diff --git a/services/surfaceflinger/tests/Credentials_test.cpp b/services/surfaceflinger/tests/Credentials_test.cpp
index 434c297cc9..822ac4d99f 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 <android/gui/ISurfaceComposer.h>
#include <gtest/gtest.h>
-#include <gui/ISurfaceComposer.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/LayerDebugInfo.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>
#include <private/android_filesystem_config.h>
-#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
#include <ui/DisplayMode.h>
#include <ui/DynamicDisplayInfo.h>
#include <utils/String8.h>
#include <functional>
#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<SurfaceControl> mVirtualSurfaceControl;
void initClient() {
- mComposerClient = new SurfaceComposerClient;
+ mComposerClient = sp<SurfaceComposerClient>::make();
ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
}
+ static sp<IBinder> getFirstDisplayToken() {
+ const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ if (ids.empty()) {
+ return nullptr;
+ }
+
+ return SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+ }
+
+ static std::optional<uint64_t> 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<SurfaceComposerClient>::make();
ASSERT_NO_FATAL_FAILURE(initClient());
}
}
TEST_F(CredentialsTest, GetBuiltInDisplayAccessTest) {
- std::function<bool()> condition = [] {
- return SurfaceComposerClient::getInternalDisplayToken() != nullptr;
- };
+ std::function<bool()> 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<ui::DisplayMode> 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<status_t()> condition = [=]() {
ui::DynamicDisplayInfo info;
- return SurfaceComposerClient::getDynamicDisplayInfo(display, &info);
+ return SurfaceComposerClient::getDynamicDisplayInfoFromId(*id, &info);
};
ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, NO_ERROR));
}
TEST_F(CredentialsTest, GetDisplayNativePrimariesTest) {
- const auto display = SurfaceComposerClient::getInternalDisplayToken();
+ const auto display = getFirstDisplayToken();
std::function<status_t()> 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<status_t()> condition = [=]() {
- return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, defaultMode,
- allowGroupSwitching, primaryFpsMin,
- primaryFpsMax, appRequestFpsMin,
- appRequestFpsMax);
+ return SurfaceComposerClient::setDesiredDisplayModeSpecs(display, specs);
};
ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
}
TEST_F(CredentialsTest, SetActiveColorModeTest) {
- const auto display = SurfaceComposerClient::getInternalDisplayToken();
+ const auto display = getFirstDisplayToken();
std::function<status_t()> condition = [=]() {
return SurfaceComposerClient::setActiveColorMode(display, ui::ColorMode::NATIVE);
};
@@ -270,18 +275,6 @@ TEST_F(CredentialsTest, CreateDisplayTest) {
ASSERT_NO_FATAL_FAILURE(checkWithPrivileges(condition, true, false));
}
-TEST_F(CredentialsTest, CaptureTest) {
- const auto display = SurfaceComposerClient::getInternalDisplayToken();
- std::function<status_t()> condition = [=]() {
- sp<GraphicBuffer> outBuffer;
- DisplayCaptureArgs captureArgs;
- captureArgs.displayToken = display;
- ScreenCaptureResults captureResults;
- return ScreenCapture::captureDisplay(captureArgs, captureResults);
- };
- ASSERT_NO_FATAL_FAILURE(checkWithPrivileges<status_t>(condition, NO_ERROR, PERMISSION_DENIED));
-}
-
TEST_F(CredentialsTest, CaptureLayersTest) {
setupBackgroundSurface();
sp<GraphicBuffer> outBuffer;
@@ -301,39 +294,45 @@ TEST_F(CredentialsTest, CaptureLayersTest) {
*/
TEST_F(CredentialsTest, GetLayerDebugInfo) {
setupBackgroundSurface();
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+ sp<gui::ISurfaceComposer> 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<LayerDebugInfo> 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 +349,7 @@ TEST_F(CredentialsTest, IsWideColorDisplayBasicCorrectness) {
}
TEST_F(CredentialsTest, IsWideColorDisplayWithPrivileges) {
- const auto display = SurfaceComposerClient::getInternalDisplayToken();
+ const auto display = getFirstDisplayToken();
ASSERT_FALSE(display == nullptr);
std::function<status_t()> condition = [=]() {
bool result = false;
@@ -360,14 +359,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<ColorMode>(BAD_VALUE), colorMode);
}
+TEST_F(CredentialsTest, TransactionPermissionTest) {
+ WindowInfosListenerUtils windowInfosListenerUtils;
+ std::string name = "Test Layer";
+ sp<IBinder> token = sp<BBinder>::make();
+ WindowInfo windowInfo;
+ windowInfo.name = name;
+ windowInfo.token = token;
+ sp<SurfaceControl> 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<WindowInfo>& 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<WindowInfo>& 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 2dc96b8511..aeff5a530d 100644
--- a/services/surfaceflinger/tests/DisplayConfigs_test.cpp
+++ b/services/surfaceflinger/tests/DisplayConfigs_test.cpp
@@ -39,114 +39,64 @@ 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);
+ void testSetDesiredDisplayModeSpecs(bool allowGroupSwitching = false) {
+ ui::DynamicDisplayInfo info;
+ status_t res =
+ SurfaceComposerClient::getDynamicDisplayInfoFromId(static_cast<int64_t>(mDisplayId),
+ &info);
+ const auto& modes = info.supportedDisplayModes;
+ ASSERT_EQ(res, NO_ERROR);
+ ASSERT_GT(modes.size(), 0);
+ for (const auto& mode : modes) {
+ gui::DisplayModeSpecs setSpecs;
+ setSpecs.defaultMode = mode.id;
+ setSpecs.allowGroupSwitching = allowGroupSwitching;
+ setSpecs.primaryRanges.physical.min = mode.peakRefreshRate;
+ setSpecs.primaryRanges.physical.max = mode.peakRefreshRate;
+ setSpecs.primaryRanges.render = setSpecs.primaryRanges.physical;
+ setSpecs.appRequestRanges = setSpecs.primaryRanges;
+
+ res = SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, setSpecs);
+ ASSERT_EQ(res, NO_ERROR);
+ gui::DisplayModeSpecs getSpecs;
+ res = SurfaceComposerClient::getDesiredDisplayModeSpecs(mDisplayToken, &getSpecs);
+ ASSERT_EQ(res, NO_ERROR);
+ ASSERT_EQ(setSpecs, getSpecs);
+ }
+ }
sp<IBinder> mDisplayToken;
+ uint64_t mDisplayId;
};
TEST_F(RefreshRateRangeTest, setAllConfigs) {
- ui::DynamicDisplayInfo info;
- status_t res = SurfaceComposerClient::getDynamicDisplayInfo(mDisplayToken, &info);
- const auto& modes = info.supportedDisplayModes;
- ASSERT_EQ(res, NO_ERROR);
- ASSERT_GT(modes.size(), 0);
-
- 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);
- 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);
- 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);
- }
-}
-
-void RefreshRateRangeTest::testSetAllowGroupSwitching(bool allowGroupSwitching) {
- status_t res =
- SurfaceComposerClient::setDesiredDisplayModeSpecs(mDisplayToken, 0, allowGroupSwitching,
- 0.f, 90.f, 0.f, 90.f);
- 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);
- 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);
+ testSetDesiredDisplayModeSpecs();
}
TEST_F(RefreshRateRangeTest, setAllowGroupSwitching) {
- testSetAllowGroupSwitching(true);
- testSetAllowGroupSwitching(false);
- testSetAllowGroupSwitching(true);
+ testSetDesiredDisplayModeSpecs(/*allowGroupSwitching=*/true);
+ testSetDesiredDisplayModeSpecs(/*allowGroupSwitching=*/false);
+ testSetDesiredDisplayModeSpecs(/*allowGroupSwitching=*/true);
}
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra" \ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp b/services/surfaceflinger/tests/DisplayEventReceiver_test.cpp
index 0e54664f77..4c26017241 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<size_t>::max(), vsyncEventData.preferredFrameTimelineIndex);
+ EXPECT_GT(static_cast<int64_t>(vsyncEventData.frameTimelinesLength), 0)
+ << "Frame timelines length should be greater than 0";
+ EXPECT_LE(static_cast<int64_t>(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 9fa0452915..92ebb8d73c 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);
@@ -118,7 +120,7 @@ TEST_F(EffectLayerTest, BlurEffectLayerIsVisible) {
const auto canvasSize = 256;
sp<SurfaceControl> leftLayer = createColorLayer("Left", Color::BLUE);
- sp<SurfaceControl> rightLayer = createColorLayer("Right", Color::GREEN);
+ sp<SurfaceControl> rightLayer = createColorLayer("Right", Color::RED);
sp<SurfaceControl> blurLayer;
const auto leftRect = Rect(0, 0, canvasSize / 2, canvasSize);
const auto rightRect = Rect(canvasSize / 2, 0, canvasSize, canvasSize);
@@ -138,12 +140,12 @@ TEST_F(EffectLayerTest, BlurEffectLayerIsVisible) {
{
auto shot = screenshot();
shot->expectColor(leftRect, Color::BLUE);
- shot->expectColor(rightRect, Color::GREEN);
+ shot->expectColor(rightRect, Color::RED);
}
ASSERT_NO_FATAL_FAILURE(blurLayer = createColorLayer("BackgroundBlur", Color::TRANSPARENT));
- const auto blurRadius = canvasSize / 2;
+ const auto blurRadius = canvasSize / 4;
asTransaction([&](Transaction& t) {
t.setLayer(blurLayer, mLayerZBase + 3);
t.reparent(blurLayer, mParentLayer);
@@ -157,27 +159,34 @@ TEST_F(EffectLayerTest, BlurEffectLayerIsVisible) {
auto shot = screenshot();
const auto stepSize = 1;
- const auto blurAreaOffset = blurRadius * 0.7f;
- const auto blurAreaStartX = canvasSize / 2 - blurRadius + blurAreaOffset;
- const auto blurAreaEndX = canvasSize / 2 + blurRadius - blurAreaOffset;
+ const auto expectedBlurAreaSize = blurRadius * 1.5f;
+ const auto blurAreaStartX = canvasSize / 2 - expectedBlurAreaSize / 2;
+ const auto blurAreaEndX = canvasSize / 2 + expectedBlurAreaSize / 2;
+ // testAreaEndY is needed because the setBackgroundBlurRadius API blurs everything behind
+ // the surface, which means it samples pixels from outside the canvasSize and we get some
+ // unexpected colors in the screenshot.
+ const auto testAreaEndY = canvasSize - blurRadius * 2;
+
Color previousColor;
Color currentColor;
- for (int y = 0; y < canvasSize; y++) {
+ for (int y = 0; y < testAreaEndY; y++) {
shot->checkPixel(0, y, /* r = */ 0, /* g = */ 0, /* b = */ 255);
previousColor = shot->getPixelColor(0, y);
for (int x = blurAreaStartX; x < blurAreaEndX; x += stepSize) {
currentColor = shot->getPixelColor(x, y);
- ASSERT_GT(currentColor.g, previousColor.g);
+ ASSERT_GT(currentColor.r, previousColor.r);
ASSERT_LT(currentColor.b, previousColor.b);
- ASSERT_EQ(0, currentColor.r);
+ ASSERT_EQ(0, currentColor.g);
}
- shot->checkPixel(canvasSize - 1, y, 0, 255, 0);
+ shot->checkPixel(canvasSize - 1, y, 255, 0, 0);
}
}
}
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/HdrSdrRatioOverlay_test.cpp b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
new file mode 100644
index 0000000000..77a8f9c79b
--- /dev/null
+++ b/services/surfaceflinger/tests/HdrSdrRatioOverlay_test.cpp
@@ -0,0 +1,89 @@
+/**
+ * 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 <thread>
+
+#include <gtest/gtest.h>
+
+#include <gui/SurfaceComposerClient.h>
+#include <private/gui/ComposerService.h>
+#include <chrono>
+
+using ::std::literals::chrono_literals::operator""s;
+
+static constexpr int kHdrSdrRatioOverlayCode = 1043;
+static constexpr int kHdrSdrRatioOverlayEnable = 1;
+static constexpr int kHdrSdrRatioOverlayDisable = 0;
+static constexpr int kHdrSdrRatioOverlayQuery = 2;
+
+// These values must match the ones we used for developer options in
+// com.android.settings.development.ShowHdrSdrRatioPreferenceController
+static_assert(kHdrSdrRatioOverlayCode == 1043);
+static_assert(kHdrSdrRatioOverlayEnable == 1);
+static_assert(kHdrSdrRatioOverlayDisable == 0);
+static_assert(kHdrSdrRatioOverlayQuery == 2);
+
+namespace android {
+
+namespace {
+void sendCommandToSf(int command, Parcel& reply) {
+ sp<ISurfaceComposer> sf(ComposerService::getComposerService());
+ Parcel request;
+ request.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
+ request.writeInt32(command);
+ ASSERT_EQ(NO_ERROR,
+ IInterface::asBinder(sf)->transact(kHdrSdrRatioOverlayCode, request, &reply));
+}
+
+bool isOverlayEnabled() {
+ Parcel reply;
+ sendCommandToSf(kHdrSdrRatioOverlayQuery, reply);
+ return reply.readBool();
+}
+
+void waitForOverlay(bool enabled) {
+ static constexpr auto kTimeout = std::chrono::nanoseconds(1s);
+ static constexpr auto kIterations = 10;
+ for (int i = 0; i < kIterations; i++) {
+ if (enabled == isOverlayEnabled()) {
+ return;
+ }
+ std::this_thread::sleep_for(kTimeout / kIterations);
+ }
+}
+
+void toggleOverlay(bool enabled) {
+ if (enabled == isOverlayEnabled()) {
+ return;
+ }
+
+ Parcel reply;
+ const auto command = enabled ? kHdrSdrRatioOverlayEnable : kHdrSdrRatioOverlayDisable;
+ sendCommandToSf(command, reply);
+ waitForOverlay(enabled);
+ ASSERT_EQ(enabled, isOverlayEnabled());
+}
+
+} // namespace
+
+TEST(HdrSdrRatioOverlayTest, enableAndDisableOverlay) {
+ toggleOverlay(true);
+ toggleOverlay(false);
+
+ toggleOverlay(true);
+ toggleOverlay(false);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/IPC_test.cpp b/services/surfaceflinger/tests/IPC_test.cpp
index ce94dab907..18bd3b92d2 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<SurfaceComposerClient>::make();
auto err = mClient->initCheck();
return err;
}
@@ -221,10 +221,12 @@ public:
ProcessState::self()->startThreadPool();
}
void SetUp() {
- mClient = new SurfaceComposerClient;
+ mClient = sp<SurfaceComposerClient>::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();
@@ -287,7 +289,7 @@ sp<IIPCTest> IPCTest::initRemoteService() {
IPCThreadState::self()->joinThreadPool();
[&]() { exit(0); }();
}
- sp<IBinder> binder = defaultServiceManager()->getService(serviceName);
+ sp<IBinder> binder = defaultServiceManager()->waitForService(serviceName);
remote = interface_cast<IIPCTest>(binder);
remote->setDeathToken(mDeathRecipient);
}
diff --git a/services/surfaceflinger/tests/InvalidHandles_test.cpp b/services/surfaceflinger/tests/InvalidHandles_test.cpp
index d192a2d83d..666ce76c6b 100644
--- a/services/surfaceflinger/tests/InvalidHandles_test.cpp
+++ b/services/surfaceflinger/tests/InvalidHandles_test.cpp
@@ -42,13 +42,13 @@ protected:
sp<SurfaceComposerClient> mScc;
sp<SurfaceControl> mNotSc;
void SetUp() override {
- mScc = new SurfaceComposerClient;
+ mScc = sp<SurfaceComposerClient>::make();
ASSERT_EQ(NO_ERROR, mScc->initCheck());
mNotSc = makeNotSurfaceControl();
}
sp<SurfaceControl> makeNotSurfaceControl() {
- return new SurfaceControl(mScc, new NotALayer(), nullptr, true);
+ return sp<SurfaceControl>::make(mScc, sp<NotALayer>::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 0000000000..00e134b4d2
--- /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 <chrono> // std::chrono::seconds
+#include <thread> // 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<half3(Color)> toHalf3;
+ std::function<half4(Color)> toHalf4;
+ sp<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> layer =
+ mClient->createSurface(String8("BufferState"), 0 /* width */, 0 /* height */,
+ PIXEL_FORMAT_RGBA_8888,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ mContainerLayer->getHandle());
+
+ sp<GraphicBuffer> buffer =
+ sp<GraphicBuffer>::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 219db8c148..79886bde45 100644
--- a/services/surfaceflinger/tests/LayerCallback_test.cpp
+++ b/services/surfaceflinger/tests/LayerCallback_test.cpp
@@ -51,7 +51,7 @@ public:
LayerTransactionTest::TearDown();
}
- virtual sp<SurfaceControl> createBufferStateLayer() {
+ virtual sp<SurfaceControl> createLayerWithBuffer() {
return createLayer(mClient, "test", 0, 0, ISurfaceComposerClient::eFXSurfaceBufferState);
}
@@ -164,7 +164,7 @@ public:
TEST_F(LayerCallbackTest, BufferColor) {
sp<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceComposerClient> client1(new SurfaceComposerClient),
- client2(new SurfaceComposerClient);
+ sp<SurfaceComposerClient> client1(sp<SurfaceComposerClient>::make()),
+ client2(sp<SurfaceComposerClient>::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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceComposerClient> client1(new SurfaceComposerClient),
- client2(new SurfaceComposerClient);
+ sp<SurfaceComposerClient> client1(sp<SurfaceComposerClient>::make()),
+ client2(sp<SurfaceComposerClient>::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<SurfaceComposerClient> client1(new SurfaceComposerClient),
- client2(new SurfaceComposerClient);
+ sp<SurfaceComposerClient> client1(sp<SurfaceComposerClient>::make()),
+ client2(sp<SurfaceComposerClient>::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<SurfaceComposerClient> client1(new SurfaceComposerClient),
- client2(new SurfaceComposerClient);
+ sp<SurfaceComposerClient> client1(sp<SurfaceComposerClient>::make()),
+ client2(sp<SurfaceComposerClient>::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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> layer;
- ASSERT_NO_FATAL_FAILURE(layer = createBufferStateLayer());
+ ASSERT_NO_FATAL_FAILURE(layer = createLayerWithBuffer());
sp<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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 0e2bc3df0e..2b1834d8e4 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<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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<GraphicBuffer>::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<SurfaceControl> 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<SurfaceControl> 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<GraphicBuffer> buffer =
- new GraphicBuffer(32, 64, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<SurfaceControl> 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<GraphicBuffer>::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<GraphicBuffer>::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<GraphicBuffer>::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<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test");
TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED);
sp<Fence> fence;
@@ -1370,7 +1371,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetFenceNull_BufferState) {
layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
sp<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::make(32u, 32u, PIXEL_FORMAT_RGBA_8888, 1u, kUsageFlags, "test");
TransactionUtils::fillGraphicBufferColor(buffer, Rect(0, 0, 32, 32), Color::RED);
sp<Fence> fence = Fence::NO_FENCE;
@@ -1388,7 +1389,7 @@ TEST_P(LayerRenderTypeTransactionTest, SetDataspaceBasic_BufferState) {
layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
sp<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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<GraphicBuffer> buffer =
- new GraphicBuffer(32, 32, PIXEL_FORMAT_RGBA_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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();
@@ -1478,15 +1479,11 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorTransformBasic) {
matrix[2][2] = 0.11;
// degamma before applying the matrix
- if (mColorManagementUsed) {
- ColorTransformHelper::DegammaColor(expected);
- }
+ ColorTransformHelper::DegammaColor(expected);
ColorTransformHelper::applyMatrix(expected, matrix);
- if (mColorManagementUsed) {
- ColorTransformHelper::GammaColor(expected);
- }
+ ColorTransformHelper::GammaColor(expected);
const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
uint8_t(expected.b * 255), 255};
@@ -1536,15 +1533,11 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorTransformOnParent) {
matrix[2][2] = 0.11;
// degamma before applying the matrix
- if (mColorManagementUsed) {
- ColorTransformHelper::DegammaColor(expected);
- }
+ ColorTransformHelper::DegammaColor(expected);
ColorTransformHelper::applyMatrix(expected, matrix);
- if (mColorManagementUsed) {
- ColorTransformHelper::GammaColor(expected);
- }
+ ColorTransformHelper::GammaColor(expected);
const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
uint8_t(expected.b * 255), 255};
@@ -1607,16 +1600,12 @@ TEST_P(LayerRenderTypeTransactionTest, SetColorTransformOnChildAndParent) {
matrixParent[2][2] = 0.10;
// degamma before applying the matrix
- if (mColorManagementUsed) {
- ColorTransformHelper::DegammaColor(expected);
- }
+ ColorTransformHelper::DegammaColor(expected);
ColorTransformHelper::applyMatrix(expected, matrixChild);
ColorTransformHelper::applyMatrix(expected, matrixParent);
- if (mColorManagementUsed) {
- ColorTransformHelper::GammaColor(expected);
- }
+ ColorTransformHelper::GammaColor(expected);
const Color expectedColor = {uint8_t(expected.r * 255), uint8_t(expected.g * 255),
uint8_t(expected.b * 255), 255};
@@ -1635,6 +1624,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<SurfaceControl> layer;
+ ASSERT_NO_FATAL_FAILURE(
+ layer = createLayer("test", 32, 32, ISurfaceComposerClient::eFXSurfaceBufferState));
+
+ sp<GraphicBuffer> buffer =
+ sp<GraphicBuffer>::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<SurfaceControl> 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 094b0ffc0f..15a98df275 100644
--- a/services/surfaceflinger/tests/LayerState_test.cpp
+++ b/services/surfaceflinger/tests/LayerState_test.cpp
@@ -35,10 +35,9 @@ TEST(LayerStateTest, ParcellingDisplayCaptureArgs) {
args.frameScaleX = 2;
args.frameScaleY = 4;
args.captureSecureLayers = true;
- args.displayToken = new BBinder();
+ args.displayToken = sp<BBinder>::make();
args.width = 10;
args.height = 20;
- args.useIdentityTransform = true;
args.grayscale = true;
Parcel p;
@@ -56,7 +55,6 @@ TEST(LayerStateTest, ParcellingDisplayCaptureArgs) {
ASSERT_EQ(args.displayToken, args2.displayToken);
ASSERT_EQ(args.width, args2.width);
ASSERT_EQ(args.height, args2.height);
- ASSERT_EQ(args.useIdentityTransform, args2.useIdentityTransform);
ASSERT_EQ(args.grayscale, args2.grayscale);
}
@@ -67,8 +65,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<BBinder>::make();
+ args.excludeHandles = {sp<BBinder>::make(), sp<BBinder>::make()};
args.childrenOnly = false;
args.grayscale = true;
@@ -90,13 +88,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<GraphicBuffer>::make(100u, 200u, PIXEL_FORMAT_RGBA_8888, 1u, 0u);
+ results.fenceResult = sp<Fence>::make(dup(fileno(tmpfile())));
results.capturedSecureLayers = true;
results.capturedDataspace = ui::Dataspace::DISPLAY_P3;
- results.result = BAD_VALUE;
Parcel p;
results.writeToParcel(&p);
@@ -110,10 +107,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 6bd7920a62..c9af432513 100644
--- a/services/surfaceflinger/tests/LayerTransactionTest.h
+++ b/services/surfaceflinger/tests/LayerTransactionTest.h
@@ -23,9 +23,11 @@
#include <cutils/properties.h>
#include <gtest/gtest.h>
+#include <gui/AidlStatusUtil.h>
#include <gui/ISurfaceComposer.h>
#include <gui/SurfaceComposerClient.h>
#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
#include <ui/DisplayMode.h>
#include "BufferGenerator.h"
@@ -39,15 +41,12 @@ using android::hardware::graphics::common::V1_1::BufferUsage;
class LayerTransactionTest : public ::testing::Test {
protected:
void SetUp() override {
- mClient = new SurfaceComposerClient;
+ mClient = sp<SurfaceComposerClient>::make();
ASSERT_EQ(NO_ERROR, mClient->initCheck()) << "failed to create SurfaceComposerClient";
ASSERT_NO_FATAL_FAILURE(SetUpDisplay());
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
- ASSERT_NO_FATAL_FAILURE(sf->getColorManagement(&mColorManagementUsed));
-
- mCaptureArgs.displayToken = mDisplay;
+ sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
}
virtual void TearDown() {
@@ -134,13 +133,16 @@ protected:
postBufferQueueLayerBuffer(layer);
}
- virtual void fillBufferStateLayerColor(const sp<SurfaceControl>& layer, const Color& color,
- int32_t bufferWidth, int32_t bufferHeight) {
+ virtual void fillBufferLayerColor(const sp<SurfaceControl>& layer, const Color& color,
+ int32_t bufferWidth, int32_t bufferHeight) {
sp<GraphicBuffer> 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<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+ static_cast<uint32_t>(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 +155,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 +208,13 @@ protected:
const Color& topRight, const Color& bottomLeft,
const Color& bottomRight) {
sp<GraphicBuffer> 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<GraphicBuffer>::make(static_cast<uint32_t>(bufferWidth),
+ static_cast<uint32_t>(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 +229,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<ScreenCapture> screenshot() {
@@ -273,14 +278,13 @@ protected:
const int32_t mLayerZBase = std::numeric_limits<int32_t>::max() - 256;
sp<SurfaceControl> mBlackBgSurface;
- bool mColorManagementUsed;
-
- DisplayCaptureArgs mCaptureArgs;
ScreenCaptureResults mCaptureResults;
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;
@@ -292,7 +296,7 @@ private:
// After a new buffer is queued, SurfaceFlinger is notified and will
// latch the new buffer on next vsync. Let's heuristically wait for 3
// vsyncs.
- mBufferPostDelay = static_cast<int32_t>(1e6 / mode.refreshRate) * 3;
+ mBufferPostDelay = static_cast<int32_t>(1e6 / mode.peakRefreshRate) * 3;
mBlackBgSurface =
createSurface(mClient, "BaseSurface", 0 /* buffer width */, 0 /* buffer height */,
diff --git a/services/surfaceflinger/tests/LayerTransaction_test.cpp b/services/surfaceflinger/tests/LayerTransaction_test.cpp
index ef992d6a40..03de8d0b6d 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<SurfaceControl> layer;
ASSERT_NO_FATAL_FAILURE(layer = createLayer("test", width, height));
const auto producer = layer->getIGraphicBufferProducer();
- const sp<IProducerListener> stubListener(new StubProducerListener);
+ const sp<IProducerListener> stubListener(sp<StubProducerListener>::make());
IGraphicBufferProducer::QueueBufferOutput queueBufferOutput;
ASSERT_EQ(OK, producer->connect(stubListener, NATIVE_WINDOW_API_CPU, true, &queueBufferOutput));
@@ -154,6 +154,65 @@ 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<SurfaceControl> 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<SurfaceControl> 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);
+ }
+}
+
+TEST_F(LayerTransactionTest, CommitCallbackCalledOnce) {
+ auto callCount = 0;
+ auto commitCallback =
+ [&callCount](void* /* context */, nsecs_t /* latchTime */,
+ const sp<Fence>& /* presentFence */,
+ const std::vector<SurfaceControlStats>& /* stats */) mutable {
+ callCount++;
+ };
+
+ // Create two transactions that both contain the same callback id.
+ Transaction t1;
+ t1.addTransactionCommittedCallback(commitCallback, nullptr);
+ Parcel parcel;
+ t1.writeToParcel(&parcel);
+ parcel.setDataPosition(0);
+ Transaction t2;
+ t2.readFromParcel(&parcel);
+
+ // Apply the two transactions. There is a race here as we can't guarantee that the two
+ // transactions will be applied within the same SurfaceFlinger commit. If the transactions are
+ // applied within the same commit then we verify that callback ids are deduplicated within a
+ // single commit. Otherwise, we verify that commit callbacks are deduplicated across separate
+ // commits.
+ t1.apply();
+ t2.apply(/*synchronous=*/true);
+
+ ASSERT_EQ(callCount, 1);
+}
+
} // 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 0000000000..2be8d3b013
--- /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 <gui/BufferItemConsumer.h>
+#include <ui/Transform.h>
+#include <thread>
+#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<SurfaceControl> makeLayer() {
+ sp<SurfaceControl> layer =
+ createLayer("test", 100, 100, ISurfaceComposerClient::eFXSurfaceBufferState,
+ mBlackBgSurface.get());
+ fillBufferLayerColor(layer, Color::RED, 100, 100);
+ return layer;
+ }
+ sp<SurfaceControl> mainLayer;
+ PresentationCallbackHelper pch;
+ SurfaceComposerClient::Transaction t;
+ TrustedPresentationThresholds thresholds;
+ sp<SurfaceComposerClient::PresentationCallbackRAII> 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 9cb617a980..f247c9f088 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> surface = layer->getSurface();
sp<GraphicBuffer> buffer =
- new GraphicBuffer(width, height, PIXEL_FORMAT_RGBX_8888, 1, kUsageFlags, "test");
+ sp<GraphicBuffer>::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<GraphicBuffer>::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/LayerTypeTransaction_test.cpp b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
index 34c918261a..f9b4bbac22 100644
--- a/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
+++ b/services/surfaceflinger/tests/LayerTypeTransaction_test.cpp
@@ -167,18 +167,18 @@ TEST_P(LayerTypeTransactionTest, SetFlagsSecure) {
.setFlags(layer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
.apply(true);
- DisplayCaptureArgs args;
- args.displayToken = mDisplay;
+ LayerCaptureArgs args;
+ args.layerHandle = layer->getHandle();
ScreenCaptureResults captureResults;
{
// Ensure the UID is not root because root has all permissions
UIDFaker f(AID_APP_START);
- ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(args, captureResults));
+ ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(args, captureResults));
}
Transaction().setFlags(layer, 0, layer_state_t::eLayerSecure).apply(true);
- ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, captureResults));
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(args, captureResults));
}
TEST_P(LayerTypeTransactionTest, RefreshRateIsInitialized) {
diff --git a/services/surfaceflinger/tests/LayerUpdate_test.cpp b/services/surfaceflinger/tests/LayerUpdate_test.cpp
index e1a7ecc03b..867eddbc44 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 a921aa810e..0ea0824732 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 <android-base/properties.h>
#include <private/android_filesystem_config.h>
#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<SurfaceControl> bufferStateLayer =
- createLayer("BufferStateLayer", 200, 200, ISurfaceComposerClient::eFXSurfaceBufferState,
+ sp<SurfaceControl> 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<SurfaceControl> 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 1ed6c65afb..15ff696412 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/OWNERS b/services/surfaceflinger/tests/OWNERS
new file mode 100644
index 0000000000..56f2f1b07a
--- /dev/null
+++ b/services/surfaceflinger/tests/OWNERS
@@ -0,0 +1,8 @@
+per-file HdrSdrRatioOverlay_test.cpp = alecmouri@google.com, sallyqi@google.com, jreck@google.com
+
+# Most layer-related files are owned by WM
+per-file Layer* = set noparent
+per-file Layer* = pdwilliams@google.com, vishnun@google.com, melodymhsu@google.com
+
+per-file LayerHistoryTest.cpp = file:/services/surfaceflinger/OWNERS
+per-file LayerInfoTest.cpp = file:/services/surfaceflinger/OWNERS \ No newline at end of file
diff --git a/services/surfaceflinger/tests/RelativeZ_test.cpp b/services/surfaceflinger/tests/RelativeZ_test.cpp
index 50a4092ddb..9cebf11b9c 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 a6d7f5834f..c23fb9bd23 100644
--- a/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
+++ b/services/surfaceflinger/tests/ReleaseBufferCallback_test.cpp
@@ -85,7 +85,8 @@ public:
sp<Fence> 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<GraphicBuffer> 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<GraphicBuffer>::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<GraphicBuffer> 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<TransactionCompletedListener> firstCompletedListener = new TransactionCompletedListener();
- sp<TransactionCompletedListener> secondCompletedListener = new TransactionCompletedListener();
+ sp<TransactionCompletedListener> firstCompletedListener =
+ sp<TransactionCompletedListener>::make();
+ sp<TransactionCompletedListener> secondCompletedListener =
+ sp<TransactionCompletedListener>::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<GraphicBuffer> 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<GraphicBuffer> 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<TransactionCompletedListener> firstCompletedListener = new TransactionCompletedListener();
- sp<TransactionCompletedListener> secondCompletedListener = new TransactionCompletedListener();
+ sp<TransactionCompletedListener> firstCompletedListener =
+ sp<TransactionCompletedListener>::make();
+ sp<TransactionCompletedListener> secondCompletedListener =
+ sp<TransactionCompletedListener>::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<GraphicBuffer> 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 6a7d8b8ca5..18262f664a 100644
--- a/services/surfaceflinger/tests/ScreenCapture_test.cpp
+++ b/services/surfaceflinger/tests/ScreenCapture_test.cpp
@@ -15,10 +15,13 @@
*/
// TODO(b/129481165): remove the #pragma below and fix conversion issues
+#include <sys/types.h>
+#include <cstdint>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
#include <private/android_filesystem_config.h>
+#include <ui/DisplayState.h>
#include "LayerTransactionTest.h"
@@ -30,24 +33,22 @@ protected:
LayerTransactionTest::SetUp();
ASSERT_EQ(NO_ERROR, mClient->initCheck());
- 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;
-
- mDisplaySize = resolution;
+ // Root surface
+ mRootSurfaceControl =
+ createLayer(String8("RootTestSurface"), mDisplayWidth, mDisplayHeight, 0);
+ ASSERT_TRUE(mRootSurfaceControl != nullptr);
+ ASSERT_TRUE(mRootSurfaceControl->isValid());
// Background surface
- mBGSurfaceControl = createLayer(String8("BG Test Surface"), resolution.getWidth(),
- resolution.getHeight(), 0);
+ mBGSurfaceControl = createLayer(String8("BG Test Surface"), mDisplayWidth, mDisplayHeight,
+ 0, mRootSurfaceControl.get());
ASSERT_TRUE(mBGSurfaceControl != nullptr);
ASSERT_TRUE(mBGSurfaceControl->isValid());
TransactionUtils::fillSurfaceRGBA8(mBGSurfaceControl, 63, 63, 195);
// Foreground surface
- mFGSurfaceControl = createLayer(String8("FG Test Surface"), 64, 64, 0);
+ mFGSurfaceControl =
+ createLayer(String8("FG Test Surface"), 64, 64, 0, mRootSurfaceControl.get());
ASSERT_TRUE(mFGSurfaceControl != nullptr);
ASSERT_TRUE(mFGSurfaceControl->isValid());
@@ -55,7 +56,7 @@ protected:
TransactionUtils::fillSurfaceRGBA8(mFGSurfaceControl, 195, 63, 63);
asTransaction([&](Transaction& t) {
- t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
+ t.setDisplayLayerStack(mDisplay, ui::DEFAULT_LAYER_STACK);
t.setLayer(mBGSurfaceControl, INT32_MAX - 2).show(mBGSurfaceControl);
@@ -63,6 +64,9 @@ protected:
.setPosition(mFGSurfaceControl, 64, 64)
.show(mFGSurfaceControl);
});
+
+ mCaptureArgs.sourceCrop = mDisplayRect;
+ mCaptureArgs.layerHandle = mRootSurfaceControl->getHandle();
}
virtual void TearDown() {
@@ -71,10 +75,11 @@ protected:
mFGSurfaceControl = 0;
}
+ sp<SurfaceControl> mRootSurfaceControl;
sp<SurfaceControl> mBGSurfaceControl;
sp<SurfaceControl> mFGSurfaceControl;
std::unique_ptr<ScreenCapture> mCapture;
- ui::Size mDisplaySize;
+ LayerCaptureArgs mCaptureArgs;
};
TEST_F(ScreenCaptureTest, SetFlagsSecureEUidSystem) {
@@ -82,7 +87,8 @@ TEST_F(ScreenCaptureTest, SetFlagsSecureEUidSystem) {
ASSERT_NO_FATAL_FAILURE(
layer = createLayer("test", 32, 32,
ISurfaceComposerClient::eSecure |
- ISurfaceComposerClient::eFXSurfaceBufferQueue));
+ ISurfaceComposerClient::eFXSurfaceBufferQueue,
+ mRootSurfaceControl.get()));
ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
Transaction().show(layer).setLayer(layer, INT32_MAX).apply(true);
@@ -90,30 +96,45 @@ TEST_F(ScreenCaptureTest, SetFlagsSecureEUidSystem) {
{
// Ensure the UID is not root because root has all permissions
UIDFaker f(AID_APP_START);
- ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(mCaptureArgs, mCaptureResults));
+ ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
}
- UIDFaker f(AID_SYSTEM);
-
- // By default the system can capture screenshots with secure layers but they
- // will be blacked out
- ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(mCaptureArgs, mCaptureResults));
+ {
+ UIDFaker f(AID_SYSTEM);
+
+ // By default the system can capture screenshots with secure layers but they
+ // will be blacked out
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+
+ {
+ SCOPED_TRACE("as system");
+ auto shot = screenshot();
+ shot->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+ }
+
+ mCaptureArgs.captureSecureLayers = true;
+ // AID_SYSTEM is allowed to capture secure content.
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(Rect(0, 0, 32, 32), Color::RED);
+ }
{
- SCOPED_TRACE("as system");
- auto shot = screenshot();
- shot->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
+ // Attempt secure screenshot from shell since it doesn't have CAPTURE_BLACKOUT_CONTENT
+ // permission, but is allowed normal screenshots.
+ UIDFaker faker(AID_SHELL);
+ ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
}
- // Here we pass captureSecureLayers = true and since we are AID_SYSTEM we should be able
- // to receive them...we are expected to take care with the results.
- DisplayCaptureArgs args;
- args.displayToken = mDisplay;
- args.captureSecureLayers = true;
- ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, mCaptureResults));
- ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
- ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
- sc.expectColor(Rect(0, 0, 32, 32), Color::RED);
+ // Remove flag secure from the layer.
+ Transaction().setFlags(layer, 0, layer_state_t::eLayerSecure).apply(true);
+ {
+ // Assert that screenshot fails without CAPTURE_BLACKOUT_CONTENT when requesting
+ // captureSecureLayers even if there are no actual secure layers on screen.
+ UIDFaker faker(AID_SHELL);
+ ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
+ }
}
TEST_F(ScreenCaptureTest, CaptureChildSetParentFlagsSecureEUidSystem) {
@@ -121,7 +142,8 @@ TEST_F(ScreenCaptureTest, CaptureChildSetParentFlagsSecureEUidSystem) {
ASSERT_NO_FATAL_FAILURE(
parentLayer = createLayer("parent-test", 32, 32,
ISurfaceComposerClient::eSecure |
- ISurfaceComposerClient::eFXSurfaceBufferQueue));
+ ISurfaceComposerClient::eFXSurfaceBufferQueue,
+ mRootSurfaceControl.get()));
ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(parentLayer, Color::RED, 32, 32));
sp<SurfaceControl> childLayer;
@@ -142,15 +164,140 @@ TEST_F(ScreenCaptureTest, CaptureChildSetParentFlagsSecureEUidSystem) {
// Here we pass captureSecureLayers = true and since we are AID_SYSTEM we should be able
// to receive them...we are expected to take care with the results.
- DisplayCaptureArgs args;
- args.displayToken = mDisplay;
- args.captureSecureLayers = true;
- ASSERT_EQ(NO_ERROR, ScreenCapture::captureDisplay(args, mCaptureResults));
+ mCaptureArgs.captureSecureLayers = true;
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, mCaptureResults));
ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
sc.expectColor(Rect(0, 0, 10, 10), Color::BLUE);
}
+/**
+ * If a parent layer sets the secure flag, but the screenshot requests is for the child hierarchy,
+ * we need to ensure the secure flag is respected from the parent even though the parent isn't
+ * in the captured sub-hierarchy
+ */
+TEST_F(ScreenCaptureTest, CaptureChildRespectsParentSecureFlag) {
+ Rect size(0, 0, 100, 100);
+ Transaction().hide(mBGSurfaceControl).hide(mFGSurfaceControl).apply();
+ sp<SurfaceControl> parentLayer;
+ ASSERT_NO_FATAL_FAILURE(parentLayer = createLayer("parent-test", 0, 0,
+ ISurfaceComposerClient::eHidden,
+ mRootSurfaceControl.get()));
+
+ sp<SurfaceControl> childLayer;
+ ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("child-test", 0, 0,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ parentLayer.get()));
+ ASSERT_NO_FATAL_FAILURE(
+ fillBufferLayerColor(childLayer, Color::GREEN, size.width(), size.height()));
+
+ // hide the parent layer to ensure secure flag is passed down to child when screenshotting
+ Transaction().setLayer(parentLayer, INT32_MAX).show(childLayer).apply(true);
+ Transaction()
+ .setFlags(parentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
+ .apply();
+ LayerCaptureArgs captureArgs;
+ captureArgs.layerHandle = childLayer->getHandle();
+ captureArgs.sourceCrop = size;
+ captureArgs.captureSecureLayers = false;
+ {
+ SCOPED_TRACE("parent hidden");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::BLACK);
+ }
+
+ captureArgs.captureSecureLayers = true;
+ {
+ SCOPED_TRACE("capture secure parent not visible");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::GREEN);
+ }
+
+ Transaction().show(parentLayer).apply();
+ captureArgs.captureSecureLayers = false;
+ {
+ SCOPED_TRACE("parent visible");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::BLACK);
+ }
+
+ captureArgs.captureSecureLayers = true;
+ {
+ SCOPED_TRACE("capture secure parent visible");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::GREEN);
+ }
+}
+
+TEST_F(ScreenCaptureTest, CaptureOffscreenChildRespectsParentSecureFlag) {
+ Rect size(0, 0, 100, 100);
+ Transaction().hide(mBGSurfaceControl).hide(mFGSurfaceControl).apply();
+ // Parent layer should be offscreen.
+ sp<SurfaceControl> parentLayer;
+ ASSERT_NO_FATAL_FAILURE(
+ parentLayer = createLayer("parent-test", 0, 0, ISurfaceComposerClient::eHidden));
+
+ sp<SurfaceControl> childLayer;
+ ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("child-test", 0, 0,
+ ISurfaceComposerClient::eFXSurfaceBufferState,
+ parentLayer.get()));
+ ASSERT_NO_FATAL_FAILURE(
+ fillBufferLayerColor(childLayer, Color::GREEN, size.width(), size.height()));
+
+ // hide the parent layer to ensure secure flag is passed down to child when screenshotting
+ Transaction().setLayer(parentLayer, INT32_MAX).show(childLayer).apply(true);
+ Transaction()
+ .setFlags(parentLayer, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure)
+ .apply();
+ LayerCaptureArgs captureArgs;
+ captureArgs.layerHandle = childLayer->getHandle();
+ captureArgs.sourceCrop = size;
+ captureArgs.captureSecureLayers = false;
+ {
+ SCOPED_TRACE("parent hidden");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::BLACK);
+ }
+
+ captureArgs.captureSecureLayers = true;
+ {
+ SCOPED_TRACE("capture secure parent not visible");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::GREEN);
+ }
+
+ Transaction().show(parentLayer).apply();
+ captureArgs.captureSecureLayers = false;
+ {
+ SCOPED_TRACE("parent visible");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::BLACK);
+ }
+
+ captureArgs.captureSecureLayers = true;
+ {
+ SCOPED_TRACE("capture secure parent visible");
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(captureArgs, mCaptureResults));
+ ASSERT_TRUE(mCaptureResults.capturedSecureLayers);
+ ScreenCapture sc(mCaptureResults.buffer, mCaptureResults.capturedHdrLayers);
+ sc.expectColor(size, Color::GREEN);
+ }
+}
+
TEST_F(ScreenCaptureTest, CaptureSingleLayer) {
LayerCaptureArgs captureArgs;
captureArgs.layerHandle = mBGSurfaceControl->getHandle();
@@ -220,6 +367,14 @@ TEST_F(ScreenCaptureTest, CaptureLayerExclude) {
mCapture->checkPixel(0, 0, 200, 200, 200);
}
+TEST_F(ScreenCaptureTest, CaptureLayerExcludeThroughDisplayArgs) {
+ mCaptureArgs.excludeHandles = {mFGSurfaceControl->getHandle()};
+ ScreenCapture::captureLayers(&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 +526,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 +604,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 +639,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 +674,7 @@ TEST_F(ScreenCaptureTest, CaptureSize) {
TEST_F(ScreenCaptureTest, CaptureInvalidLayer) {
LayerCaptureArgs args;
- args.layerHandle = new BBinder();
+ args.layerHandle = sp<BBinder>::make();
ScreenCaptureResults captureResults;
// Layer was deleted so captureLayers should fail with NAME_NOT_FOUND
@@ -549,8 +704,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()
@@ -587,60 +742,55 @@ TEST_F(ScreenCaptureTest, CaptureSecureLayer) {
mCapture->expectColor(Rect(30, 30, 60, 60), Color::RED);
}
-TEST_F(ScreenCaptureTest, CaptureDisplayWithUid) {
- uid_t fakeUid = 12345;
+TEST_F(ScreenCaptureTest, ScreenshotProtectedBuffer) {
+ const uint32_t bufferWidth = 60;
+ const uint32_t bufferHeight = 60;
- DisplayCaptureArgs captureArgs;
- captureArgs.displayToken = mDisplay;
+ sp<SurfaceControl> layer =
+ createLayer(String8("Colored surface"), bufferWidth, bufferHeight,
+ ISurfaceComposerClient::eFXSurfaceBufferState, mRootSurfaceControl.get());
- sp<SurfaceControl> layer;
- ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 32, 32,
- ISurfaceComposerClient::eFXSurfaceBufferQueue,
- mBGSurfaceControl.get()));
- ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layer, Color::RED, 32, 32));
+ Transaction().show(layer).setLayer(layer, INT32_MAX).apply(true);
- Transaction().show(layer).setLayer(layer, INT32_MAX).apply();
+ sp<Surface> surface = layer->getSurface();
+ ASSERT_TRUE(surface != nullptr);
+ sp<ANativeWindow> anw(surface);
- // Make sure red layer with the background layer is screenshot.
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
- mCapture->expectColor(Rect(0, 0, 32, 32), Color::RED);
- mCapture->expectBorder(Rect(0, 0, 32, 32), {63, 63, 195, 255});
+ ASSERT_EQ(NO_ERROR, native_window_api_connect(anw.get(), NATIVE_WINDOW_API_CPU));
+ ASSERT_EQ(NO_ERROR, native_window_set_usage(anw.get(), GRALLOC_USAGE_PROTECTED));
- // From non system uid, can't request screenshot without a specified uid.
- UIDFaker f(fakeUid);
- ASSERT_EQ(PERMISSION_DENIED, ScreenCapture::captureDisplay(captureArgs, mCaptureResults));
+ int fenceFd;
+ ANativeWindowBuffer* buf = nullptr;
- // Make screenshot request with current uid set. No layers were created with the current
- // uid so screenshot will be black.
- captureArgs.uid = fakeUid;
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
- mCapture->expectColor(Rect(0, 0, 32, 32), Color::BLACK);
- mCapture->expectBorder(Rect(0, 0, 32, 32), Color::BLACK);
+ // End test if device does not support USAGE_PROTECTED
+ // b/309965549 This check does not exit the test when running on AVDs
+ status_t err = anw->dequeueBuffer(anw.get(), &buf, &fenceFd);
+ if (err) {
+ return;
+ }
+ anw->queueBuffer(anw.get(), buf, fenceFd);
- sp<SurfaceControl> layerWithFakeUid;
- // Create a new layer with the current uid
- ASSERT_NO_FATAL_FAILURE(layerWithFakeUid =
- createLayer("new test layer", 32, 32,
- ISurfaceComposerClient::eFXSurfaceBufferQueue,
- mBGSurfaceControl.get()));
- ASSERT_NO_FATAL_FAILURE(fillBufferQueueLayerColor(layerWithFakeUid, Color::GREEN, 32, 32));
- Transaction()
- .show(layerWithFakeUid)
- .setLayer(layerWithFakeUid, INT32_MAX)
- .setPosition(layerWithFakeUid, 128, 128)
- .apply();
+ // USAGE_PROTECTED buffer is read as a black screen
+ ScreenCaptureResults captureResults;
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
- // Screenshot from the fakeUid caller with the uid requested allows the layer
- // with that uid to be screenshotted. Everything else is black
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
- mCapture->expectColor(Rect(128, 128, 160, 160), Color::GREEN);
- mCapture->expectBorder(Rect(128, 128, 160, 160), Color::BLACK);
+ ScreenCapture sc(captureResults.buffer, captureResults.capturedHdrLayers);
+ sc.expectColor(Rect(0, 0, bufferWidth, bufferHeight), Color::BLACK);
+
+ // Reading color data will expectedly result in crash, only check usage bit
+ // b/309965549 Checking that the usage bit is protected does not work for
+ // devices that do not support usage protected.
+ mCaptureArgs.allowProtected = true;
+ ASSERT_EQ(NO_ERROR, ScreenCapture::captureLayers(mCaptureArgs, captureResults));
+ // ASSERT_EQ(GRALLOC_USAGE_PROTECTED, GRALLOC_USAGE_PROTECTED &
+ // captureResults.buffer->getUsage());
}
-TEST_F(ScreenCaptureTest, CaptureDisplayPrimaryDisplayOnly) {
+TEST_F(ScreenCaptureTest, CaptureLayer) {
sp<SurfaceControl> layer;
- ASSERT_NO_FATAL_FAILURE(
- layer = createLayer("test layer", 0, 0, ISurfaceComposerClient::eFXSurfaceEffect));
+ ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 0, 0,
+ ISurfaceComposerClient::eFXSurfaceEffect,
+ mRootSurfaceControl.get()));
const Color layerColor = Color::RED;
const Rect bounds = Rect(10, 10, 40, 40);
@@ -648,17 +798,13 @@ TEST_F(ScreenCaptureTest, CaptureDisplayPrimaryDisplayOnly) {
Transaction()
.show(layer)
.hide(mFGSurfaceControl)
- .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
.setLayer(layer, INT32_MAX)
.setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
.setCrop(layer, bounds)
.apply();
- DisplayCaptureArgs captureArgs;
- captureArgs.displayToken = mDisplay;
-
{
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
+ ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
mCapture->expectColor(bounds, layerColor);
mCapture->expectBorder(bounds, {63, 63, 195, 255});
}
@@ -671,17 +817,18 @@ TEST_F(ScreenCaptureTest, CaptureDisplayPrimaryDisplayOnly) {
{
// Can't screenshot test layer since it now has flag
// eLayerSkipScreenshot
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
+ ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
mCapture->expectColor(bounds, {63, 63, 195, 255});
mCapture->expectBorder(bounds, {63, 63, 195, 255});
}
}
-TEST_F(ScreenCaptureTest, CaptureDisplayChildPrimaryDisplayOnly) {
+TEST_F(ScreenCaptureTest, CaptureLayerChild) {
sp<SurfaceControl> layer;
sp<SurfaceControl> childLayer;
- ASSERT_NO_FATAL_FAILURE(
- layer = createLayer("test layer", 0, 0, ISurfaceComposerClient::eFXSurfaceEffect));
+ ASSERT_NO_FATAL_FAILURE(layer = createLayer("test layer", 0, 0,
+ ISurfaceComposerClient::eFXSurfaceEffect,
+ mRootSurfaceControl.get()));
ASSERT_NO_FATAL_FAILURE(childLayer = createLayer("test layer", 0, 0,
ISurfaceComposerClient::eFXSurfaceEffect,
layer.get()));
@@ -695,7 +842,6 @@ TEST_F(ScreenCaptureTest, CaptureDisplayChildPrimaryDisplayOnly) {
.show(layer)
.show(childLayer)
.hide(mFGSurfaceControl)
- .setLayerStack(layer, ui::DEFAULT_LAYER_STACK)
.setLayer(layer, INT32_MAX)
.setColor(layer, {layerColor.r / 255, layerColor.g / 255, layerColor.b / 255})
.setColor(childLayer, {childColor.r / 255, childColor.g / 255, childColor.b / 255})
@@ -703,11 +849,8 @@ TEST_F(ScreenCaptureTest, CaptureDisplayChildPrimaryDisplayOnly) {
.setCrop(childLayer, childBounds)
.apply();
- DisplayCaptureArgs captureArgs;
- captureArgs.displayToken = mDisplay;
-
{
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
+ ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
mCapture->expectColor(childBounds, childColor);
mCapture->expectBorder(childBounds, layerColor);
mCapture->expectBorder(bounds, {63, 63, 195, 255});
@@ -721,7 +864,7 @@ TEST_F(ScreenCaptureTest, CaptureDisplayChildPrimaryDisplayOnly) {
{
// Can't screenshot child layer since the parent has the flag
// eLayerSkipScreenshot
- ScreenCapture::captureDisplay(&mCapture, captureArgs);
+ ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
mCapture->expectColor(childBounds, {63, 63, 195, 255});
mCapture->expectBorder(childBounds, {63, 63, 195, 255});
mCapture->expectBorder(bounds, {63, 63, 195, 255});
@@ -803,7 +946,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 +968,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,18 +981,14 @@ 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();
- DisplayCaptureArgs displayCaptureArgs;
- displayCaptureArgs.displayToken = mDisplay;
-
{
// Validate that the red layer is not on screen
- ScreenCapture::captureDisplay(&mCapture, displayCaptureArgs);
- mCapture->expectColor(Rect(0, 0, mDisplaySize.width, mDisplaySize.height),
- {63, 63, 195, 255});
+ ScreenCapture::captureLayers(&mCapture, mCaptureArgs);
+ mCapture->expectColor(Rect(0, 0, mDisplayWidth, mDisplayHeight), {63, 63, 195, 255});
}
LayerCaptureArgs captureArgs;
@@ -865,7 +1004,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 +1024,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
deleted file mode 100644
index 4efec7738a..0000000000
--- a/services/surfaceflinger/tests/SetFrameRateOverride_test.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include <gtest/gtest.h>
-#include <gui/DisplayEventReceiver.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/SurfaceComposerClient.h>
-#include <sys/epoll.h>
-#include <algorithm>
-
-namespace android {
-namespace {
-using FrameRateOverride = DisplayEventReceiver::Event::FrameRateOverride;
-
-class SetFrameRateOverrideTest : public ::testing::Test {
-protected:
- void SetUp() override {
- const ISurfaceComposer::VsyncSource vsyncSource = ISurfaceComposer::eVsyncSourceApp;
- const ISurfaceComposer::EventRegistrationFlags eventRegistration = {
- ISurfaceComposer::EventRegistration::frameRateOverride};
-
- mDisplayEventReceiver =
- std::make_unique<DisplayEventReceiver>(vsyncSource, eventRegistration);
- EXPECT_EQ(NO_ERROR, mDisplayEventReceiver->initCheck());
-
- mEpollFd = epoll_create1(EPOLL_CLOEXEC);
- EXPECT_GT(mEpollFd, 1);
-
- epoll_event event;
- event.events = EPOLLIN;
- EXPECT_EQ(0, epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mDisplayEventReceiver->getFd(), &event));
- }
-
- void TearDown() override { close(mEpollFd); }
-
- void setFrameRateAndListenEvents(uid_t uid, float frameRate) {
- status_t ret = SurfaceComposerClient::setOverrideFrameRate(uid, frameRate);
- ASSERT_EQ(NO_ERROR, ret);
-
- DisplayEventReceiver::Event event;
- bool isOverrideFlushReceived = false;
- mFrameRateOverrides.clear();
-
- epoll_event epollEvent;
- while (epoll_wait(mEpollFd, &epollEvent, 1, 1000) > 0) {
- while (mDisplayEventReceiver->getEvents(&event, 1) > 0) {
- if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE) {
- mFrameRateOverrides.emplace_back(event.frameRateOverride);
- }
- if (event.header.type ==
- DisplayEventReceiver::DISPLAY_EVENT_FRAME_RATE_OVERRIDE_FLUSH) {
- isOverrideFlushReceived = true;
- }
- }
-
- if (isOverrideFlushReceived) break;
- }
- }
-
- std::unique_ptr<DisplayEventReceiver> mDisplayEventReceiver;
- std::vector<FrameRateOverride> mFrameRateOverrides;
-
- int mEpollFd;
-};
-
-TEST_F(SetFrameRateOverrideTest, SetFrameRateOverrideCall) {
- uid_t uid = getuid();
- float frameRate = 30.0f;
- setFrameRateAndListenEvents(uid, frameRate);
- // check if the frame rate override we set exists
- ASSERT_TRUE(std::find_if(mFrameRateOverrides.begin(), mFrameRateOverrides.end(),
- [uid = uid, frameRate = frameRate](auto i) {
- return uid == i.uid && frameRate == i.frameRateHz;
- }) != mFrameRateOverrides.end());
-
- // test removing frame rate override
- frameRate = 0.0f;
- setFrameRateAndListenEvents(uid, frameRate);
- ASSERT_TRUE(std::find_if(mFrameRateOverrides.begin(), mFrameRateOverrides.end(),
- [uid = uid, frameRate = frameRate](auto i) {
- return uid == i.uid && frameRate == i.frameRateHz;
- }) == mFrameRateOverrides.end());
-}
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/Stress_test.cpp b/services/surfaceflinger/tests/Stress_test.cpp
index e9b6ba0f64..b30df5ef15 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<SurfaceComposerClient> client = new SurfaceComposerClient;
+ sp<SurfaceComposerClient> client = sp<SurfaceComposerClient>::make();
ASSERT_EQ(NO_ERROR, client->initCheck());
for (int j = 0; j < 1000; j++) {
auto surf = client->createSurface(String8("t"), 100, 100,
@@ -51,9 +51,9 @@ TEST(SurfaceFlingerStress, create_and_destroy) {
}
}
-surfaceflinger::LayersProto generateLayerProto() {
- surfaceflinger::LayersProto layersProto;
- std::array<surfaceflinger::LayerProto*, 10> layers = {};
+perfetto::protos::LayersProto generateLayerProto() {
+ perfetto::protos::LayersProto layersProto;
+ std::array<perfetto::protos::LayerProto*, 10> layers = {};
for (size_t i = 0; i < layers.size(); ++i) {
layers[i] = layersProto.add_layers();
layers[i]->set_id(i);
@@ -103,7 +103,7 @@ TEST(LayerProtoStress, mem_info) {
cmd += std::to_string(getpid());
system(cmd.c_str());
for (int i = 0; i < 100000; i++) {
- surfaceflinger::LayersProto layersProto = generateLayerProto();
+ perfetto::protos::LayersProto layersProto = generateLayerProto();
auto layerTree = surfaceflinger::LayerProtoParser::generateLayerTree(layersProto);
surfaceflinger::LayerProtoParser::layerTreeToString(layerTree);
}
diff --git a/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp b/services/surfaceflinger/tests/SurfaceInterceptor_test.cpp
deleted file mode 100644
index 28e8b8c78b..0000000000
--- 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 <android-base/stringprintf.h>
-#include <frameworks/native/cmds/surfacereplayer/proto/src/trace.pb.h>
-#include <google/protobuf/io/zero_copy_stream_impl.h>
-#include <gtest/gtest.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/LayerState.h>
-#include <gui/Surface.h>
-#include <gui/SurfaceComposerClient.h>
-#include <private/gui/ComposerService.h>
-#include <ui/DisplayMode.h>
-
-#include <fstream>
-#include <random>
-#include <thread>
-
-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<BlurRegion> 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<SurfaceControl>& sc, uint8_t r, uint8_t g, uint8_t b) {
- ANativeWindow_Buffer outBuffer;
- sp<Surface> s = sc->getSurface();
- ASSERT_TRUE(s != nullptr);
- ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr));
- uint8_t* img = reinterpret_cast<uint8_t*>(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<SurfaceComposerClient> mComposerClient;
- sp<SurfaceControl> mBGSurfaceControl;
- sp<SurfaceControl> 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.empty();
- 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<IBinder> testDisplay = SurfaceComposerClient::createDisplay(DISPLAY_NAME, false);
- SurfaceComposerClient::destroyDisplay(testDisplay);
-}
-
-void SurfaceInterceptorTest::displayDeletion(Transaction&) {
- sp<IBinder> 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<uint8_t> 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.string() &&
- !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.string());
- 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 0000000000..c5d118c1aa
--- /dev/null
+++ b/services/surfaceflinger/tests/TextureFiltering_test.cpp
@@ -0,0 +1,216 @@
+/*
+ * 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 <android/gui/ISurfaceComposerClient.h>
+#include <gtest/gtest.h>
+#include <gui/DisplayCaptureArgs.h>
+#include <ui/GraphicTypes.h>
+#include <ui/Rect.h>
+
+#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<GraphicBuffer> buffer =
+ sp<GraphicBuffer>::make(static_cast<uint32_t>(100), static_cast<uint32_t>(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<uint32_t>(x),
+ static_cast<uint32_t>(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<uint32_t>(x),
+ static_cast<uint32_t>(y)) == Color::BLUE;
+ }
+ }
+ ASSERT_LT(bluePixels, blueRect.getWidth() * blueRect.getHeight());
+ }
+
+ sp<SurfaceControl> mParent;
+ sp<SurfaceControl> mLayer;
+ std::unique_ptr<ScreenCapture> mCapture;
+ gui::LayerCaptureArgs captureArgs;
+};
+
+TEST_F(TextureFilteringTest, NoFiltering) {
+ captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+ captureArgs.layerHandle = mParent->getHandle();
+ ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+ mCapture->expectColor(Rect{0, 0, 50, 100}, Color::RED);
+ mCapture->expectColor(Rect{50, 0, 100, 100}, Color::BLUE);
+}
+
+TEST_F(TextureFilteringTest, BufferCropNoFiltering) {
+ captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+ captureArgs.layerHandle = mParent->getHandle();
+ ScreenCapture::captureLayers(&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();
+
+ captureArgs.sourceCrop = Rect{0, 0, 100, 100};
+ captureArgs.layerHandle = mParent->getHandle();
+ ScreenCapture::captureLayers(&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) {
+ captureArgs.frameScaleX = 2;
+ captureArgs.frameScaleY = 2;
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ captureArgs.layerHandle = mParent->getHandle();
+ ScreenCapture::captureLayers(&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();
+ captureArgs.frameScaleX = 2;
+ captureArgs.frameScaleY = 2;
+ captureArgs.sourceCrop = Rect{25, 25, 75, 75};
+ captureArgs.layerHandle = mParent->getHandle();
+ ScreenCapture::captureLayers(&mCapture, captureArgs);
+
+ expectFiltered({0, 0, 50, 100}, {50, 0, 100, 100});
+}
+
+// Expect filtering because the layer is scaled up.
+TEST_F(TextureFilteringTest, LayerCaptureWithScalingIsFiltered) {
+ 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) {
+ 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();
+
+ 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) {
+ 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 layer crop shouldn't scale the layer.
+TEST_F(TextureFilteringTest, LayerCropDisplayFrameMatchNoFiltering) {
+ Transaction().setCrop(mLayer, Rect{25, 25, 75, 75}).apply();
+
+ captureArgs.layerHandle = mLayer->getHandle();
+ ScreenCapture::captureLayers(&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();
+
+ captureArgs.layerHandle = mLayer->getHandle();
+ ScreenCapture::captureLayers(&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();
+
+ 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 8ce63bc64c..797a64c7d7 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<BufferListener> listener = new BufferListener(this);
+ itemConsumer = sp<BufferItemConsumer>::make(consumer,
+ // Sample usage bits from screenrecord
+ GRALLOC_USAGE_HW_VIDEO_ENCODER |
+ GRALLOC_USAGE_SW_READ_OFTEN);
+ sp<BufferListener> listener = sp<BufferListener>::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 18e08062c0..f31f582ffd 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<GLConsumer>::make(consumer, GLConsumer::TEXTURE_EXTERNAL, true, false);
}
sp<IGraphicBufferProducer> mProducer;
@@ -55,7 +55,7 @@ TEST_F(VirtualDisplayTest, VirtualDisplayDestroyedSurfaceReuse) {
// add another sync since we are deferring the display destruction
t.apply(true);
- sp<Surface> surface = new Surface(mProducer);
+ sp<Surface> surface = sp<Surface>::make(mProducer);
sp<ANativeWindow> 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 bb522451a6..ad9a674456 100644
--- a/services/surfaceflinger/tests/WindowInfosListener_test.cpp
+++ b/services/surfaceflinger/tests/WindowInfosListener_test.cpp
@@ -16,68 +16,40 @@
#include <gtest/gtest.h>
#include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfosUpdate.h>
#include <private/android_filesystem_config.h>
+#include <cstdint>
#include <future>
-#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<bool(const std::vector<WindowInfo>&)>;
class WindowInfosListenerTest : public ::testing::Test {
protected:
void SetUp() override {
seteuid(AID_SYSTEM);
- mClient = new SurfaceComposerClient;
- mWindowInfosListener = new SyncWindowInfosListener();
- mClient->addWindowInfosListener(mWindowInfosListener);
+ mClient = sp<SurfaceComposerClient>::make();
}
- void TearDown() override {
- mClient->removeWindowInfosListener(mWindowInfosListener);
- seteuid(AID_ROOT);
- }
-
- struct SyncWindowInfosListener : public gui::WindowInfosListener {
- public:
- void onWindowInfosChanged(const std::vector<WindowInfo>& windowInfos,
- const std::vector<DisplayInfo>&) override {
- windowInfosPromise.set_value(windowInfos);
- }
-
- std::vector<WindowInfo> waitForWindowInfos() {
- std::future<std::vector<WindowInfo>> windowInfosFuture =
- windowInfosPromise.get_future();
- std::vector<WindowInfo> windowInfos = windowInfosFuture.get();
- windowInfosPromise = std::promise<std::vector<WindowInfo>>();
- return windowInfos;
- }
-
- private:
- std::promise<std::vector<WindowInfo>> windowInfosPromise;
- };
+ void TearDown() override { seteuid(AID_ROOT); }
sp<SurfaceComposerClient> mClient;
- sp<SyncWindowInfosListener> mWindowInfosListener;
-};
+ WindowInfosListenerUtils mWindowInfosListenerUtils;
-std::optional<WindowInfo> findMatchingWindowInfo(WindowInfo targetWindowInfo,
- std::vector<WindowInfo> windowInfos) {
- std::optional<WindowInfo> 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<IBinder> token = new BBinder();
+ sp<IBinder> token = sp<BBinder>::make();
WindowInfo windowInfo;
windowInfo.name = name;
windowInfo.token = token;
@@ -92,20 +64,22 @@ TEST_F(WindowInfosListenerTest, WindowInfoAddedAndRemoved) {
.setInputWindowInfo(surfaceControl, windowInfo)
.apply();
- std::vector<WindowInfo> windowInfos = mWindowInfosListener->waitForWindowInfos();
- std::optional<WindowInfo> foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
- ASSERT_NE(std::nullopt, foundWindowInfo);
+ auto windowPresent = [&](const std::vector<WindowInfo>& 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<WindowInfo>& windowInfos) {
+ return !findMatchingWindowInfo(windowInfo, windowInfos);
+ };
+ ASSERT_TRUE(waitForWindowInfosPredicate(windowNotPresent));
}
TEST_F(WindowInfosListenerTest, WindowInfoChanged) {
std::string name = "Test Layer";
- sp<IBinder> token = new BBinder();
+ sp<IBinder> token = sp<BBinder>::make();
WindowInfo windowInfo;
windowInfo.name = name;
windowInfo.token = token;
@@ -121,19 +95,30 @@ TEST_F(WindowInfosListenerTest, WindowInfoChanged) {
.setInputWindowInfo(surfaceControl, windowInfo)
.apply();
- std::vector<WindowInfo> windowInfos = mWindowInfosListener->waitForWindowInfos();
- std::optional<WindowInfo> foundWindowInfo = findMatchingWindowInfo(windowInfo, windowInfos);
- ASSERT_NE(std::nullopt, foundWindowInfo);
- ASSERT_TRUE(foundWindowInfo->touchableRegion.isEmpty());
+ auto windowIsPresentAndTouchableRegionEmpty = [&](const std::vector<WindowInfo>& 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<WindowInfo>& 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 704815de3a..0000000000
--- 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 b38032d265..0000000000
--- 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 <gui/SurfaceComposerClient.h>
-
-#include <log/log.h>
-
-#include <gtest/gtest.h>
-
-#include <inttypes.h>
-#include <time.h>
-#include <algorithm>
-#include <condition_variable>
-#include <iostream>
-#include <mutex>
-#include <set>
-#include <thread>
-
-constexpr Config NULL_DISPLAY_CONFIG = static_cast<Config>(0);
-
-using namespace sftest;
-
-using android::Condition;
-using android::Mutex;
-
-using Clock = std::chrono::steady_clock;
-using TimePoint = std::chrono::time_point<Clock>;
-
-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<std::unique_ptr<FrameRect>> rectangles;
-};
-
-class DelayedEventGenerator {
-public:
- explicit DelayedEventGenerator(std::function<void()> onTimerExpired)
- : mOnTimerExpired(onTimerExpired), mThread([this]() { loop(); }) {}
-
- ~DelayedEventGenerator() {
- ALOGI("DelayedEventGenerator exiting.");
- {
- std::unique_lock<std::mutex> lock(mMutex);
- mRunning = false;
- mWakeups.clear();
- mCondition.notify_one();
- }
- mThread.join();
- ALOGI("DelayedEventGenerator exited.");
- }
-
- void wakeAfter(std::chrono::nanoseconds waitTime) {
- std::unique_lock<std::mutex> lock(mMutex);
- mWakeups.insert(Clock::now() + waitTime);
- mCondition.notify_one();
- }
-
-private:
- void loop() {
- while (true) {
- // Lock scope
- {
- std::unique_lock<std::mutex> 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<void()> mOnTimerExpired;
- std::thread mThread;
- std::mutex mMutex;
- std::condition_variable mCondition;
- bool mRunning = true;
- std::set<TimePoint> mWakeups;
-};
-
-} // namespace
-
-FakeComposerClient::FakeComposerClient()
- : mEventCallback(nullptr),
- mEventCallback_2_4(nullptr),
- mCurrentConfig(NULL_DISPLAY_CONFIG),
- mVsyncEnabled(false),
- mLayers(),
- mDelayedEventGenerator(
- std::make_unique<DelayedEventGenerator>([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<LayerImpl>();
- 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<V1_0::ColorMode>* /*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<IComposerClient::Attribute>(attribute), outValue);
- return static_cast<V2_1::Error>(tmpError);
-}
-
-V2_1::Error FakeComposerClient::getDisplayConfigs(Display display, hidl_vec<Config>* 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<IComposerClient::DisplayType>(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<V1_0::Hdr>* /*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<hwc_rect_t>& /*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<Layer>* /*outChangedLayers*/,
- std::vector<IComposerClient::Composition>* /*outCompositionTypes*/,
- uint32_t* /*outDisplayRequestMask*/, std::vector<Layer>* /*outRequestedLayers*/,
- std::vector<uint32_t>* /*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<FrameRect>& a, const std::unique_ptr<FrameRect>& b) {
- return a->z <= b->z;
-}
-
-V2_1::Error FakeComposerClient::presentDisplay(Display /*display*/, int32_t* /*outPresentFence*/,
- std::vector<Layer>* /*outLayers*/,
- std::vector<int32_t>* /*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<Frame> newFrame(new Frame);
- for (uint64_t layer = 0; layer < mLayers.size(); layer++) {
- const LayerImpl& layerImpl = *mLayers[layer];
-
- if (!layerImpl.mValid) continue;
-
- auto rect = std::make_unique<FrameRect>(layer, layerImpl.mRenderState, layerImpl.mZ);
- newFrame->rectangles.push_back(std::move(rect));
- }
- std::sort(newFrame->rectangles.begin(), newFrame->rectangles.end(), layerZOrdering);
- {
- Mutex::Autolock _l(mStateMutex);
- mFrames.push_back(std::move(newFrame));
- mFramesAvailable.broadcast();
- }
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerCursorPosition(Display /*display*/, Layer /*layer*/,
- int32_t /*x*/, int32_t /*y*/) {
- ALOGV("setLayerCursorPosition");
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerBuffer(Display /*display*/, Layer layer,
- buffer_handle_t buffer, int32_t acquireFence) {
- ALOGV("setLayerBuffer");
- LayerImpl& l = getLayerImpl(layer);
- if (buffer != l.mRenderState.mBuffer) {
- l.mRenderState.mSwapCount++; // TODO: Is setting to same value a swap or not?
- }
- l.mRenderState.mBuffer = buffer;
- l.mRenderState.mAcquireFence = acquireFence;
-
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerSurfaceDamage(Display /*display*/, Layer /*layer*/,
- const std::vector<hwc_rect_t>& /*damage*/) {
- ALOGV("setLayerSurfaceDamage");
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerBlendMode(Display /*display*/, Layer layer, int32_t mode) {
- ALOGV("setLayerBlendMode");
- getLayerImpl(layer).mRenderState.mBlendMode = static_cast<hwc2_blend_mode_t>(mode);
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerColor(Display /*display*/, Layer layer,
- IComposerClient::Color color) {
- ALOGV("setLayerColor");
- getLayerImpl(layer).mRenderState.mLayerColor.r = color.r;
- getLayerImpl(layer).mRenderState.mLayerColor.g = color.g;
- getLayerImpl(layer).mRenderState.mLayerColor.b = color.b;
- getLayerImpl(layer).mRenderState.mLayerColor.a = color.a;
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerCompositionType(Display /*display*/, Layer /*layer*/,
- int32_t /*type*/) {
- ALOGV("setLayerCompositionType");
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerDataspace(Display /*display*/, Layer /*layer*/,
- int32_t /*dataspace*/) {
- ALOGV("setLayerDataspace");
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerDisplayFrame(Display /*display*/, Layer layer,
- const hwc_rect_t& frame) {
- ALOGV("setLayerDisplayFrame (%d, %d, %d, %d)", frame.left, frame.top, frame.right,
- frame.bottom);
- getLayerImpl(layer).mRenderState.mDisplayFrame = frame;
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerPlaneAlpha(Display /*display*/, Layer layer, float alpha) {
- ALOGV("setLayerPlaneAlpha");
- getLayerImpl(layer).mRenderState.mPlaneAlpha = alpha;
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerSidebandStream(Display /*display*/, Layer /*layer*/,
- buffer_handle_t /*stream*/) {
- ALOGV("setLayerSidebandStream");
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerSourceCrop(Display /*display*/, Layer layer,
- const hwc_frect_t& crop) {
- ALOGV("setLayerSourceCrop");
- getLayerImpl(layer).mRenderState.mSourceCrop = crop;
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerTransform(Display /*display*/, Layer layer,
- int32_t transform) {
- ALOGV("setLayerTransform");
- getLayerImpl(layer).mRenderState.mTransform = static_cast<hwc_transform_t>(transform);
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerVisibleRegion(Display /*display*/, Layer layer,
- const std::vector<hwc_rect_t>& visible) {
- ALOGV("setLayerVisibleRegion");
- getLayerImpl(layer).mRenderState.mVisibleRegion = visible;
- return V2_1::Error::NONE;
-}
-
-V2_1::Error FakeComposerClient::setLayerZOrder(Display /*display*/, Layer layer, uint32_t z) {
- ALOGV("setLayerZOrder");
- getLayerImpl(layer).mZ = z;
- return V2_1::Error::NONE;
-}
-
-// Composer 2.2
-V2_1::Error FakeComposerClient::getPerFrameMetadataKeys(
- Display /*display*/, std::vector<V2_2::IComposerClient::PerFrameMetadataKey>* /*outKeys*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setLayerPerFrameMetadata(
- Display /*display*/, Layer /*layer*/,
- const std::vector<V2_2::IComposerClient::PerFrameMetadata>& /*metadata*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getReadbackBufferAttributes(
- Display /*display*/, graphics::common::V1_1::PixelFormat* /*outFormat*/,
- graphics::common::V1_1::Dataspace* /*outDataspace*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setReadbackBuffer(Display /*display*/,
- const native_handle_t* /*bufferHandle*/,
- android::base::unique_fd /*fenceFd*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getReadbackBufferFence(Display /*display*/,
- android::base::unique_fd* /*outFenceFd*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::createVirtualDisplay_2_2(
- uint32_t /*width*/, uint32_t /*height*/, graphics::common::V1_1::PixelFormat* /*format*/,
- Display* /*outDisplay*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-V2_1::Error FakeComposerClient::getClientTargetSupport_2_2(
- Display /*display*/, uint32_t /*width*/, uint32_t /*height*/,
- graphics::common::V1_1::PixelFormat /*format*/,
- graphics::common::V1_1::Dataspace /*dataspace*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setPowerMode_2_2(Display /*display*/,
- V2_2::IComposerClient::PowerMode /*mode*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setLayerFloatColor(Display /*display*/, Layer /*layer*/,
- V2_2::IComposerClient::FloatColor /*color*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getColorModes_2_2(
- Display /*display*/, hidl_vec<graphics::common::V1_1::ColorMode>* /*outModes*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getRenderIntents(
- Display /*display*/, graphics::common::V1_1::ColorMode /*mode*/,
- std::vector<graphics::common::V1_1::RenderIntent>* /*outIntents*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setColorMode_2_2(Display /*display*/,
- graphics::common::V1_1::ColorMode /*mode*/,
- graphics::common::V1_1::RenderIntent /*intent*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-std::array<float, 16> FakeComposerClient::getDataspaceSaturationMatrix(
- graphics::common::V1_1::Dataspace /*dataspace*/) {
- return {};
-}
-
-// Composer 2.3
-V2_1::Error FakeComposerClient::getPerFrameMetadataKeys_2_3(
- Display /*display*/, std::vector<V2_3::IComposerClient::PerFrameMetadataKey>* /*outKeys*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setColorMode_2_3(Display /*display*/,
- graphics::common::V1_2::ColorMode /*mode*/,
- graphics::common::V1_1::RenderIntent /*intent*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getRenderIntents_2_3(
- Display /*display*/, graphics::common::V1_2::ColorMode /*mode*/,
- std::vector<graphics::common::V1_1::RenderIntent>* /*outIntents*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getColorModes_2_3(
- Display /*display*/, hidl_vec<graphics::common::V1_2::ColorMode>* /*outModes*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getClientTargetSupport_2_3(
- Display /*display*/, uint32_t /*width*/, uint32_t /*height*/,
- graphics::common::V1_2::PixelFormat /*format*/,
- graphics::common::V1_2::Dataspace /*dataspace*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getReadbackBufferAttributes_2_3(
- Display /*display*/, graphics::common::V1_2::PixelFormat* /*outFormat*/,
- graphics::common::V1_2::Dataspace* /*outDataspace*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getHdrCapabilities_2_3(
- Display /*display*/, hidl_vec<graphics::common::V1_2::Hdr>* /*outTypes*/,
- float* /*outMaxLuminance*/, float* /*outMaxAverageLuminance*/, float* /*outMinLuminance*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setLayerPerFrameMetadata_2_3(
- Display /*display*/, Layer /*layer*/,
- const std::vector<V2_3::IComposerClient::PerFrameMetadata>& /*metadata*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getDisplayIdentificationData(Display /*display*/,
- uint8_t* /*outPort*/,
- std::vector<uint8_t>* /*outData*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setLayerColorTransform(Display /*display*/, Layer /*layer*/,
- const float* /*matrix*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getDisplayedContentSamplingAttributes(
- uint64_t /*display*/, graphics::common::V1_2::PixelFormat& /*format*/,
- graphics::common::V1_2::Dataspace& /*dataspace*/,
- hidl_bitfield<V2_3::IComposerClient::FormatColorComponent>& /*componentMask*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setDisplayedContentSamplingEnabled(
- uint64_t /*display*/, V2_3::IComposerClient::DisplayedContentSampling /*enable*/,
- hidl_bitfield<V2_3::IComposerClient::FormatColorComponent> /*componentMask*/,
- uint64_t /*maxFrames*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getDisplayedContentSample(
- uint64_t /*display*/, uint64_t /*maxFrames*/, uint64_t /*timestamp*/,
- uint64_t& /*frameCount*/, hidl_vec<uint64_t>& /*sampleComponent0*/,
- hidl_vec<uint64_t>& /*sampleComponent1*/, hidl_vec<uint64_t>& /*sampleComponent2*/,
- hidl_vec<uint64_t>& /*sampleComponent3*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getDisplayCapabilities(
- Display /*display*/,
- std::vector<V2_3::IComposerClient::DisplayCapability>* /*outCapabilities*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setLayerPerFrameMetadataBlobs(
- Display /*display*/, Layer /*layer*/,
- std::vector<V2_3::IComposerClient::PerFrameMetadataBlob>& /*blobs*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::getDisplayBrightnessSupport(Display /*display*/,
- bool* /*outSupport*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-V2_1::Error FakeComposerClient::setDisplayBrightness(Display /*display*/, float /*brightness*/) {
- return V2_1::Error::UNSUPPORTED;
-}
-
-// Composer 2.4
-void FakeComposerClient::registerEventCallback_2_4(EventCallback_2_4* callback) {
- ALOGV("registerEventCallback_2_4");
- LOG_FATAL_IF(mEventCallback != nullptr, "already registered using registerEventCallback");
-
- mEventCallback_2_4 = callback;
- if (mEventCallback_2_4) {
- mEventCallback_2_4->onHotplug(PRIMARY_DISPLAY, IComposerCallback::Connection::CONNECTED);
- }
-}
-
-void FakeComposerClient::unregisterEventCallback_2_4() {
- ALOGV("unregisterEventCallback_2_4");
- mEventCallback_2_4 = nullptr;
-}
-
-V2_4::Error FakeComposerClient::getDisplayCapabilities_2_4(
- Display /*display*/,
- std::vector<V2_4::IComposerClient::DisplayCapability>* /*outCapabilities*/) {
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::getDisplayConnectionType(
- Display /*display*/, V2_4::IComposerClient::DisplayConnectionType* /*outType*/) {
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::getDisplayAttribute_2_4(Display display, Config config,
- IComposerClient::Attribute attribute,
- int32_t* outValue) {
- ALOGV("getDisplayAttribute (%d, %d, %d, %p)", static_cast<int>(display),
- static_cast<int>(config), static_cast<int>(attribute), outValue);
- if (mMockHal) {
- return mMockHal->getDisplayAttribute_2_4(display, config, attribute, outValue);
- }
-
- // TODO: SOOO much fun to be had with these alone
- switch (attribute) {
- case IComposerClient::Attribute::WIDTH:
- *outValue = 1920;
- break;
- case IComposerClient::Attribute::HEIGHT:
- *outValue = 1080;
- break;
- case IComposerClient::Attribute::VSYNC_PERIOD:
- *outValue = 1666666666;
- break; // TOOD: Tests break down if lowered to 16ms?
- case IComposerClient::Attribute::DPI_X:
- *outValue = 240;
- break;
- case IComposerClient::Attribute::DPI_Y:
- *outValue = 240;
- break;
- default:
- LOG_ALWAYS_FATAL("Say what!?! New attribute");
- }
-
- return Error::NONE;
-}
-
-V2_4::Error FakeComposerClient::getDisplayVsyncPeriod(Display display,
- V2_4::VsyncPeriodNanos* outVsyncPeriod) {
- ALOGV("getDisplayVsyncPeriod");
- if (mMockHal) {
- return mMockHal->getDisplayVsyncPeriod(display, outVsyncPeriod);
- }
-
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::setActiveConfigWithConstraints(
- Display display, Config config,
- const V2_4::IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
- VsyncPeriodChangeTimeline* timeline) {
- ALOGV("setActiveConfigWithConstraints");
- if (mMockHal) {
- return mMockHal->setActiveConfigWithConstraints(display, config,
- vsyncPeriodChangeConstraints, timeline);
- }
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::setAutoLowLatencyMode(Display, bool) {
- ALOGV("setAutoLowLatencyMode");
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::getSupportedContentTypes(
- Display, std::vector<IComposerClient::ContentType>*) {
- ALOGV("getSupportedContentTypes");
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::setContentType(Display, IComposerClient::ContentType) {
- ALOGV("setContentType");
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::validateDisplay_2_4(
- Display /*display*/, std::vector<Layer>* /*outChangedLayers*/,
- std::vector<IComposerClient::Composition>* /*outCompositionTypes*/,
- uint32_t* /*outDisplayRequestMask*/, std::vector<Layer>* /*outRequestedLayers*/,
- std::vector<uint32_t>* /*outRequestMasks*/,
- IComposerClient::ClientTargetProperty* /*outClientTargetProperty*/) {
- return V2_4::Error::NONE;
-}
-
-V2_4::Error FakeComposerClient::setLayerGenericMetadata(Display, Layer, const std::string&, bool,
- const std::vector<uint8_t>&) {
- ALOGV("setLayerGenericMetadata");
- return V2_4::Error::UNSUPPORTED;
-}
-
-V2_4::Error FakeComposerClient::getLayerGenericMetadataKeys(
- std::vector<IComposerClient::LayerGenericMetadataKey>*) {
- ALOGV("getLayerGenericMetadataKeys");
- return V2_4::Error::UNSUPPORTED;
-}
-
-//////////////////////////////////////////////////////////////////
-
-void FakeComposerClient::requestVSync(uint64_t vsyncTime) {
- if (mEventCallback || mEventCallback_2_4) {
- uint64_t timestamp = vsyncTime;
- ALOGV("Vsync");
- if (timestamp == 0) {
- struct timespec ts;
- clock_gettime(CLOCK_MONOTONIC, &ts);
- timestamp = ts.tv_sec * 1000 * 1000 * 1000 + ts.tv_nsec;
- }
- if (mSurfaceComposer != nullptr) {
- mSurfaceComposer->injectVSync(timestamp);
- } else if (mEventCallback) {
- mEventCallback->onVsync(PRIMARY_DISPLAY, timestamp);
- } else {
- mEventCallback_2_4->onVsync_2_4(PRIMARY_DISPLAY, timestamp, 16'666'666);
- }
- }
-}
-
-void FakeComposerClient::runVSyncAfter(std::chrono::nanoseconds wait) {
- mDelayedEventGenerator->wakeAfter(wait);
-}
-
-LayerImpl& FakeComposerClient::getLayerImpl(Layer handle) {
- // TODO Change these to an internal state check that can be
- // invoked from the gtest? GTest macros do not seem all that safe
- // when used outside the test class
- EXPECT_GE(handle, static_cast<Layer>(0));
- EXPECT_LT(handle, mLayers.size());
- return *(mLayers[handle]);
-}
-
-int FakeComposerClient::getFrameCount() const {
- return mFrames.size();
-}
-
-static std::vector<RenderState> extractRenderState(
- const std::vector<std::unique_ptr<FrameRect>>& internalRects) {
- std::vector<RenderState> result;
- result.reserve(internalRects.size());
- for (const std::unique_ptr<FrameRect>& rect : internalRects) {
- result.push_back(rect->renderState);
- }
- return result;
-}
-
-std::vector<RenderState> FakeComposerClient::getFrameRects(int frame) const {
- Mutex::Autolock _l(mStateMutex);
- return extractRenderState(mFrames[frame]->rectangles);
-}
-
-std::vector<RenderState> FakeComposerClient::getLatestFrame() const {
- Mutex::Autolock _l(mStateMutex);
- return extractRenderState(mFrames[mFrames.size() - 1]->rectangles);
-}
-
-void FakeComposerClient::runVSyncAndWait(std::chrono::nanoseconds maxWait) {
- int currentFrame = 0;
- {
- Mutex::Autolock _l(mStateMutex); // I hope this is ok...
- currentFrame = static_cast<int>(mFrames.size());
- requestVSync();
- }
- waitUntilFrame(currentFrame + 1, maxWait);
-}
-
-void FakeComposerClient::waitUntilFrame(int targetFrame, std::chrono::nanoseconds maxWait) const {
- Mutex::Autolock _l(mStateMutex);
- while (mFrames.size() < static_cast<size_t>(targetFrame)) {
- android::status_t result = mFramesAvailable.waitRelative(mStateMutex, maxWait.count());
- if (result == android::TIMED_OUT) {
- ALOGE("Waiting for frame %d (at frame %zu now) timed out after %lld ns", targetFrame,
- mFrames.size(), maxWait.count());
- return;
- }
- }
-}
-
-void FakeComposerClient::clearFrames() {
- Mutex::Autolock _l(mStateMutex);
- mFrames.clear();
- for (const std::unique_ptr<LayerImpl>& layer : mLayers) {
- if (layer->mValid) {
- layer->mRenderState.mSwapCount = 0;
- }
- }
-}
-
-void FakeComposerClient::onSurfaceFlingerStart() {
- mSurfaceComposer = nullptr;
- do {
- mSurfaceComposer = new android::SurfaceComposerClient;
- android::status_t initResult = mSurfaceComposer->initCheck();
- if (initResult != android::NO_ERROR) {
- ALOGD("Init result: %d", initResult);
- mSurfaceComposer = nullptr;
- std::this_thread::sleep_for(10ms);
- }
- } while (mSurfaceComposer == nullptr);
- ALOGD("SurfaceComposerClient created");
- mSurfaceComposer->enableVSyncInjections(true);
-}
-
-void FakeComposerClient::onSurfaceFlingerStop() {
- mSurfaceComposer->enableVSyncInjections(false);
- mSurfaceComposer->dispose();
- mSurfaceComposer.clear();
-}
-
-// Includes destroyed layers, stored in order of creation.
-int FakeComposerClient::getLayerCount() const {
- return mLayers.size();
-}
-
-Layer FakeComposerClient::getLayer(size_t index) const {
- // NOTE: If/when passing calls through to actual implementation,
- // this might get more involving.
- return static_cast<Layer>(index);
-}
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h b/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
deleted file mode 100644
index 600e765e9b..0000000000
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerClient.h
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright 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 <chrono>
-
-#include <composer-hal/2.1/ComposerClient.h>
-#include <composer-hal/2.2/ComposerClient.h>
-#include <composer-hal/2.3/ComposerClient.h>
-#include <composer-hal/2.4/ComposerClient.h>
-#include <utils/Condition.h>
-
-#include "MockComposerHal.h"
-#include "RenderState.h"
-
-using namespace android::hardware::graphics::common;
-using namespace android::hardware::graphics::composer;
-using namespace android::hardware::graphics::composer::V2_4;
-using namespace android::hardware::graphics::composer::V2_4::hal;
-using namespace android::hardware;
-using namespace std::chrono_literals;
-
-namespace {
-class LayerImpl;
-class Frame;
-class DelayedEventGenerator;
-} // namespace
-
-namespace android {
-class SurfaceComposerClient;
-} // namespace android
-
-namespace sftest {
-// NOTE: The ID's need to be exactly these. VR composer and parts of
-// the SurfaceFlinger assume the display IDs to have these values
-// despite the enum being documented as a display type.
-// TODO: Reference to actual documentation
-constexpr Display PRIMARY_DISPLAY = static_cast<Display>(HWC_DISPLAY_PRIMARY);
-constexpr Display EXTERNAL_DISPLAY = static_cast<Display>(HWC_DISPLAY_EXTERNAL);
-
-class FakeComposerClient : public ComposerHal {
-public:
- FakeComposerClient();
- virtual ~FakeComposerClient();
-
- void setMockHal(MockComposerHal* mockHal) { mMockHal = mockHal; }
-
- bool hasCapability(hwc2_capability_t capability) override;
-
- std::string dumpDebugInfo() override;
- void registerEventCallback(EventCallback* callback) override;
- void unregisterEventCallback() override;
-
- uint32_t getMaxVirtualDisplayCount() override;
- V2_1::Error createVirtualDisplay(uint32_t width, uint32_t height, V1_0::PixelFormat* format,
- Display* outDisplay) override;
- V2_1::Error destroyVirtualDisplay(Display display) override;
- V2_1::Error createLayer(Display display, Layer* outLayer) override;
- V2_1::Error destroyLayer(Display display, Layer layer) override;
-
- V2_1::Error getActiveConfig(Display display, Config* outConfig) override;
- V2_1::Error getClientTargetSupport(Display display, uint32_t width, uint32_t height,
- V1_0::PixelFormat format,
- V1_0::Dataspace dataspace) override;
- V2_1::Error getColorModes(Display display, hidl_vec<V1_0::ColorMode>* outModes) override;
- V2_1::Error getDisplayAttribute(Display display, Config config,
- V2_1::IComposerClient::Attribute attribute,
- int32_t* outValue) override;
- V2_1::Error getDisplayConfigs(Display display, hidl_vec<Config>* outConfigs) override;
- V2_1::Error getDisplayName(Display display, hidl_string* outName) override;
- V2_1::Error getDisplayType(Display display, IComposerClient::DisplayType* outType) override;
- V2_1::Error getDozeSupport(Display display, bool* outSupport) override;
- V2_1::Error getHdrCapabilities(Display display, hidl_vec<V1_0::Hdr>* outTypes,
- float* outMaxLuminance, float* outMaxAverageLuminance,
- float* outMinLuminance) override;
-
- V2_1::Error setActiveConfig(Display display, Config config) override;
- V2_1::Error setColorMode(Display display, V1_0::ColorMode mode) override;
- V2_1::Error setPowerMode(Display display, V2_1::IComposerClient::PowerMode mode) override;
- V2_1::Error setVsyncEnabled(Display display, IComposerClient::Vsync enabled) override;
-
- V2_1::Error setColorTransform(Display display, const float* matrix, int32_t hint) override;
- V2_1::Error setClientTarget(Display display, buffer_handle_t target, int32_t acquireFence,
- int32_t dataspace, const std::vector<hwc_rect_t>& damage) override;
- V2_1::Error setOutputBuffer(Display display, buffer_handle_t buffer,
- int32_t releaseFence) override;
- V2_1::Error validateDisplay(Display display, std::vector<Layer>* outChangedLayers,
- std::vector<IComposerClient::Composition>* outCompositionTypes,
- uint32_t* outDisplayRequestMask,
- std::vector<Layer>* outRequestedLayers,
- std::vector<uint32_t>* outRequestMasks) override;
- V2_1::Error acceptDisplayChanges(Display display) override;
- V2_1::Error presentDisplay(Display display, int32_t* outPresentFence,
- std::vector<Layer>* outLayers,
- std::vector<int32_t>* outReleaseFences) override;
-
- V2_1::Error setLayerCursorPosition(Display display, Layer layer, int32_t x, int32_t y) override;
- V2_1::Error setLayerBuffer(Display display, Layer layer, buffer_handle_t buffer,
- int32_t acquireFence) override;
- V2_1::Error setLayerSurfaceDamage(Display display, Layer layer,
- const std::vector<hwc_rect_t>& damage) override;
- V2_1::Error setLayerBlendMode(Display display, Layer layer, int32_t mode) override;
- V2_1::Error setLayerColor(Display display, Layer layer, IComposerClient::Color color) override;
- V2_1::Error setLayerCompositionType(Display display, Layer layer, int32_t type) override;
- V2_1::Error setLayerDataspace(Display display, Layer layer, int32_t dataspace) override;
- V2_1::Error setLayerDisplayFrame(Display display, Layer layer,
- const hwc_rect_t& frame) override;
- V2_1::Error setLayerPlaneAlpha(Display display, Layer layer, float alpha) override;
- V2_1::Error setLayerSidebandStream(Display display, Layer layer,
- buffer_handle_t stream) override;
- V2_1::Error setLayerSourceCrop(Display display, Layer layer, const hwc_frect_t& crop) override;
- V2_1::Error setLayerTransform(Display display, Layer layer, int32_t transform) override;
- V2_1::Error setLayerVisibleRegion(Display display, Layer layer,
- const std::vector<hwc_rect_t>& visible) override;
- V2_1::Error setLayerZOrder(Display display, Layer layer, uint32_t z) override;
-
- // Composer 2.2
- V2_1::Error getPerFrameMetadataKeys(
- Display display,
- std::vector<V2_2::IComposerClient::PerFrameMetadataKey>* outKeys) override;
- V2_1::Error setLayerPerFrameMetadata(
- Display display, Layer layer,
- const std::vector<V2_2::IComposerClient::PerFrameMetadata>& metadata) override;
-
- V2_1::Error getReadbackBufferAttributes(
- Display display, graphics::common::V1_1::PixelFormat* outFormat,
- graphics::common::V1_1::Dataspace* outDataspace) override;
- V2_1::Error setReadbackBuffer(Display display, const native_handle_t* bufferHandle,
- android::base::unique_fd fenceFd) override;
- V2_1::Error getReadbackBufferFence(Display display,
- android::base::unique_fd* outFenceFd) override;
- V2_1::Error createVirtualDisplay_2_2(uint32_t width, uint32_t height,
- graphics::common::V1_1::PixelFormat* format,
- Display* outDisplay) override;
- V2_1::Error getClientTargetSupport_2_2(Display display, uint32_t width, uint32_t height,
- graphics::common::V1_1::PixelFormat format,
- graphics::common::V1_1::Dataspace dataspace) override;
- V2_1::Error setPowerMode_2_2(Display display, V2_2::IComposerClient::PowerMode mode) override;
-
- V2_1::Error setLayerFloatColor(Display display, Layer layer,
- V2_2::IComposerClient::FloatColor color) override;
-
- V2_1::Error getColorModes_2_2(Display display,
- hidl_vec<graphics::common::V1_1::ColorMode>* outModes) override;
- V2_1::Error getRenderIntents(
- Display display, graphics::common::V1_1::ColorMode mode,
- std::vector<graphics::common::V1_1::RenderIntent>* outIntents) override;
- V2_1::Error setColorMode_2_2(Display display, graphics::common::V1_1::ColorMode mode,
- graphics::common::V1_1::RenderIntent intent) override;
-
- std::array<float, 16> getDataspaceSaturationMatrix(
- graphics::common::V1_1::Dataspace dataspace) override;
-
- // Composer 2.3
- V2_1::Error getPerFrameMetadataKeys_2_3(
- Display display,
- std::vector<V2_3::IComposerClient::PerFrameMetadataKey>* outKeys) override;
-
- V2_1::Error setColorMode_2_3(Display display, graphics::common::V1_2::ColorMode mode,
- graphics::common::V1_1::RenderIntent intent) override;
-
- V2_1::Error getRenderIntents_2_3(
- Display display, graphics::common::V1_2::ColorMode mode,
- std::vector<graphics::common::V1_1::RenderIntent>* outIntents) override;
-
- V2_1::Error getColorModes_2_3(Display display,
- hidl_vec<graphics::common::V1_2::ColorMode>* outModes) override;
-
- V2_1::Error getClientTargetSupport_2_3(Display display, uint32_t width, uint32_t height,
- graphics::common::V1_2::PixelFormat format,
- graphics::common::V1_2::Dataspace dataspace) override;
- V2_1::Error getReadbackBufferAttributes_2_3(
- Display display, graphics::common::V1_2::PixelFormat* outFormat,
- graphics::common::V1_2::Dataspace* outDataspace) override;
- V2_1::Error getHdrCapabilities_2_3(Display display,
- hidl_vec<graphics::common::V1_2::Hdr>* outTypes,
- float* outMaxLuminance, float* outMaxAverageLuminance,
- float* outMinLuminance) override;
- V2_1::Error setLayerPerFrameMetadata_2_3(
- Display display, Layer layer,
- const std::vector<V2_3::IComposerClient::PerFrameMetadata>& metadata) override;
- V2_1::Error getDisplayIdentificationData(Display display, uint8_t* outPort,
- std::vector<uint8_t>* outData) override;
- V2_1::Error setLayerColorTransform(Display display, Layer layer, const float* matrix) override;
- V2_1::Error getDisplayedContentSamplingAttributes(
- uint64_t display, graphics::common::V1_2::PixelFormat& format,
- graphics::common::V1_2::Dataspace& dataspace,
- hidl_bitfield<V2_3::IComposerClient::FormatColorComponent>& componentMask) override;
- V2_1::Error setDisplayedContentSamplingEnabled(
- uint64_t display, V2_3::IComposerClient::DisplayedContentSampling enable,
- hidl_bitfield<V2_3::IComposerClient::FormatColorComponent> componentMask,
- uint64_t maxFrames) override;
- V2_1::Error getDisplayedContentSample(uint64_t display, uint64_t maxFrames, uint64_t timestamp,
- uint64_t& frameCount,
- hidl_vec<uint64_t>& sampleComponent0,
- hidl_vec<uint64_t>& sampleComponent1,
- hidl_vec<uint64_t>& sampleComponent2,
- hidl_vec<uint64_t>& sampleComponent3) override;
- V2_1::Error getDisplayCapabilities(
- Display display,
- std::vector<V2_3::IComposerClient::DisplayCapability>* outCapabilities) override;
- V2_1::Error setLayerPerFrameMetadataBlobs(
- Display display, Layer layer,
- std::vector<V2_3::IComposerClient::PerFrameMetadataBlob>& blobs) override;
- V2_1::Error getDisplayBrightnessSupport(Display display, bool* outSupport) override;
- V2_1::Error setDisplayBrightness(Display display, float brightness) override;
-
- // Composer 2.4
- void registerEventCallback_2_4(EventCallback_2_4* callback) override;
-
- void unregisterEventCallback_2_4() override;
-
- V2_4::Error getDisplayCapabilities_2_4(
- Display display,
- std::vector<V2_4::IComposerClient::DisplayCapability>* outCapabilities) override;
- V2_4::Error getDisplayConnectionType(
- Display display, V2_4::IComposerClient::DisplayConnectionType* outType) override;
- V2_4::Error getDisplayAttribute_2_4(Display display, Config config,
- IComposerClient::Attribute attribute,
- int32_t* outValue) override;
- V2_4::Error getDisplayVsyncPeriod(Display display,
- V2_4::VsyncPeriodNanos* outVsyncPeriod) override;
- V2_4::Error setActiveConfigWithConstraints(
- Display display, Config config,
- const V2_4::IComposerClient::VsyncPeriodChangeConstraints& vsyncPeriodChangeConstraints,
- VsyncPeriodChangeTimeline* outTimeline) override;
- V2_4::Error setAutoLowLatencyMode(Display display, bool on) override;
- V2_4::Error getSupportedContentTypes(
- Display display,
- std::vector<IComposerClient::ContentType>* outSupportedContentTypes) override;
- V2_4::Error setContentType(Display display, IComposerClient::ContentType type) override;
- V2_4::Error validateDisplay_2_4(
- Display display, std::vector<Layer>* outChangedLayers,
- std::vector<IComposerClient::Composition>* outCompositionTypes,
- uint32_t* outDisplayRequestMask, std::vector<Layer>* outRequestedLayers,
- std::vector<uint32_t>* outRequestMasks,
- IComposerClient::ClientTargetProperty* outClientTargetProperty) override;
- V2_4::Error setLayerGenericMetadata(Display display, Layer layer, const std::string& key,
- bool mandatory, const std::vector<uint8_t>& value) override;
- V2_4::Error getLayerGenericMetadataKeys(
- std::vector<IComposerClient::LayerGenericMetadataKey>* outKeys) override;
-
- void setClient(ComposerClient* client);
-
- void requestVSync(uint64_t vsyncTime = 0);
- // We don't want tests hanging, so always use a timeout. Remember
- // to always check the number of frames with test ASSERT_!
- // Wait until next frame is rendered after requesting vsync.
- void runVSyncAndWait(std::chrono::nanoseconds maxWait = 100ms);
- void runVSyncAfter(std::chrono::nanoseconds wait);
-
- int getFrameCount() const;
- // We don't want tests hanging, so always use a timeout. Remember
- // to always check the number of frames with test ASSERT_!
- void waitUntilFrame(int targetFrame, std::chrono::nanoseconds maxWait = 100ms) const;
- std::vector<RenderState> getFrameRects(int frame) const;
- std::vector<RenderState> getLatestFrame() const;
- void clearFrames();
-
- void onSurfaceFlingerStart();
- void onSurfaceFlingerStop();
-
- int getLayerCount() const;
- Layer getLayer(size_t index) const;
-
- void hotplugDisplay(Display display, IComposerCallback::Connection state);
- void refreshDisplay(Display display);
-
-private:
- LayerImpl& getLayerImpl(Layer handle);
-
- EventCallback* mEventCallback;
- EventCallback_2_4* mEventCallback_2_4;
- Config mCurrentConfig;
- bool mVsyncEnabled;
- std::vector<std::unique_ptr<LayerImpl>> mLayers;
- std::vector<std::unique_ptr<Frame>> mFrames;
- // Using a pointer to hide the implementation into the CPP file.
- std::unique_ptr<DelayedEventGenerator> mDelayedEventGenerator;
- android::sp<android::SurfaceComposerClient> mSurfaceComposer; // For VSync injections
- mutable android::Mutex mStateMutex;
- mutable android::Condition mFramesAvailable;
-
- MockComposerHal* mMockHal = nullptr;
-};
-
-} // namespace sftest
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp
deleted file mode 100644
index c656eed0fb..0000000000
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerService.cpp
+++ /dev/null
@@ -1,179 +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 "FakeHwcService"
-#include <log/log.h>
-
-#include "FakeComposerService.h"
-
-using namespace android::hardware;
-using namespace android::hardware::graphics::composer;
-
-namespace sftest {
-
-FakeComposerService_2_1::FakeComposerService_2_1(android::sp<ComposerClient>& client)
- : mClient(client) {}
-
-FakeComposerService_2_1::~FakeComposerService_2_1() {
- ALOGI("Maybe killing client %p", mClient.get());
- // Rely on sp to kill the client.
-}
-
-Return<void> FakeComposerService_2_1::getCapabilities(getCapabilities_cb hidl_cb) {
- ALOGI("FakeComposerService::getCapabilities");
- hidl_cb(hidl_vec<Capability>());
- return Void();
-}
-
-Return<void> FakeComposerService_2_1::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
- ALOGI("FakeComposerService::dumpDebugInfo");
- hidl_cb(hidl_string());
- return Void();
-}
-
-Return<void> FakeComposerService_2_1::createClient(createClient_cb hidl_cb) {
- ALOGI("FakeComposerService::createClient %p", mClient.get());
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_1::Error::NONE, mClient);
- return Void();
-}
-
-FakeComposerService_2_2::FakeComposerService_2_2(android::sp<ComposerClient>& client)
- : mClient(client) {}
-
-FakeComposerService_2_2::~FakeComposerService_2_2() {
- ALOGI("Maybe killing client %p", mClient.get());
- // Rely on sp to kill the client.
-}
-
-Return<void> FakeComposerService_2_2::getCapabilities(getCapabilities_cb hidl_cb) {
- ALOGI("FakeComposerService::getCapabilities");
- hidl_cb(hidl_vec<Capability>());
- return Void();
-}
-
-Return<void> FakeComposerService_2_2::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
- ALOGI("FakeComposerService::dumpDebugInfo");
- hidl_cb(hidl_string());
- return Void();
-}
-
-Return<void> FakeComposerService_2_2::createClient(createClient_cb hidl_cb) {
- ALOGI("FakeComposerService::createClient %p", mClient.get());
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_1::Error::NONE, mClient);
- return Void();
-}
-
-FakeComposerService_2_3::FakeComposerService_2_3(android::sp<ComposerClient>& client)
- : mClient(client) {}
-
-FakeComposerService_2_3::~FakeComposerService_2_3() {
- ALOGI("Maybe killing client %p", mClient.get());
- // Rely on sp to kill the client.
-}
-
-Return<void> FakeComposerService_2_3::getCapabilities(getCapabilities_cb hidl_cb) {
- ALOGI("FakeComposerService::getCapabilities");
- hidl_cb(hidl_vec<Capability>());
- return Void();
-}
-
-Return<void> FakeComposerService_2_3::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
- ALOGI("FakeComposerService::dumpDebugInfo");
- hidl_cb(hidl_string());
- return Void();
-}
-
-Return<void> FakeComposerService_2_3::createClient(createClient_cb hidl_cb) {
- LOG_ALWAYS_FATAL("createClient called on FakeComposerService_2_3");
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_1::Error::UNSUPPORTED, nullptr);
- return Void();
-}
-
-Return<void> FakeComposerService_2_3::createClient_2_3(createClient_2_3_cb hidl_cb) {
- ALOGI("FakeComposerService_2_3::createClient_2_3 %p", mClient.get());
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_1::Error::NONE, mClient);
- return Void();
-}
-
-FakeComposerService_2_4::FakeComposerService_2_4(android::sp<ComposerClient>& client)
- : mClient(client) {}
-
-FakeComposerService_2_4::~FakeComposerService_2_4() {
- ALOGI("Maybe killing client %p", mClient.get());
- // Rely on sp to kill the client.
-}
-
-Return<void> FakeComposerService_2_4::getCapabilities(getCapabilities_cb hidl_cb) {
- ALOGI("FakeComposerService::getCapabilities");
- hidl_cb(hidl_vec<Capability>());
- return Void();
-}
-
-Return<void> FakeComposerService_2_4::dumpDebugInfo(dumpDebugInfo_cb hidl_cb) {
- ALOGI("FakeComposerService::dumpDebugInfo");
- hidl_cb(hidl_string());
- return Void();
-}
-
-Return<void> FakeComposerService_2_4::createClient(createClient_cb hidl_cb) {
- LOG_ALWAYS_FATAL("createClient called on FakeComposerService_2_4");
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_1::Error::UNSUPPORTED, nullptr);
- return Void();
-}
-
-Return<void> FakeComposerService_2_4::createClient_2_3(createClient_2_3_cb hidl_cb) {
- LOG_ALWAYS_FATAL("createClient_2_3 called on FakeComposerService_2_4");
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_1::Error::UNSUPPORTED, nullptr);
- return Void();
-}
-
-Return<void> FakeComposerService_2_4::createClient_2_4(createClient_2_4_cb hidl_cb) {
- ALOGI("FakeComposerService_2_4::createClient_2_4 %p", mClient.get());
- if (!mClient->init()) {
- LOG_ALWAYS_FATAL("failed to initialize ComposerClient");
- }
- hidl_cb(V2_4::Error::NONE, mClient);
- return Void();
-}
-
-} // namespace sftest
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerService.h b/services/surfaceflinger/tests/fakehwc/FakeComposerService.h
deleted file mode 100644
index 47f970f5b2..0000000000
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerService.h
+++ /dev/null
@@ -1,92 +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 <android/hardware/graphics/composer/2.4/IComposer.h>
-#include <composer-hal/2.1/ComposerClient.h>
-#include <composer-hal/2.2/ComposerClient.h>
-#include <composer-hal/2.3/ComposerClient.h>
-#include <composer-hal/2.4/ComposerClient.h>
-
-using android::hardware::Return;
-
-using ComposerClient = android::hardware::graphics::composer::V2_4::hal::ComposerClient;
-
-namespace sftest {
-
-using IComposer_2_1 = android::hardware::graphics::composer::V2_1::IComposer;
-
-class FakeComposerService_2_1 : public IComposer_2_1 {
-public:
- explicit FakeComposerService_2_1(android::sp<ComposerClient>& client);
- virtual ~FakeComposerService_2_1();
-
- Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
- Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
- Return<void> createClient(createClient_cb hidl_cb) override;
-
-private:
- android::sp<ComposerClient> mClient;
-};
-
-using IComposer_2_2 = android::hardware::graphics::composer::V2_2::IComposer;
-class FakeComposerService_2_2 : public IComposer_2_2 {
-public:
- explicit FakeComposerService_2_2(android::sp<ComposerClient>& client);
- virtual ~FakeComposerService_2_2();
-
- Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
- Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
- Return<void> createClient(createClient_cb hidl_cb) override;
-
-private:
- android::sp<ComposerClient> mClient;
-};
-
-using IComposer_2_3 = android::hardware::graphics::composer::V2_3::IComposer;
-class FakeComposerService_2_3 : public IComposer_2_3 {
-public:
- explicit FakeComposerService_2_3(android::sp<ComposerClient>& client);
- virtual ~FakeComposerService_2_3();
-
- Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
- Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
- Return<void> createClient(createClient_cb hidl_cb) override;
- Return<void> createClient_2_3(createClient_2_3_cb hidl_cb) override;
-
-private:
- android::sp<ComposerClient> mClient;
-};
-
-using IComposer_2_4 = android::hardware::graphics::composer::V2_4::IComposer;
-
-class FakeComposerService_2_4 : public IComposer_2_4 {
-public:
- explicit FakeComposerService_2_4(android::sp<ComposerClient>& client);
- virtual ~FakeComposerService_2_4();
-
- Return<void> getCapabilities(getCapabilities_cb hidl_cb) override;
- Return<void> dumpDebugInfo(dumpDebugInfo_cb hidl_cb) override;
- Return<void> createClient(createClient_cb hidl_cb) override;
- Return<void> createClient_2_3(createClient_2_3_cb hidl_cb) override;
- Return<void> createClient_2_4(createClient_2_4_cb hidl_cb) override;
-
-private:
- android::sp<ComposerClient> mClient;
-};
-
-} // namespace sftest
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp
deleted file mode 100644
index 1cea25a80f..0000000000
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.cpp
+++ /dev/null
@@ -1,193 +0,0 @@
-/*
- * Copyright 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 "FakeHwcUtil"
-#include <log/log.h>
-
-#include "FakeComposerUtils.h"
-#include "RenderState.h"
-
-#include "SurfaceFlinger.h" // Get the name of the service...
-
-#include <binder/IServiceManager.h>
-#include <cutils/properties.h>
-#include <hidl/ServiceManagement.h>
-
-#include <iomanip>
-#include <thread>
-
-using android::String16;
-using android::sp;
-using namespace std::chrono_literals;
-using namespace sftest;
-using std::setw;
-
-namespace sftest {
-
-// clang-format off
-inline void printSourceRectAligned(::std::ostream& os, const hwc_frect_t& sourceRect, int align) {
- os << std::fixed << std::setprecision(1) << "("
- << setw(align) << sourceRect.left << setw(0) << ","
- << setw(align) << sourceRect.top << setw(0) << ","
- << setw(align) << sourceRect.right << setw(0) << ","
- << setw(align) << sourceRect.bottom << setw(0) << ")";
-}
-
-inline void printDisplayRectAligned(::std::ostream& os, const hwc_rect_t& displayRect, int align) {
- os << "("
- << setw(align) << displayRect.left << setw(0) << ","
- << setw(align) << displayRect.top << setw(0) << ","
- << setw(align) << displayRect.right << setw(0) << ","
- << setw(align) << displayRect.bottom << setw(0) << ")";
-}
-// clang-format on
-
-inline ::std::ostream& operator<<(::std::ostream& os, const sftest::RenderState& state) {
- printSourceRectAligned(os, state.mSourceCrop, 7);
- os << "->";
- printDisplayRectAligned(os, state.mDisplayFrame, 5);
- return os << " Swaps:" << state.mSwapCount << " Alpha:" << std::setprecision(3)
- << state.mPlaneAlpha << " Xform:" << state.mTransform;
-}
-
-// Helper for verifying the parts of the RenderState
-template <typename T>
-bool valuesMatch(::testing::AssertionResult& message, const T& ref, const T& val,
- const char* name) {
- if (ref != val) {
- message = message << "Expected " << name << ":" << ref << ", got:" << val << ".";
- return false;
- }
- return true;
-}
-
-::testing::AssertionResult rectsAreSame(const RenderState& ref, const RenderState& val) {
- // TODO: Message could start as success and be assigned as failure.
- // Only problem is that utility assumes it to be failure and just adds stuff. Would
- // need still special case the initial failure in the utility?
- // TODO: ... or would it be possible to break this back to gtest primitives?
- ::testing::AssertionResult message = ::testing::AssertionFailure();
- bool passes = true;
-
- // The work here is mostly about providing good log strings for differences
- passes &= valuesMatch(message, ref.mDisplayFrame, val.mDisplayFrame, "display frame");
- passes &= valuesMatch(message, ref.mPlaneAlpha, val.mPlaneAlpha, "alpha");
- passes &= valuesMatch(message, ref.mSwapCount, val.mSwapCount, "swap count");
- passes &= valuesMatch(message, ref.mSourceCrop, val.mSourceCrop, "source crop");
- // ... add more
- if (passes) {
- return ::testing::AssertionSuccess();
- }
- return message;
-}
-
-::testing::AssertionResult framesAreSame(const std::vector<RenderState>& ref,
- const std::vector<RenderState>& val) {
- ::testing::AssertionResult message = ::testing::AssertionFailure();
- bool passed = true;
- if (ref.size() != val.size()) {
- message << "Expected " << ref.size() << " rects, got " << val.size() << ".";
- passed = false;
- }
- for (size_t rectIndex = 0; rectIndex < std::min(ref.size(), val.size()); rectIndex++) {
- ::testing::AssertionResult rectResult = rectsAreSame(ref[rectIndex], val[rectIndex]);
- if (rectResult == false) {
- message << "First different rect at " << rectIndex << ": " << rectResult.message();
- passed = false;
- break;
- }
- }
-
- if (passed) {
- return ::testing::AssertionSuccess();
- } else {
- message << "\nReference:";
- for (auto state = ref.begin(); state != ref.end(); ++state) {
- message << "\n" << *state;
- }
- message << "\nActual:";
- for (auto state = val.begin(); state != val.end(); ++state) {
- message << "\n" << *state;
- }
- }
- return message;
-}
-
-void startSurfaceFlinger() {
- ALOGI("Start SurfaceFlinger");
- system("start surfaceflinger");
-
- sp<android::IServiceManager> sm(android::defaultServiceManager());
- sp<android::IBinder> sf;
- while (sf == nullptr) {
- std::this_thread::sleep_for(10ms);
- sf = sm->checkService(String16(android::SurfaceFlinger::getServiceName()));
- }
- ALOGV("SurfaceFlinger running");
-}
-
-void stopSurfaceFlinger() {
- ALOGI("Stop SurfaceFlinger");
- system("stop surfaceflinger");
- sp<android::IServiceManager> sm(android::defaultServiceManager());
- sp<android::IBinder> sf;
- while (sf != nullptr) {
- std::this_thread::sleep_for(10ms);
- sf = sm->checkService(String16(android::SurfaceFlinger::getServiceName()));
- }
- ALOGV("SurfaceFlinger stopped");
-}
-
-////////////////////////////////////////////////
-
-void FakeHwcEnvironment::SetUp() {
- ALOGI("Test env setup");
- system("setenforce 0");
- system("stop");
- property_set("debug.sf.nobootanimation", "1");
- {
- char value[PROPERTY_VALUE_MAX];
- property_get("debug.sf.nobootanimation", value, "0");
- LOG_FATAL_IF(atoi(value) != 1, "boot skip not set");
- }
- // TODO: Try registering the mock as the default service instead.
- property_set("debug.sf.hwc_service_name", "mock");
-
- // This allows tests/SF to register/load a HIDL service not listed in manifest files.
- android::hardware::details::setTrebleTestingOverride(true);
- property_set("debug.sf.treble_testing_override", "true");
-}
-
-void FakeHwcEnvironment::TearDown() {
- ALOGI("Test env tear down");
- system("stop");
- // Wait for mock call signaling teardown?
- property_set("debug.sf.nobootanimation", "0");
- property_set("debug.sf.hwc_service_name", "default");
- system("setenforce 1");
- ALOGI("Test env tear down - done");
-}
-
-} // namespace sftest
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h b/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h
deleted file mode 100644
index 383a111859..0000000000
--- a/services/surfaceflinger/tests/fakehwc/FakeComposerUtils.h
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright 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 "FakeComposerClient.h"
-
-#include <gui/SurfaceComposerClient.h>
-#include <log/log.h>
-#include <gtest/gtest.h>
-
-// clang-format off
-// Note: This needs to reside in the global namespace for the GTest to use it
-inline ::std::ostream& operator<<(::std::ostream& os, const hwc_rect_t& rect) {
- return os << "(" << rect.left << ","
- << rect.top << ","
- << rect.right << ","
- << rect.bottom << ")";
-}
-
-inline ::std::ostream& operator<<(::std::ostream& os, const hwc_frect_t& rect) {
- return os << "(" << rect.left << ","
- << rect.top << ","
- << rect.right << ","
- << rect.bottom << ")";
-}
-// clang-format on
-
-namespace sftest {
-
-class RenderState;
-
-// clang-format off
-inline bool operator==(const hwc_rect_t& a, const hwc_rect_t& b) {
- return a.top == b.top &&
- a.left == b.left &&
- a.bottom == b.bottom &&
- a.right == b.right;
-}
-
-inline bool operator==(const hwc_frect_t& a, const hwc_frect_t& b) {
- return a.top == b.top &&
- a.left == b.left &&
- a.bottom == b.bottom &&
- a.right == b.right;
-}
-// clang-format on
-
-inline bool operator!=(const hwc_rect_t& a, const hwc_rect_t& b) {
- return !(a == b);
-}
-
-inline bool operator!=(const hwc_frect_t& a, const hwc_frect_t& b) {
- return !(a == b);
-}
-
-::testing::AssertionResult rectsAreSame(const RenderState& ref, const RenderState& val);
-::testing::AssertionResult framesAreSame(const std::vector<RenderState>& ref,
- const std::vector<RenderState>& val);
-
-void startSurfaceFlinger();
-void stopSurfaceFlinger();
-
-class FakeHwcEnvironment : public ::testing::Environment {
-public:
- virtual ~FakeHwcEnvironment() {}
- void SetUp() override;
- void TearDown() override;
-};
-
-/*
- * All surface state changes are supposed to happen inside a global
- * transaction. TransactionScope object at the beginning of
- * scope automates the process. The resulting scope gives a visual cue
- * on the span of the transaction as well.
- *
- * Closing the transaction is synchronous, i.e., it waits for
- * SurfaceFlinger to composite one frame. Now, the FakeComposerClient
- * is built to explicitly request vsyncs one at the time. A delayed
- * request must be made before closing the transaction or the test
- * thread stalls until SurfaceFlinger does an emergency vsync by
- * itself. TransactionScope encapsulates this vsync magic.
- */
-class TransactionScope : public android::SurfaceComposerClient::Transaction {
-public:
- explicit TransactionScope(FakeComposerClient& composer) : Transaction(), mComposer(composer) {}
-
- ~TransactionScope() {
- int frameCount = mComposer.getFrameCount();
- mComposer.runVSyncAfter(1ms);
- LOG_ALWAYS_FATAL_IF(android::NO_ERROR != apply());
- // Make sure that exactly one frame has been rendered.
- mComposer.waitUntilFrame(frameCount + 1);
- // LOG_ALWAYS_FATAL_IF(frameCount + 1 != mComposer.getFrameCount(),
- // "Unexpected frame advance. Delta: %d",
- // mComposer.getFrameCount() - frameCount);
- }
-
- FakeComposerClient& mComposer;
-};
-
-} // namespace sftest
diff --git a/services/surfaceflinger/tests/fakehwc/MockComposerHal.h b/services/surfaceflinger/tests/fakehwc/MockComposerHal.h
deleted file mode 100644
index 5dc3778153..0000000000
--- a/services/surfaceflinger/tests/fakehwc/MockComposerHal.h
+++ /dev/null
@@ -1,47 +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.
- */
-
-#pragma once
-
-#include <composer-hal/2.4/ComposerClient.h>
-
-#include <gmock/gmock.h>
-
-using namespace android::hardware::graphics::common;
-using namespace android::hardware::graphics::composer;
-using namespace android::hardware::graphics::composer::V2_4;
-using namespace android::hardware::graphics::composer::V2_4::hal;
-using namespace android::hardware;
-using namespace std::chrono_literals;
-
-namespace sftest {
-
-// Mock class for ComposerHal. Implements only the functions used in the test.
-class MockComposerHal {
-public:
- MOCK_METHOD2(getActiveConfig, V2_1::Error(Display, Config*));
- MOCK_METHOD4(getDisplayAttribute_2_4,
- V2_4::Error(Display, Config, V2_4::IComposerClient::Attribute, int32_t*));
- MOCK_METHOD2(getDisplayConfigs, V2_1::Error(Display, hidl_vec<Config>*));
- MOCK_METHOD2(setActiveConfig, V2_1::Error(Display, Config));
- MOCK_METHOD2(getDisplayVsyncPeriod, V2_4::Error(Display, V2_4::VsyncPeriodNanos*));
- MOCK_METHOD4(setActiveConfigWithConstraints,
- V2_4::Error(Display, Config,
- const V2_4::IComposerClient::VsyncPeriodChangeConstraints&,
- VsyncPeriodChangeTimeline*));
-};
-
-} // namespace sftest \ No newline at end of file
diff --git a/services/surfaceflinger/tests/fakehwc/RenderState.h b/services/surfaceflinger/tests/fakehwc/RenderState.h
deleted file mode 100644
index 40193f237e..0000000000
--- a/services/surfaceflinger/tests/fakehwc/RenderState.h
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 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 <vector>
-
-namespace sftest {
-// Description of a rendered rectangle. Should only contain
-// instructions necessary to rasterize the rectangle. The full scene
-// is given as a sorted list of rectangles, bottom layer at index 0.
-class RenderState {
-public:
- RenderState() = default;
- // Default copy-ctor
-
- hwc_rect_t mDisplayFrame = {0, 0, 0, 0};
- hwc_frect_t mSourceCrop = {0.f, 0.f, 0.f, 0.f};
- std::vector<hwc_rect_t> mVisibleRegion;
- hwc2_blend_mode_t mBlendMode = HWC2_BLEND_MODE_NONE;
- buffer_handle_t mBuffer = 0;
- uint32_t mSwapCount = 0; // How many set buffer calls to the layer.
- int32_t mAcquireFence = 0; // Probably should not be here.
- float mPlaneAlpha = 0.f;
- hwc_color_t mLayerColor = {0, 0, 0, 0};
- hwc_transform_t mTransform = static_cast<hwc_transform_t>(0);
-};
-
-} // namespace sftest
diff --git a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp b/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
deleted file mode 100644
index b3b4ec15cd..0000000000
--- a/services/surfaceflinger/tests/fakehwc/SFFakeHwc_test.cpp
+++ /dev/null
@@ -1,1790 +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"
-#pragma clang diagnostic ignored "-Wextra"
-
-// #define LOG_NDEBUG 0
-#undef LOG_TAG
-#define LOG_TAG "FakeHwcTest"
-
-#include "FakeComposerClient.h"
-#include "FakeComposerService.h"
-#include "FakeComposerUtils.h"
-#include "MockComposerHal.h"
-
-#include <binder/Parcel.h>
-#include <gui/DisplayEventReceiver.h>
-#include <gui/ISurfaceComposer.h>
-#include <gui/LayerDebugInfo.h>
-#include <gui/LayerState.h>
-#include <gui/Surface.h>
-#include <gui/SurfaceComposerClient.h>
-
-#include <android/hidl/manager/1.0/IServiceManager.h>
-#include <android/looper.h>
-#include <android/native_window.h>
-#include <binder/ProcessState.h>
-#include <hwbinder/ProcessState.h>
-#include <log/log.h>
-#include <private/gui/ComposerService.h>
-#include <ui/DisplayMode.h>
-#include <ui/DynamicDisplayInfo.h>
-#include <utils/Looper.h>
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <limits>
-#include <thread>
-
-using namespace std::chrono_literals;
-
-using namespace android;
-using namespace android::hardware;
-
-using namespace sftest;
-
-namespace {
-
-// Mock test helpers
-using ::testing::_;
-using ::testing::DoAll;
-using ::testing::Return;
-using ::testing::SetArgPointee;
-
-using Transaction = SurfaceComposerClient::Transaction;
-using Attribute = V2_4::IComposerClient::Attribute;
-using Display = V2_1::Display;
-
-///////////////////////////////////////////////
-constexpr PhysicalDisplayId physicalIdFromHwcDisplayId(Display hwcId) {
- return PhysicalDisplayId::fromPort(hwcId);
-}
-constexpr PhysicalDisplayId kPrimaryDisplayId = physicalIdFromHwcDisplayId(PRIMARY_DISPLAY);
-constexpr PhysicalDisplayId kExternalDisplayId = physicalIdFromHwcDisplayId(EXTERNAL_DISPLAY);
-
-struct TestColor {
-public:
- uint8_t r;
- uint8_t g;
- uint8_t b;
- uint8_t a;
-};
-
-constexpr static TestColor RED = {195, 63, 63, 255};
-constexpr static TestColor LIGHT_RED = {255, 177, 177, 255};
-constexpr static TestColor GREEN = {63, 195, 63, 255};
-constexpr static TestColor BLUE = {63, 63, 195, 255};
-constexpr static TestColor LIGHT_GRAY = {200, 200, 200, 255};
-
-// Fill an RGBA_8888 formatted surface with a single color.
-static void fillSurfaceRGBA8(const sp<SurfaceControl>& sc, const TestColor& color,
- bool unlock = true) {
- ANativeWindow_Buffer outBuffer;
- sp<Surface> s = sc->getSurface();
- ASSERT_TRUE(s != nullptr);
- ASSERT_EQ(NO_ERROR, s->lock(&outBuffer, nullptr));
- uint8_t* img = reinterpret_cast<uint8_t*>(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] = color.a;
- }
- }
- if (unlock) {
- ASSERT_EQ(NO_ERROR, s->unlockAndPost());
- }
-}
-
-inline RenderState makeSimpleRect(int left, int top, int right, int bottom) {
- RenderState res;
- res.mDisplayFrame = hwc_rect_t{left, top, right, bottom};
- res.mPlaneAlpha = 1.0f;
- res.mSwapCount = 0;
- res.mSourceCrop = hwc_frect_t{0.f, 0.f, static_cast<float>(right - left),
- static_cast<float>(bottom - top)};
- return res;
-}
-
-inline RenderState makeSimpleRect(unsigned int left, unsigned int top, unsigned int right,
- unsigned int bottom) {
- EXPECT_LE(left, static_cast<unsigned int>(INT_MAX));
- EXPECT_LE(top, static_cast<unsigned int>(INT_MAX));
- EXPECT_LE(right, static_cast<unsigned int>(INT_MAX));
- EXPECT_LE(bottom, static_cast<unsigned int>(INT_MAX));
- return makeSimpleRect(static_cast<int>(left), static_cast<int>(top), static_cast<int>(right),
- static_cast<int>(bottom));
-}
-
-///////////////////////////////////////////////
-template <typename FakeComposerService>
-class DisplayTest : public ::testing::Test {
-protected:
- struct TestConfig {
- int32_t id;
- int32_t w;
- int32_t h;
- int32_t vsyncPeriod;
- int32_t group;
- };
-
- static int processDisplayEvents(int /*fd*/, int /*events*/, void* data) {
- auto self = static_cast<DisplayTest*>(data);
-
- ssize_t n;
- DisplayEventReceiver::Event buffer[1];
-
- while ((n = self->mReceiver->getEvents(buffer, 1)) > 0) {
- for (int i = 0; i < n; i++) {
- self->mReceivedDisplayEvents.push_back(buffer[i]);
- }
- }
- ALOGD_IF(n < 0, "Error reading events (%s)", strerror(-n));
- return 1;
- }
-
- Error getDisplayAttributeNoMock(Display display, Config config,
- V2_4::IComposerClient::Attribute attribute, int32_t* outValue) {
- mFakeComposerClient->setMockHal(nullptr);
- auto ret =
- mFakeComposerClient->getDisplayAttribute_2_4(display, config, attribute, outValue);
- mFakeComposerClient->setMockHal(mMockComposer.get());
- return ret;
- }
-
- void setExpectationsForConfigs(Display display, std::vector<TestConfig> testConfigs,
- Config activeConfig, V2_4::VsyncPeriodNanos defaultVsyncPeriod) {
- std::vector<Config> configIds;
- for (size_t i = 0; i < testConfigs.size(); i++) {
- configIds.push_back(testConfigs[i].id);
-
- EXPECT_CALL(*mMockComposer,
- getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::WIDTH, _))
- .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].w), Return(Error::NONE)));
- EXPECT_CALL(*mMockComposer,
- getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::HEIGHT, _))
- .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].h), Return(Error::NONE)));
- EXPECT_CALL(*mMockComposer,
- getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::VSYNC_PERIOD,
- _))
- .WillRepeatedly(DoAll(SetArgPointee<3>(testConfigs[i].vsyncPeriod),
- Return(Error::NONE)));
- EXPECT_CALL(*mMockComposer,
- getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::CONFIG_GROUP,
- _))
- .WillRepeatedly(
- DoAll(SetArgPointee<3>(testConfigs[i].group), Return(Error::NONE)));
- EXPECT_CALL(*mMockComposer,
- getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::DPI_X, _))
- .WillRepeatedly(Return(Error::UNSUPPORTED));
- EXPECT_CALL(*mMockComposer,
- getDisplayAttribute_2_4(display, testConfigs[i].id, Attribute::DPI_Y, _))
- .WillRepeatedly(Return(Error::UNSUPPORTED));
- }
-
- EXPECT_CALL(*mMockComposer, getDisplayConfigs(display, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(hidl_vec<Config>(configIds)),
- Return(V2_1::Error::NONE)));
-
- EXPECT_CALL(*mMockComposer, getActiveConfig(display, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(activeConfig), Return(V2_1::Error::NONE)));
-
- EXPECT_CALL(*mMockComposer, getDisplayVsyncPeriod(display, _))
- .WillRepeatedly(
- DoAll(SetArgPointee<1>(defaultVsyncPeriod), Return(V2_4::Error::NONE)));
- }
-
- void SetUp() override {
- mMockComposer = std::make_unique<MockComposerHal>();
- mFakeComposerClient = new FakeComposerClient();
- mFakeComposerClient->setMockHal(mMockComposer.get());
-
- sp<V2_4::hal::ComposerClient> client = new V2_4::hal::ComposerClient(mFakeComposerClient);
- mFakeService = new FakeComposerService(client);
- ASSERT_EQ(android::OK, mFakeService->registerAsService("mock"));
-
- android::hardware::ProcessState::self()->startThreadPool();
- android::ProcessState::self()->startThreadPool();
-
- setExpectationsForConfigs(PRIMARY_DISPLAY,
- {{
- .id = 1,
- .w = 1920,
- .h = 1024,
- .vsyncPeriod = 16'666'666,
- .group = 0,
- }},
- 1, 16'666'666);
-
- startSurfaceFlinger();
-
- // Fake composer wants to enable VSync injection
- mFakeComposerClient->onSurfaceFlingerStart();
-
- mComposerClient = new SurfaceComposerClient;
- ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
-
- mReceiver.reset(new DisplayEventReceiver(ISurfaceComposer::eVsyncSourceApp,
- ISurfaceComposer::EventRegistration::modeChanged));
- mLooper = new Looper(false);
- mLooper->addFd(mReceiver->getFd(), 0, ALOOPER_EVENT_INPUT, processDisplayEvents, this);
- }
-
- void TearDown() override {
- mLooper = nullptr;
- mReceiver = nullptr;
-
- mComposerClient->dispose();
- mComposerClient = nullptr;
-
- // Fake composer needs to release SurfaceComposerClient before the stop.
- mFakeComposerClient->onSurfaceFlingerStop();
- stopSurfaceFlinger();
-
- mFakeComposerClient->setMockHal(nullptr);
-
- mFakeService = nullptr;
- // TODO: Currently deleted in FakeComposerClient::removeClient(). Devise better lifetime
- // management.
- mMockComposer = nullptr;
- }
-
- void waitForDisplayTransaction(Display display) {
- // Both a refresh and a vsync event are needed to apply pending display
- // transactions.
- mFakeComposerClient->refreshDisplay(display);
- mFakeComposerClient->runVSyncAndWait();
-
- // Extra vsync and wait to avoid a 10% flake due to a race.
- mFakeComposerClient->runVSyncAndWait();
- }
-
- bool waitForHotplugEvent(Display displayId, bool connected) {
- return waitForHotplugEvent(physicalIdFromHwcDisplayId(displayId), connected);
- }
-
- bool waitForHotplugEvent(PhysicalDisplayId displayId, bool connected) {
- int waitCount = 20;
- while (waitCount--) {
- while (!mReceivedDisplayEvents.empty()) {
- auto event = mReceivedDisplayEvents.front();
- mReceivedDisplayEvents.pop_front();
-
- ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG,
- "event hotplug: displayId %s, connected %d",
- to_string(event.header.displayId).c_str(), event.hotplug.connected);
-
- if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_HOTPLUG &&
- event.header.displayId == displayId && event.hotplug.connected == connected) {
- return true;
- }
- }
-
- mLooper->pollOnce(1);
- }
- return false;
- }
-
- bool waitForModeChangedEvent(Display display, int32_t modeId) {
- PhysicalDisplayId displayId = physicalIdFromHwcDisplayId(display);
- int waitCount = 20;
- while (waitCount--) {
- while (!mReceivedDisplayEvents.empty()) {
- auto event = mReceivedDisplayEvents.front();
- mReceivedDisplayEvents.pop_front();
-
- ALOGV_IF(event.header.type == DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE,
- "event mode: displayId %s, modeId %d",
- to_string(event.header.displayId).c_str(), event.modeChange.modeId);
-
- if (event.header.type == DisplayEventReceiver::DISPLAY_EVENT_MODE_CHANGE &&
- event.header.displayId == displayId && event.modeChange.modeId == modeId) {
- return true;
- }
- }
-
- mLooper->pollOnce(1);
- }
- return false;
- }
-
- void Test_HotplugOneConfig() {
- ALOGD("DisplayTest::Test_Hotplug_oneConfig");
-
- setExpectationsForConfigs(EXTERNAL_DISPLAY,
- {{.id = 1,
- .w = 200,
- .h = 400,
- .vsyncPeriod = 16'666'666,
- .group = 0}},
- 1, 16'666'666);
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::CONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
-
- {
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId);
- EXPECT_FALSE(display == nullptr);
-
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- const ui::Size& resolution = mode.resolution;
- EXPECT_EQ(ui::Size(200, 400), resolution);
- EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
-
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::DISCONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- mFakeComposerClient->clearFrames();
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
-
- {
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId);
- EXPECT_TRUE(display == nullptr);
-
- ui::DisplayMode mode;
- EXPECT_NE(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- }
- }
-
- void Test_HotplugTwoSeparateConfigs() {
- ALOGD("DisplayTest::Test_HotplugTwoSeparateConfigs");
-
- setExpectationsForConfigs(EXTERNAL_DISPLAY,
- {{.id = 1,
- .w = 200,
- .h = 400,
- .vsyncPeriod = 16'666'666,
- .group = 0},
- {.id = 2,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 11'111'111,
- .group = 1}},
- 1, 16'666'666);
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::CONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
-
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId);
- EXPECT_FALSE(display == nullptr);
-
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(200, 400), mode.resolution);
- EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- ui::DynamicDisplayInfo info;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
- const auto& modes = info.supportedDisplayModes;
- EXPECT_EQ(modes.size(), 2);
-
- // change active mode
-
- if (mIs2_4Client) {
- EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 2, _, _))
- .WillOnce(Return(V2_4::Error::NONE));
- } else {
- EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 2))
- .WillOnce(Return(V2_1::Error::NONE));
- }
-
- for (int i = 0; i < modes.size(); i++) {
- const auto& mode = modes[i];
- if (mode.resolution.getWidth() == 800) {
- EXPECT_EQ(NO_ERROR,
- SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate));
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i));
- break;
- }
- }
-
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::DISCONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- mFakeComposerClient->clearFrames();
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
- }
-
- void Test_HotplugTwoConfigsSameGroup() {
- ALOGD("DisplayTest::Test_HotplugTwoConfigsSameGroup");
-
- setExpectationsForConfigs(EXTERNAL_DISPLAY,
- {{.id = 2,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 16'666'666,
- .group = 31},
- {.id = 3,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 11'111'111,
- .group = 31}},
- 2, 16'666'666);
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::CONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
-
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId);
- EXPECT_FALSE(display == nullptr);
-
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- ui::DynamicDisplayInfo info;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
- const auto& modes = info.supportedDisplayModes;
- EXPECT_EQ(modes.size(), 2);
-
- // change active mode
- if (mIs2_4Client) {
- EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 3, _, _))
- .WillOnce(Return(V2_4::Error::NONE));
- } else {
- EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 3))
- .WillOnce(Return(V2_1::Error::NONE));
- }
-
- for (int i = 0; i < modes.size(); i++) {
- const auto& mode = modes[i];
- if (mode.refreshRate == 1e9f / 11'111'111) {
- EXPECT_EQ(NO_ERROR,
- SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate));
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i));
- break;
- }
- }
-
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::DISCONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- mFakeComposerClient->clearFrames();
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
- }
-
- void Test_HotplugThreeConfigsMixedGroups() {
- ALOGD("DisplayTest::Test_HotplugThreeConfigsMixedGroups");
-
- setExpectationsForConfigs(EXTERNAL_DISPLAY,
- {{.id = 2,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 16'666'666,
- .group = 0},
- {.id = 3,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 11'111'111,
- .group = 0},
- {.id = 4,
- .w = 1600,
- .h = 3200,
- .vsyncPeriod = 8'333'333,
- .group = 1},
- {.id = 5,
- .w = 1600,
- .h = 3200,
- .vsyncPeriod = 11'111'111,
- .group = 1}},
- 2, 16'666'666);
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::CONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, true));
-
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kExternalDisplayId);
- EXPECT_FALSE(display == nullptr);
-
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- ui::DynamicDisplayInfo info;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
- const auto& modes = info.supportedDisplayModes;
- EXPECT_EQ(modes.size(), 4);
-
- // change active mode to 800x1600@90Hz
- if (mIs2_4Client) {
- EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 3, _, _))
- .WillOnce(Return(V2_4::Error::NONE));
- } else {
- EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 3))
- .WillOnce(Return(V2_1::Error::NONE));
- }
-
- for (size_t i = 0; i < modes.size(); i++) {
- const auto& mode = modes[i];
- if (mode.resolution.getWidth() == 800 && mode.refreshRate == 1e9f / 11'111'111) {
- EXPECT_EQ(NO_ERROR,
- SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false,
- modes[i].refreshRate,
- modes[i].refreshRate,
- modes[i].refreshRate,
- modes[i].refreshRate));
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i));
- break;
- }
- }
-
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- // change active mode to 1600x3200@120Hz
- if (mIs2_4Client) {
- EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 4, _, _))
- .WillOnce(Return(V2_4::Error::NONE));
- } else {
- EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 4))
- .WillOnce(Return(V2_1::Error::NONE));
- }
-
- for (int i = 0; i < modes.size(); i++) {
- const auto& mode = modes[i];
- if (mode.refreshRate == 1e9f / 8'333'333) {
- EXPECT_EQ(NO_ERROR,
- SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate));
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i));
- break;
- }
- }
-
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(1600, 3200), mode.resolution);
- EXPECT_EQ(1e9f / 8'333'333, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- // change active mode to 1600x3200@90Hz
- if (mIs2_4Client) {
- EXPECT_CALL(*mMockComposer, setActiveConfigWithConstraints(EXTERNAL_DISPLAY, 5, _, _))
- .WillOnce(Return(V2_4::Error::NONE));
- } else {
- EXPECT_CALL(*mMockComposer, setActiveConfig(EXTERNAL_DISPLAY, 5))
- .WillOnce(Return(V2_1::Error::NONE));
- }
-
- for (int i = 0; i < modes.size(); i++) {
- const auto& mode = modes[i];
- if (mode.resolution.getWidth() == 1600 && mode.refreshRate == 1e9f / 11'111'111) {
- EXPECT_EQ(NO_ERROR,
- SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate,
- mode.refreshRate));
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- EXPECT_TRUE(waitForModeChangedEvent(EXTERNAL_DISPLAY, i));
- break;
- }
- }
-
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(1600, 3200), mode.resolution);
- EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate);
-
- mFakeComposerClient->clearFrames();
- {
- const ui::Size& resolution = mode.resolution;
- auto surfaceControl =
- mComposerClient->createSurface(String8("Display Test Surface Foo"),
- resolution.getWidth(), resolution.getHeight(),
- PIXEL_FORMAT_RGBA_8888, 0);
- EXPECT_TRUE(surfaceControl != nullptr);
- EXPECT_TRUE(surfaceControl->isValid());
- fillSurfaceRGBA8(surfaceControl, BLUE);
-
- {
- TransactionScope ts(*mFakeComposerClient);
- ts.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- ts.setLayer(surfaceControl, INT32_MAX - 2).show(surfaceControl);
- }
- }
-
- mFakeComposerClient->hotplugDisplay(EXTERNAL_DISPLAY,
- V2_1::IComposerCallback::Connection::DISCONNECTED);
- waitForDisplayTransaction(EXTERNAL_DISPLAY);
- mFakeComposerClient->clearFrames();
- EXPECT_TRUE(waitForHotplugEvent(EXTERNAL_DISPLAY, false));
- }
-
- void Test_HotplugPrimaryDisplay() {
- ALOGD("DisplayTest::HotplugPrimaryDisplay");
-
- mFakeComposerClient->hotplugDisplay(PRIMARY_DISPLAY,
- V2_1::IComposerCallback::Connection::DISCONNECTED);
-
- waitForDisplayTransaction(PRIMARY_DISPLAY);
-
- EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, false));
- {
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kPrimaryDisplayId);
- EXPECT_TRUE(display == nullptr);
-
- ui::DisplayMode mode;
- auto result = SurfaceComposerClient::getActiveDisplayMode(display, &mode);
- EXPECT_NE(NO_ERROR, result);
- }
-
- mFakeComposerClient->clearFrames();
-
- setExpectationsForConfigs(PRIMARY_DISPLAY,
- {{.id = 1,
- .w = 400,
- .h = 200,
- .vsyncPeriod = 16'666'666,
- .group = 0}},
- 1, 16'666'666);
-
- mFakeComposerClient->hotplugDisplay(PRIMARY_DISPLAY,
- V2_1::IComposerCallback::Connection::CONNECTED);
-
- waitForDisplayTransaction(PRIMARY_DISPLAY);
-
- EXPECT_TRUE(waitForHotplugEvent(PRIMARY_DISPLAY, true));
-
- {
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kPrimaryDisplayId);
- EXPECT_FALSE(display == nullptr);
-
- ui::DisplayMode mode;
- auto result = SurfaceComposerClient::getActiveDisplayMode(display, &mode);
- EXPECT_EQ(NO_ERROR, result);
- ASSERT_EQ(ui::Size(400, 200), mode.resolution);
- EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
- }
- }
-
- void Test_SubsequentHotplugConnectUpdatesDisplay(Display hwcDisplayId) {
- ALOGD("DisplayTest::Test_SubsequentHotplugConnectUpdatesDisplay");
-
- // Send a hotplug connected event to set up the initial display modes.
- // The primary display is already connected so this will update it.
- // If we're running the test of an external display this will create it.
- setExpectationsForConfigs(hwcDisplayId,
- {{.id = 1,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 11'111'111,
- .group = 1}},
- /* activeConfig */ 1, 11'111'111);
-
- mFakeComposerClient->hotplugDisplay(hwcDisplayId,
- V2_1::IComposerCallback::Connection::CONNECTED);
- waitForDisplayTransaction(hwcDisplayId);
- EXPECT_TRUE(waitForHotplugEvent(hwcDisplayId, true));
-
- const auto displayId = physicalIdFromHwcDisplayId(hwcDisplayId);
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(displayId);
- EXPECT_FALSE(display == nullptr);
-
- // Verify that the active mode and the supported moded are updated
- {
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 11'111'111, mode.refreshRate);
-
- ui::DynamicDisplayInfo info;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
- const auto& modes = info.supportedDisplayModes;
- EXPECT_EQ(modes.size(), 1);
- }
-
- // Send another hotplug connected event
- setExpectationsForConfigs(hwcDisplayId,
- {
- {.id = 1,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 16'666'666,
- .group = 1},
- {.id = 2,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 11'111'111,
- .group = 1},
- {.id = 3,
- .w = 800,
- .h = 1600,
- .vsyncPeriod = 8'333'333,
- .group = 1},
- },
- /* activeConfig */ 1, 16'666'666);
-
- mFakeComposerClient->hotplugDisplay(hwcDisplayId,
- V2_1::IComposerCallback::Connection::CONNECTED);
- waitForDisplayTransaction(hwcDisplayId);
- EXPECT_TRUE(waitForHotplugEvent(hwcDisplayId, true));
-
- // Verify that the active mode and the supported moded are updated
- {
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(1e9f / 16'666'666, mode.refreshRate);
- }
-
- ui::DynamicDisplayInfo info;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getDynamicDisplayInfo(display, &info));
- const auto& modes = info.supportedDisplayModes;
- EXPECT_EQ(modes.size(), 3);
-
- EXPECT_EQ(ui::Size(800, 1600), modes[0].resolution);
- EXPECT_EQ(1e9f / 16'666'666, modes[0].refreshRate);
-
- EXPECT_EQ(ui::Size(800, 1600), modes[1].resolution);
- EXPECT_EQ(1e9f / 11'111'111, modes[1].refreshRate);
-
- EXPECT_EQ(ui::Size(800, 1600), modes[2].resolution);
- EXPECT_EQ(1e9f / 8'333'333, modes[2].refreshRate);
-
- // Verify that we are able to switch to any of the modes
- for (int i = modes.size() - 1; i >= 0; i--) {
- const auto hwcId = i + 1;
- // Set up HWC expectations for the mode change
- if (mIs2_4Client) {
- EXPECT_CALL(*mMockComposer,
- setActiveConfigWithConstraints(hwcDisplayId, hwcId, _, _))
- .WillOnce(Return(V2_4::Error::NONE));
- } else {
- EXPECT_CALL(*mMockComposer, setActiveConfig(hwcDisplayId, hwcId))
- .WillOnce(Return(V2_1::Error::NONE));
- }
-
- EXPECT_EQ(NO_ERROR,
- SurfaceComposerClient::setDesiredDisplayModeSpecs(display, i, false,
- modes[i].refreshRate,
- modes[i].refreshRate,
- modes[i].refreshRate,
- modes[i].refreshRate));
- // We need to refresh twice - once to apply the pending mode change request,
- // and once to process the change.
- waitForDisplayTransaction(hwcDisplayId);
- waitForDisplayTransaction(hwcDisplayId);
- EXPECT_TRUE(waitForModeChangedEvent(hwcDisplayId, i))
- << "Failure while switching to mode " << i;
-
- ui::DisplayMode mode;
- EXPECT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
- EXPECT_EQ(ui::Size(800, 1600), mode.resolution);
- EXPECT_EQ(modes[i].refreshRate, mode.refreshRate);
- }
- }
-
- sp<V2_1::IComposer> mFakeService;
- sp<SurfaceComposerClient> mComposerClient;
-
- std::unique_ptr<MockComposerHal> mMockComposer;
- FakeComposerClient* mFakeComposerClient;
-
- std::unique_ptr<DisplayEventReceiver> mReceiver;
- sp<Looper> mLooper;
- std::deque<DisplayEventReceiver::Event> mReceivedDisplayEvents;
-
- static constexpr bool mIs2_4Client =
- std::is_same<FakeComposerService, FakeComposerService_2_4>::value;
-};
-
-using DisplayTest_2_1 = DisplayTest<FakeComposerService_2_1>;
-
-// Tests that VSYNC injection can be safely toggled while invalidating.
-TEST_F(DisplayTest_2_1, VsyncInjection) {
- const auto flinger = ComposerService::getComposerService();
- bool enable = true;
-
- for (int i = 0; i < 100; i++) {
- flinger->enableVSyncInjections(enable);
- enable = !enable;
-
- constexpr uint32_t kForceInvalidate = 1004;
- android::Parcel data, reply;
- data.writeInterfaceToken(String16("android.ui.ISurfaceComposer"));
- EXPECT_EQ(NO_ERROR,
- android::IInterface::asBinder(flinger)->transact(kForceInvalidate, data, &reply));
-
- std::this_thread::sleep_for(5ms);
- }
-}
-
-TEST_F(DisplayTest_2_1, HotplugOneConfig) {
- Test_HotplugOneConfig();
-}
-
-TEST_F(DisplayTest_2_1, HotplugTwoSeparateConfigs) {
- Test_HotplugTwoSeparateConfigs();
-}
-
-TEST_F(DisplayTest_2_1, HotplugTwoConfigsSameGroup) {
- Test_HotplugTwoConfigsSameGroup();
-}
-
-TEST_F(DisplayTest_2_1, HotplugThreeConfigsMixedGroups) {
- Test_HotplugThreeConfigsMixedGroups();
-}
-
-TEST_F(DisplayTest_2_1, HotplugPrimaryOneConfig) {
- Test_HotplugPrimaryDisplay();
-}
-
-TEST_F(DisplayTest_2_1, SubsequentHotplugConnectUpdatesPrimaryDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY);
-}
-
-TEST_F(DisplayTest_2_1, SubsequentHotplugConnectUpdatesExternalDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY);
-}
-
-using DisplayTest_2_2 = DisplayTest<FakeComposerService_2_2>;
-
-TEST_F(DisplayTest_2_2, HotplugOneConfig) {
- Test_HotplugOneConfig();
-}
-
-TEST_F(DisplayTest_2_2, HotplugTwoSeparateConfigs) {
- Test_HotplugTwoSeparateConfigs();
-}
-
-TEST_F(DisplayTest_2_2, HotplugTwoConfigsSameGroup) {
- Test_HotplugTwoConfigsSameGroup();
-}
-
-TEST_F(DisplayTest_2_2, HotplugThreeConfigsMixedGroups) {
- Test_HotplugThreeConfigsMixedGroups();
-}
-
-TEST_F(DisplayTest_2_2, HotplugPrimaryOneConfig) {
- Test_HotplugPrimaryDisplay();
-}
-
-TEST_F(DisplayTest_2_2, SubsequentHotplugConnectUpdatesPrimaryDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY);
-}
-
-TEST_F(DisplayTest_2_2, SubsequentHotplugConnectUpdatesExternalDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY);
-}
-
-using DisplayTest_2_3 = DisplayTest<FakeComposerService_2_3>;
-
-TEST_F(DisplayTest_2_3, HotplugOneConfig) {
- Test_HotplugOneConfig();
-}
-
-TEST_F(DisplayTest_2_3, HotplugTwoSeparateConfigs) {
- Test_HotplugTwoSeparateConfigs();
-}
-
-TEST_F(DisplayTest_2_3, HotplugTwoConfigsSameGroup) {
- Test_HotplugTwoConfigsSameGroup();
-}
-
-TEST_F(DisplayTest_2_3, HotplugThreeConfigsMixedGroups) {
- Test_HotplugThreeConfigsMixedGroups();
-}
-
-TEST_F(DisplayTest_2_3, HotplugPrimaryOneConfig) {
- Test_HotplugPrimaryDisplay();
-}
-
-TEST_F(DisplayTest_2_3, SubsequentHotplugConnectUpdatesPrimaryDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY);
-}
-
-TEST_F(DisplayTest_2_3, SubsequentHotplugConnectUpdatesExternalDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY);
-}
-
-using DisplayTest_2_4 = DisplayTest<FakeComposerService_2_4>;
-
-TEST_F(DisplayTest_2_4, HotplugOneConfig) {
- Test_HotplugOneConfig();
-}
-
-TEST_F(DisplayTest_2_4, HotplugTwoSeparateConfigs) {
- Test_HotplugTwoSeparateConfigs();
-}
-
-TEST_F(DisplayTest_2_4, HotplugTwoConfigsSameGroup) {
- Test_HotplugTwoConfigsSameGroup();
-}
-
-TEST_F(DisplayTest_2_4, HotplugThreeConfigsMixedGroups) {
- Test_HotplugThreeConfigsMixedGroups();
-}
-
-TEST_F(DisplayTest_2_4, HotplugPrimaryOneConfig) {
- Test_HotplugPrimaryDisplay();
-}
-
-TEST_F(DisplayTest_2_4, SubsequentHotplugConnectUpdatesPrimaryDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(PRIMARY_DISPLAY);
-}
-
-TEST_F(DisplayTest_2_4, SubsequentHotplugConnectUpdatesExternalDisplay) {
- Test_SubsequentHotplugConnectUpdatesDisplay(EXTERNAL_DISPLAY);
-}
-
-////////////////////////////////////////////////
-
-template <typename FakeComposerService>
-class TransactionTest : public ::testing::Test {
-protected:
- // Layer array indexing constants.
- constexpr static int BG_LAYER = 0;
- constexpr static int FG_LAYER = 1;
-
- static void SetUpTestCase() {
- // TODO: See TODO comment at DisplayTest::SetUp for background on
- // the lifetime of the FakeComposerClient.
- sFakeComposer = new FakeComposerClient;
- sp<V2_4::hal::ComposerClient> client = new V2_4::hal::ComposerClient(sFakeComposer);
- sp<V2_1::IComposer> fakeService = new FakeComposerService(client);
- (void)fakeService->registerAsService("mock");
-
- android::hardware::ProcessState::self()->startThreadPool();
- android::ProcessState::self()->startThreadPool();
-
- startSurfaceFlinger();
-
- // Fake composer wants to enable VSync injection
- sFakeComposer->onSurfaceFlingerStart();
- }
-
- static void TearDownTestCase() {
- // Fake composer needs to release SurfaceComposerClient before the stop.
- sFakeComposer->onSurfaceFlingerStop();
- stopSurfaceFlinger();
- // TODO: This is deleted when the ComposerClient calls
- // removeClient. Devise better lifetime control.
- sFakeComposer = nullptr;
- }
-
- void SetUp() override {
- ALOGI("TransactionTest::SetUp");
- mComposerClient = new SurfaceComposerClient;
- ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());
-
- ALOGI("TransactionTest::SetUp - display");
- const auto display = SurfaceComposerClient::getPhysicalDisplayToken(kPrimaryDisplayId);
- ASSERT_FALSE(display == nullptr);
-
- ui::DisplayMode mode;
- ASSERT_EQ(NO_ERROR, SurfaceComposerClient::getActiveDisplayMode(display, &mode));
-
- const ui::Size& resolution = mode.resolution;
- mDisplayWidth = resolution.getWidth();
- mDisplayHeight = resolution.getHeight();
-
- // Background surface
- mBGSurfaceControl =
- mComposerClient->createSurface(String8("BG Test Surface"), mDisplayWidth,
- mDisplayHeight, PIXEL_FORMAT_RGBA_8888, 0);
- ASSERT_TRUE(mBGSurfaceControl != nullptr);
- ASSERT_TRUE(mBGSurfaceControl->isValid());
- fillSurfaceRGBA8(mBGSurfaceControl, BLUE);
-
- // Foreground surface
- mFGSurfaceControl = mComposerClient->createSurface(String8("FG Test Surface"), 64, 64,
- PIXEL_FORMAT_RGBA_8888, 0);
- ASSERT_TRUE(mFGSurfaceControl != nullptr);
- ASSERT_TRUE(mFGSurfaceControl->isValid());
-
- fillSurfaceRGBA8(mFGSurfaceControl, RED);
-
- Transaction t;
- t.setDisplayLayerStack(display, ui::DEFAULT_LAYER_STACK);
-
- t.setLayer(mBGSurfaceControl, INT32_MAX - 2);
- t.show(mBGSurfaceControl);
-
- t.setLayer(mFGSurfaceControl, INT32_MAX - 1);
- t.setPosition(mFGSurfaceControl, 64, 64);
- t.show(mFGSurfaceControl);
-
- // Synchronous transaction will stop this thread, so we set up a
- // delayed, off-thread vsync request before closing the
- // transaction. In the test code this is usually done with
- // TransactionScope. Leaving here in the 'vanilla' form for
- // reference.
- ASSERT_EQ(0, sFakeComposer->getFrameCount());
- sFakeComposer->runVSyncAfter(1ms);
- t.apply();
- sFakeComposer->waitUntilFrame(1);
-
- // Reference data. This is what the HWC should see.
- static_assert(BG_LAYER == 0 && FG_LAYER == 1, "Unexpected enum values for array indexing");
- mBaseFrame.push_back(makeSimpleRect(0u, 0u, mDisplayWidth, mDisplayHeight));
- mBaseFrame[BG_LAYER].mSwapCount = 1;
- mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64));
- mBaseFrame[FG_LAYER].mSwapCount = 1;
-
- auto frame = sFakeComposer->getFrameRects(0);
- ASSERT_TRUE(framesAreSame(mBaseFrame, frame));
- }
-
- void TearDown() override {
- ALOGD("TransactionTest::TearDown");
-
- mComposerClient->dispose();
- mBGSurfaceControl = 0;
- mFGSurfaceControl = 0;
- mComposerClient = 0;
-
- sFakeComposer->runVSyncAndWait();
- mBaseFrame.clear();
- sFakeComposer->clearFrames();
- ASSERT_EQ(0, sFakeComposer->getFrameCount());
-
- sp<ISurfaceComposer> sf(ComposerService::getComposerService());
- std::vector<LayerDebugInfo> layers;
- status_t result = sf->getLayerDebugInfo(&layers);
- if (result != NO_ERROR) {
- ALOGE("Failed to get layers %s %d", strerror(-result), result);
- } else {
- // If this fails, the test being torn down leaked layers.
- EXPECT_EQ(0u, layers.size());
- if (layers.size() > 0) {
- for (auto layer = layers.begin(); layer != layers.end(); ++layer) {
- std::cout << to_string(*layer).c_str();
- }
- // To ensure the next test has clean slate, will run the class
- // tear down and setup here.
- TearDownTestCase();
- SetUpTestCase();
- }
- }
- ALOGD("TransactionTest::TearDown - complete");
- }
-
- void Test_LayerMove() {
- ALOGD("TransactionTest::LayerMove");
-
- // The scope opens and closes a global transaction and, at the
- // same time, makes sure the SurfaceFlinger progresses one frame
- // after the transaction closes. The results of the transaction
- // should be available in the latest frame stored by the fake
- // composer.
- {
- TransactionScope ts(*sFakeComposer);
- ts.setPosition(mFGSurfaceControl, 128, 128);
- // NOTE: No changes yet, so vsync will do nothing, HWC does not get any calls.
- // (How to verify that? Throw in vsync and wait a 2x frame time? Separate test?)
- //
- // sFakeComposer->runVSyncAndWait();
- }
-
- fillSurfaceRGBA8(mFGSurfaceControl, GREEN);
- sFakeComposer->runVSyncAndWait();
-
- ASSERT_EQ(3, sFakeComposer->getFrameCount()); // Make sure the waits didn't time out and
- // there's no extra frames.
-
- // NOTE: Frame 0 is produced in the SetUp.
- auto frame1Ref = mBaseFrame;
- frame1Ref[FG_LAYER].mDisplayFrame =
- hwc_rect_t{128, 128, 128 + 64, 128 + 64}; // Top-most layer moves.
- EXPECT_TRUE(framesAreSame(frame1Ref, sFakeComposer->getFrameRects(1)));
-
- auto frame2Ref = frame1Ref;
- frame2Ref[FG_LAYER].mSwapCount++;
- EXPECT_TRUE(framesAreSame(frame2Ref, sFakeComposer->getFrameRects(2)));
- }
-
- void Test_LayerCrop() {
- // TODO: Add scaling to confirm that crop happens in buffer space?
- {
- TransactionScope ts(*sFakeComposer);
- Rect cropRect(16, 16, 32, 32);
- ts.setCrop(mFGSurfaceControl, cropRect);
- }
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
-
- auto referenceFrame = mBaseFrame;
- referenceFrame[FG_LAYER].mSourceCrop = hwc_frect_t{16.f, 16.f, 32.f, 32.f};
- referenceFrame[FG_LAYER].mDisplayFrame = hwc_rect_t{64 + 16, 64 + 16, 64 + 32, 64 + 32};
- EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerSetLayer() {
- {
- TransactionScope ts(*sFakeComposer);
- ts.setLayer(mFGSurfaceControl, INT_MAX - 3);
- }
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
-
- // The layers will switch order, but both are rendered because the background layer is
- // transparent (RGBA8888).
- std::vector<RenderState> referenceFrame(2);
- referenceFrame[0] = mBaseFrame[FG_LAYER];
- referenceFrame[1] = mBaseFrame[BG_LAYER];
- EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerSetLayerOpaque() {
- {
- TransactionScope ts(*sFakeComposer);
- ts.setLayer(mFGSurfaceControl, INT_MAX - 3);
- ts.setFlags(mBGSurfaceControl, layer_state_t::eLayerOpaque,
- layer_state_t::eLayerOpaque);
- }
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
-
- // The former foreground layer is now covered with opaque layer - it should have disappeared
- std::vector<RenderState> referenceFrame(1);
- referenceFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
- EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_SetLayerStack() {
- ALOGD("TransactionTest::SetLayerStack");
- {
- TransactionScope ts(*sFakeComposer);
- ts.setLayerStack(mFGSurfaceControl, ui::LayerStack{1});
- }
-
- // Foreground layer should have disappeared.
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
- std::vector<RenderState> refFrame(1);
- refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
- EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerShowHide() {
- ALOGD("TransactionTest::LayerShowHide");
- {
- TransactionScope ts(*sFakeComposer);
- ts.hide(mFGSurfaceControl);
- }
-
- // Foreground layer should have disappeared.
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
- std::vector<RenderState> refFrame(1);
- refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
- EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
-
- {
- TransactionScope ts(*sFakeComposer);
- ts.show(mFGSurfaceControl);
- }
-
- // Foreground layer should be back
- ASSERT_EQ(3, sFakeComposer->getFrameCount());
- EXPECT_TRUE(framesAreSame(mBaseFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerSetAlpha() {
- {
- TransactionScope ts(*sFakeComposer);
- ts.setAlpha(mFGSurfaceControl, 0.75f);
- }
-
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
- auto referenceFrame = mBaseFrame;
- referenceFrame[FG_LAYER].mPlaneAlpha = 0.75f;
- EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerSetFlags() {
- {
- TransactionScope ts(*sFakeComposer);
- ts.setFlags(mFGSurfaceControl, layer_state_t::eLayerHidden,
- layer_state_t::eLayerHidden);
- }
-
- // Foreground layer should have disappeared.
- ASSERT_EQ(2, sFakeComposer->getFrameCount());
- std::vector<RenderState> refFrame(1);
- refFrame[BG_LAYER] = mBaseFrame[BG_LAYER];
- EXPECT_TRUE(framesAreSame(refFrame, sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerSetMatrix() {
- struct matrixTestData {
- float matrix[4];
- hwc_transform_t expectedTransform;
- hwc_rect_t expectedDisplayFrame;
- };
-
- // The matrix operates on the display frame and is applied before
- // the position is added. So, the foreground layer rect is (0, 0,
- // 64, 64) is first transformed, potentially yielding negative
- // coordinates and then the position (64, 64) is added yielding
- // the final on-screen rectangles given.
-
- const matrixTestData MATRIX_TESTS[7] = // clang-format off
- {{{-1.f, 0.f, 0.f, 1.f}, HWC_TRANSFORM_FLIP_H, {0, 64, 64, 128}},
- {{1.f, 0.f, 0.f, -1.f}, HWC_TRANSFORM_FLIP_V, {64, 0, 128, 64}},
- {{0.f, 1.f, -1.f, 0.f}, HWC_TRANSFORM_ROT_90, {0, 64, 64, 128}},
- {{-1.f, 0.f, 0.f, -1.f}, HWC_TRANSFORM_ROT_180, {0, 0, 64, 64}},
- {{0.f, -1.f, 1.f, 0.f}, HWC_TRANSFORM_ROT_270, {64, 0, 128, 64}},
- {{0.f, 1.f, 1.f, 0.f}, HWC_TRANSFORM_FLIP_H_ROT_90, {64, 64, 128, 128}},
- {{0.f, 1.f, 1.f, 0.f}, HWC_TRANSFORM_FLIP_V_ROT_90, {64, 64, 128, 128}}};
- // clang-format on
- constexpr int TEST_COUNT = sizeof(MATRIX_TESTS) / sizeof(matrixTestData);
-
- for (int i = 0; i < TEST_COUNT; i++) {
- // TODO: How to leverage the HWC2 stringifiers?
- const matrixTestData& xform = MATRIX_TESTS[i];
- SCOPED_TRACE(i);
- {
- TransactionScope ts(*sFakeComposer);
- ts.setMatrix(mFGSurfaceControl, xform.matrix[0], xform.matrix[1], xform.matrix[2],
- xform.matrix[3]);
- }
-
- auto referenceFrame = mBaseFrame;
- referenceFrame[FG_LAYER].mTransform = xform.expectedTransform;
- referenceFrame[FG_LAYER].mDisplayFrame = xform.expectedDisplayFrame;
-
- EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
- }
- }
-
- void Test_SetRelativeLayer() {
- constexpr int RELATIVE_LAYER = 2;
- auto relativeSurfaceControl = mComposerClient->createSurface(String8("Test Surface"), 64,
- 64, PIXEL_FORMAT_RGBA_8888, 0);
- fillSurfaceRGBA8(relativeSurfaceControl, LIGHT_RED);
-
- // Now we stack the surface above the foreground surface and make sure it is visible.
- {
- TransactionScope ts(*sFakeComposer);
- ts.setPosition(relativeSurfaceControl, 64, 64);
- ts.show(relativeSurfaceControl);
- ts.setRelativeLayer(relativeSurfaceControl, mFGSurfaceControl, 1);
- }
- auto referenceFrame = mBaseFrame;
- // NOTE: All three layers will be visible as the surfaces are
- // transparent because of the RGBA format.
- referenceFrame.push_back(makeSimpleRect(64, 64, 64 + 64, 64 + 64));
- referenceFrame[RELATIVE_LAYER].mSwapCount = 1;
- EXPECT_TRUE(framesAreSame(referenceFrame, sFakeComposer->getLatestFrame()));
-
- // A call to setLayer will override a call to setRelativeLayer
- {
- TransactionScope ts(*sFakeComposer);
- ts.setLayer(relativeSurfaceControl, 0);
- }
-
- // Previous top layer will now appear at the bottom.
- auto referenceFrame2 = mBaseFrame;
- referenceFrame2.insert(referenceFrame2.begin(), referenceFrame[RELATIVE_LAYER]);
- EXPECT_EQ(3, sFakeComposer->getFrameCount());
- EXPECT_TRUE(framesAreSame(referenceFrame2, sFakeComposer->getLatestFrame()));
- }
-
- sp<SurfaceComposerClient> mComposerClient;
- sp<SurfaceControl> mBGSurfaceControl;
- sp<SurfaceControl> mFGSurfaceControl;
- std::vector<RenderState> mBaseFrame;
- uint32_t mDisplayWidth;
- uint32_t mDisplayHeight;
-
- static inline FakeComposerClient* sFakeComposer;
-};
-
-using TransactionTest_2_1 = TransactionTest<FakeComposerService_2_1>;
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerMove) {
- Test_LayerMove();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerCrop) {
- Test_LayerCrop();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerSetLayer) {
- Test_LayerSetLayer();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerSetLayerOpaque) {
- Test_LayerSetLayerOpaque();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_SetLayerStack) {
- Test_SetLayerStack();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerShowHide) {
- Test_LayerShowHide();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerSetAlpha) {
- Test_LayerSetAlpha();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerSetFlags) {
- Test_LayerSetFlags();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_LayerSetMatrix) {
- Test_LayerSetMatrix();
-}
-
-TEST_F(TransactionTest_2_1, DISABLED_SetRelativeLayer) {
- Test_SetRelativeLayer();
-}
-
-template <typename FakeComposerService>
-class ChildLayerTest : public TransactionTest<FakeComposerService> {
- using Base = TransactionTest<FakeComposerService>;
-
-protected:
- constexpr static int CHILD_LAYER = 2;
-
- void SetUp() override {
- Base::SetUp();
- mChild = Base::mComposerClient->createSurface(String8("Child surface"), 10, 10,
- PIXEL_FORMAT_RGBA_8888, 0,
- Base::mFGSurfaceControl->getHandle());
- fillSurfaceRGBA8(mChild, LIGHT_GRAY);
-
- Base::sFakeComposer->runVSyncAndWait();
- Base::mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10));
- Base::mBaseFrame[CHILD_LAYER].mSwapCount = 1;
- ASSERT_EQ(2, Base::sFakeComposer->getFrameCount());
- ASSERT_TRUE(framesAreSame(Base::mBaseFrame, Base::sFakeComposer->getLatestFrame()));
- }
-
- void TearDown() override {
- mChild = 0;
- Base::TearDown();
- }
-
- void Test_Positioning() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.show(mChild);
- ts.setPosition(mChild, 10, 10);
- // Move to the same position as in the original setup.
- ts.setPosition(Base::mFGSurfaceControl, 64, 64);
- }
-
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{64, 64, 64 + 64, 64 + 64};
- referenceFrame[CHILD_LAYER].mDisplayFrame =
- hwc_rect_t{64 + 10, 64 + 10, 64 + 10 + 10, 64 + 10 + 10};
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
-
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- }
-
- auto referenceFrame2 = Base::mBaseFrame;
- referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 64, 0 + 64};
- referenceFrame2[CHILD_LAYER].mDisplayFrame =
- hwc_rect_t{0 + 10, 0 + 10, 0 + 10 + 10, 0 + 10 + 10};
- EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
- }
-
- void Test_Cropping() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.show(mChild);
- ts.setPosition(mChild, 0, 0);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- ts.setCrop(Base::mFGSurfaceControl, Rect(0, 0, 5, 5));
- }
- // NOTE: The foreground surface would be occluded by the child
- // now, but is included in the stack because the child is
- // transparent.
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5};
- referenceFrame[Base::FG_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f};
- referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 0 + 5, 0 + 5};
- referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 5.f, 5.f};
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
- }
-
- void Test_Constraints() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.show(mChild);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- ts.setPosition(mChild, 63, 63);
- }
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
- referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{63, 63, 64, 64};
- referenceFrame[CHILD_LAYER].mSourceCrop = hwc_frect_t{0.f, 0.f, 1.f, 1.f};
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
- }
-
- void Test_Scaling() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- }
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
- referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
-
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setMatrix(Base::mFGSurfaceControl, 2.0, 0, 0, 2.0);
- }
-
- auto referenceFrame2 = Base::mBaseFrame;
- referenceFrame2[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 128, 128};
- referenceFrame2[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 20, 20};
- EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerAlpha() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.show(mChild);
- ts.setPosition(mChild, 0, 0);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- ts.setAlpha(mChild, 0.5);
- }
-
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
- referenceFrame[CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
- referenceFrame[CHILD_LAYER].mPlaneAlpha = 0.5f;
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
-
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setAlpha(Base::mFGSurfaceControl, 0.5);
- }
-
- auto referenceFrame2 = referenceFrame;
- referenceFrame2[Base::FG_LAYER].mPlaneAlpha = 0.5f;
- referenceFrame2[CHILD_LAYER].mPlaneAlpha = 0.25f;
- EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
- }
-
- sp<SurfaceControl> mChild;
-};
-
-using ChildLayerTest_2_1 = ChildLayerTest<FakeComposerService_2_1>;
-
-TEST_F(ChildLayerTest_2_1, DISABLED_Positioning) {
- Test_Positioning();
-}
-
-TEST_F(ChildLayerTest_2_1, DISABLED_Cropping) {
- Test_Cropping();
-}
-
-TEST_F(ChildLayerTest_2_1, DISABLED_Constraints) {
- Test_Constraints();
-}
-
-TEST_F(ChildLayerTest_2_1, DISABLED_Scaling) {
- Test_Scaling();
-}
-
-TEST_F(ChildLayerTest_2_1, DISABLED_LayerAlpha) {
- Test_LayerAlpha();
-}
-
-template <typename FakeComposerService>
-class ChildColorLayerTest : public ChildLayerTest<FakeComposerService> {
- using Base = ChildLayerTest<FakeComposerService>;
-
-protected:
- void SetUp() override {
- Base::SetUp();
- Base::mChild =
- Base::mComposerClient->createSurface(String8("Child surface"), 0, 0,
- PIXEL_FORMAT_RGBA_8888,
- ISurfaceComposerClient::eFXSurfaceEffect,
- Base::mFGSurfaceControl->getHandle());
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setColor(Base::mChild,
- {LIGHT_GRAY.r / 255.0f, LIGHT_GRAY.g / 255.0f, LIGHT_GRAY.b / 255.0f});
- ts.setCrop(Base::mChild, Rect(0, 0, 10, 10));
- }
-
- Base::sFakeComposer->runVSyncAndWait();
- Base::mBaseFrame.push_back(makeSimpleRect(64, 64, 64 + 10, 64 + 10));
- Base::mBaseFrame[Base::CHILD_LAYER].mSourceCrop = hwc_frect_t{0.0f, 0.0f, 0.0f, 0.0f};
- Base::mBaseFrame[Base::CHILD_LAYER].mSwapCount = 0;
- ASSERT_EQ(2, Base::sFakeComposer->getFrameCount());
- ASSERT_TRUE(framesAreSame(Base::mBaseFrame, Base::sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerAlpha() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.show(Base::mChild);
- ts.setPosition(Base::mChild, 0, 0);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- ts.setAlpha(Base::mChild, 0.5);
- }
-
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
- referenceFrame[Base::CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
- referenceFrame[Base::CHILD_LAYER].mPlaneAlpha = 0.5f;
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
-
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setAlpha(Base::mFGSurfaceControl, 0.5);
- }
-
- auto referenceFrame2 = referenceFrame;
- referenceFrame2[Base::FG_LAYER].mPlaneAlpha = 0.5f;
- referenceFrame2[Base::CHILD_LAYER].mPlaneAlpha = 0.25f;
- EXPECT_TRUE(framesAreSame(referenceFrame2, Base::sFakeComposer->getLatestFrame()));
- }
-
- void Test_LayerZeroAlpha() {
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.show(Base::mChild);
- ts.setPosition(Base::mChild, 0, 0);
- ts.setPosition(Base::mFGSurfaceControl, 0, 0);
- ts.setAlpha(Base::mChild, 0.5);
- }
-
- auto referenceFrame = Base::mBaseFrame;
- referenceFrame[Base::FG_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 64, 64};
- referenceFrame[Base::CHILD_LAYER].mDisplayFrame = hwc_rect_t{0, 0, 10, 10};
- referenceFrame[Base::CHILD_LAYER].mPlaneAlpha = 0.5f;
- EXPECT_TRUE(framesAreSame(referenceFrame, Base::sFakeComposer->getLatestFrame()));
-
- {
- TransactionScope ts(*Base::sFakeComposer);
- ts.setAlpha(Base::mFGSurfaceControl, 0.0f);
- }
-
- std::vector<RenderState> refFrame(1);
- refFrame[Base::BG_LAYER] = Base::mBaseFrame[Base::BG_LAYER];
-
- EXPECT_TRUE(framesAreSame(refFrame, Base::sFakeComposer->getLatestFrame()));
- }
-};
-
-using ChildColorLayerTest_2_1 = ChildColorLayerTest<FakeComposerService_2_1>;
-
-TEST_F(ChildColorLayerTest_2_1, DISABLED_LayerAlpha) {
- Test_LayerAlpha();
-}
-
-TEST_F(ChildColorLayerTest_2_1, DISABLED_LayerZeroAlpha) {
- Test_LayerZeroAlpha();
-}
-} // namespace
-
-int main(int argc, char** argv) {
- ::testing::InitGoogleTest(&argc, argv);
-
- auto* fakeEnvironment = new sftest::FakeHwcEnvironment;
- ::testing::AddGlobalTestEnvironment(fakeEnvironment);
- ::testing::InitGoogleMock(&argc, argv);
- return RUN_ALL_TESTS();
-}
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion -Wextra"
diff --git a/services/surfaceflinger/tests/tracing/Android.bp b/services/surfaceflinger/tests/tracing/Android.bp
index aa6c74eb65..aeceadb5d4 100644
--- a/services/surfaceflinger/tests/tracing/Android.bp
+++ b/services/surfaceflinger/tests/tracing/Android.bp
@@ -29,13 +29,9 @@ cc_test {
"skia_renderengine_deps",
],
test_suites: ["device-tests"],
- sanitize: {
- address: false,
- },
srcs: [
":libsurfaceflinger_sources",
":libsurfaceflinger_mock_sources",
- ":layertracegenerator_sources",
"TransactionTraceTestSuite.cpp",
],
static_libs: [
@@ -43,7 +39,6 @@ cc_test {
],
header_libs: [
"libsurfaceflinger_mocks_headers",
- "layertracegenerator_headers",
],
data: ["testdata/*"],
}
diff --git a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
index ac4354cde1..3c09422e42 100644
--- a/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
+++ b/services/surfaceflinger/tests/tracing/TransactionTraceTestSuite.cpp
@@ -20,9 +20,12 @@
#include <fstream>
#include <iostream>
#include <string>
+#include <unordered_map>
-#include <LayerTraceGenerator.h>
+#include <LayerProtoHelper.h>
+#include <Tracing/LayerTracing.h>
#include <Tracing/TransactionProtoParser.h>
+#include <Tracing/tools/LayerTraceGenerator.h>
#include <layerproto/LayerProtoHeader.h>
#include <log/log.h>
@@ -38,9 +41,9 @@ public:
static constexpr std::string_view sLayersTracePrefix = "layers_trace_";
static constexpr std::string_view sTracePostfix = ".winscope";
- proto::TransactionTraceFile mTransactionTrace;
- LayersTraceFileProto mExpectedLayersTraceProto;
- LayersTraceFileProto mActualLayersTraceProto;
+ perfetto::protos::TransactionTraceFile mTransactionTrace;
+ perfetto::protos::LayersTraceFileProto mExpectedLayersTraceProto;
+ perfetto::protos::LayersTraceFileProto mActualLayersTraceProto;
protected:
void SetUp() override {
@@ -54,18 +57,24 @@ protected:
EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(expectedLayersTracePath)));
parseLayersTraceFromFile(expectedLayersTracePath.c_str(), mExpectedLayersTraceProto);
TemporaryDir temp_dir;
+
std::string actualLayersTracePath =
std::string(temp_dir.path) + "/" + expectedLayersFilename + "_actual";
+ {
+ auto traceFlags = LayerTracing::TRACE_INPUT | LayerTracing::TRACE_BUFFERS;
+ std::ofstream outStream{actualLayersTracePath, std::ios::binary | std::ios::app};
+ auto layerTracing = LayerTracing{outStream};
+ EXPECT_TRUE(LayerTraceGenerator().generate(mTransactionTrace, traceFlags, layerTracing,
+ /*onlyLastEntry=*/true))
+ << "Failed to generate layers trace from " << transactionTracePath;
+ }
- EXPECT_TRUE(
- LayerTraceGenerator().generate(mTransactionTrace, actualLayersTracePath.c_str()))
- << "Failed to generate layers trace from " << transactionTracePath;
EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(actualLayersTracePath)));
parseLayersTraceFromFile(actualLayersTracePath.c_str(), mActualLayersTraceProto);
}
void parseTransactionTraceFromFile(const char* transactionTracePath,
- proto::TransactionTraceFile& outProto) {
+ perfetto::protos::TransactionTraceFile& outProto) {
ALOGD("Parsing file %s...", transactionTracePath);
std::fstream input(transactionTracePath, std::ios::in | std::ios::binary);
EXPECT_TRUE(input) << "Error could not open " << transactionTracePath;
@@ -73,7 +82,8 @@ protected:
<< "Failed to parse " << transactionTracePath;
}
- void parseLayersTraceFromFile(const char* layersTracePath, LayersTraceFileProto& outProto) {
+ void parseLayersTraceFromFile(const char* layersTracePath,
+ perfetto::protos::LayersTraceFileProto& outProto) {
ALOGD("Parsing file %s...", layersTracePath);
std::fstream input(layersTracePath, std::ios::in | std::ios::binary);
EXPECT_TRUE(input) << "Error could not open " << layersTracePath;
@@ -83,6 +93,102 @@ protected:
std::vector<std::filesystem::path> TransactionTraceTestSuite::sTransactionTraces{};
+struct LayerInfo {
+ uint64_t id;
+ std::string name;
+ uint64_t parent;
+ int z;
+ uint64_t curr_frame;
+ float x;
+ float y;
+ uint32_t bufferWidth;
+ uint32_t bufferHeight;
+ Rect touchableRegionBounds;
+};
+
+bool operator==(const LayerInfo& lh, const LayerInfo& rh) {
+ return std::make_tuple(lh.id, lh.name, lh.parent, lh.z, lh.curr_frame, lh.bufferWidth,
+ lh.bufferHeight, lh.touchableRegionBounds) ==
+ std::make_tuple(rh.id, rh.name, rh.parent, rh.z, rh.curr_frame, rh.bufferWidth,
+ rh.bufferHeight, rh.touchableRegionBounds);
+}
+
+bool compareById(const LayerInfo& a, const LayerInfo& b) {
+ return a.id < b.id;
+}
+
+inline void PrintTo(const LayerInfo& info, ::std::ostream* os) {
+ *os << "Layer [" << info.id << "] name=" << info.name << " parent=" << info.parent
+ << " z=" << info.z << " curr_frame=" << info.curr_frame << " x=" << info.x
+ << " y=" << info.y << " bufferWidth=" << info.bufferWidth
+ << " bufferHeight=" << info.bufferHeight << "touchableRegionBounds={"
+ << info.touchableRegionBounds.left << "," << info.touchableRegionBounds.top << ","
+ << info.touchableRegionBounds.right << "," << info.touchableRegionBounds.bottom << "}";
+}
+
+struct find_id {
+ uint64_t id;
+ find_id(uint64_t id) : id(id) {}
+ bool operator()(LayerInfo const& m) const { return m.id == id; }
+};
+
+static LayerInfo getLayerInfoFromProto(perfetto::protos::LayerProto& proto) {
+ Rect touchableRegionBounds = Rect::INVALID_RECT;
+ // ignore touchable region for layers without buffers, the new fe aggressively avoids
+ // calculating state for layers that are not visible which could lead to mismatches
+ if (proto.has_input_window_info() && proto.input_window_info().has_touchable_region() &&
+ proto.has_active_buffer()) {
+ Region touchableRegion;
+ LayerProtoHelper::readFromProto(proto.input_window_info().touchable_region(),
+ touchableRegion);
+ touchableRegionBounds = touchableRegion.bounds();
+ }
+
+ return {static_cast<uint64_t>(proto.id()),
+ proto.name(),
+ static_cast<uint64_t>(proto.parent()),
+ proto.z(),
+ proto.curr_frame(),
+ proto.has_position() ? proto.position().x() : -1,
+ proto.has_position() ? proto.position().y() : -1,
+ proto.has_active_buffer() ? proto.active_buffer().width() : 0,
+ proto.has_active_buffer() ? proto.active_buffer().height() : 0,
+ touchableRegionBounds};
+}
+
+static std::vector<LayerInfo> getLayerInfosFromProto(perfetto::protos::LayersSnapshotProto& entry) {
+ std::unordered_map<uint64_t /* snapshotId*/, uint64_t /*layerId*/> snapshotIdToLayerId;
+ std::vector<LayerInfo> layers;
+ layers.reserve(static_cast<size_t>(entry.layers().layers_size()));
+ bool mapSnapshotIdToLayerId = false;
+ for (int i = 0; i < entry.layers().layers_size(); i++) {
+ auto layer = entry.layers().layers(i);
+ LayerInfo layerInfo = getLayerInfoFromProto(layer);
+
+ uint64_t layerId = layerInfo.name.find("(Mirror)") == std::string::npos
+ ? static_cast<uint64_t>(layer.original_id())
+ : static_cast<uint64_t>(layer.original_id()) | 1ull << 63;
+
+ snapshotIdToLayerId[layerInfo.id] = layerId;
+
+ if (layer.original_id() != 0) {
+ mapSnapshotIdToLayerId = true;
+ }
+ layers.push_back(layerInfo);
+ }
+ std::sort(layers.begin(), layers.end(), compareById);
+
+ if (!mapSnapshotIdToLayerId) {
+ return layers;
+ }
+ for (auto& layer : layers) {
+ layer.id = snapshotIdToLayerId[layer.id];
+ auto it = snapshotIdToLayerId.find(layer.parent);
+ layer.parent = it == snapshotIdToLayerId.end() ? static_cast<uint64_t>(-1) : it->second;
+ }
+ return layers;
+}
+
TEST_P(TransactionTraceTestSuite, validateEndState) {
ASSERT_GT(mActualLayersTraceProto.entry_size(), 0);
ASSERT_GT(mExpectedLayersTraceProto.entry_size(), 0);
@@ -92,19 +198,45 @@ TEST_P(TransactionTraceTestSuite, validateEndState) {
auto actualLastEntry = mActualLayersTraceProto.entry(mActualLayersTraceProto.entry_size() - 1);
EXPECT_EQ(expectedLastEntry.layers().layers_size(), actualLastEntry.layers().layers_size());
- for (int i = 0;
- i < expectedLastEntry.layers().layers_size() && i < actualLastEntry.layers().layers_size();
- i++) {
- auto expectedLayer = expectedLastEntry.layers().layers(i);
- auto actualLayer = actualLastEntry.layers().layers(i);
- EXPECT_EQ(expectedLayer.id(), actualLayer.id());
- EXPECT_EQ(expectedLayer.name(), actualLayer.name());
- EXPECT_EQ(expectedLayer.parent(), actualLayer.parent());
- EXPECT_EQ(expectedLayer.z(), actualLayer.z());
- EXPECT_EQ(expectedLayer.curr_frame(), actualLayer.curr_frame());
- ALOGV("Validating %s[%d] parent=%d z=%d frame=%" PRIu64, expectedLayer.name().c_str(),
- expectedLayer.id(), expectedLayer.parent(), expectedLayer.z(),
- expectedLayer.curr_frame());
+
+ std::vector<LayerInfo> expectedLayers = getLayerInfosFromProto(expectedLastEntry);
+ std::vector<LayerInfo> actualLayers = getLayerInfosFromProto(actualLastEntry);
+
+ size_t i = 0;
+ for (; i < actualLayers.size() && i < expectedLayers.size(); i++) {
+ auto it = std::find_if(actualLayers.begin(), actualLayers.end(),
+ find_id(expectedLayers[i].id));
+ EXPECT_NE(it, actualLayers.end());
+ EXPECT_EQ(expectedLayers[i], *it);
+ ALOGV("Validating %s[%" PRIu64 "] parent=%" PRIu64 " z=%d frame=%" PRIu64,
+ expectedLayers[i].name.c_str(), expectedLayers[i].id, expectedLayers[i].parent,
+ expectedLayers[i].z, expectedLayers[i].curr_frame);
+ }
+
+ EXPECT_EQ(expectedLayers.size(), actualLayers.size());
+
+ if (i < actualLayers.size()) {
+ for (size_t j = 0; j < actualLayers.size(); j++) {
+ if (std::find_if(expectedLayers.begin(), expectedLayers.end(),
+ find_id(actualLayers[j].id)) == expectedLayers.end()) {
+ ALOGD("actualLayers [%" PRIu64 "]:%s parent=%" PRIu64 " z=%d frame=%" PRIu64,
+ actualLayers[j].id, actualLayers[j].name.c_str(), actualLayers[j].parent,
+ actualLayers[j].z, actualLayers[j].curr_frame);
+ }
+ }
+ FAIL();
+ }
+
+ if (i < expectedLayers.size()) {
+ for (size_t j = 0; j < expectedLayers.size(); j++) {
+ if (std::find_if(actualLayers.begin(), actualLayers.end(),
+ find_id(expectedLayers[j].id)) == actualLayers.end()) {
+ ALOGD("expectedLayers [%" PRIu64 "]:%s parent=%" PRIu64 " z=%d frame=%" PRIu64,
+ expectedLayers[j].id, expectedLayers[j].name.c_str(),
+ expectedLayers[j].parent, expectedLayers[j].z, expectedLayers[j].curr_frame);
+ }
+ }
+ FAIL();
}
}
@@ -142,4 +274,4 @@ int main(int argc, char** argv) {
}
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
-} \ No newline at end of file
+}
diff --git a/services/surfaceflinger/tests/tracing/readme.md b/services/surfaceflinger/tests/tracing/readme.md
index 3e80a741c4..f545a3c12d 100644
--- a/services/surfaceflinger/tests/tracing/readme.md
+++ b/services/surfaceflinger/tests/tracing/readme.md
@@ -14,7 +14,9 @@ corresponding layer trace in testdata.
#### Workflow ####
Add transaction traces that resulted in front end bugs along
with the layer trace after fixing the issue. The layer trace
-can be generated by using the layertracegenerator tool. The
+can be generated by using the layertracegenerator tool. Use the
+--last-entry-only flag to generate only the last entry in the
+trace. This will keep the test data to a manageable size. The
main goal of this test suite is to add regression tests with
minimal effort.
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope
new file mode 100644
index 0000000000..fe504d741b
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b275630566.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope
new file mode 100644
index 0000000000..3246453971
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_b282110579.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
index 9e4005c2cf..296d2fdec6 100644
--- a/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_boot.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope b/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope
new file mode 100644
index 0000000000..ae5441550d
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/layers_trace_nodisplayfound.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope
new file mode 100644
index 0000000000..6f7ba15421
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b275630566.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope
new file mode 100644
index 0000000000..ecb94319d2
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_b282110579.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
index 8356ae7992..8d03df4735 100644
--- a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_boot.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope
new file mode 100644
index 0000000000..022861c7ea
--- /dev/null
+++ b/services/surfaceflinger/tests/tracing/testdata/transactions_trace_nodisplayfound.winscope
Binary files differ
diff --git a/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
new file mode 100644
index 0000000000..f1bb231f26
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/ActiveDisplayRotationFlagsTest.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+namespace android {
+namespace {
+
+struct ActiveDisplayRotationFlagsTest : DisplayTransactionTest {
+ static constexpr bool kWithMockScheduler = false;
+ ActiveDisplayRotationFlagsTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+ void SetUp() override {
+ injectMockScheduler(kInnerDisplayId);
+
+ // Inject inner and outer displays with uninitialized power modes.
+ constexpr bool kInitPowerMode = false;
+ {
+ InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+ auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+ injector.setPowerMode(std::nullopt);
+ injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+ mInnerDisplay = injector.inject();
+ }
+ {
+ OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+ auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+ injector.setPowerMode(std::nullopt);
+ mOuterDisplay = injector.inject();
+ }
+
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+ // The flags are a static variable, so by modifying them in the test, we
+ // are modifying the real ones used by SurfaceFlinger. Save the original
+ // flags so we can restore them on teardown. This isn't perfect - the
+ // phone may have been rotated during the test, so we're restoring the
+ // wrong flags. But if the phone is rotated, this may also fail the test.
+ mOldRotationFlags = mFlinger.mutableActiveDisplayRotationFlags();
+
+ // Reset to the expected default state.
+ mFlinger.mutableActiveDisplayRotationFlags() = ui::Transform::ROT_0;
+ }
+
+ void TearDown() override { mFlinger.mutableActiveDisplayRotationFlags() = mOldRotationFlags; }
+
+ static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+ static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+ sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+ ui::Transform::RotationFlags mOldRotationFlags;
+};
+
+TEST_F(ActiveDisplayRotationFlagsTest, defaultRotation) {
+ ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90) {
+ auto displayToken = mInnerDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_90;
+
+ mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ ASSERT_EQ(ui::Transform::ROT_90, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotate90inactive) {
+ auto displayToken = mOuterDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_90;
+
+ mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ ASSERT_EQ(ui::Transform::ROT_0, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBothInnerActive) {
+ auto displayToken = mInnerDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_180;
+
+ displayToken = mOuterDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_270;
+
+ mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, rotateBothOuterActive) {
+ mFlinger.mutableActiveDisplayId() = kOuterDisplayId;
+ auto displayToken = mInnerDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_180;
+
+ displayToken = mOuterDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_270;
+
+ mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+TEST_F(ActiveDisplayRotationFlagsTest, onActiveDisplayChanged) {
+ auto displayToken = mInnerDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_180;
+
+ displayToken = mOuterDisplay->getDisplayToken().promote();
+ mFlinger.mutableDrawingState().displays.editValueFor(displayToken).orientation = ui::ROTATION_0;
+ mFlinger.mutableCurrentState().displays.editValueFor(displayToken).orientation =
+ ui::ROTATION_270;
+
+ mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ ASSERT_EQ(ui::Transform::ROT_180, SurfaceFlinger::getActiveDisplayRotationFlags());
+
+ mFlinger.onActiveDisplayChanged(mInnerDisplay.get(), *mOuterDisplay);
+ ASSERT_EQ(ui::Transform::ROT_270, SurfaceFlinger::getActiveDisplayRotationFlags());
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp b/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
deleted file mode 100644
index 53de4a6e56..0000000000
--- a/services/surfaceflinger/tests/unittests/AidlPowerHalWrapperTest.cpp
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "AidlPowerHalWrapperTest"
-
-#include <android-base/stringprintf.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/IPowerHintSession.h>
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <algorithm>
-#include <chrono>
-#include <memory>
-#include "DisplayHardware/PowerAdvisor.h"
-#include "android/hardware/power/WorkDuration.h"
-#include "binder/Status.h"
-#include "log/log_main.h"
-#include "mock/DisplayHardware/MockIPower.h"
-#include "mock/DisplayHardware/MockIPowerHintSession.h"
-#include "utils/Timers.h"
-
-using namespace android;
-using namespace android::Hwc2::mock;
-using namespace android::hardware::power;
-using namespace std::chrono_literals;
-using namespace testing;
-
-namespace android::Hwc2::impl {
-
-class AidlPowerHalWrapperTest : public testing::Test {
-public:
- void SetUp() override;
-
-protected:
- std::unique_ptr<AidlPowerHalWrapper> mWrapper = nullptr;
- sp<NiceMock<MockIPower>> mMockHal = nullptr;
- sp<NiceMock<MockIPowerHintSession>> mMockSession = nullptr;
- void verifyAndClearExpectations();
- void sendActualWorkDurationGroup(std::vector<WorkDuration> durations,
- std::chrono::nanoseconds sleepBeforeLastSend);
- std::chrono::nanoseconds mAllowedDeviation;
- std::chrono::nanoseconds mStaleTimeout;
-};
-
-void AidlPowerHalWrapperTest::SetUp() {
- mMockHal = new NiceMock<MockIPower>();
- mMockSession = new NiceMock<MockIPowerHintSession>();
- ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_)).WillByDefault(Return(Status::ok()));
- mWrapper = std::make_unique<AidlPowerHalWrapper>(mMockHal);
- mWrapper->setAllowedActualDeviation(std::chrono::nanoseconds{10ms}.count());
- mAllowedDeviation = std::chrono::nanoseconds{mWrapper->mAllowedActualDeviation};
- mStaleTimeout = AidlPowerHalWrapper::kStaleTimeout;
-}
-
-void AidlPowerHalWrapperTest::verifyAndClearExpectations() {
- Mock::VerifyAndClearExpectations(mMockHal.get());
- Mock::VerifyAndClearExpectations(mMockSession.get());
-}
-
-void AidlPowerHalWrapperTest::sendActualWorkDurationGroup(
- std::vector<WorkDuration> durations, std::chrono::nanoseconds sleepBeforeLastSend) {
- for (size_t i = 0; i < durations.size(); i++) {
- if (i == durations.size() - 1) {
- std::this_thread::sleep_for(sleepBeforeLastSend);
- }
- auto duration = durations[i];
- mWrapper->sendActualWorkDuration(duration.durationNanos, duration.timeStampNanos);
- }
-}
-
-WorkDuration toWorkDuration(std::chrono::nanoseconds durationNanos, int64_t timeStampNanos) {
- WorkDuration duration;
- duration.durationNanos = durationNanos.count();
- duration.timeStampNanos = timeStampNanos;
- return duration;
-}
-
-WorkDuration toWorkDuration(std::pair<std::chrono::nanoseconds, nsecs_t> timePair) {
- return toWorkDuration(timePair.first, timePair.second);
-}
-
-std::string printWorkDurations(const ::std::vector<WorkDuration>& durations) {
- std::ostringstream os;
- for (auto duration : durations) {
- os << duration.toString();
- os << "\n";
- }
- return os.str();
-}
-
-namespace {
-TEST_F(AidlPowerHalWrapperTest, supportsPowerHintSession) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
- Mock::VerifyAndClearExpectations(mMockHal.get());
- ON_CALL(*mMockHal.get(), getHintSessionPreferredRate(_))
- .WillByDefault(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
- auto newWrapper = AidlPowerHalWrapper(mMockHal);
- EXPECT_FALSE(newWrapper.supportsPowerHintSession());
-}
-
-TEST_F(AidlPowerHalWrapperTest, startPowerHintSession) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
- std::vector<int32_t> threadIds = {1, 2};
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- EXPECT_TRUE(mWrapper->startPowerHintSession());
- EXPECT_FALSE(mWrapper->startPowerHintSession());
-}
-
-TEST_F(AidlPowerHalWrapperTest, restartNewPowerHintSessionWithNewThreadIds) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
- std::vector<int32_t> threadIds = {1, 2};
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds);
- ASSERT_TRUE(mWrapper->startPowerHintSession());
- verifyAndClearExpectations();
-
- threadIds = {2, 3};
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- EXPECT_CALL(*mMockSession.get(), close()).Times(1);
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_EQ(mWrapper->getPowerHintSessionThreadIds(), threadIds);
- verifyAndClearExpectations();
-
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _)).Times(0);
- EXPECT_CALL(*mMockSession.get(), close()).Times(0);
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- verifyAndClearExpectations();
-}
-
-TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
- std::vector<int32_t> threadIds = {1, 2};
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- ASSERT_TRUE(mWrapper->startPowerHintSession());
- verifyAndClearExpectations();
-
- std::chrono::nanoseconds base = 100ms;
- // test cases with target work duration and whether it should update hint against baseline 100ms
- const std::vector<std::pair<std::chrono::nanoseconds, bool>> testCases =
- {{0ms, true}, {-1ms, true}, {200ms, true}, {2ms, true}, {100ms, false}, {109ms, true}};
-
- for (const auto& test : testCases) {
- // reset to 100ms baseline
- mWrapper->setTargetWorkDuration(1);
- mWrapper->setTargetWorkDuration(base.count());
-
- auto target = test.first;
- EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(target.count()))
- .Times(test.second ? 1 : 0);
- mWrapper->setTargetWorkDuration(target.count());
- verifyAndClearExpectations();
- }
-}
-
-TEST_F(AidlPowerHalWrapperTest, setTargetWorkDuration_shouldReconnectOnError) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
- std::vector<int32_t> threadIds = {1, 2};
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- ASSERT_TRUE(mWrapper->startPowerHintSession());
- verifyAndClearExpectations();
-
- EXPECT_CALL(*mMockSession.get(), updateTargetWorkDuration(1))
- .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
- mWrapper->setTargetWorkDuration(1);
- EXPECT_TRUE(mWrapper->shouldReconnectHAL());
-}
-
-TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
- std::vector<int32_t> threadIds = {1, 2};
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- ASSERT_TRUE(mWrapper->startPowerHintSession());
- verifyAndClearExpectations();
-
- auto base = toWorkDuration(100ms, 0);
- // test cases with actual work durations and whether it should update hint against baseline
- // 100ms
- const std::vector<std::pair<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>, bool>>
- testCases = {{{{-1ms, 100}}, false},
- {{{100ms - (mAllowedDeviation / 2), 100}}, false},
- {{{100ms + (mAllowedDeviation / 2), 100}}, false},
- {{{100ms + (mAllowedDeviation + 1ms), 100}}, true},
- {{{100ms - (mAllowedDeviation + 1ms), 100}}, true},
- {{{100ms, 100}, {200ms, 200}}, true},
- {{{100ms, 500}, {100ms, 600}, {3ms, 600}}, true}};
-
- for (const auto& test : testCases) {
- // reset actual duration
- sendActualWorkDurationGroup({base}, mStaleTimeout);
-
- auto raw = test.first;
- std::vector<WorkDuration> durations(raw.size());
- std::transform(raw.begin(), raw.end(), durations.begin(),
- [](auto d) { return toWorkDuration(d); });
- EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations))
- .Times(test.second ? 1 : 0);
- sendActualWorkDurationGroup(durations, 0ms);
- verifyAndClearExpectations();
- }
-}
-
-TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_exceedsStaleTime) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
- std::vector<int32_t> threadIds = {1, 2};
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- ASSERT_TRUE(mWrapper->startPowerHintSession());
- verifyAndClearExpectations();
-
- auto base = toWorkDuration(100ms, 0);
- // test cases with actual work durations and whether it should update hint against baseline
- // 100ms
- const std::vector<std::tuple<std::vector<std::pair<std::chrono::nanoseconds, nsecs_t>>,
- std::chrono::nanoseconds, bool>>
- testCases = {{{{100ms, 100}}, mStaleTimeout, true},
- {{{100ms + (mAllowedDeviation / 2), 100}}, mStaleTimeout, true},
- {{{100ms, 100}}, mStaleTimeout / 2, false}};
-
- for (const auto& test : testCases) {
- // reset actual duration
- sendActualWorkDurationGroup({base}, mStaleTimeout);
-
- auto raw = std::get<0>(test);
- std::vector<WorkDuration> durations(raw.size());
- std::transform(raw.begin(), raw.end(), durations.begin(),
- [](auto d) { return toWorkDuration(d); });
- EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(durations))
- .Times(std::get<2>(test) ? 1 : 0);
- sendActualWorkDurationGroup(durations, std::get<1>(test));
- verifyAndClearExpectations();
- }
-}
-
-TEST_F(AidlPowerHalWrapperTest, sendActualWorkDuration_shouldReconnectOnError) {
- ASSERT_TRUE(mWrapper->supportsPowerHintSession());
-
- std::vector<int32_t> threadIds = {1, 2};
- mWrapper->setPowerHintSessionThreadIds(threadIds);
- EXPECT_CALL(*mMockHal.get(), createHintSession(_, _, threadIds, _, _))
- .WillOnce(DoAll(SetArgPointee<4>(mMockSession), Return(Status::ok())));
- ASSERT_TRUE(mWrapper->startPowerHintSession());
- verifyAndClearExpectations();
- WorkDuration duration;
- duration.durationNanos = 1;
- EXPECT_CALL(*mMockSession.get(), reportActualWorkDuration(_))
- .WillOnce(Return(Status::fromExceptionCode(Status::Exception::EX_ILLEGAL_STATE)));
- sendActualWorkDurationGroup({duration}, 0ms);
- EXPECT_TRUE(mWrapper->shouldReconnectHAL());
-}
-
-} // namespace
-} // namespace android::Hwc2::impl
diff --git a/services/surfaceflinger/tests/unittests/Android.bp b/services/surfaceflinger/tests/unittests/Android.bp
index 339d746fac..c75f90222a 100644
--- a/services/surfaceflinger/tests/unittests/Android.bp
+++ b/services/surfaceflinger/tests/unittests/Android.bp
@@ -24,7 +24,7 @@ package {
filegroup {
name: "libsurfaceflinger_mock_sources",
srcs: [
- "mock/DisplayHardware/MockAidlPowerHalWrapper.cpp",
+ "mock/DisplayHardware/MockPowerHalController.cpp",
"mock/DisplayHardware/MockComposer.cpp",
"mock/DisplayHardware/MockHWC2.cpp",
"mock/DisplayHardware/MockIPower.cpp",
@@ -34,14 +34,20 @@ filegroup {
"mock/MockFrameTimeline.cpp",
"mock/MockFrameTracer.cpp",
"mock/MockNativeWindowSurface.cpp",
- "mock/MockSurfaceInterceptor.cpp",
"mock/MockTimeStats.cpp",
"mock/MockVsyncController.cpp",
+ "mock/MockVSyncDispatch.cpp",
"mock/MockVSyncTracker.cpp",
"mock/system/window/MockNativeWindow.cpp",
],
}
+cc_aconfig_library {
+ name: "libsurfaceflingerflags_test",
+ aconfig_declarations: "surfaceflinger_flags",
+ mode: "test",
+}
+
cc_test {
name: "libsurfaceflinger_unittest",
defaults: [
@@ -50,30 +56,15 @@ cc_test {
"surfaceflinger_defaults",
],
test_suites: ["device-tests"],
- sanitize: {
- // Using the address sanitizer not only helps uncover issues in the code
- // covered by the tests, but also covers some of the tricky injection of
- // fakes the unit tests currently do.
- //
- // Note: If you get an runtime link error like:
- //
- // CANNOT LINK EXECUTABLE "/data/local/tmp/libsurfaceflinger_unittest": library "libclang_rt.asan-aarch64-android.so" not found
- //
- // it is because the address sanitizer shared objects are not installed
- // by default in the system image.
- //
- // You can either "make dist tests" before flashing, or set this
- // option to false temporarily.
- address: true,
- },
+ static_libs: ["libc++fs"],
srcs: [
":libsurfaceflinger_mock_sources",
":libsurfaceflinger_sources",
"libsurfaceflinger_unittest_main.cpp",
- "AidlPowerHalWrapperTest.cpp",
- "CachingTest.cpp",
+ "ActiveDisplayRotationFlagsTest.cpp",
+ "BackgroundExecutorTest.cpp",
+ "CommitTest.cpp",
"CompositionTest.cpp",
- "DispSyncSourceTest.cpp",
"DisplayIdGeneratorTest.cpp",
"DisplayTransactionTest.cpp",
"DisplayDevice_GetBestColorModeTest.cpp",
@@ -86,33 +77,45 @@ cc_test {
"FpsTest.cpp",
"FramebufferSurfaceTest.cpp",
"FrameRateOverrideMappingsTest.cpp",
+ "FrameRateSelectionPriorityTest.cpp",
+ "FrameRateSelectionStrategyTest.cpp",
"FrameTimelineTest.cpp",
"GameModeTest.cpp",
"HWComposerTest.cpp",
"OneShotTimerTest.cpp",
"LayerHistoryTest.cpp",
+ "LayerHistoryIntegrationTest.cpp",
"LayerInfoTest.cpp",
"LayerMetadataTest.cpp",
+ "LayerHierarchyTest.cpp",
+ "LayerLifecycleManagerTest.cpp",
+ "LayerSnapshotTest.cpp",
"LayerTest.cpp",
"LayerTestUtils.cpp",
"MessageQueueTest.cpp",
"PowerAdvisorTest.cpp",
+ "SmallAreaDetectionAllowMappingsTest.cpp",
"SurfaceFlinger_CreateDisplayTest.cpp",
"SurfaceFlinger_DestroyDisplayTest.cpp",
"SurfaceFlinger_DisplayModeSwitching.cpp",
"SurfaceFlinger_DisplayTransactionCommitTest.cpp",
+ "SurfaceFlinger_ExcludeDolbyVisionTest.cpp",
+ "SurfaceFlinger_FoldableTest.cpp",
"SurfaceFlinger_GetDisplayNativePrimariesTest.cpp",
+ "SurfaceFlinger_GetDisplayStatsTest.cpp",
+ "SurfaceFlinger_HdrOutputControlTest.cpp",
"SurfaceFlinger_HotplugTest.cpp",
+ "SurfaceFlinger_InitializeDisplaysTest.cpp",
+ "SurfaceFlinger_NotifyExpectedPresentTest.cpp",
"SurfaceFlinger_NotifyPowerBoostTest.cpp",
- "SurfaceFlinger_OnInitializeDisplaysTest.cpp",
"SurfaceFlinger_PowerHintTest.cpp",
"SurfaceFlinger_SetDisplayStateTest.cpp",
"SurfaceFlinger_SetPowerModeInternalTest.cpp",
"SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp",
+ "SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp",
"SchedulerTest.cpp",
"SetFrameRateTest.cpp",
- "RefreshRateConfigsTest.cpp",
- "RefreshRateSelectionTest.cpp",
+ "RefreshRateSelectorTest.cpp",
"RefreshRateStatsTest.cpp",
"RegionSamplingTest.cpp",
"TimeStatsTest.cpp",
@@ -121,34 +124,41 @@ cc_test {
"TransactionFrameTracerTest.cpp",
"TransactionProtoParserTest.cpp",
"TransactionSurfaceFrameTest.cpp",
+ "TransactionTraceWriterTest.cpp",
"TransactionTracingTest.cpp",
"TunnelModeEnabledReporterTest.cpp",
"StrongTypingTest.cpp",
+ "VSyncCallbackRegistrationTest.cpp",
"VSyncDispatchTimerQueueTest.cpp",
"VSyncDispatchRealtimeTest.cpp",
"VsyncModulatorTest.cpp",
"VSyncPredictorTest.cpp",
"VSyncReactorTest.cpp",
"VsyncConfigurationTest.cpp",
+ "VsyncScheduleTest.cpp",
+ "WindowInfosListenerInvokerTest.cpp",
],
}
cc_defaults {
name: "libsurfaceflinger_mocks_defaults",
+ defaults: [
+ "android.hardware.graphics.common-ndk_static",
+ "android.hardware.graphics.composer3-ndk_static",
+ "android.hardware.power-ndk_static",
+ "librenderengine_deps",
+ ],
static_libs: [
"android.hardware.common-V2-ndk",
"android.hardware.common.fmq-V1-ndk",
- "android.hardware.graphics.common-V4-ndk",
"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.power@1.0",
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V2-cpp",
"libaidlcommonsupport",
"libcompositionengine_mocks",
"libcompositionengine",
@@ -161,12 +171,13 @@ cc_defaults {
"librenderengine_mocks",
"libscheduler",
"libserviceutils",
+ "libsurfaceflinger_common_test",
"libtimestats",
"libtimestats_atoms_proto",
"libtimestats_proto",
"libtonemap",
- "libtrace_proto",
"perfetto_trace_protos",
+ "libsurfaceflingerflags_test",
],
shared_libs: [
"android.hardware.configstore-utils",
@@ -188,6 +199,7 @@ cc_defaults {
"libinput",
"liblog",
"libnativewindow",
+ "libpowermanager",
"libprocessgroup",
"libprotobuf-cpp-lite",
"libSurfaceFlingerProp",
diff --git a/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
new file mode 100644
index 0000000000..5413bae55c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/BackgroundExecutorTest.cpp
@@ -0,0 +1,57 @@
+#include <gtest/gtest.h>
+#include <condition_variable>
+
+#include "BackgroundExecutor.h"
+
+namespace android {
+
+class BackgroundExecutorTest : public testing::Test {};
+
+namespace {
+
+TEST_F(BackgroundExecutorTest, singleProducer) {
+ std::mutex mutex;
+ std::condition_variable condition_variable;
+ bool backgroundTaskComplete = false;
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&mutex, &condition_variable, &backgroundTaskComplete]() {
+ std::lock_guard<std::mutex> lock{mutex};
+ condition_variable.notify_one();
+ backgroundTaskComplete = true;
+ }});
+
+ std::unique_lock<std::mutex> lock{mutex};
+ condition_variable.wait(lock, [&backgroundTaskComplete]() { return backgroundTaskComplete; });
+ ASSERT_TRUE(backgroundTaskComplete);
+}
+
+TEST_F(BackgroundExecutorTest, multipleProducers) {
+ std::mutex mutex;
+ std::condition_variable condition_variable;
+ const int backgroundTaskCount = 10;
+ int backgroundTaskCompleteCount = 0;
+
+ for (int i = 0; i < backgroundTaskCount; i++) {
+ std::thread([&mutex, &condition_variable, &backgroundTaskCompleteCount]() {
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&mutex, &condition_variable, &backgroundTaskCompleteCount]() {
+ std::lock_guard<std::mutex> lock{mutex};
+ backgroundTaskCompleteCount++;
+ if (backgroundTaskCompleteCount == backgroundTaskCount) {
+ condition_variable.notify_one();
+ }
+ }});
+ }).detach();
+ }
+
+ std::unique_lock<std::mutex> lock{mutex};
+ condition_variable.wait(lock, [&backgroundTaskCompleteCount]() {
+ return backgroundTaskCompleteCount == backgroundTaskCount;
+ });
+ ASSERT_EQ(backgroundTaskCount, backgroundTaskCompleteCount);
+}
+
+} // namespace
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CachingTest.cpp b/services/surfaceflinger/tests/unittests/CachingTest.cpp
deleted file mode 100644
index 6f85498670..0000000000
--- a/services/surfaceflinger/tests/unittests/CachingTest.cpp
+++ /dev/null
@@ -1,94 +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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "CachingTest"
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-#include <gui/BufferQueue.h>
-#include "BufferStateLayer.h"
-
-namespace android {
-
-class SlotGenerationTest : public testing::Test {
-protected:
- sp<BufferStateLayer::HwcSlotGenerator> mHwcSlotGenerator =
- sp<BufferStateLayer::HwcSlotGenerator>::make();
- sp<GraphicBuffer> mBuffer1{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
- sp<GraphicBuffer> mBuffer2{new GraphicBuffer(1, 1, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
- sp<GraphicBuffer> mBuffer3{new GraphicBuffer(10, 10, HAL_PIXEL_FORMAT_RGBA_8888, 1, 0)};
-};
-
-TEST_F(SlotGenerationTest, getHwcCacheSlot_Invalid) {
- sp<IBinder> binder = new BBinder();
- // test getting invalid client_cache_id
- client_cache_t id;
- int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
- EXPECT_EQ(BufferQueue::INVALID_BUFFER_SLOT, slot);
-}
-
-TEST_F(SlotGenerationTest, getHwcCacheSlot_Basic) {
- sp<IBinder> binder = new BBinder();
- client_cache_t id;
- id.token = binder;
- id.id = 0;
- int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot);
-
- client_cache_t idB;
- idB.token = binder;
- idB.id = 1;
- slot = mHwcSlotGenerator->getHwcCacheSlot(idB);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 2, slot);
-
- slot = mHwcSlotGenerator->getHwcCacheSlot(idB);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 2, slot);
-
- slot = mHwcSlotGenerator->getHwcCacheSlot(id);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - 1, slot);
-}
-
-TEST_F(SlotGenerationTest, getHwcCacheSlot_Reuse) {
- sp<IBinder> binder = new BBinder();
- std::vector<client_cache_t> ids;
- uint32_t cacheId = 0;
- // fill up cache
- for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- client_cache_t id;
- id.token = binder;
- id.id = cacheId;
- ids.push_back(id);
-
- int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
- cacheId++;
- }
- for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- int slot = mHwcSlotGenerator->getHwcCacheSlot(ids[static_cast<uint32_t>(i)]);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
- }
-
- for (int i = 0; i < BufferQueue::NUM_BUFFER_SLOTS; i++) {
- client_cache_t id;
- id.token = binder;
- id.id = cacheId;
- int slot = mHwcSlotGenerator->getHwcCacheSlot(id);
- EXPECT_EQ(BufferQueue::NUM_BUFFER_SLOTS - (i + 1), slot);
- cacheId++;
- }
-}
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CommitTest.cpp b/services/surfaceflinger/tests/unittests/CommitTest.cpp
new file mode 100644
index 0000000000..df53d1988d
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/CommitTest.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "CommitTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <renderengine/mock/RenderEngine.h>
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class CommitTest : public testing::Test {
+protected:
+ CommitTest() {
+ mFlinger.setupMockScheduler();
+ mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+ mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+ }
+ TestableSurfaceFlinger mFlinger;
+ renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+};
+
+namespace {
+
+TEST_F(CommitTest, noUpdatesDoesNotScheduleComposite) {
+ bool unused;
+ bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+ /*transactionsFlushed=*/0, unused);
+ EXPECT_FALSE(mustComposite);
+}
+
+// Ensure that we handle eTransactionNeeded correctly
+TEST_F(CommitTest, eTransactionNeededFlagSchedulesComposite) {
+ // update display level color matrix
+ mFlinger.setDaltonizerType(ColorBlindnessType::Deuteranomaly);
+ bool unused;
+ bool mustComposite = mFlinger.updateLayerSnapshots(VsyncId{1}, /*frameTimeNs=*/0,
+ /*transactionsFlushed=*/0, unused);
+ EXPECT_TRUE(mustComposite);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/CompositionTest.cpp b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
index bbfedc7685..beb2147c98 100644
--- a/services/surfaceflinger/tests/unittests/CompositionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/CompositionTest.cpp
@@ -31,16 +31,11 @@
#include <gui/LayerMetadata.h>
#include <log/log.h>
#include <renderengine/mock/FakeExternalTexture.h>
-#include <renderengine/mock/Framebuffer.h>
-#include <renderengine/mock/Image.h>
#include <renderengine/mock/RenderEngine.h>
#include <system/window.h>
#include <utils/String8.h>
-#include "BufferQueueLayer.h"
-#include "ContainerLayer.h"
#include "DisplayRenderArea.h"
-#include "EffectLayer.h"
#include "Layer.h"
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
@@ -102,7 +97,7 @@ public:
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- setupScheduler();
+ mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
.WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0)));
@@ -125,34 +120,6 @@ public:
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
- void setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
-
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread),
- TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
- TestableSurfaceFlinger::kTwoDisplayModes);
- }
-
void setupForceGeometryDirty() {
// TODO: This requires the visible region and other related
// state to be set, and is problematic for BufferLayers since they are
@@ -177,13 +144,14 @@ public:
bool mDisplayOff = false;
TestableSurfaceFlinger mFlinger;
sp<DisplayDevice> mDisplay;
- sp<DisplayDevice> mExternalDisplay;
sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
- new compositionengine::mock::DisplaySurface();
- mock::NativeWindow* mNativeWindow = new mock::NativeWindow();
+ sp<compositionengine::mock::DisplaySurface>::make();
+ sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
std::vector<sp<Layer>> mAuxiliaryLayers;
- sp<GraphicBuffer> mBuffer = new GraphicBuffer();
+ sp<GraphicBuffer> mBuffer =
+ sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
ANativeWindowBuffer* mNativeWindowBuffer = mBuffer->getNativeBuffer();
Hwc2::mock::Composer* mComposer = nullptr;
@@ -226,17 +194,18 @@ void CompositionTest::captureScreenComposition() {
LayerCase::setupForScreenCapture(this);
const Rect sourceCrop(0, 0, DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT);
- constexpr bool forSystem = true;
constexpr bool regionSampling = false;
auto renderArea = DisplayRenderArea::create(mDisplay, sourceCrop, sourceCrop.getSize(),
- ui::Dataspace::V0_SRGB, ui::Transform::ROT_0);
+ ui::Dataspace::V0_SRGB, true, true);
auto traverseLayers = [this](const LayerVector::Visitor& visitor) {
return mFlinger.traverseLayersInLayerStack(mDisplay->getLayerStack(),
- CaptureArgs::UNSET_UID, visitor);
+ CaptureArgs::UNSET_UID, {}, visitor);
};
+ auto getLayerSnapshots = RenderArea::fromTraverseLayersLambda(traverseLayers);
+
const uint32_t usage = GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN |
GRALLOC_USAGE_HW_RENDER | GRALLOC_USAGE_HW_TEXTURE;
mCaptureScreenBuffer =
@@ -245,8 +214,8 @@ void CompositionTest::captureScreenComposition() {
HAL_PIXEL_FORMAT_RGBA_8888, 1,
usage);
- auto future = mFlinger.renderScreenImpl(*renderArea, traverseLayers, mCaptureScreenBuffer,
- forSystem, regionSampling);
+ auto future = mFlinger.renderScreenImpl(std::move(renderArea), getLayerSnapshots,
+ mCaptureScreenBuffer, regionSampling);
ASSERT_TRUE(future.valid());
const auto fenceResult = future.get();
@@ -309,16 +278,23 @@ struct BaseDisplayVariant {
compositionengine::impl::createDisplay(test->mFlinger.getCompositionEngine(),
ceDisplayArgs);
- test->mDisplay = FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay,
- ui::DisplayConnectionType::Internal, HWC_DISPLAY,
- true /* isPrimary */)
- .setDisplaySurface(test->mDisplaySurface)
- .setNativeWindow(test->mNativeWindow)
- .setSecure(Derived::IS_SECURE)
- .setPowerMode(Derived::INIT_POWER_MODE)
- .inject();
- Mock::VerifyAndClear(test->mNativeWindow);
- test->mDisplay->setLayerStack(LAYER_STACK);
+ constexpr auto kDisplayConnectionType = ui::DisplayConnectionType::Internal;
+ constexpr bool kIsPrimary = true;
+
+ test->mDisplay =
+ FakeDisplayDeviceInjector(test->mFlinger, compositionDisplay,
+ kDisplayConnectionType, HWC_DISPLAY, kIsPrimary)
+ .setDisplaySurface(test->mDisplaySurface)
+ .setNativeWindow(test->mNativeWindow)
+ .setSecure(Derived::IS_SECURE)
+ .setPowerMode(Derived::INIT_POWER_MODE)
+ .setRefreshRateSelector(test->mFlinger.scheduler()->refreshRateSelector())
+ .skipRegisterDisplay()
+ .inject();
+ Mock::VerifyAndClear(test->mNativeWindow.get());
+
+ constexpr bool kIsInternal = kDisplayConnectionType == ui::DisplayConnectionType::Internal;
+ test->mDisplay->setLayerFilter({LAYER_STACK, kIsInternal});
}
template <typename Case>
@@ -339,7 +315,7 @@ struct BaseDisplayVariant {
EXPECT_CALL(*test->mComposer, getReleaseFences(HWC_DISPLAY, _, _)).Times(1);
EXPECT_CALL(*test->mDisplaySurface, onFrameCommitted()).Times(1);
- EXPECT_CALL(*test->mDisplaySurface, advanceFrame()).Times(1);
+ EXPECT_CALL(*test->mDisplaySurface, advanceFrame(_)).Times(1);
Case::CompositionType::setupHwcSetCallExpectations(test);
Case::CompositionType::setupHwcGetCallExpectations(test);
@@ -351,15 +327,13 @@ struct BaseDisplayVariant {
.WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>&,
const std::shared_ptr<renderengine::ExternalTexture>&,
- const bool, base::unique_fd&&)
- -> std::future<renderengine::RenderEngineResult> {
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.clip);
- return futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()});
+ return futureOf<FenceResult>(Fence::NO_FENCE);
});
}
@@ -372,7 +346,7 @@ struct BaseDisplayVariant {
}
static void setupHwcCompositionCallExpectations(CompositionTest* test) {
- EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+ EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _))
.Times(1);
EXPECT_CALL(*test->mDisplaySurface,
@@ -381,12 +355,12 @@ struct BaseDisplayVariant {
}
static void setupHwcClientCompositionCallExpectations(CompositionTest* test) {
- EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
+ EXPECT_CALL(*test->mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _))
.Times(1);
}
static void setupHwcForcedClientCompositionCallExpectations(CompositionTest* test) {
- EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _)).Times(1);
+ EXPECT_CALL(*test->mComposer, validateDisplay(HWC_DISPLAY, _, _, _, _)).Times(1);
}
static void setupRECompositionCallExpectations(CompositionTest* test) {
@@ -404,16 +378,14 @@ struct BaseDisplayVariant {
.WillRepeatedly([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>&,
const std::shared_ptr<renderengine::ExternalTexture>&,
- const bool, base::unique_fd&&)
- -> std::future<renderengine::RenderEngineResult> {
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.clip);
EXPECT_EQ(ui::Dataspace::UNKNOWN, displaySettings.outputDataspace);
- return futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()});
+ return futureOf<FenceResult>(Fence::NO_FENCE);
});
}
@@ -492,65 +464,31 @@ struct BaseLayerProperties {
static constexpr IComposerClient::BlendMode BLENDMODE =
IComposerClient::BlendMode::PREMULTIPLIED;
- static void enqueueBuffer(CompositionTest*, sp<BufferQueueLayer> layer) {
- auto producer = layer->getProducer();
-
- IGraphicBufferProducer::QueueBufferOutput qbo;
- status_t result = producer->connect(nullptr, NATIVE_WINDOW_API_EGL, false, &qbo);
- if (result != NO_ERROR) {
- ALOGE("Failed to connect() (%d)", result);
- return;
- }
-
- int slot;
- sp<Fence> fence;
- result = producer->dequeueBuffer(&slot, &fence, LayerProperties::WIDTH,
- LayerProperties::HEIGHT, LayerProperties::FORMAT,
- LayerProperties::USAGE, nullptr, nullptr);
- if (result != IGraphicBufferProducer::BUFFER_NEEDS_REALLOCATION) {
- ALOGE("Failed to dequeueBuffer() (%d)", result);
- return;
- }
-
- sp<GraphicBuffer> buffer;
- result = producer->requestBuffer(slot, &buffer);
- if (result != NO_ERROR) {
- ALOGE("Failed to requestBuffer() (%d)", result);
- return;
- }
-
- IGraphicBufferProducer::QueueBufferInput qbi(systemTime(), false /* isAutoTimestamp */,
- LayerProperties::DATASPACE,
- Rect(LayerProperties::WIDTH,
- LayerProperties::HEIGHT),
- LayerProperties::SCALING_MODE,
- LayerProperties::TRANSFORM, Fence::NO_FENCE);
- result = producer->queueBuffer(slot, qbi, &qbo);
- if (result != NO_ERROR) {
- ALOGE("Failed to queueBuffer (%d)", result);
- return;
- }
- }
-
- static void setupLatchedBuffer(CompositionTest* test, sp<BufferQueueLayer> layer) {
- // TODO: Eliminate the complexity of actually creating a buffer
- layer->setSizeForTest(LayerProperties::WIDTH, LayerProperties::HEIGHT);
- status_t err =
- layer->setDefaultBufferProperties(LayerProperties::WIDTH, LayerProperties::HEIGHT,
- LayerProperties::FORMAT);
- ASSERT_EQ(NO_ERROR, err);
+ static void setupLatchedBuffer(CompositionTest* test, sp<Layer> layer) {
Mock::VerifyAndClear(test->mRenderEngine);
- EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1);
- enqueueBuffer(test, layer);
- Mock::VerifyAndClearExpectations(test->mFlinger.scheduler());
+ const auto buffer = std::make_shared<
+ renderengine::mock::FakeExternalTexture>(LayerProperties::WIDTH,
+ LayerProperties::HEIGHT,
+ DEFAULT_TEXTURE_ID,
+ LayerProperties::FORMAT,
+ LayerProperties::USAGE |
+ GraphicBuffer::USAGE_HW_TEXTURE);
+
+ auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
+ layerDrawingState.crop = Rect(0, 0, LayerProperties::HEIGHT, LayerProperties::WIDTH);
+ layerDrawingState.buffer = buffer;
+ layerDrawingState.acquireFence = Fence::NO_FENCE;
+ layerDrawingState.dataspace = ui::Dataspace::UNKNOWN;
+ layer->setSurfaceDamageRegion(
+ Region(Rect(LayerProperties::HEIGHT, LayerProperties::WIDTH)));
bool ignoredRecomputeVisibleRegions;
- layer->latchBuffer(ignoredRecomputeVisibleRegions, 0, 0);
+ layer->latchBuffer(ignoredRecomputeVisibleRegions, 0);
Mock::VerifyAndClear(test->mRenderEngine);
}
- static void setupLayerState(CompositionTest* test, sp<BufferQueueLayer> layer) {
+ static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
setupLatchedBuffer(test, layer);
}
@@ -639,8 +577,8 @@ struct BaseLayerProperties {
EXPECT_CALL(*test->mRenderEngine, drawLayers)
.WillOnce([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layerSettings,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
@@ -648,9 +586,7 @@ struct BaseLayerProperties {
displaySettings.clip);
// screen capture adds an additional color layer as an alpha
// prefill, so gtet the back layer.
- std::future<renderengine::RenderEngineResult> resultFuture =
- futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()});
+ std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
if (layerSettings.empty()) {
ADD_FAILURE() << "layerSettings was not expected to be empty in "
"setupREBufferCompositionCommonCallExpectations "
@@ -660,12 +596,11 @@ struct BaseLayerProperties {
const renderengine::LayerSettings layer = layerSettings.back();
EXPECT_THAT(layer.source.buffer.buffer, Not(IsNull()));
EXPECT_THAT(layer.source.buffer.fence, Not(IsNull()));
- EXPECT_EQ(DEFAULT_TEXTURE_ID, layer.source.buffer.textureName);
- EXPECT_EQ(false, layer.source.buffer.isY410BT2020);
EXPECT_EQ(true, layer.source.buffer.usePremultipliedAlpha);
EXPECT_EQ(false, layer.source.buffer.isOpaque);
- EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+ EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
+ EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
+ EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace);
EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
return resultFuture;
});
@@ -691,8 +626,8 @@ struct BaseLayerProperties {
EXPECT_CALL(*test->mRenderEngine, drawLayers)
.WillOnce([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layerSettings,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
@@ -700,9 +635,7 @@ struct BaseLayerProperties {
displaySettings.clip);
// screen capture adds an additional color layer as an alpha
// prefill, so get the back layer.
- std::future<renderengine::RenderEngineResult> resultFuture =
- futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()});
+ std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
if (layerSettings.empty()) {
ADD_FAILURE()
<< "layerSettings was not expected to be empty in "
@@ -714,8 +647,9 @@ struct BaseLayerProperties {
EXPECT_EQ(half3(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
LayerProperties::COLOR[2]),
layer.source.solidColor);
- EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+ EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
+ EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
+ EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace);
EXPECT_EQ(LayerProperties::COLOR[3], layer.alpha);
return resultFuture;
});
@@ -736,11 +670,14 @@ struct SidebandLayerProperties : public BaseLayerProperties<SidebandLayerPropert
using Base = BaseLayerProperties<SidebandLayerProperties>;
static constexpr IComposerClient::BlendMode BLENDMODE = IComposerClient::BlendMode::NONE;
- static void setupLayerState(CompositionTest* test, sp<BufferQueueLayer> layer) {
+ static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
sp<NativeHandle> stream =
NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
false);
test->mFlinger.setLayerSidebandStream(layer, stream);
+ auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
+ layerDrawingState.crop =
+ Rect(0, 0, SidebandLayerProperties::HEIGHT, SidebandLayerProperties::WIDTH);
}
static void setupHwcSetSourceCropBufferCallExpectations(CompositionTest* test) {
@@ -771,8 +708,8 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties>
EXPECT_CALL(*test->mRenderEngine, drawLayers)
.WillOnce([&](const renderengine::DisplaySettings& displaySettings,
const std::vector<renderengine::LayerSettings>& layerSettings,
- const std::shared_ptr<renderengine::ExternalTexture>&, const bool,
- base::unique_fd&&) -> std::future<renderengine::RenderEngineResult> {
+ const std::shared_ptr<renderengine::ExternalTexture>&,
+ base::unique_fd&&) -> std::future<FenceResult> {
EXPECT_EQ(DEFAULT_DISPLAY_MAX_LUMINANCE, displaySettings.maxLuminance);
EXPECT_EQ(Rect(DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT),
displaySettings.physicalDisplay);
@@ -780,9 +717,7 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties>
displaySettings.clip);
// screen capture adds an additional color layer as an alpha
// prefill, so get the back layer.
- std::future<renderengine::RenderEngineResult> resultFuture =
- futureOf<renderengine::RenderEngineResult>(
- {NO_ERROR, base::unique_fd()});
+ std::future<FenceResult> resultFuture = futureOf<FenceResult>(Fence::NO_FENCE);
if (layerSettings.empty()) {
ADD_FAILURE() << "layerSettings was not expected to be empty in "
"setupInsecureREBufferCompositionCommonCallExpectations "
@@ -792,8 +727,9 @@ struct CommonSecureLayerProperties : public BaseLayerProperties<LayerProperties>
const renderengine::LayerSettings layer = layerSettings.back();
EXPECT_THAT(layer.source.buffer.buffer, IsNull());
EXPECT_EQ(half3(0.0f, 0.0f, 0.0f), layer.source.solidColor);
- EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius);
- EXPECT_EQ(ui::Dataspace::UNKNOWN, layer.sourceDataspace);
+ EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.x);
+ EXPECT_EQ(0.0, layer.geometry.roundedCornersRadius.y);
+ EXPECT_EQ(ui::Dataspace::V0_SRGB, layer.sourceDataspace);
EXPECT_EQ(1.0f, layer.alpha);
return resultFuture;
});
@@ -818,14 +754,14 @@ struct SecureLayerProperties : public CommonSecureLayerProperties<SecureLayerPro
struct CursorLayerProperties : public BaseLayerProperties<CursorLayerProperties> {
using Base = BaseLayerProperties<CursorLayerProperties>;
- static void setupLayerState(CompositionTest* test, sp<BufferQueueLayer> layer) {
+ static void setupLayerState(CompositionTest* test, sp<Layer> layer) {
Base::setupLayerState(test, layer);
test->mFlinger.setLayerPotentialCursor(layer, true);
}
};
struct NoLayerVariant {
- using FlingerLayerType = sp<BufferQueueLayer>;
+ using FlingerLayerType = sp<Layer>;
static FlingerLayerType createLayer(CompositionTest*) { return FlingerLayerType(); }
static void injectLayer(CompositionTest*, FlingerLayerType) {}
@@ -859,8 +795,6 @@ struct BaseLayerVariant {
static void initLayerDrawingStateAndComputeBounds(CompositionTest* test, sp<L> layer) {
auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
layerDrawingState.layerStack = LAYER_STACK;
- layerDrawingState.width = 100;
- layerDrawingState.height = 100;
layerDrawingState.color = half4(LayerProperties::COLOR[0], LayerProperties::COLOR[1],
LayerProperties::COLOR[2], LayerProperties::COLOR[3]);
layer->computeBounds(FloatRect(0, 0, 100, 100), ui::Transform(), 0.f /* shadowRadius */);
@@ -878,6 +812,7 @@ struct BaseLayerVariant {
Mock::VerifyAndClear(test->mComposer);
test->mFlinger.mutableDrawingState().layersSortedByZ.add(layer);
+ test->mFlinger.mutableVisibleRegionsDirty() = true;
}
static void cleanupInjectedLayers(CompositionTest* test) {
@@ -886,6 +821,7 @@ struct BaseLayerVariant {
test->mDisplay->getCompositionDisplay()->clearOutputLayers();
test->mFlinger.mutableDrawingState().layersSortedByZ.clear();
+ test->mFlinger.mutablePreviouslyComposedLayers().clear();
// Layer should be unregistered with scheduler.
test->mFlinger.commit();
@@ -896,13 +832,13 @@ struct BaseLayerVariant {
template <typename LayerProperties>
struct EffectLayerVariant : public BaseLayerVariant<LayerProperties> {
using Base = BaseLayerVariant<LayerProperties>;
- using FlingerLayerType = sp<EffectLayer>;
+ using FlingerLayerType = sp<Layer>;
static FlingerLayerType createLayer(CompositionTest* test) {
- FlingerLayerType layer = Base::template createLayerWithFactory<EffectLayer>(test, [test]() {
- return new EffectLayer(
- LayerCreationArgs(test->mFlinger.flinger(), sp<Client>(), "test-layer",
- LayerProperties::LAYER_FLAGS, LayerMetadata()));
+ FlingerLayerType layer = Base::template createLayerWithFactory<Layer>(test, [test]() {
+ return sp<Layer>::make(LayerCreationArgs(test->mFlinger.flinger(), sp<Client>(),
+ "test-layer", LayerProperties::LAYER_FLAGS,
+ LayerMetadata()));
});
auto& layerDrawingState = test->mFlinger.mutableLayerDrawingState(layer);
@@ -932,18 +868,14 @@ struct EffectLayerVariant : public BaseLayerVariant<LayerProperties> {
template <typename LayerProperties>
struct BufferLayerVariant : public BaseLayerVariant<LayerProperties> {
using Base = BaseLayerVariant<LayerProperties>;
- using FlingerLayerType = sp<BufferQueueLayer>;
+ using FlingerLayerType = sp<Layer>;
static FlingerLayerType createLayer(CompositionTest* test) {
- test->mFlinger.mutableTexturePool().push_back(DEFAULT_TEXTURE_ID);
-
- FlingerLayerType layer =
- Base::template createLayerWithFactory<BufferQueueLayer>(test, [test]() {
- LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
- LayerProperties::LAYER_FLAGS, LayerMetadata());
- args.textureName = test->mFlinger.mutableTexturePool().back();
- return new BufferQueueLayer(args);
- });
+ FlingerLayerType layer = Base::template createLayerWithFactory<Layer>(test, [test]() {
+ LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-layer",
+ LayerProperties::LAYER_FLAGS, LayerMetadata());
+ return sp<Layer>::make(args);
+ });
LayerProperties::setupLayerState(test, layer);
@@ -984,12 +916,12 @@ struct BufferLayerVariant : public BaseLayerVariant<LayerProperties> {
template <typename LayerProperties>
struct ContainerLayerVariant : public BaseLayerVariant<LayerProperties> {
using Base = BaseLayerVariant<LayerProperties>;
- using FlingerLayerType = sp<ContainerLayer>;
+ using FlingerLayerType = sp<Layer>;
static FlingerLayerType createLayer(CompositionTest* test) {
LayerCreationArgs args(test->mFlinger.flinger(), sp<Client>(), "test-container-layer",
LayerProperties::LAYER_FLAGS, LayerMetadata());
- FlingerLayerType layer = new ContainerLayer(args);
+ FlingerLayerType layer = sp<Layer>::make(args);
Base::template initLayerDrawingStateAndComputeBounds(test, layer);
return layer;
}
diff --git a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp b/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
deleted file mode 100644
index 67ace1af83..0000000000
--- a/services/surfaceflinger/tests/unittests/DispSyncSourceTest.cpp
+++ /dev/null
@@ -1,315 +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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "LibSurfaceFlingerUnittests"
-#define LOG_NDEBUG 0
-
-#include <inttypes.h>
-
-#include <gmock/gmock.h>
-#include <gtest/gtest.h>
-
-#include <log/log.h>
-
-#include "AsyncCallRecorder.h"
-#include "Scheduler/DispSyncSource.h"
-#include "Scheduler/VSyncDispatch.h"
-#include "mock/MockVSyncTracker.h"
-
-namespace android {
-namespace {
-
-using namespace std::chrono_literals;
-using namespace testing;
-
-class MockVSyncDispatch : public scheduler::VSyncDispatch {
-public:
- MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
- MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
- MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
- MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken), (override));
- MOCK_METHOD(void, dump, (std::string&), (const, override));
-
- MockVSyncDispatch() {
- ON_CALL(*this, registerCallback)
- .WillByDefault(
- [this](std::function<void(nsecs_t, nsecs_t, nsecs_t)> const& callback,
- std::string) {
- CallbackToken token(mNextToken);
- mNextToken++;
-
- mCallbacks.emplace(token, CallbackData(callback));
- ALOGD("registerCallback: %zu", token.value());
- return token;
- });
-
- ON_CALL(*this, unregisterCallback).WillByDefault([this](CallbackToken token) {
- ALOGD("unregisterCallback: %zu", token.value());
- mCallbacks.erase(token);
- });
-
- ON_CALL(*this, schedule).WillByDefault([this](CallbackToken token, ScheduleTiming timing) {
- ALOGD("schedule: %zu", token.value());
- if (mCallbacks.count(token) == 0) {
- ALOGD("schedule: callback %zu not registered", token.value());
- return scheduler::ScheduleResult{};
- }
-
- auto& callback = mCallbacks.at(token);
- callback.scheduled = true;
- callback.vsyncTime = timing.earliestVsync;
- callback.targetWakeupTime =
- timing.earliestVsync - timing.workDuration - timing.readyDuration;
- ALOGD("schedule: callback %zu scheduled", token.value());
- return scheduler::ScheduleResult{callback.targetWakeupTime};
- });
-
- ON_CALL(*this, cancel).WillByDefault([this](CallbackToken token) {
- ALOGD("cancel: %zu", token.value());
- if (mCallbacks.count(token) == 0) {
- ALOGD("cancel: callback %zu is not registered", token.value());
- return scheduler::CancelResult::Error;
- }
-
- auto& callback = mCallbacks.at(token);
- callback.scheduled = false;
- ALOGD("cancel: callback %zu cancelled", token.value());
- return scheduler::CancelResult::Cancelled;
- });
- }
-
- void triggerCallbacks() {
- ALOGD("triggerCallbacks");
- for (auto& [token, callback] : mCallbacks) {
- if (callback.scheduled) {
- ALOGD("triggerCallbacks: callback %zu", token.value());
- callback.scheduled = false;
- callback.func(callback.vsyncTime, callback.targetWakeupTime, callback.readyTime);
- } else {
- ALOGD("triggerCallbacks: callback %zu is not scheduled", token.value());
- }
- }
- }
-
-private:
- struct CallbackData {
- explicit CallbackData(std::function<void(nsecs_t, nsecs_t, nsecs_t)> func)
- : func(std::move(func)) {}
-
- std::function<void(nsecs_t, nsecs_t, nsecs_t)> func;
- bool scheduled = false;
- nsecs_t vsyncTime = 0;
- nsecs_t targetWakeupTime = 0;
- nsecs_t readyTime = 0;
- };
-
- std::unordered_map<CallbackToken, CallbackData> mCallbacks;
- size_t mNextToken;
-};
-
-class DispSyncSourceTest : public testing::Test, private VSyncSource::Callback {
-protected:
- DispSyncSourceTest();
- ~DispSyncSourceTest() override;
-
- void SetUp() override;
- void createDispSyncSource();
-
- void onVSyncEvent(nsecs_t when, VSyncSource::VSyncData) override;
-
- std::unique_ptr<MockVSyncDispatch> mVSyncDispatch;
- std::unique_ptr<mock::VSyncTracker> mVSyncTracker;
- std::unique_ptr<scheduler::DispSyncSource> mDispSyncSource;
-
- AsyncCallRecorder<void (*)(nsecs_t, VSyncSource::VSyncData)> mVSyncEventCallRecorder;
-
- static constexpr std::chrono::nanoseconds mWorkDuration = 20ms;
- static constexpr std::chrono::nanoseconds mReadyDuration = 10ms;
- static constexpr int mIterations = 100;
- const scheduler::VSyncDispatch::CallbackToken mFakeToken{2398};
- const std::string mName = "DispSyncSourceTest";
-};
-
-DispSyncSourceTest::DispSyncSourceTest() {
- 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());
-}
-
-DispSyncSourceTest::~DispSyncSourceTest() {
- 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());
-}
-
-void DispSyncSourceTest::SetUp() {
- mVSyncDispatch = std::make_unique<MockVSyncDispatch>();
- mVSyncTracker = std::make_unique<mock::VSyncTracker>();
-}
-
-void DispSyncSourceTest::onVSyncEvent(nsecs_t when, VSyncSource::VSyncData vsyncData) {
- ALOGD("onVSyncEvent: %" PRId64, when);
-
- mVSyncEventCallRecorder.recordCall(when, vsyncData);
-}
-
-void DispSyncSourceTest::createDispSyncSource() {
- mDispSyncSource = std::make_unique<scheduler::DispSyncSource>(*mVSyncDispatch, *mVSyncTracker,
- mWorkDuration, mReadyDuration,
- true, mName.c_str());
- mDispSyncSource->setCallback(this);
-}
-
-/* ------------------------------------------------------------------------
- * Test cases
- */
-
-TEST_F(DispSyncSourceTest, createDispSync) {
- EXPECT_TRUE(mVSyncDispatch);
-}
-
-TEST_F(DispSyncSourceTest, createDispSyncSource) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).WillOnce(Return(mFakeToken));
- EXPECT_CALL(*mVSyncDispatch, cancel(mFakeToken))
- .WillOnce(Return(scheduler::CancelResult::Cancelled));
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mFakeToken)).WillOnce(Return());
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-}
-
-TEST_F(DispSyncSourceTest, noCallbackAfterInit) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-
- // DispSyncSource starts with Vsync disabled
- mVSyncDispatch->triggerCallbacks();
- EXPECT_FALSE(mVSyncEventCallRecorder.waitForUnexpectedCall().has_value());
-}
-
-TEST_F(DispSyncSourceTest, waitForCallbacks) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch,
- schedule(_, Truly([&](auto timings) {
- return timings.workDuration == mWorkDuration.count() &&
- timings.readyDuration == mReadyDuration.count();
- })))
- .Times(mIterations + 1);
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-
- mDispSyncSource->setVSyncEnabled(true);
- for (int i = 0; i < mIterations; i++) {
- mVSyncDispatch->triggerCallbacks();
- const auto callbackData = mVSyncEventCallRecorder.waitForCall();
- ASSERT_TRUE(callbackData.has_value());
- const auto [when, vsyncData] = callbackData.value();
- EXPECT_EQ(when,
- vsyncData.expectedPresentationTime - mWorkDuration.count() -
- mReadyDuration.count());
- }
-}
-
-TEST_F(DispSyncSourceTest, waitForCallbacksWithDurationChange) {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch,
- schedule(_, Truly([&](auto timings) {
- return timings.workDuration == mWorkDuration.count() &&
- timings.readyDuration == mReadyDuration.count();
- })))
- .Times(1);
-
- createDispSyncSource();
-
- EXPECT_TRUE(mDispSyncSource);
-
- mDispSyncSource->setVSyncEnabled(true);
- EXPECT_CALL(*mVSyncDispatch,
- schedule(_, Truly([&](auto timings) {
- return timings.workDuration == mWorkDuration.count() &&
- timings.readyDuration == mReadyDuration.count();
- })))
- .Times(mIterations);
- for (int i = 0; i < mIterations; i++) {
- mVSyncDispatch->triggerCallbacks();
- const auto callbackData = mVSyncEventCallRecorder.waitForCall();
- ASSERT_TRUE(callbackData.has_value());
- const auto [when, vsyncData] = callbackData.value();
- EXPECT_EQ(when,
- vsyncData.expectedPresentationTime - mWorkDuration.count() -
- mReadyDuration.count());
- }
-
- const auto newDuration = mWorkDuration / 2;
- EXPECT_CALL(*mVSyncDispatch, schedule(_, Truly([&](auto timings) {
- return timings.workDuration == newDuration.count() &&
- timings.readyDuration == 0;
- })))
- .Times(1);
- mDispSyncSource->setDuration(newDuration, 0ns);
-
- EXPECT_CALL(*mVSyncDispatch, schedule(_, Truly([&](auto timings) {
- return timings.workDuration == newDuration.count() &&
- timings.readyDuration == 0;
- })))
- .Times(mIterations);
- for (int i = 0; i < mIterations; i++) {
- mVSyncDispatch->triggerCallbacks();
- const auto callbackData = mVSyncEventCallRecorder.waitForCall();
- ASSERT_TRUE(callbackData.has_value());
- const auto [when, vsyncData] = callbackData.value();
- EXPECT_EQ(when, vsyncData.expectedPresentationTime - newDuration.count());
- }
-
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
-}
-
-TEST_F(DispSyncSourceTest, getLatestVsyncData) {
- const nsecs_t now = systemTime();
- const nsecs_t expectedPresentationTime =
- now + mWorkDuration.count() + mReadyDuration.count() + 1;
- EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_))
- .WillOnce(Return(expectedPresentationTime));
- {
- InSequence seq;
- EXPECT_CALL(*mVSyncDispatch, registerCallback(_, mName)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, cancel(_)).Times(1);
- EXPECT_CALL(*mVSyncDispatch, unregisterCallback(_)).Times(1);
- }
-
- createDispSyncSource();
- EXPECT_TRUE(mDispSyncSource);
-
- const auto vsyncData = mDispSyncSource->getLatestVSyncData();
- ASSERT_EQ(vsyncData.expectedPresentationTime, expectedPresentationTime);
- EXPECT_EQ(vsyncData.deadlineTimestamp, expectedPresentationTime - mReadyDuration.count());
-}
-
-} // namespace
-} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
index 93af225b3e..c463a9271b 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_InitiateModeChange.cpp
@@ -18,19 +18,25 @@
#define LOG_TAG "LibSurfaceFlingerUnittests"
#include "DisplayTransactionTestHelpers.h"
+#include "mock/MockFrameRateMode.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#define EXPECT_DISPLAY_MODE_REQUEST(expected, requestOpt) \
+ ASSERT_TRUE(requestOpt); \
+ EXPECT_FRAME_RATE_MODE(expected.mode.modePtr, expected.mode.fps, requestOpt->mode); \
+ EXPECT_EQ(expected.emitEvent, requestOpt->emitEvent)
+
namespace android {
namespace {
using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+using DisplayModeRequest = display::DisplayModeRequest;
class InitiateModeChangeTest : public DisplayTransactionTest {
public:
- using Event = scheduler::DisplayModeEvent;
-
+ using Action = DisplayDevice::DesiredModeAction;
void SetUp() override {
injectFakeBufferQueueFactory();
injectFakeNativeWindowSurfaceFactory();
@@ -41,7 +47,9 @@ public:
PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
- mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+ mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+ DisplayHotplugEvent::CONNECTED);
+ mFlinger.configureAndCommit();
mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
.setDisplayModes(makeModes(kMode60, kMode90, kMode120), kModeId60)
@@ -55,92 +63,89 @@ protected:
static constexpr DisplayModeId kModeId90{1};
static constexpr DisplayModeId kModeId120{2};
- static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
- static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz);
- static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz);
+ static inline const ftl::NonNull<DisplayModePtr> kMode60 =
+ ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode90 =
+ ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode120 =
+ ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
+
+ static inline const DisplayModeRequest kDesiredMode30{{30_Hz, kMode60}, .emitEvent = false};
+ static inline const DisplayModeRequest kDesiredMode60{{60_Hz, kMode60}, .emitEvent = true};
+ static inline const DisplayModeRequest kDesiredMode90{{90_Hz, kMode90}, .emitEvent = false};
+ static inline const DisplayModeRequest kDesiredMode120{{120_Hz, kMode120}, .emitEvent = true};
};
-TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setCurrentMode) {
- EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode60, Event::None}));
- EXPECT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+TEST_F(InitiateModeChangeTest, setDesiredModeToActiveMode) {
+ EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode60)));
+ EXPECT_FALSE(mDisplay->getDesiredMode());
}
-TEST_F(InitiateModeChangeTest, setDesiredActiveMode_setNewMode) {
- EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
- ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
- EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
- EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
-
- // Setting another mode should be cached but return false
- EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None}));
- ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
- EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode);
- EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+TEST_F(InitiateModeChangeTest, setDesiredMode) {
+ EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+ mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
+
+ EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
}
-TEST_F(InitiateModeChangeTest, clearDesiredActiveModeState) {
- EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
- ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
+TEST_F(InitiateModeChangeTest, clearDesiredMode) {
+ EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+ mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
+ EXPECT_TRUE(mDisplay->getDesiredMode());
- mDisplay->clearDesiredActiveModeState();
- ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+ mDisplay->clearDesiredMode();
+ EXPECT_FALSE(mDisplay->getDesiredMode());
}
-TEST_F(InitiateModeChangeTest, initiateModeChange) NO_THREAD_SAFETY_ANALYSIS {
- EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
- ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
- EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
- EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+TEST_F(InitiateModeChangeTest, initiateModeChange) REQUIRES(kMainThreadContext) {
+ EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+ mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
- hal::VsyncPeriodChangeConstraints constraints{
+ const hal::VsyncPeriodChangeConstraints constraints{
.desiredTimeNanos = systemTime(),
.seamlessRequired = false,
};
hal::VsyncPeriodChangeTimeline timeline;
- EXPECT_EQ(OK,
- mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
- &timeline));
- EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
- EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
-
- mDisplay->clearDesiredActiveModeState();
- ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+ EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
+
+ mDisplay->clearDesiredMode();
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+}
+
+TEST_F(InitiateModeChangeTest, initiateRenderRateSwitch) {
+ EXPECT_EQ(Action::InitiateRenderRateSwitch,
+ mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode30)));
+ EXPECT_FALSE(mDisplay->getDesiredMode());
}
-TEST_F(InitiateModeChangeTest, getUpcomingActiveMode_desiredActiveModeChanged)
-NO_THREAD_SAFETY_ANALYSIS {
- EXPECT_TRUE(mDisplay->setDesiredActiveMode({kMode90, Event::None}));
- ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
- EXPECT_EQ(kMode90, mDisplay->getDesiredActiveMode()->mode);
- EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
+TEST_F(InitiateModeChangeTest, initiateDisplayModeSwitch) FTL_FAKE_GUARD(kMainThreadContext) {
+ EXPECT_EQ(Action::InitiateDisplayModeSwitch,
+ mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode90)));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getDesiredMode());
- hal::VsyncPeriodChangeConstraints constraints{
+ const hal::VsyncPeriodChangeConstraints constraints{
.desiredTimeNanos = systemTime(),
.seamlessRequired = false,
};
hal::VsyncPeriodChangeTimeline timeline;
- EXPECT_EQ(OK,
- mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
- &timeline));
- EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
- EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
-
- EXPECT_FALSE(mDisplay->setDesiredActiveMode({kMode120, Event::None}));
- ASSERT_NE(std::nullopt, mDisplay->getDesiredActiveMode());
- EXPECT_EQ(kMode120, mDisplay->getDesiredActiveMode()->mode);
- EXPECT_EQ(Event::None, mDisplay->getDesiredActiveMode()->event);
-
- EXPECT_EQ(kMode90, mDisplay->getUpcomingActiveMode().mode);
- EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
-
- EXPECT_EQ(OK,
- mDisplay->initiateModeChange(*mDisplay->getDesiredActiveMode(), constraints,
- &timeline));
- EXPECT_EQ(kMode120, mDisplay->getUpcomingActiveMode().mode);
- EXPECT_EQ(Event::None, mDisplay->getUpcomingActiveMode().event);
-
- mDisplay->clearDesiredActiveModeState();
- ASSERT_EQ(std::nullopt, mDisplay->getDesiredActiveMode());
+ EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
+
+ EXPECT_EQ(Action::None, mDisplay->setDesiredMode(DisplayModeRequest(kDesiredMode120)));
+ ASSERT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getDesiredMode());
+
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode90, mDisplay->getPendingMode());
+
+ EXPECT_TRUE(mDisplay->initiateModeChange(*mDisplay->getDesiredMode(), constraints, timeline));
+ EXPECT_DISPLAY_MODE_REQUEST(kDesiredMode120, mDisplay->getPendingMode());
+
+ mDisplay->clearDesiredMode();
+ EXPECT_FALSE(mDisplay->getDesiredMode());
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
index 225ad163d9..ac5e9274bb 100644
--- a/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayDevice_SetDisplayBrightnessTest.cpp
@@ -96,5 +96,23 @@ TEST_F(SetDisplayBrightnessTest, persistDisplayBrightnessWithCompositeShortCircu
EXPECT_EQ(std::nullopt, displayDevice->getCompositionDisplay()->getState().displayBrightness);
}
+TEST_F(SetDisplayBrightnessTest, firstDisplayBrightnessWithComposite) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ sp<DisplayDevice> displayDevice = getDisplayDevice();
+
+ EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+
+ constexpr float kDisplayBrightness = -1.0f;
+ displayDevice->stageBrightness(kDisplayBrightness);
+
+ EXPECT_EQ(-1.0f, displayDevice->getStagedBrightness());
+
+ displayDevice->persistBrightness(true);
+
+ EXPECT_EQ(std::nullopt, displayDevice->getStagedBrightness());
+ EXPECT_EQ(kDisplayBrightness,
+ displayDevice->getCompositionDisplay()->getState().displayBrightness);
+}
+
} // namespace
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
index f04221cc21..13796650c8 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTest.cpp
@@ -29,15 +29,12 @@ using testing::SetArgPointee;
using android::hardware::graphics::composer::hal::HWDisplayId;
-using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
-
-DisplayTransactionTest::DisplayTransactionTest() {
+DisplayTransactionTest::DisplayTransactionTest(bool withMockScheduler) {
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());
- // Default to no wide color display support configured
- mFlinger.mutableHasWideColorDisplay() = false;
+ mFlinger.mutableSupportsWideColor() = false;
mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged;
mFlinger.setCreateBufferQueueFunction([](auto, auto, auto) {
@@ -49,9 +46,11 @@ DisplayTransactionTest::DisplayTransactionTest() {
return nullptr;
});
- injectMockScheduler();
+ if (withMockScheduler) {
+ injectMockScheduler(PhysicalDisplayId::fromPort(0));
+ }
+
mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
- mFlinger.mutableInterceptor() = mSurfaceInterceptor;
injectMockComposer(0);
}
@@ -63,22 +62,26 @@ DisplayTransactionTest::~DisplayTransactionTest() {
mFlinger.resetScheduler(nullptr);
}
-void DisplayTransactionTest::injectMockScheduler() {
+void DisplayTransactionTest::injectMockScheduler(PhysicalDisplayId displayId) {
+ LOG_ALWAYS_FATAL_IF(mFlinger.scheduler());
+
EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*mEventThread, createEventConnection(_, _))
.WillOnce(Return(
- new EventThreadConnection(mEventThread, /*callingUid=*/0, ResyncCallback())));
+ sp<EventThreadConnection>::make(mEventThread, mock::EventThread::kCallingUid)));
EXPECT_CALL(*mSFEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*mSFEventThread, createEventConnection(_, _))
- .WillOnce(Return(
- new EventThreadConnection(mSFEventThread, /*callingUid=*/0, ResyncCallback())));
+ .WillOnce(Return(sp<EventThreadConnection>::make(mSFEventThread,
+ mock::EventThread::kCallingUid)));
- mFlinger.setupScheduler(std::unique_ptr<scheduler::VsyncController>(mVsyncController),
- std::unique_ptr<scheduler::VSyncTracker>(mVSyncTracker),
+ mFlinger.setupScheduler(std::make_unique<mock::VsyncController>(),
+ std::make_shared<mock::VSyncTracker>(),
std::unique_ptr<EventThread>(mEventThread),
std::unique_ptr<EventThread>(mSFEventThread),
- TestableSurfaceFlinger::SchedulerCallbackImpl::kMock);
+ TestableSurfaceFlinger::DefaultDisplayMode{displayId},
+ TestableSurfaceFlinger::SchedulerCallbackImpl::kMock,
+ TestableSurfaceFlinger::VsyncTrackerCallbackImpl::kMock);
}
void DisplayTransactionTest::injectMockComposer(int virtualDisplayCount) {
@@ -100,8 +103,8 @@ void DisplayTransactionTest::injectFakeBufferQueueFactory() {
// This setup is only expected once per test.
ASSERT_TRUE(mConsumer == nullptr && mProducer == nullptr);
- mConsumer = new mock::GraphicBufferConsumer();
- mProducer = new mock::GraphicBufferProducer();
+ mConsumer = sp<mock::GraphicBufferConsumer>::make();
+ mProducer = sp<mock::GraphicBufferProducer>::make();
mFlinger.setCreateBufferQueueFunction([this](auto outProducer, auto outConsumer, bool) {
*outProducer = mProducer;
@@ -120,53 +123,13 @@ void DisplayTransactionTest::injectFakeNativeWindowSurfaceFactory() {
});
}
-sp<DisplayDevice> DisplayTransactionTest::injectDefaultInternalDisplay(
- std::function<void(FakeDisplayDeviceInjector&)> injectExtra) {
- constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
- constexpr int DEFAULT_DISPLAY_WIDTH = 1080;
- constexpr int DEFAULT_DISPLAY_HEIGHT = 1920;
- constexpr HWDisplayId DEFAULT_DISPLAY_HWC_DISPLAY_ID = 0;
-
- // The DisplayDevice is required to have a framebuffer (behind the
- // ANativeWindow interface) which uses the actual hardware display
- // size.
- EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_WIDTH), Return(0)));
- EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
- .WillRepeatedly(DoAll(SetArgPointee<1>(DEFAULT_DISPLAY_HEIGHT), Return(0)));
- EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT));
- EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT));
- EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
- EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
-
- auto compositionDisplay =
- compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
- compositionengine::DisplayCreationArgsBuilder()
- .setId(DEFAULT_DISPLAY_ID)
- .setPixels({DEFAULT_DISPLAY_WIDTH,
- DEFAULT_DISPLAY_HEIGHT})
- .setPowerAdvisor(&mPowerAdvisor)
- .build());
-
- constexpr bool kIsPrimary = true;
- auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
- ui::DisplayConnectionType::Internal,
- DEFAULT_DISPLAY_HWC_DISPLAY_ID, kIsPrimary);
-
- injector.setNativeWindow(mNativeWindow);
- if (injectExtra) {
- injectExtra(injector);
- }
-
- auto displayDevice = injector.inject();
+bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const {
+ const auto& map = mFlinger.hwcPhysicalDisplayIdMap();
- Mock::VerifyAndClear(mNativeWindow.get());
+ const auto it = map.find(hwcDisplayId);
+ if (it == map.end()) return false;
- return displayDevice;
-}
-
-bool DisplayTransactionTest::hasPhysicalHwcDisplay(HWDisplayId hwcDisplayId) const {
- return mFlinger.hwcPhysicalDisplayIdMap().count(hwcDisplayId) == 1;
+ return mFlinger.hwcDisplayData().count(it->second) == 1;
}
bool DisplayTransactionTest::hasTransactionFlagSet(int32_t flag) const {
diff --git a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
index f5235ce953..ee12276994 100644
--- a/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
+++ b/services/surfaceflinger/tests/unittests/DisplayTransactionTestHelpers.h
@@ -42,6 +42,7 @@
#include <renderengine/mock/RenderEngine.h>
#include <ui/DebugUtils.h>
+#include "FakeDisplayInjector.h"
#include "TestableScheduler.h"
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
@@ -49,7 +50,6 @@
#include "mock/DisplayHardware/MockPowerAdvisor.h"
#include "mock/MockEventThread.h"
#include "mock/MockNativeWindowSurface.h"
-#include "mock/MockSurfaceInterceptor.h"
#include "mock/MockVsyncController.h"
#include "mock/system/window/MockNativeWindow.h"
@@ -85,12 +85,15 @@ public:
// --------------------------------------------------------------------
// Mock/Fake injection
- void injectMockScheduler();
+ void injectMockScheduler(PhysicalDisplayId);
void injectMockComposer(int virtualDisplayCount);
void injectFakeBufferQueueFactory();
void injectFakeNativeWindowSurfaceFactory();
+
sp<DisplayDevice> injectDefaultInternalDisplay(
- std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)>);
+ std::function<void(TestableSurfaceFlinger::FakeDisplayDeviceInjector&)> injectExtra) {
+ return mFakeDisplayInjector.injectInternalDisplay(injectExtra);
+ }
// --------------------------------------------------------------------
// Postcondition helpers
@@ -111,19 +114,20 @@ public:
// Test instances
TestableSurfaceFlinger mFlinger;
- sp<mock::NativeWindow> mNativeWindow = new mock::NativeWindow();
- sp<GraphicBuffer> mBuffer = new GraphicBuffer();
+ sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
+ sp<GraphicBuffer> mBuffer =
+ sp<GraphicBuffer>::make(1u, 1u, PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_OFTEN);
Hwc2::mock::PowerAdvisor mPowerAdvisor;
+ FakeDisplayInjector mFakeDisplayInjector{mFlinger, mPowerAdvisor, mNativeWindow};
+
// These mocks are created by the test, but are destroyed by SurfaceFlinger
// by virtue of being stored into a std::unique_ptr. However we still need
// to keep a reference to them for use in setting up call expectations.
renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
Hwc2::mock::Composer* mComposer = nullptr;
- sp<mock::SurfaceInterceptor> mSurfaceInterceptor = new mock::SurfaceInterceptor;
- mock::VsyncController* mVsyncController = new mock::VsyncController;
- mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker;
mock::EventThread* mEventThread = new mock::EventThread;
mock::EventThread* mSFEventThread = new mock::EventThread;
@@ -133,7 +137,7 @@ public:
surfaceflinger::mock::NativeWindowSurface* mNativeWindowSurface = nullptr;
protected:
- DisplayTransactionTest();
+ DisplayTransactionTest(bool withMockScheduler = true);
};
constexpr int32_t DEFAULT_VSYNC_PERIOD = 16'666'667;
@@ -152,7 +156,6 @@ constexpr int POWER_MODE_LEET = 1337; // An out of range power mode value
#define BOOL_SUBSTITUTE(TYPENAME) enum class TYPENAME : bool { FALSE = false, TRUE = true };
BOOL_SUBSTITUTE(Async);
-BOOL_SUBSTITUTE(Critical);
BOOL_SUBSTITUTE(Primary);
BOOL_SUBSTITUTE(Secure);
BOOL_SUBSTITUTE(Virtual);
@@ -232,8 +235,8 @@ struct HwcDisplayIdGetter<PhysicalDisplayIdType<PhysicalDisplay>> {
// 1) PhysicalDisplayIdType<...> for generated ID of physical display backed by HWC.
// 2) HalVirtualDisplayIdType<...> for hard-coded ID of virtual display backed by HWC.
// 3) GpuVirtualDisplayIdType for virtual display without HWC backing.
-template <typename DisplayIdType, int width, int height, Critical critical, Async async,
- Secure secure, Primary primary, int grallocUsage, int displayFlags>
+template <typename DisplayIdType, int width, int height, Async async, Secure secure,
+ Primary primary, int grallocUsage, int displayFlags>
struct DisplayVariant {
using DISPLAY_ID = DisplayIdGetter<DisplayIdType>;
using CONNECTION_TYPE = DisplayConnectionTypeGetter<DisplayIdType>;
@@ -249,9 +252,6 @@ struct DisplayVariant {
static constexpr Virtual VIRTUAL =
IsPhysicalDisplayId<DisplayIdType>{} ? Virtual::FALSE : Virtual::TRUE;
- // When creating native window surfaces for the framebuffer, whether those should be critical
- static constexpr Critical CRITICAL = critical;
-
// When creating native window surfaces for the framebuffer, whether those should be async
static constexpr Async ASYNC = async;
@@ -355,6 +355,7 @@ struct HwcDisplayVariant {
}
// Called by tests to inject a HWC display setup
+ template <bool kInitPowerMode = true>
static void injectHwcDisplayWithNoDefaultCapabilities(DisplayTransactionTest* test) {
const auto displayId = DisplayVariant::DISPLAY_ID::get();
ASSERT_FALSE(GpuVirtualDisplayId::tryCast(displayId));
@@ -363,18 +364,22 @@ struct HwcDisplayVariant {
.setHwcDisplayId(HWC_DISPLAY_ID)
.setResolution(DisplayVariant::RESOLUTION)
.setActiveConfig(HWC_ACTIVE_CONFIG_ID)
- .setPowerMode(INIT_POWER_MODE)
+ .setPowerMode(kInitPowerMode ? std::make_optional(INIT_POWER_MODE) : std::nullopt)
.inject(&test->mFlinger, test->mComposer);
}
// Called by tests to inject a HWC display setup
+ template <bool kInitPowerMode = true>
static void injectHwcDisplay(DisplayTransactionTest* test) {
- EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
- .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
- Return(Error::NONE)));
- EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
- .WillOnce(Return(Error::NONE));
- injectHwcDisplayWithNoDefaultCapabilities(test);
+ if constexpr (kInitPowerMode) {
+ EXPECT_CALL(*test->mComposer, getDisplayCapabilities(HWC_DISPLAY_ID, _))
+ .WillOnce(DoAll(SetArgPointee<1>(std::vector<DisplayCapability>({})),
+ Return(Error::NONE)));
+
+ EXPECT_CALL(*test->mComposer, setPowerMode(HWC_DISPLAY_ID, INIT_POWER_MODE))
+ .WillOnce(Return(Error::NONE));
+ }
+ injectHwcDisplayWithNoDefaultCapabilities<kInitPowerMode>(test);
}
static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
@@ -434,14 +439,18 @@ struct HwcDisplayVariant {
}
}
+ template <bool kFailedHotplug = false>
static void setupHwcHotplugCallExpectations(DisplayTransactionTest* test) {
- constexpr auto CONNECTION_TYPE =
- PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal
- ? IComposerClient::DisplayConnectionType::INTERNAL
- : IComposerClient::DisplayConnectionType::EXTERNAL;
-
- EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _))
- .WillOnce(DoAll(SetArgPointee<1>(CONNECTION_TYPE), Return(hal::V2_4::Error::NONE)));
+ if constexpr (!kFailedHotplug) {
+ constexpr auto CONNECTION_TYPE =
+ PhysicalDisplay::CONNECTION_TYPE == ui::DisplayConnectionType::Internal
+ ? IComposerClient::DisplayConnectionType::INTERNAL
+ : IComposerClient::DisplayConnectionType::EXTERNAL;
+
+ EXPECT_CALL(*test->mComposer, getDisplayConnectionType(HWC_DISPLAY_ID, _))
+ .WillOnce(DoAll(SetArgPointee<1>(CONNECTION_TYPE),
+ Return(hal::V2_4::Error::NONE)));
+ }
EXPECT_CALL(*test->mComposer, setClientTargetSlotCount(_))
.WillOnce(Return(hal::Error::NONE));
@@ -472,17 +481,16 @@ constexpr uint32_t GRALLOC_USAGE_PHYSICAL_DISPLAY =
constexpr int PHYSICAL_DISPLAY_FLAGS = 0x1;
-template <typename PhysicalDisplay, int width, int height, Critical critical>
+template <typename PhysicalDisplay, int width, int height>
struct PhysicalDisplayVariant
- : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, critical,
- Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
- GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
- HwcDisplayVariant<
- PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
- DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, critical,
- Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
- GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
- PhysicalDisplay> {};
+ : DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height, Async::FALSE,
+ Secure::TRUE, PhysicalDisplay::PRIMARY, GRALLOC_USAGE_PHYSICAL_DISPLAY,
+ PHYSICAL_DISPLAY_FLAGS>,
+ HwcDisplayVariant<PhysicalDisplay::HWC_DISPLAY_ID, DisplayType::PHYSICAL,
+ DisplayVariant<PhysicalDisplayIdType<PhysicalDisplay>, width, height,
+ Async::FALSE, Secure::TRUE, PhysicalDisplay::PRIMARY,
+ GRALLOC_USAGE_PHYSICAL_DISPLAY, PHYSICAL_DISPLAY_FLAGS>,
+ PhysicalDisplay> {};
template <bool hasIdentificationData>
struct PrimaryDisplay {
@@ -494,14 +502,16 @@ struct PrimaryDisplay {
static constexpr auto GET_IDENTIFICATION_DATA = getInternalEdid;
};
-template <bool hasIdentificationData>
-struct ExternalDisplay {
- static constexpr auto CONNECTION_TYPE = ui::DisplayConnectionType::External;
+template <ui::DisplayConnectionType connectionType, bool hasIdentificationData>
+struct SecondaryDisplay {
+ static constexpr auto CONNECTION_TYPE = connectionType;
static constexpr Primary PRIMARY = Primary::FALSE;
static constexpr uint8_t PORT = 254;
static constexpr HWDisplayId HWC_DISPLAY_ID = 1002;
static constexpr bool HAS_IDENTIFICATION_DATA = hasIdentificationData;
- static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid;
+ static constexpr auto GET_IDENTIFICATION_DATA =
+ connectionType == ui::DisplayConnectionType::Internal ? getInternalEdid
+ : getExternalEdid;
};
struct TertiaryDisplay {
@@ -511,15 +521,18 @@ struct TertiaryDisplay {
static constexpr auto GET_IDENTIFICATION_DATA = getExternalEdid;
};
-// A primary display is a physical display that is critical
-using PrimaryDisplayVariant =
- PhysicalDisplayVariant<PrimaryDisplay<false>, 3840, 2160, Critical::TRUE>;
+using PrimaryDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<false>, 3840, 2160>;
+
+using InnerDisplayVariant = PhysicalDisplayVariant<PrimaryDisplay<true>, 1840, 2208>;
+using OuterDisplayVariant =
+ PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::Internal, true>, 1080,
+ 2092>;
-// An external display is physical display that is not critical.
using ExternalDisplayVariant =
- PhysicalDisplayVariant<ExternalDisplay<false>, 1920, 1280, Critical::FALSE>;
+ PhysicalDisplayVariant<SecondaryDisplay<ui::DisplayConnectionType::External, false>, 1920,
+ 1280>;
-using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200, Critical::FALSE>;
+using TertiaryDisplayVariant = PhysicalDisplayVariant<TertiaryDisplay, 1600, 1200>;
// A virtual display not supported by the HWC.
constexpr uint32_t GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY = 0;
@@ -528,12 +541,11 @@ constexpr int VIRTUAL_DISPLAY_FLAGS = 0x0;
template <int width, int height, Secure secure>
struct NonHwcVirtualDisplayVariant
- : DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE, Async::TRUE, secure,
- Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY,
- VIRTUAL_DISPLAY_FLAGS> {
- using Base = DisplayVariant<GpuVirtualDisplayIdType, width, height, Critical::FALSE,
- Async::TRUE, secure, Primary::FALSE,
- GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>;
+ : DisplayVariant<GpuVirtualDisplayIdType, width, height, Async::TRUE, secure, Primary::FALSE,
+ GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS> {
+ using Base = DisplayVariant<GpuVirtualDisplayIdType, width, height, Async::TRUE, secure,
+ Primary::FALSE, GRALLOC_USAGE_NONHWC_VIRTUAL_DISPLAY,
+ VIRTUAL_DISPLAY_FLAGS>;
static void injectHwcDisplay(DisplayTransactionTest*) {}
@@ -575,17 +587,14 @@ constexpr uint32_t GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY = GRALLOC_USAGE_HW_COMPOSER
template <int width, int height, Secure secure>
struct HwcVirtualDisplayVariant
- : DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE, Async::TRUE,
- secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY,
- VIRTUAL_DISPLAY_FLAGS>,
- HwcDisplayVariant<
- HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL,
- DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE,
- Async::TRUE, secure, Primary::FALSE,
- GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>> {
- using Base = DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Critical::FALSE,
- Async::TRUE, secure, Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER,
- VIRTUAL_DISPLAY_FLAGS>;
+ : DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Async::TRUE, secure,
+ Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY, VIRTUAL_DISPLAY_FLAGS>,
+ HwcDisplayVariant<HWC_VIRTUAL_DISPLAY_HWC_DISPLAY_ID, DisplayType::VIRTUAL,
+ DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Async::TRUE,
+ secure, Primary::FALSE, GRALLOC_USAGE_HWC_VIRTUAL_DISPLAY,
+ VIRTUAL_DISPLAY_FLAGS>> {
+ using Base = DisplayVariant<HalVirtualDisplayIdType<42>, width, height, Async::TRUE, secure,
+ Primary::FALSE, GRALLOC_USAGE_HW_COMPOSER, VIRTUAL_DISPLAY_FLAGS>;
using Self = HwcVirtualDisplayVariant<width, height, secure>;
static std::shared_ptr<compositionengine::Display> injectCompositionDisplay(
@@ -675,12 +684,10 @@ struct WideColorNotSupportedVariant {
static constexpr bool WIDE_COLOR_SUPPORTED = false;
static void injectConfigChange(DisplayTransactionTest* test) {
- test->mFlinger.mutableHasWideColorDisplay() = true;
+ test->mFlinger.mutableSupportsWideColor() = true;
}
static void setupComposerCallExpectations(DisplayTransactionTest* test) {
- EXPECT_CALL(*test->mComposer, getColorModes(Display::HWC_DISPLAY_ID, _))
- .WillOnce(DoAll(SetArgPointee<1>(std::vector<ColorMode>()), Return(Error::NONE)));
EXPECT_CALL(*test->mComposer, setColorMode(_, _, _)).Times(0);
}
};
@@ -692,12 +699,11 @@ struct WideColorSupportNotConfiguredVariant {
static constexpr bool WIDE_COLOR_SUPPORTED = false;
static void injectConfigChange(DisplayTransactionTest* test) {
- test->mFlinger.mutableHasWideColorDisplay() = false;
+ test->mFlinger.mutableSupportsWideColor() = false;
test->mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged;
}
static void setupComposerCallExpectations(DisplayTransactionTest* test) {
- EXPECT_CALL(*test->mComposer, getColorModes(_, _)).Times(0);
EXPECT_CALL(*test->mComposer, getRenderIntents(_, _, _)).Times(0);
EXPECT_CALL(*test->mComposer, setColorMode(_, _, _)).Times(0);
}
diff --git a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
index c033af8645..8891c06c75 100644
--- a/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
+++ b/services/surfaceflinger/tests/unittests/EventThreadTest.cpp
@@ -24,18 +24,23 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
+#include <scheduler/VsyncConfig.h>
#include <utils/Errors.h>
#include "AsyncCallRecorder.h"
#include "DisplayHardware/DisplayMode.h"
#include "FrameTimeline.h"
#include "Scheduler/EventThread.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+#include "mock/MockVsyncController.h"
using namespace std::chrono_literals;
using namespace std::placeholders;
using testing::_;
using testing::Invoke;
+using testing::Return;
namespace android {
@@ -50,31 +55,18 @@ constexpr PhysicalDisplayId DISPLAY_ID_64BIT =
constexpr std::chrono::duration VSYNC_PERIOD(16ms);
-class MockVSyncSource : public VSyncSource {
-public:
- const char* getName() const override { return "test"; }
-
- MOCK_METHOD1(setVSyncEnabled, void(bool));
- MOCK_METHOD1(setCallback, void(VSyncSource::Callback*));
- MOCK_METHOD2(setDuration,
- void(std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration));
- MOCK_METHOD1(pauseVsyncCallback, void(bool));
- MOCK_METHOD(VSyncSource::VSyncData, getLatestVSyncData, (), (const, override));
- MOCK_CONST_METHOD1(dump, void(std::string&));
-};
-
} // namespace
-class EventThreadTest : public testing::Test {
+class EventThreadTest : public testing::Test, public IEventThreadCallback {
protected:
+ static constexpr std::chrono::nanoseconds kWorkDuration = 0ms;
+ static constexpr std::chrono::nanoseconds kReadyDuration = 3ms;
+
class MockEventThreadConnection : public EventThreadConnection {
public:
MockEventThreadConnection(impl::EventThread* eventThread, uid_t callingUid,
- ResyncCallback&& resyncCallback,
- ISurfaceComposer::EventRegistrationFlags eventRegistration)
- : EventThreadConnection(eventThread, callingUid, std::move(resyncCallback),
- eventRegistration) {}
+ EventRegistrationFlags eventRegistration)
+ : EventThreadConnection(eventThread, callingUid, eventRegistration) {}
MOCK_METHOD1(postEvent, status_t(const DisplayEventReceiver::Event& event));
};
@@ -84,23 +76,28 @@ protected:
EventThreadTest();
~EventThreadTest() override;
- void createThread(std::unique_ptr<VSyncSource>);
- sp<MockEventThreadConnection> createConnection(
- ConnectionEventRecorder& recorder,
- ISurfaceComposer::EventRegistrationFlags eventRegistration = {},
- uid_t ownerUid = mConnectionUid);
+ void SetUp() override { mVsyncPeriod = VSYNC_PERIOD; }
- void expectVSyncSetEnabledCallReceived(bool expectedState);
+ // IEventThreadCallback overrides
+ bool throttleVsync(TimePoint, uid_t) override;
+ Period getVsyncPeriod(uid_t) override;
+ void resync() override;
+
+ void setupEventThread();
+ sp<MockEventThreadConnection> createConnection(ConnectionEventRecorder& recorder,
+ EventRegistrationFlags eventRegistration = {},
+ uid_t ownerUid = mConnectionUid);
+
+ void expectVSyncCallbackScheduleReceived(bool expectState);
void expectVSyncSetDurationCallReceived(std::chrono::nanoseconds expectedDuration,
std::chrono::nanoseconds expectedReadyDuration);
- VSyncSource::Callback* expectVSyncSetCallbackCallReceived();
- void expectInterceptCallReceived(nsecs_t expectedTimestamp);
void expectVsyncEventReceivedByConnection(const char* name,
ConnectionEventRecorder& connectionEventRecorder,
nsecs_t expectedTimestamp, unsigned expectedCount);
void expectVsyncEventReceivedByConnection(nsecs_t expectedTimestamp, unsigned expectedCount);
- void expectVsyncEventFrameTimelinesCorrect(nsecs_t expectedTimestamp,
- VSyncSource::VSyncData preferredVsyncData);
+ void expectVsyncEventFrameTimelinesCorrect(
+ nsecs_t expectedTimestamp, gui::VsyncEventData::FrameTimeline preferredVsyncData);
+ void expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData);
void expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
bool expectedConnected);
void expectConfigChangedEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
@@ -110,23 +107,38 @@ protected:
void expectUidFrameRateMappingEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
std::vector<FrameRateOverride>);
- AsyncCallRecorder<void (*)(bool)> mVSyncSetEnabledCallRecorder;
- AsyncCallRecorder<void (*)(VSyncSource::Callback*)> mVSyncSetCallbackCallRecorder;
- AsyncCallRecorder<void (*)(std::chrono::nanoseconds, std::chrono::nanoseconds)>
- mVSyncSetDurationCallRecorder;
+ void onVSyncEvent(nsecs_t timestamp, nsecs_t expectedPresentationTime,
+ nsecs_t deadlineTimestamp) {
+ mThread->onVsync(expectedPresentationTime, timestamp, deadlineTimestamp);
+ }
+
+ AsyncCallRecorderWithCannedReturn<
+ scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken,
+ scheduler::VSyncDispatch::ScheduleTiming)>
+ mVSyncCallbackScheduleRecorder{0};
+ AsyncCallRecorderWithCannedReturn<
+ scheduler::ScheduleResult (*)(scheduler::VSyncDispatch::CallbackToken,
+ scheduler::VSyncDispatch::ScheduleTiming)>
+ mVSyncCallbackUpdateRecorder{0};
+ AsyncCallRecorderWithCannedReturn<
+ scheduler::VSyncDispatch::CallbackToken (*)(scheduler::VSyncDispatch::Callback,
+ std::string)>
+ mVSyncCallbackRegisterRecorder{scheduler::VSyncDispatch::CallbackToken(0)};
+ AsyncCallRecorder<void (*)(scheduler::VSyncDispatch::CallbackToken)>
+ mVSyncCallbackUnregisterRecorder;
AsyncCallRecorder<void (*)()> mResyncCallRecorder;
- AsyncCallRecorder<void (*)(nsecs_t)> mInterceptVSyncCallRecorder;
AsyncCallRecorder<void (*)(nsecs_t, uid_t)> mThrottleVsyncCallRecorder;
ConnectionEventRecorder mConnectionEventCallRecorder{0};
ConnectionEventRecorder mThrottledConnectionEventCallRecorder{0};
- MockVSyncSource* mVSyncSource;
- VSyncSource::Callback* mCallback = nullptr;
+ std::shared_ptr<scheduler::VsyncSchedule> mVsyncSchedule;
std::unique_ptr<impl::EventThread> mThread;
sp<MockEventThreadConnection> mConnection;
sp<MockEventThreadConnection> mThrottledConnection;
std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
+ std::chrono::nanoseconds mVsyncPeriod;
+
static constexpr uid_t mConnectionUid = 443;
static constexpr uid_t mThrottledConnectionUid = 177;
};
@@ -136,29 +148,20 @@ EventThreadTest::EventThreadTest() {
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- auto vsyncSource = std::make_unique<MockVSyncSource>();
- mVSyncSource = vsyncSource.get();
-
- EXPECT_CALL(*mVSyncSource, setVSyncEnabled(_))
- .WillRepeatedly(Invoke(mVSyncSetEnabledCallRecorder.getInvocable()));
-
- EXPECT_CALL(*mVSyncSource, setCallback(_))
- .WillRepeatedly(Invoke(mVSyncSetCallbackCallRecorder.getInvocable()));
-
- EXPECT_CALL(*mVSyncSource, setDuration(_, _))
- .WillRepeatedly(Invoke(mVSyncSetDurationCallRecorder.getInvocable()));
-
- createThread(std::move(vsyncSource));
- mConnection = createConnection(mConnectionEventCallRecorder,
- ISurfaceComposer::EventRegistration::modeChanged |
- ISurfaceComposer::EventRegistration::frameRateOverride);
- mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder,
- ISurfaceComposer::EventRegistration::modeChanged,
- mThrottledConnectionUid);
-
- // A display must be connected for VSYNC events to be delivered.
- mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
- expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
+ auto mockDispatchPtr = std::make_shared<mock::VSyncDispatch>();
+ mVsyncSchedule = std::shared_ptr<scheduler::VsyncSchedule>(
+ new scheduler::VsyncSchedule(INTERNAL_DISPLAY_ID,
+ std::make_shared<mock::VSyncTracker>(), mockDispatchPtr,
+ nullptr));
+ mock::VSyncDispatch& mockDispatch = *mockDispatchPtr;
+ EXPECT_CALL(mockDispatch, registerCallback(_, _))
+ .WillRepeatedly(Invoke(mVSyncCallbackRegisterRecorder.getInvocable()));
+ EXPECT_CALL(mockDispatch, schedule(_, _))
+ .WillRepeatedly(Invoke(mVSyncCallbackScheduleRecorder.getInvocable()));
+ EXPECT_CALL(mockDispatch, update(_, _))
+ .WillRepeatedly(Invoke(mVSyncCallbackUpdateRecorder.getInvocable()));
+ EXPECT_CALL(mockDispatch, unregisterCallback(_))
+ .WillRepeatedly(Invoke(mVSyncCallbackUnregisterRecorder.getInvocable()));
}
EventThreadTest::~EventThreadTest() {
@@ -166,62 +169,69 @@ EventThreadTest::~EventThreadTest() {
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
+ mThread.reset();
// EventThread should unregister itself as VSyncSource callback.
- EXPECT_TRUE(!mVSyncSetCallbackCallRecorder.waitForUnexpectedCall().has_value());
+ EXPECT_TRUE(mVSyncCallbackUnregisterRecorder.waitForCall().has_value());
}
-void EventThreadTest::createThread(std::unique_ptr<VSyncSource> source) {
- const auto throttleVsync = [&](nsecs_t expectedVsyncTimestamp, uid_t uid) {
- mThrottleVsyncCallRecorder.getInvocable()(expectedVsyncTimestamp, uid);
- return (uid == mThrottledConnectionUid);
- };
- const auto getVsyncPeriod = [](uid_t uid) {
- return VSYNC_PERIOD.count();
- };
+bool EventThreadTest::throttleVsync(android::TimePoint expectedVsyncTimestamp, uid_t uid) {
+ mThrottleVsyncCallRecorder.recordCall(expectedVsyncTimestamp.ns(), uid);
+ return (uid == mThrottledConnectionUid);
+}
+
+Period EventThreadTest::getVsyncPeriod(uid_t) {
+ return mVsyncPeriod;
+}
+
+void EventThreadTest::resync() {
+ mResyncCallRecorder.recordCall();
+}
+void EventThreadTest::setupEventThread() {
mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
- mThread = std::make_unique<impl::EventThread>(std::move(source), mTokenManager.get(),
- mInterceptVSyncCallRecorder.getInvocable(),
- throttleVsync, getVsyncPeriod);
+ mThread = std::make_unique<impl::EventThread>("EventThreadTest", mVsyncSchedule,
+ mTokenManager.get(), *this, kWorkDuration,
+ kReadyDuration);
// EventThread should register itself as VSyncSource callback.
- mCallback = expectVSyncSetCallbackCallReceived();
- ASSERT_TRUE(mCallback);
+ EXPECT_TRUE(mVSyncCallbackRegisterRecorder.waitForCall().has_value());
+
+ mConnection =
+ createConnection(mConnectionEventCallRecorder,
+ gui::ISurfaceComposer::EventRegistration::modeChanged |
+ gui::ISurfaceComposer::EventRegistration::frameRateOverride);
+ mThrottledConnection = createConnection(mThrottledConnectionEventCallRecorder,
+ gui::ISurfaceComposer::EventRegistration::modeChanged,
+ mThrottledConnectionUid);
+
+ // A display must be connected for VSYNC events to be delivered.
+ mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
+ expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
}
sp<EventThreadTest::MockEventThreadConnection> EventThreadTest::createConnection(
- ConnectionEventRecorder& recorder,
- ISurfaceComposer::EventRegistrationFlags eventRegistration, uid_t ownerUid) {
+ ConnectionEventRecorder& recorder, EventRegistrationFlags eventRegistration,
+ uid_t ownerUid) {
sp<MockEventThreadConnection> connection =
- new MockEventThreadConnection(mThread.get(), ownerUid,
- mResyncCallRecorder.getInvocable(), eventRegistration);
+ sp<MockEventThreadConnection>::make(mThread.get(), ownerUid, eventRegistration);
EXPECT_CALL(*connection, postEvent(_)).WillRepeatedly(Invoke(recorder.getInvocable()));
return connection;
}
-void EventThreadTest::expectVSyncSetEnabledCallReceived(bool expectedState) {
- auto args = mVSyncSetEnabledCallRecorder.waitForCall();
- ASSERT_TRUE(args.has_value());
- EXPECT_EQ(expectedState, std::get<0>(args.value()));
+void EventThreadTest::expectVSyncCallbackScheduleReceived(bool expectState) {
+ if (expectState) {
+ ASSERT_TRUE(mVSyncCallbackScheduleRecorder.waitForCall().has_value());
+ } else {
+ ASSERT_FALSE(mVSyncCallbackScheduleRecorder.waitForUnexpectedCall().has_value());
+ }
}
void EventThreadTest::expectVSyncSetDurationCallReceived(
std::chrono::nanoseconds expectedDuration, std::chrono::nanoseconds expectedReadyDuration) {
- auto args = mVSyncSetDurationCallRecorder.waitForCall();
- ASSERT_TRUE(args.has_value());
- EXPECT_EQ(expectedDuration, std::get<0>(args.value()));
- EXPECT_EQ(expectedReadyDuration, std::get<1>(args.value()));
-}
-
-VSyncSource::Callback* EventThreadTest::expectVSyncSetCallbackCallReceived() {
- auto callbackSet = mVSyncSetCallbackCallRecorder.waitForCall();
- return callbackSet.has_value() ? std::get<0>(callbackSet.value()) : nullptr;
-}
-
-void EventThreadTest::expectInterceptCallReceived(nsecs_t expectedTimestamp) {
- auto args = mInterceptVSyncCallRecorder.waitForCall();
+ auto args = mVSyncCallbackUpdateRecorder.waitForCall();
ASSERT_TRUE(args.has_value());
- EXPECT_EQ(expectedTimestamp, std::get<0>(args.value()));
+ EXPECT_EQ(expectedDuration.count(), std::get<1>(args.value()).workDuration);
+ EXPECT_EQ(expectedReadyDuration.count(), std::get<1>(args.value()).readyDuration);
}
void EventThreadTest::expectThrottleVsyncReceived(nsecs_t expectedTimestamp, uid_t uid) {
@@ -254,12 +264,12 @@ void EventThreadTest::expectVsyncEventReceivedByConnection(nsecs_t expectedTimes
}
void EventThreadTest::expectVsyncEventFrameTimelinesCorrect(
- nsecs_t expectedTimestamp, VSyncSource::VSyncData preferredVsyncData) {
+ nsecs_t expectedTimestamp, VsyncEventData::FrameTimeline preferredVsyncData) {
auto args = mConnectionEventCallRecorder.waitForCall();
ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
<< expectedTimestamp;
const auto& event = std::get<0>(args.value());
- for (int i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+ for (int i = 0; i < event.vsync.vsyncData.frameTimelinesLength; i++) {
auto prediction = mTokenManager->getPredictionsForToken(
event.vsync.vsyncData.frameTimelines[i].vsyncId);
EXPECT_TRUE(prediction.has_value());
@@ -293,6 +303,20 @@ void EventThreadTest::expectVsyncEventFrameTimelinesCorrect(
}
}
+void EventThreadTest::expectVsyncEventDataFrameTimelinesValidLength(VsyncEventData vsyncEventData) {
+ float nonPreferredTimelinesAmount =
+ scheduler::VsyncConfig::kEarlyLatchMaxThreshold / mVsyncPeriod;
+ EXPECT_LE(vsyncEventData.frameTimelinesLength, nonPreferredTimelinesAmount + 1)
+ << "Amount of non-preferred frame timelines too many;"
+ << " expected presentation time will be over threshold";
+ EXPECT_LT(nonPreferredTimelinesAmount, VsyncEventData::kFrameTimelinesCapacity)
+ << "Amount of non-preferred frame timelines should be less than max capacity";
+ EXPECT_GT(static_cast<int64_t>(vsyncEventData.frameTimelinesLength), 0)
+ << "Frame timelines length should be greater than 0";
+ EXPECT_LT(vsyncEventData.preferredFrameTimelineIndex, vsyncEventData.frameTimelinesLength)
+ << "Preferred frame timeline index should be less than frame timelines length";
+}
+
void EventThreadTest::expectHotplugEventReceivedByConnection(PhysicalDisplayId expectedDisplayId,
bool expectedConnected) {
auto args = mConnectionEventCallRecorder.waitForCall();
@@ -343,15 +367,19 @@ using namespace testing;
*/
TEST_F(EventThreadTest, canCreateAndDestroyThreadWithNoEventsSent) {
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
- EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value());
- EXPECT_FALSE(mVSyncSetDurationCallRecorder.waitForCall(0us).has_value());
+ setupEventThread();
+
+ EXPECT_FALSE(mVSyncCallbackRegisterRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackScheduleRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackUpdateRecorder.waitForCall(0us).has_value());
+ EXPECT_FALSE(mVSyncCallbackUnregisterRecorder.waitForCall(0us).has_value());
EXPECT_FALSE(mResyncCallRecorder.waitForCall(0us).has_value());
- EXPECT_FALSE(mInterceptVSyncCallRecorder.waitForCall(0us).has_value());
EXPECT_FALSE(mConnectionEventCallRecorder.waitForCall(0us).has_value());
}
TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) {
+ setupEventThread();
+
mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
@@ -359,69 +387,102 @@ TEST_F(EventThreadTest, vsyncRequestIsIgnoredIfDisplayIsDisconnected) {
mThread->requestNextVsync(mConnection);
// EventThread should not enable vsync callbacks.
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, requestNextVsyncPostsASingleVSyncEventToTheConnection) {
+ setupEventThread();
+
// Signal that we want the next vsync event to be posted to the connection
mThread->requestNextVsync(mConnection);
// EventThread should immediately request a resync.
EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
- // EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ // EventThread should enable schedule a vsync callback
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a first vsync event.
- // The interceptor should receive the event, as well as the connection.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // The throttler should receive the event, as well as the connection.
+ onVSyncEvent(123, 456, 789);
expectThrottleVsyncReceived(456, mConnectionUid);
expectVsyncEventReceivedByConnection(123, 1u);
+ // EventThread is requesting one more callback due to VsyncRequest::SingleSuppressCallback
+ expectVSyncCallbackScheduleReceived(true);
+
// Use the received callback to signal a second vsync event.
- // The interceptor should receive the event, but the connection should
+ // The throttler should receive the event, but the connection should
// not as it was only interested in the first.
- mCallback->onVSyncEvent(456, {123, 0});
- expectInterceptCallReceived(456);
+ onVSyncEvent(456, 123, 0);
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
// EventThread should also detect that at this point that it does not need
// any more vsync events, and should disable their generation.
- expectVSyncSetEnabledCallReceived(false);
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesCorrect) {
+ setupEventThread();
+
// Signal that we want the next vsync event to be posted to the connection
mThread->requestNextVsync(mConnection);
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a vsync event.
- // The interceptor should receive the event, as well as the connection.
- VSyncSource::VSyncData vsyncData = {456, 789};
- mCallback->onVSyncEvent(123, vsyncData);
- expectInterceptCallReceived(123);
- expectVsyncEventFrameTimelinesCorrect(123, vsyncData);
+ // The throttler should receive the event, as well as the connection.
+ onVSyncEvent(123, 456, 789);
+ expectVsyncEventFrameTimelinesCorrect(123, {-1, 789, 456});
+}
+
+TEST_F(EventThreadTest, requestNextVsyncEventFrameTimelinesValidLength) {
+ setupEventThread();
+ // The VsyncEventData should not have kFrameTimelinesCapacity amount of valid frame timelines,
+ // due to longer vsync period and kEarlyLatchMaxThreshold. Use length-2 to avoid decimal
+ // truncation (e.g. 60Hz has 16.6... ms vsync period).
+ mVsyncPeriod = (scheduler::VsyncConfig::kEarlyLatchMaxThreshold /
+ (VsyncEventData::kFrameTimelinesCapacity - 2));
+
+ // Signal that we want the next vsync event to be posted to the connection
+ mThread->requestNextVsync(mConnection);
+
+ expectVSyncCallbackScheduleReceived(true);
+
+ // Use the received callback to signal a vsync event.
+ // The throttler should receive the event, as well as the connection.
+ nsecs_t expectedTimestamp = 123;
+ onVSyncEvent(expectedTimestamp, 456, 789);
+
+ auto args = mConnectionEventCallRecorder.waitForCall();
+ ASSERT_TRUE(args.has_value()) << " did not receive an event for timestamp "
+ << expectedTimestamp;
+ const VsyncEventData vsyncEventData = std::get<0>(args.value()).vsync.vsyncData;
+ expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
}
TEST_F(EventThreadTest, getLatestVsyncEventData) {
+ setupEventThread();
+
const nsecs_t now = systemTime();
- const nsecs_t preferredDeadline = now + 10000000;
const nsecs_t preferredExpectedPresentationTime = now + 20000000;
- const VSyncSource::VSyncData preferredData = {preferredExpectedPresentationTime,
- preferredDeadline};
- EXPECT_CALL(*mVSyncSource, getLatestVSyncData()).WillOnce(Return(preferredData));
+ const nsecs_t preferredDeadline = preferredExpectedPresentationTime - kReadyDuration.count();
+
+ mock::VSyncTracker& mockTracker =
+ *static_cast<mock::VSyncTracker*>(&mVsyncSchedule->getTracker());
+ EXPECT_CALL(mockTracker, nextAnticipatedVSyncTimeFrom(_))
+ .WillOnce(Return(preferredExpectedPresentationTime));
VsyncEventData vsyncEventData = mThread->getLatestVsyncEventData(mConnection);
// Check EventThread immediately requested a resync.
EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
+ expectVsyncEventDataFrameTimelinesValidLength(vsyncEventData);
EXPECT_GT(vsyncEventData.frameTimelines[0].deadlineTimestamp, now)
<< "Deadline timestamp should be greater than frame time";
- for (size_t i = 0; i < VsyncEventData::kFrameTimelinesLength; i++) {
+ for (size_t i = 0; i < vsyncEventData.frameTimelinesLength; i++) {
auto prediction =
mTokenManager->getPredictionsForToken(vsyncEventData.frameTimelines[i].vsyncId);
EXPECT_TRUE(prediction.has_value());
@@ -457,14 +518,15 @@ TEST_F(EventThreadTest, getLatestVsyncEventData) {
}
TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
+ setupEventThread();
+
// Create a first connection, register it, and request a vsync rate of zero.
ConnectionEventRecorder firstConnectionEventRecorder{0};
sp<MockEventThreadConnection> firstConnection = createConnection(firstConnectionEventRecorder);
mThread->setVsyncRate(0, firstConnection);
// By itself, this should not enable vsync events
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
- EXPECT_FALSE(mVSyncSetCallbackCallRecorder.waitForCall(0us).has_value());
+ expectVSyncCallbackScheduleReceived(false);
// However if there is another connection which wants events at a nonzero rate.....
ConnectionEventRecorder secondConnectionEventRecorder{0};
@@ -473,223 +535,220 @@ TEST_F(EventThreadTest, setVsyncRateZeroPostsNoVSyncEventsToThatConnection) {
mThread->setVsyncRate(1, secondConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Send a vsync event. EventThread should then make a call to the
- // interceptor, and the second connection. The first connection should not
+ // the second connection. The first connection should not
// get the event.
- mCallback->onVSyncEvent(123, {456, 0});
- expectInterceptCallReceived(123);
+ onVSyncEvent(123, 0456, 0);
EXPECT_FALSE(firstConnectionEventRecorder.waitForUnexpectedCall().has_value());
expectVsyncEventReceivedByConnection("secondConnection", secondConnectionEventRecorder, 123,
1u);
}
TEST_F(EventThreadTest, setVsyncRateOnePostsAllEventsToThatConnection) {
+ setupEventThread();
+
mThread->setVsyncRate(1, mConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Send a vsync event. EventThread should then make a call to the
- // interceptor, and the connection.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // throttler, and the connection.
+ onVSyncEvent(123, 456, 789);
expectThrottleVsyncReceived(456, mConnectionUid);
expectVsyncEventReceivedByConnection(123, 1u);
// A second event should go to the same places.
- mCallback->onVSyncEvent(456, {123, 0});
- expectInterceptCallReceived(456);
+ onVSyncEvent(456, 123, 0);
expectThrottleVsyncReceived(123, mConnectionUid);
expectVsyncEventReceivedByConnection(456, 2u);
// A third event should go to the same places.
- mCallback->onVSyncEvent(789, {777, 111});
- expectInterceptCallReceived(789);
+ onVSyncEvent(789, 777, 111);
expectThrottleVsyncReceived(777, mConnectionUid);
expectVsyncEventReceivedByConnection(789, 3u);
}
TEST_F(EventThreadTest, setVsyncRateTwoPostsEveryOtherEventToThatConnection) {
+ setupEventThread();
+
mThread->setVsyncRate(2, mConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
- // The first event will be seen by the interceptor, and not the connection.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // The first event will not be seen by the connection.
+ onVSyncEvent(123, 456, 789);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
- // The second event will be seen by the interceptor and the connection.
- mCallback->onVSyncEvent(456, {123, 0});
- expectInterceptCallReceived(456);
+ // The second event will be seen by the connection.
+ onVSyncEvent(456, 123, 0);
expectVsyncEventReceivedByConnection(456, 2u);
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
- // The third event will be seen by the interceptor, and not the connection.
- mCallback->onVSyncEvent(789, {777, 744});
- expectInterceptCallReceived(789);
+ // The third event will not be seen by the connection.
+ onVSyncEvent(789, 777, 744);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
EXPECT_FALSE(mThrottleVsyncCallRecorder.waitForUnexpectedCall().has_value());
- // The fourth event will be seen by the interceptor and the connection.
- mCallback->onVSyncEvent(101112, {7847, 86});
- expectInterceptCallReceived(101112);
+ // The fourth event will be seen by the connection.
+ onVSyncEvent(101112, 7847, 86);
expectVsyncEventReceivedByConnection(101112, 4u);
}
TEST_F(EventThreadTest, connectionsRemovedIfInstanceDestroyed) {
+ setupEventThread();
+
mThread->setVsyncRate(1, mConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Destroy the only (strong) reference to the connection.
mConnection = nullptr;
- // The first event will be seen by the interceptor, and not the connection.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // The first event will not be seen by the connection.
+ onVSyncEvent(123, 56, 789);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
// EventThread should disable vsync callbacks
- expectVSyncSetEnabledCallReceived(false);
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, connectionsRemovedIfEventDeliveryError) {
+ setupEventThread();
+
ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
mThread->setVsyncRate(1, errorConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
- // The first event will be seen by the interceptor, and by the connection,
- // which then returns an error.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // The first event will be seen by the connection, which then returns an error.
+ onVSyncEvent(123, 456, 789);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
- // A subsequent event will be seen by the interceptor and not by the
- // connection.
- mCallback->onVSyncEvent(456, {123, 0});
- expectInterceptCallReceived(456);
+ // Another schedule is expected, since the connection is removed only after
+ // the next vsync is requested.
+ expectVSyncCallbackScheduleReceived(true);
+
+ // A subsequent event will not be seen by the connection.
+ onVSyncEvent(456, 123, 0);
EXPECT_FALSE(errorConnectionEventRecorder.waitForUnexpectedCall().has_value());
// EventThread should disable vsync callbacks with the second event
- expectVSyncSetEnabledCallReceived(false);
-}
-
-TEST_F(EventThreadTest, tracksEventConnections) {
- EXPECT_EQ(2, mThread->getEventThreadConnectionCount());
- ConnectionEventRecorder errorConnectionEventRecorder{NO_MEMORY};
- sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
- mThread->setVsyncRate(1, errorConnection);
- EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
- ConnectionEventRecorder secondConnectionEventRecorder{0};
- sp<MockEventThreadConnection> secondConnection =
- createConnection(secondConnectionEventRecorder);
- mThread->setVsyncRate(1, secondConnection);
- EXPECT_EQ(4, mThread->getEventThreadConnectionCount());
-
- // EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
-
- // The first event will be seen by the interceptor, and by the connection,
- // which then returns an error.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
- expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
- expectVsyncEventReceivedByConnection("successConnection", secondConnectionEventRecorder, 123,
- 1u);
- EXPECT_EQ(3, mThread->getEventThreadConnectionCount());
+ expectVSyncCallbackScheduleReceived(false);
}
TEST_F(EventThreadTest, eventsDroppedIfNonfatalEventDeliveryError) {
+ setupEventThread();
+
ConnectionEventRecorder errorConnectionEventRecorder{WOULD_BLOCK};
sp<MockEventThreadConnection> errorConnection = createConnection(errorConnectionEventRecorder);
mThread->setVsyncRate(1, errorConnection);
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
- // The first event will be seen by the interceptor, and by the connection,
- // which then returns an non-fatal error.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // The first event will be seen by the connection, which then returns a non-fatal error.
+ onVSyncEvent(123, 456, 789);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 123, 1u);
+ expectVSyncCallbackScheduleReceived(true);
- // A subsequent event will be seen by the interceptor, and by the connection,
- // which still then returns an non-fatal error.
- mCallback->onVSyncEvent(456, {123, 0});
- expectInterceptCallReceived(456);
+ // A subsequent event will be seen by the connection, which still then returns a non-fatal
+ // error.
+ onVSyncEvent(456, 123, 0);
expectVsyncEventReceivedByConnection("errorConnection", errorConnectionEventRecorder, 456, 2u);
+ expectVSyncCallbackScheduleReceived(true);
// EventThread will not disable vsync callbacks as the errors are non-fatal.
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+ onVSyncEvent(456, 123, 0);
+ expectVSyncCallbackScheduleReceived(true);
}
TEST_F(EventThreadTest, setPhaseOffsetForwardsToVSyncSource) {
+ setupEventThread();
+
mThread->setDuration(321ns, 456ns);
expectVSyncSetDurationCallReceived(321ns, 456ns);
}
TEST_F(EventThreadTest, postHotplugInternalDisconnect) {
+ setupEventThread();
+
mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, false);
expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, false);
}
TEST_F(EventThreadTest, postHotplugInternalConnect) {
+ setupEventThread();
+
mThread->onHotplugReceived(INTERNAL_DISPLAY_ID, true);
expectHotplugEventReceivedByConnection(INTERNAL_DISPLAY_ID, true);
}
TEST_F(EventThreadTest, postHotplugExternalDisconnect) {
+ setupEventThread();
+
mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, false);
expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, false);
}
TEST_F(EventThreadTest, postHotplugExternalConnect) {
+ setupEventThread();
+
mThread->onHotplugReceived(EXTERNAL_DISPLAY_ID, true);
expectHotplugEventReceivedByConnection(EXTERNAL_DISPLAY_ID, true);
}
TEST_F(EventThreadTest, postConfigChangedPrimary) {
+ setupEventThread();
+
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setPhysicalDisplayId(INTERNAL_DISPLAY_ID)
.setId(DisplayModeId(7))
.setVsyncPeriod(16666666)
.build();
+ const Fps fps = mode->getPeakFps() / 2;
- mThread->onModeChanged(mode);
- expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, 16666666);
+ mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+ expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 7, fps.getPeriodNsecs());
}
TEST_F(EventThreadTest, postConfigChangedExternal) {
+ setupEventThread();
+
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setPhysicalDisplayId(EXTERNAL_DISPLAY_ID)
.setId(DisplayModeId(5))
.setVsyncPeriod(16666666)
.build();
+ const Fps fps = mode->getPeakFps() / 2;
- mThread->onModeChanged(mode);
- expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, 16666666);
+ mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+ expectConfigChangedEventReceivedByConnection(EXTERNAL_DISPLAY_ID, 5, fps.getPeriodNsecs());
}
TEST_F(EventThreadTest, postConfigChangedPrimary64bit) {
+ setupEventThread();
+
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setPhysicalDisplayId(DISPLAY_ID_64BIT)
.setId(DisplayModeId(7))
.setVsyncPeriod(16666666)
.build();
- mThread->onModeChanged(mode);
- expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, 16666666);
+ const Fps fps = mode->getPeakFps() / 2;
+ mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+ expectConfigChangedEventReceivedByConnection(DISPLAY_ID_64BIT, 7, fps.getPeriodNsecs());
}
TEST_F(EventThreadTest, suppressConfigChanged) {
+ setupEventThread();
+
ConnectionEventRecorder suppressConnectionEventRecorder{0};
sp<MockEventThreadConnection> suppressConnection =
createConnection(suppressConnectionEventRecorder);
@@ -699,15 +758,18 @@ TEST_F(EventThreadTest, suppressConfigChanged) {
.setId(DisplayModeId(9))
.setVsyncPeriod(16666666)
.build();
+ const Fps fps = mode->getPeakFps() / 2;
- mThread->onModeChanged(mode);
- expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, 16666666);
+ mThread->onModeChanged({fps, ftl::as_non_null(mode)});
+ expectConfigChangedEventReceivedByConnection(INTERNAL_DISPLAY_ID, 9, fps.getPeriodNsecs());
auto args = suppressConnectionEventRecorder.waitForCall();
ASSERT_FALSE(args.has_value());
}
TEST_F(EventThreadTest, postUidFrameRateMapping) {
+ setupEventThread();
+
const std::vector<FrameRateOverride> overrides = {
{.uid = 1, .frameRateHz = 20},
{.uid = 3, .frameRateHz = 40},
@@ -719,6 +781,8 @@ TEST_F(EventThreadTest, postUidFrameRateMapping) {
}
TEST_F(EventThreadTest, suppressUidFrameRateMapping) {
+ setupEventThread();
+
const std::vector<FrameRateOverride> overrides = {
{.uid = 1, .frameRateHz = 20},
{.uid = 3, .frameRateHz = 40},
@@ -737,6 +801,8 @@ TEST_F(EventThreadTest, suppressUidFrameRateMapping) {
}
TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
+ setupEventThread();
+
// Signal that we want the next vsync event to be posted to the throttled connection
mThread->requestNextVsync(mThrottledConnection);
@@ -744,26 +810,27 @@ TEST_F(EventThreadTest, requestNextVsyncWithThrottleVsyncDoesntPostVSync) {
EXPECT_TRUE(mResyncCallRecorder.waitForCall().has_value());
// EventThread should enable vsync callbacks.
- expectVSyncSetEnabledCallReceived(true);
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a first vsync event.
- // The interceptor should receive the event, but not the connection.
- mCallback->onVSyncEvent(123, {456, 789});
- expectInterceptCallReceived(123);
+ // The throttler should receive the event, but not the connection.
+ onVSyncEvent(123, 456, 789);
expectThrottleVsyncReceived(456, mThrottledConnectionUid);
mThrottledConnectionEventCallRecorder.waitForUnexpectedCall();
+ expectVSyncCallbackScheduleReceived(true);
// Use the received callback to signal a second vsync event.
- // The interceptor should receive the event, but the connection should
+ // The throttler should receive the event, but the connection should
// not as it was only interested in the first.
- mCallback->onVSyncEvent(456, {123, 0});
- expectInterceptCallReceived(456);
+ onVSyncEvent(456, 123, 0);
expectThrottleVsyncReceived(123, mThrottledConnectionUid);
EXPECT_FALSE(mConnectionEventCallRecorder.waitForUnexpectedCall().has_value());
+ expectVSyncCallbackScheduleReceived(true);
// EventThread should not change the vsync state as it didn't send the event
// yet
- EXPECT_FALSE(mVSyncSetEnabledCallRecorder.waitForUnexpectedCall().has_value());
+ onVSyncEvent(456, 123, 0);
+ expectVSyncCallbackScheduleReceived(true);
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.h
new file mode 100644
index 0000000000..6e4bf2b06e
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FakeDisplayInjector.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 <gmock/gmock.h>
+
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+namespace android {
+
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+using android::hardware::graphics::composer::hal::HWDisplayId;
+using android::Hwc2::mock::PowerAdvisor;
+
+struct FakeDisplayInjectorArgs {
+ PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(255u);
+ HWDisplayId hwcDisplayId = 0;
+ bool isPrimary = true;
+};
+
+class FakeDisplayInjector {
+public:
+ FakeDisplayInjector(TestableSurfaceFlinger& flinger, Hwc2::mock::PowerAdvisor& powerAdvisor,
+ sp<mock::NativeWindow> nativeWindow)
+ : mFlinger(flinger), mPowerAdvisor(powerAdvisor), mNativeWindow(nativeWindow) {}
+
+ sp<DisplayDevice> injectInternalDisplay(
+ const std::function<void(FakeDisplayDeviceInjector&)>& injectExtra,
+ FakeDisplayInjectorArgs args = {}) {
+ using testing::_;
+ using testing::AnyNumber;
+ using testing::DoAll;
+ using testing::Mock;
+ using testing::Return;
+ using testing::SetArgPointee;
+
+ constexpr ui::Size kResolution = {1080, 1920};
+
+ // The DisplayDevice is required to have a framebuffer (behind the
+ // ANativeWindow interface) which uses the actual hardware display
+ // size.
+ EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_WIDTH, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getWidth()), Return(0)));
+ EXPECT_CALL(*mNativeWindow, query(NATIVE_WINDOW_HEIGHT, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(kResolution.getHeight()), Return(0)));
+ EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_FORMAT));
+ EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_CONNECT));
+ EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_SET_USAGE64));
+ EXPECT_CALL(*mNativeWindow, perform(NATIVE_WINDOW_API_DISCONNECT)).Times(AnyNumber());
+
+ auto compositionDisplay = compositionengine::impl::
+ createDisplay(mFlinger.getCompositionEngine(),
+ compositionengine::DisplayCreationArgsBuilder()
+ .setId(args.displayId)
+ .setPixels(kResolution)
+ .setPowerAdvisor(&mPowerAdvisor)
+ .build());
+
+ auto injector = FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+ ui::DisplayConnectionType::Internal,
+ args.hwcDisplayId, args.isPrimary);
+
+ injector.setNativeWindow(mNativeWindow);
+ if (injectExtra) {
+ injectExtra(injector);
+ }
+
+ auto displayDevice = injector.inject();
+
+ Mock::VerifyAndClear(mNativeWindow.get());
+
+ return displayDevice;
+ }
+
+ TestableSurfaceFlinger& mFlinger;
+ Hwc2::mock::PowerAdvisor& mPowerAdvisor;
+ sp<mock::NativeWindow> mNativeWindow;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
index 0905cd14b1..0c820fb291 100644
--- a/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FlagManagerTest.cpp
@@ -14,130 +14,174 @@
* limitations under the License.
*/
-#include <cstdint>
#undef LOG_TAG
#define LOG_TAG "FlagManagerTest"
-#include "FlagManager.h"
+#include <common/FlagManager.h>
+#include <common/test/FlagUtils.h>
-#include <android-base/properties.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
-#include <server_configurable_flags/get_flags.h>
-#include <optional>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
namespace android {
using testing::Return;
-class MockFlagManager : public FlagManager {
+class TestableFlagManager : public FlagManager {
public:
- MockFlagManager() = default;
- ~MockFlagManager() = default;
+ TestableFlagManager() : FlagManager(ConstructorTag{}) { markBootCompleted(); }
+ ~TestableFlagManager() = default;
+
+ MOCK_METHOD(std::optional<bool>, getBoolProperty, (const char*), (const, override));
+ MOCK_METHOD(bool, getServerConfigurableFlag, (const char*), (const, override));
- MOCK_METHOD(std::string, getServerConfigurableFlag, (const std::string& experimentFlagName),
- (const, override));
+ void markBootIncomplete() { mBootCompleted = false; }
};
class FlagManagerTest : public testing::Test {
public:
- FlagManagerTest();
- ~FlagManagerTest() override;
- std::unique_ptr<MockFlagManager> mFlagManager;
-
- template <typename T>
- T getValue(const std::string& experimentFlagName, std::optional<T> systemPropertyOpt,
- T defaultValue);
+ FlagManagerTest() {
+ 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());
+ }
+ ~FlagManagerTest() override {
+ 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());
+ }
+
+ TestableFlagManager mFlagManager;
};
-FlagManagerTest::FlagManagerTest() {
- 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());
- mFlagManager = std::make_unique<MockFlagManager>();
+TEST_F(FlagManagerTest, isSingleton) {
+ EXPECT_EQ(&FlagManager::getInstance(), &FlagManager::getInstance());
}
-FlagManagerTest::~FlagManagerTest() {
- 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());
+TEST_F(FlagManagerTest, legacyCreashesIfQueriedBeforeBoot) {
+ mFlagManager.markBootIncomplete();
+ EXPECT_DEATH(FlagManager::getInstance().test_flag(), "");
}
-template <typename T>
-T FlagManagerTest::getValue(const std::string& experimentFlagName,
- std::optional<T> systemPropertyOpt, T defaultValue) {
- return mFlagManager->getValue(experimentFlagName, systemPropertyOpt, defaultValue);
+TEST_F(FlagManagerTest, legacyReturnsOverride) {
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
+ EXPECT_EQ(true, mFlagManager.test_flag());
+
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(false));
+ EXPECT_EQ(false, mFlagManager.test_flag());
}
-namespace {
-TEST_F(FlagManagerTest, getValue_bool_default) {
- EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
- const bool defaultValue = false;
- std::optional<bool> systemPropertyValue = std::nullopt;
- const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, defaultValue);
+TEST_F(FlagManagerTest, legacyReturnsValue) {
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+ EXPECT_CALL(mFlagManager, getServerConfigurableFlag).WillOnce(Return(true));
+ EXPECT_EQ(true, mFlagManager.test_flag());
+
+ EXPECT_CALL(mFlagManager, getServerConfigurableFlag).WillOnce(Return(false));
+ EXPECT_EQ(false, mFlagManager.test_flag());
}
-TEST_F(FlagManagerTest, getValue_bool_sysprop) {
- const bool defaultValue = false;
- std::optional<bool> systemPropertyValue = std::make_optional(true);
- const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, true);
+TEST_F(FlagManagerTest, creashesIfQueriedBeforeBoot) {
+ mFlagManager.markBootIncomplete();
+ EXPECT_DEATH(FlagManager::getInstance().late_boot_misc2(), "");
}
-TEST_F(FlagManagerTest, getValue_bool_experiment) {
- EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("1"));
- const bool defaultValue = false;
- std::optional<bool> systemPropertyValue = std::nullopt;
- const bool result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, true);
+TEST_F(FlagManagerTest, returnsOverrideTrue) {
+ mFlagManager.markBootCompleted();
+
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false);
+
+ // This is stored in a static variable, so this test depends on the fact
+ // that this flag has not been read in this process.
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
+ EXPECT_TRUE(mFlagManager.late_boot_misc2());
+
+ // Further calls will not result in further calls to getBoolProperty.
+ EXPECT_TRUE(mFlagManager.late_boot_misc2());
}
-TEST_F(FlagManagerTest, getValue_int32_default) {
- EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
- int32_t defaultValue = 30;
- std::optional<int32_t> systemPropertyValue = std::nullopt;
- int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, defaultValue);
+TEST_F(FlagManagerTest, returnsOverrideReadonly) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
+ false);
+
+ // This is stored in a static variable, so this test depends on the fact
+ // that this flag has not been read in this process.
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(true));
+ EXPECT_TRUE(mFlagManager.add_sf_skipped_frames_to_trace());
}
-TEST_F(FlagManagerTest, getValue_int32_sysprop) {
- int32_t defaultValue = 30;
- std::optional<int32_t> systemPropertyValue = std::make_optional(10);
- int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, 10);
+TEST_F(FlagManagerTest, returnsOverrideFalse) {
+ mFlagManager.markBootCompleted();
+
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::
+ refresh_rate_overlay_on_external_display,
+ true);
+
+ // This is stored in a static variable, so this test depends on the fact
+ // that this flag has not been read in this process.
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillOnce(Return(false));
+ EXPECT_FALSE(mFlagManager.refresh_rate_overlay_on_external_display());
}
-TEST_F(FlagManagerTest, getValue_int32_experiment) {
- EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("50"));
- std::int32_t defaultValue = 30;
- std::optional<std::int32_t> systemPropertyValue = std::nullopt;
- std::int32_t result = FlagManagerTest::getValue("test_flag", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, 50);
+TEST_F(FlagManagerTest, ignoresOverrideInUnitTestMode) {
+ mFlagManager.setUnitTestMode();
+
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::multithreaded_present, true);
+
+ // If this has not been called in this process, it will be called.
+ // Regardless, the result is ignored.
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(false));
+
+ EXPECT_EQ(true, mFlagManager.multithreaded_present());
}
-TEST_F(FlagManagerTest, getValue_int64_default) {
- EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return(""));
- int64_t defaultValue = 30;
- std::optional<int64_t> systemPropertyValue = std::nullopt;
- int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, defaultValue);
+TEST_F(FlagManagerTest, returnsValue) {
+ mFlagManager.setUnitTestMode();
+
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+ {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, true);
+ EXPECT_EQ(true, mFlagManager.late_boot_misc2());
+ }
+
+ {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::late_boot_misc2, false);
+ EXPECT_EQ(false, mFlagManager.late_boot_misc2());
+ }
}
-TEST_F(FlagManagerTest, getValue_int64_sysprop) {
- int64_t defaultValue = 30;
- std::optional<int64_t> systemPropertyValue = std::make_optional(10);
- int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, 10);
+TEST_F(FlagManagerTest, readonlyReturnsValue) {
+ mFlagManager.setUnitTestMode();
+
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+ {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, true);
+ EXPECT_EQ(true, mFlagManager.misc1());
+ }
+
+ {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::misc1, false);
+ EXPECT_EQ(false, mFlagManager.misc1());
+ }
}
-TEST_F(FlagManagerTest, getValue_int64_experiment) {
- EXPECT_CALL(*mFlagManager, getServerConfigurableFlag).Times(1).WillOnce(Return("50"));
- int64_t defaultValue = 30;
- std::optional<int64_t> systemPropertyValue = std::nullopt;
- int64_t result = getValue("flag_name", systemPropertyValue, defaultValue);
- ASSERT_EQ(result, 50);
+TEST_F(FlagManagerTest, dontSkipOnEarlyIsNotCached) {
+ EXPECT_CALL(mFlagManager, getBoolProperty).WillRepeatedly(Return(std::nullopt));
+
+ const auto initialValue = com::android::graphics::surfaceflinger::flags::dont_skip_on_early();
+
+ com::android::graphics::surfaceflinger::flags::dont_skip_on_early(true);
+ EXPECT_EQ(true, mFlagManager.dont_skip_on_early());
+
+ com::android::graphics::surfaceflinger::flags::dont_skip_on_early(false);
+ EXPECT_EQ(false, mFlagManager.dont_skip_on_early());
+
+ com::android::graphics::surfaceflinger::flags::dont_skip_on_early(initialValue);
}
-} // namespace
+
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
index bb1f4328b5..f695b096a7 100644
--- a/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FpsReporterTest.cpp
@@ -24,17 +24,12 @@
#include <gtest/gtest.h>
#include <gui/LayerMetadata.h>
-#include "BufferQueueLayer.h"
-#include "BufferStateLayer.h"
-#include "EffectLayer.h"
#include "FpsReporter.h"
#include "Layer.h"
#include "TestableSurfaceFlinger.h"
#include "fake/FakeClock.h"
#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
#include "mock/MockFrameTimeline.h"
-#include "mock/MockVsyncController.h"
namespace android {
@@ -50,7 +45,7 @@ using testing::UnorderedElementsAre;
using android::Hwc2::IComposer;
using android::Hwc2::IComposerClient;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using gui::LayerMetadata;
struct TestableFpsListener : public gui::BnFpsListener {
TestableFpsListener() {}
@@ -79,8 +74,7 @@ protected:
static constexpr uint32_t LAYER_FLAGS = 0;
static constexpr int32_t PRIORITY_UNSET = -1;
- void setupScheduler();
- sp<BufferStateLayer> createBufferStateLayer(LayerMetadata metadata);
+ sp<Layer> createBufferStateLayer(LayerMetadata metadata);
TestableSurfaceFlinger mFlinger;
mock::FrameTimeline mFrameTimeline =
@@ -95,8 +89,8 @@ protected:
sp<TestableFpsListener> mFpsListener;
fake::FakeClock* mClock = new fake::FakeClock();
- sp<FpsReporter> mFpsReporter =
- new FpsReporter(mFrameTimeline, *(mFlinger.flinger()), std::unique_ptr<Clock>(mClock));
+ sp<FpsReporter> mFpsReporter = sp<FpsReporter>::make(mFrameTimeline, *(mFlinger.flinger()),
+ std::unique_ptr<Clock>(mClock));
};
FpsReporterTest::FpsReporterTest() {
@@ -104,10 +98,10 @@ FpsReporterTest::FpsReporterTest() {
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- setupScheduler();
+ mFlinger.setupMockScheduler();
mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
- mFpsListener = new TestableFpsListener();
+ mFpsListener = sp<TestableFpsListener>::make();
}
FpsReporterTest::~FpsReporterTest() {
@@ -116,35 +110,10 @@ FpsReporterTest::~FpsReporterTest() {
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
-sp<BufferStateLayer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
+sp<Layer> FpsReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
sp<Client> client;
LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
- return new BufferStateLayer(args);
-}
-
-void FpsReporterTest::setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread));
+ return sp<Layer>::make(args);
}
namespace {
@@ -153,7 +122,7 @@ TEST_F(FpsReporterTest, callsListeners) {
mParent = createBufferStateLayer();
constexpr int32_t kTaskId = 12;
LayerMetadata targetMetadata;
- targetMetadata.setInt32(METADATA_TASK_ID, kTaskId);
+ targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
mTarget = createBufferStateLayer(targetMetadata);
mChild = createBufferStateLayer();
mGrandChild = createBufferStateLayer();
@@ -188,7 +157,7 @@ TEST_F(FpsReporterTest, callsListeners) {
TEST_F(FpsReporterTest, rateLimits) {
const constexpr int32_t kTaskId = 12;
LayerMetadata targetMetadata;
- targetMetadata.setInt32(METADATA_TASK_ID, kTaskId);
+ targetMetadata.setInt32(gui::METADATA_TASK_ID, kTaskId);
mTarget = createBufferStateLayer(targetMetadata);
mFlinger.mutableCurrentState().layersSortedByZ.add(mTarget);
diff --git a/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
index a581c7a4a4..7c1d4b4092 100644
--- a/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameRateOverrideMappingsTest.cpp
@@ -17,6 +17,8 @@
#undef LOG_TAG
#define LOG_TAG "FrameRateOverrideMappingsTest"
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
#include <gtest/gtest.h>
#include <unordered_map>
@@ -34,6 +36,8 @@ protected:
};
namespace {
+using namespace com::android::graphics::surfaceflinger;
+
TEST_F(FrameRateOverrideMappingsTest, testUpdateFrameRateOverridesByContent) {
mFrameRateOverrideByContent.clear();
mFrameRateOverrideByContent.emplace(0, 30.0_Hz);
@@ -59,6 +63,8 @@ TEST_F(FrameRateOverrideMappingsTest, testUpdateFrameRateOverridesByContent) {
}
TEST_F(FrameRateOverrideMappingsTest, testSetGameModeRefreshRateForUid) {
+ SET_FLAG_FOR_TEST(flags::game_default_frame_rate, false);
+
mFrameRateOverrideMappings.setGameModeRefreshRateForUid({1, 30.0f});
mFrameRateOverrideMappings.setGameModeRefreshRateForUid({2, 90.0f});
@@ -95,6 +101,7 @@ TEST_F(FrameRateOverrideMappingsTest, testSetPreferredRefreshRateForUid) {
}
TEST_F(FrameRateOverrideMappingsTest, testGetFrameRateOverrideForUidMixed) {
+ SET_FLAG_FOR_TEST(flags::game_default_frame_rate, false);
mFrameRateOverrideByContent.clear();
mFrameRateOverrideByContent.emplace(0, 30.0_Hz);
mFrameRateOverrideByContent.emplace(1, 60.0_Hz);
@@ -111,7 +118,6 @@ TEST_F(FrameRateOverrideMappingsTest, testGetFrameRateOverrideForUidMixed) {
ASSERT_EQ(allFrameRateOverrides,
mFrameRateOverrideMappings.getAllFrameRateOverrides(
/*supportsFrameRateOverrideByContent*/ true));
-
mFrameRateOverrideMappings.setGameModeRefreshRateForUid({1, 30.0f});
mFrameRateOverrideMappings.setGameModeRefreshRateForUid({2, 90.0f});
mFrameRateOverrideMappings.setGameModeRefreshRateForUid({4, 120.0f});
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
index 1e6e3361b2..d30d5b8223 100644
--- a/services/surfaceflinger/tests/unittests/RefreshRateSelectionTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionPriorityTest.cpp
@@ -21,13 +21,9 @@
#include <gtest/gtest.h>
#include <gui/LayerMetadata.h>
-#include "BufferStateLayer.h"
-#include "EffectLayer.h"
#include "Layer.h"
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
-#include "mock/MockVsyncController.h"
namespace android {
@@ -40,8 +36,6 @@ using testing::SetArgPointee;
using android::Hwc2::IComposer;
using android::Hwc2::IComposerClient;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-
/**
* This class covers all the test that are related to refresh rate selection.
*/
@@ -58,9 +52,8 @@ protected:
static constexpr uint32_t LAYER_FLAGS = 0;
static constexpr int32_t PRIORITY_UNSET = -1;
- void setupScheduler();
- sp<BufferStateLayer> createBufferStateLayer();
- sp<EffectLayer> createEffectLayer();
+ sp<Layer> createBufferStateLayer();
+ sp<Layer> createEffectLayer();
void setParent(Layer* child, Layer* parent);
void commitTransaction(Layer* layer);
@@ -78,7 +71,7 @@ RefreshRateSelectionTest::RefreshRateSelectionTest() {
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- setupScheduler();
+ mFlinger.setupMockScheduler();
mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
}
@@ -88,58 +81,29 @@ RefreshRateSelectionTest::~RefreshRateSelectionTest() {
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
-
-sp<BufferStateLayer> RefreshRateSelectionTest::createBufferStateLayer() {
+sp<Layer> RefreshRateSelectionTest::createBufferStateLayer() {
sp<Client> client;
LayerCreationArgs args(mFlinger.flinger(), client, "buffer-queue-layer", LAYER_FLAGS,
LayerMetadata());
- return new BufferStateLayer(args);
+ return sp<Layer>::make(args);
}
-sp<EffectLayer> RefreshRateSelectionTest::createEffectLayer() {
+sp<Layer> RefreshRateSelectionTest::createEffectLayer() {
sp<Client> client;
LayerCreationArgs args(mFlinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata());
- return new EffectLayer(args);
+ return sp<Layer>::make(args);
}
void RefreshRateSelectionTest::setParent(Layer* child, Layer* parent) {
- child->setParent(parent);
+ child->setParent(sp<Layer>::fromExisting(parent));
}
void RefreshRateSelectionTest::commitTransaction(Layer* layer) {
- auto c = layer->getDrawingState();
- layer->commitTransaction(c);
-}
-
-void RefreshRateSelectionTest::setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread));
+ layer->commitTransaction();
}
namespace {
-/* ------------------------------------------------------------------------
- * Test cases
- */
+
TEST_F(RefreshRateSelectionTest, testPriorityOnBufferStateLayers) {
mParent = createBufferStateLayer();
mChild = createBufferStateLayer();
diff --git a/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
new file mode 100644
index 0000000000..5c742d7360
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/FrameRateSelectionStrategyTest.cpp
@@ -0,0 +1,224 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "Layer.h"
+#include "LayerTestUtils.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+
+namespace android {
+
+using testing::DoAll;
+using testing::Mock;
+using testing::SetArgPointee;
+
+using android::Hwc2::IComposer;
+using android::Hwc2::IComposerClient;
+
+using scheduler::LayerHistory;
+
+using FrameRate = Layer::FrameRate;
+using FrameRateCompatibility = Layer::FrameRateCompatibility;
+using FrameRateSelectionStrategy = scheduler::LayerInfo::FrameRateSelectionStrategy;
+
+/**
+ * This class tests the behaviour of Layer::setFrameRateSelectionStrategy.
+ */
+class FrameRateSelectionStrategyTest : public BaseLayerTest {
+protected:
+ const FrameRate FRAME_RATE_VOTE1 = FrameRate(11_Hz, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_VOTE2 = FrameRate(22_Hz, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_VOTE3 = FrameRate(33_Hz, FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_DEFAULT = FrameRate(Fps(), FrameRateCompatibility::Default);
+ const FrameRate FRAME_RATE_TREE = FrameRate(Fps(), FrameRateCompatibility::NoVote);
+
+ FrameRateSelectionStrategyTest();
+
+ void addChild(sp<Layer> layer, sp<Layer> child);
+ void removeChild(sp<Layer> layer, sp<Layer> child);
+ void commitTransaction();
+
+ std::vector<sp<Layer>> mLayers;
+};
+
+FrameRateSelectionStrategyTest::FrameRateSelectionStrategyTest() {
+ 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());
+
+ mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+}
+
+void FrameRateSelectionStrategyTest::addChild(sp<Layer> layer, sp<Layer> child) {
+ layer->addChild(child);
+}
+
+void FrameRateSelectionStrategyTest::removeChild(sp<Layer> layer, sp<Layer> child) {
+ layer->removeChild(child);
+}
+
+void FrameRateSelectionStrategyTest::commitTransaction() {
+ for (auto layer : mLayers) {
+ layer->commitTransaction();
+ }
+}
+
+namespace {
+
+INSTANTIATE_TEST_SUITE_P(PerLayerType, FrameRateSelectionStrategyTest,
+ testing::Values(std::make_shared<BufferStateLayerFactory>(),
+ std::make_shared<EffectLayerFactory>()),
+ PrintToStringParamName);
+
+TEST_P(FrameRateSelectionStrategyTest, SetAndGet) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ layer->setFrameRate(FRAME_RATE_VOTE1.vote);
+ layer->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ commitTransaction();
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetChildOverrideChildren) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto child1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto child2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ addChild(parent, child1);
+ addChild(child1, child2);
+
+ child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+ child2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ commitTransaction();
+ EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ parent->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ child1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ child2->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, SetParentOverrideChildren) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ addChild(layer1, layer2);
+ addChild(layer2, layer3);
+
+ layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+ layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
+ layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ layer3->setFrameRate(FRAME_RATE_VOTE3.vote);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+
+ layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Propagate);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+}
+
+TEST_P(FrameRateSelectionStrategyTest, OverrideChildrenAndSelf) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ const auto& layerFactory = GetParam();
+ auto layer1 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto layer2 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ auto layer3 = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
+ addChild(layer1, layer2);
+ addChild(layer2, layer3);
+
+ layer1->setFrameRate(FRAME_RATE_VOTE1.vote);
+ layer2->setFrameRate(FRAME_RATE_VOTE2.vote);
+ layer2->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::Self);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_DEFAULT, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+
+ layer1->setFrameRateSelectionStrategy(FrameRateSelectionStrategy::OverrideChildren);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE1, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+
+ layer1->setFrameRate(FRAME_RATE_DEFAULT.vote);
+ commitTransaction();
+
+ EXPECT_EQ(FRAME_RATE_TREE, layer1->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::OverrideChildren,
+ layer1->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer2->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Self,
+ layer2->getDrawingState().frameRateSelectionStrategy);
+ EXPECT_EQ(FRAME_RATE_VOTE2, layer3->getFrameRateForLayerTree());
+ EXPECT_EQ(FrameRateSelectionStrategy::Propagate,
+ layer3->getDrawingState().frameRateSelectionStrategy);
+}
+
+} // namespace
+} // namespace android \ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
index f1efa9286d..ddc3967c40 100644
--- a/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTimelineTest.cpp
@@ -14,7 +14,8 @@
* limitations under the License.
*/
-
+#include <common/test/FlagUtils.h>
+#include "com_android_graphics_surfaceflinger_flags.h"
#include "gmock/gmock-spec-builders.h"
#include "mock/MockTimeStats.h"
#undef LOG_TAG
@@ -40,10 +41,24 @@ using ProtoActualSurfaceFrameStart = perfetto::protos::FrameTimelineEvent_Actual
using ProtoFrameEnd = perfetto::protos::FrameTimelineEvent_FrameEnd;
using ProtoPresentType = perfetto::protos::FrameTimelineEvent_PresentType;
using ProtoJankType = perfetto::protos::FrameTimelineEvent_JankType;
+using ProtoJankSeverityType = perfetto::protos::FrameTimelineEvent_JankSeverityType;
using ProtoPredictionType = perfetto::protos::FrameTimelineEvent_PredictionType;
namespace android::frametimeline {
+static const std::string sLayerNameOne = "layer1";
+static const std::string sLayerNameTwo = "layer2";
+
+constexpr const uid_t sUidOne = 0;
+constexpr pid_t sPidOne = 10;
+constexpr pid_t sPidTwo = 20;
+constexpr int32_t sInputEventId = 5;
+constexpr int32_t sLayerIdOne = 1;
+constexpr int32_t sLayerIdTwo = 2;
+constexpr GameMode sGameMode = GameMode::Unsupported;
+constexpr Fps RR_11 = Fps::fromPeriodNsecs(11);
+constexpr Fps RR_30 = Fps::fromPeriodNsecs(30);
+
class FrameTimelineTest : public testing::Test {
public:
FrameTimelineTest() {
@@ -106,6 +121,14 @@ public:
return packets;
}
+ void addEmptySurfaceFrame() {
+ auto surfaceFrame =
+ mFrameTimeline->createSurfaceFrameForToken({}, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ false, sGameMode);
+ mFrameTimeline->addSurfaceFrame(std::move(surfaceFrame));
+ }
+
void addEmptyDisplayFrame() {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
// Trigger a flushPresentFence by calling setSfPresent for the next frame
@@ -168,17 +191,6 @@ public:
kStartThreshold};
};
-static const std::string sLayerNameOne = "layer1";
-static const std::string sLayerNameTwo = "layer2";
-
-constexpr const uid_t sUidOne = 0;
-constexpr pid_t sPidOne = 10;
-constexpr pid_t sPidTwo = 20;
-constexpr int32_t sInputEventId = 5;
-constexpr int32_t sLayerIdOne = 1;
-constexpr int32_t sLayerIdTwo = 2;
-constexpr GameMode sGameMode = GameMode::Unsupported;
-
TEST_F(FrameTimelineTest, tokenManagerRemovesStalePredictions) {
int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
EXPECT_EQ(getPredictions().size(), 1u);
@@ -217,9 +229,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_noToken) {
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_expiredToken) {
int64_t token1 = mTokenManager->generateTokenForPredictions({0, 0, 0});
flushTokens();
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
- sLayerIdOne, sLayerNameOne, sLayerNameOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Expired);
@@ -227,9 +242,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_expiredToken) {
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validToken) {
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
- sLayerIdOne, sLayerNameOne, sLayerNameOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
EXPECT_EQ(surfaceFrame->getPredictionState(), PredictionState::Valid);
@@ -239,9 +257,12 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validToken) {
TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validInputEventId) {
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
constexpr int32_t inputEventId = 1;
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = inputEventId;
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken({token1, inputEventId}, sPidOne, sUidOne,
- sLayerIdOne, sLayerNameOne, sLayerNameOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
EXPECT_EQ(inputEventId, surfaceFrame->getInputEventId());
@@ -250,13 +271,16 @@ TEST_F(FrameTimelineTest, createSurfaceFrameForToken_validInputEventId) {
TEST_F(FrameTimelineTest, presentFenceSignaled_droppedFramesNotUpdated) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
- sLayerIdOne, sLayerNameOne, sLayerNameOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
surfaceFrame1->setDropTime(12);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -278,15 +302,18 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_presentedFramesUpdated) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdTwo, sLayerNameTwo,
- sLayerNameTwo, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdTwo,
+ sLayerNameTwo, sLayerNameTwo,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
@@ -309,7 +336,9 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_presentedFramesUpdated) {
EXPECT_EQ(presentedSurfaceFrame1.getActuals().presentTime, 42);
EXPECT_EQ(presentedSurfaceFrame2.getActuals().presentTime, 42);
EXPECT_NE(surfaceFrame1->getJankType(), std::nullopt);
+ EXPECT_NE(surfaceFrame1->getJankSeverityType(), std::nullopt);
EXPECT_NE(surfaceFrame2->getJankType(), std::nullopt);
+ EXPECT_NE(surfaceFrame2->getJankSeverityType(), std::nullopt);
}
TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
@@ -324,12 +353,14 @@ TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
{10 + frameTimeFactor, 20 + frameTimeFactor, 30 + frameTimeFactor});
int64_t sfToken = mTokenManager->generateTokenForPredictions(
{22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId},
- sPidOne, sUidOne, sLayerIdOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, RR_11, RR_11);
surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
@@ -347,11 +378,14 @@ TEST_F(FrameTimelineTest, displayFramesSlidingWindowMovesAfterLimit) {
{10 + frameTimeFactor, 20 + frameTimeFactor, 30 + frameTimeFactor});
int64_t sfToken = mTokenManager->generateTokenForPredictions(
{22 + frameTimeFactor, 26 + frameTimeFactor, 30 + frameTimeFactor});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken, 22 + frameTimeFactor, RR_11, RR_11);
surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27 + frameTimeFactor, presentFence);
@@ -394,7 +428,7 @@ TEST_F(FrameTimelineTest, setMaxDisplayFramesSetsSizeProperly) {
sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
- mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27, presentFence);
@@ -411,7 +445,7 @@ TEST_F(FrameTimelineTest, setMaxDisplayFramesSetsSizeProperly) {
sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
- mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27, presentFence);
@@ -428,7 +462,7 @@ TEST_F(FrameTimelineTest, setMaxDisplayFramesSetsSizeProperly) {
sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
int64_t sfToken = mTokenManager->generateTokenForPredictions({22, 26, 30});
- mFrameTimeline->setSfWakeUp(sfToken, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken, 22, RR_11, RR_11);
surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(27, presentFence);
@@ -437,17 +471,20 @@ TEST_F(FrameTimelineTest, setMaxDisplayFramesSetsSizeProperly) {
}
TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) {
- Fps refreshRate = Fps::fromPeriodNsecs(11);
+ Fps refreshRate = RR_11;
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setAcquireFenceTime(20);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -457,25 +494,30 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_invalidSignalTime) {
addEmptyDisplayFrame();
auto displayFrame0 = getDisplayFrame(0);
- EXPECT_EQ(displayFrame0->getActuals().presentTime, -1);
- EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown);
+ EXPECT_EQ(displayFrame0->getActuals().presentTime, 59);
+ EXPECT_EQ(displayFrame0->getJankType(), JankType::Unknown | JankType::DisplayHAL);
+ EXPECT_EQ(displayFrame0->getJankSeverityType(), JankSeverityType::Unknown);
EXPECT_EQ(surfaceFrame1->getActuals().presentTime, -1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Unknown);
}
// Tests related to TimeStats
TEST_F(FrameTimelineTest, presentFenceSignaled_doesNotReportForInvalidTokens) {
- Fps refreshRate = Fps::fromPeriodNsecs(11);
+ Fps refreshRate = RR_11;
EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(0);
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = -1;
int64_t sfToken1 = -1;
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setAcquireFenceTime(20);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -485,7 +527,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_doesNotReportForInvalidTokens) {
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
- Fps refreshRate = Fps::fromPeriodNsecs(11);
+ Fps refreshRate = RR_11;
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(
TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
@@ -495,12 +537,15 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setAcquireFenceTime(20);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -510,7 +555,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfCpu) {
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
- Fps refreshRate = Fps::fromPeriodNsecs(11);
+ Fps refreshRate = RR_11;
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(
TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
@@ -521,12 +566,15 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
auto gpuFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setAcquireFenceTime(20);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -537,7 +585,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsLongSfGpu) {
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
- Fps refreshRate = Fps::fromPeriodNsecs(30);
+ Fps refreshRate = RR_30;
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, std::nullopt, sUidOne,
sLayerNameOne, sGameMode,
@@ -546,18 +594,22 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsDisplayMiss) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
surfaceFrame1->setAcquireFenceTime(20);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
presentFence1->signalForTest(90);
mFrameTimeline->setSfPresent(56, presentFence1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::DisplayHAL);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
@@ -570,13 +622,16 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(45);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -584,6 +639,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMiss) {
mFrameTimeline->setSfPresent(86, presentFence1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
@@ -596,13 +652,16 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({40, 60, 92});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(50);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -610,6 +669,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfScheduling) {
mFrameTimeline->setSfPresent(56, presentFence1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
@@ -622,13 +682,16 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(40);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -636,6 +699,7 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsSfPredictionError) {
mFrameTimeline->setSfPresent(56, presentFence1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::PredictionError);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
@@ -648,13 +712,16 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({30, 40, 58});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(40);
- mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 82, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented,
/*previousLatchTime*/ 56);
@@ -663,11 +730,12 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppBufferStuffing) {
mFrameTimeline->setSfPresent(86, presentFence1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::BufferStuffing);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
- Fps refreshRate = Fps::fromPeriodNsecs(11);
- Fps renderRate = Fps::fromPeriodNsecs(30);
+ Fps refreshRate = RR_11;
+ Fps renderRate = RR_30;
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(TimeStats::JankyFramesInfo{refreshRate, renderRate, sUidOne,
sLayerNameOne, sGameMode,
@@ -676,13 +744,16 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(45);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
surfaceFrame1->setRenderRate(renderRate);
@@ -691,11 +762,12 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_reportsAppMissWithRenderRate) {
mFrameTimeline->setSfPresent(86, presentFence1);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::AppDeadlineMissed);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPresentsSurfaceFrame) {
- Fps refreshRate = Fps::fromPeriodNsecs(11);
- Fps renderRate = Fps::fromPeriodNsecs(30);
+ Fps refreshRate = RR_11;
+ Fps renderRate = RR_30;
EXPECT_CALL(*mTimeStats,
incrementJankyFrames(
@@ -706,15 +778,18 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPres
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({10, 20, 60});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(45);
// Trigger a prediction expiry
flushTokens();
- mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, refreshRate, refreshRate);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
surfaceFrame1->setRenderRate(renderRate);
@@ -724,12 +799,14 @@ TEST_F(FrameTimelineTest, presentFenceSignaled_displayFramePredictionExpiredPres
auto displayFrame = getDisplayFrame(0);
EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Unknown);
EXPECT_EQ(displayFrame->getFrameStartMetadata(), FrameStartMetadata::UnknownStart);
EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
EXPECT_EQ(surfaceFrame1->getActuals().presentTime, 90);
EXPECT_EQ(surfaceFrame1->getJankType(), JankType::Unknown | JankType::AppDeadlineMissed);
+ EXPECT_EQ(surfaceFrame1->getJankSeverityType(), JankSeverityType::Full);
}
/*
@@ -744,13 +821,16 @@ TEST_F(FrameTimelineTest, tracing_noPacketsSentWithoutTraceStart) {
auto tracingSession = getTracingSessionForTest();
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
- sLayerIdOne, sLayerNameOne, sLayerNameOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
@@ -771,13 +851,16 @@ TEST_F(FrameTimelineTest, tracing_sanityTest) {
tracingSession->StartBlocking();
int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t token2 = mTokenManager->generateTokenForPredictions({40, 50, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({token1, sInputEventId}, sPidOne, sUidOne,
- sLayerIdOne, sLayerNameOne, sLayerNameOne,
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
/*isBuffer*/ true, sGameMode);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token2, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(token2, 20, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
@@ -799,7 +882,7 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_invalidTokenDoesNotEmitTracePacket)
tracingSession->StartBlocking();
// Set up the display frame
- mFrameTimeline->setSfWakeUp(-1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(-1, 20, RR_11, RR_11);
mFrameTimeline->setSfPresent(25, presentFence1);
presentFence1->signalForTest(30);
@@ -823,7 +906,7 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_invalidTokenDoesNotEmitTracePacket)
/*isBuffer*/ true, sGameMode);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(token1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(25, presentFence1);
@@ -850,7 +933,8 @@ ProtoExpectedDisplayFrameStart createProtoExpectedDisplayFrameStart(int64_t cook
ProtoActualDisplayFrameStart createProtoActualDisplayFrameStart(
int64_t cookie, int64_t token, pid_t pid, ProtoPresentType presentType, bool onTimeFinish,
- bool gpuComposition, ProtoJankType jankType, ProtoPredictionType predictionType) {
+ bool gpuComposition, ProtoJankType jankType, ProtoJankSeverityType jankSeverityType,
+ ProtoPredictionType predictionType) {
ProtoActualDisplayFrameStart proto;
proto.set_cookie(cookie);
proto.set_token(token);
@@ -859,6 +943,7 @@ ProtoActualDisplayFrameStart createProtoActualDisplayFrameStart(
proto.set_on_time_finish(onTimeFinish);
proto.set_gpu_composition(gpuComposition);
proto.set_jank_type(jankType);
+ proto.set_jank_severity_type(jankSeverityType);
proto.set_prediction_type(predictionType);
return proto;
}
@@ -879,7 +964,8 @@ ProtoExpectedSurfaceFrameStart createProtoExpectedSurfaceFrameStart(int64_t cook
ProtoActualSurfaceFrameStart createProtoActualSurfaceFrameStart(
int64_t cookie, int64_t token, int64_t displayFrameToken, pid_t pid, std::string layerName,
ProtoPresentType presentType, bool onTimeFinish, bool gpuComposition,
- ProtoJankType jankType, ProtoPredictionType predictionType, bool isBuffer) {
+ ProtoJankType jankType, ProtoJankSeverityType jankSeverityType,
+ ProtoPredictionType predictionType, bool isBuffer) {
ProtoActualSurfaceFrameStart proto;
proto.set_cookie(cookie);
proto.set_token(token);
@@ -890,6 +976,7 @@ ProtoActualSurfaceFrameStart createProtoActualSurfaceFrameStart(
proto.set_on_time_finish(onTimeFinish);
proto.set_gpu_composition(gpuComposition);
proto.set_jank_type(jankType);
+ proto.set_jank_severity_type(jankSeverityType);
proto.set_prediction_type(predictionType);
proto.set_is_buffer(isBuffer);
return proto;
@@ -932,6 +1019,8 @@ void validateTraceEvent(const ProtoActualDisplayFrameStart& received,
EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
ASSERT_TRUE(received.has_jank_type());
EXPECT_EQ(received.jank_type(), source.jank_type());
+ ASSERT_TRUE(received.has_jank_severity_type());
+ EXPECT_EQ(received.jank_severity_type(), source.jank_severity_type());
ASSERT_TRUE(received.has_prediction_type());
EXPECT_EQ(received.prediction_type(), source.prediction_type());
}
@@ -979,6 +1068,8 @@ void validateTraceEvent(const ProtoActualSurfaceFrameStart& received,
EXPECT_EQ(received.gpu_composition(), source.gpu_composition());
ASSERT_TRUE(received.has_jank_type());
EXPECT_EQ(received.jank_type(), source.jank_type());
+ ASSERT_TRUE(received.has_jank_severity_type());
+ EXPECT_EQ(received.jank_severity_type(), source.jank_severity_type());
ASSERT_TRUE(received.has_prediction_type());
EXPECT_EQ(received.prediction_type(), source.prediction_type());
ASSERT_TRUE(received.has_is_buffer());
@@ -990,15 +1081,110 @@ void validateTraceEvent(const ProtoFrameEnd& received, const ProtoFrameEnd& sour
EXPECT_EQ(received.cookie(), source.cookie());
}
+TEST_F(FrameTimelineTest, traceDisplayFrameSkipped) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::add_sf_skipped_frames_to_trace,
+ true);
+
+ // setup 2 display frames
+ // DF 1: [22,40] -> [5, 40]
+ // DF : [36, 70] (Skipped one, added by the trace)
+ // DF 2: [82, 100] -> SF [25, 70]
+ auto tracingSession = getTracingSessionForTest();
+ tracingSession->StartBlocking();
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 100});
+ int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
+ int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+
+ int64_t traceCookie = snoopCurrentTraceCookie();
+
+ // set up 1st display frame
+ FrameTimelineInfo ftInfo1;
+ ftInfo1.vsyncId = surfaceFrameToken1;
+ ftInfo1.inputEventId = sInputEventId;
+ auto surfaceFrame1 =
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo1, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ surfaceFrame1->setAcquireFenceTime(16);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_30);
+ surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame1);
+ mFrameTimeline->setSfPresent(30, presentFence1);
+ presentFence1->signalForTest(40);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = surfaceFrameToken2;
+ ftInfo2.inputEventId = sInputEventId;
+ auto surfaceFrame2 =
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+
+ // set up 2nd display frame
+ surfaceFrame2->setAcquireFenceTime(36);
+ mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_11, RR_30);
+ surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame2);
+ mFrameTimeline->setSfPresent(90, presentFence2);
+ presentFence2->signalForTest(100);
+
+ // the token of skipped Display Frame
+ auto protoSkippedActualDisplayFrameStart =
+ createProtoActualDisplayFrameStart(traceCookie + 9, 0, kSurfaceFlingerPid,
+ FrameTimelineEvent::PRESENT_DROPPED, true, false,
+ FrameTimelineEvent::JANK_DROPPED,
+ FrameTimelineEvent::SEVERITY_NONE,
+ FrameTimelineEvent::PREDICTION_VALID);
+ auto protoSkippedActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 9);
+
+ // Trigger a flush by finalizing the next DisplayFrame
+ addEmptyDisplayFrame();
+ flushTrace();
+ tracingSession->StopBlocking();
+
+ auto packets = readFrameTimelinePacketsBlocking(tracingSession.get());
+ // 8 Valid Display Frames + 8 Valid Surface Frames + 2 Skipped Display Frames
+ EXPECT_EQ(packets.size(), 18u);
+
+ // Packet - 16: Actual skipped Display Frame Start
+ // the timestamp should be equal to the 2nd expected surface frame's end time
+ const auto& packet16 = packets[16];
+ ASSERT_TRUE(packet16.has_timestamp());
+ EXPECT_EQ(packet16.timestamp(), 36u);
+ ASSERT_TRUE(packet16.has_frame_timeline_event());
+
+ const auto& event16 = packet16.frame_timeline_event();
+ const auto& actualSkippedDisplayFrameStart = event16.actual_display_frame_start();
+ validateTraceEvent(actualSkippedDisplayFrameStart, protoSkippedActualDisplayFrameStart);
+
+ // Packet - 17: Actual skipped Display Frame End
+ // the timestamp should be equal to the 2nd expected surface frame's present time
+ const auto& packet17 = packets[17];
+ ASSERT_TRUE(packet17.has_timestamp());
+ EXPECT_EQ(packet17.timestamp(), 70u);
+ ASSERT_TRUE(packet17.has_frame_timeline_event());
+
+ const auto& event17 = packet17.frame_timeline_event();
+ const auto& actualSkippedDisplayFrameEnd = event17.frame_end();
+ validateTraceEvent(actualSkippedDisplayFrameEnd, protoSkippedActualDisplayFrameEnd);
+}
+
TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
auto tracingSession = getTracingSessionForTest();
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
tracingSession->StartBlocking();
+
+ // Add an empty surface frame so that display frame would get traced.
+ addEmptySurfaceFrame();
int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({10, 30, 30});
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
mFrameTimeline->setSfPresent(26, presentFence1);
presentFence1->signalForTest(31);
@@ -1012,6 +1198,7 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_emitsValidTracePacket) {
kSurfaceFlingerPid,
FrameTimelineEvent::PRESENT_ON_TIME, true, false,
FrameTimelineEvent::JANK_NONE,
+ FrameTimelineEvent::SEVERITY_NONE,
FrameTimelineEvent::PREDICTION_VALID);
auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 2);
@@ -1076,8 +1263,11 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_predictionExpiredDoesNotTraceExpecte
// Flush the token so that it would expire
flushTokens();
+ // Add an empty surface frame so that display frame would get traced.
+ addEmptySurfaceFrame();
+
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
mFrameTimeline->setSfPresent(26, presentFence1);
presentFence1->signalForTest(31);
@@ -1088,6 +1278,7 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_predictionExpiredDoesNotTraceExpecte
kSurfaceFlingerPid,
FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
false, FrameTimelineEvent::JANK_UNKNOWN,
+ FrameTimelineEvent::SEVERITY_UNKNOWN,
FrameTimelineEvent::PREDICTION_EXPIRED);
auto protoActualDisplayFrameEnd = createProtoFrameEnd(traceCookie + 1);
@@ -1125,7 +1316,7 @@ TEST_F(FrameTimelineTest, traceDisplayFrame_predictionExpiredDoesNotTraceExpecte
TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
auto tracingSession = getTracingSessionForTest();
// Layer specific increment
- EXPECT_CALL(*mTimeStats, incrementJankyFrames(_));
+ EXPECT_CALL(*mTimeStats, incrementJankyFrames(_)).Times(2);
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
@@ -1133,14 +1324,18 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 25, 40});
int64_t displayFrameToken1 = mTokenManager->generateTokenForPredictions({30, 35, 40});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken;
+ ftInfo.inputEventId = sInputEventId;
+
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setActualQueueTime(10);
surfaceFrame1->setDropTime(15);
@@ -1157,8 +1352,9 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
auto protoDroppedSurfaceFrameActualStart =
createProtoActualSurfaceFrameStart(traceCookie + 2, surfaceFrameToken,
displayFrameToken1, sPidOne, sLayerNameOne,
- FrameTimelineEvent::PRESENT_DROPPED, false, false,
- FrameTimelineEvent::JANK_NONE,
+ FrameTimelineEvent::PRESENT_DROPPED, true, false,
+ FrameTimelineEvent::JANK_DROPPED,
+ FrameTimelineEvent::SEVERITY_UNKNOWN,
FrameTimelineEvent::PREDICTION_VALID, true);
auto protoDroppedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 2);
@@ -1171,11 +1367,12 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_emitsValidTracePacket) {
displayFrameToken1, sPidOne, sLayerNameOne,
FrameTimelineEvent::PRESENT_ON_TIME, true, false,
FrameTimelineEvent::JANK_NONE,
+ FrameTimelineEvent::SEVERITY_NONE,
FrameTimelineEvent::PREDICTION_VALID, true);
auto protoPresentedSurfaceFrameActualEnd = createProtoFrameEnd(traceCookie + 4);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(displayFrameToken1, 20, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -1293,10 +1490,13 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredIsAppMissedDeadline
// Flush the token so that it would expire
flushTokens();
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken;
+ ftInfo.inputEventId = 0;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0},
- sPidOne, sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setActualQueueTime(appEndTime);
surfaceFrame1->setAcquireFenceTime(appEndTime);
@@ -1314,11 +1514,12 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredIsAppMissedDeadline
displayFrameToken, sPidOne, sLayerNameOne,
FrameTimelineEvent::PRESENT_UNSPECIFIED, false,
false, FrameTimelineEvent::JANK_APP_DEADLINE_MISSED,
+ FrameTimelineEvent::SEVERITY_UNKNOWN,
FrameTimelineEvent::PREDICTION_EXPIRED, true);
auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(sfEndTime, presentFence1);
@@ -1369,10 +1570,13 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredDroppedFramesTraced
// Flush the token so that it would expire
flushTokens();
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken;
+ ftInfo.inputEventId = 0;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, /*inputEventId*/ 0},
- sPidOne, sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
constexpr nsecs_t sfStartTime = std::chrono::nanoseconds(22ms).count();
constexpr nsecs_t sfEndTime = std::chrono::nanoseconds(30ms).count();
@@ -1387,12 +1591,13 @@ TEST_F(FrameTimelineTest, traceSurfaceFrame_predictionExpiredDroppedFramesTraced
createProtoActualSurfaceFrameStart(traceCookie + 1, surfaceFrameToken,
displayFrameToken, sPidOne, sLayerNameOne,
FrameTimelineEvent::PRESENT_DROPPED, false, false,
- FrameTimelineEvent::JANK_NONE,
+ FrameTimelineEvent::JANK_DROPPED,
+ FrameTimelineEvent::SEVERITY_UNKNOWN,
FrameTimelineEvent::PREDICTION_EXPIRED, true);
auto protoActualSurfaceFrameEnd = createProtoFrameEnd(traceCookie + 1);
// Set up the display frame
- mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(displayFrameToken, sfStartTime, RR_11, RR_11);
surfaceFrame1->setDropTime(sfStartTime);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Dropped);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
@@ -1438,11 +1643,14 @@ TEST_F(FrameTimelineTest, jankClassification_presentOnTimeDoesNotClassify) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t surfaceFrameToken = mTokenManager->generateTokenForPredictions({10, 20, 30});
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
- mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame);
mFrameTimeline->setSfPresent(26, presentFence1);
@@ -1463,14 +1671,15 @@ TEST_F(FrameTimelineTest, jankClassification_presentOnTimeDoesNotClassify) {
EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::None);
}
TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresent) {
- Fps vsyncRate = Fps::fromPeriodNsecs(11);
+ Fps vsyncRate = RR_11;
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
- mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate, vsyncRate);
mFrameTimeline->setSfPresent(26, presentFence1);
auto displayFrame = getDisplayFrame(0);
presentFence1->signalForTest(30);
@@ -1480,7 +1689,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresen
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
- mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate, vsyncRate);
mFrameTimeline->setSfPresent(56, presentFence2);
displayFrame = getDisplayFrame(0);
@@ -1489,6 +1698,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresen
EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
// Fences for the second frame haven't been flushed yet, so it should be 0
auto displayFrame2 = getDisplayFrame(1);
@@ -1502,14 +1712,15 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishEarlyPresen
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent) {
- Fps vsyncRate = Fps::fromPeriodNsecs(11);
+ Fps vsyncRate = RR_11;
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 30, 40});
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
- mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate);
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, vsyncRate, vsyncRate);
mFrameTimeline->setSfPresent(26, presentFence1);
auto displayFrame = getDisplayFrame(0);
presentFence1->signalForTest(50);
@@ -1519,7 +1730,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
- mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate);
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, vsyncRate, vsyncRate);
mFrameTimeline->setSfPresent(56, presentFence2);
displayFrame = getDisplayFrame(0);
@@ -1528,6 +1739,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent
EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame->getJankType(), JankType::DisplayHAL);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
// Fences for the second frame haven't been flushed yet, so it should be 0
auto displayFrame2 = getDisplayFrame(1);
@@ -1542,12 +1754,13 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameOnTimeFinishLatePresent
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishEarlyPresent) {
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({12, 18, 40});
- mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
mFrameTimeline->setSfPresent(22, presentFence1);
auto displayFrame = getDisplayFrame(0);
@@ -1564,6 +1777,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishEarlyPresent)
EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerScheduling);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent) {
@@ -1587,7 +1801,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent)
int64_t sfToken4 = mTokenManager->generateTokenForPredictions({112, 120, 120});
// case 1 - cpu time = 33 - 12 = 21, vsync period = 11
- mFrameTimeline->setSfWakeUp(sfToken1, 12, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
mFrameTimeline->setSfPresent(33, presentFence1, gpuFence1);
auto displayFrame0 = getDisplayFrame(0);
gpuFence1->signalForTest(36);
@@ -1597,7 +1811,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame0->getActuals().presentTime, 0);
// case 2 - cpu time = 56 - 52 = 4, vsync period = 30
- mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
mFrameTimeline->setSfPresent(56, presentFence2, gpuFence2);
auto displayFrame1 = getDisplayFrame(1);
gpuFence2->signalForTest(76);
@@ -1608,10 +1822,11 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame0->getActuals().presentTime, 52);
EXPECT_EQ(displayFrame0->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame0->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
- EXPECT_EQ(displayFrame0->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+ EXPECT_EQ(displayFrame0->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+ EXPECT_EQ(displayFrame0->getJankSeverityType(), JankSeverityType::Full);
// case 3 - cpu time = 86 - 82 = 4, vsync period = 30
- mFrameTimeline->setSfWakeUp(sfToken3, 106, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken3, 106, RR_30, RR_30);
mFrameTimeline->setSfPresent(112, presentFence3, gpuFence3);
auto displayFrame2 = getDisplayFrame(2);
gpuFence3->signalForTest(116);
@@ -1623,9 +1838,10 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::Full);
// case 4 - cpu time = 86 - 82 = 4, vsync period = 30
- mFrameTimeline->setSfWakeUp(sfToken4, 120, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken4, 120, RR_30, RR_30);
mFrameTimeline->setSfPresent(140, presentFence4, gpuFence4);
auto displayFrame3 = getDisplayFrame(3);
gpuFence4->signalForTest(156);
@@ -1637,6 +1853,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerStuffing);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Full);
addEmptyDisplayFrame();
@@ -1645,6 +1862,7 @@ TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame3->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame3->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(displayFrame3->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+ EXPECT_EQ(displayFrame3->getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresent) {
@@ -1654,12 +1872,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(16);
- mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(27, presentFence1);
@@ -1674,12 +1895,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = surfaceFrameToken2;
+ ftInfo2.inputEventId = sInputEventId;
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame2->setAcquireFenceTime(36);
- mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame2);
mFrameTimeline->setSfPresent(57, presentFence2);
@@ -1691,12 +1915,14 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::SurfaceFlingerScheduling);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::Partial);
actuals1 = presentedSurfaceFrame1.getActuals();
EXPECT_EQ(actuals1.presentTime, 30);
EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::SurfaceFlingerScheduling);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
// Fences for the second frame haven't been flushed yet, so it should be 0
presentFence2->signalForTest(65);
@@ -1707,10 +1933,10 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen
::testing::Mock::VerifyAndClearExpectations(mTimeStats.get());
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(
- TimeStats::JankyFramesInfo{Fps::fromPeriodNsecs(11), std::nullopt, sUidOne,
- sLayerNameOne, sGameMode,
- JankType::PredictionError, -3, 5, 0}));
+ incrementJankyFrames(TimeStats::JankyFramesInfo{RR_11, std::nullopt, sUidOne,
+ sLayerNameOne, sGameMode,
+ JankType::PredictionError, -3, 5,
+ 0}));
addEmptyDisplayFrame();
@@ -1719,12 +1945,14 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishEarlyPresen
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Partial);
actuals2 = presentedSurfaceFrame2.getActuals();
EXPECT_EQ(actuals2.presentTime, 65);
EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::PredictionError);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent) {
@@ -1734,12 +1962,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 70});
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 40});
int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 70});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(16);
- mFrameTimeline->setSfWakeUp(sfToken1, 22, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(26, presentFence1);
@@ -1754,12 +1985,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = surfaceFrameToken2;
+ ftInfo2.inputEventId = sInputEventId;
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame2->setAcquireFenceTime(36);
- mFrameTimeline->setSfWakeUp(sfToken2, 52, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame2);
mFrameTimeline->setSfPresent(57, presentFence2);
@@ -1771,12 +2005,14 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::DisplayHAL);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::Partial);
actuals1 = presentedSurfaceFrame1.getActuals();
EXPECT_EQ(actuals1.presentTime, 50);
EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::DisplayHAL);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
// Fences for the second frame haven't been flushed yet, so it should be 0
presentFence2->signalForTest(86);
@@ -1787,10 +2023,10 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent
::testing::Mock::VerifyAndClearExpectations(mTimeStats.get());
EXPECT_CALL(*mTimeStats,
- incrementJankyFrames(
- TimeStats::JankyFramesInfo{Fps::fromPeriodNsecs(11), std::nullopt, sUidOne,
- sLayerNameOne, sGameMode,
- JankType::PredictionError, -3, 5, 0}));
+ incrementJankyFrames(TimeStats::JankyFramesInfo{RR_11, std::nullopt, sUidOne,
+ sLayerNameOne, sGameMode,
+ JankType::PredictionError, -3, 5,
+ 0}));
addEmptyDisplayFrame();
@@ -1799,12 +2035,14 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameOnTimeFinishLatePresent
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::PredictionError);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Full);
actuals2 = presentedSurfaceFrame2.getActuals();
EXPECT_EQ(actuals2.presentTime, 86);
EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::PredictionError);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishEarlyPresent) {
@@ -1813,12 +2051,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishEarlyPresent)
auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({42, 50, 50});
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 26, 60});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(40);
- mFrameTimeline->setSfWakeUp(sfToken1, 42, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken1, 42, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(46, presentFence1);
@@ -1838,12 +2079,14 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishEarlyPresent)
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
actuals1 = presentedSurfaceFrame1.getActuals();
EXPECT_EQ(actuals1.presentTime, 50);
EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::EarlyPresent);
EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::Unknown);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent) {
@@ -1857,12 +2100,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent)
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({42, 50, 50});
int64_t surfaceFrameToken1 = mTokenManager->generateTokenForPredictions({5, 16, 30});
int64_t surfaceFrameToken2 = mTokenManager->generateTokenForPredictions({25, 36, 50});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(26);
- mFrameTimeline->setSfWakeUp(sfToken1, 32, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken1, 32, RR_11, RR_11);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(36, presentFence1);
@@ -1877,12 +2123,15 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent)
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = surfaceFrameToken2;
+ ftInfo2.inputEventId = sInputEventId;
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame2->setAcquireFenceTime(40);
- mFrameTimeline->setSfWakeUp(sfToken2, 43, Fps::fromPeriodNsecs(11));
+ mFrameTimeline->setSfWakeUp(sfToken2, 43, RR_11, RR_11);
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame2);
mFrameTimeline->setSfPresent(56, presentFence2);
@@ -1894,12 +2143,14 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
actuals1 = presentedSurfaceFrame1.getActuals();
EXPECT_EQ(actuals1.presentTime, 40);
EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Partial);
// Fences for the second frame haven't been flushed yet, so it should be 0
presentFence2->signalForTest(60);
@@ -1914,6 +2165,7 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent)
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Partial);
actuals2 = presentedSurfaceFrame2.getActuals();
EXPECT_EQ(actuals2.presentTime, 60);
@@ -1921,6 +2173,7 @@ TEST_F(FrameTimelineTest, jankClassification_surfaceFrameLateFinishLatePresent)
EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
JankType::SurfaceFlingerCpuDeadlineMissed | JankType::AppDeadlineMissed);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Partial);
}
TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadlineMissed) {
@@ -1932,12 +2185,15 @@ TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadli
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({112, 120, 120});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(50);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, RR_30, RR_30);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(56, presentFence1);
@@ -1952,12 +2208,15 @@ TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadli
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = surfaceFrameToken2;
+ ftInfo2.inputEventId = sInputEventId;
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame2->setAcquireFenceTime(84);
- mFrameTimeline->setSfWakeUp(sfToken2, 112, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken2, 112, RR_30, RR_30);
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
mFrameTimeline->addSurfaceFrame(surfaceFrame2);
mFrameTimeline->setSfPresent(116, presentFence2);
@@ -1974,10 +2233,12 @@ TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadli
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Full);
// Fences for the second frame haven't been flushed yet, so it should be 0
EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
@@ -1994,11 +2255,13 @@ TEST_F(FrameTimelineTest, jankClassification_multiJankBufferStuffingAndAppDeadli
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::None);
EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(presentedSurfaceFrame2.getJankType(),
JankType::AppDeadlineMissed | JankType::BufferStuffing);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Full);
}
TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffing) {
@@ -2010,12 +2273,15 @@ TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffin
int64_t sfToken1 = mTokenManager->generateTokenForPredictions({52, 60, 60});
int64_t sfToken2 = mTokenManager->generateTokenForPredictions({82, 90, 90});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = surfaceFrameToken1;
+ ftInfo.inputEventId = sInputEventId;
auto surfaceFrame1 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken1, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame1->setAcquireFenceTime(50);
- mFrameTimeline->setSfWakeUp(sfToken1, 52, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken1, 52, RR_30, RR_30);
surfaceFrame1->setPresentState(SurfaceFrame::PresentState::Presented);
mFrameTimeline->addSurfaceFrame(surfaceFrame1);
mFrameTimeline->setSfPresent(56, presentFence1);
@@ -2030,12 +2296,15 @@ TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffin
// Trigger a flush by finalizing the next DisplayFrame
auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = surfaceFrameToken2;
+ ftInfo2.inputEventId = sInputEventId;
auto surfaceFrame2 =
- mFrameTimeline->createSurfaceFrameForToken({surfaceFrameToken2, sInputEventId}, sPidOne,
- sUidOne, sLayerIdOne, sLayerNameOne,
- sLayerNameOne, /*isBuffer*/ true, sGameMode);
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo2, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
surfaceFrame2->setAcquireFenceTime(80);
- mFrameTimeline->setSfWakeUp(sfToken2, 82, Fps::fromPeriodNsecs(30));
+ mFrameTimeline->setSfWakeUp(sfToken2, 82, RR_30, RR_30);
// Setting previous latch time to 54, adjusted deadline will be 54 + vsyncTime(30) = 84
surfaceFrame2->setPresentState(SurfaceFrame::PresentState::Presented, 54);
mFrameTimeline->addSurfaceFrame(surfaceFrame2);
@@ -2053,10 +2322,12 @@ TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffin
EXPECT_EQ(displayFrame1->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame1->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame1->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame1->getJankSeverityType(), JankSeverityType::None);
EXPECT_EQ(presentedSurfaceFrame1.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame1.getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
EXPECT_EQ(presentedSurfaceFrame1.getJankType(), JankType::AppDeadlineMissed);
+ EXPECT_EQ(presentedSurfaceFrame1.getJankSeverityType(), JankSeverityType::Full);
// Fences for the second frame haven't been flushed yet, so it should be 0
EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
@@ -2073,10 +2344,106 @@ TEST_F(FrameTimelineTest, jankClassification_appDeadlineAdjustedForBufferStuffin
EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(displayFrame2->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::None);
EXPECT_EQ(presentedSurfaceFrame2.getFramePresentMetadata(), FramePresentMetadata::LatePresent);
EXPECT_EQ(presentedSurfaceFrame2.getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
EXPECT_EQ(presentedSurfaceFrame2.getJankType(), JankType::BufferStuffing);
+ EXPECT_EQ(presentedSurfaceFrame2.getJankSeverityType(), JankSeverityType::Full);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_displayFrameLateFinishLatePresent_GpuAndCpuMiss) {
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto presentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto gpuFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+
+ // Case 1: cpu time = 33 - 12 = 21, vsync period = 11
+ mFrameTimeline->setSfWakeUp(sfToken1, 12, RR_11, RR_11);
+ mFrameTimeline->setSfPresent(33, presentFence1, gpuFence1);
+ auto displayFrame = getDisplayFrame(0);
+ gpuFence1->signalForTest(36);
+ presentFence1->signalForTest(52);
+
+ // Fences haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+ displayFrame = getDisplayFrame(0);
+
+ // Fences have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 52);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::SurfaceFlingerGpuDeadlineMissed);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Full);
+
+ // Case 2: No GPU fence so it will not use GPU composition.
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_30, RR_30);
+ mFrameTimeline->setSfPresent(66, presentFence2);
+ auto displayFrame2 = getDisplayFrame(2); // 2 because of previous empty frame
+ presentFence2->signalForTest(90);
+
+ // Fences for the frame haven't been flushed yet, so it should be 0
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 0);
+
+ addEmptyDisplayFrame();
+
+ // Fences have flushed, so the present timestamps should be updated
+ EXPECT_EQ(displayFrame2->getActuals().presentTime, 90);
+ EXPECT_EQ(displayFrame2->getFramePresentMetadata(), FramePresentMetadata::LatePresent);
+ EXPECT_EQ(displayFrame2->getFrameReadyMetadata(), FrameReadyMetadata::LateFinish);
+ EXPECT_EQ(displayFrame2->getJankType(), JankType::SurfaceFlingerCpuDeadlineMissed);
+ EXPECT_EQ(displayFrame2->getJankSeverityType(), JankSeverityType::Full);
+}
+
+TEST_F(FrameTimelineTest, jankClassification_presentFenceError) {
+ auto erroneousPresentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto erroneousPresentFence2 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ auto validPresentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t sfToken1 = mTokenManager->generateTokenForPredictions({22, 26, 40});
+ int64_t sfToken2 = mTokenManager->generateTokenForPredictions({52, 60, 60});
+ int64_t sfToken3 = mTokenManager->generateTokenForPredictions({72, 80, 80});
+
+ mFrameTimeline->setSfWakeUp(sfToken1, 22, RR_11, RR_11);
+ mFrameTimeline->setSfPresent(26, erroneousPresentFence1);
+
+ mFrameTimeline->setSfWakeUp(sfToken2, 52, RR_11, RR_11);
+ mFrameTimeline->setSfPresent(60, erroneousPresentFence2);
+
+ mFrameTimeline->setSfWakeUp(sfToken3, 72, RR_11, RR_11);
+ mFrameTimeline->setSfPresent(80, validPresentFence);
+
+ erroneousPresentFence2->signalForTest(2);
+ validPresentFence->signalForTest(80);
+
+ addEmptyDisplayFrame();
+
+ {
+ auto displayFrame = getDisplayFrame(0);
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 26);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Unknown);
+ }
+ {
+ auto displayFrame = getDisplayFrame(1);
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 60);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::UnknownPresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::UnknownFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::Unknown | JankType::DisplayHAL);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::Unknown);
+ }
+ {
+ auto displayFrame = getDisplayFrame(2);
+ EXPECT_EQ(displayFrame->getActuals().presentTime, 80);
+ EXPECT_EQ(displayFrame->getFramePresentMetadata(), FramePresentMetadata::OnTimePresent);
+ EXPECT_EQ(displayFrame->getFrameReadyMetadata(), FrameReadyMetadata::OnTimeFinish);
+ EXPECT_EQ(displayFrame->getJankType(), JankType::None);
+ EXPECT_EQ(displayFrame->getJankSeverityType(), JankSeverityType::None);
+ }
}
TEST_F(FrameTimelineTest, computeFps_noLayerIds_returnsZero) {
@@ -2237,4 +2604,78 @@ TEST_F(FrameTimelineTest, computeFps_averagesOverMultipleFrames) {
EXPECT_EQ(mFrameTimeline->computeFps({sLayerIdOne}), 5.0f);
}
+TEST_F(FrameTimelineTest, getMinTime) {
+ // Use SurfaceFrame::getBaseTime to test the getMinTime.
+ FrameTimelineInfo ftInfo;
+
+ // Valid prediction state test.
+ ftInfo.vsyncId = 0L;
+ mTokenManager->generateTokenForPredictions({10});
+ auto surfaceFrame =
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ ASSERT_EQ(surfaceFrame->getBaseTime(), 10);
+
+ // Test prediction state which is not valid.
+ ftInfo.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
+ surfaceFrame = mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ // Start time test.
+ surfaceFrame->setActualStartTime(200);
+ ASSERT_EQ(surfaceFrame->getBaseTime(), 200);
+
+ // End time test.
+ surfaceFrame->setAcquireFenceTime(100);
+ ASSERT_EQ(surfaceFrame->getBaseTime(), 100);
+
+ // Present time test.
+ auto presentFence = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
+ presentFence->signalForTest(std::chrono::nanoseconds(50ns).count());
+ mFrameTimeline->setSfPresent(50, presentFence);
+ ASSERT_EQ(surfaceFrame->getBaseTime(), 50);
+}
+
+TEST_F(FrameTimelineTest, surfaceFrameRenderRateUsingDisplayRate) {
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
+ auto surfaceFrame =
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+
+ mFrameTimeline->setSfWakeUp(token1, 20, RR_30, RR_11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
+ presentFence1->signalForTest(std::chrono::nanoseconds(50ns).count());
+ mFrameTimeline->setSfPresent(50, presentFence1);
+
+ EXPECT_EQ(surfaceFrame->getRenderRate().getPeriodNsecs(), 11);
+}
+
+TEST_F(FrameTimelineTest, surfaceFrameRenderRateUsingAppFrameRate) {
+ auto presentFence1 = fenceFactory.createFenceTimeForTest(Fence::NO_FENCE);
+ int64_t token1 = mTokenManager->generateTokenForPredictions({10, 20, 30});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = token1;
+ ftInfo.inputEventId = sInputEventId;
+ auto surfaceFrame =
+ mFrameTimeline->createSurfaceFrameForToken(ftInfo, sPidOne, sUidOne, sLayerIdOne,
+ sLayerNameOne, sLayerNameOne,
+ /*isBuffer*/ true, sGameMode);
+ surfaceFrame->setRenderRate(RR_30);
+ mFrameTimeline->setSfWakeUp(token1, 20, RR_11, RR_11);
+ surfaceFrame->setPresentState(SurfaceFrame::PresentState::Presented);
+ mFrameTimeline->addSurfaceFrame(surfaceFrame);
+ presentFence1->signalForTest(std::chrono::nanoseconds(50ns).count());
+ mFrameTimeline->setSfPresent(50, presentFence1);
+
+ EXPECT_EQ(surfaceFrame->getRenderRate().getPeriodNsecs(), 30);
+}
} // namespace android::frametimeline
diff --git a/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
index 2c71a2e45a..99062f0b0a 100644
--- a/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/FrameTracerTest.cpp
@@ -179,23 +179,35 @@ TEST_F(FrameTracerTest, canTraceAfterAddingLayer) {
tracingSession->StopBlocking();
auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
- EXPECT_EQ(packets.size(), 1);
-
- const auto& packet = packets[0];
- ASSERT_TRUE(packet.has_timestamp());
- EXPECT_EQ(packet.timestamp(), timestamp);
- ASSERT_TRUE(packet.has_graphics_frame_event());
- const auto& frame_event = packet.graphics_frame_event();
- ASSERT_TRUE(frame_event.has_buffer_event());
- const auto& buffer_event = frame_event.buffer_event();
- ASSERT_TRUE(buffer_event.has_buffer_id());
- EXPECT_EQ(buffer_event.buffer_id(), bufferID);
- ASSERT_TRUE(buffer_event.has_frame_number());
- EXPECT_EQ(buffer_event.frame_number(), frameNumber);
- ASSERT_TRUE(buffer_event.has_type());
- EXPECT_EQ(buffer_event.type(), perfetto::protos::GraphicsFrameEvent_BufferEventType(type));
- ASSERT_TRUE(buffer_event.has_duration_ns());
- EXPECT_EQ(buffer_event.duration_ns(), duration);
+ ASSERT_EQ(packets.size(), 2);
+
+ const auto& packet1 = packets[0];
+ ASSERT_TRUE(packet1.has_timestamp());
+ EXPECT_EQ(packet1.timestamp(), timestamp);
+ ASSERT_TRUE(packet1.has_graphics_frame_event());
+ const auto& frame_event1 = packet1.graphics_frame_event();
+ ASSERT_TRUE(frame_event1.has_buffer_event());
+ const auto& buffer_event1 = frame_event1.buffer_event();
+ ASSERT_TRUE(buffer_event1.has_buffer_id());
+ EXPECT_EQ(buffer_event1.buffer_id(), bufferID);
+ ASSERT_TRUE(buffer_event1.has_frame_number());
+ EXPECT_EQ(buffer_event1.frame_number(), frameNumber);
+ ASSERT_TRUE(buffer_event1.has_type());
+ EXPECT_EQ(buffer_event1.type(), perfetto::protos::GraphicsFrameEvent_BufferEventType(type));
+ ASSERT_TRUE(buffer_event1.has_duration_ns());
+ EXPECT_EQ(buffer_event1.duration_ns(), duration);
+
+ const auto& packet2 = packets[1];
+ ASSERT_TRUE(packet2.has_timestamp());
+ EXPECT_EQ(packet2.timestamp(), 0);
+ ASSERT_TRUE(packet2.has_graphics_frame_event());
+ const auto& frame_event2 = packet2.graphics_frame_event();
+ ASSERT_TRUE(frame_event2.has_buffer_event());
+ const auto& buffer_event2 = frame_event2.buffer_event();
+ ASSERT_TRUE(buffer_event2.has_type());
+ EXPECT_EQ(buffer_event2.type(),
+ perfetto::protos::GraphicsFrameEvent_BufferEventType(
+ FrameTracer::FrameEvent::UNSPECIFIED));
}
}
@@ -219,7 +231,18 @@ TEST_F(FrameTracerTest, traceFenceTriggersOnNextTraceAfterFenceFired) {
tracingSession->StopBlocking();
auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
- EXPECT_EQ(packets.size(), 0);
+ ASSERT_EQ(packets.size(), 1);
+ const auto& packet = packets[0];
+ ASSERT_TRUE(packet.has_timestamp());
+ EXPECT_EQ(packet.timestamp(), 0);
+ ASSERT_TRUE(packet.has_graphics_frame_event());
+ const auto& frame_event = packet.graphics_frame_event();
+ ASSERT_TRUE(frame_event.has_buffer_event());
+ const auto& buffer_event = frame_event.buffer_event();
+ ASSERT_TRUE(buffer_event.has_type());
+ EXPECT_EQ(buffer_event.type(),
+ perfetto::protos::GraphicsFrameEvent_BufferEventType(
+ FrameTracer::FrameEvent::UNSPECIFIED));
}
{
@@ -235,22 +258,56 @@ TEST_F(FrameTracerTest, traceFenceTriggersOnNextTraceAfterFenceFired) {
tracingSession->StopBlocking();
auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
- EXPECT_EQ(packets.size(), 2); // Two packets because of the extra trace made above.
-
- const auto& packet = packets[1];
- ASSERT_TRUE(packet.has_timestamp());
- EXPECT_EQ(packet.timestamp(), timestamp);
- ASSERT_TRUE(packet.has_graphics_frame_event());
- const auto& frame_event = packet.graphics_frame_event();
- ASSERT_TRUE(frame_event.has_buffer_event());
- const auto& buffer_event = frame_event.buffer_event();
- ASSERT_TRUE(buffer_event.has_buffer_id());
- EXPECT_EQ(buffer_event.buffer_id(), bufferID);
- ASSERT_TRUE(buffer_event.has_frame_number());
- EXPECT_EQ(buffer_event.frame_number(), frameNumber);
- ASSERT_TRUE(buffer_event.has_type());
- EXPECT_EQ(buffer_event.type(), perfetto::protos::GraphicsFrameEvent_BufferEventType(type));
- EXPECT_FALSE(buffer_event.has_duration_ns());
+ ASSERT_EQ(packets.size(), 3);
+
+ const auto& packet1 = packets[0];
+ ASSERT_TRUE(packet1.has_timestamp());
+ EXPECT_EQ(packet1.timestamp(), timestamp);
+ ASSERT_TRUE(packet1.has_graphics_frame_event());
+ const auto& frame_event1 = packet1.graphics_frame_event();
+ ASSERT_TRUE(frame_event1.has_buffer_event());
+ const auto& buffer_event1 = frame_event1.buffer_event();
+ ASSERT_TRUE(buffer_event1.has_buffer_id());
+ EXPECT_EQ(buffer_event1.buffer_id(), bufferID);
+ ASSERT_TRUE(buffer_event1.has_frame_number());
+ EXPECT_EQ(buffer_event1.frame_number(), frameNumber);
+ ASSERT_TRUE(buffer_event1.has_type());
+ EXPECT_EQ(buffer_event1.type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+ EXPECT_FALSE(buffer_event1.has_duration_ns());
+
+ const auto& packet2 = packets[1];
+ ASSERT_TRUE(packet2.has_timestamp());
+ EXPECT_EQ(packet2.timestamp(), timestamp);
+ ASSERT_TRUE(packet2.has_graphics_frame_event());
+ const auto& frame_event2 = packet2.graphics_frame_event();
+ ASSERT_TRUE(frame_event2.has_buffer_event());
+ const auto& buffer_event2 = frame_event2.buffer_event();
+ ASSERT_TRUE(buffer_event2.has_buffer_id());
+ EXPECT_EQ(buffer_event2.buffer_id(), bufferID);
+ ASSERT_TRUE(buffer_event2.has_frame_number());
+ EXPECT_EQ(buffer_event2.frame_number(), frameNumber);
+ ASSERT_TRUE(buffer_event2.has_type());
+ EXPECT_EQ(buffer_event2.type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+ EXPECT_FALSE(buffer_event2.has_duration_ns());
+
+ const auto& packet3 = packets[2];
+ ASSERT_TRUE(packet3.has_timestamp());
+ EXPECT_EQ(packet3.timestamp(), 0);
+ ASSERT_TRUE(packet3.has_graphics_frame_event());
+ const auto& frame_event3 = packet3.graphics_frame_event();
+ ASSERT_TRUE(frame_event3.has_buffer_event());
+ const auto& buffer_event3 = frame_event3.buffer_event();
+ ASSERT_TRUE(buffer_event3.has_buffer_id());
+ EXPECT_EQ(buffer_event3.buffer_id(), bufferID);
+ ASSERT_TRUE(buffer_event3.has_frame_number());
+ EXPECT_EQ(buffer_event3.frame_number(), 0);
+ ASSERT_TRUE(buffer_event3.has_type());
+ EXPECT_EQ(buffer_event3.type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(
+ FrameTracer::FrameEvent::UNSPECIFIED));
+ EXPECT_FALSE(buffer_event3.has_duration_ns());
}
}
@@ -285,7 +342,7 @@ TEST_F(FrameTracerTest, traceFenceWithStartTimeAfterSignalTime_ShouldHaveNoDurat
tracingSession->StopBlocking();
auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
- EXPECT_EQ(packets.size(), 2);
+ ASSERT_EQ(packets.size(), 3);
const auto& packet1 = packets[0];
ASSERT_TRUE(packet1.has_timestamp());
@@ -293,6 +350,8 @@ TEST_F(FrameTracerTest, traceFenceWithStartTimeAfterSignalTime_ShouldHaveNoDurat
ASSERT_TRUE(packet1.has_graphics_frame_event());
ASSERT_TRUE(packet1.graphics_frame_event().has_buffer_event());
ASSERT_FALSE(packet1.graphics_frame_event().buffer_event().has_duration_ns());
+ EXPECT_EQ(packet1.graphics_frame_event().buffer_event().type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
const auto& packet2 = packets[1];
ASSERT_TRUE(packet2.has_timestamp());
@@ -300,6 +359,17 @@ TEST_F(FrameTracerTest, traceFenceWithStartTimeAfterSignalTime_ShouldHaveNoDurat
ASSERT_TRUE(packet2.has_graphics_frame_event());
ASSERT_TRUE(packet2.graphics_frame_event().has_buffer_event());
ASSERT_FALSE(packet2.graphics_frame_event().buffer_event().has_duration_ns());
+ EXPECT_EQ(packet2.graphics_frame_event().buffer_event().type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+
+ const auto& packet3 = packets[2];
+ ASSERT_TRUE(packet3.has_timestamp());
+ EXPECT_EQ(packet3.timestamp(), 0);
+ ASSERT_TRUE(packet3.has_graphics_frame_event());
+ ASSERT_TRUE(packet3.graphics_frame_event().has_buffer_event());
+ EXPECT_EQ(packet3.graphics_frame_event().buffer_event().type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(
+ FrameTracer::FrameEvent::UNSPECIFIED));
}
TEST_F(FrameTracerTest, traceFenceOlderThanDeadline_ShouldBeIgnored) {
@@ -322,7 +392,15 @@ TEST_F(FrameTracerTest, traceFenceOlderThanDeadline_ShouldBeIgnored) {
tracingSession->StopBlocking();
auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
- EXPECT_EQ(packets.size(), 0);
+ ASSERT_EQ(packets.size(), 1);
+ const auto& packet = packets[0];
+ ASSERT_TRUE(packet.has_timestamp());
+ EXPECT_EQ(packet.timestamp(), 0);
+ ASSERT_TRUE(packet.has_graphics_frame_event());
+ ASSERT_TRUE(packet.graphics_frame_event().has_buffer_event());
+ EXPECT_EQ(packet.graphics_frame_event().buffer_event().type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(
+ FrameTracer::FrameEvent::UNSPECIFIED));
}
TEST_F(FrameTracerTest, traceFenceWithValidStartTime_ShouldHaveCorrectDuration) {
@@ -357,7 +435,7 @@ TEST_F(FrameTracerTest, traceFenceWithValidStartTime_ShouldHaveCorrectDuration)
tracingSession->StopBlocking();
auto packets = readGraphicsFramePacketsBlocking(tracingSession.get());
- EXPECT_EQ(packets.size(), 2);
+ ASSERT_EQ(packets.size(), 3);
const auto& packet1 = packets[0];
ASSERT_TRUE(packet1.has_timestamp());
@@ -367,6 +445,7 @@ TEST_F(FrameTracerTest, traceFenceWithValidStartTime_ShouldHaveCorrectDuration)
ASSERT_TRUE(packet1.graphics_frame_event().buffer_event().has_duration_ns());
const auto& buffer_event1 = packet1.graphics_frame_event().buffer_event();
EXPECT_EQ(buffer_event1.duration_ns(), duration);
+ EXPECT_EQ(buffer_event1.type(), perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
const auto& packet2 = packets[1];
ASSERT_TRUE(packet2.has_timestamp());
@@ -376,6 +455,17 @@ TEST_F(FrameTracerTest, traceFenceWithValidStartTime_ShouldHaveCorrectDuration)
ASSERT_TRUE(packet2.graphics_frame_event().buffer_event().has_duration_ns());
const auto& buffer_event2 = packet2.graphics_frame_event().buffer_event();
EXPECT_EQ(buffer_event2.duration_ns(), duration);
+ EXPECT_EQ(buffer_event2.type(), perfetto::protos::GraphicsFrameEvent::BufferEventType(type));
+
+ const auto& packet3 = packets[2];
+ ASSERT_TRUE(packet3.has_timestamp());
+ EXPECT_EQ(packet3.timestamp(), 0);
+ ASSERT_TRUE(packet3.has_graphics_frame_event());
+ ASSERT_TRUE(packet3.graphics_frame_event().has_buffer_event());
+ const auto& buffer_event3 = packet3.graphics_frame_event().buffer_event();
+ EXPECT_EQ(buffer_event3.type(),
+ perfetto::protos::GraphicsFrameEvent::BufferEventType(
+ FrameTracer::FrameEvent::UNSPECIFIED));
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/GameModeTest.cpp b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
index 981ca1d227..1b5c6e70f8 100644
--- a/services/surfaceflinger/tests/unittests/GameModeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/GameModeTest.cpp
@@ -25,15 +25,15 @@
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
-#include "mock/MockVsyncController.h"
namespace android {
using testing::_;
using testing::Mock;
using testing::Return;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+using gui::GameMode;
+using gui::LayerMetadata;
class GameModeTest : public testing::Test {
public:
@@ -41,7 +41,7 @@ public:
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());
- setupScheduler();
+ mFlinger.setupMockScheduler();
setupComposer();
}
@@ -51,36 +51,10 @@ public:
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
- sp<BufferStateLayer> createBufferStateLayer() {
+ sp<Layer> createLayer() {
sp<Client> client;
- LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0,
- LayerMetadata());
- return new BufferStateLayer(args);
- }
-
- void setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread));
+ LayerCreationArgs args(mFlinger.flinger(), client, "layer", 0, LayerMetadata());
+ return sp<Layer>::make(args);
}
void setupComposer() {
@@ -92,7 +66,7 @@ public:
// Mocks the behavior of applying a transaction from WMShell
void setGameModeMetadata(sp<Layer> layer, GameMode gameMode) {
- mLayerMetadata.setInt32(METADATA_GAME_MODE, static_cast<int32_t>(gameMode));
+ mLayerMetadata.setInt32(gui::METADATA_GAME_MODE, static_cast<int32_t>(gameMode));
layer->setMetadata(mLayerMetadata);
layer->setGameModeForTree(gameMode);
}
@@ -104,9 +78,9 @@ public:
};
TEST_F(GameModeTest, SetGameModeSetsForAllCurrentChildren) {
- sp<BufferStateLayer> rootLayer = createBufferStateLayer();
- sp<BufferStateLayer> childLayer1 = createBufferStateLayer();
- sp<BufferStateLayer> childLayer2 = createBufferStateLayer();
+ sp<Layer> rootLayer = createLayer();
+ sp<Layer> childLayer1 = createLayer();
+ sp<Layer> childLayer2 = createLayer();
rootLayer->addChild(childLayer1);
rootLayer->addChild(childLayer2);
rootLayer->setGameModeForTree(GameMode::Performance);
@@ -117,8 +91,8 @@ TEST_F(GameModeTest, SetGameModeSetsForAllCurrentChildren) {
}
TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) {
- sp<BufferStateLayer> rootLayer = createBufferStateLayer();
- sp<BufferStateLayer> childLayer = createBufferStateLayer();
+ sp<Layer> rootLayer = createLayer();
+ sp<Layer> childLayer = createLayer();
rootLayer->setGameModeForTree(GameMode::Performance);
rootLayer->addChild(childLayer);
@@ -127,8 +101,8 @@ TEST_F(GameModeTest, AddChildAppliesGameModeFromParent) {
}
TEST_F(GameModeTest, RemoveChildResetsGameMode) {
- sp<BufferStateLayer> rootLayer = createBufferStateLayer();
- sp<BufferStateLayer> childLayer = createBufferStateLayer();
+ sp<Layer> rootLayer = createLayer();
+ sp<Layer> childLayer = createLayer();
rootLayer->setGameModeForTree(GameMode::Performance);
rootLayer->addChild(childLayer);
@@ -140,9 +114,9 @@ TEST_F(GameModeTest, RemoveChildResetsGameMode) {
}
TEST_F(GameModeTest, ReparentingDoesNotOverrideMetadata) {
- sp<BufferStateLayer> rootLayer = createBufferStateLayer();
- sp<BufferStateLayer> childLayer1 = createBufferStateLayer();
- sp<BufferStateLayer> childLayer2 = createBufferStateLayer();
+ sp<Layer> rootLayer = createLayer();
+ sp<Layer> childLayer1 = createLayer();
+ sp<Layer> childLayer2 = createLayer();
rootLayer->setGameModeForTree(GameMode::Standard);
rootLayer->addChild(childLayer1);
diff --git a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
index 5241604cd0..6edecff6a6 100644
--- a/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/HWComposerTest.cpp
@@ -30,9 +30,12 @@
#include <gmock/gmock.h>
#pragma clang diagnostic pop
+#include <common/FlagManager.h>
#include <gui/LayerMetadata.h>
#include <log/log.h>
+#include <chrono>
+#include <common/test/FlagUtils.h>
#include "DisplayHardware/DisplayMode.h"
#include "DisplayHardware/HWComposer.h"
#include "DisplayHardware/Hal.h"
@@ -40,50 +43,374 @@
#include "mock/DisplayHardware/MockComposer.h"
#include "mock/DisplayHardware/MockHWC2.h"
+#include <com_android_graphics_surfaceflinger_flags.h>
+
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion"
namespace android {
-namespace {
namespace V2_1 = hardware::graphics::composer::V2_1;
namespace V2_4 = hardware::graphics::composer::V2_4;
namespace aidl = aidl::android::hardware::graphics::composer3;
+using namespace std::chrono_literals;
using Hwc2::Config;
+using ::aidl::android::hardware::graphics::common::DisplayHotplugEvent;
+using ::aidl::android::hardware::graphics::composer3::RefreshRateChangedDebugData;
+using hal::IComposerClient;
using ::testing::_;
using ::testing::DoAll;
-using ::testing::ElementsAreArray;
using ::testing::Return;
using ::testing::SetArgPointee;
using ::testing::StrictMock;
-TEST(HWComposerTest, isHeadless) {
- Hwc2::mock::Composer* mHal = new StrictMock<Hwc2::mock::Composer>();
- impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
- ASSERT_TRUE(hwc.isHeadless());
+struct HWComposerTest : testing::Test {
+ using HalError = hardware::graphics::composer::V2_1::Error;
+
+ Hwc2::mock::Composer* const mHal = new StrictMock<Hwc2::mock::Composer>();
+ impl::HWComposer mHwc{std::unique_ptr<Hwc2::Composer>(mHal)};
+
+ void expectHotplugConnect(hal::HWDisplayId hwcDisplayId) {
+ constexpr uint8_t kPort = 255;
+ EXPECT_CALL(*mHal, getDisplayIdentificationData(hwcDisplayId, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kPort),
+ SetArgPointee<2>(getExternalEdid()), Return(HalError::NONE)));
+
+ EXPECT_CALL(*mHal, setClientTargetSlotCount(_));
+ EXPECT_CALL(*mHal, setVsyncEnabled(hwcDisplayId, Hwc2::IComposerClient::Vsync::DISABLE));
+ EXPECT_CALL(*mHal, onHotplugConnect(hwcDisplayId));
+ }
+};
+
+TEST_F(HWComposerTest, isHeadless) {
+ ASSERT_TRUE(mHwc.isHeadless());
+
+ constexpr hal::HWDisplayId kHwcDisplayId = 1;
+ expectHotplugConnect(kHwcDisplayId);
+
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+ ASSERT_TRUE(info);
+
+ ASSERT_FALSE(mHwc.isHeadless());
+
+ mHwc.disconnectDisplay(info->id);
+ ASSERT_TRUE(mHwc.isHeadless());
+}
+
+TEST_F(HWComposerTest, getActiveMode) {
+ // Unknown display.
+ EXPECT_EQ(mHwc.getActiveMode(PhysicalDisplayId::fromPort(0)), std::nullopt);
+
+ constexpr hal::HWDisplayId kHwcDisplayId = 2;
+ expectHotplugConnect(kHwcDisplayId);
- const hal::HWDisplayId hwcId = 1;
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+ ASSERT_TRUE(info);
- EXPECT_CALL(*mHal, getDisplayIdentificationData(_, _, _))
- .WillOnce(DoAll(SetArgPointee<2>(getExternalEdid()),
- Return(hardware::graphics::composer::V2_1::Error::NONE)));
+ {
+ // Display is known to SF but not HWC, e.g. the hotplug disconnect is pending.
+ EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
+ .WillOnce(Return(HalError::BAD_DISPLAY));
- EXPECT_CALL(*mHal, setVsyncEnabled(_, _));
- EXPECT_CALL(*mHal, setClientTargetSlotCount(_));
+ EXPECT_EQ(mHwc.getActiveMode(info->id), std::nullopt);
+ }
+ {
+ constexpr hal::HWConfigId kConfigId = 42;
+ EXPECT_CALL(*mHal, getActiveConfig(kHwcDisplayId, _))
+ .WillOnce(DoAll(SetArgPointee<1>(kConfigId), Return(HalError::NONE)));
+
+ EXPECT_EQ(mHwc.getActiveMode(info->id), kConfigId);
+ }
+}
- auto info = hwc.onHotplug(hwcId, hal::Connection::CONNECTED);
+TEST_F(HWComposerTest, getModesWithLegacyDisplayConfigs) {
+ constexpr hal::HWDisplayId kHwcDisplayId = 2;
+ constexpr hal::HWConfigId kConfigId = 42;
+ constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+
+ expectHotplugConnect(kHwcDisplayId);
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
ASSERT_TRUE(info);
- auto displayId = info->id;
- ASSERT_FALSE(hwc.isHeadless());
- hwc.disconnectDisplay(displayId);
- ASSERT_TRUE(hwc.isHeadless());
+ EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
+
+ {
+ EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+ .WillOnce(Return(HalError::BAD_DISPLAY));
+ EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+ }
+ {
+ constexpr int32_t kWidth = 480;
+ constexpr int32_t kHeight = 720;
+ constexpr int32_t kConfigGroup = 1;
+ constexpr int32_t kVsyncPeriod = 16666667;
+
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
+ _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kWidth), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId,
+ IComposerClient::Attribute::HEIGHT, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kHeight), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId,
+ IComposerClient::Attribute::CONFIG_GROUP, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kConfigGroup), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId,
+ IComposerClient::Attribute::VSYNC_PERIOD, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kVsyncPeriod), Return(HalError::NONE)));
+
+ // Optional Parameters UNSUPPORTED
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+ _))
+ .WillOnce(Return(HalError::UNSUPPORTED));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+ _))
+ .WillOnce(Return(HalError::UNSUPPORTED));
+
+ EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{kConfigId}),
+ Return(HalError::NONE)));
+
+ auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+ EXPECT_EQ(modes.size(), size_t{1});
+ EXPECT_EQ(modes.front().hwcId, kConfigId);
+ EXPECT_EQ(modes.front().width, kWidth);
+ EXPECT_EQ(modes.front().height, kHeight);
+ EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+ EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+ EXPECT_EQ(modes.front().dpiX, -1);
+ EXPECT_EQ(modes.front().dpiY, -1);
+
+ // Optional parameters are supported
+ constexpr int32_t kDpi = 320;
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+ _))
+ .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+ _))
+ .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+
+ modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+ EXPECT_EQ(modes.size(), size_t{1});
+ EXPECT_EQ(modes.front().hwcId, kConfigId);
+ EXPECT_EQ(modes.front().width, kWidth);
+ EXPECT_EQ(modes.front().height, kHeight);
+ EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+ EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+ // DPI values are scaled by 1000 in the legacy implementation.
+ EXPECT_EQ(modes.front().dpiX, kDpi / 1000.f);
+ EXPECT_EQ(modes.front().dpiY, kDpi / 1000.f);
+ }
+}
+
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_OFF) {
+ // if vrr_config is off, getDisplayConfigurationsSupported() is off as well
+ // then getModesWithLegacyDisplayConfigs should be called instead
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, false);
+ ASSERT_FALSE(FlagManager::getInstance().vrr_config());
+
+ constexpr hal::HWDisplayId kHwcDisplayId = 2;
+ constexpr hal::HWConfigId kConfigId = 42;
+ constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+
+ expectHotplugConnect(kHwcDisplayId);
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+ ASSERT_TRUE(info);
+
+ EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(false));
+
+ {
+ EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+ .WillOnce(Return(HalError::BAD_DISPLAY));
+ EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+ }
+ {
+ constexpr int32_t kWidth = 480;
+ constexpr int32_t kHeight = 720;
+ constexpr int32_t kConfigGroup = 1;
+ constexpr int32_t kVsyncPeriod = 16666667;
+
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::WIDTH,
+ _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kWidth), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId,
+ IComposerClient::Attribute::HEIGHT, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kHeight), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId,
+ IComposerClient::Attribute::CONFIG_GROUP, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kConfigGroup), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId,
+ IComposerClient::Attribute::VSYNC_PERIOD, _))
+ .WillRepeatedly(DoAll(SetArgPointee<3>(kVsyncPeriod), Return(HalError::NONE)));
+
+ // Optional Parameters UNSUPPORTED
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+ _))
+ .WillOnce(Return(HalError::UNSUPPORTED));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+ _))
+ .WillOnce(Return(HalError::UNSUPPORTED));
+
+ EXPECT_CALL(*mHal, getDisplayConfigs(kHwcDisplayId, _))
+ .WillRepeatedly(DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{kConfigId}),
+ Return(HalError::NONE)));
+
+ auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+ EXPECT_EQ(modes.size(), size_t{1});
+ EXPECT_EQ(modes.front().hwcId, kConfigId);
+ EXPECT_EQ(modes.front().width, kWidth);
+ EXPECT_EQ(modes.front().height, kHeight);
+ EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+ EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+ EXPECT_EQ(modes.front().dpiX, -1);
+ EXPECT_EQ(modes.front().dpiY, -1);
+
+ // Optional parameters are supported
+ constexpr int32_t kDpi = 320;
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_X,
+ _))
+ .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+ EXPECT_CALL(*mHal,
+ getDisplayAttribute(kHwcDisplayId, kConfigId, IComposerClient::Attribute::DPI_Y,
+ _))
+ .WillOnce(DoAll(SetArgPointee<3>(kDpi), Return(HalError::NONE)));
+
+ modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+ EXPECT_EQ(modes.size(), size_t{1});
+ EXPECT_EQ(modes.front().hwcId, kConfigId);
+ EXPECT_EQ(modes.front().width, kWidth);
+ EXPECT_EQ(modes.front().height, kHeight);
+ EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+ EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+ // DPI values are scaled by 1000 in the legacy implementation.
+ EXPECT_EQ(modes.front().dpiX, kDpi / 1000.f);
+ EXPECT_EQ(modes.front().dpiY, kDpi / 1000.f);
+ }
+}
+
+TEST_F(HWComposerTest, getModesWithDisplayConfigurations_VRR_ON) {
+ SET_FLAG_FOR_TEST(com::android::graphics::surfaceflinger::flags::vrr_config, true);
+ ASSERT_TRUE(FlagManager::getInstance().vrr_config());
+
+ constexpr hal::HWDisplayId kHwcDisplayId = 2;
+ constexpr hal::HWConfigId kConfigId = 42;
+ constexpr int32_t kMaxFrameIntervalNs = 50000000; // 20Fps
+ expectHotplugConnect(kHwcDisplayId);
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+ ASSERT_TRUE(info);
+
+ EXPECT_CALL(*mHal, isVrrSupported()).WillRepeatedly(Return(true));
+
+ {
+ EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+ .WillOnce(Return(HalError::BAD_DISPLAY));
+ EXPECT_TRUE(mHwc.getModes(info->id, kMaxFrameIntervalNs).empty());
+ }
+ {
+ constexpr int32_t kWidth = 480;
+ constexpr int32_t kHeight = 720;
+ constexpr int32_t kConfigGroup = 1;
+ constexpr int32_t kVsyncPeriod = 16666667;
+ const hal::VrrConfig vrrConfig =
+ hal::VrrConfig{.minFrameIntervalNs = static_cast<Fps>(120_Hz).getPeriodNsecs(),
+ .notifyExpectedPresentConfig = hal::VrrConfig::
+ NotifyExpectedPresentConfig{.notifyExpectedPresentHeadsUpNs =
+ ms2ns(30),
+ .notifyExpectedPresentTimeoutNs =
+ ms2ns(30)}};
+ hal::DisplayConfiguration displayConfiguration{.configId = kConfigId,
+ .width = kWidth,
+ .height = kHeight,
+ .configGroup = kConfigGroup,
+ .vsyncPeriod = kVsyncPeriod,
+ .vrrConfig = vrrConfig};
+
+ EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+ displayConfiguration}),
+ Return(HalError::NONE)));
+
+ // Optional dpi not supported
+ auto modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+ EXPECT_EQ(modes.size(), size_t{1});
+ EXPECT_EQ(modes.front().hwcId, kConfigId);
+ EXPECT_EQ(modes.front().width, kWidth);
+ EXPECT_EQ(modes.front().height, kHeight);
+ EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+ EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+ EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
+ EXPECT_EQ(modes.front().dpiX, -1);
+ EXPECT_EQ(modes.front().dpiY, -1);
+
+ // Supports optional dpi parameter
+ constexpr int32_t kDpi = 320;
+ displayConfiguration.dpi = {kDpi, kDpi};
+
+ EXPECT_CALL(*mHal, getDisplayConfigurations(kHwcDisplayId, _, _))
+ .WillOnce(DoAll(SetArgPointee<2>(std::vector<hal::DisplayConfiguration>{
+ displayConfiguration}),
+ Return(HalError::NONE)));
+
+ modes = mHwc.getModes(info->id, kMaxFrameIntervalNs);
+ EXPECT_EQ(modes.size(), size_t{1});
+ EXPECT_EQ(modes.front().hwcId, kConfigId);
+ EXPECT_EQ(modes.front().width, kWidth);
+ EXPECT_EQ(modes.front().height, kHeight);
+ EXPECT_EQ(modes.front().configGroup, kConfigGroup);
+ EXPECT_EQ(modes.front().vsyncPeriod, kVsyncPeriod);
+ EXPECT_EQ(modes.front().vrrConfig, vrrConfig);
+ EXPECT_EQ(modes.front().dpiX, kDpi);
+ EXPECT_EQ(modes.front().dpiY, kDpi);
+ }
+}
+
+TEST_F(HWComposerTest, onVsync) {
+ constexpr hal::HWDisplayId kHwcDisplayId = 1;
+ expectHotplugConnect(kHwcDisplayId);
+
+ const auto info = mHwc.onHotplug(kHwcDisplayId, hal::Connection::CONNECTED);
+ ASSERT_TRUE(info);
+
+ const auto physicalDisplayId = info->id;
+
+ // Deliberately chosen not to match DisplayData.lastPresentTimestamp's
+ // initial value.
+ constexpr nsecs_t kTimestamp = 1;
+ auto displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp);
+ ASSERT_TRUE(displayIdOpt);
+ EXPECT_EQ(physicalDisplayId, displayIdOpt);
+
+ // Attempt to send the same time stamp again.
+ displayIdOpt = mHwc.onVsync(kHwcDisplayId, kTimestamp);
+ EXPECT_FALSE(displayIdOpt);
+}
+
+TEST_F(HWComposerTest, onVsyncInvalid) {
+ constexpr hal::HWDisplayId kInvalidHwcDisplayId = 2;
+ constexpr nsecs_t kTimestamp = 1;
+ const auto displayIdOpt = mHwc.onVsync(kInvalidHwcDisplayId, kTimestamp);
+ EXPECT_FALSE(displayIdOpt);
}
struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
- MOCK_METHOD2(onComposerHalHotplug, void(hal::HWDisplayId, hal::Connection));
+ MOCK_METHOD(void, onComposerHalHotplugEvent, (hal::HWDisplayId, DisplayHotplugEvent),
+ (override));
MOCK_METHOD1(onComposerHalRefresh, void(hal::HWDisplayId));
MOCK_METHOD3(onComposerHalVsync,
void(hal::HWDisplayId, int64_t timestamp, std::optional<hal::VsyncPeriodNanos>));
@@ -91,10 +418,10 @@ struct MockHWC2ComposerCallback final : StrictMock<HWC2::ComposerCallback> {
void(hal::HWDisplayId, const hal::VsyncPeriodChangeTimeline&));
MOCK_METHOD1(onComposerHalSeamlessPossible, void(hal::HWDisplayId));
MOCK_METHOD1(onComposerHalVsyncIdle, void(hal::HWDisplayId));
+ MOCK_METHOD(void, onRefreshRateChangedDebug, (const RefreshRateChangedDebugData&), (override));
};
-struct HWComposerSetCallbackTest : testing::Test {
- Hwc2::mock::Composer* mHal = new StrictMock<Hwc2::mock::Composer>();
+struct HWComposerSetCallbackTest : HWComposerTest {
MockHWC2ComposerCallback mCallback;
};
@@ -111,12 +438,14 @@ TEST_F(HWComposerSetCallbackTest, loadsLayerMetadataSupport) {
{kMetadata2Name, kMetadata2Mandatory},
}),
Return(hardware::graphics::composer::V2_4::Error::NONE)));
+ EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::NONE));
+ EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::NONE));
+
EXPECT_CALL(*mHal, registerCallback(_));
- impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
- hwc.setCallback(mCallback);
+ mHwc.setCallback(mCallback);
- const auto& supported = hwc.getSupportedLayerGenericMetadata();
+ const auto& supported = mHwc.getSupportedLayerGenericMetadata();
EXPECT_EQ(2u, supported.size());
EXPECT_EQ(1u, supported.count(kMetadata1Name));
EXPECT_EQ(kMetadata1Mandatory, supported.find(kMetadata1Name)->second);
@@ -128,13 +457,14 @@ TEST_F(HWComposerSetCallbackTest, handlesUnsupportedCallToGetLayerGenericMetadat
EXPECT_CALL(*mHal, getCapabilities()).WillOnce(Return(std::vector<aidl::Capability>{}));
EXPECT_CALL(*mHal, getLayerGenericMetadataKeys(_))
.WillOnce(Return(hardware::graphics::composer::V2_4::Error::UNSUPPORTED));
+ EXPECT_CALL(*mHal, getOverlaySupport(_)).WillOnce(Return(HalError::UNSUPPORTED));
+ EXPECT_CALL(*mHal, getHdrConversionCapabilities(_)).WillOnce(Return(HalError::UNSUPPORTED));
EXPECT_CALL(*mHal, registerCallback(_));
- impl::HWComposer hwc{std::unique_ptr<Hwc2::Composer>(mHal)};
- hwc.setCallback(mCallback);
+ mHwc.setCallback(mCallback);
- const auto& supported = hwc.getSupportedLayerGenericMetadata();
- EXPECT_EQ(0u, supported.size());
+ const auto& supported = mHwc.getSupportedLayerGenericMetadata();
+ EXPECT_TRUE(supported.empty());
}
struct HWComposerLayerTest : public testing::Test {
@@ -203,5 +533,4 @@ TEST_F(HWComposerLayerGenericMetadataTest, forwardsSupportedMetadata) {
EXPECT_EQ(hal::Error::UNSUPPORTED, result);
}
-} // namespace
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
new file mode 100644
index 0000000000..95f19406b4
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.cpp
@@ -0,0 +1,757 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "LayerHierarchyTest.h"
+
+#define UPDATE_AND_VERIFY(HIERARCHY) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((HIERARCHY)); \
+ })
+
+namespace android::surfaceflinger::frontend {
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+ /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+ --gtest_filter="LayerHierarchyTest.*" --gtest_repeat=100 \
+ --gtest_shuffle \
+ --gtest_brief=1
+*/
+
+class LayerHierarchyTest : public LayerHierarchyTestBase {
+protected:
+ LayerHierarchyTest() : LayerHierarchyTestBase() { mLifecycleManager.commitChanges(); }
+};
+
+// reparenting tests
+TEST_F(LayerHierarchyTest, addLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ createRootLayer(3);
+ createLayer(112, 11);
+ createLayer(12211, 1221);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ expectedTraversalPath = {1, 11, 111, 112, 12, 121, 122, 1221, 12211, 13, 2, 3};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentLayer(2, 11);
+ reparentLayer(111, 12);
+ reparentLayer(1221, 1);
+ reparentLayer(1221, 13);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 12, 111, 121, 122, 13, 1221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentLayerToNull) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ reparentLayer(2, UNASSIGNED_LAYER_ID);
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ reparentLayer(1221, 13);
+ reparentLayer(1221, UNASSIGNED_LAYER_ID);
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {2, 11, 111, 1221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentLayerToNullAndDestroyHandles) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentLayer(2, UNASSIGNED_LAYER_ID);
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ reparentLayer(1221, UNASSIGNED_LAYER_ID);
+
+ destroyLayerHandle(2);
+ destroyLayerHandle(11);
+ destroyLayerHandle(1221);
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, destroyHandleThenDestroyParentLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ destroyLayerHandle(111);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ // handle is destroyed but layer is kept alive and reachable by parent
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ // destroy parent layer and the child gets destroyed
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ destroyLayerHandle(11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, layerSurvivesTemporaryReparentToNull) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ reparentLayer(11, 1);
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// offscreen tests
+TEST_F(LayerHierarchyTest, layerMovesOnscreen) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ reparentLayer(11, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, addLayerToOffscreenParent) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ createLayer(112, 11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {11, 111, 112};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// rel-z tests
+TEST_F(LayerHierarchyTest, setRelativeParent) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentFromRelativeParentWithSetLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ // This calls setLayer
+ removeRelativeZ(11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentToRelativeParent) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ reparentLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, setParentAsRelativeParent) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ reparentRelativeLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, relativeChildMovesOffscreenIsNotTraversable) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ reparentLayer(2, UNASSIGNED_LAYER_ID);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {2, 11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, reparentRelativeLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(11, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ reparentLayer(11, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 2, 11, 111};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ setZ(11, 0);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// mirror tests
+TEST_F(LayerHierarchyTest, canTraverseMirrorLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122,
+ 1221, 13, 14, 11, 111, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, canMirrorOffscreenLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ reparentLayer(11, UNASSIGNED_LAYER_ID);
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 12, 121, 122, 1221, 13, 14, 11, 111, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, newChildLayerIsUpdatedInMirrorHierarchy) {
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ createLayer(1111, 111);
+ createLayer(112, 11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 1111, 112, 12, 121, 122,
+ 1221, 13, 14, 11, 111, 1111, 112, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// mirror & relatives tests
+TEST_F(LayerHierarchyTest, mirrorWithRelativeOutsideMirrorHierarchy) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(111, 12);
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ ├── 122
+ // │ │ │ └── 1221
+ // │ │ └ - 111 (relative)
+ // │ ├── 13
+ // │ └── 14
+ // │ └ * 11 (mirroring)
+ // └── 2
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 111, 121, 122,
+ 1221, 13, 14, 11, 111, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ // 111 is not reachable in the mirror
+ expectedTraversalPath = {1, 11, 12, 111, 121, 122, 1221, 13, 14, 11, 2};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, mirrorWithRelativeInsideMirrorHierarchy) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(1221, 12);
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 12);
+
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ ├── 122
+ // │ │ │ └── 1221
+ // │ │ └ - 1221 (relative)
+ // │ ├── 13
+ // │ └── 14
+ // │ └ * 12 (mirroring)
+ // └── 2
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 1221,
+ 13, 14, 12, 121, 122, 1221, 1221, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ // relative layer 1221 is traversable in the mirrored hierarchy as well
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 14, 12, 121, 122, 1221, 2};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, childMovesOffscreenWhenRelativeParentDies) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ reparentRelativeLayer(11, 2);
+ reparentLayer(2, UNASSIGNED_LAYER_ID);
+ destroyLayerHandle(2);
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {11, 111};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ // remove relative parent so layer becomes onscreen again
+ removeRelativeZ(11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, offscreenLayerCannotBeRelativeToOnscreenLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentRelativeLayer(1221, 2);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ // verify relz path
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 1221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 13, 2, 1221};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ // offscreen layer cannot be reached as a relative child
+ reparentLayer(12, UNASSIGNED_LAYER_ID);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ expectedTraversalPath = {1, 11, 111, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {12, 121, 122, 1221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ // layer when onscreen can be reached as a relative child again
+ reparentLayer(12, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 1221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 13, 2, 1221};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, backgroundLayersAreBehindParentLayer) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ updateBackgroundColor(1, 0.5);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ auto hierarchy = hierarchyBuilder.getPartialHierarchy(1, /*childrenOnly=*/true);
+ auto bgLayerId = hierarchy.mChildren.front().first->getLayer()->id;
+ std::vector<uint32_t> expectedTraversalPath = {1, bgLayerId, 11, 111, 12,
+ 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {bgLayerId, 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// cycle tests
+TEST_F(LayerHierarchyTest, ParentBecomesTheChild) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ createRootLayer(1);
+ createLayer(11, 1);
+ reparentLayer(1, 11);
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ std::vector<uint32_t> expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, RelativeLoops) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ createRootLayer(1);
+ createRootLayer(2);
+ createLayer(11, 1);
+ reparentRelativeLayer(11, 2);
+ reparentRelativeLayer(2, 11);
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ // fix loop
+ uint32_t invalidRelativeRoot;
+ bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
+ EXPECT_TRUE(hasRelZLoop);
+ mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+ hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
+ EXPECT_EQ(invalidRelativeRoot, 11u);
+ EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {11, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, IndirectRelativeLoops) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ createRootLayer(1);
+ createRootLayer(2);
+ createLayer(11, 1);
+ createLayer(111, 11);
+ createLayer(21, 2);
+ createLayer(22, 2);
+ createLayer(221, 22);
+ reparentRelativeLayer(22, 111);
+ reparentRelativeLayer(11, 221);
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+
+ // fix loop
+ uint32_t invalidRelativeRoot;
+ bool hasRelZLoop = hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot);
+ EXPECT_TRUE(hasRelZLoop);
+ mLifecycleManager.fixRelativeZLoop(invalidRelativeRoot);
+ hierarchyBuilder.update(mLifecycleManager.getLayers(), mLifecycleManager.getDestroyedLayers());
+ EXPECT_FALSE(hierarchyBuilder.getHierarchy().hasRelZLoop(invalidRelativeRoot));
+
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 111, 22, 221, 2, 21, 22, 221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 2, 21};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {11, 111, 22, 221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, ReparentRootLayerToNull) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentLayer(1, UNASSIGNED_LAYER_ID);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {1, 11, 111, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, AddRemoveLayerInSameTransaction) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ createRootLayer(1);
+ destroyLayerHandle(1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+// traversal path test
+TEST_F(LayerHierarchyTest, traversalPathId) {
+ setZ(122, -1);
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ auto checkTraversalPathIdVisitor =
+ [](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath& traversalPath) -> bool {
+ EXPECT_EQ(hierarchy.getLayer()->id, traversalPath.id);
+ return true;
+ };
+ hierarchyBuilder.getHierarchy().traverse(checkTraversalPathIdVisitor);
+ hierarchyBuilder.getHierarchy().traverseInZOrder(checkTraversalPathIdVisitor);
+}
+
+TEST_F(LayerHierarchyTest, zorderRespectsLayerSequenceId) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ createRootLayer(1);
+ createRootLayer(2);
+ createRootLayer(4);
+ createRootLayer(5);
+ createLayer(11, 1);
+ createLayer(51, 5);
+ createLayer(53, 5);
+
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 2, 4, 5, 51, 53};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+
+ // A new layer is added with a smaller sequence id. Make sure its sorted correctly. While
+ // sequence ids are always incremented, this scenario can happen when a layer is reparented.
+ createRootLayer(3);
+ createLayer(52, 5);
+
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ expectedTraversalPath = {1, 11, 2, 3, 4, 5, 51, 52, 53};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, zorderRespectsLayerZ) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ createRootLayer(1);
+ createLayer(11, 1);
+ createLayer(12, 1);
+ createLayer(13, 1);
+ setZ(11, -1);
+ setZ(12, 2);
+ setZ(13, 1);
+
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ std::vector<uint32_t> expectedTraversalPath = {1, 11, 13, 12};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+
+ expectedTraversalPath = {11, 1, 13, 12};
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, zorderRespectsLayerStack) {
+ // remove default hierarchy
+ mLifecycleManager = LayerLifecycleManager();
+ createRootLayer(1);
+ createRootLayer(2);
+ createLayer(11, 1);
+ createLayer(21, 2);
+ setLayerStack(1, 20);
+ setLayerStack(2, 10);
+
+ mLifecycleManager.commitChanges();
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+ std::vector<uint32_t> expectedTraversalPath = {2, 21, 1, 11};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expectedTraversalPath);
+ expectedTraversalPath = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expectedTraversalPath);
+}
+
+TEST_F(LayerHierarchyTest, canMirrorDisplay) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3,
+ 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, mirrorNonExistingDisplay) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(5));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, newRootLayerIsMirrored) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ createRootLayer(4);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 4, 3,
+ 1, 11, 111, 12, 121, 122, 1221, 13, 2, 4};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, removedRootLayerIsNoLongerMirrored) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ reparentLayer(1, UNASSIGNED_LAYER_ID);
+ destroyLayerHandle(1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {2, 3, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {11, 111, 12, 121, 122, 1221, 13};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+TEST_F(LayerHierarchyTest, canMirrorDisplayWithMirrors) {
+ LayerHierarchyBuilder hierarchyBuilder(mLifecycleManager.getLayers());
+ reparentLayer(12, UNASSIGNED_LAYER_ID);
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ UPDATE_AND_VERIFY(hierarchyBuilder);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2, 3,
+ 1, 11, 111, 13, 14, 11, 111, 2};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()), expected);
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()), expected);
+ expected = {12, 121, 122, 1221};
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getOffscreenHierarchy()), expected);
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
new file mode 100644
index 0000000000..7e9abce690
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHierarchyTest.h
@@ -0,0 +1,541 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <gui/fake/BufferData.h>
+#include <renderengine/mock/FakeExternalTexture.h>
+#include <ui/ShadowSettings.h>
+
+#include "Client.h" // temporarily needed for LayerCreationArgs
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
+
+namespace android::surfaceflinger::frontend {
+
+class LayerHierarchyTestBase : public testing::Test {
+protected:
+ LayerHierarchyTestBase() {
+ // tree with 3 levels of children
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ └── 122
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+
+ createRootLayer(1);
+ createRootLayer(2);
+ createLayer(11, 1);
+ createLayer(12, 1);
+ createLayer(13, 1);
+ createLayer(111, 11);
+ createLayer(121, 12);
+ createLayer(122, 12);
+ createLayer(1221, 122);
+ }
+
+ LayerCreationArgs createArgs(uint32_t id, bool canBeRoot, uint32_t parentId,
+ uint32_t layerIdToMirror) {
+ LayerCreationArgs args(std::make_optional(id));
+ args.name = "testlayer";
+ args.addToRoot = canBeRoot;
+ args.parentId = parentId;
+ args.layerIdToMirror = layerIdToMirror;
+ return args;
+ }
+
+ LayerCreationArgs createDisplayMirrorArgs(uint32_t id, ui::LayerStack layerStackToMirror) {
+ LayerCreationArgs args(std::make_optional(id));
+ args.name = "testlayer";
+ args.addToRoot = true;
+ args.layerStackToMirror = layerStackToMirror;
+ return args;
+ }
+
+ std::vector<uint32_t> getTraversalPath(const LayerHierarchy& hierarchy) const {
+ std::vector<uint32_t> layerIds;
+ hierarchy.traverse([&layerIds = layerIds](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath&) -> bool {
+ layerIds.emplace_back(hierarchy.getLayer()->id);
+ return true;
+ });
+ return layerIds;
+ }
+
+ std::vector<uint32_t> getTraversalPathInZOrder(const LayerHierarchy& hierarchy) const {
+ std::vector<uint32_t> layerIds;
+ hierarchy.traverseInZOrder(
+ [&layerIds = layerIds](const LayerHierarchy& hierarchy,
+ const LayerHierarchy::TraversalPath&) -> bool {
+ layerIds.emplace_back(hierarchy.getLayer()->id);
+ return true;
+ });
+ return layerIds;
+ }
+
+ virtual void createRootLayer(uint32_t id) {
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/true, /*parent=*/UNASSIGNED_LAYER_ID,
+ /*mirror=*/UNASSIGNED_LAYER_ID)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ void createDisplayMirrorLayer(uint32_t id, ui::LayerStack layerStack) {
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createDisplayMirrorArgs(/*id=*/id, layerStack)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ virtual void createLayer(uint32_t id, uint32_t parentId) {
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+ /*mirror=*/UNASSIGNED_LAYER_ID)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ std::vector<TransactionState> reparentLayerTransaction(uint32_t id, uint32_t newParentId) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().parentId = newParentId;
+ transactions.back().states.front().state.what = layer_state_t::eReparent;
+ transactions.back().states.front().relativeParentId = UNASSIGNED_LAYER_ID;
+ transactions.back().states.front().layerId = id;
+ return transactions;
+ }
+
+ void reparentLayer(uint32_t id, uint32_t newParentId) {
+ mLifecycleManager.applyTransactions(reparentLayerTransaction(id, newParentId));
+ }
+
+ std::vector<TransactionState> relativeLayerTransaction(uint32_t id, uint32_t relativeParentId) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().relativeParentId = relativeParentId;
+ transactions.back().states.front().state.what = layer_state_t::eRelativeLayerChanged;
+ transactions.back().states.front().layerId = id;
+ return transactions;
+ }
+
+ void reparentRelativeLayer(uint32_t id, uint32_t relativeParentId) {
+ mLifecycleManager.applyTransactions(relativeLayerTransaction(id, relativeParentId));
+ }
+
+ void removeRelativeZ(uint32_t id) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ transactions.back().states.front().layerId = id;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ virtual void mirrorLayer(uint32_t id, uint32_t parentId, uint32_t layerIdToMirror) {
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(
+ createArgs(/*id=*/id, /*canBeRoot=*/false, /*parent=*/parentId,
+ /*mirror=*/layerIdToMirror)));
+ mLifecycleManager.addLayers(std::move(layers));
+ }
+
+ void updateBackgroundColor(uint32_t id, half alpha) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().state.bgColor.a = alpha;
+ transactions.back().states.front().layerId = id;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void destroyLayerHandle(uint32_t id) { mLifecycleManager.onHandlesDestroyed({{id, "test"}}); }
+
+ void updateAndVerify(LayerHierarchyBuilder& hierarchyBuilder) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ hierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ mLifecycleManager.commitChanges();
+
+ // rebuild layer hierarchy from scratch and verify that it matches the updated state.
+ LayerHierarchyBuilder newBuilder(mLifecycleManager.getLayers());
+ EXPECT_EQ(getTraversalPath(hierarchyBuilder.getHierarchy()),
+ getTraversalPath(newBuilder.getHierarchy()));
+ EXPECT_EQ(getTraversalPathInZOrder(hierarchyBuilder.getHierarchy()),
+ getTraversalPathInZOrder(newBuilder.getHierarchy()));
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ }
+
+ std::vector<TransactionState> setZTransaction(uint32_t id, int32_t z) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eLayerChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.z = z;
+ return transactions;
+ }
+
+ void setZ(uint32_t id, int32_t z) {
+ mLifecycleManager.applyTransactions(setZTransaction(id, z));
+ }
+
+ void setCrop(uint32_t id, const Rect& crop) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eCropChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.crop = crop;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFlags(uint32_t id, uint32_t mask, uint32_t flags) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eFlagsChanged;
+ transactions.back().states.front().state.flags = flags;
+ transactions.back().states.front().state.mask = mask;
+ transactions.back().states.front().layerId = id;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setAlpha(uint32_t id, float alpha) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eAlphaChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.color.a = static_cast<half>(alpha);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void hideLayer(uint32_t id) {
+ setFlags(id, layer_state_t::eLayerHidden, layer_state_t::eLayerHidden);
+ }
+
+ void showLayer(uint32_t id) { setFlags(id, layer_state_t::eLayerHidden, 0); }
+
+ void setColor(uint32_t id, half3 rgb = half3(1._hf, 1._hf, 1._hf)) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eColorChanged;
+ transactions.back().states.front().state.color.rgb = rgb;
+ transactions.back().states.front().layerId = id;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setLayerStack(uint32_t id, int32_t layerStack) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eLayerStackChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.layerStack = ui::LayerStack::fromValue(layerStack);
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setTouchableRegion(uint32_t id, Region region) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.windowInfoHandle =
+ sp<gui::WindowInfoHandle>::make();
+ auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+ inputInfo->touchableRegion = region;
+ inputInfo->token = sp<BBinder>::make();
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setTouchableRegionCrop(uint32_t id, Region region, uint32_t touchCropId,
+ bool replaceTouchableRegionWithCrop) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eInputInfoChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.windowInfoHandle =
+ sp<gui::WindowInfoHandle>::make();
+ auto inputInfo = transactions.back().states.front().state.windowInfoHandle->editInfo();
+ inputInfo->touchableRegion = region;
+ inputInfo->replaceTouchableRegionWithCrop = replaceTouchableRegionWithCrop;
+ transactions.back().states.front().touchCropId = touchCropId;
+
+ inputInfo->token = sp<BBinder>::make();
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setBackgroundBlurRadius(uint32_t id, uint32_t backgroundBlurRadius) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.backgroundBlurRadius = backgroundBlurRadius;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFrameRateSelectionPriority(uint32_t id, int32_t priority) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eFrameRateSelectionPriority;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.frameRateSelectionPriority = priority;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFrameRate(uint32_t id, float frameRate, int8_t compatibility,
+ int8_t changeFrameRateStrategy) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eFrameRateChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.frameRate = frameRate;
+ transactions.back().states.front().state.frameRateCompatibility = compatibility;
+ transactions.back().states.front().state.changeFrameRateStrategy = changeFrameRateStrategy;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFrameRateCategory(uint32_t id, int8_t frameRateCategory) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eFrameRateCategoryChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.frameRateCategory = frameRateCategory;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setFrameRateSelectionStrategy(uint32_t id, int8_t strategy) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what =
+ layer_state_t::eFrameRateSelectionStrategyChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.frameRateSelectionStrategy = strategy;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setDefaultFrameRateCompatibility(uint32_t id, int8_t defaultFrameRateCompatibility) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what =
+ layer_state_t::eDefaultFrameRateCompatibilityChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.defaultFrameRateCompatibility =
+ defaultFrameRateCompatibility;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setRoundedCorners(uint32_t id, float radius) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eCornerRadiusChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.cornerRadius = radius;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setBuffer(uint32_t id, std::shared_ptr<renderengine::ExternalTexture> texture) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eBufferChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().externalTexture = texture;
+ transactions.back().states.front().state.bufferData =
+ std::make_shared<fake::BufferData>(texture->getId(), texture->getWidth(),
+ texture->getHeight(), texture->getPixelFormat(),
+ texture->getUsage());
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setBuffer(uint32_t id) {
+ static uint64_t sBufferId = 1;
+ setBuffer(id,
+ std::make_shared<renderengine::mock::
+ FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ sBufferId++,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+ }
+
+ void setBufferCrop(uint32_t id, const Rect& bufferCrop) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eBufferCropChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.bufferCrop = bufferCrop;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setDamageRegion(uint32_t id, const Region& damageRegion) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eSurfaceDamageRegionChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.surfaceDamageRegion = damageRegion;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setDataspace(uint32_t id, ui::Dataspace dataspace) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eDataspaceChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.dataspace = dataspace;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setMatrix(uint32_t id, float dsdx, float dtdx, float dtdy, float dsdy) {
+ layer_state_t::matrix22_t matrix{dsdx, dtdx, dtdy, dsdy};
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eMatrixChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.matrix = matrix;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setShadowRadius(uint32_t id, float shadowRadius) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eShadowRadiusChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.shadowRadius = shadowRadius;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setTrustedOverlay(uint32_t id, bool isTrustedOverlay) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eTrustedOverlayChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.isTrustedOverlay = isTrustedOverlay;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ void setDropInputMode(uint32_t id, gui::DropInputMode dropInputMode) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+
+ transactions.back().states.front().state.what = layer_state_t::eDropInputModeChanged;
+ transactions.back().states.front().layerId = id;
+ transactions.back().states.front().state.dropInputMode = dropInputMode;
+ mLifecycleManager.applyTransactions(transactions);
+ }
+
+ LayerLifecycleManager mLifecycleManager;
+};
+
+class LayerSnapshotTestBase : public LayerHierarchyTestBase {
+protected:
+ LayerSnapshotTestBase() : LayerHierarchyTestBase() {}
+
+ void createRootLayer(uint32_t id) override {
+ LayerHierarchyTestBase::createRootLayer(id);
+ setColor(id);
+ }
+
+ void createLayer(uint32_t id, uint32_t parentId) override {
+ LayerHierarchyTestBase::createLayer(id, parentId);
+ setColor(parentId);
+ }
+
+ void mirrorLayer(uint32_t id, uint32_t parent, uint32_t layerToMirror) override {
+ LayerHierarchyTestBase::mirrorLayer(id, parent, layerToMirror);
+ setColor(id);
+ }
+
+ void update(LayerSnapshotBuilder& snapshotBuilder) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ mHierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = mHasDisplayChanges,
+ .globalShadowSettings = globalShadowSettings,
+ .supportsBlur = true,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {}};
+ snapshotBuilder.update(args);
+
+ mLifecycleManager.commitChanges();
+ }
+
+ LayerHierarchyBuilder mHierarchyBuilder{{}};
+
+ DisplayInfos mFrontEndDisplayInfos;
+ bool mHasDisplayChanges = false;
+
+ ShadowSettings globalShadowSettings;
+};
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
new file mode 100644
index 0000000000..8a412f8509
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryIntegrationTest.cpp
@@ -0,0 +1,1096 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LayerHistoryIntegrationTest"
+
+#include <Layer.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include <renderengine/mock/FakeExternalTexture.h>
+
+#include <common/test/FlagUtils.h>
+#include "FpsOps.h"
+#include "LayerHierarchyTest.h"
+#include "Scheduler/LayerHistory.h"
+#include "Scheduler/LayerInfo.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockSchedulerCallback.h"
+
+namespace android::scheduler {
+
+using android::mock::createDisplayMode;
+using namespace com::android::graphics::surfaceflinger;
+
+class LayerHistoryIntegrationTest : public surfaceflinger::frontend::LayerSnapshotTestBase {
+protected:
+ static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
+ static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::kMaxPeriodForFrequentLayerNs;
+ static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::kFrequentLayerWindowSize;
+ static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
+
+ static constexpr Fps LO_FPS = 30_Hz;
+ static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
+
+ static constexpr Fps HI_FPS = 90_Hz;
+ static constexpr auto HI_FPS_PERIOD = HI_FPS.getPeriodNsecs();
+
+ LayerHistoryIntegrationTest() : LayerSnapshotTestBase() {
+ mFlinger.resetScheduler(mScheduler);
+ mLifecycleManager = {};
+ mHierarchyBuilder = {{}};
+ }
+
+ void updateLayerSnapshotsAndLayerHistory(nsecs_t now) {
+ LayerSnapshotTestBase::update(mFlinger.mutableLayerSnapshotBuilder());
+ mFlinger.updateLayerHistory(now);
+ }
+
+ void setBufferWithPresentTime(sp<Layer>& layer, nsecs_t time) {
+ uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+ setBuffer(sequence);
+ layer->setDesiredPresentTime(time, false /*autotimestamp*/);
+ updateLayerSnapshotsAndLayerHistory(time);
+ }
+
+ LayerHistory& history() { return mScheduler->mutableLayerHistory(); }
+ const LayerHistory& history() const { return mScheduler->mutableLayerHistory(); }
+
+ LayerHistory::Summary summarizeLayerHistory(nsecs_t now) {
+ // LayerHistory::summarize makes no guarantee of the order of the elements in the summary
+ // however, for testing only, a stable order is required, therefore we sort the list here.
+ // Any tests requiring ordered results must create layers with names.
+ auto summary = history().summarize(*mScheduler->refreshRateSelector(), now);
+ std::sort(summary.begin(), summary.end(),
+ [](const RefreshRateSelector::LayerRequirement& lhs,
+ const RefreshRateSelector::LayerRequirement& rhs) -> bool {
+ return lhs.name < rhs.name;
+ });
+ return summary;
+ }
+
+ size_t layerCount() const { return mScheduler->layerHistorySize(); }
+ size_t activeLayerCount() const NO_THREAD_SAFETY_ANALYSIS {
+ return history().mActiveLayerInfos.size();
+ }
+
+ auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mActiveLayerInfos;
+ return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+ return pair.second.second->isFrequent(now).isFrequent;
+ });
+ }
+
+ auto animatingLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mActiveLayerInfos;
+ return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+ return pair.second.second->isAnimating(now);
+ });
+ }
+
+ auto clearLayerHistoryCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mActiveLayerInfos;
+ return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+ return pair.second.second->isFrequent(now).clearHistory;
+ });
+ }
+
+ void setDefaultLayerVote(Layer* layer,
+ LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
+ auto [found, layerPair] = history().findLayer(layer->getSequence());
+ if (found != LayerHistory::LayerStatus::NotFound) {
+ layerPair->second->setDefaultLayerVote(vote);
+ }
+ }
+
+ auto createLegacyAndFrontedEndLayer(uint32_t sequence) {
+ std::string layerName = "test layer:" + std::to_string(sequence);
+ const auto layer =
+ sp<Layer>::make(LayerCreationArgs{mFlinger.flinger(),
+ nullptr,
+ layerName,
+ 0,
+ {},
+ std::make_optional<uint32_t>(sequence)});
+ mFlinger.injectLegacyLayer(layer);
+ createRootLayer(sequence);
+ return layer;
+ }
+
+ auto destroyLayer(sp<Layer>& layer) {
+ uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+ mFlinger.releaseLegacyLayer(sequence);
+ layer.clear();
+ destroyLayerHandle(sequence);
+ }
+
+ void recordFramesAndExpect(sp<Layer>& layer, nsecs_t& time, Fps frameRate,
+ Fps desiredRefreshRate, int numFrames) {
+ LayerHistory::Summary summary;
+ for (int i = 0; i < numFrames; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += frameRate.getPeriodNsecs();
+
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
+ }
+
+ std::shared_ptr<RefreshRateSelector> mSelector =
+ std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
+ LO_FPS),
+ createDisplayMode(DisplayModeId(1),
+ HI_FPS)),
+ DisplayModeId(0));
+
+ mock::SchedulerCallback mSchedulerCallback;
+ mock::VsyncTrackerCallback mVsyncTrackerCallback;
+
+ TestableScheduler* mScheduler =
+ new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
+
+ TestableSurfaceFlinger mFlinger;
+};
+
+namespace {
+
+TEST_F(LayerHistoryIntegrationTest, singleLayerNoVoteDefaultCompatibility) {
+ createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ // No layers returned if no layers are active.
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ setBuffer(1);
+ setDefaultFrameRateCompatibility(1, ANATIVEWINDOW_FRAME_RATE_NO_VOTE);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, singleLayerMinVoteDefaultCompatibility) {
+ createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ setBuffer(1);
+ setDefaultFrameRateCompatibility(1, ANATIVEWINDOW_FRAME_RATE_MIN);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ auto summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneInvisibleLayer) {
+ createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ setBuffer(1);
+ updateLayerSnapshotsAndLayerHistory(time);
+ auto summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ // Layer is still considered inactive so we expect to get Min
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+
+ hideLayer(1);
+ setBuffer(1);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ summary = summarizeLayerHistory(time);
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(0u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitVote) {
+ createLegacyAndFrontedEndLayer(1);
+ setFrameRate(1, 73.4f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitExactVote) {
+ createLegacyAndFrontedEndLayer(1);
+ setFrameRate(1, 73.4f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, oneLayerExplicitCategory) {
+ createLegacyAndFrontedEndLayer(1);
+ setFrameRateCategory(1, ANATIVEWINDOW_FRAME_RATE_CATEGORY_HIGH);
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ // First LayerRequirement is the frame rate specification
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryIntegrationTest, multipleLayers) {
+ auto layer1 = createLegacyAndFrontedEndLayer(1);
+ auto layer2 = createLegacyAndFrontedEndLayer(2);
+ auto layer3 = createLegacyAndFrontedEndLayer(3);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(3u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ LayerHistory::Summary summary;
+
+ // layer1 is active but infrequent.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ setBufferWithPresentTime(layer1, time);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ // layer2 is frequent and has high refresh rate.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ setBufferWithPresentTime(layer2, time);
+ time += HI_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ // layer1 is still active but infrequent.
+ setBufferWithPresentTime(layer1, time);
+
+ ASSERT_EQ(2u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_EQ(HI_FPS, summarizeLayerHistory(time)[1].desiredRefreshRate);
+
+ EXPECT_EQ(2u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer1 is no longer active.
+ // layer2 is frequent and has low refresh rate.
+ for (size_t i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
+ setBufferWithPresentTime(layer2, time);
+ time += LO_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer2 still has low refresh rate.
+ // layer3 has high refresh rate but not enough history.
+ constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
+ if (i % RATIO == 0) {
+ setBufferWithPresentTime(layer2, time);
+ }
+
+ setBufferWithPresentTime(layer3, time);
+ time += HI_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(2u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[1].vote);
+ EXPECT_EQ(2u, activeLayerCount());
+ EXPECT_EQ(2, frequentLayerCount(time));
+
+ // layer3 becomes recently active.
+ setBufferWithPresentTime(layer3, time);
+ summary = summarizeLayerHistory(time);
+ ASSERT_EQ(2u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+ EXPECT_EQ(2u, activeLayerCount());
+ EXPECT_EQ(2, frequentLayerCount(time));
+
+ // layer1 expires.
+ destroyLayer(layer1);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ summary = summarizeLayerHistory(time);
+ ASSERT_EQ(2u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[1].vote);
+ EXPECT_EQ(HI_FPS, summary[1].desiredRefreshRate);
+ EXPECT_EQ(2u, layerCount());
+ EXPECT_EQ(2u, activeLayerCount());
+ EXPECT_EQ(2, frequentLayerCount(time));
+
+ // layer2 still has low refresh rate.
+ // layer3 becomes inactive.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ setBufferWithPresentTime(layer2, time);
+ time += LO_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(LO_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer2 expires.
+ destroyLayer(layer2);
+ updateLayerSnapshotsAndLayerHistory(time);
+ summary = summarizeLayerHistory(time);
+ EXPECT_TRUE(summary.empty());
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ // layer3 becomes active and has high refresh rate.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
+ setBufferWithPresentTime(layer3, time);
+ time += HI_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_EQ(HI_FPS, summary[0].desiredRefreshRate);
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer3 expires.
+ destroyLayer(layer3);
+ updateLayerSnapshotsAndLayerHistory(time);
+ summary = summarizeLayerHistory(time);
+ EXPECT_TRUE(summary.empty());
+ EXPECT_EQ(0u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, inactiveLayers) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+
+ // the very first updates makes the layer frequent
+ for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ EXPECT_EQ(1u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ setBufferWithPresentTime(layer, time);
+
+ EXPECT_EQ(1u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ // advance the time for the previous frame to be inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+
+ // Now even if we post a quick few frame we should stay infrequent
+ for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += HI_FPS_PERIOD;
+
+ EXPECT_EQ(1u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ }
+
+ // More quick frames will get us to frequent again
+ setBufferWithPresentTime(layer, time);
+ time += HI_FPS_PERIOD;
+
+ EXPECT_EQ(1u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayerIsActive) {
+ SET_FLAG_FOR_TEST(flags::misc1, false);
+
+ auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
+ auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
+ hideLayer(2);
+ setFrameRate(1, 60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+ setFrameRate(2, 90.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+ nsecs_t time = systemTime();
+
+ // Post a buffer to the layers to make them active
+ setBufferWithPresentTime(explicitVisiblelayer, time);
+ setBufferWithPresentTime(explicitInvisiblelayer, time);
+
+ EXPECT_EQ(2u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(2u, activeLayerCount());
+ EXPECT_EQ(2, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, invisibleExplicitLayerIsNotActive) {
+ SET_FLAG_FOR_TEST(flags::misc1, true);
+
+ auto explicitVisiblelayer = createLegacyAndFrontedEndLayer(1);
+ auto explicitInvisiblelayer = createLegacyAndFrontedEndLayer(2);
+ hideLayer(2);
+ setFrameRate(1, 60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+ setFrameRate(2, 90.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+ nsecs_t time = systemTime();
+
+ // Post a buffer to the layers to make them active
+ setBufferWithPresentTime(explicitVisiblelayer, time);
+ setBufferWithPresentTime(explicitInvisiblelayer, time);
+
+ EXPECT_EQ(2u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, infrequentAnimatingLayer) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer is active but infrequent.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ }
+
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // another update with the same cadence keep in infrequent
+ setBufferWithPresentTime(layer, time);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->changes |=
+ frontend::RequestedLayerState::Changes::Animation;
+ mFlinger.updateLayerHistory(time);
+ // an update as animation will immediately vote for Max
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(1, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, frequentLayerBecomingInfrequentAndBack) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // Fill up the window with frequent updates
+ for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += (60_Hz).getPeriodNsecs();
+
+ EXPECT_EQ(1u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // posting a buffer after long inactivity should retain the layer as active
+ time += std::chrono::nanoseconds(3s).count();
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting more infrequent buffer should make the layer infrequent
+ time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+ setBufferWithPresentTime(layer, time);
+ time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting another buffer should keep the layer infrequent
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting more buffers would mean starting of an animation, so making the layer frequent
+ setBufferWithPresentTime(layer, time);
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(1, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting a buffer after long inactivity should retain the layer as active
+ time += std::chrono::nanoseconds(3s).count();
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting another buffer should keep the layer frequent
+ time += (60_Hz).getPeriodNsecs();
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, inconclusiveLayerBecomingFrequent) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // Fill up the window with frequent updates
+ for (size_t i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += (60_Hz).getPeriodNsecs();
+
+ EXPECT_EQ(1u, layerCount());
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // posting infrequent buffers after long inactivity should make the layer
+ // inconclusive but frequent.
+ time += std::chrono::nanoseconds(3s).count();
+ setBufferWithPresentTime(layer, time);
+ time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting more buffers should make the layer frequent and switch the refresh rate to max
+ // by clearing the history
+ setBufferWithPresentTime(layer, time);
+ setBufferWithPresentTime(layer, time);
+ setBufferWithPresentTime(layer, time);
+ EXPECT_EQ(1, clearLayerHistoryCount(time));
+ ASSERT_EQ(1u, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryIntegrationTest, getFramerate) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer is active but infrequent.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ setBufferWithPresentTime(layer, time);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ }
+
+ float expectedFramerate = 1e9f / MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ EXPECT_FLOAT_EQ(expectedFramerate, history().getLayerFramerate(time, layer->getSequence()));
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayer60Hz) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+ for (float fps = 54.0f; fps < 65.0f; fps += 0.1f) {
+ recordFramesAndExpect(layer, time, Fps::fromValue(fps), 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+ }
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayer60_30Hz) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+ recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+
+ recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 30_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 30_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 60_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 60_Hz, 60_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayerNotOscillating) {
+ SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, false);
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryIntegrationTest, heuristicLayerNotOscillating_useKnownRefreshRate) {
+ SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, true);
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.1_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryIntegrationTest, smallDirtyLayer) {
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ LayerHistory::Summary summary;
+
+ // layer is active but infrequent.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ auto props = layer->getLayerProps();
+ if (i % 3 == 0) {
+ props.isSmallDirty = false;
+ } else {
+ props.isSmallDirty = true;
+ }
+
+ setBufferWithPresentTime(layer, time);
+ time += HI_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_GE(HI_FPS, summary[0].desiredRefreshRate);
+}
+
+TEST_F(LayerHistoryIntegrationTest, DISABLED_smallDirtyInMultiLayer) {
+ auto uiLayer = createLegacyAndFrontedEndLayer(1);
+ auto videoLayer = createLegacyAndFrontedEndLayer(2);
+ setFrameRate(2, 30.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
+ ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(2u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ LayerHistory::Summary summary;
+
+ // uiLayer is updating small dirty.
+ for (size_t i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
+ auto props = uiLayer->getLayerProps();
+ props.isSmallDirty = true;
+ setBuffer(1);
+ uiLayer->setDesiredPresentTime(0, false /*autotimestamp*/);
+ updateLayerSnapshotsAndLayerHistory(time);
+ setBufferWithPresentTime(videoLayer, time);
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1u, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+ ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
+}
+
+TEST_F(LayerHistoryIntegrationTest, hidingLayerUpdatesLayerHistory) {
+ createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ setBuffer(1);
+ updateLayerSnapshotsAndLayerHistory(time);
+ auto summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+
+ hideLayer(1);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ summary = summarizeLayerHistory(time);
+ EXPECT_TRUE(summary.empty());
+ EXPECT_EQ(0u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, showingLayerUpdatesLayerHistory) {
+ createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ hideLayer(1);
+ setBuffer(1);
+ updateLayerSnapshotsAndLayerHistory(time);
+ auto summary = summarizeLayerHistory(time);
+ EXPECT_TRUE(summary.empty());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ showLayer(1);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+}
+
+TEST_F(LayerHistoryIntegrationTest, updatingGeometryUpdatesWeight) {
+ createLegacyAndFrontedEndLayer(1);
+ nsecs_t time = systemTime();
+ updateLayerSnapshotsAndLayerHistory(time);
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(100U /*width*/, 100U /*height*/, 1,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+ mFlinger.setLayerHistoryDisplayArea(100 * 100);
+ updateLayerSnapshotsAndLayerHistory(time);
+ auto summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+
+ auto startingWeight = summary[0].weight;
+
+ setMatrix(1, 0.1f, 0.f, 0.f, 0.1f);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1u, summary.size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summary[0].vote);
+ EXPECT_EQ(1u, activeLayerCount());
+ EXPECT_GT(startingWeight, summary[0].weight);
+}
+
+class LayerHistoryIntegrationTestParameterized
+ : public LayerHistoryIntegrationTest,
+ public testing::WithParamInterface<std::chrono::nanoseconds> {};
+
+TEST_P(LayerHistoryIntegrationTestParameterized, HeuristicLayerWithInfrequentLayer) {
+ std::chrono::nanoseconds infrequentUpdateDelta = GetParam();
+ auto heuristicLayer = createLegacyAndFrontedEndLayer(1);
+ auto infrequentLayer = createLegacyAndFrontedEndLayer(2);
+
+ const nsecs_t startTime = systemTime();
+
+ const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
+ setBufferWithPresentTime(heuristicLayer, startTime);
+ setBufferWithPresentTime(infrequentLayer, startTime);
+
+ nsecs_t time = startTime;
+ nsecs_t lastInfrequentUpdate = startTime;
+ const size_t totalInfrequentLayerUpdates = FREQUENT_LAYER_WINDOW_SIZE * 5;
+ size_t infrequentLayerUpdates = 0;
+ while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
+ time += heuristicUpdateDelta.count();
+ setBufferWithPresentTime(heuristicLayer, time);
+
+ if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
+ ALOGI("submitting infrequent frame [%zu/%zu]", infrequentLayerUpdates,
+ totalInfrequentLayerUpdates);
+ lastInfrequentUpdate = time;
+ setBufferWithPresentTime(infrequentLayer, time);
+ infrequentLayerUpdates++;
+ }
+
+ if (time - startTime > PRESENT_TIME_HISTORY_DURATION.count()) {
+ ASSERT_NE(0u, summarizeLayerHistory(time).size());
+ ASSERT_GE(2u, summarizeLayerHistory(time).size());
+
+ bool max = false;
+ bool min = false;
+ Fps heuristic;
+ for (const auto& layer : summarizeLayerHistory(time)) {
+ if (layer.vote == LayerHistory::LayerVoteType::Heuristic) {
+ heuristic = layer.desiredRefreshRate;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Max) {
+ max = true;
+ } else if (layer.vote == LayerHistory::LayerVoteType::Min) {
+ min = true;
+ }
+ }
+
+ if (infrequentLayerUpdates > FREQUENT_LAYER_WINDOW_SIZE) {
+ EXPECT_EQ(24_Hz, heuristic);
+ EXPECT_FALSE(max);
+ if (summarizeLayerHistory(time).size() == 2) {
+ EXPECT_TRUE(min);
+ }
+ }
+ }
+ }
+}
+
+class SmallAreaDetectionTest : public LayerHistoryIntegrationTest {
+protected:
+ static constexpr int32_t DISPLAY_WIDTH = 100;
+ static constexpr int32_t DISPLAY_HEIGHT = 100;
+
+ static constexpr int32_t kAppId1 = 10100;
+ static constexpr int32_t kAppId2 = 10101;
+
+ static constexpr float kThreshold1 = 0.05f;
+ static constexpr float kThreshold2 = 0.07f;
+
+ SmallAreaDetectionTest() : LayerHistoryIntegrationTest() {
+ std::vector<std::pair<int32_t, float>> mappings;
+ mappings.reserve(2);
+ mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+ mappings.push_back(std::make_pair(kAppId2, kThreshold2));
+
+ mFlinger.enableNewFrontEnd();
+
+ mScheduler->onActiveDisplayAreaChanged(DISPLAY_WIDTH * DISPLAY_HEIGHT);
+ mScheduler->updateSmallAreaDetection(mappings);
+ }
+
+ auto createLegacyAndFrontedEndLayer(uint32_t sequence) {
+ std::string layerName = "test layer:" + std::to_string(sequence);
+
+ LayerCreationArgs args = LayerCreationArgs{mFlinger.flinger(),
+ nullptr,
+ layerName,
+ 0,
+ {},
+ std::make_optional<uint32_t>(sequence)};
+ args.ownerUid = kAppId1;
+ args.metadata.setInt32(gui::METADATA_WINDOW_TYPE, 2); // APPLICATION
+ const auto layer = sp<Layer>::make(args);
+ mFlinger.injectLegacyLayer(layer);
+ createRootLayer(sequence);
+ return layer;
+ }
+};
+
+TEST_F(SmallAreaDetectionTest, SmallDirtyLayer) {
+ SET_FLAG_FOR_TEST(flags::enable_small_area_detection, true);
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+ setBuffer(sequence);
+ setDamageRegion(sequence, Region(Rect(10, 10)));
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, NotSmallDirtyLayer) {
+ SET_FLAG_FOR_TEST(flags::enable_small_area_detection, true);
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+ setBuffer(sequence);
+ setDamageRegion(sequence, Region(Rect(50, 50)));
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+TEST_F(SmallAreaDetectionTest, smallDirtyLayerWithMatrix) {
+ SET_FLAG_FOR_TEST(flags::enable_small_area_detection, true);
+ auto layer = createLegacyAndFrontedEndLayer(1);
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1u, layerCount());
+ EXPECT_EQ(0u, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ // Original damage region is a small dirty.
+ uint32_t sequence = static_cast<uint32_t>(layer->sequence);
+ setBuffer(sequence);
+ setDamageRegion(sequence, Region(Rect(20, 20)));
+ updateLayerSnapshotsAndLayerHistory(time);
+ ASSERT_EQ(true, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+
+ setMatrix(sequence, 2.0f, 0, 0, 2.0f);
+ updateLayerSnapshotsAndLayerHistory(time);
+
+ // Verify if the small dirty is scaled.
+ ASSERT_EQ(false, mFlinger.mutableLayerSnapshotBuilder().getSnapshot(1)->isSmallDirty);
+}
+
+INSTANTIATE_TEST_CASE_P(LeapYearTests, LayerHistoryIntegrationTestParameterized,
+ ::testing::Values(1s, 2s, 3s, 4s, 5s));
+
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
index 17511cd615..0ae3ca33b1 100644
--- a/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerHistoryTest.cpp
@@ -22,10 +22,12 @@
#define LOG_TAG "LayerHistoryTest"
#include <Layer.h>
+#include <com_android_graphics_surfaceflinger_flags.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
+#include <common/test/FlagUtils.h>
#include "FpsOps.h"
#include "Scheduler/LayerHistory.h"
#include "Scheduler/LayerInfo.h"
@@ -45,14 +47,14 @@ using MockLayer = android::mock::MockLayer;
using android::mock::createDisplayMode;
+// WARNING: LEGACY TESTS FOR LEGACY FRONT END
+// Update LayerHistoryIntegrationTest instead
class LayerHistoryTest : public testing::Test {
protected:
static constexpr auto PRESENT_TIME_HISTORY_SIZE = LayerInfo::HISTORY_SIZE;
static constexpr auto MAX_FREQUENT_LAYER_PERIOD_NS = LayerInfo::kMaxPeriodForFrequentLayerNs;
static constexpr auto FREQUENT_LAYER_WINDOW_SIZE = LayerInfo::kFrequentLayerWindowSize;
static constexpr auto PRESENT_TIME_HISTORY_DURATION = LayerInfo::HISTORY_DURATION;
- static constexpr auto REFRESH_RATE_AVERAGE_HISTORY_DURATION =
- LayerInfo::RefreshRateHistory::HISTORY_DURATION;
static constexpr Fps LO_FPS = 30_Hz;
static constexpr auto LO_FPS_PERIOD = LO_FPS.getPeriodNsecs();
@@ -69,11 +71,11 @@ protected:
// LayerHistory::summarize makes no guarantee of the order of the elements in the summary
// however, for testing only, a stable order is required, therefore we sort the list here.
// Any tests requiring ordered results must create layers with names.
- auto summary = history().summarize(*mScheduler->refreshRateConfigs(), now);
+ auto summary = history().summarize(*mScheduler->refreshRateSelector(), now);
std::sort(summary.begin(), summary.end(),
- [](const RefreshRateConfigs::LayerRequirement& a,
- const RefreshRateConfigs::LayerRequirement& b) -> bool {
- return a.name < b.name;
+ [](const RefreshRateSelector::LayerRequirement& lhs,
+ const RefreshRateSelector::LayerRequirement& rhs) -> bool {
+ return lhs.name < rhs.name;
});
return summary;
}
@@ -86,7 +88,7 @@ protected:
auto frequentLayerCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
const auto& infos = history().mActiveLayerInfos;
return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
- return pair.second.second->isFrequent(now);
+ return pair.second.second->isFrequent(now).isFrequent;
});
}
@@ -97,6 +99,13 @@ protected:
});
}
+ auto clearLayerHistoryCount(nsecs_t now) const NO_THREAD_SAFETY_ANALYSIS {
+ const auto& infos = history().mActiveLayerInfos;
+ return std::count_if(infos.begin(), infos.end(), [now](const auto& pair) {
+ return pair.second.second->isFrequent(now).clearHistory;
+ });
+ }
+
void setDefaultLayerVote(Layer* layer,
LayerHistory::LayerVoteType vote) NO_THREAD_SAFETY_ANALYSIS {
auto [found, layerPair] = history().findLayer(layer->getSequence());
@@ -109,12 +118,16 @@ protected:
auto createLayer(std::string name) {
return sp<MockLayer>::make(mFlinger.flinger(), std::move(name));
}
+ auto createLayer(std::string name, uint32_t uid) {
+ return sp<MockLayer>::make(mFlinger.flinger(), std::move(name), std::move(uid));
+ }
void recordFramesAndExpect(const sp<MockLayer>& layer, nsecs_t& time, Fps frameRate,
Fps desiredRefreshRate, int numFrames) {
LayerHistory::Summary summary;
for (int i = 0; i < numFrames; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += frameRate.getPeriodNsecs();
summary = summarizeLayerHistory(time);
@@ -125,22 +138,81 @@ protected:
ASSERT_EQ(desiredRefreshRate, summary[0].desiredRefreshRate);
}
- std::shared_ptr<RefreshRateConfigs> mConfigs =
- std::make_shared<RefreshRateConfigs>(makeModes(createDisplayMode(DisplayModeId(0),
- LO_FPS),
- createDisplayMode(DisplayModeId(1),
- HI_FPS)),
- DisplayModeId(0));
+ std::shared_ptr<RefreshRateSelector> mSelector =
+ std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
+ LO_FPS),
+ createDisplayMode(DisplayModeId(1),
+ HI_FPS)),
+ DisplayModeId(0));
mock::SchedulerCallback mSchedulerCallback;
- TestableScheduler* mScheduler = new TestableScheduler(mConfigs, mSchedulerCallback);
+ mock::VsyncTrackerCallback mVsyncTrackerCallback;
+ TestableScheduler* mScheduler =
+ new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
TestableSurfaceFlinger mFlinger;
};
namespace {
+using namespace com::android::graphics::surfaceflinger;
+
+TEST_F(LayerHistoryTest, singleLayerNoVoteDefaultCompatibility) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+ EXPECT_CALL(*layer, getDefaultFrameRateCompatibility())
+ .WillOnce(Return(FrameRateCompatibility::NoVote));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+
+ // No layers returned if no layers are active.
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+
+ history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().setDefaultFrameRateCompatibility(layer->getSequence(),
+
+ layer->getDefaultFrameRateCompatibility(),
+ true /* contentDetectionEnabled */);
+
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(1, activeLayerCount());
+}
+
+TEST_F(LayerHistoryTest, singleLayerMinVoteDefaultCompatibility) {
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+ EXPECT_CALL(*layer, getDefaultFrameRateCompatibility())
+ .WillOnce(Return(FrameRateCompatibility::Min));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+
+ EXPECT_TRUE(summarizeLayerHistory(time).empty());
+ EXPECT_EQ(0, activeLayerCount());
+
+ history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().setDefaultFrameRateCompatibility(layer->getSequence(),
+ layer->getDefaultFrameRateCompatibility(),
+ true /* contentDetectionEnabled */);
+
+ auto summary = summarizeLayerHistory(time);
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+}
+
TEST_F(LayerHistoryTest, oneLayer) {
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
@@ -159,7 +231,8 @@ TEST_F(LayerHistoryTest, oneLayer) {
// Max returned if active layers have insufficient history.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+ LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1, summarizeLayerHistory(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
@@ -168,7 +241,8 @@ TEST_F(LayerHistoryTest, oneLayer) {
// Max is returned since we have enough history but there is no timestamp votes.
for (int i = 0; i < 10; i++) {
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+ LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1, summarizeLayerHistory(time).size());
EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
EXPECT_EQ(1, activeLayerCount());
@@ -176,6 +250,105 @@ TEST_F(LayerHistoryTest, oneLayer) {
}
}
+TEST_F(LayerHistoryTest, gameFrameRateOverrideMapping) {
+ SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+
+ history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 60.0f}));
+
+ auto overridePair = history().getGameFrameRateOverride(0);
+ EXPECT_EQ(0_Hz, overridePair.first);
+ EXPECT_EQ(60_Hz, overridePair.second);
+
+ history().updateGameModeFrameRateOverride(FrameRateOverride({0, 40.0f}));
+ history().updateGameModeFrameRateOverride(FrameRateOverride({1, 120.0f}));
+
+ overridePair = history().getGameFrameRateOverride(0);
+ EXPECT_EQ(40_Hz, overridePair.first);
+ EXPECT_EQ(60_Hz, overridePair.second);
+
+ overridePair = history().getGameFrameRateOverride(1);
+ EXPECT_EQ(120_Hz, overridePair.first);
+ EXPECT_EQ(0_Hz, overridePair.second);
+
+ history().updateGameDefaultFrameRateOverride(FrameRateOverride({0, 0.0f}));
+ history().updateGameModeFrameRateOverride(FrameRateOverride({1, 0.0f}));
+
+ overridePair = history().getGameFrameRateOverride(0);
+ EXPECT_EQ(40_Hz, overridePair.first);
+ EXPECT_EQ(0_Hz, overridePair.second);
+
+ overridePair = history().getGameFrameRateOverride(1);
+ EXPECT_EQ(0_Hz, overridePair.first);
+ EXPECT_EQ(0_Hz, overridePair.second);
+}
+
+TEST_F(LayerHistoryTest, oneLayerGameFrameRateOverride) {
+ SET_FLAG_FOR_TEST(flags::game_default_frame_rate, true);
+
+ const uid_t uid = 0;
+ const Fps gameDefaultFrameRate = Fps::fromValue(30.0f);
+ const Fps gameModeFrameRate = Fps::fromValue(60.0f);
+ const auto layer = createLayer("GameFrameRateLayer", uid);
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+ EXPECT_CALL(*layer, getOwnerUid()).WillRepeatedly(Return(uid));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ // update game default frame rate override
+ history().updateGameDefaultFrameRateOverride(
+ FrameRateOverride({uid, gameDefaultFrameRate.getValue()}));
+
+ nsecs_t time = systemTime();
+ LayerHistory::Summary summary;
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += gameDefaultFrameRate.getPeriodNsecs();
+
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+ ASSERT_EQ(30.0_Hz, summary[0].desiredRefreshRate);
+
+ // test against setFrameRate vote
+ const Fps setFrameRate = Fps::fromValue(120.0f);
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(setFrameRate, Layer::FrameRateCompatibility::Default)));
+
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += setFrameRate.getPeriodNsecs();
+
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+ ASSERT_EQ(120.0_Hz, summary[0].desiredRefreshRate);
+
+ // update game mode frame rate override
+ history().updateGameModeFrameRateOverride(
+ FrameRateOverride({uid, gameModeFrameRate.getValue()}));
+
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += gameModeFrameRate.getPeriodNsecs();
+
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+ ASSERT_EQ(60.0_Hz, summary[0].desiredRefreshRate);
+}
+
TEST_F(LayerHistoryTest, oneInvisibleLayer) {
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
@@ -186,7 +359,8 @@ TEST_F(LayerHistoryTest, oneInvisibleLayer) {
nsecs_t time = systemTime();
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+ LayerHistory::LayerUpdateType::Buffer);
auto summary = summarizeLayerHistory(time);
ASSERT_EQ(1, summarizeLayerHistory(time).size());
// Layer is still considered inactive so we expect to get Min
@@ -194,7 +368,8 @@ TEST_F(LayerHistoryTest, oneInvisibleLayer) {
EXPECT_EQ(1, activeLayerCount());
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
- history().record(layer.get(), 0, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), 0, time,
+ LayerHistory::LayerUpdateType::Buffer);
summary = summarizeLayerHistory(time);
EXPECT_TRUE(summarizeLayerHistory(time).empty());
@@ -211,7 +386,8 @@ TEST_F(LayerHistoryTest, explicitTimestamp) {
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -234,7 +410,8 @@ TEST_F(LayerHistoryTest, oneLayerNoVote) {
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -261,7 +438,8 @@ TEST_F(LayerHistoryTest, oneLayerMinVote) {
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -289,7 +467,8 @@ TEST_F(LayerHistoryTest, oneLayerMaxVote) {
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
}
@@ -317,7 +496,8 @@ TEST_F(LayerHistoryTest, oneLayerExplicitVote) {
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -349,7 +529,8 @@ TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) {
nsecs_t time = systemTime();
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
}
@@ -371,6 +552,144 @@ TEST_F(LayerHistoryTest, oneLayerExplicitExactVote) {
EXPECT_EQ(0, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, oneLayerExplicitCategory) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(0_Hz, Layer::FrameRateCompatibility::Default,
+ Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ // First LayerRequirement is the frame rate specification
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+
+ // layer became inactive, but the vote stays
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+// This test case should be the same as oneLayerNoVote except instead of layer vote is NoVote,
+// the category is NoPreference.
+TEST_F(LayerHistoryTest, oneLayerCategoryNoPreference) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(Layer::FrameRate(0_Hz, Layer::FrameRateCompatibility::Default,
+ Seamlessness::OnlySeamless,
+ FrameRateCategory::NoPreference)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ EXPECT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ EXPECT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategory) {
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(73.4_Hz, Layer::FrameRateCompatibility::Default,
+ Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ // There are 2 LayerRequirement's due to the frame rate category.
+ ASSERT_EQ(2, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ // First LayerRequirement is the layer's category specification
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+
+ // Second LayerRequirement is the frame rate specification
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summarizeLayerHistory(time)[1].vote);
+ EXPECT_EQ(73.4_Hz, summarizeLayerHistory(time)[1].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[1].frameRateCategory);
+
+ // layer became inactive, but the vote stays
+ setDefaultLayerVote(layer.get(), LayerHistory::LayerVoteType::Heuristic);
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(2, summarizeLayerHistory(time).size());
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitCategory, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(0_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(FrameRateCategory::High, summarizeLayerHistory(time)[0].frameRateCategory);
+}
+
+TEST_F(LayerHistoryTest, oneLayerExplicitVoteWithCategoryNotVisibleDoesNotVote) {
+ SET_FLAG_FOR_TEST(flags::misc1, true);
+
+ auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(12.34_Hz, Layer::FrameRateCompatibility::Default,
+ Seamlessness::OnlySeamless, FrameRateCategory::High)));
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+
+ nsecs_t time = systemTime();
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ }
+
+ // Layer is not visible, so the layer is moved to inactive, infrequent, and it will not have
+ // votes to consider for refresh rate selection.
+ ASSERT_EQ(0, summarizeLayerHistory(time).size());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+}
+
TEST_F(LayerHistoryTest, multipleLayers) {
auto layer1 = createLayer("A");
auto layer2 = createLayer("B");
@@ -395,7 +714,8 @@ TEST_F(LayerHistoryTest, multipleLayers) {
// layer1 is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer1->getSequence(), layer1->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
summary = summarizeLayerHistory(time);
}
@@ -407,13 +727,15 @@ TEST_F(LayerHistoryTest, multipleLayers) {
// layer2 is frequent and has high refresh rate.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
summary = summarizeLayerHistory(time);
}
// layer1 is still active but infrequent.
- history().record(layer1.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer1->getSequence(), layer1->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(2, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Min, summary[0].vote);
@@ -426,7 +748,8 @@ TEST_F(LayerHistoryTest, multipleLayers) {
// layer1 is no longer active.
// layer2 is frequent and has low refresh rate.
for (int i = 0; i < 2 * PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
summary = summarizeLayerHistory(time);
}
@@ -442,10 +765,12 @@ TEST_F(LayerHistoryTest, multipleLayers) {
constexpr int RATIO = LO_FPS_PERIOD / HI_FPS_PERIOD;
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE - 1; i++) {
if (i % RATIO == 0) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
}
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
summary = summarizeLayerHistory(time);
}
@@ -458,7 +783,8 @@ TEST_F(LayerHistoryTest, multipleLayers) {
EXPECT_EQ(2, frequentLayerCount(time));
// layer3 becomes recently active.
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
summary = summarizeLayerHistory(time);
ASSERT_EQ(2, summary.size());
EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
@@ -484,7 +810,8 @@ TEST_F(LayerHistoryTest, multipleLayers) {
// layer2 still has low refresh rate.
// layer3 becomes inactive.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer2.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += LO_FPS_PERIOD;
summary = summarizeLayerHistory(time);
}
@@ -505,7 +832,8 @@ TEST_F(LayerHistoryTest, multipleLayers) {
// layer3 becomes active and has high refresh rate.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
- history().record(layer3.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer3->getSequence(), layer3->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
summary = summarizeLayerHistory(time);
}
@@ -536,7 +864,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) {
// the very first updates makes the layer frequent
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
EXPECT_EQ(1, layerCount());
@@ -547,7 +876,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) {
}
// the next update with the MAX_FREQUENT_LAYER_PERIOD_NS will get us to infrequent
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
EXPECT_EQ(1, layerCount());
@@ -559,9 +889,10 @@ TEST_F(LayerHistoryTest, inactiveLayers) {
// advance the time for the previous frame to be inactive
time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
- // Now event if we post a quick few frame we should stay infrequent
+ // Now even if we post a quick few frame we should stay infrequent
for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE - 1; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
EXPECT_EQ(1, layerCount());
@@ -572,7 +903,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) {
}
// More quick frames will get us to frequent again
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += HI_FPS_PERIOD;
EXPECT_EQ(1, layerCount());
@@ -583,6 +915,8 @@ TEST_F(LayerHistoryTest, inactiveLayers) {
}
TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
+ SET_FLAG_FOR_TEST(flags::misc1, false);
+
auto explicitVisiblelayer = createLayer();
auto explicitInvisiblelayer = createLayer();
@@ -599,9 +933,10 @@ TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
nsecs_t time = systemTime();
// Post a buffer to the layers to make them active
- history().record(explicitVisiblelayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
- history().record(explicitInvisiblelayer.get(), time, time,
- LayerHistory::LayerUpdateType::Buffer);
+ history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(),
+ time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(),
+ time, time, LayerHistory::LayerUpdateType::Buffer);
EXPECT_EQ(2, layerCount());
ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -612,6 +947,39 @@ TEST_F(LayerHistoryTest, invisibleExplicitLayer) {
EXPECT_EQ(2, frequentLayerCount(time));
}
+TEST_F(LayerHistoryTest, invisibleExplicitLayerDoesNotVote) {
+ SET_FLAG_FOR_TEST(flags::misc1, true);
+
+ auto explicitVisiblelayer = createLayer();
+ auto explicitInvisiblelayer = createLayer();
+
+ EXPECT_CALL(*explicitVisiblelayer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*explicitVisiblelayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(60_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ EXPECT_CALL(*explicitInvisiblelayer, isVisible()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*explicitInvisiblelayer, getFrameRateForLayerTree())
+ .WillRepeatedly(Return(
+ Layer::FrameRate(90_Hz, Layer::FrameRateCompatibility::ExactOrMultiple)));
+
+ nsecs_t time = systemTime();
+
+ // Post a buffer to the layers to make them active
+ history().record(explicitVisiblelayer->getSequence(), explicitVisiblelayer->getLayerProps(),
+ time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(explicitInvisiblelayer->getSequence(), explicitInvisiblelayer->getLayerProps(),
+ time, time, LayerHistory::LayerUpdateType::Buffer);
+
+ EXPECT_EQ(2, layerCount());
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::ExplicitExactOrMultiple,
+ summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+}
+
TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
auto layer = createLayer();
@@ -627,7 +995,8 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
// layer is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
}
@@ -638,7 +1007,8 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
EXPECT_EQ(0, animatingLayerCount(time));
// another update with the same cadence keep in infrequent
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -648,7 +1018,8 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
EXPECT_EQ(0, animatingLayerCount(time));
// an update as animation will immediately vote for Max
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::AnimationTX);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::AnimationTX);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
ASSERT_EQ(1, summarizeLayerHistory(time).size());
@@ -658,6 +1029,197 @@ TEST_F(LayerHistoryTest, infrequentAnimatingLayer) {
EXPECT_EQ(1, animatingLayerCount(time));
}
+TEST_F(LayerHistoryTest, frontBufferedLayerVotesMax) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+ EXPECT_CALL(*layer, isFrontBuffered()).WillRepeatedly(Return(true));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer is active but infrequent.
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
+ }
+
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // layer became inactive
+ time += MAX_ACTIVE_LAYER_PERIOD_NS.count();
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, frequentLayerBecomingInfrequentAndBack) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // Fill up the window with frequent updates
+ for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += (60_Hz).getPeriodNsecs();
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // posting a buffer after long inactivity should retain the layer as active
+ time += std::chrono::nanoseconds(3s).count();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(60_Hz, summarizeLayerHistory(time)[0].desiredRefreshRate);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting more infrequent buffer should make the layer infrequent
+ time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting another buffer should keep the layer infrequent
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Min, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting more buffers would mean starting of an animation, so making the layer frequent
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(1, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting a buffer after long inactivity should retain the layer as active
+ time += std::chrono::nanoseconds(3s).count();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting another buffer should keep the layer frequent
+ time += (60_Hz).getPeriodNsecs();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+}
+
+TEST_F(LayerHistoryTest, inconclusiveLayerBecomingFrequent) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // Fill up the window with frequent updates
+ for (int i = 0; i < FREQUENT_LAYER_WINDOW_SIZE; i++) {
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += (60_Hz).getPeriodNsecs();
+
+ EXPECT_EQ(1, layerCount());
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ }
+
+ // posting infrequent buffers after long inactivity should make the layer
+ // inconclusive but frequent.
+ time += std::chrono::nanoseconds(3s).count();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += (MAX_FREQUENT_LAYER_PERIOD_NS + 1ms).count();
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(0, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Heuristic, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+
+ // posting more buffers should make the layer frequent and switch the refresh rate to max
+ // by clearing the history
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ EXPECT_EQ(1, clearLayerHistoryCount(time));
+ ASSERT_EQ(1, summarizeLayerHistory(time).size());
+ EXPECT_EQ(LayerHistory::LayerVoteType::Max, summarizeLayerHistory(time)[0].vote);
+ EXPECT_EQ(1, activeLayerCount());
+ EXPECT_EQ(1, frequentLayerCount(time));
+ EXPECT_EQ(0, animatingLayerCount(time));
+}
+
TEST_F(LayerHistoryTest, getFramerate) {
auto layer = createLayer();
@@ -673,7 +1235,8 @@ TEST_F(LayerHistoryTest, getFramerate) {
// layer is active but infrequent.
for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
- history().record(layer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer->getSequence(), layer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
time += MAX_FREQUENT_LAYER_PERIOD_NS.count();
}
@@ -708,6 +1271,8 @@ TEST_F(LayerHistoryTest, heuristicLayer60_30Hz) {
}
TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
+ SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, false);
+
const auto layer = createLayer();
EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
@@ -721,6 +1286,94 @@ TEST_F(LayerHistoryTest, heuristicLayerNotOscillating) {
recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
}
+TEST_F(LayerHistoryTest, heuristicLayerNotOscillating_useKnownRefreshRate) {
+ SET_FLAG_FOR_TEST(flags::use_known_refresh_rate_for_fps_consistency, true);
+
+ const auto layer = createLayer();
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.9_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 26.9_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.1_Hz, 24_Hz, PRESENT_TIME_HISTORY_SIZE);
+ recordFramesAndExpect(layer, time, 27.1_Hz, 30_Hz, PRESENT_TIME_HISTORY_SIZE);
+}
+
+TEST_F(LayerHistoryTest, smallDirtyLayer) {
+ auto layer = createLayer();
+
+ EXPECT_CALL(*layer, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(1, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ LayerHistory::Summary summary;
+
+ // layer is active but infrequent.
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE; i++) {
+ auto props = layer->getLayerProps();
+ if (i % 3 == 0) {
+ props.isSmallDirty = false;
+ } else {
+ props.isSmallDirty = true;
+ }
+
+ history().record(layer->getSequence(), props, time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::Heuristic, summary[0].vote);
+ EXPECT_GE(HI_FPS, summary[0].desiredRefreshRate);
+}
+
+TEST_F(LayerHistoryTest, smallDirtyInMultiLayer) {
+ auto layer1 = createLayer("UI");
+ auto layer2 = createLayer("Video");
+
+ EXPECT_CALL(*layer1, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer1, getFrameRateForLayerTree()).WillRepeatedly(Return(Layer::FrameRate()));
+
+ EXPECT_CALL(*layer2, isVisible()).WillRepeatedly(Return(true));
+ EXPECT_CALL(*layer2, getFrameRateForLayerTree())
+ .WillRepeatedly(
+ Return(Layer::FrameRate(30_Hz, Layer::FrameRateCompatibility::Default)));
+
+ nsecs_t time = systemTime();
+
+ EXPECT_EQ(2, layerCount());
+ EXPECT_EQ(0, activeLayerCount());
+ EXPECT_EQ(0, frequentLayerCount(time));
+
+ LayerHistory::Summary summary;
+
+ // layer1 is updating small dirty.
+ for (int i = 0; i < PRESENT_TIME_HISTORY_SIZE + FREQUENT_LAYER_WINDOW_SIZE + 1; i++) {
+ auto props = layer1->getLayerProps();
+ props.isSmallDirty = true;
+ history().record(layer1->getSequence(), props, 0 /*presentTime*/, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ history().record(layer2->getSequence(), layer2->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
+ time += HI_FPS_PERIOD;
+ summary = summarizeLayerHistory(time);
+ }
+
+ ASSERT_EQ(1, summary.size());
+ ASSERT_EQ(LayerHistory::LayerVoteType::ExplicitDefault, summary[0].vote);
+ ASSERT_EQ(30_Hz, summary[0].desiredRefreshRate);
+}
+
class LayerHistoryTestParameterized : public LayerHistoryTest,
public testing::WithParamInterface<std::chrono::nanoseconds> {
};
@@ -741,10 +1394,10 @@ TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) {
const nsecs_t startTime = systemTime();
const std::chrono::nanoseconds heuristicUpdateDelta = 41'666'667ns;
- history().record(heuristicLayer.get(), startTime, startTime,
- LayerHistory::LayerUpdateType::Buffer);
- history().record(infrequentLayer.get(), startTime, startTime,
- LayerHistory::LayerUpdateType::Buffer);
+ history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), startTime,
+ startTime, LayerHistory::LayerUpdateType::Buffer);
+ history().record(infrequentLayer->getSequence(), heuristicLayer->getLayerProps(), startTime,
+ startTime, LayerHistory::LayerUpdateType::Buffer);
nsecs_t time = startTime;
nsecs_t lastInfrequentUpdate = startTime;
@@ -752,14 +1405,15 @@ TEST_P(LayerHistoryTestParameterized, HeuristicLayerWithInfrequentLayer) {
int infrequentLayerUpdates = 0;
while (infrequentLayerUpdates <= totalInfrequentLayerUpdates) {
time += heuristicUpdateDelta.count();
- history().record(heuristicLayer.get(), time, time, LayerHistory::LayerUpdateType::Buffer);
+ history().record(heuristicLayer->getSequence(), heuristicLayer->getLayerProps(), time, time,
+ LayerHistory::LayerUpdateType::Buffer);
if (time - lastInfrequentUpdate >= infrequentUpdateDelta.count()) {
ALOGI("submitting infrequent frame [%d/%d]", infrequentLayerUpdates,
totalInfrequentLayerUpdates);
lastInfrequentUpdate = time;
- history().record(infrequentLayer.get(), time, time,
- LayerHistory::LayerUpdateType::Buffer);
+ history().record(infrequentLayer->getSequence(), infrequentLayer->getLayerProps(), time,
+ time, LayerHistory::LayerUpdateType::Buffer);
infrequentLayerUpdates++;
}
diff --git a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
index 5c2d2e1f43..07a522ac4e 100644
--- a/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerInfoTest.cpp
@@ -21,16 +21,29 @@
#include <scheduler/Fps.h>
+#include <common/test/FlagUtils.h>
#include "FpsOps.h"
#include "Scheduler/LayerHistory.h"
#include "Scheduler/LayerInfo.h"
+#include "TestableScheduler.h"
+#include "TestableSurfaceFlinger.h"
+#include "mock/MockSchedulerCallback.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
namespace android::scheduler {
+using android::mock::createDisplayMode;
+
class LayerInfoTest : public testing::Test {
protected:
using FrameTimeData = LayerInfo::FrameTimeData;
+ static constexpr Fps LO_FPS = 30_Hz;
+ static constexpr Fps HI_FPS = 90_Hz;
+
+ LayerInfoTest() { mFlinger.resetScheduler(mScheduler); }
+
void setFrameTimes(const std::deque<FrameTimeData>& frameTimes) {
layerInfo.mFrameTimes = frameTimes;
}
@@ -43,10 +56,24 @@ protected:
auto calculateAverageFrameTime() { return layerInfo.calculateAverageFrameTime(); }
LayerInfo layerInfo{"TestLayerInfo", 0, LayerHistory::LayerVoteType::Heuristic};
+
+ std::shared_ptr<RefreshRateSelector> mSelector =
+ std::make_shared<RefreshRateSelector>(makeModes(createDisplayMode(DisplayModeId(0),
+ LO_FPS),
+ createDisplayMode(DisplayModeId(1),
+ HI_FPS)),
+ DisplayModeId(0));
+ mock::SchedulerCallback mSchedulerCallback;
+ mock::VsyncTrackerCallback mVsyncTrackerCallback;
+ TestableScheduler* mScheduler =
+ new TestableScheduler(mSelector, mSchedulerCallback, mVsyncTrackerCallback);
+ TestableSurfaceFlinger mFlinger;
};
namespace {
+using namespace com::android::graphics::surfaceflinger;
+
TEST_F(LayerInfoTest, prefersPresentTime) {
std::deque<FrameTimeData> frameTimes;
constexpr auto kExpectedFps = 50_Hz;
@@ -171,5 +198,88 @@ TEST_F(LayerInfoTest, ignoresLargePeriods) {
ASSERT_EQ(kExpectedFps, Fps::fromPeriodNsecs(*averageFrameTime));
}
+TEST_F(LayerInfoTest, getRefreshRateVote_explicitVote) {
+ LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+ .fps = 20_Hz};
+ layerInfo.setLayerVote(vote);
+
+ auto actualVotes =
+ layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+ ASSERT_EQ(actualVotes.size(), 1u);
+ ASSERT_EQ(actualVotes[0].type, vote.type);
+ ASSERT_EQ(actualVotes[0].fps, vote.fps);
+ ASSERT_EQ(actualVotes[0].seamlessness, vote.seamlessness);
+ ASSERT_EQ(actualVotes[0].category, vote.category);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_explicitVoteWithCategory) {
+ LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+ .fps = 20_Hz,
+ .category = FrameRateCategory::High};
+ layerInfo.setLayerVote(vote);
+
+ auto actualVotes =
+ layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+ ASSERT_EQ(actualVotes.size(), 2u);
+ ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+ ASSERT_EQ(actualVotes[0].category, vote.category);
+ ASSERT_EQ(actualVotes[1].type, vote.type);
+ ASSERT_EQ(actualVotes[1].fps, vote.fps);
+ ASSERT_EQ(actualVotes[1].seamlessness, vote.seamlessness);
+ ASSERT_EQ(actualVotes[1].category, vote.category);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_explicitCategory) {
+ LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+ .category = FrameRateCategory::High};
+ layerInfo.setLayerVote(vote);
+
+ auto actualVotes =
+ layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+ ASSERT_EQ(actualVotes.size(), 1u);
+ ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+ ASSERT_EQ(actualVotes[0].category, vote.category);
+ ASSERT_EQ(actualVotes[0].fps, 0_Hz);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_categoryNoPreference) {
+ LayerInfo::LayerVote vote = {.type = LayerHistory::LayerVoteType::ExplicitDefault,
+ .category = FrameRateCategory::NoPreference};
+ layerInfo.setLayerVote(vote);
+
+ auto actualVotes =
+ layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+ ASSERT_EQ(actualVotes.size(), 1u);
+ ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::ExplicitCategory);
+ ASSERT_EQ(actualVotes[0].category, vote.category);
+ ASSERT_EQ(actualVotes[0].fps, 0_Hz);
+}
+
+TEST_F(LayerInfoTest, getRefreshRateVote_noData) {
+ LayerInfo::LayerVote vote = {
+ .type = LayerHistory::LayerVoteType::Heuristic,
+ };
+ layerInfo.setLayerVote(vote);
+
+ auto actualVotes =
+ layerInfo.getRefreshRateVote(*mScheduler->refreshRateSelector(), systemTime());
+ ASSERT_EQ(actualVotes.size(), 1u);
+ ASSERT_EQ(actualVotes[0].type, LayerHistory::LayerVoteType::Max);
+ ASSERT_EQ(actualVotes[0].fps, vote.fps);
+}
+
+TEST_F(LayerInfoTest, isFrontBuffered) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ ASSERT_FALSE(layerInfo.isFrontBuffered());
+
+ LayerProps prop = {.isFrontBuffered = true};
+ layerInfo.setLastPresentTime(0, 0, LayerHistory::LayerUpdateType::Buffer, true, prop);
+ ASSERT_TRUE(layerInfo.isFrontBuffered());
+
+ prop.isFrontBuffered = false;
+ layerInfo.setLastPresentTime(0, 0, LayerHistory::LayerUpdateType::Buffer, true, prop);
+ ASSERT_FALSE(layerInfo.isFrontBuffered());
+}
+
} // namespace
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
new file mode 100644
index 0000000000..aecfcba6e6
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerLifecycleManagerTest.cpp
@@ -0,0 +1,563 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <renderengine/mock/FakeExternalTexture.h>
+
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "LayerHierarchyTest.h"
+#include "TransactionState.h"
+
+using namespace android::surfaceflinger;
+
+namespace android::surfaceflinger::frontend {
+
+using namespace ftl::flag_operators;
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+ /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+ --gtest_filter="LayerLifecycleManagerTest.*" --gtest_repeat=100 \
+ --gtest_shuffle \
+ --gtest_brief=1
+*/
+class ExpectLayerLifecycleListener : public LayerLifecycleManager::ILifecycleListener {
+public:
+ void onLayerAdded(const RequestedLayerState& layer) override {
+ mActualLayersAdded.push_back(layer.id);
+ };
+ void onLayerDestroyed(const RequestedLayerState& layer) override {
+ mActualLayersDestroyed.emplace(layer.id);
+ };
+
+ void expectLayersAdded(const std::vector<uint32_t>& expectedLayersAdded) {
+ EXPECT_EQ(expectedLayersAdded, mActualLayersAdded);
+ mActualLayersAdded.clear();
+ }
+ void expectLayersDestroyed(const std::unordered_set<uint32_t>& expectedLayersDestroyed) {
+ EXPECT_EQ(expectedLayersDestroyed, mActualLayersDestroyed);
+ mActualLayersDestroyed.clear();
+ }
+
+ std::vector<uint32_t> mActualLayersAdded;
+ std::unordered_set<uint32_t> mActualLayersDestroyed;
+};
+
+class LayerLifecycleManagerTest : public LayerHierarchyTestBase {
+protected:
+ std::unique_ptr<RequestedLayerState> rootLayer(uint32_t id) {
+ return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/true,
+ /*parent=*/UNASSIGNED_LAYER_ID,
+ /*mirror=*/UNASSIGNED_LAYER_ID));
+ }
+
+ std::unique_ptr<RequestedLayerState> childLayer(uint32_t id, uint32_t parentId) {
+ return std::make_unique<RequestedLayerState>(createArgs(/*id=*/id, /*canBeRoot=*/false,
+ parentId,
+ /*mirror=*/UNASSIGNED_LAYER_ID));
+ }
+
+ RequestedLayerState* getRequestedLayerState(LayerLifecycleManager& lifecycleManager,
+ uint32_t layerId) {
+ return lifecycleManager.getLayerFromId(layerId);
+ }
+};
+
+TEST_F(LayerLifecycleManagerTest, addLayers) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(rootLayer(3));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.onHandlesDestroyed({{1, "1"}, {2, "2"}, {3, "3"}});
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ listener->expectLayersAdded({1, 2, 3});
+ listener->expectLayersDestroyed({1, 2, 3});
+}
+
+TEST_F(LayerLifecycleManagerTest, updateLayerStates) {
+ LayerLifecycleManager lifecycleManager;
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.applyTransactions(setZTransaction(1, 2));
+
+ auto& managedLayers = lifecycleManager.getLayers();
+ ASSERT_EQ(managedLayers.size(), 1u);
+
+ EXPECT_EQ(managedLayers.front()->z, 2);
+ EXPECT_TRUE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ ASSERT_EQ(managedLayers.size(), 1u);
+ EXPECT_FALSE(managedLayers.front()->changes.test(RequestedLayerState::Changes::Z));
+
+ // apply transactions that do not affect the hierarchy
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.backgroundBlurRadius = 22;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundBlurRadiusChanged;
+ transactions.back().states.front().layerId = 1;
+ lifecycleManager.applyTransactions(transactions);
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ EXPECT_FALSE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ EXPECT_EQ(managedLayers.front()->backgroundBlurRadius, 22u);
+}
+
+TEST_F(LayerLifecycleManagerTest, layerWithoutHandleIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.onHandlesDestroyed({{1, "1"}});
+ lifecycleManager.commitChanges();
+
+ SCOPED_TRACE("layerWithoutHandleIsDestroyed");
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, rootLayerWithoutHandleIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.onHandlesDestroyed({{1, "1"}});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2});
+ listener->expectLayersDestroyed({1});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenLayerIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.onHandlesDestroyed({{3, "3"}});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithHandleIsNotDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
+ lifecycleManager.onHandlesDestroyed({{3, "3"}});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({3});
+}
+
+TEST_F(LayerLifecycleManagerTest, offscreenChildLayerWithoutHandleIsDestroyed) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions(reparentLayerTransaction(3, UNASSIGNED_LAYER_ID));
+ lifecycleManager.onHandlesDestroyed({{3, "3"}, {4, "4"}});
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({3, 4});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingDoesNotAffectRelativeZ) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions(relativeLayerTransaction(4, 1));
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+ lifecycleManager.applyTransactions(reparentLayerTransaction(4, 2));
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, reparentingToNullRemovesRelativeZ) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions(relativeLayerTransaction(4, 1));
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+ lifecycleManager.applyTransactions(reparentLayerTransaction(4, UNASSIGNED_LAYER_ID));
+ EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, setZRemovesRelativeZ) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ layers.emplace_back(rootLayer(2));
+ layers.emplace_back(childLayer(3, /*parent*/ 2));
+ layers.emplace_back(childLayer(4, /*parent*/ 3));
+
+ lifecycleManager.addLayers(std::move(layers));
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({1, 2, 3, 4});
+ listener->expectLayersDestroyed({});
+
+ lifecycleManager.applyTransactions(relativeLayerTransaction(4, 1));
+ EXPECT_TRUE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+ lifecycleManager.applyTransactions(setZTransaction(4, 1));
+ EXPECT_FALSE(getRequestedLayerState(lifecycleManager, 4)->isRelativeOf);
+
+ lifecycleManager.commitChanges();
+ listener->expectLayersAdded({});
+ listener->expectLayersDestroyed({});
+}
+
+TEST_F(LayerLifecycleManagerTest, canAddBackgroundLayer) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColor.a = 0.5;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().layerId = 1;
+ lifecycleManager.applyTransactions(transactions);
+
+ auto& managedLayers = lifecycleManager.getLayers();
+ ASSERT_EQ(managedLayers.size(), 2u);
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ ASSERT_EQ(listener->mActualLayersAdded.size(), 2u);
+ auto bgLayerId = listener->mActualLayersAdded[1];
+ listener->expectLayersAdded({1, bgLayerId});
+ listener->expectLayersDestroyed({});
+ EXPECT_EQ(getRequestedLayerState(lifecycleManager, bgLayerId)->color.a, 0.5_hf);
+}
+
+TEST_F(LayerLifecycleManagerTest, canDestroyBackgroundLayer) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColor.a = 0.5;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().layerId = 1;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColor.a = 0;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().layerId = 1;
+
+ lifecycleManager.applyTransactions(transactions);
+
+ ASSERT_EQ(lifecycleManager.getLayers().size(), 1u);
+ ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 1u);
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ ASSERT_EQ(listener->mActualLayersAdded.size(), 2u);
+ auto bgLayerId = listener->mActualLayersAdded[1];
+ listener->expectLayersAdded({1, bgLayerId});
+ listener->expectLayersDestroyed({bgLayerId});
+}
+
+TEST_F(LayerLifecycleManagerTest, onParentDestroyDestroysBackgroundLayer) {
+ LayerLifecycleManager lifecycleManager;
+ auto listener = std::make_shared<ExpectLayerLifecycleListener>();
+ lifecycleManager.addLifecycleListener(listener);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(rootLayer(1));
+ lifecycleManager.addLayers(std::move(layers));
+
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.bgColor.a = 0.5;
+ transactions.back().states.front().state.what = layer_state_t::eBackgroundColorChanged;
+ transactions.back().states.front().layerId = 1;
+ transactions.emplace_back();
+ lifecycleManager.applyTransactions(transactions);
+ lifecycleManager.onHandlesDestroyed({{1, "1"}});
+
+ ASSERT_EQ(lifecycleManager.getLayers().size(), 0u);
+ ASSERT_EQ(lifecycleManager.getDestroyedLayers().size(), 2u);
+
+ EXPECT_TRUE(lifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy));
+ lifecycleManager.commitChanges();
+ ASSERT_EQ(listener->mActualLayersAdded.size(), 2u);
+ auto bgLayerId = listener->mActualLayersAdded[1];
+ listener->expectLayersAdded({1, bgLayerId});
+ listener->expectLayersDestroyed({1, bgLayerId});
+}
+
+TEST_F(LayerLifecycleManagerTest, blurSetsVisibilityChangeFlag) {
+ // clear default color on layer so we start with a layer that does not draw anything.
+ setColor(1, {-1.f, -1.f, -1.f});
+ mLifecycleManager.commitChanges();
+
+ // layer has something to draw
+ setBackgroundBlurRadius(1, 2);
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer still has something to draw, so visibility shouldn't change
+ setBackgroundBlurRadius(1, 3);
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer has nothing to draw
+ setBackgroundBlurRadius(1, 0);
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, colorSetsVisibilityChangeFlag) {
+ // clear default color on layer so we start with a layer that does not draw anything.
+ setColor(1, {-1.f, -1.f, -1.f});
+ mLifecycleManager.commitChanges();
+
+ // layer has something to draw
+ setColor(1, {2.f, 3.f, 4.f});
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer still has something to draw, so visibility shouldn't change
+ setColor(1, {0.f, 0.f, 0.f});
+ EXPECT_FALSE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+
+ // layer has nothing to draw
+ setColor(1, {-1.f, -1.f, -1.f});
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, layerOpacityChangesSetsVisibilityChangeFlag) {
+ // add a default buffer and make the layer opaque
+ setFlags(1, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 1ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+
+ mLifecycleManager.commitChanges();
+
+ // set new buffer but layer opacity doesn't change
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 2ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+ ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
+ RequestedLayerState::Changes::Content)
+ .get());
+ mLifecycleManager.commitChanges();
+
+ // change layer flags and confirm visibility flag is set
+ setFlags(1, layer_state_t::eLayerOpaque, 0);
+ EXPECT_TRUE(
+ mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Visibility));
+ mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, bufferFormatChangesSetsVisibilityChangeFlag) {
+ // add a default buffer and make the layer opaque
+ setFlags(1, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 1ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+
+ mLifecycleManager.commitChanges();
+
+ // set new buffer with an opaque buffer format
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 2ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGB_888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+ ftl::Flags<RequestedLayerState::Changes>(RequestedLayerState::Changes::Buffer |
+ RequestedLayerState::Changes::Content |
+ RequestedLayerState::Changes::VisibleRegion |
+ RequestedLayerState::Changes::Visibility)
+ .get());
+ mLifecycleManager.commitChanges();
+}
+
+TEST_F(LayerLifecycleManagerTest, roundedCornerChangesSetsVisibilityChangeFlag) {
+ // add a default buffer and make the layer opaque
+ setFlags(1, layer_state_t::eLayerOpaque, layer_state_t::eLayerOpaque);
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 1ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+
+ mLifecycleManager.commitChanges();
+
+ // add rounded corners which should make the layer translucent
+ setRoundedCorners(1, 5.f);
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().get(),
+ ftl::Flags<RequestedLayerState::Changes>(
+ RequestedLayerState::Changes::AffectsChildren |
+ RequestedLayerState::Changes::Content |
+ RequestedLayerState::Changes::Geometry |
+ RequestedLayerState::Changes::VisibleRegion)
+ .get());
+ mLifecycleManager.commitChanges();
+}
+
+// Even when it does not change visible region, we should mark alpha changes as affecting
+// visible region because HWC impl depends on it. writeOutputIndependentGeometryStateToHWC
+// is only called if we are updating geometry.
+TEST_F(LayerLifecycleManagerTest, alphaChangesAlwaysSetsVisibleRegionFlag) {
+ mLifecycleManager.commitChanges();
+ float startingAlpha = 0.5f;
+ setAlpha(1, startingAlpha);
+
+ // this is expected because layer alpha changes from 1 to 0.5, it may no longer be opaque
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+ ftl::Flags<RequestedLayerState::Changes>(
+ RequestedLayerState::Changes::Content |
+ RequestedLayerState::Changes::AffectsChildren |
+ RequestedLayerState::Changes::VisibleRegion)
+ .string());
+ EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(startingAlpha));
+ mLifecycleManager.commitChanges();
+
+ float endingAlpha = 0.2f;
+ setAlpha(1, endingAlpha);
+
+ // this is not expected but we should make sure this behavior does not change
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+ ftl::Flags<RequestedLayerState::Changes>(
+ RequestedLayerState::Changes::Content |
+ RequestedLayerState::Changes::AffectsChildren |
+ RequestedLayerState::Changes::VisibleRegion)
+ .string());
+ EXPECT_EQ(mLifecycleManager.getChangedLayers()[0]->color.a, static_cast<half>(endingAlpha));
+ mLifecycleManager.commitChanges();
+
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges().string(),
+ ftl::Flags<RequestedLayerState::Changes>().string());
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp b/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp
index 373fd74020..e6e02c1806 100644
--- a/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerMetadataTest.cpp
@@ -27,6 +27,8 @@
#include <gui/LayerMetadata.h>
#include <log/log.h>
+using android::gui::LayerMetadata;
+
namespace android {
namespace {
@@ -113,4 +115,4 @@ TEST_F(LayerMetadataTest, merge) {
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wextra" \ No newline at end of file
+#pragma clang diagnostic pop // ignored "-Wextra"
diff --git a/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
new file mode 100644
index 0000000000..50cd784725
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/LayerSnapshotTest.cpp
@@ -0,0 +1,1186 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <renderengine/mock/FakeExternalTexture.h>
+
+#include "FrontEnd/LayerHierarchy.h"
+#include "FrontEnd/LayerLifecycleManager.h"
+#include "FrontEnd/LayerSnapshotBuilder.h"
+#include "Layer.h"
+#include "LayerHierarchyTest.h"
+#include "ui/GraphicTypes.h"
+
+#define UPDATE_AND_VERIFY(BUILDER, ...) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((BUILDER), /*displayChanges=*/false, __VA_ARGS__); \
+ })
+
+#define UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(BUILDER, ...) \
+ ({ \
+ SCOPED_TRACE(""); \
+ updateAndVerify((BUILDER), /*displayChanges=*/true, __VA_ARGS__); \
+ })
+
+namespace android::surfaceflinger::frontend {
+
+using ftl::Flags;
+using namespace ftl::flag_operators;
+
+// To run test:
+/**
+ mp :libsurfaceflinger_unittest && adb sync; adb shell \
+ /data/nativetest/libsurfaceflinger_unittest/libsurfaceflinger_unittest \
+ --gtest_filter="LayerSnapshotTest.*" --gtest_brief=1
+*/
+
+class LayerSnapshotTest : public LayerSnapshotTestBase {
+protected:
+ LayerSnapshotTest() : LayerSnapshotTestBase() {
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ }
+
+ void update(LayerSnapshotBuilder& actualBuilder, LayerSnapshotBuilder::Args& args) {
+ if (mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Hierarchy)) {
+ mHierarchyBuilder.update(mLifecycleManager.getLayers(),
+ mLifecycleManager.getDestroyedLayers());
+ }
+ args.root = mHierarchyBuilder.getHierarchy();
+ actualBuilder.update(args);
+ }
+
+ void updateAndVerify(LayerSnapshotBuilder& actualBuilder, bool hasDisplayChanges,
+ const std::vector<uint32_t> expectedVisibleLayerIdsInZOrder) {
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = hasDisplayChanges,
+ .globalShadowSettings = globalShadowSettings,
+ .supportsBlur = true,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {}};
+ update(actualBuilder, args);
+
+ // rebuild layer snapshots from scratch and verify that it matches the updated state.
+ LayerSnapshotBuilder expectedBuilder(args);
+ mLifecycleManager.commitChanges();
+ ASSERT_TRUE(expectedBuilder.getSnapshots().size() > 0);
+ ASSERT_TRUE(actualBuilder.getSnapshots().size() > 0);
+
+ std::vector<uint32_t> actualVisibleLayerIdsInZOrder;
+ actualBuilder.forEachVisibleSnapshot(
+ [&actualVisibleLayerIdsInZOrder](const LayerSnapshot& snapshot) {
+ actualVisibleLayerIdsInZOrder.push_back(snapshot.path.id);
+ });
+ EXPECT_EQ(expectedVisibleLayerIdsInZOrder, actualVisibleLayerIdsInZOrder);
+ }
+
+ LayerSnapshot* getSnapshot(uint32_t layerId) { return mSnapshotBuilder.getSnapshot(layerId); }
+ LayerSnapshot* getSnapshot(const LayerHierarchy::TraversalPath path) {
+ return mSnapshotBuilder.getSnapshot(path);
+ }
+ LayerSnapshotBuilder mSnapshotBuilder;
+ static const std::vector<uint32_t> STARTING_ZORDER;
+};
+const std::vector<uint32_t> LayerSnapshotTest::STARTING_ZORDER = {1, 11, 111, 12, 121,
+ 122, 1221, 13, 2};
+
+TEST_F(LayerSnapshotTest, buildSnapshot) {
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .globalShadowSettings = globalShadowSettings,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {}};
+ LayerSnapshotBuilder builder(args);
+}
+
+TEST_F(LayerSnapshotTest, updateSnapshot) {
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .globalShadowSettings = globalShadowSettings,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {}
+
+ };
+
+ LayerSnapshotBuilder builder;
+ builder.update(args);
+}
+
+// update using parent snapshot data
+TEST_F(LayerSnapshotTest, croppedByParent) {
+ /// MAKE ALL LAYERS VISIBLE BY DEFAULT
+ DisplayInfo info;
+ info.info.logicalHeight = 100;
+ info.info.logicalWidth = 200;
+ mFrontEndDisplayInfos.emplace_or_replace(ui::LayerStack::fromValue(1), info);
+ Rect layerCrop(0, 0, 10, 20);
+ setCrop(11, layerCrop);
+ EXPECT_TRUE(mLifecycleManager.getGlobalChanges().test(RequestedLayerState::Changes::Geometry));
+ UPDATE_AND_VERIFY_WITH_DISPLAY_CHANGES(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(11)->geomCrop, layerCrop);
+ EXPECT_EQ(getSnapshot(111)->geomLayerBounds, layerCrop.toFloatRect());
+ float maxHeight = static_cast<float>(info.info.logicalHeight * 10);
+ float maxWidth = static_cast<float>(info.info.logicalWidth * 10);
+
+ FloatRect maxDisplaySize(-maxWidth, -maxHeight, maxWidth, maxHeight);
+ EXPECT_EQ(getSnapshot(1)->geomLayerBounds, maxDisplaySize);
+}
+
+// visibility tests
+TEST_F(LayerSnapshotTest, newLayerHiddenByPolicy) {
+ createLayer(112, 11);
+ hideLayer(112);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ showLayer(112);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 111, 112, 12, 121, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, hiddenByParent) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, reparentShowsChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ showLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+}
+
+TEST_F(LayerSnapshotTest, reparentHidesChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ reparentLayer(121, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 122, 1221, 13, 2});
+}
+
+TEST_F(LayerSnapshotTest, unHidingUpdatesSnapshot) {
+ hideLayer(11);
+ Rect crop(1, 2, 3, 4);
+ setCrop(111, crop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+
+ showLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(111)->geomLayerBounds, crop.toFloatRect());
+}
+
+TEST_F(LayerSnapshotTest, childBehindParentCanBeHiddenByParent) {
+ setZ(111, -1);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 111, 11, 12, 121, 122, 1221, 13, 2});
+
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+}
+
+// relative tests
+TEST_F(LayerSnapshotTest, RelativeParentCanHideChild) {
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 13, 111, 12, 121, 122, 1221, 2});
+
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 2});
+}
+
+TEST_F(LayerSnapshotTest, ReparentingToHiddenRelativeParentHidesChild) {
+ hideLayer(11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 13, 2});
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 12, 121, 122, 1221, 2});
+}
+
+TEST_F(LayerSnapshotTest, AlphaInheritedByChildren) {
+ setAlpha(1, 0.5);
+ setAlpha(122, 0.5);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->alpha, 0.5f);
+ EXPECT_EQ(getSnapshot(12)->alpha, 0.5f);
+ EXPECT_EQ(getSnapshot(1221)->alpha, 0.25f);
+}
+
+// Change states
+TEST_F(LayerSnapshotTest, UpdateClearsPreviousChangeStates) {
+ setCrop(1, Rect(1, 2, 3, 4));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(1)->changes.test(RequestedLayerState::Changes::Geometry));
+ EXPECT_TRUE(getSnapshot(11)->changes.test(RequestedLayerState::Changes::Geometry));
+ setCrop(2, Rect(1, 2, 3, 4));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(2)->changes.test(RequestedLayerState::Changes::Geometry));
+ EXPECT_FALSE(getSnapshot(1)->changes.test(RequestedLayerState::Changes::Geometry));
+ EXPECT_FALSE(getSnapshot(11)->changes.test(RequestedLayerState::Changes::Geometry));
+}
+
+TEST_F(LayerSnapshotTest, FastPathClearsPreviousChangeStates) {
+ setColor(11, {1._hf, 0._hf, 0._hf});
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(11)->changes, RequestedLayerState::Changes::Content);
+ EXPECT_EQ(getSnapshot(11)->clientChanges, layer_state_t::eColorChanged);
+ EXPECT_EQ(getSnapshot(1)->changes.get(), 0u);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(11)->changes.get(), 0u);
+}
+
+TEST_F(LayerSnapshotTest, FastPathSetsChangeFlagToContent) {
+ setColor(1, {1._hf, 0._hf, 0._hf});
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->changes, RequestedLayerState::Changes::Content);
+ EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eColorChanged);
+}
+
+TEST_F(LayerSnapshotTest, GameMode) {
+ std::vector<TransactionState> transactions;
+ transactions.emplace_back();
+ transactions.back().states.push_back({});
+ transactions.back().states.front().state.what = layer_state_t::eMetadataChanged;
+ transactions.back().states.front().state.metadata = LayerMetadata();
+ transactions.back().states.front().state.metadata.setInt32(METADATA_GAME_MODE, 42);
+ transactions.back().states.front().layerId = 1;
+ transactions.back().states.front().state.layerId = static_cast<int32_t>(1);
+ mLifecycleManager.applyTransactions(transactions);
+ EXPECT_EQ(mLifecycleManager.getGlobalChanges(), RequestedLayerState::Changes::GameMode);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->clientChanges, layer_state_t::eMetadataChanged);
+ EXPECT_EQ(static_cast<int32_t>(getSnapshot(1)->gameMode), 42);
+ EXPECT_EQ(static_cast<int32_t>(getSnapshot(11)->gameMode), 42);
+}
+
+TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotes) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11 (frame rate set)
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ └── 122
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+
+ setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
+ EXPECT_EQ(getSnapshot(11)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+ EXPECT_EQ(getSnapshot(111)->frameRate.vote.rate.getIntValue(), 90);
+ EXPECT_EQ(getSnapshot(111)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+ EXPECT_EQ(getSnapshot(1)->frameRate.vote.rate.getIntValue(), 0);
+ EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
+}
+
+TEST_F(LayerSnapshotTest, NoLayerVoteForParentWithChildVotesDoesNotAffectSiblings) {
+ // ROOT
+ // ├── 1 (verify layer has no vote)
+ // │ ├── 11 (frame rate set)
+ // │ │ └── 111
+ // │ ├── 12 (frame rate set)
+ // │ │ ├── 121
+ // │ │ └── 122
+ // │ │ └── 1221
+ // │ └── 13 (verify layer has default vote)
+ // └── 2
+
+ setFrameRate(11, 90.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+ setFrameRate(12, 45.0, ANATIVEWINDOW_FRAME_RATE_EXACT, ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_EQ(getSnapshot(11)->frameRate.vote.rate.getIntValue(), 90);
+ EXPECT_EQ(getSnapshot(11)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+ EXPECT_EQ(getSnapshot(111)->frameRate.vote.rate.getIntValue(), 90);
+ EXPECT_EQ(getSnapshot(111)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+ EXPECT_EQ(getSnapshot(12)->frameRate.vote.rate.getIntValue(), 45);
+ EXPECT_EQ(getSnapshot(12)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+ EXPECT_EQ(getSnapshot(121)->frameRate.vote.rate.getIntValue(), 45);
+ EXPECT_EQ(getSnapshot(121)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+ EXPECT_EQ(getSnapshot(1221)->frameRate.vote.rate.getIntValue(), 45);
+ EXPECT_EQ(getSnapshot(1221)->frameRate.vote.type, scheduler::FrameRateCompatibility::Exact);
+
+ EXPECT_EQ(getSnapshot(1)->frameRate.vote.rate.getIntValue(), 0);
+ EXPECT_EQ(getSnapshot(1)->frameRate.vote.type, scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_EQ(getSnapshot(13)->frameRate.vote.rate.getIntValue(), 0);
+ EXPECT_EQ(getSnapshot(13)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+ EXPECT_EQ(getSnapshot(2)->frameRate.vote.rate.getIntValue(), 0);
+ EXPECT_EQ(getSnapshot(2)->frameRate.vote.type, scheduler::FrameRateCompatibility::Default);
+}
+
+TEST_F(LayerSnapshotTest, CanCropTouchableRegion) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111 (touchregion set to touch but cropped by layer 13)
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ └── 122
+ // │ │ └── 1221
+ // │ └── 13 (crop set to touchCrop)
+ // └── 2
+
+ Rect touchCrop{300, 300, 400, 500};
+ setCrop(13, touchCrop);
+ Region touch{Rect{0, 0, 1000, 1000}};
+ setTouchableRegionCrop(111, touch, /*touchCropId=*/13, /*replaceTouchableRegionWithCrop=*/true);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), touchCrop);
+
+ Rect modifiedTouchCrop{100, 300, 400, 700};
+ setCrop(13, modifiedTouchCrop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 111})->inputInfo.touchableRegion.bounds(), modifiedTouchCrop);
+}
+
+TEST_F(LayerSnapshotTest, blurUpdatesWhenAlphaChanges) {
+ int blurRadius = 42;
+ setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+
+ blurRadius = 21;
+ setBackgroundBlurRadius(1221, static_cast<uint32_t>(blurRadius));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius, blurRadius);
+
+ static constexpr float alpha = 0.5;
+ setAlpha(12, alpha);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1221})->backgroundBlurRadius,
+ static_cast<int>(static_cast<float>(blurRadius) * alpha));
+}
+
+// Display Mirroring Tests
+// tree with 3 levels of children
+// ROOT (DISPLAY 0)
+// ├── 1
+// │ ├── 11
+// │ │ └── 111
+// │ ├── 12 (has skip screenshot flag)
+// │ │ ├── 121
+// │ │ └── 122
+// │ │ └── 1221
+// │ └── 13
+// └── 2
+// ROOT (DISPLAY 1)
+// └── 3 (mirrors display 0)
+TEST_F(LayerSnapshotTest, displayMirrorRespectsLayerSkipScreenshotFlag) {
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+}
+
+// ROOT (DISPLAY 0)
+// ├── 1
+// │ ├── 11
+// │ │ └── 111
+// │ └── 13
+// └── 2
+// ROOT (DISPLAY 3)
+// └── 3 (mirrors display 0)
+TEST_F(LayerSnapshotTest, mirrorLayerGetsCorrectLayerStack) {
+ reparentLayer(12, UNASSIGNED_LAYER_ID);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 3);
+ createDisplayMirrorLayer(4, ui::LayerStack::fromValue(0));
+ setLayerStack(4, 4);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 13, 2, 3, 1, 11, 111,
+ 13, 2, 4, 1, 11, 111, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 3u})->outputFilter.layerStack.id, 3u);
+ EXPECT_EQ(getSnapshot({.id = 111, .mirrorRootIds = 4u})->outputFilter.layerStack.id, 4u);
+}
+
+// ROOT (DISPLAY 0)
+// ├── 1 (crop 50x50)
+// │ ├── 11
+// │ │ └── 111
+// │ └── 13
+// └── 2
+// ROOT (DISPLAY 3)
+// └── 3 (mirrors display 0) (crop 100x100)
+TEST_F(LayerSnapshotTest, mirrorLayerTouchIsCroppedByMirrorRoot) {
+ reparentLayer(12, UNASSIGNED_LAYER_ID);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 3);
+ setCrop(1, Rect{50, 50});
+ setCrop(3, Rect{100, 100});
+ setCrop(111, Rect{200, 200});
+ Region touch{Rect{0, 0, 1000, 1000}};
+ setTouchableRegion(111, touch);
+ std::vector<uint32_t> expected = {1, 11, 111, 13, 2, 3, 1, 11, 111, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ EXPECT_TRUE(getSnapshot({.id = 111})->inputInfo.touchableRegion.hasSameRects(touch));
+ Region touchCroppedByMirrorRoot{Rect{0, 0, 50, 50}};
+ EXPECT_TRUE(getSnapshot({.id = 111, .mirrorRootIds = 3u})
+ ->inputInfo.touchableRegion.hasSameRects(touchCroppedByMirrorRoot));
+}
+
+TEST_F(LayerSnapshotTest, canRemoveDisplayMirror) {
+ setFlags(12, layer_state_t::eLayerSkipScreenshot, layer_state_t::eLayerSkipScreenshot);
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3, 1, 11, 111, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ destroyLayerHandle(3);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+}
+
+TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterMirroring) {
+ size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 1);
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13, 2, 3,
+ 1, 11, 111, 12, 121, 122, 1221, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ destroyLayerHandle(3);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_EQ(startingNumSnapshots, mSnapshotBuilder.getSnapshots().size());
+}
+
+TEST_F(LayerSnapshotTest, canMirrorDisplayWithMirrors) {
+ reparentLayer(12, UNASSIGNED_LAYER_ID);
+ mirrorLayer(/*layer*/ 14, /*parent*/ 1, /*layerToMirror*/ 11);
+ std::vector<uint32_t> expected = {1, 11, 111, 13, 14, 11, 111, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+ createDisplayMirrorLayer(3, ui::LayerStack::fromValue(0));
+ setLayerStack(3, 3);
+ expected = {1, 11, 111, 13, 14, 11, 111, 2, 3, 1, 11, 111, 13, 14, 11, 111, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 14u})->outputFilter.layerStack.id, 0u);
+ EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 3u})->outputFilter.layerStack.id, 3u);
+ EXPECT_EQ(getSnapshot({.id = 11, .mirrorRootIds = 3u, 14u})->outputFilter.layerStack.id, 3u);
+}
+
+// Rel z doesn't create duplicate snapshots but this is for completeness
+TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterRelZ) {
+ size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
+ reparentRelativeLayer(13, 11);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {1, 11, 13, 111, 12, 121, 122, 1221, 2});
+ setZ(13, 0);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_EQ(startingNumSnapshots, mSnapshotBuilder.getSnapshots().size());
+}
+
+TEST_F(LayerSnapshotTest, cleanUpUnreachableSnapshotsAfterLayerDestruction) {
+ size_t startingNumSnapshots = mSnapshotBuilder.getSnapshots().size();
+ destroyLayerHandle(2);
+ destroyLayerHandle(122);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 12, 121, 122, 1221, 13};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+ EXPECT_LE(startingNumSnapshots - 2, mSnapshotBuilder.getSnapshots().size());
+}
+
+TEST_F(LayerSnapshotTest, snashotContainsMetadataFromLayerCreationArgs) {
+ LayerCreationArgs args(std::make_optional<uint32_t>(200));
+ args.name = "testlayer";
+ args.addToRoot = true;
+ args.metadata.setInt32(42, 24);
+
+ std::vector<std::unique_ptr<RequestedLayerState>> layers;
+ layers.emplace_back(std::make_unique<RequestedLayerState>(args));
+ EXPECT_TRUE(layers.back()->metadata.has(42));
+ EXPECT_EQ(layers.back()->metadata.getInt32(42, 0), 24);
+ mLifecycleManager.addLayers(std::move(layers));
+
+ std::vector<uint32_t> expected = STARTING_ZORDER;
+ expected.push_back(200);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+ EXPECT_TRUE(mSnapshotBuilder.getSnapshot(200)->layerMetadata.has(42));
+ EXPECT_EQ(mSnapshotBuilder.getSnapshot(200)->layerMetadata.getInt32(42, 0), 24);
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionPriorityPassedToChildLayers) {
+ setFrameRateSelectionPriority(11, 1);
+
+ setFrameRateSelectionPriority(12, 2);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionPriority, Layer::PRIORITY_UNSET);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionPriority, 1);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionPriority, 2);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionPriority, 2);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionPriority, 2);
+
+ // reparent and verify the child gets the new parent's framerate selection priority
+ reparentLayer(122, 11);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 122, 1221, 12, 121, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionPriority, Layer::PRIORITY_UNSET);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionPriority, 1);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionPriority, 2);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionPriority, 1);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionPriority, 1);
+}
+
+TEST_F(LayerSnapshotTest, framerate) {
+ setFrameRate(11, 244.f, 0, 0);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent is gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer and children get the requested votes
+ EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // reparent and verify the child gets the new parent's framerate
+ reparentLayer(122, 11);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 122, 1221, 12, 121, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ // verify parent is gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+
+ // verify layer and children get the requested votes
+ EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+
+ EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+
+ EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // reparent and verify the new parent gets no vote
+ reparentLayer(11, 2);
+ expected = {1, 12, 121, 13, 2, 11, 111, 122, 1221};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+
+ // verify old parent has invalid framerate (default)
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify new parent get no vote
+ EXPECT_FALSE(getSnapshot({.id = 2})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 2})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 2})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer and children get the requested votes (unchanged)
+ EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+
+ EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+
+ EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+}
+
+TEST_F(LayerSnapshotTest, translateDataspace) {
+ setDataspace(1, ui::Dataspace::UNKNOWN);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1})->dataspace, ui::Dataspace::V0_SRGB);
+}
+
+// This test is similar to "frameRate" test case but checks that the setFrameRateCategory API
+// interaction also works correctly with the setFrameRate API within SF frontend.
+TEST_F(LayerSnapshotTest, frameRateWithCategory) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11 (frame rate set to 244.f)
+ // │ │ └── 111
+ // │ ├── 12
+ // │ │ ├── 121
+ // │ │ └── 122 (frame rate category set to Normal)
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+ setFrameRate(11, 244.f, 0, 0);
+ setFrameRateCategory(122, 3 /* Normal */);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 11 and children 111 get the requested votes
+ EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify parent 12 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 122 and children 1221 get the requested votes
+ EXPECT_FALSE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+ EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Normal);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+ EXPECT_TRUE(
+ getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::AffectsChildren));
+
+ EXPECT_FALSE(getSnapshot({.id = 1221})->frameRate.vote.rate.isValid());
+ EXPECT_TRUE(getSnapshot({.id = 1221})->frameRate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Normal);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+ EXPECT_TRUE(
+ getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::AffectsChildren));
+
+ // reparent and verify the child does NOT get the new parent's framerate because it already has
+ // the frame rate category specified.
+ // ROOT
+ // ├─1
+ // │ ├─11 (frame rate set to 244.f)
+ // │ │ ├─111
+ // │ │ └─122 (frame rate category set to Normal)
+ // │ │ └─1221
+ // │ ├─12
+ // │ │ └─121
+ // │ └─13
+ // └─2
+ reparentLayer(122, 11);
+
+ std::vector<uint32_t> expected = {1, 11, 111, 122, 1221, 12, 121, 13, 2};
+ UPDATE_AND_VERIFY(mSnapshotBuilder, expected);
+ // verify parent is gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+
+ // verify layer 11 and children 111 get the requested votes
+ EXPECT_TRUE(getSnapshot({.id = 11})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+
+ EXPECT_TRUE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+
+ // verify layer 122 and children 1221 get the requested category vote (unchanged from
+ // reparenting)
+ EXPECT_FALSE(getSnapshot({.id = 122})->frameRate.vote.rate.isValid());
+ EXPECT_TRUE(getSnapshot({.id = 122})->frameRate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Normal);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_FALSE(getSnapshot({.id = 1221})->frameRate.vote.rate.isValid());
+ EXPECT_TRUE(getSnapshot({.id = 1221})->frameRate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Normal);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategy) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12 (frame rate set to 244.f with strategy OverrideChildren)
+ // │ │ ├── 121
+ // │ │ └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+ setFrameRate(12, 244.f, 0, 0);
+ setFrameRate(122, 123.f, 0, 0);
+ setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12 (frame rate set to default with strategy default)
+ // │ │ ├── 121
+ // │ │ └── 122 (frame rate set to 123.f)
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+ setFrameRate(12, -1.f, 0, 0);
+ setFrameRateSelectionStrategy(12, 0 /* Default */);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+ EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_FALSE(getSnapshot({.id = 121})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 123.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 123.f);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithCategory) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12 (frame rate category set to high with strategy OverrideChildren)
+ // │ │ ├── 121
+ // │ │ └── 122 (frame rate set to 123.f but should be overridden by layer 12)
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+ setFrameRateCategory(12, 4 /* high */);
+ setFrameRate(122, 123.f, 0, 0);
+ setFrameRateSelectionStrategy(12, 1 /* OverrideChildren */);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::High);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::High);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::High);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::High);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // ROOT
+ // ├── 1
+ // │ ├── 11
+ // │ │ └── 111
+ // │ ├── 12 (frame rate category to default with strategy default)
+ // │ │ ├── 121
+ // │ │ └── 122 (frame rate set to 123.f)
+ // │ │ └── 1221
+ // │ └── 13
+ // └── 2
+ setFrameRateCategory(12, 0 /* default */);
+ setFrameRateSelectionStrategy(12, 0 /* Default */);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.category, FrameRateCategory::Default);
+ EXPECT_FALSE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+ EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.category, FrameRateCategory::Default);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_FALSE(getSnapshot({.id = 12})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.category, FrameRateCategory::Default);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 123.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.category, FrameRateCategory::Default);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 123.f);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.category, FrameRateCategory::Default);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, frameRateSelectionStrategyWithOverrideChildrenAndSelf) {
+ // ROOT
+ // ├── 1
+ // │ ├── 11 (frame rate set to 11.f with strategy Self)
+ // │ │ └── 111 (frame rate is not inherited)
+ // │ ├── 12 (frame rate set to 244.f)
+ // │ │ ├── 121
+ // │ │ └── 122 (strategy OverrideChildren and inherits frame rate 244.f)
+ // │ │ └── 1221 (frame rate set to 123.f but should be overridden by layer 122)
+ // │ └── 13
+ // └── 2
+ setFrameRate(11, 11.f, 0, 0);
+ setFrameRateSelectionStrategy(11, 2 /* Self */);
+ setFrameRate(12, 244.f, 0, 0);
+ setFrameRateSelectionStrategy(122, 1 /* OverrideChildren */);
+ setFrameRate(1221, 123.f, 0, 0);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ // verify parent 1 gets no vote
+ EXPECT_FALSE(getSnapshot({.id = 1})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::NoVote);
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 11.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Self);
+ EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 11 does does not propagate its framerate to 111.
+ EXPECT_FALSE(getSnapshot({.id = 111})->frameRate.vote.rate.isValid());
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 12 and all descendants (121, 122, 1221) get the requested vote
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 12})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 12})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 121})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::Propagate);
+ EXPECT_TRUE(getSnapshot({.id = 121})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 122})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 122})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRate.vote.rate.getValue(), 244.f);
+ EXPECT_EQ(getSnapshot({.id = 1221})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 1221})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // ROOT
+ // ├── 1 (frame rate set to 1.f with strategy OverrideChildren)
+ // │ ├── 11 (frame rate set to 11.f with strategy Self, but overridden by 1)
+ // │ │ └── 111 (frame rate inherited from 11 due to override from 1)
+ // â‹® â‹®
+ setFrameRate(1, 1.f, 0, 0);
+ setFrameRateSelectionStrategy(1, 1 /* OverrideChildren */);
+ setFrameRate(11, 11.f, 0, 0);
+ setFrameRateSelectionStrategy(11, 2 /* Self */);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.rate.getValue(), 1.f);
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRate.vote.type,
+ scheduler::FrameRateCompatibility::Default);
+ EXPECT_EQ(getSnapshot({.id = 1})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 1})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRate.vote.rate.getValue(), 1.f);
+ EXPECT_EQ(getSnapshot({.id = 11})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 11})->changes.test(RequestedLayerState::Changes::FrameRate));
+
+ // verify layer 11 does does not propagate its framerate to 111.
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRate.vote.rate.getValue(), 1.f);
+ EXPECT_EQ(getSnapshot({.id = 111})->frameRateSelectionStrategy,
+ scheduler::LayerInfo::FrameRateSelectionStrategy::OverrideChildren);
+ EXPECT_TRUE(getSnapshot({.id = 111})->changes.test(RequestedLayerState::Changes::FrameRate));
+}
+
+TEST_F(LayerSnapshotTest, skipRoundCornersWhenProtected) {
+ setRoundedCorners(1, 42.f);
+ setRoundedCorners(2, 42.f);
+ setCrop(1, Rect{1000, 1000});
+ setCrop(2, Rect{1000, 1000});
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot({.id = 1})->roundedCorner.hasRoundedCorners());
+ EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, 42.f);
+ EXPECT_TRUE(getSnapshot({.id = 2})->roundedCorner.hasRoundedCorners());
+
+ // add a buffer with the protected bit, check rounded corners are not set when
+ // skipRoundCornersWhenProtected == true
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 1ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_PROTECTED /*usage*/));
+
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = false,
+ .globalShadowSettings = globalShadowSettings,
+ .supportsBlur = true,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {},
+ .skipRoundCornersWhenProtected = true};
+ update(mSnapshotBuilder, args);
+ EXPECT_FALSE(getSnapshot({.id = 1})->roundedCorner.hasRoundedCorners());
+ // layer 2 doesn't have a buffer and should be unaffected
+ EXPECT_TRUE(getSnapshot({.id = 2})->roundedCorner.hasRoundedCorners());
+
+ // remove protected bit, check rounded corners are set
+ setBuffer(1,
+ std::make_shared<renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 2ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ 0 /*usage*/));
+ update(mSnapshotBuilder, args);
+ EXPECT_TRUE(getSnapshot({.id = 1})->roundedCorner.hasRoundedCorners());
+ EXPECT_EQ(getSnapshot({.id = 1})->roundedCorner.radius.x, 42.f);
+}
+
+TEST_F(LayerSnapshotTest, setRefreshRateIndicatorCompositionType) {
+ setFlags(1, layer_state_t::eLayerIsRefreshRateIndicator,
+ layer_state_t::eLayerIsRefreshRateIndicator);
+ setBuffer(1,
+ std::make_shared<renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 42ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ 0 /*usage*/));
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot({.id = 1})->compositionType,
+ aidl::android::hardware::graphics::composer3::Composition::REFRESH_RATE_INDICATOR);
+}
+
+TEST_F(LayerSnapshotTest, setBufferCrop) {
+ // validate no buffer but has crop
+ Rect crop = Rect(0, 0, 50, 50);
+ setBufferCrop(1, crop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop);
+
+ setBuffer(1,
+ std::make_shared<renderengine::mock::FakeExternalTexture>(100U /*width*/,
+ 100U /*height*/,
+ 42ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ 0 /*usage*/));
+ // validate a buffer crop within the buffer bounds
+ setBufferCrop(1, crop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->geomContentCrop, crop);
+
+ // validate a buffer crop outside the buffer bounds
+ crop = Rect(0, 0, 150, 150);
+ setBufferCrop(1, crop);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
+
+ // validate no buffer crop
+ setBufferCrop(1, Rect());
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->geomContentCrop, Rect(0, 0, 100, 100));
+}
+
+TEST_F(LayerSnapshotTest, setShadowRadius) {
+ static constexpr float SHADOW_RADIUS = 123.f;
+ setShadowRadius(1, SHADOW_RADIUS);
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_EQ(getSnapshot(1)->shadowSettings.length, SHADOW_RADIUS);
+}
+
+TEST_F(LayerSnapshotTest, setTrustedOverlayForNonVisibleInput) {
+ hideLayer(1);
+ setTrustedOverlay(1, true);
+ Region touch{Rect{0, 0, 1000, 1000}};
+ setTouchableRegion(1, touch);
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, {2});
+ EXPECT_TRUE(getSnapshot(1)->inputInfo.inputConfig.test(
+ gui::WindowInfo::InputConfig::TRUSTED_OVERLAY));
+}
+
+TEST_F(LayerSnapshotTest, isFrontBuffered) {
+ setBuffer(1,
+ std::make_shared<renderengine::mock::FakeExternalTexture>(
+ 1U /*width*/, 1U /*height*/, 1ULL /* bufferId */, HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_HW_TEXTURE | AHARDWAREBUFFER_USAGE_FRONT_BUFFER /*usage*/));
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_TRUE(getSnapshot(1)->isFrontBuffered());
+
+ setBuffer(1,
+ std::make_shared<
+ renderengine::mock::FakeExternalTexture>(1U /*width*/, 1U /*height*/,
+ 1ULL /* bufferId */,
+ HAL_PIXEL_FORMAT_RGBA_8888,
+ GRALLOC_USAGE_HW_TEXTURE /*usage*/));
+
+ UPDATE_AND_VERIFY(mSnapshotBuilder, STARTING_ZORDER);
+ EXPECT_FALSE(getSnapshot(1)->isFrontBuffered());
+}
+
+TEST_F(LayerSnapshotTest, setSecureRootSnapshot) {
+ setFlags(1, layer_state_t::eLayerSecure, layer_state_t::eLayerSecure);
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = false,
+ .globalShadowSettings = globalShadowSettings,
+ .supportsBlur = true,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {}};
+ args.rootSnapshot.isSecure = true;
+ update(mSnapshotBuilder, args);
+
+ EXPECT_TRUE(getSnapshot(1)->isSecure);
+ // Ensure child is also marked as secure
+ EXPECT_TRUE(getSnapshot(11)->isSecure);
+}
+
+// b/314350323
+TEST_F(LayerSnapshotTest, propagateDropInputMode) {
+ setDropInputMode(1, gui::DropInputMode::ALL);
+ LayerSnapshotBuilder::Args args{.root = mHierarchyBuilder.getHierarchy(),
+ .layerLifecycleManager = mLifecycleManager,
+ .includeMetadata = false,
+ .displays = mFrontEndDisplayInfos,
+ .displayChanges = false,
+ .globalShadowSettings = globalShadowSettings,
+ .supportsBlur = true,
+ .supportedLayerGenericMetadata = {},
+ .genericLayerMetadataKeyMap = {}};
+ args.rootSnapshot.isSecure = true;
+ update(mSnapshotBuilder, args);
+
+ EXPECT_EQ(getSnapshot(1)->dropInputMode, gui::DropInputMode::ALL);
+ // Ensure child also has the correct drop input mode regardless of whether either layer has
+ // an input channel
+ EXPECT_EQ(getSnapshot(11)->dropInputMode, gui::DropInputMode::ALL);
+}
+
+} // namespace android::surfaceflinger::frontend
diff --git a/services/surfaceflinger/tests/unittests/LayerTest.cpp b/services/surfaceflinger/tests/unittests/LayerTest.cpp
index 4974f90383..95e54f655b 100644
--- a/services/surfaceflinger/tests/unittests/LayerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerTest.cpp
@@ -17,7 +17,6 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
-#include <EffectLayer.h>
#include <gtest/gtest.h>
#include <ui/FloatRect.h>
#include <ui/Transform.h>
diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
index 5a2c1479bb..803e807d7b 100644
--- a/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
+++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -16,26 +16,19 @@
#include "LayerTestUtils.h"
-#include "mock/MockEventThread.h"
-
namespace android {
-using testing::_;
-using testing::Return;
-
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-
sp<Layer> BufferStateLayerFactory::createLayer(TestableSurfaceFlinger& flinger) {
sp<Client> client;
LayerCreationArgs args(flinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS,
LayerMetadata());
- return new BufferStateLayer(args);
+ return sp<Layer>::make(args);
}
sp<Layer> EffectLayerFactory::createLayer(TestableSurfaceFlinger& flinger) {
sp<Client> client;
LayerCreationArgs args(flinger.flinger(), client, "color-layer", LAYER_FLAGS, LayerMetadata());
- return new EffectLayer(args);
+ return sp<Layer>::make(args);
}
std::string PrintToStringParamName(
@@ -44,34 +37,7 @@ std::string PrintToStringParamName(
}
BaseLayerTest::BaseLayerTest() {
- setupScheduler();
-}
-
-void BaseLayerTest::setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread),
- TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
- TestableSurfaceFlinger::kTwoDisplayModes);
+ mFlinger.setupMockScheduler();
}
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/LayerTestUtils.h b/services/surfaceflinger/tests/unittests/LayerTestUtils.h
index fc9b6a27aa..0773d9081e 100644
--- a/services/surfaceflinger/tests/unittests/LayerTestUtils.h
+++ b/services/surfaceflinger/tests/unittests/LayerTestUtils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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.
@@ -23,8 +23,6 @@
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
-#include "BufferStateLayer.h"
-#include "EffectLayer.h"
#include "Layer.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion"
@@ -65,8 +63,6 @@ class BaseLayerTest : public ::testing::TestWithParam<std::shared_ptr<LayerFacto
protected:
BaseLayerTest();
- void setupScheduler();
-
TestableSurfaceFlinger mFlinger;
};
diff --git a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
index e0aa0b1768..9aa089f900 100644
--- a/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/MessageQueueTest.cpp
@@ -20,9 +20,11 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <scheduler/interface/ICompositor.h>
+
#include "FrameTimeline.h"
#include "Scheduler/MessageQueue.h"
-#include "SurfaceFlinger.h"
+#include "mock/MockVSyncDispatch.h"
namespace android {
@@ -32,8 +34,12 @@ using namespace testing;
using CallbackToken = scheduler::VSyncDispatch::CallbackToken;
struct NoOpCompositor final : ICompositor {
- bool commit(nsecs_t, int64_t, nsecs_t) override { return false; }
- void composite(nsecs_t, int64_t) override {}
+ void configure() override {}
+ bool commit(PhysicalDisplayId, const scheduler::FrameTargets&) override { return false; }
+ CompositeResultsPerDisplay composite(PhysicalDisplayId,
+ const scheduler::FrameTargeters&) override {
+ return {};
+ }
void sample() override {}
} gNoOpCompositor;
@@ -41,12 +47,15 @@ class TestableMessageQueue : public impl::MessageQueue {
struct MockHandler : MessageQueue::Handler {
using MessageQueue::Handler::Handler;
- MOCK_METHOD(void, dispatchFrame, (int64_t, nsecs_t), (override));
+ MOCK_METHOD(void, dispatchFrame, (VsyncId, TimePoint), (override));
};
explicit TestableMessageQueue(sp<MockHandler> handler)
: impl::MessageQueue(gNoOpCompositor, handler), mHandler(std::move(handler)) {}
+ // impl::MessageQueue overrides:
+ void onFrameSignal(ICompositor&, VsyncId, TimePoint) override {}
+
public:
TestableMessageQueue() : TestableMessageQueue(sp<MockHandler>::make(*this)) {}
@@ -55,14 +64,6 @@ public:
const sp<MockHandler> mHandler;
};
-struct MockVSyncDispatch : scheduler::VSyncDispatch {
- MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
- MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
- MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
- MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken token), (override));
- MOCK_METHOD(void, dump, (std::string&), (const, override));
-};
-
struct MockTokenManager : frametimeline::TokenManager {
MOCK_METHOD1(generateTokenForPredictions, int64_t(frametimeline::TimelineItem&& prediction));
MOCK_CONST_METHOD1(getPredictionsForToken, std::optional<frametimeline::TimelineItem>(int64_t));
@@ -70,31 +71,30 @@ struct MockTokenManager : frametimeline::TokenManager {
struct MessageQueueTest : testing::Test {
void SetUp() override {
- EXPECT_CALL(mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
- EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, mDuration));
- EXPECT_CALL(mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+ EXPECT_CALL(*mVSyncDispatch, registerCallback(_, "sf")).WillOnce(Return(mCallbackToken));
+ EXPECT_NO_FATAL_FAILURE(mEventQueue.initVsync(mVSyncDispatch, mTokenManager, kDuration));
+ EXPECT_CALL(*mVSyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
}
- MockVSyncDispatch mVSyncDispatch;
+ std::shared_ptr<mock::VSyncDispatch> mVSyncDispatch = std::make_shared<mock::VSyncDispatch>();
MockTokenManager mTokenManager;
TestableMessageQueue mEventQueue;
const CallbackToken mCallbackToken{5};
- constexpr static auto mDuration = std::chrono::nanoseconds(100ms);
- constexpr static auto mDifferentDuration = std::chrono::nanoseconds(250ms);
+
+ static constexpr Duration kDuration = 100ms;
+ static constexpr Duration kDifferentDuration = 250ms;
};
namespace {
-/* ------------------------------------------------------------------------
- * Test cases
- */
+
TEST_F(MessageQueueTest, commit) {
- const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
+ const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
.readyDuration = 0,
.earliestVsync = 0};
EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
- EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+ EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
@@ -103,17 +103,17 @@ TEST_F(MessageQueueTest, commit) {
TEST_F(MessageQueueTest, commitTwice) {
InSequence s;
- const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
+ const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
.readyDuration = 0,
.earliestVsync = 0};
- EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+ EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
- EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
+ EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(4567));
EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
@@ -122,47 +122,50 @@ TEST_F(MessageQueueTest, commitTwice) {
TEST_F(MessageQueueTest, commitTwiceWithCallback) {
InSequence s;
- const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
+ const auto timing = scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
.readyDuration = 0,
.earliestVsync = 0};
- EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
+ EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(1234));
EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
ASSERT_TRUE(mEventQueue.getScheduledFrameTime());
EXPECT_EQ(1234, mEventQueue.getScheduledFrameTime()->time_since_epoch().count());
- const auto startTime = 100;
- const auto endTime = startTime + mDuration.count();
- const auto presentTime = 500;
- const auto vsyncId = 42;
+ constexpr TimePoint kStartTime = TimePoint::fromNs(100);
+ constexpr TimePoint kEndTime = kStartTime + kDuration;
+ constexpr TimePoint kPresentTime = TimePoint::fromNs(500);
+ constexpr VsyncId vsyncId{42};
+
EXPECT_CALL(mTokenManager,
- generateTokenForPredictions(
- frametimeline::TimelineItem(startTime, endTime, presentTime)))
- .WillOnce(Return(vsyncId));
- EXPECT_CALL(*mEventQueue.mHandler, dispatchFrame(vsyncId, presentTime)).Times(1);
- EXPECT_NO_FATAL_FAILURE(mEventQueue.vsyncCallback(presentTime, startTime, endTime));
+ generateTokenForPredictions(frametimeline::TimelineItem(kStartTime.ns(),
+ kEndTime.ns(),
+ kPresentTime.ns())))
+ .WillOnce(Return(ftl::to_underlying(vsyncId)));
+ EXPECT_CALL(*mEventQueue.mHandler, dispatchFrame(vsyncId, kPresentTime)).Times(1);
+ EXPECT_NO_FATAL_FAILURE(
+ mEventQueue.vsyncCallback(kPresentTime.ns(), kStartTime.ns(), kEndTime.ns()));
EXPECT_FALSE(mEventQueue.getScheduledFrameTime());
const auto timingAfterCallback =
- scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDuration.count(),
+ scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDuration.ns(),
.readyDuration = 0,
- .earliestVsync = presentTime};
+ .earliestVsync = kPresentTime.ns()};
- EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
+ EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timingAfterCallback)).WillOnce(Return(0));
EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
}
TEST_F(MessageQueueTest, commitWithDurationChange) {
- EXPECT_NO_FATAL_FAILURE(mEventQueue.setDuration(mDifferentDuration));
+ EXPECT_NO_FATAL_FAILURE(mEventQueue.setDuration(kDifferentDuration));
const auto timing =
- scheduler::VSyncDispatch::ScheduleTiming{.workDuration = mDifferentDuration.count(),
+ scheduler::VSyncDispatch::ScheduleTiming{.workDuration = kDifferentDuration.ns(),
.readyDuration = 0,
.earliestVsync = 0};
- EXPECT_CALL(mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
+ EXPECT_CALL(*mVSyncDispatch, schedule(mCallbackToken, timing)).WillOnce(Return(0));
EXPECT_NO_FATAL_FAILURE(mEventQueue.scheduleFrame());
}
diff --git a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
index aafc323a9b..d08e12c7a4 100644
--- a/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/OneShotTimerTest.cpp
@@ -224,7 +224,8 @@ TEST_F(OneShotTimerTest, DISABLED_timeoutCallbackExecutionTest) {
EXPECT_FALSE(mResetTimerCallback.waitForUnexpectedCall().has_value());
}
-TEST_F(OneShotTimerTest, noCallbacksAfterStopAndResetTest) {
+// TODO(b/186417847) This test is flaky. Reenable once fixed.
+TEST_F(OneShotTimerTest, DISABLED_noCallbacksAfterStopAndResetTest) {
fake::FakeClock* clock = new fake::FakeClock();
mIdleTimer = std::make_unique<scheduler::OneShotTimer>("TestTimer", 1ms,
mResetTimerCallback.getInvocable(),
diff --git a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
index 8711a42b2b..9c66a97573 100644
--- a/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/PowerAdvisorTest.cpp
@@ -18,70 +18,104 @@
#define LOG_TAG "PowerAdvisorTest"
#include <DisplayHardware/PowerAdvisor.h>
-#include <compositionengine/Display.h>
-#include <ftl/fake_guard.h>
+#include <binder/Status.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <powermanager/PowerHalWrapper.h>
#include <ui/DisplayId.h>
#include <chrono>
+#include <future>
#include "TestableSurfaceFlinger.h"
-#include "mock/DisplayHardware/MockAidlPowerHalWrapper.h"
+#include "mock/DisplayHardware/MockIPowerHintSession.h"
+#include "mock/DisplayHardware/MockPowerHalController.h"
using namespace android;
using namespace android::Hwc2::mock;
using namespace android::hardware::power;
using namespace std::chrono_literals;
using namespace testing;
+using namespace android::power;
namespace android::Hwc2::impl {
class PowerAdvisorTest : public testing::Test {
public:
void SetUp() override;
- void startPowerHintSession();
- void fakeBasicFrameTiming(nsecs_t startTime, nsecs_t vsyncPeriod);
- void setExpectedTiming(nsecs_t startTime, nsecs_t vsyncPeriod);
- nsecs_t getFenceWaitDelayDuration(bool skipValidate);
+ void startPowerHintSession(bool returnValidSession = true);
+ void fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod);
+ void setExpectedTiming(Duration totalFrameTargetDuration, TimePoint expectedPresentTime);
+ Duration getFenceWaitDelayDuration(bool skipValidate);
+ Duration getErrorMargin();
+ void setTimingTestingMode(bool testinMode);
+ void allowReportActualToAcquireMutex();
+ bool sessionExists();
protected:
TestableSurfaceFlinger mFlinger;
std::unique_ptr<PowerAdvisor> mPowerAdvisor;
- NiceMock<MockAidlPowerHalWrapper>* mMockAidlWrapper;
- nsecs_t kErrorMargin = std::chrono::nanoseconds(1ms).count();
+ MockPowerHalController* mMockPowerHalController;
+ std::shared_ptr<MockIPowerHintSession> mMockPowerHintSession;
};
-void PowerAdvisorTest::SetUp() FTL_FAKE_GUARD(mPowerAdvisor->mPowerHalMutex) {
- std::unique_ptr<MockAidlPowerHalWrapper> mockAidlWrapper =
- std::make_unique<NiceMock<MockAidlPowerHalWrapper>>();
- mPowerAdvisor = std::make_unique<PowerAdvisor>(*mFlinger.flinger());
- ON_CALL(*mockAidlWrapper.get(), supportsPowerHintSession()).WillByDefault(Return(true));
- ON_CALL(*mockAidlWrapper.get(), startPowerHintSession()).WillByDefault(Return(true));
- mPowerAdvisor->mHalWrapper = std::move(mockAidlWrapper);
- mMockAidlWrapper =
- reinterpret_cast<NiceMock<MockAidlPowerHalWrapper>*>(mPowerAdvisor->mHalWrapper.get());
+bool PowerAdvisorTest::sessionExists() {
+ std::scoped_lock lock(mPowerAdvisor->mHintSessionMutex);
+ return mPowerAdvisor->mHintSession != nullptr;
}
-void PowerAdvisorTest::startPowerHintSession() {
- const std::vector<int32_t> threadIds = {1, 2, 3};
- mPowerAdvisor->enablePowerHint(true);
- mPowerAdvisor->startPowerHintSession(threadIds);
+void PowerAdvisorTest::SetUp() {
+ mPowerAdvisor = std::make_unique<impl::PowerAdvisor>(*mFlinger.flinger());
+ mPowerAdvisor->mPowerHal = std::make_unique<NiceMock<MockPowerHalController>>();
+ mMockPowerHalController =
+ reinterpret_cast<MockPowerHalController*>(mPowerAdvisor->mPowerHal.get());
+ ON_CALL(*mMockPowerHalController, getHintSessionPreferredRate)
+ .WillByDefault(Return(HalResult<int64_t>::fromStatus(binder::Status::ok(), 16000)));
}
-void PowerAdvisorTest::setExpectedTiming(nsecs_t totalFrameTarget, nsecs_t expectedPresentTime) {
- mPowerAdvisor->setTotalFrameTargetWorkDuration(totalFrameTarget);
+void PowerAdvisorTest::startPowerHintSession(bool returnValidSession) {
+ mMockPowerHintSession = ndk::SharedRefBase::make<NiceMock<MockIPowerHintSession>>();
+ if (returnValidSession) {
+ ON_CALL(*mMockPowerHalController, createHintSession)
+ .WillByDefault(
+ Return(HalResult<std::shared_ptr<IPowerHintSession>>::
+ fromStatus(binder::Status::ok(), mMockPowerHintSession)));
+ } else {
+ ON_CALL(*mMockPowerHalController, createHintSession)
+ .WillByDefault(Return(HalResult<std::shared_ptr<IPowerHintSession>>::
+ fromStatus(binder::Status::ok(), nullptr)));
+ }
+ mPowerAdvisor->enablePowerHintSession(true);
+ mPowerAdvisor->startPowerHintSession({1, 2, 3});
+ ON_CALL(*mMockPowerHintSession, updateTargetWorkDuration)
+ .WillByDefault(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
+}
+
+void PowerAdvisorTest::setExpectedTiming(Duration totalFrameTargetDuration,
+ TimePoint expectedPresentTime) {
+ mPowerAdvisor->setTotalFrameTargetWorkDuration(totalFrameTargetDuration);
mPowerAdvisor->setExpectedPresentTime(expectedPresentTime);
}
-void PowerAdvisorTest::fakeBasicFrameTiming(nsecs_t startTime, nsecs_t vsyncPeriod) {
+void PowerAdvisorTest::fakeBasicFrameTiming(TimePoint startTime, Duration vsyncPeriod) {
mPowerAdvisor->setCommitStart(startTime);
- mPowerAdvisor->setFrameDelay(0);
- mPowerAdvisor->setTargetWorkDuration(vsyncPeriod);
+ mPowerAdvisor->setFrameDelay(0ns);
+ mPowerAdvisor->updateTargetWorkDuration(vsyncPeriod);
+}
+
+void PowerAdvisorTest::setTimingTestingMode(bool testingMode) {
+ mPowerAdvisor->mTimingTestingMode = testingMode;
}
-nsecs_t PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
+void PowerAdvisorTest::allowReportActualToAcquireMutex() {
+ mPowerAdvisor->mDelayReportActualMutexAcquisitonPromise.set_value(true);
+}
+
+Duration PowerAdvisorTest::getFenceWaitDelayDuration(bool skipValidate) {
return (skipValidate ? PowerAdvisor::kFenceWaitStartDelaySkippedValidate
- : PowerAdvisor::kFenceWaitStartDelayValidated)
- .count();
+ : PowerAdvisor::kFenceWaitStartDelayValidated);
+}
+
+Duration PowerAdvisorTest::getErrorMargin() {
+ return mPowerAdvisor->sTargetSafetyMargin;
}
namespace {
@@ -93,11 +127,11 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) {
std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
// 60hz
- const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60;
- const nsecs_t presentDuration = std::chrono::nanoseconds(5ms).count();
- const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count();
+ const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
+ const Duration presentDuration = 5ms;
+ const Duration postCompDuration = 1ms;
- nsecs_t startTime = 100;
+ TimePoint startTime{100ns};
// advisor only starts on frame 2 so do an initial no-op frame
fakeBasicFrameTiming(startTime, vsyncPeriod);
@@ -109,16 +143,19 @@ TEST_F(PowerAdvisorTest, hintSessionUseHwcDisplay) {
// increment the frame
startTime += vsyncPeriod;
- const nsecs_t expectedDuration = kErrorMargin + presentDuration + postCompDuration;
- EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
-
+ const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration;
+ EXPECT_CALL(*mMockPowerHintSession,
+ reportActualWorkDuration(ElementsAre(
+ Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+ .Times(1)
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
fakeBasicFrameTiming(startTime, vsyncPeriod);
setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
mPowerAdvisor->setDisplays(displayIds);
- mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000);
- mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 2500000);
+ mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+ mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
- mPowerAdvisor->sendActualWorkDuration();
+ mPowerAdvisor->reportActualWorkDuration();
}
TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) {
@@ -128,12 +165,12 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) {
std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
// 60hz
- const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60;
- const nsecs_t presentDuration = std::chrono::nanoseconds(5ms).count();
- const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count();
- const nsecs_t hwcBlockedDuration = std::chrono::nanoseconds(500us).count();
+ const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
+ const Duration presentDuration = 5ms;
+ const Duration postCompDuration = 1ms;
+ const Duration hwcBlockedDuration = 500us;
- nsecs_t startTime = 100;
+ TimePoint startTime{100ns};
// advisor only starts on frame 2 so do an initial no-op frame
fakeBasicFrameTiming(startTime, vsyncPeriod);
@@ -145,19 +182,23 @@ TEST_F(PowerAdvisorTest, hintSessionSubtractsHwcFenceTime) {
// increment the frame
startTime += vsyncPeriod;
- const nsecs_t expectedDuration = kErrorMargin + presentDuration +
+ const Duration expectedDuration = getErrorMargin() + presentDuration +
getFenceWaitDelayDuration(false) - hwcBlockedDuration + postCompDuration;
- EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+ EXPECT_CALL(*mMockPowerHintSession,
+ reportActualWorkDuration(ElementsAre(
+ Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+ .Times(1)
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
fakeBasicFrameTiming(startTime, vsyncPeriod);
setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
mPowerAdvisor->setDisplays(displayIds);
- mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000);
- mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 3000000);
+ mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+ mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 3ms);
// now report the fence as having fired during the display HWC time
- mPowerAdvisor->setSfPresentTiming(startTime + 2000000 + hwcBlockedDuration,
+ mPowerAdvisor->setSfPresentTiming(startTime + 2ms + hwcBlockedDuration,
startTime + presentDuration);
- mPowerAdvisor->sendActualWorkDuration();
+ mPowerAdvisor->reportActualWorkDuration();
}
TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) {
@@ -168,12 +209,12 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) {
GpuVirtualDisplayId(1)};
// 60hz
- const nsecs_t vsyncPeriod = std::chrono::nanoseconds(1s).count() / 60;
+ const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
// make present duration much later than the hwc display by itself will account for
- const nsecs_t presentDuration = std::chrono::nanoseconds(10ms).count();
- const nsecs_t postCompDuration = std::chrono::nanoseconds(1ms).count();
+ const Duration presentDuration{10ms};
+ const Duration postCompDuration{1ms};
- nsecs_t startTime = 100;
+ TimePoint startTime{100ns};
// advisor only starts on frame 2 so do an initial no-op frame
fakeBasicFrameTiming(startTime, vsyncPeriod);
@@ -185,18 +226,148 @@ TEST_F(PowerAdvisorTest, hintSessionUsingSecondaryVirtualDisplays) {
// increment the frame
startTime += vsyncPeriod;
- const nsecs_t expectedDuration = kErrorMargin + presentDuration + postCompDuration;
- EXPECT_CALL(*mMockAidlWrapper, sendActualWorkDuration(Eq(expectedDuration), _)).Times(1);
+ const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration;
+ EXPECT_CALL(*mMockPowerHintSession,
+ reportActualWorkDuration(ElementsAre(
+ Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+ .Times(1)
+ .WillOnce(Return(testing::ByMove(ndk::ScopedAStatus::ok())));
fakeBasicFrameTiming(startTime, vsyncPeriod);
setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
mPowerAdvisor->setDisplays(displayIds);
// don't report timing for the gpu displays since they don't use hwc
- mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1000000, startTime + 1500000);
- mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2000000, startTime + 2500000);
+ mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+ mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
+ mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+ mPowerAdvisor->reportActualWorkDuration();
+}
+
+TEST_F(PowerAdvisorTest, hintSessionValidWhenNullFromPowerHAL) {
+ mPowerAdvisor->onBootFinished();
+
+ startPowerHintSession(false);
+
+ std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
+
+ // 60hz
+ const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
+ const Duration presentDuration = 5ms;
+ const Duration postCompDuration = 1ms;
+
+ TimePoint startTime{100ns};
+
+ // advisor only starts on frame 2 so do an initial no-op frame
+ fakeBasicFrameTiming(startTime, vsyncPeriod);
+ setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+ mPowerAdvisor->setDisplays(displayIds);
+ mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+ mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+
+ // increment the frame
+ startTime += vsyncPeriod;
+
+ const Duration expectedDuration = getErrorMargin() + presentDuration + postCompDuration;
+ EXPECT_CALL(*mMockPowerHintSession,
+ reportActualWorkDuration(ElementsAre(
+ Field(&WorkDuration::durationNanos, Eq(expectedDuration.ns())))))
+ .Times(0);
+ fakeBasicFrameTiming(startTime, vsyncPeriod);
+ setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+ mPowerAdvisor->setDisplays(displayIds);
+ mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+ mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
+ mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+ mPowerAdvisor->reportActualWorkDuration();
+}
+
+TEST_F(PowerAdvisorTest, hintSessionOnlyCreatedOnce) {
+ EXPECT_CALL(*mMockPowerHalController, createHintSession(_, _, _, _)).Times(1);
+ mPowerAdvisor->onBootFinished();
+ startPowerHintSession();
+ mPowerAdvisor->startPowerHintSession({1, 2, 3});
+}
+
+TEST_F(PowerAdvisorTest, hintSessionTestNotifyReportRace) {
+ // notifyDisplayUpdateImminentAndCpuReset or notifyCpuLoadUp gets called in background
+ // reportActual gets called during callback and sees true session, passes ensure
+ // first notify finishes, setting value to true. Another async method gets called, acquires the
+ // lock between reportactual finishing ensure and acquiring the lock itself, and sets session to
+ // nullptr. reportActual acquires the lock, and the session is now null, so it does nullptr
+ // deref
+
+ mPowerAdvisor->onBootFinished();
+ startPowerHintSession();
+
+ // --- fake a bunch of timing data
+ std::vector<DisplayId> displayIds{PhysicalDisplayId::fromPort(42u)};
+ // 60hz
+ const Duration vsyncPeriod{std::chrono::nanoseconds(1s) / 60};
+ const Duration presentDuration = 5ms;
+ const Duration postCompDuration = 1ms;
+ TimePoint startTime{100ns};
+ // advisor only starts on frame 2 so do an initial no-op frame
+ fakeBasicFrameTiming(startTime, vsyncPeriod);
+ setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+ mPowerAdvisor->setDisplays(displayIds);
+ mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
+ mPowerAdvisor->setCompositeEnd(startTime + presentDuration + postCompDuration);
+ // increment the frame
+ startTime += vsyncPeriod;
+ fakeBasicFrameTiming(startTime, vsyncPeriod);
+ setExpectedTiming(vsyncPeriod, startTime + vsyncPeriod);
+ mPowerAdvisor->setDisplays(displayIds);
+ mPowerAdvisor->setHwcValidateTiming(displayIds[0], startTime + 1ms, startTime + 1500us);
+ mPowerAdvisor->setHwcPresentTiming(displayIds[0], startTime + 2ms, startTime + 2500us);
mPowerAdvisor->setSfPresentTiming(startTime, startTime + presentDuration);
- mPowerAdvisor->sendActualWorkDuration();
+ // --- Done faking timing data
+
+ setTimingTestingMode(true);
+ std::promise<bool> letSendHintFinish;
+
+ ON_CALL(*mMockPowerHintSession, sendHint).WillByDefault([&letSendHintFinish] {
+ letSendHintFinish.get_future().wait();
+ return ndk::ScopedAStatus::fromExceptionCode(-127);
+ });
+
+ ON_CALL(*mMockPowerHintSession, reportActualWorkDuration).WillByDefault([] {
+ return ndk::ScopedAStatus::fromExceptionCode(-127);
+ });
+
+ ON_CALL(*mMockPowerHalController, createHintSession)
+ .WillByDefault(Return(
+ HalResult<std::shared_ptr<IPowerHintSession>>::
+ fromStatus(ndk::ScopedAStatus::fromExceptionCode(-127), nullptr)));
+
+ // First background call, to notice the session is down
+ auto firstHint = std::async(std::launch::async, [this] {
+ mPowerAdvisor->notifyCpuLoadUp();
+ return true;
+ });
+ std::this_thread::sleep_for(10ms);
+
+ // Call reportActual while callback is resolving to try and sneak past ensure
+ auto reportActual =
+ std::async(std::launch::async, [this] { mPowerAdvisor->reportActualWorkDuration(); });
+
+ std::this_thread::sleep_for(10ms);
+ // Let the first call finish
+ letSendHintFinish.set_value(true);
+ letSendHintFinish = std::promise<bool>{};
+ firstHint.wait();
+
+ // Do the second notify call, to ensure the session is nullptr
+ auto secondHint = std::async(std::launch::async, [this] {
+ mPowerAdvisor->notifyCpuLoadUp();
+ return true;
+ });
+ letSendHintFinish.set_value(true);
+ secondHint.wait();
+ // Let report finish, potentially dereferencing
+ allowReportActualToAcquireMutex();
+ reportActual.wait();
+ EXPECT_EQ(sessionExists(), false);
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
deleted file mode 100644
index 188fd58dea..0000000000
--- a/services/surfaceflinger/tests/unittests/RefreshRateConfigsTest.cpp
+++ /dev/null
@@ -1,2077 +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.
- */
-
-#undef LOG_TAG
-#define LOG_TAG "SchedulerUnittests"
-
-#include <ftl/enum.h>
-#include <gmock/gmock.h>
-#include <log/log.h>
-#include <ui/Size.h>
-
-#include "DisplayHardware/HWC2.h"
-#include "FpsOps.h"
-#include "Scheduler/RefreshRateConfigs.h"
-#include "mock/DisplayHardware/MockDisplayMode.h"
-
-using namespace std::chrono_literals;
-
-namespace android::scheduler {
-
-namespace hal = android::hardware::graphics::composer::hal;
-
-using LayerVoteType = RefreshRateConfigs::LayerVoteType;
-using LayerRequirement = RefreshRateConfigs::LayerRequirement;
-
-using mock::createDisplayMode;
-
-struct TestableRefreshRateConfigs : RefreshRateConfigs {
- using RefreshRateConfigs::RefreshRateConfigs;
-
- DisplayModePtr getMinSupportedRefreshRate() const {
- std::lock_guard lock(mLock);
- return mMinRefreshRateModeIt->second;
- }
-
- DisplayModePtr getMaxSupportedRefreshRate() const {
- std::lock_guard lock(mLock);
- return mMaxRefreshRateModeIt->second;
- }
-
- DisplayModePtr getMinRefreshRateByPolicy() const {
- std::lock_guard lock(mLock);
- return getMinRefreshRateByPolicyLocked();
- }
-
- const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
-
- using RefreshRateConfigs::GetBestRefreshRateCache;
- auto& mutableGetBestRefreshRateCache() { return mGetBestRefreshRateCache; }
-
- auto getBestRefreshRateAndSignals(const std::vector<LayerRequirement>& layers,
- GlobalSignals signals) const {
- return RefreshRateConfigs::getBestRefreshRate(layers, signals);
- }
-
- DisplayModePtr getBestRefreshRate(const std::vector<LayerRequirement>& layers = {},
- GlobalSignals signals = {}) const {
- return getBestRefreshRateAndSignals(layers, signals).first;
- }
-};
-
-class RefreshRateConfigsTest : public testing::Test {
-protected:
- RefreshRateConfigsTest();
- ~RefreshRateConfigsTest();
-
- static constexpr DisplayModeId kModeId60{0};
- static constexpr DisplayModeId kModeId90{1};
- static constexpr DisplayModeId kModeId72{2};
- static constexpr DisplayModeId kModeId120{3};
- static constexpr DisplayModeId kModeId30{4};
- static constexpr DisplayModeId kModeId25{5};
- static constexpr DisplayModeId kModeId50{6};
- static constexpr DisplayModeId kModeId24{7};
- static constexpr DisplayModeId kModeId24Frac{8};
- static constexpr DisplayModeId kModeId30Frac{9};
- static constexpr DisplayModeId kModeId60Frac{10};
-
- static inline const DisplayModePtr kMode60 = createDisplayMode(kModeId60, 60_Hz);
- static inline const DisplayModePtr kMode60Frac = createDisplayMode(kModeId60Frac, 59.94_Hz);
- static inline const DisplayModePtr kMode90 = createDisplayMode(kModeId90, 90_Hz);
- static inline const DisplayModePtr kMode90_G1 = createDisplayMode(kModeId90, 90_Hz, 1);
- static inline const DisplayModePtr kMode90_4K =
- createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160});
- static inline const DisplayModePtr kMode72 = createDisplayMode(kModeId72, 72_Hz);
- static inline const DisplayModePtr kMode72_G1 = createDisplayMode(kModeId72, 72_Hz, 1);
- static inline const DisplayModePtr kMode120 = createDisplayMode(kModeId120, 120_Hz);
- static inline const DisplayModePtr kMode120_G1 = createDisplayMode(kModeId120, 120_Hz, 1);
- static inline const DisplayModePtr kMode30 = createDisplayMode(kModeId30, 30_Hz);
- static inline const DisplayModePtr kMode30_G1 = createDisplayMode(kModeId30, 30_Hz, 1);
- static inline const DisplayModePtr kMode30Frac = createDisplayMode(kModeId30Frac, 29.97_Hz);
- static inline const DisplayModePtr kMode25 = createDisplayMode(kModeId25, 25_Hz);
- static inline const DisplayModePtr kMode25_G1 = createDisplayMode(kModeId25, 25_Hz, 1);
- static inline const DisplayModePtr kMode50 = createDisplayMode(kModeId50, 50_Hz);
- static inline const DisplayModePtr kMode24 = createDisplayMode(kModeId24, 24_Hz);
- static inline const DisplayModePtr kMode24Frac = createDisplayMode(kModeId24Frac, 23.976_Hz);
-
- // Test configurations.
- static inline const DisplayModes kModes_60 = makeModes(kMode60);
- static inline const DisplayModes kModes_60_90 = makeModes(kMode60, kMode90);
- static inline const DisplayModes kModes_60_90_G1 = makeModes(kMode60, kMode90_G1);
- static inline const DisplayModes kModes_60_90_4K = makeModes(kMode60, kMode90_4K);
- static inline const DisplayModes kModes_60_72_90 = makeModes(kMode60, kMode90, kMode72);
- static inline const DisplayModes kModes_60_90_72_120 =
- makeModes(kMode60, kMode90, kMode72, kMode120);
- static inline const DisplayModes kModes_30_60_72_90_120 =
- makeModes(kMode60, kMode90, kMode72, kMode120, kMode30);
-
- static inline const DisplayModes kModes_30_60 =
- makeModes(kMode60, kMode90_G1, kMode72_G1, kMode120_G1, kMode30);
- static inline const DisplayModes kModes_30_60_72_90 =
- makeModes(kMode60, kMode90, kMode72, kMode120_G1, kMode30);
- static inline const DisplayModes kModes_30_60_90 =
- makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30);
- static inline const DisplayModes kModes_25_30_50_60 =
- makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30_G1, kMode25_G1, kMode50);
- static inline const DisplayModes kModes_60_120 = makeModes(kMode60, kMode120);
-
- // This is a typical TV configuration.
- static inline const DisplayModes kModes_24_25_30_50_60_Frac =
- makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
- kMode60Frac);
-};
-
-RefreshRateConfigsTest::RefreshRateConfigsTest() {
- 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());
-}
-
-RefreshRateConfigsTest::~RefreshRateConfigsTest() {
- 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());
-}
-
-namespace {
-
-TEST_F(RefreshRateConfigsTest, oneMode_canSwitch) {
- RefreshRateConfigs configs(kModes_60, kModeId60);
- EXPECT_FALSE(configs.canSwitch());
-}
-
-TEST_F(RefreshRateConfigsTest, invalidPolicy) {
- RefreshRateConfigs configs(kModes_60, kModeId60);
- EXPECT_LT(configs.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}), 0);
- EXPECT_LT(configs.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}), 0);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- const auto minRate = configs.getMinSupportedRefreshRate();
- const auto performanceRate = configs.getMaxSupportedRefreshRate();
-
- EXPECT_EQ(kMode60, minRate);
- EXPECT_EQ(kMode90, performanceRate);
-
- const auto minRateByPolicy = configs.getMinRefreshRateByPolicy();
- const auto performanceRateByPolicy = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(minRateByPolicy, minRate);
- EXPECT_EQ(performanceRateByPolicy, performanceRate);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentGroups) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- const auto minRate = configs.getMinRefreshRateByPolicy();
- const auto performanceRate = configs.getMaxSupportedRefreshRate();
- const auto minRate60 = configs.getMinRefreshRateByPolicy();
- const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(kMode60, minRate);
- EXPECT_EQ(kMode60, minRate60);
- EXPECT_EQ(kMode60, performanceRate60);
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0);
- configs.setActiveModeId(kModeId90);
-
- const auto minRate90 = configs.getMinRefreshRateByPolicy();
- const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(kMode90_G1, performanceRate);
- EXPECT_EQ(kMode90_G1, minRate90);
- EXPECT_EQ(kMode90_G1, performanceRate90);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
- TestableRefreshRateConfigs configs(kModes_60_90_4K, kModeId60);
-
- const auto minRate = configs.getMinRefreshRateByPolicy();
- const auto performanceRate = configs.getMaxSupportedRefreshRate();
- const auto minRate60 = configs.getMinRefreshRateByPolicy();
- const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(kMode60, minRate);
- EXPECT_EQ(kMode60, minRate60);
- EXPECT_EQ(kMode60, performanceRate60);
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}), 0);
- configs.setActiveModeId(kModeId90);
-
- const auto minRate90 = configs.getMinRefreshRateByPolicy();
- const auto performanceRate90 = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(kMode90_4K, performanceRate);
- EXPECT_EQ(kMode90_4K, minRate90);
- EXPECT_EQ(kMode90_4K, performanceRate90);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_policyChange) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- const auto minRate = configs.getMinRefreshRateByPolicy();
- const auto performanceRate = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(kMode60, minRate);
- EXPECT_EQ(kMode90, performanceRate);
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
-
- const auto minRate60 = configs.getMinRefreshRateByPolicy();
- const auto performanceRate60 = configs.getMaxRefreshRateByPolicy();
-
- EXPECT_EQ(kMode60, minRate60);
- EXPECT_EQ(kMode60, performanceRate60);
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_getActiveMode) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
- {
- const auto mode = configs.getActiveMode();
- EXPECT_EQ(mode->getId(), kModeId60);
- }
-
- configs.setActiveModeId(kModeId90);
- {
- const auto mode = configs.getActiveMode();
- EXPECT_EQ(mode->getId(), kModeId90);
- }
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
- {
- const auto mode = configs.getActiveMode();
- EXPECT_EQ(mode->getId(), kModeId90);
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_noLayers) {
- {
- TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId72);
-
- // If there are no layers we select the default frame rate, which is the max of the primary
- // range.
- EXPECT_EQ(kMode90, configs.getBestRefreshRate());
-
- EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), NO_ERROR);
- EXPECT_EQ(kMode60, configs.getBestRefreshRate());
- }
- {
- // We select max even when this will cause a non-seamless switch.
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
- constexpr bool kAllowGroupSwitching = true;
- EXPECT_EQ(configs.setDisplayManagerPolicy({kModeId90, kAllowGroupSwitching, {0_Hz, 90_Hz}}),
- NO_ERROR);
- EXPECT_EQ(kMode90_G1, configs.getBestRefreshRate());
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_90) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::Min;
- lr.name = "Min";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- lr.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- lr.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- lr.name = "45Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- lr.name = "30Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- lr.name = "24Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.name = "";
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
-
- lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
-
- lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}), 0);
- lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_multipleThreshold_60_90) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::Min;
- lr.name = "Min";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- lr.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- lr.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- lr.name = "45Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- lr.name = "30Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- lr.name = "24Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_60_72_90) {
- TestableRefreshRateConfigs configs(kModes_60_72_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90_120) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 48_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 48_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "60Hz ExplicitDefault";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::Heuristic;
- lr1.name = "24Hz Heuristic";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.name = "90Hz ExplicitExactOrMultiple";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_90_120_DifferentTypes_multipleThreshold) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
- {.frameRateMultipleThreshold = 120});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
- auto& lr3 = layers[2];
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "60Hz ExplicitDefault";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::Heuristic;
- lr1.name = "24Hz Heuristic";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.name = "24Hz ExplicitDefault";
- lr2.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.name = "90Hz ExplicitExactOrMultiple";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Max;
- lr2.name = "Max";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 120_Hz;
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.name = "120Hz ExplicitDefault";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 24_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "24Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 120_Hz;
- lr2.vote = LayerVoteType::ExplicitExact;
- lr2.name = "120Hz ExplicitExact";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 10_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "30Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 120_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.name = "120Hz ExplicitExact";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- lr1.desiredRefreshRate = 30_Hz;
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.name = "30Hz ExplicitExactOrMultiple";
- lr2.desiredRefreshRate = 30_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.name = "30Hz ExplicitExactOrMultiple";
- lr3.vote = LayerVoteType::Heuristic;
- lr3.desiredRefreshRate = 120_Hz;
- lr3.name = "120Hz Heuristic";
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60) {
- TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::Min;
- EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 30_Hz;
- EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_30_60_72_90) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::Min;
- lr.name = "Min";
- EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- lr.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 90_Hz;
- lr.vote = LayerVoteType::Heuristic;
- lr.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz Heuristic";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
- lr.desiredRefreshRate = 45_Hz;
- lr.name = "45Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
- lr.desiredRefreshRate = 30_Hz;
- lr.name = "30Hz Heuristic";
- EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
- lr.desiredRefreshRate = 24_Hz;
- lr.name = "24Hz Heuristic";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
- lr.desiredRefreshRate = 24_Hz;
- lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr.name = "24Hz ExplicitExactOrMultiple";
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_PriorityTest) {
- TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::Min;
- lr2.vote = LayerVoteType::Max;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Min;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Min;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 24_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Max;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Max;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 15_Hz;
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 30_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 45_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
- lr.desiredRefreshRate = Fps::fromValue(fps);
- const auto mode = configs.getBestRefreshRate(layers);
- EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
- << to_string(mode->getFps());
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_24FpsVideo_multipleThreshold_60_120) {
- TestableRefreshRateConfigs configs(kModes_60_120, kModeId60,
- {.frameRateMultipleThreshold = 120});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
- lr.desiredRefreshRate = Fps::fromValue(fps);
- const auto mode = configs.getBestRefreshRate(layers);
- EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
- << to_string(mode->getFps());
- }
-}
-
-TEST_F(RefreshRateConfigsTest, twoModes_getBestRefreshRate_Explicit) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 60_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 90_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::Heuristic;
- lr1.desiredRefreshRate = 90_Hz;
- lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr2.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_75HzContent) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
- lr.desiredRefreshRate = Fps::fromValue(fps);
- const auto mode = configs.getBestRefreshRate(layers, {});
- EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
- << to_string(mode->getFps());
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_Multiples) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 90_Hz;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::ExplicitDefault;
- lr2.desiredRefreshRate = 90_Hz;
- lr2.name = "90Hz ExplicitDefault";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Max;
- lr2.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 30_Hz;
- lr1.name = "30Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 90_Hz;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 30_Hz;
- lr1.name = "30Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Max;
- lr2.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, scrollWhileWatching60fps_60_90) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::NoVote;
- lr2.name = "NoVote";
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::NoVote;
- lr2.name = "NoVote";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Max;
- lr2.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.touch = true}));
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Max;
- lr2.name = "Max";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- // The other layer starts to provide buffers
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 90_Hz;
- lr2.name = "90Hz Heuristic";
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, touchConsidered) {
- RefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- auto [_, signals] = configs.getBestRefreshRate({}, {});
- EXPECT_FALSE(signals.touch);
-
- std::tie(std::ignore, signals) = configs.getBestRefreshRate({}, {.touch = true});
- EXPECT_TRUE(signals.touch);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
- auto& lr1 = layers[0];
- auto& lr2 = layers[1];
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60_Hz;
- lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
- EXPECT_TRUE(signals.touch);
-
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60_Hz;
- lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
- EXPECT_FALSE(signals.touch);
-
- lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60_Hz;
- lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
- EXPECT_TRUE(signals.touch);
-
- lr1.vote = LayerVoteType::ExplicitDefault;
- lr1.desiredRefreshRate = 60_Hz;
- lr1.name = "60Hz ExplicitExactOrMultiple";
- lr2.vote = LayerVoteType::Heuristic;
- lr2.desiredRefreshRate = 60_Hz;
- lr2.name = "60Hz Heuristic";
- std::tie(std::ignore, signals) = configs.getBestRefreshRate(layers, {.touch = true});
- EXPECT_FALSE(signals.touch);
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitDefault) {
- TestableRefreshRateConfigs configs(kModes_60_90_72_120, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- // Prepare a table with the vote and the expected refresh rate
- const std::initializer_list<std::pair<Fps, Fps>> testCases = {
- {130_Hz, 120_Hz}, {120_Hz, 120_Hz}, {119_Hz, 120_Hz}, {110_Hz, 120_Hz},
-
- {100_Hz, 90_Hz}, {90_Hz, 90_Hz}, {89_Hz, 90_Hz},
-
- {80_Hz, 72_Hz}, {73_Hz, 72_Hz}, {72_Hz, 72_Hz}, {71_Hz, 72_Hz}, {70_Hz, 72_Hz},
-
- {65_Hz, 60_Hz}, {60_Hz, 60_Hz}, {59_Hz, 60_Hz}, {58_Hz, 60_Hz},
-
- {55_Hz, 90_Hz}, {50_Hz, 90_Hz}, {45_Hz, 90_Hz},
-
- {42_Hz, 120_Hz}, {40_Hz, 120_Hz}, {39_Hz, 120_Hz},
-
- {37_Hz, 72_Hz}, {36_Hz, 72_Hz}, {35_Hz, 72_Hz},
-
- {30_Hz, 60_Hz},
- };
-
- for (auto [desired, expected] : testCases) {
- lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = desired;
-
- std::stringstream ss;
- ss << "ExplicitDefault " << desired;
- lr.name = ss.str();
-
- EXPECT_EQ(expected, configs.getBestRefreshRate(layers)->getFps());
- }
-}
-
-TEST_F(RefreshRateConfigsTest,
- getBestRefreshRate_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- // Test that 23.976 will choose 24 if 23.976 is not supported
- {
- TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
- kMode60, kMode60Frac),
- kModeId60);
-
- lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr.desiredRefreshRate = 23.976_Hz;
- lr.name = "ExplicitExactOrMultiple 23.976 Hz";
- EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers)->getId());
- }
-
- // Test that 24 will choose 23.976 if 24 is not supported
- {
- TestableRefreshRateConfigs configs(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
- kMode60, kMode60Frac),
- kModeId60);
-
- lr.desiredRefreshRate = 24_Hz;
- lr.name = "ExplicitExactOrMultiple 24 Hz";
- EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers)->getId());
- }
-
- // Test that 29.97 will prefer 59.94 over 60 and 30
- {
- TestableRefreshRateConfigs configs(makeModes(kMode24, kMode24Frac, kMode25, kMode30,
- kMode60, kMode60Frac),
- kModeId60);
-
- lr.desiredRefreshRate = 29.97_Hz;
- lr.name = "ExplicitExactOrMultiple 29.97 Hz";
- EXPECT_EQ(kModeId60Frac, configs.getBestRefreshRate(layers)->getId());
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact_WithFractionalRefreshRates) {
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- // Test that voting for supported refresh rate will select this refresh rate
- {
- TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60);
-
- for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
- lr.vote = LayerVoteType::ExplicitExact;
- lr.desiredRefreshRate = desired;
- std::stringstream ss;
- ss << "ExplicitExact " << desired;
- lr.name = ss.str();
-
- EXPECT_EQ(lr.desiredRefreshRate, configs.getBestRefreshRate(layers)->getFps());
- }
- }
-
- // Test that 23.976 will choose 24 if 23.976 is not supported
- {
- TestableRefreshRateConfigs configs(makeModes(kMode24, kMode25, kMode30, kMode30Frac,
- kMode60, kMode60Frac),
- kModeId60);
-
- lr.vote = LayerVoteType::ExplicitExact;
- lr.desiredRefreshRate = 23.976_Hz;
- lr.name = "ExplicitExact 23.976 Hz";
- EXPECT_EQ(kModeId24, configs.getBestRefreshRate(layers)->getId());
- }
-
- // Test that 24 will choose 23.976 if 24 is not supported
- {
- TestableRefreshRateConfigs configs(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
- kMode60, kMode60Frac),
- kModeId60);
-
- lr.desiredRefreshRate = 24_Hz;
- lr.name = "ExplicitExact 24 Hz";
- EXPECT_EQ(kModeId24Frac, configs.getBestRefreshRate(layers)->getId());
- }
-}
-
-TEST_F(RefreshRateConfigsTest,
- getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
- RefreshRateConfigs configs(kModes_60_90, kModeId90);
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz ExplicitDefault";
- lr.focused = true;
-
- const auto [mode, signals] = configs.getBestRefreshRate(layers, {.touch = true, .idle = true});
-
- EXPECT_EQ(mode, kMode60);
- EXPECT_FALSE(signals.touch);
-}
-
-TEST_F(RefreshRateConfigsTest,
- getBestRefreshRate_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 90_Hz}}), 0);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = 90_Hz;
- lr.name = "90Hz ExplicitDefault";
- lr.focused = true;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers, {.idle = true}));
-}
-
-TEST_F(RefreshRateConfigsTest,
- getBestRefreshRate_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId90);
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
-
- const auto [mode, signals] = configs.getBestRefreshRateAndSignals({}, {});
- EXPECT_EQ(mode, kMode90);
- EXPECT_FALSE(signals.touch);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& lr = layers[0];
-
- lr.vote = LayerVoteType::ExplicitExactOrMultiple;
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz ExplicitExactOrMultiple";
- lr.focused = false;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.focused = true;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::ExplicitDefault;
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz ExplicitDefault";
- lr.focused = false;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.focused = true;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Heuristic;
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz Heuristic";
- lr.focused = false;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.focused = true;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Max;
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz Max";
- lr.focused = false;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.focused = true;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.vote = LayerVoteType::Min;
- lr.desiredRefreshRate = 60_Hz;
- lr.name = "60Hz Min";
- lr.focused = false;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- lr.focused = true;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingNotAllowed) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- // The default policy doesn't allow group switching. Verify that no
- // group switches are performed.
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::ExplicitDefault;
- layer.desiredRefreshRate = 90_Hz;
- layer.seamlessness = Seamlessness::SeamedAndSeamless;
- layer.name = "90Hz ExplicitDefault";
- layer.focused = true;
-
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayer) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::ExplicitDefault;
- layer.desiredRefreshRate = 90_Hz;
- layer.seamlessness = Seamlessness::SeamedAndSeamless;
- layer.name = "90Hz ExplicitDefault";
- layer.focused = true;
- EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamless) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- // Verify that we won't change the group if seamless switch is required.
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::ExplicitDefault;
- layer.desiredRefreshRate = 90_Hz;
- layer.seamlessness = Seamlessness::OnlySeamless;
- layer.name = "90Hz ExplicitDefault";
- layer.focused = true;
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- configs.setActiveModeId(kModeId90);
-
- // Verify that we won't do a seamless switch if we request the same mode as the default
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::ExplicitDefault;
- layer.desiredRefreshRate = 60_Hz;
- layer.seamlessness = Seamlessness::OnlySeamless;
- layer.name = "60Hz ExplicitDefault";
- layer.focused = true;
- EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- configs.setActiveModeId(kModeId90);
-
- // Verify that if the current config is in another group and there are no layers with
- // seamlessness=SeamedAndSeamless we'll go back to the default group.
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::ExplicitDefault;
- layer.desiredRefreshRate = 60_Hz;
- layer.seamlessness = Seamlessness::Default;
- layer.name = "60Hz ExplicitDefault";
- layer.focused = true;
-
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- configs.setActiveModeId(kModeId90);
-
- // If there's a layer with seamlessness=SeamedAndSeamless, another layer with
- // seamlessness=OnlySeamless can't change the mode group.
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].vote = LayerVoteType::ExplicitDefault;
- layers[0].desiredRefreshRate = 60_Hz;
- layers[0].seamlessness = Seamlessness::OnlySeamless;
- layers[0].name = "60Hz ExplicitDefault";
- layers[0].focused = true;
-
- layers.push_back(LayerRequirement{.weight = 0.5f});
- layers[1].vote = LayerVoteType::ExplicitDefault;
- layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
- layers[1].desiredRefreshRate = 90_Hz;
- layers[1].name = "90Hz ExplicitDefault";
- layers[1].focused = false;
-
- EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- configs.setActiveModeId(kModeId90);
-
- // If there's a focused layer with seamlessness=SeamedAndSeamless, another layer with
- // seamlessness=Default can't change the mode group back to the group of the default
- // mode.
- // For example, this may happen when a video playback requests and gets a seamed switch,
- // but another layer (with default seamlessness) starts animating. The animating layer
- // should not cause a seamed switch.
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].seamlessness = Seamlessness::Default;
- layers[0].desiredRefreshRate = 60_Hz;
- layers[0].focused = true;
- layers[0].vote = LayerVoteType::ExplicitDefault;
- layers[0].name = "60Hz ExplicitDefault";
-
- layers.push_back(LayerRequirement{.weight = 0.1f});
- layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
- layers[1].desiredRefreshRate = 90_Hz;
- layers[1].focused = true;
- layers[1].vote = LayerVoteType::ExplicitDefault;
- layers[1].name = "90Hz ExplicitDefault";
-
- EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId60);
-
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- configs.setActiveModeId(kModeId90);
-
- // Layer with seamlessness=Default can change the mode group if there's a not
- // focused layer with seamlessness=SeamedAndSeamless. This happens for example,
- // when in split screen mode the user switches between the two visible applications.
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].seamlessness = Seamlessness::Default;
- layers[0].desiredRefreshRate = 60_Hz;
- layers[0].focused = true;
- layers[0].vote = LayerVoteType::ExplicitDefault;
- layers[0].name = "60Hz ExplicitDefault";
-
- layers.push_back(LayerRequirement{.weight = 0.7f});
- layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
- layers[1].desiredRefreshRate = 90_Hz;
- layers[1].focused = false;
- layers[1].vote = LayerVoteType::ExplicitDefault;
- layers[1].name = "90Hz ExplicitDefault";
-
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, nonSeamlessVotePrefersSeamlessSwitches) {
- TestableRefreshRateConfigs configs(kModes_30_60, kModeId60);
-
- // Allow group switching.
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::ExplicitExactOrMultiple;
- layer.desiredRefreshRate = 60_Hz;
- layer.seamlessness = Seamlessness::SeamedAndSeamless;
- layer.name = "60Hz ExplicitExactOrMultiple";
- layer.focused = true;
-
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate(layers)->getId());
-
- configs.setActiveModeId(kModeId120);
- EXPECT_EQ(kModeId120, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, nonSeamlessExactAndSeamlessMultipleLayers) {
- TestableRefreshRateConfigs configs(kModes_25_30_50_60, kModeId60);
-
- // Allow group switching.
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- std::vector<LayerRequirement> layers = {{.name = "60Hz ExplicitDefault",
- .vote = LayerVoteType::ExplicitDefault,
- .desiredRefreshRate = 60_Hz,
- .seamlessness = Seamlessness::SeamedAndSeamless,
- .weight = 0.5f,
- .focused = false},
- {.name = "25Hz ExplicitExactOrMultiple",
- .vote = LayerVoteType::ExplicitExactOrMultiple,
- .desiredRefreshRate = 25_Hz,
- .seamlessness = Seamlessness::OnlySeamless,
- .weight = 1.f,
- .focused = true}};
-
- EXPECT_EQ(kModeId50, configs.getBestRefreshRate(layers)->getId());
-
- auto& seamedLayer = layers[0];
- seamedLayer.desiredRefreshRate = 30_Hz;
- seamedLayer.name = "30Hz ExplicitDefault";
- configs.setActiveModeId(kModeId30);
-
- EXPECT_EQ(kModeId25, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, minLayersDontTrigerSeamedSwitch) {
- TestableRefreshRateConfigs configs(kModes_60_90_G1, kModeId90);
-
- // Allow group switching.
- RefreshRateConfigs::Policy policy;
- policy.defaultMode = configs.getCurrentPolicy().defaultMode;
- policy.allowGroupSwitching = true;
- EXPECT_GE(configs.setDisplayManagerPolicy(policy), 0);
-
- std::vector<LayerRequirement> layers = {
- {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
-
- EXPECT_EQ(kModeId90, configs.getBestRefreshRate(layers)->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, primaryVsAppRequestPolicy) {
- TestableRefreshRateConfigs configs(kModes_30_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].name = "Test layer";
-
- struct Args {
- bool touch = false;
- bool focused = true;
- };
-
- // Return the config ID from calling getBestRefreshRate() for a single layer with the
- // given voteType and fps.
- auto getFrameRate = [&](LayerVoteType voteType, Fps fps, Args args = {}) -> DisplayModeId {
- layers[0].vote = voteType;
- layers[0].desiredRefreshRate = fps;
- layers[0].focused = args.focused;
- return configs.getBestRefreshRate(layers, {.touch = args.touch})->getId();
- };
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {30_Hz, 60_Hz}, {30_Hz, 90_Hz}}), 0);
-
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate()->getId());
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
- EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
- EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
-
- // Unfocused layers are not allowed to override primary config.
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false}));
- EXPECT_EQ(kModeId60,
- getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.focused = false}));
-
- // Touch boost should be restricted to the primary range.
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz, {.touch = true}));
-
- // When we're higher than the primary range max due to a layer frame rate setting, touch boost
- // shouldn't drag us back down to the primary range max.
- EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
- EXPECT_EQ(kModeId60,
- getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}, {60_Hz, 60_Hz}}), 0);
-
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
- EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
-}
-
-TEST_F(RefreshRateConfigsTest, idle) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].name = "Test layer";
-
- const auto getIdleFrameRate = [&](LayerVoteType voteType, bool touchActive) -> DisplayModeId {
- layers[0].vote = voteType;
- layers[0].desiredRefreshRate = 90_Hz;
-
- const auto [refreshRate, signals] =
- configs.getBestRefreshRateAndSignals(layers, {.touch = touchActive, .idle = true});
-
- // Refresh rate will be chosen by either touch state or idle state.
- EXPECT_EQ(!touchActive, signals.idle);
- return refreshRate->getId();
- };
-
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}, {60_Hz, 90_Hz}}), 0);
-
- // Idle should be lower priority than touch boost.
- {
- constexpr bool kTouchActive = true;
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
- EXPECT_EQ(kModeId90, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
- EXPECT_EQ(kModeId90,
- getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
- }
-
- // With no layers, idle should still be lower priority than touch boost.
- EXPECT_EQ(kModeId90, configs.getBestRefreshRate({}, {.touch = true, .idle = true})->getId());
-
- // Idle should be higher precedence than other layer frame rate considerations.
- configs.setActiveModeId(kModeId90);
-
- {
- constexpr bool kTouchActive = false;
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::NoVote, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Min, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Max, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::Heuristic, kTouchActive));
- EXPECT_EQ(kModeId60, getIdleFrameRate(LayerVoteType::ExplicitDefault, kTouchActive));
- EXPECT_EQ(kModeId60,
- getIdleFrameRate(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
- }
-
- // Idle should be applied rather than the current config when there are no layers.
- EXPECT_EQ(kModeId60, configs.getBestRefreshRate({}, {.idle = true})->getId());
-}
-
-TEST_F(RefreshRateConfigsTest, findClosestKnownFrameRate) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
- const auto knownFrameRate = configs.findClosestKnownFrameRate(Fps::fromValue(fps));
- const Fps expectedFrameRate = [fps] {
- if (fps < 26.91f) return 24_Hz;
- if (fps < 37.51f) return 30_Hz;
- if (fps < 52.51f) return 45_Hz;
- if (fps < 66.01f) return 60_Hz;
- if (fps < 81.01f) return 72_Hz;
- return 90_Hz;
- }();
-
- EXPECT_EQ(expectedFrameRate, knownFrameRate);
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_KnownFrameRate) {
- TestableRefreshRateConfigs configs(kModes_60_90, kModeId60);
-
- struct Expectation {
- Fps fps;
- DisplayModePtr mode;
- };
-
- const std::initializer_list<Expectation> knownFrameRatesExpectations = {
- {24_Hz, kMode60}, {30_Hz, kMode60}, {45_Hz, kMode90},
- {60_Hz, kMode60}, {72_Hz, kMode90}, {90_Hz, kMode90},
- };
-
- // Make sure the test tests all the known frame rate
- const auto& knownFrameRates = configs.knownFrameRates();
- const bool equal = std::equal(knownFrameRates.begin(), knownFrameRates.end(),
- knownFrameRatesExpectations.begin(),
- [](Fps fps, const Expectation& expected) {
- return isApproxEqual(fps, expected.fps);
- });
- EXPECT_TRUE(equal);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- auto& layer = layers[0];
- layer.vote = LayerVoteType::Heuristic;
-
- for (const auto& [fps, mode] : knownFrameRatesExpectations) {
- layer.desiredRefreshRate = fps;
- EXPECT_EQ(mode, configs.getBestRefreshRate(layers));
- }
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExact) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- auto& explicitExactLayer = layers[0];
- auto& explicitExactOrMultipleLayer = layers[1];
-
- explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
- explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
- explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
- explicitExactLayer.vote = LayerVoteType::ExplicitExact;
- explicitExactLayer.name = "ExplicitExact";
- explicitExactLayer.desiredRefreshRate = 30_Hz;
-
- EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode30, configs.getBestRefreshRate(layers, {.touch = true}));
-
- explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
- explicitExactLayer.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 72_Hz;
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 90_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 120_Hz;
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactEnableFrameRateOverride) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60,
- {.enableFrameRateOverride = true});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- auto& explicitExactLayer = layers[0];
- auto& explicitExactOrMultipleLayer = layers[1];
-
- explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
- explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
- explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
- explicitExactLayer.vote = LayerVoteType::ExplicitExact;
- explicitExactLayer.name = "ExplicitExact";
- explicitExactLayer.desiredRefreshRate = 30_Hz;
-
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true}));
-
- explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
- explicitExactLayer.desiredRefreshRate = 60_Hz;
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 72_Hz;
- EXPECT_EQ(kMode72, configs.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 90_Hz;
- EXPECT_EQ(kMode90, configs.getBestRefreshRate(layers));
-
- explicitExactLayer.desiredRefreshRate = 120_Hz;
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ReadsCache) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
- using GlobalSignals = RefreshRateConfigs::GlobalSignals;
- const auto args = std::make_pair(std::vector<LayerRequirement>{},
- GlobalSignals{.touch = true, .idle = true});
- const auto result = std::make_pair(kMode90, GlobalSignals{.touch = true});
-
- configs.mutableGetBestRefreshRateCache() = {args, result};
-
- EXPECT_EQ(result, configs.getBestRefreshRateAndSignals(args.first, args.second));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_WritesCache) {
- TestableRefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId60);
-
- EXPECT_FALSE(configs.mutableGetBestRefreshRateCache());
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- RefreshRateConfigs::GlobalSignals globalSignals{.touch = true, .idle = true};
-
- const auto result = configs.getBestRefreshRateAndSignals(layers, globalSignals);
-
- const auto& cache = configs.mutableGetBestRefreshRateCache();
- ASSERT_TRUE(cache);
-
- EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
- EXPECT_EQ(cache->result, result);
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_ExplicitExactTouchBoost) {
- TestableRefreshRateConfigs configs(kModes_60_120, kModeId60, {.enableFrameRateOverride = true});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
- auto& explicitExactLayer = layers[0];
- auto& explicitExactOrMultipleLayer = layers[1];
-
- explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
- explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
- explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
- explicitExactLayer.vote = LayerVoteType::ExplicitExact;
- explicitExactLayer.name = "ExplicitExact";
- explicitExactLayer.desiredRefreshRate = 30_Hz;
-
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode120, configs.getBestRefreshRate(layers, {.touch = true}));
-
- explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
-
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers, {.touch = true}));
-}
-
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_FractionalRefreshRates_ExactAndDefault) {
- TestableRefreshRateConfigs configs(kModes_24_25_30_50_60_Frac, kModeId60,
- {.enableFrameRateOverride = true});
-
- std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
- auto& explicitDefaultLayer = layers[0];
- auto& explicitExactOrMultipleLayer = layers[1];
-
- explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
- explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
- explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
-
- explicitDefaultLayer.vote = LayerVoteType::ExplicitDefault;
- explicitDefaultLayer.name = "ExplicitDefault";
- explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
-
- EXPECT_EQ(kMode60, configs.getBestRefreshRate(layers));
-}
-
-// b/190578904
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_withCloseRefreshRates) {
- constexpr int kMinRefreshRate = 10;
- constexpr int kMaxRefreshRate = 240;
-
- DisplayModes displayModes;
- for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
- const DisplayModeId modeId(fps);
- displayModes.try_emplace(modeId,
- createDisplayMode(modeId,
- Fps::fromValue(static_cast<float>(fps))));
- }
-
- const TestableRefreshRateConfigs configs(std::move(displayModes),
- DisplayModeId(kMinRefreshRate));
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
- layers[0].desiredRefreshRate = fps;
- layers[0].vote = vote;
- EXPECT_EQ(fps.getIntValue(), configs.getBestRefreshRate(layers)->getFps().getIntValue())
- << "Failed for " << ftl::enum_string(vote);
- };
-
- for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
- const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
- testRefreshRate(refreshRate, LayerVoteType::Heuristic);
- testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
- testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
- testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
- }
-}
-
-// b/190578904
-TEST_F(RefreshRateConfigsTest, getBestRefreshRate_conflictingVotes) {
- constexpr DisplayModeId kActiveModeId{0};
- DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz),
- createDisplayMode(DisplayModeId(1), 53_Hz),
- createDisplayMode(DisplayModeId(2), 55_Hz),
- createDisplayMode(DisplayModeId(3), 60_Hz));
-
- const RefreshRateConfigs::GlobalSignals globalSignals = {.touch = false, .idle = false};
- const TestableRefreshRateConfigs configs(std::move(displayModes), kActiveModeId);
-
- const std::vector<LayerRequirement> layers = {
- {
- .vote = LayerVoteType::ExplicitDefault,
- .desiredRefreshRate = 43_Hz,
- .seamlessness = Seamlessness::SeamedAndSeamless,
- .weight = 0.41f,
- },
- {
- .vote = LayerVoteType::ExplicitExactOrMultiple,
- .desiredRefreshRate = 53_Hz,
- .seamlessness = Seamlessness::SeamedAndSeamless,
- .weight = 0.41f,
- },
- };
-
- EXPECT_EQ(53_Hz, configs.getBestRefreshRate(layers, globalSignals)->getFps());
-}
-
-TEST_F(RefreshRateConfigsTest, modeComparison) {
- EXPECT_LT(kMode60->getFps(), kMode90->getFps());
- EXPECT_GE(kMode60->getFps(), kMode60->getFps());
- EXPECT_GE(kMode90->getFps(), kMode90->getFps());
-}
-
-TEST_F(RefreshRateConfigsTest, testKernelIdleTimerAction) {
- using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
-
- RefreshRateConfigs configs(kModes_60_90, kModeId90);
-
- // SetPolicy(60, 90), current 90Hz => TurnOn.
- EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
- // SetPolicy(60, 90), current 60Hz => TurnOn.
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
- // SetPolicy(60, 60), current 60Hz => TurnOff
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-
- // SetPolicy(90, 90), current 90Hz => TurnOff.
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-}
-
-TEST_F(RefreshRateConfigsTest, testKernelIdleTimerActionFor120Hz) {
- using KernelIdleTimerAction = RefreshRateConfigs::KernelIdleTimerAction;
-
- RefreshRateConfigs configs(kModes_60_120, kModeId120);
-
- // SetPolicy(0, 60), current 60Hz => TurnOn.
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
- // SetPolicy(60, 60), current 60Hz => TurnOff.
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-
- // SetPolicy(60, 120), current 60Hz => TurnOn.
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOn, configs.getIdleTimerAction());
-
- // SetPolicy(120, 120), current 120Hz => TurnOff.
- EXPECT_GE(configs.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}), 0);
- EXPECT_EQ(KernelIdleTimerAction::TurnOff, configs.getIdleTimerAction());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateDivisor) {
- RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId30);
-
- const auto frameRate = 30_Hz;
- Fps displayRefreshRate = configs.getActiveMode()->getFps();
- EXPECT_EQ(1, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
- configs.setActiveModeId(kModeId60);
- displayRefreshRate = configs.getActiveMode()->getFps();
- EXPECT_EQ(2, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
- configs.setActiveModeId(kModeId72);
- displayRefreshRate = configs.getActiveMode()->getFps();
- EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
- configs.setActiveModeId(kModeId90);
- displayRefreshRate = configs.getActiveMode()->getFps();
- EXPECT_EQ(3, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
- configs.setActiveModeId(kModeId120);
- displayRefreshRate = configs.getActiveMode()->getFps();
- EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, frameRate));
-
- configs.setActiveModeId(kModeId90);
- displayRefreshRate = configs.getActiveMode()->getFps();
- EXPECT_EQ(4, RefreshRateConfigs::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
-
- EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 25_Hz));
- EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(24_Hz, 23.976_Hz));
- EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(30_Hz, 29.97_Hz));
- EXPECT_EQ(0, RefreshRateConfigs::getFrameRateDivisor(60_Hz, 59.94_Hz));
-}
-
-TEST_F(RefreshRateConfigsTest, isFractionalPairOrMultiple) {
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
-
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 30_Hz));
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 29.97_Hz));
-
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 60_Hz));
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 59.94_Hz));
-
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 60_Hz));
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(60_Hz, 29.97_Hz));
-
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(59.94_Hz, 30_Hz));
- EXPECT_TRUE(RefreshRateConfigs::isFractionalPairOrMultiple(30_Hz, 59.94_Hz));
-
- const auto refreshRates = {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz};
- for (auto refreshRate : refreshRates) {
- EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(refreshRate, refreshRate));
- }
-
- EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(24_Hz, 25_Hz));
- EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(23.978_Hz, 25_Hz));
- EXPECT_FALSE(RefreshRateConfigs::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz));
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_noLayers) {
- RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120);
-
- EXPECT_TRUE(configs.getFrameRateOverrides({}, 120_Hz, {}).empty());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_60on120) {
- RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = true});
-
- std::vector<LayerRequirement> layers = {{.weight = 1.f}};
- layers[0].name = "Test layer";
- layers[0].ownerUid = 1234;
- layers[0].desiredRefreshRate = 60_Hz;
- layers[0].vote = LayerVoteType::ExplicitDefault;
-
- auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[0].vote = LayerVoteType::NoVote;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Min;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Max;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-
- layers[0].vote = LayerVoteType::Heuristic;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_twoUids) {
- RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = true});
-
- std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
- {.ownerUid = 5678, .weight = 1.f}};
-
- layers[0].name = "Test layer 1234";
- layers[0].desiredRefreshRate = 60_Hz;
- layers[0].vote = LayerVoteType::ExplicitDefault;
-
- layers[1].name = "Test layer 5678";
- layers[1].desiredRefreshRate = 30_Hz;
- layers[1].vote = LayerVoteType::ExplicitDefault;
- auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
-
- EXPECT_EQ(2u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
- ASSERT_EQ(1u, frameRateOverrides.count(5678));
- EXPECT_EQ(30_Hz, frameRateOverrides.at(5678));
-
- layers[1].vote = LayerVoteType::Heuristic;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[1].ownerUid = 1234;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-TEST_F(RefreshRateConfigsTest, getFrameRateOverrides_touch) {
- RefreshRateConfigs configs(kModes_30_60_72_90_120, kModeId120,
- {.enableFrameRateOverride = true});
-
- std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
- layers[0].name = "Test layer";
- layers[0].desiredRefreshRate = 60_Hz;
- layers[0].vote = LayerVoteType::ExplicitDefault;
-
- auto frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[0].vote = LayerVoteType::ExplicitExact;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {});
- EXPECT_EQ(1u, frameRateOverrides.size());
- ASSERT_EQ(1u, frameRateOverrides.count(1234));
- EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
-
- frameRateOverrides = configs.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
- EXPECT_TRUE(frameRateOverrides.empty());
-}
-
-} // namespace
-} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
new file mode 100644
index 0000000000..0cacf810c0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/RefreshRateSelectorTest.cpp
@@ -0,0 +1,3747 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SchedulerUnittests"
+
+#include <algorithm>
+#include <array>
+
+#include <ftl/enum.h>
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <log/log.h>
+#include <ui/Size.h>
+
+#include <common/test/FlagUtils.h>
+#include <scheduler/Fps.h>
+#include <scheduler/FrameRateMode.h>
+#include "DisplayHardware/HWC2.h"
+#include "FpsOps.h"
+#include "Scheduler/RefreshRateSelector.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockFrameRateMode.h"
+
+#include "libsurfaceflinger_unittest_main.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+
+using namespace com::android::graphics::surfaceflinger;
+using namespace std::chrono_literals;
+
+namespace android::scheduler {
+
+namespace hal = android::hardware::graphics::composer::hal;
+
+using Config = RefreshRateSelector::Config;
+using LayerRequirement = RefreshRateSelector::LayerRequirement;
+using LayerVoteType = RefreshRateSelector::LayerVoteType;
+using SetPolicyResult = RefreshRateSelector::SetPolicyResult;
+
+using mock::createDisplayMode;
+using mock::createVrrDisplayMode;
+
+struct TestableRefreshRateSelector : RefreshRateSelector {
+ using RefreshRateSelector::FrameRateRanking;
+ using RefreshRateSelector::RefreshRateOrder;
+
+ using RefreshRateSelector::RefreshRateSelector;
+
+ void setActiveMode(DisplayModeId modeId, Fps renderFrameRate) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ return RefreshRateSelector::setActiveMode(modeId, renderFrameRate);
+ }
+
+ const DisplayMode& getActiveMode() const {
+ std::lock_guard lock(mLock);
+ return *RefreshRateSelector::getActiveModeLocked().modePtr;
+ }
+
+ ftl::NonNull<DisplayModePtr> getMinSupportedRefreshRate() const {
+ std::lock_guard lock(mLock);
+ return ftl::as_non_null(mMinRefreshRateModeIt->second);
+ }
+
+ ftl::NonNull<DisplayModePtr> getMaxSupportedRefreshRate() const {
+ std::lock_guard lock(mLock);
+ return ftl::as_non_null(mMaxRefreshRateModeIt->second);
+ }
+
+ ftl::NonNull<DisplayModePtr> getMinRefreshRateByPolicy() const {
+ std::lock_guard lock(mLock);
+ return ftl::as_non_null(getMinRefreshRateByPolicyLocked());
+ }
+
+ ftl::NonNull<DisplayModePtr> getMaxRefreshRateByPolicy() const {
+ std::lock_guard lock(mLock);
+ return ftl::as_non_null(
+ getMaxRefreshRateByPolicyLocked(getActiveModeLocked().modePtr->getGroup()));
+ }
+
+ FrameRateRanking rankRefreshRates(std::optional<int> anchorGroupOpt,
+ RefreshRateOrder refreshRateOrder) const {
+ std::lock_guard lock(mLock);
+ return RefreshRateSelector::rankFrameRates(anchorGroupOpt, refreshRateOrder);
+ }
+
+ const std::vector<Fps>& knownFrameRates() const { return mKnownFrameRates; }
+
+ using RefreshRateSelector::GetRankedFrameRatesCache;
+ auto& mutableGetRankedRefreshRatesCache() { return mGetRankedFrameRatesCache; }
+
+ auto getRankedFrameRates(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const {
+ const auto result = RefreshRateSelector::getRankedFrameRates(layers, signals);
+
+ EXPECT_TRUE(std::is_sorted(result.ranking.begin(), result.ranking.end(),
+ ScoredFrameRate::DescendingScore{}));
+
+ return result;
+ }
+
+ auto getRankedRefreshRatesAsPair(const std::vector<LayerRequirement>& layers,
+ GlobalSignals signals) const {
+ const auto [ranking, consideredSignals] = getRankedFrameRates(layers, signals);
+ return std::make_pair(ranking, consideredSignals);
+ }
+
+ FrameRateMode getBestFrameRateMode(const std::vector<LayerRequirement>& layers = {},
+ GlobalSignals signals = {}) const {
+ return getRankedFrameRates(layers, signals).ranking.front().frameRateMode;
+ }
+
+ ScoredFrameRate getBestScoredFrameRate(const std::vector<LayerRequirement>& layers = {},
+ GlobalSignals signals = {}) const {
+ return getRankedFrameRates(layers, signals).ranking.front();
+ }
+
+ SetPolicyResult setPolicy(const PolicyVariant& policy) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ return RefreshRateSelector::setPolicy(policy);
+ }
+
+ SetPolicyResult setDisplayManagerPolicy(const DisplayManagerPolicy& policy) {
+ return setPolicy(policy);
+ }
+
+ const auto& getPrimaryFrameRates() const { return mPrimaryFrameRates; }
+};
+
+class RefreshRateSelectorTest : public testing::TestWithParam<Config::FrameRateOverride> {
+protected:
+ using RefreshRateOrder = TestableRefreshRateSelector::RefreshRateOrder;
+
+ RefreshRateSelectorTest();
+ ~RefreshRateSelectorTest();
+
+ static constexpr DisplayModeId kModeId60{0};
+ static constexpr DisplayModeId kModeId90{1};
+ static constexpr DisplayModeId kModeId72{2};
+ static constexpr DisplayModeId kModeId120{3};
+ static constexpr DisplayModeId kModeId30{4};
+ static constexpr DisplayModeId kModeId25{5};
+ static constexpr DisplayModeId kModeId50{6};
+ static constexpr DisplayModeId kModeId24{7};
+ static constexpr DisplayModeId kModeId24Frac{8};
+ static constexpr DisplayModeId kModeId30Frac{9};
+ static constexpr DisplayModeId kModeId60Frac{10};
+ static constexpr DisplayModeId kModeId35{11};
+ static constexpr DisplayModeId kModeId1{12};
+ static constexpr DisplayModeId kModeId5{13};
+ static constexpr DisplayModeId kModeId10{14};
+
+ static inline const ftl::NonNull<DisplayModePtr> kMode60 =
+ ftl::as_non_null(createDisplayMode(kModeId60, 60_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode60Frac =
+ ftl::as_non_null(createDisplayMode(kModeId60Frac, 59.94_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode90 =
+ ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode90_G1 =
+ ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz, 1));
+ static inline const ftl::NonNull<DisplayModePtr> kMode90_4K =
+ ftl::as_non_null(createDisplayMode(kModeId90, 90_Hz, 0, {3840, 2160}));
+ static inline const ftl::NonNull<DisplayModePtr> kMode72 =
+ ftl::as_non_null(createDisplayMode(kModeId72, 72_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode72_G1 =
+ ftl::as_non_null(createDisplayMode(kModeId72, 72_Hz, 1));
+ static inline const ftl::NonNull<DisplayModePtr> kMode120 =
+ ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode120_G1 =
+ ftl::as_non_null(createDisplayMode(kModeId120, 120_Hz, 1));
+ static inline const ftl::NonNull<DisplayModePtr> kMode30 =
+ ftl::as_non_null(createDisplayMode(kModeId30, 30_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode30_G1 =
+ ftl::as_non_null(createDisplayMode(kModeId30, 30_Hz, 1));
+ static inline const ftl::NonNull<DisplayModePtr> kMode30Frac =
+ ftl::as_non_null(createDisplayMode(kModeId30Frac, 29.97_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode25 =
+ ftl::as_non_null(createDisplayMode(kModeId25, 25_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode25_G1 =
+ ftl::as_non_null(createDisplayMode(kModeId25, 25_Hz, 1));
+ static inline const ftl::NonNull<DisplayModePtr> kMode35 =
+ ftl::as_non_null(createDisplayMode(kModeId35, 35_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode50 =
+ ftl::as_non_null(createDisplayMode(kModeId50, 50_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode24 =
+ ftl::as_non_null(createDisplayMode(kModeId24, 24_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode24Frac =
+ ftl::as_non_null(createDisplayMode(kModeId24Frac, 23.976_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode1 =
+ ftl::as_non_null(createDisplayMode(kModeId1, 1_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode5 =
+ ftl::as_non_null(createDisplayMode(kModeId5, 5_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kMode10 =
+ ftl::as_non_null(createDisplayMode(kModeId10, 10_Hz));
+
+ // VRR modes
+ static inline const ftl::NonNull<DisplayModePtr> kVrrMode120TE240 = ftl::as_non_null(
+ createVrrDisplayMode(kModeId120, 240_Hz,
+ hal::VrrConfig{
+ .minFrameIntervalNs =
+ static_cast<Fps>(120_Hz).getPeriodNsecs()}));
+
+ static inline const ftl::NonNull<DisplayModePtr> kVrrMode60TE120 = ftl::as_non_null(
+ createVrrDisplayMode(kModeId60, 120_Hz,
+ hal::VrrConfig{.minFrameIntervalNs =
+ static_cast<Fps>(60_Hz).getPeriodNsecs()},
+ /*group=*/1));
+
+ // Test configurations.
+ static inline const DisplayModes kModes_60 = makeModes(kMode60);
+ static inline const DisplayModes kModes_35_60_90 = makeModes(kMode35, kMode60, kMode90);
+ static inline const DisplayModes kModes_60_90 = makeModes(kMode60, kMode90);
+ static inline const DisplayModes kModes_60_90_G1 = makeModes(kMode60, kMode90_G1);
+ static inline const DisplayModes kModes_60_90_4K = makeModes(kMode60, kMode90_4K);
+ static inline const DisplayModes kModes_60_72_90 = makeModes(kMode60, kMode90, kMode72);
+ static inline const DisplayModes kModes_60_90_72_120 =
+ makeModes(kMode60, kMode90, kMode72, kMode120);
+ static inline const DisplayModes kModes_30_60_72_90_120 =
+ makeModes(kMode60, kMode90, kMode72, kMode120, kMode30);
+
+ static inline const DisplayModes kModes_30_60 =
+ makeModes(kMode60, kMode90_G1, kMode72_G1, kMode120_G1, kMode30);
+ static inline const DisplayModes kModes_30_60_72_90 =
+ makeModes(kMode60, kMode90, kMode72, kMode120_G1, kMode30);
+ static inline const DisplayModes kModes_30_60_90 =
+ makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30);
+ static inline const DisplayModes kModes_25_30_50_60 =
+ makeModes(kMode60, kMode90, kMode72_G1, kMode120_G1, kMode30_G1, kMode25_G1, kMode50);
+ static inline const DisplayModes kModes_60_120 = makeModes(kMode60, kMode120);
+ static inline const DisplayModes kModes_1_5_10 = makeModes(kMode1, kMode5, kMode10);
+ static inline const DisplayModes kModes_60_90_120 = makeModes(kMode60, kMode90, kMode120);
+
+ // VRR display modes
+ static inline const DisplayModes kVrrMode_120 = makeModes(kVrrMode120TE240);
+ static inline const DisplayModes kVrrModes_60_120 =
+ makeModes(kVrrMode60TE120, kVrrMode120TE240);
+
+ // This is a typical TV configuration.
+ static inline const DisplayModes kModes_24_25_30_50_60_Frac =
+ makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode30Frac, kMode50, kMode60,
+ kMode60Frac);
+
+ static TestableRefreshRateSelector createSelector(DisplayModes modes,
+ DisplayModeId activeModeId,
+ Config config = {}) {
+ config.enableFrameRateOverride = GetParam();
+ return TestableRefreshRateSelector(modes, activeModeId, config);
+ }
+};
+
+RefreshRateSelectorTest::RefreshRateSelectorTest() {
+ 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());
+}
+
+RefreshRateSelectorTest::~RefreshRateSelectorTest() {
+ 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());
+}
+
+namespace {
+
+INSTANTIATE_TEST_SUITE_P(PerOverrideConfig, RefreshRateSelectorTest,
+ testing::Values(Config::FrameRateOverride::Disabled,
+ Config::FrameRateOverride::AppOverrideNativeRefreshRates,
+ Config::FrameRateOverride::AppOverride,
+ Config::FrameRateOverride::Enabled));
+
+TEST_P(RefreshRateSelectorTest, oneMode_canSwitch) {
+ auto selector = createSelector(kModes_60, kModeId60);
+ if (GetParam() == Config::FrameRateOverride::Enabled) {
+ EXPECT_TRUE(selector.canSwitch());
+ } else {
+ EXPECT_FALSE(selector.canSwitch());
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, invalidPolicy) {
+ auto selector = createSelector(kModes_60, kModeId60);
+
+ EXPECT_EQ(SetPolicyResult::Invalid,
+ selector.setDisplayManagerPolicy({DisplayModeId(10), {60_Hz, 60_Hz}}));
+ EXPECT_EQ(SetPolicyResult::Invalid,
+ selector.setDisplayManagerPolicy({kModeId60, {20_Hz, 40_Hz}}));
+}
+
+TEST_P(RefreshRateSelectorTest, unchangedPolicy) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+
+ EXPECT_EQ(SetPolicyResult::Unchanged,
+ selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+
+ // Override to the same policy.
+ EXPECT_EQ(SetPolicyResult::Unchanged,
+ selector.setPolicy(RefreshRateSelector::OverridePolicy{kModeId90, {60_Hz, 90_Hz}}));
+
+ // Clear override to restore DisplayManagerPolicy.
+ EXPECT_EQ(SetPolicyResult::Unchanged,
+ selector.setPolicy(RefreshRateSelector::NoOverridePolicy{}));
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {30_Hz, 90_Hz}}));
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ const auto minRate = selector.getMinSupportedRefreshRate();
+ const auto performanceRate = selector.getMaxSupportedRefreshRate();
+
+ EXPECT_EQ(kMode60, minRate);
+ EXPECT_EQ(kMode90, performanceRate);
+
+ const auto minRateByPolicy = selector.getMinRefreshRateByPolicy();
+ const auto performanceRateByPolicy = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(minRateByPolicy, minRate);
+ EXPECT_EQ(performanceRateByPolicy, performanceRate);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentGroups) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ const auto minRate = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate = selector.getMaxSupportedRefreshRate();
+ const auto minRate60 = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate60 = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kMode60, minRate);
+ EXPECT_EQ(kMode60, minRate60);
+ EXPECT_EQ(kMode60, performanceRate60);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ const auto minRate90 = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate90 = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kMode90_G1, performanceRate);
+ EXPECT_EQ(kMode90_G1, minRate90);
+ EXPECT_EQ(kMode90_G1, performanceRate90);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_storesFullRefreshRateMap_differentResolutions) {
+ auto selector = createSelector(kModes_60_90_4K, kModeId60);
+
+ const auto minRate = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate = selector.getMaxSupportedRefreshRate();
+ const auto minRate60 = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate60 = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kMode60, minRate);
+ EXPECT_EQ(kMode60, minRate60);
+ EXPECT_EQ(kMode60, performanceRate60);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {60_Hz, 90_Hz}}));
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ const auto minRate90 = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate90 = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kMode90_4K, performanceRate);
+ EXPECT_EQ(kMode90_4K, minRate90);
+ EXPECT_EQ(kMode90_4K, performanceRate90);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_policyChange) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ const auto minRate = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kMode60, minRate);
+ EXPECT_EQ(kMode90, performanceRate);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+
+ const auto minRate60 = selector.getMinRefreshRateByPolicy();
+ const auto performanceRate60 = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kMode60, minRate60);
+ EXPECT_EQ(kMode60, performanceRate60);
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_getActiveMode) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+ {
+ const auto& mode = selector.getActiveMode();
+ EXPECT_EQ(mode.getId(), kModeId60);
+ }
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+ {
+ const auto& mode = selector.getActiveMode();
+ EXPECT_EQ(mode.getId(), kModeId90);
+ }
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
+ {
+ const auto& mode = selector.getActiveMode();
+ EXPECT_EQ(mode.getId(), kModeId90);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_noLayers) {
+ {
+ auto selector = createSelector(kModes_60_72_90, kModeId72);
+
+ // If there are no layers we select the default frame rate, which is the max of the primary
+ // range.
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode().modePtr);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode().modePtr);
+ }
+ {
+ // We select max even when this will cause a non-seamless switch.
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+ constexpr bool kAllowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId90, {0_Hz, 90_Hz}, kAllowGroupSwitching}));
+ EXPECT_EQ(kMode90_G1, selector.getBestFrameRateMode().modePtr);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_exactDontChangeRefreshRateWhenNotInPolicy) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId72);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].vote = LayerVoteType::ExplicitExact;
+ layers[0].desiredRefreshRate = 120_Hz;
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId72, {0_Hz, 90_Hz}}));
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::Min;
+ lr.name = "Min";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ lr.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ lr.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ lr.name = "45Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ lr.name = "30Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ lr.name = "24Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.name = "";
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+
+ lr.vote = LayerVoteType::Min;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
+
+ lr.vote = LayerVoteType::Min;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 120_Hz}}));
+ lr.vote = LayerVoteType::Min;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_multipleThreshold_60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId60, {.frameRateMultipleThreshold = 90});
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::Min;
+ lr.name = "Min";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ lr.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ lr.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ lr.name = "45Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ lr.name = "30Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ lr.name = "24Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_60_72_90) {
+ auto selector = createSelector(kModes_60_72_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::Min;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90_120) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 48_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 48_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_90_120_DifferentTypes) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.name = "24Hz ExplicitDefault";
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "60Hz ExplicitDefault";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.name = "24Hz ExplicitDefault";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.name = "24Hz Heuristic";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "90Hz ExplicitDefault";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "90Hz ExplicitDefault";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.name = "24Hz ExplicitDefault";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.name = "90Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_30_60_90_120_DifferentTypes_multipleThreshold) {
+ auto selector =
+ createSelector(kModes_30_60_72_90_120, kModeId60, {.frameRateMultipleThreshold = 120});
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+ auto& lr3 = layers[2];
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.name = "24Hz ExplicitDefault";
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "60Hz ExplicitDefault";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.name = "24Hz ExplicitDefault";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.name = "24Hz Heuristic";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "90Hz ExplicitDefault";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "90Hz ExplicitDefault";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.name = "24Hz ExplicitDefault";
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.name = "90Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 120_Hz;
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.name = "120Hz ExplicitDefault";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 24_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "24Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 120_Hz;
+ lr2.vote = LayerVoteType::ExplicitExact;
+ lr2.name = "120Hz ExplicitExact";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 10_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "30Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 120_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.name = "120Hz ExplicitExact";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.desiredRefreshRate = 30_Hz;
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.name = "30Hz ExplicitExactOrMultiple";
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.name = "30Hz ExplicitExactOrMultiple";
+ lr3.vote = LayerVoteType::Heuristic;
+ lr3.desiredRefreshRate = 120_Hz;
+ lr3.name = "120Hz Heuristic";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60) {
+ auto selector = createSelector(kModes_30_60, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::Min;
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_30_60_72_90) {
+ auto selector = createSelector(kModes_30_60_72_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::Min;
+ lr.name = "Min";
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ lr.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 90_Hz;
+ lr.vote = LayerVoteType::Heuristic;
+ lr.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz Heuristic";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+
+ lr.desiredRefreshRate = 45_Hz;
+ lr.name = "45Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+
+ lr.desiredRefreshRate = 30_Hz;
+ lr.name = "30Hz Heuristic";
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ lr.name = "24Hz Heuristic";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+
+ lr.desiredRefreshRate = 24_Hz;
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr.name = "24Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode72, selector.getBestFrameRateMode(layers).modePtr);
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_PriorityTest) {
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::Min;
+ lr2.vote = LayerVoteType::Max;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Min;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Min;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 24_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Max;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Max;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.desiredRefreshRate = 15_Hz;
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.desiredRefreshRate = 30_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 45_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
+ lr.desiredRefreshRate = Fps::fromValue(fps);
+ const auto mode = selector.getBestFrameRateMode(layers).modePtr;
+ EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
+ << to_string(mode->getPeakFps()) << "("
+ << to_string(mode->getVsyncRate()) << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_24FpsVideo_multipleThreshold_60_120) {
+ auto selector = createSelector(kModes_60_120, kModeId60, {.frameRateMultipleThreshold = 120});
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ for (float fps = 23.0f; fps < 25.0f; fps += 0.1f) {
+ lr.desiredRefreshRate = Fps::fromValue(fps);
+ const auto mode = selector.getBestFrameRateMode(layers).modePtr;
+ EXPECT_EQ(kMode60, mode) << lr.desiredRefreshRate << " chooses "
+ << to_string(mode->getPeakFps()) << "("
+ << to_string(mode->getVsyncRate()) << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, twoModes_getBestFrameRateMode_Explicit) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 90_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.desiredRefreshRate = 90_Hz;
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 60_Hz;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_75HzContent) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ for (float fps = 75.0f; fps < 100.0f; fps += 0.1f) {
+ lr.desiredRefreshRate = Fps::fromValue(fps);
+ const auto mode = selector.getBestFrameRateMode(layers, {}).modePtr;
+ EXPECT_EQ(kMode90, mode) << lr.desiredRefreshRate << " chooses "
+ << to_string(mode->getPeakFps()) << "("
+ << to_string(mode->getVsyncRate()) << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_Multiples) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.name = "90Hz ExplicitDefault";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 30_Hz;
+ lr1.name = "30Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 30_Hz;
+ lr1.name = "30Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, scrollWhileWatching60fps_60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::NoVote;
+ lr2.name = "NoVote";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::NoVote;
+ lr2.name = "NoVote";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Max;
+ lr2.name = "Max";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ // The other layer starts to provide buffers
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.name = "90Hz Heuristic";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitGte) {
+ auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitGte";
+ lr2.vote = LayerVoteType::NoVote;
+ lr2.name = "NoVote";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 25_Hz;
+ lr1.name = "25Hz ExplicitGte";
+ lr2.vote = LayerVoteType::NoVote;
+ lr2.name = "NoVote";
+ EXPECT_EQ(kMode30, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 91_Hz;
+ lr1.name = "91Hz ExplicitGte";
+ lr2.vote = LayerVoteType::NoVote;
+ lr2.name = "NoVote";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitGte";
+ lr2.vote = LayerVoteType::ExplicitDefault;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz ExplicitDefault";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitGte";
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 30_Hz;
+ lr2.name = "30Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitGte";
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitGte";
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 90_Hz;
+ lr2.name = "90Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr1.vote = LayerVoteType::ExplicitGte;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitGte";
+ lr2.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr2.desiredRefreshRate = 120_Hz;
+ lr2.name = "120Hz ExplicitExactOrMultiple";
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicy) {
+ // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+ // different group.
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
+ const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
+ RefreshRateOrder::Descending);
+
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+ }
+ }();
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicy) {
+ // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+ // different group.
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
+
+ const auto refreshRates = selector.rankRefreshRates(selector.getActiveMode().getGroup(),
+ RefreshRateOrder::Ascending);
+
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ }
+ }();
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getMinRefreshRatesByPolicyOutsideTheGroup) {
+ // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+ // different group.
+ auto selector = createSelector(kModes_30_60_90, kModeId72);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
+
+ const auto refreshRates =
+ selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt, RefreshRateOrder::Ascending);
+
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}};
+ }
+ }();
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getMaxRefreshRatesByPolicyOutsideTheGroup) {
+ // The kModes_30_60_90 contains two kMode72_G1, kMode120_G1 which are from the
+ // different group.
+ auto selector = createSelector(kModes_30_60_90, kModeId72);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {30_Hz, 90_Hz}}));
+
+ const auto refreshRates = selector.rankRefreshRates(/*anchorGroupOpt*/ std::nullopt,
+ RefreshRateOrder::Descending);
+
+ const auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90}, {30_Hz, kMode30}};
+ }
+ }();
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, powerOnImminentConsidered) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ auto [refreshRates, signals] = selector.getRankedFrameRates({}, {});
+ EXPECT_FALSE(signals.powerOnImminent);
+
+ auto expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {45_Hz, kMode90},
+ {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+ }
+ }();
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+
+ std::tie(refreshRates, signals) =
+ selector.getRankedRefreshRatesAsPair({}, {.powerOnImminent = true});
+ EXPECT_TRUE(signals.powerOnImminent);
+
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr1 = layers[0];
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+
+ std::tie(refreshRates, signals) =
+ selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = true});
+ EXPECT_TRUE(signals.powerOnImminent);
+
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+
+ std::tie(refreshRates, signals) =
+ selector.getRankedRefreshRatesAsPair(layers, {.powerOnImminent = false});
+ EXPECT_FALSE(signals.powerOnImminent);
+
+ expectedRefreshRates = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{60_Hz, kMode60}, {90_Hz, kMode90}};
+ case Config::FrameRateOverride::Enabled:
+ return {{60_Hz, kMode60}, {90_Hz, kMode90}, {45_Hz, kMode90},
+ {30_Hz, kMode60}, {22.5_Hz, kMode90}, {20_Hz, kMode60}};
+ }
+ }();
+ ASSERT_EQ(expectedRefreshRates.size(), refreshRates.size());
+
+ for (size_t i = 0; i < expectedRefreshRates.size(); ++i) {
+ EXPECT_EQ(expectedRefreshRates[i], refreshRates[i].frameRateMode)
+ << "Expected " << expectedRefreshRates[i].fps.getIntValue() << " ("
+ << expectedRefreshRates[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << refreshRates[i].frameRateMode.fps.getIntValue() << " ("
+ << refreshRates[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, touchConsidered) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ auto [_, signals] = selector.getRankedFrameRates({}, {});
+ EXPECT_FALSE(signals.touch);
+
+ std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair({}, {.touch = true});
+ EXPECT_TRUE(signals.touch);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz Heuristic";
+ std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+ EXPECT_TRUE(signals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitDefault";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz Heuristic";
+ std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+ EXPECT_FALSE(signals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitExactOrMultiple";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz Heuristic";
+ std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+ EXPECT_TRUE(signals.touch);
+
+ lr1.vote = LayerVoteType::ExplicitDefault;
+ lr1.desiredRefreshRate = 60_Hz;
+ lr1.name = "60Hz ExplicitDefault";
+ lr2.vote = LayerVoteType::Heuristic;
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz Heuristic";
+ std::tie(std::ignore, signals) = selector.getRankedRefreshRatesAsPair(layers, {.touch = true});
+ EXPECT_FALSE(signals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_30_60_90_120) {
+ auto selector = createSelector(makeModes(kMode30, kMode60, kMode90, kMode120), kModeId60);
+
+ struct Case {
+ // Params
+ Fps desiredFrameRate = 0_Hz;
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ };
+
+ // Prepare a table with the vote and the expected refresh rate
+ const std::initializer_list<Case> testCases = {
+ // Cases that only have frame rate category requirements, but no desired frame rate.
+ // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+ {0_Hz, FrameRateCategory::High, 90_Hz},
+ {0_Hz, FrameRateCategory::Normal, 60_Hz},
+ {0_Hz, FrameRateCategory::Low, 30_Hz},
+ {0_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+ // Cases that have both desired frame rate and frame rate category requirements.
+ {24_Hz, FrameRateCategory::High, 120_Hz},
+ {30_Hz, FrameRateCategory::High, 90_Hz},
+ {12_Hz, FrameRateCategory::Normal, 60_Hz},
+ {30_Hz, FrameRateCategory::NoPreference, 30_Hz},
+
+ // Cases that only have desired frame rate.
+ {30_Hz, FrameRateCategory::Default, 30_Hz},
+ };
+
+ for (auto testCase : testCases) {
+ std::vector<LayerRequirement> layers;
+ ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+ to_string(testCase.desiredFrameRate).c_str(),
+ ftl::enum_string(testCase.frameRateCategory).c_str());
+
+ if (testCase.desiredFrameRate.isValid()) {
+ std::stringstream ss;
+ ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitDefault,
+ .desiredRefreshRate = testCase.desiredFrameRate,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ if (testCase.frameRateCategory != FrameRateCategory::Default) {
+ std::stringstream ss;
+ ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitCategory,
+ .frameRateCategory = testCase.frameRateCategory,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ EXPECT_EQ(testCase.expectedFrameRate,
+ selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+ << "Did not get expected frame rate for frameRate="
+ << to_string(testCase.desiredFrameRate)
+ << " category=" << ftl::enum_string(testCase.frameRateCategory);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withFrameRateCategory_60_120) {
+ auto selector = createSelector(makeModes(kMode60, kMode120), kModeId60);
+
+ struct Case {
+ // Params
+ Fps desiredFrameRate = 0_Hz;
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ };
+
+ // Prepare a table with the vote and the expected refresh rate
+ const std::initializer_list<Case> testCases = {
+ // Cases that only have frame rate category requirements, but no desired frame rate.
+ // When frame rates get an equal score, the lower is chosen, unless there are Max votes.
+ {0_Hz, FrameRateCategory::High, 120_Hz},
+ {0_Hz, FrameRateCategory::Normal, 60_Hz},
+ {0_Hz, FrameRateCategory::Low, 60_Hz},
+ {0_Hz, FrameRateCategory::NoPreference, 60_Hz},
+
+ // Cases that have both desired frame rate and frame rate category requirements.
+ {24_Hz, FrameRateCategory::High, 120_Hz},
+ {30_Hz, FrameRateCategory::High, 120_Hz},
+ {12_Hz, FrameRateCategory::Normal, 60_Hz},
+ {30_Hz, FrameRateCategory::NoPreference, 60_Hz},
+
+ // Cases that only have desired frame rate.
+ {30_Hz, FrameRateCategory::Default, 60_Hz},
+ };
+
+ for (auto testCase : testCases) {
+ std::vector<LayerRequirement> layers;
+ ALOGI("**** %s: Testing desiredFrameRate=%s, frameRateCategory=%s", __func__,
+ to_string(testCase.desiredFrameRate).c_str(),
+ ftl::enum_string(testCase.frameRateCategory).c_str());
+
+ if (testCase.desiredFrameRate.isValid()) {
+ std::stringstream ss;
+ ss << to_string(testCase.desiredFrameRate) << "ExplicitDefault";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitDefault,
+ .desiredRefreshRate = testCase.desiredFrameRate,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ if (testCase.frameRateCategory != FrameRateCategory::Default) {
+ std::stringstream ss;
+ ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory) << ")";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitCategory,
+ .frameRateCategory = testCase.frameRateCategory,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ EXPECT_EQ(testCase.expectedFrameRate,
+ selector.getBestFrameRateMode(layers).modePtr->getPeakFps())
+ << "Did not get expected frame rate for frameRate="
+ << to_string(testCase.desiredFrameRate)
+ << " category=" << ftl::enum_string(testCase.frameRateCategory);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_nonVrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, false);
+ // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
+ auto selector = createSelector(makeModes(kMode60, kMode120), kModeId120);
+
+ struct Case {
+ // Params
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+ bool smoothSwitchOnly = false;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ DisplayModeId expectedModeId = kModeId60;
+ };
+
+ const std::initializer_list<Case> testCases = {
+ // These layers may switch modes because smoothSwitchOnly=false.
+ {FrameRateCategory::Default, false, 120_Hz, kModeId120},
+ // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+ {FrameRateCategory::NoPreference, false, 60_Hz, kModeId60},
+ {FrameRateCategory::Low, false, 30_Hz, kModeId60},
+ {FrameRateCategory::Normal, false, 60_Hz, kModeId60},
+ {FrameRateCategory::High, false, 120_Hz, kModeId120},
+
+ // These layers cannot change mode due to smoothSwitchOnly, and will definitely use
+ // active mode (120Hz).
+ {FrameRateCategory::NoPreference, true, 120_Hz, kModeId120},
+ {FrameRateCategory::Low, true, 120_Hz, kModeId120},
+ {FrameRateCategory::Normal, true, 40_Hz, kModeId120},
+ {FrameRateCategory::High, true, 120_Hz, kModeId120},
+ };
+
+ for (auto testCase : testCases) {
+ std::vector<LayerRequirement> layers;
+ ALOGI("**** %s: Testing frameRateCategory=%s (smooth=%d)", __func__,
+ ftl::enum_string(testCase.frameRateCategory).c_str(), testCase.smoothSwitchOnly);
+
+ if (testCase.frameRateCategory != FrameRateCategory::Default) {
+ std::stringstream ss;
+ ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory)
+ << " smooth:" << testCase.smoothSwitchOnly << ")";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitCategory,
+ .frameRateCategory = testCase.frameRateCategory,
+ .frameRateCategorySmoothSwitchOnly =
+ testCase.smoothSwitchOnly,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+ EXPECT_EQ(testCase.expectedFrameRate, actualFrameRateMode.fps)
+ << "Did not get expected frame rate for category="
+ << ftl::enum_string(testCase.frameRateCategory)
+ << " (smooth=" << testCase.smoothSwitchOnly << ")";
+
+ EXPECT_EQ(testCase.expectedModeId, actualFrameRateMode.modePtr->getId())
+ << "Did not get expected mode for category="
+ << ftl::enum_string(testCase.frameRateCategory)
+ << " (smooth=" << testCase.smoothSwitchOnly << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withFrameRateCategory_smoothSwitchOnly_60_120_vrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ // VRR compatibility is determined by the presence of a vrr config in the DisplayMode.
+ auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+ struct Case {
+ // Params
+ FrameRateCategory frameRateCategory = FrameRateCategory::Default;
+ bool smoothSwitchOnly = false;
+
+ // Expected result
+ Fps expectedFrameRate = 0_Hz;
+ };
+
+ // Note that `smoothSwitchOnly` should not have an effect.
+ const std::initializer_list<Case> testCases = {
+ {FrameRateCategory::Default, false, 120_Hz},
+ // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+ {FrameRateCategory::NoPreference, false, 120_Hz},
+ {FrameRateCategory::Low, false, 30_Hz},
+ {FrameRateCategory::Normal, false, 60_Hz},
+ {FrameRateCategory::High, false, 120_Hz},
+ {FrameRateCategory::Default, true, 120_Hz},
+ // TODO(b/266481656): Once this bug is fixed, NoPreference should be a lower frame rate.
+ {FrameRateCategory::NoPreference, true, 120_Hz},
+ {FrameRateCategory::Low, true, 30_Hz},
+ {FrameRateCategory::Normal, true, 60_Hz},
+ {FrameRateCategory::High, true, 120_Hz},
+ };
+
+ for (auto testCase : testCases) {
+ std::vector<LayerRequirement> layers;
+ ALOGI("**** %s: Testing frameRateCategory=%s (smooth=%d)", __func__,
+ ftl::enum_string(testCase.frameRateCategory).c_str(), testCase.smoothSwitchOnly);
+
+ if (testCase.frameRateCategory != FrameRateCategory::Default) {
+ std::stringstream ss;
+ ss << "ExplicitCategory (" << ftl::enum_string(testCase.frameRateCategory)
+ << " smooth:" << testCase.smoothSwitchOnly << ")";
+ LayerRequirement layer = {.name = ss.str(),
+ .vote = LayerVoteType::ExplicitCategory,
+ .frameRateCategory = testCase.frameRateCategory,
+ .frameRateCategorySmoothSwitchOnly =
+ testCase.smoothSwitchOnly,
+ .weight = 1.f};
+ layers.push_back(layer);
+ }
+
+ auto actualFrameRateMode = selector.getBestFrameRateMode(layers);
+ EXPECT_EQ(testCase.expectedFrameRate, actualFrameRateMode.fps)
+ << "Did not get expected frame rate for category="
+ << ftl::enum_string(testCase.frameRateCategory)
+ << " (smooth=" << testCase.smoothSwitchOnly << ")";
+
+ // Expect all cases to be able to stay at the mode with TE 240 due to VRR compatibility.
+ EXPECT_EQ(kVrrMode120TE240->getId(), actualFrameRateMode.modePtr->getId())
+ << "Did not get expected mode for category="
+ << ftl::enum_string(testCase.frameRateCategory)
+ << " (smooth=" << testCase.smoothSwitchOnly << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitDefault) {
+ auto selector = createSelector(kModes_60_90_72_120, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ // Prepare a table with the vote and the expected refresh rate
+ const std::initializer_list<std::pair<Fps, Fps>> testCases = {
+ {130_Hz, 120_Hz}, {120_Hz, 120_Hz}, {119_Hz, 120_Hz}, {110_Hz, 120_Hz},
+
+ {100_Hz, 90_Hz}, {90_Hz, 90_Hz}, {89_Hz, 90_Hz},
+
+ {80_Hz, 72_Hz}, {73_Hz, 72_Hz}, {72_Hz, 72_Hz}, {71_Hz, 72_Hz}, {70_Hz, 72_Hz},
+
+ {65_Hz, 60_Hz}, {60_Hz, 60_Hz}, {59_Hz, 60_Hz}, {58_Hz, 60_Hz},
+
+ {55_Hz, 90_Hz}, {50_Hz, 90_Hz}, {45_Hz, 90_Hz},
+
+ {42_Hz, 120_Hz}, {40_Hz, 120_Hz}, {39_Hz, 120_Hz},
+
+ {37_Hz, 72_Hz}, {36_Hz, 72_Hz}, {35_Hz, 72_Hz},
+
+ {30_Hz, 60_Hz},
+ };
+
+ for (auto [desired, expected] : testCases) {
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = desired;
+
+ std::stringstream ss;
+ ss << "ExplicitDefault " << desired;
+ lr.name = ss.str();
+
+ const auto bestMode = selector.getBestFrameRateMode(layers).modePtr;
+ EXPECT_EQ(expected, bestMode->getPeakFps())
+ << "expected " << expected << " for " << desired << " but got "
+ << bestMode->getPeakFps() << "(" << bestMode->getVsyncRate() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_ExplicitExactOrMultiple_WithFractionalRefreshRates) {
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ // Test that 23.976 will prefer 60 over 59.94 and 30
+ {
+ auto selector = createSelector(makeModes(kMode24, kMode25, kMode30, kMode30Frac, kMode60,
+ kMode60Frac),
+ kModeId60);
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr.desiredRefreshRate = 23.976_Hz;
+ lr.name = "ExplicitExactOrMultiple 23.976 Hz";
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+ }
+
+ // Test that 24 will choose 23.976 if 24 is not supported
+ {
+ auto selector = createSelector(makeModes(kMode24Frac, kMode25, kMode30, kMode30Frac,
+ kMode60, kMode60Frac),
+ kModeId60);
+
+ lr.desiredRefreshRate = 24_Hz;
+ lr.name = "ExplicitExactOrMultiple 24 Hz";
+ EXPECT_EQ(kModeId24Frac, selector.getBestFrameRateMode(layers).modePtr->getId());
+ }
+
+ // Test that 29.97 will prefer 59.94 over 60 and 30
+ {
+ auto selector = createSelector(makeModes(kMode24, kMode24Frac, kMode25, kMode30, kMode60,
+ kMode60Frac),
+ kModeId60);
+
+ lr.desiredRefreshRate = 29.97_Hz;
+ lr.name = "ExplicitExactOrMultiple 29.97 Hz";
+ EXPECT_EQ(kModeId60Frac, selector.getBestFrameRateMode(layers).modePtr->getId());
+ }
+
+ // Test that 29.97 will choose 60 if 59.94 is not supported
+ {
+ auto selector = createSelector(makeModes(kMode30, kMode60), kModeId60);
+
+ lr.desiredRefreshRate = 29.97_Hz;
+ lr.name = "ExplicitExactOrMultiple 29.97 Hz";
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+ }
+
+ // Test that 59.94 will choose 60 if 59.94 is not supported
+ {
+ auto selector = createSelector(makeModes(kMode60, kMode30Frac, kMode30), kModeId60);
+
+ lr.desiredRefreshRate = 59.94_Hz;
+ lr.name = "ExplicitExactOrMultiple 59.94 Hz";
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact_WithFractionalRefreshRates) {
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ // Test that voting for supported refresh rate will select this refresh rate
+ {
+ auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
+
+ for (auto desired : {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz}) {
+ lr.vote = LayerVoteType::ExplicitExact;
+ lr.desiredRefreshRate = desired;
+ std::stringstream ss;
+ ss << "ExplicitExact " << desired;
+ lr.name = ss.str();
+
+ EXPECT_EQ(lr.desiredRefreshRate,
+ selector.getBestFrameRateMode(layers).modePtr->getPeakFps());
+ }
+ }
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresTouchFlag) {
+ auto selector = createSelector(kModes_60_90, kModeId90);
+
+ constexpr FpsRange k90 = {90_Hz, 90_Hz};
+ constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz ExplicitDefault";
+ lr.focused = true;
+
+ const auto [rankedFrameRate, signals] =
+ selector.getRankedFrameRates(layers, {.touch = true, .idle = true});
+
+ EXPECT_EQ(rankedFrameRate.begin()->frameRateMode.modePtr, kMode60);
+ EXPECT_FALSE(signals.touch);
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withDisplayManagerRequestingSingleRate_ignoresIdleFlag) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ constexpr FpsRange k60 = {60_Hz, 60_Hz};
+ constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {k60, k60}, {k60_90, k60_90}}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = 90_Hz;
+ lr.name = "90Hz ExplicitDefault";
+ lr.focused = true;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers, {.idle = true}).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, testDisplayModeOrdering) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f},
+ {.weight = 1.f},
+ {.weight = 1.f},
+ {.weight = 1.f},
+ {.weight = 1.f}};
+ auto& lr1 = layers[0];
+ auto& lr2 = layers[1];
+ auto& lr3 = layers[2];
+ auto& lr4 = layers[3];
+ auto& lr5 = layers[4];
+
+ lr1.desiredRefreshRate = 90_Hz;
+ lr1.name = "90Hz";
+ lr1.focused = true;
+
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz";
+ lr2.focused = true;
+
+ lr3.desiredRefreshRate = 72_Hz;
+ lr3.name = "72Hz";
+ lr3.focused = true;
+
+ lr4.desiredRefreshRate = 120_Hz;
+ lr4.name = "120Hz";
+ lr4.focused = true;
+
+ lr5.desiredRefreshRate = 30_Hz;
+ lr5.name = "30Hz";
+ lr5.focused = true;
+
+ auto expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{120_Hz, kMode120},
+ {90_Hz, kMode90},
+ {72_Hz, kMode72},
+ {60_Hz, kMode60},
+ {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{120_Hz, kMode120}, {90_Hz, kMode90}, {72_Hz, kMode72}, {60_Hz, kMode60},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+ }
+ }();
+
+ auto actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+
+ lr1.vote = LayerVoteType::Max;
+ lr1.name = "Max";
+
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz";
+
+ lr3.desiredRefreshRate = 72_Hz;
+ lr3.name = "72Hz";
+
+ lr4.desiredRefreshRate = 90_Hz;
+ lr4.name = "90Hz";
+
+ lr5.desiredRefreshRate = 120_Hz;
+ lr5.name = "120Hz";
+
+ expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{120_Hz, kMode120},
+ {90_Hz, kMode90},
+ {72_Hz, kMode72},
+ {60_Hz, kMode60},
+ {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{120_Hz, kMode120}, {90_Hz, kMode90}, {72_Hz, kMode72}, {60_Hz, kMode60},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+ }
+ }();
+ actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+
+ lr1.vote = LayerVoteType::Heuristic;
+ lr1.desiredRefreshRate = 30_Hz;
+ lr1.name = "30Hz";
+
+ lr2.desiredRefreshRate = 120_Hz;
+ lr2.name = "120Hz";
+
+ lr3.desiredRefreshRate = 60_Hz;
+ lr3.name = "60Hz";
+
+ lr5.desiredRefreshRate = 72_Hz;
+ lr5.name = "72Hz";
+
+ expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, kMode30},
+ {60_Hz, kMode60},
+ {90_Hz, kMode90},
+ {120_Hz, kMode120},
+ {72_Hz, kMode72}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, kMode30}, {60_Hz, kMode60}, {90_Hz, kMode90}, {120_Hz, kMode120},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {72_Hz, kMode72}, {36_Hz, kMode72}};
+ }
+ }();
+ actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+
+ lr1.desiredRefreshRate = 120_Hz;
+ lr1.name = "120Hz";
+ lr1.weight = 0.0f;
+
+ lr2.desiredRefreshRate = 60_Hz;
+ lr2.name = "60Hz";
+ lr2.vote = LayerVoteType::NoVote;
+
+ lr3.name = "60Hz-2";
+ lr3.vote = LayerVoteType::Heuristic;
+
+ lr4.vote = LayerVoteType::ExplicitExact;
+
+ lr5.desiredRefreshRate = 120_Hz;
+ lr5.name = "120Hz-2";
+
+ expectedRanking = []() -> std::vector<FrameRateMode> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{90_Hz, kMode90},
+ {60_Hz, kMode60},
+ {120_Hz, kMode120},
+ {72_Hz, kMode72},
+ {30_Hz, kMode30}};
+ case Config::FrameRateOverride::Enabled:
+ return {{90_Hz, kMode90}, {60_Hz, kMode60}, {120_Hz, kMode120}, {72_Hz, kMode72},
+ {45_Hz, kMode90}, {40_Hz, kMode120}, {36_Hz, kMode72}, {30_Hz, kMode30}};
+ }
+ }();
+ actualRanking = selector.getRankedFrameRates(layers, {}).ranking;
+
+ ASSERT_EQ(expectedRanking.size(), actualRanking.size());
+
+ for (size_t i = 0; i < expectedRanking.size(); ++i) {
+ EXPECT_EQ(expectedRanking[i], actualRanking[i].frameRateMode)
+ << "Expected " << expectedRanking[i].fps.getIntValue() << " ("
+ << expectedRanking[i].modePtr->getVsyncRate().getIntValue() << ")"
+ << " Actual " << actualRanking[i].frameRateMode.fps.getIntValue() << " ("
+ << actualRanking[i].frameRateMode.modePtr->getVsyncRate().getIntValue() << ")";
+ }
+}
+
+TEST_P(RefreshRateSelectorTest,
+ getBestFrameRateMode_withDisplayManagerRequestingSingleRate_onlySwitchesRatesForExplicitFocusedLayers) {
+ auto selector = createSelector(kModes_60_90, kModeId90);
+
+ constexpr FpsRange k90 = {90_Hz, 90_Hz};
+ constexpr FpsRange k60_90 = {60_Hz, 90_Hz};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {k90, k90}, {k60_90, k60_90}}));
+
+ const auto [ranking, signals] = selector.getRankedFrameRates({}, {});
+ EXPECT_EQ(ranking.front().frameRateMode.modePtr, kMode90);
+ EXPECT_FALSE(signals.touch);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& lr = layers[0];
+
+ lr.vote = LayerVoteType::ExplicitExactOrMultiple;
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz ExplicitExactOrMultiple";
+ lr.focused = false;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.focused = true;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::ExplicitDefault;
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz ExplicitDefault";
+ lr.focused = false;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.focused = true;
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Heuristic;
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz Heuristic";
+ lr.focused = false;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.focused = true;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Max;
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz Max";
+ lr.focused = false;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.focused = true;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.vote = LayerVoteType::Min;
+ lr.desiredRefreshRate = 60_Hz;
+ lr.name = "60Hz Min";
+ lr.focused = false;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+
+ lr.focused = true;
+ EXPECT_EQ(kMode90, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingNotAllowed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ // The default policy doesn't allow group switching. Verify that no
+ // group switches are performed.
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::ExplicitDefault;
+ layer.desiredRefreshRate = 90_Hz;
+ layer.seamlessness = Seamlessness::SeamedAndSeamless;
+ layer.name = "90Hz ExplicitDefault";
+ layer.focused = true;
+
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayer) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::ExplicitDefault;
+ layer.desiredRefreshRate = 90_Hz;
+ layer.seamlessness = Seamlessness::SeamedAndSeamless;
+ layer.name = "90Hz ExplicitDefault";
+ layer.focused = true;
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamless) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ // Verify that we won't change the group if seamless switch is required.
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::ExplicitDefault;
+ layer.desiredRefreshRate = 90_Hz;
+ layer.seamlessness = Seamlessness::OnlySeamless;
+ layer.name = "90Hz ExplicitDefault";
+ layer.focused = true;
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerOnlySeamlessDefaultFps) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ // Verify that we won't do a seamless switch if we request the same mode as the default
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::ExplicitDefault;
+ layer.desiredRefreshRate = 60_Hz;
+ layer.seamlessness = Seamlessness::OnlySeamless;
+ layer.name = "60Hz ExplicitDefault";
+ layer.focused = true;
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithOneLayerDefaultSeamlessness) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ // Verify that if the active mode is in another group and there are no layers with
+ // Seamlessness::SeamedAndSeamless, we should switch back to the default group.
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::ExplicitDefault;
+ layer.desiredRefreshRate = 60_Hz;
+ layer.seamlessness = Seamlessness::Default;
+ layer.name = "60Hz ExplicitDefault";
+ layer.focused = true;
+
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersOnlySeamlessAndSeamed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ // If there's a layer with Seamlessness::SeamedAndSeamless, another layer with
+ // Seamlessness::OnlySeamless can't change the mode group.
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].seamlessness = Seamlessness::OnlySeamless;
+ layers[0].name = "60Hz ExplicitDefault";
+ layers[0].focused = true;
+
+ layers.push_back(LayerRequirement{.weight = 0.5f});
+ layers[1].vote = LayerVoteType::ExplicitDefault;
+ layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+ layers[1].desiredRefreshRate = 90_Hz;
+ layers[1].name = "90Hz ExplicitDefault";
+ layers[1].focused = false;
+
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultFocusedAndSeamed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ // If there's a focused layer with Seamlessness::SeamedAndSeamless, another layer with
+ // Seamlessness::Default can't change the mode group back to the group of the default
+ // mode.
+ // For example, this may happen when a video playback requests and gets a seamed switch,
+ // but another layer (with default seamlessness) starts animating. The animating layer
+ // should not cause a seamed switch.
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].seamlessness = Seamlessness::Default;
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].focused = true;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ layers[0].name = "60Hz ExplicitDefault";
+
+ layers.push_back(LayerRequirement{.weight = 0.1f});
+ layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+ layers[1].desiredRefreshRate = 90_Hz;
+ layers[1].focused = true;
+ layers[1].vote = LayerVoteType::ExplicitDefault;
+ layers[1].name = "90Hz ExplicitDefault";
+
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, groupSwitchingWithTwoLayersDefaultNotFocusedAndSeamed) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId60);
+
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ // Layer with Seamlessness::Default can change the mode group if there's an
+ // unfocused layer with Seamlessness::SeamedAndSeamless. For example, this happens
+ // when in split screen mode the user switches between the two visible applications.
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].seamlessness = Seamlessness::Default;
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].focused = true;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ layers[0].name = "60Hz ExplicitDefault";
+
+ layers.push_back(LayerRequirement{.weight = 0.7f});
+ layers[1].seamlessness = Seamlessness::SeamedAndSeamless;
+ layers[1].desiredRefreshRate = 90_Hz;
+ layers[1].focused = false;
+ layers[1].vote = LayerVoteType::ExplicitDefault;
+ layers[1].name = "90Hz ExplicitDefault";
+
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, nonSeamlessVotePrefersSeamlessSwitches) {
+ auto selector = createSelector(kModes_30_60, kModeId60);
+
+ // Allow group switching.
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+ layer.desiredRefreshRate = 60_Hz;
+ layer.seamlessness = Seamlessness::SeamedAndSeamless;
+ layer.name = "60Hz ExplicitExactOrMultiple";
+ layer.focused = true;
+
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode(layers).modePtr->getId());
+
+ selector.setActiveMode(kModeId120, 120_Hz);
+ EXPECT_EQ(kModeId120, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, nonSeamlessExactAndSeamlessMultipleLayers) {
+ auto selector = createSelector(kModes_25_30_50_60, kModeId60);
+
+ // Allow group switching.
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ std::vector<LayerRequirement> layers = {{.name = "60Hz ExplicitDefault",
+ .vote = LayerVoteType::ExplicitDefault,
+ .desiredRefreshRate = 60_Hz,
+ .seamlessness = Seamlessness::SeamedAndSeamless,
+ .weight = 0.5f,
+ .focused = false},
+ {.name = "25Hz ExplicitExactOrMultiple",
+ .vote = LayerVoteType::ExplicitExactOrMultiple,
+ .desiredRefreshRate = 25_Hz,
+ .seamlessness = Seamlessness::OnlySeamless,
+ .weight = 1.f,
+ .focused = true}};
+
+ EXPECT_EQ(kModeId50, selector.getBestFrameRateMode(layers).modePtr->getId());
+
+ auto& seamedLayer = layers[0];
+ seamedLayer.desiredRefreshRate = 30_Hz;
+ seamedLayer.name = "30Hz ExplicitDefault";
+ selector.setActiveMode(kModeId30, 30_Hz);
+
+ EXPECT_EQ(kModeId25, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, minLayersDontTrigerSeamedSwitch) {
+ auto selector = createSelector(kModes_60_90_G1, kModeId90);
+
+ // Allow group switching.
+ RefreshRateSelector::DisplayManagerPolicy policy;
+ policy.defaultMode = selector.getCurrentPolicy().defaultMode;
+ policy.allowGroupSwitching = true;
+ EXPECT_EQ(SetPolicyResult::Changed, selector.setPolicy(policy));
+
+ std::vector<LayerRequirement> layers = {
+ {.name = "Min", .vote = LayerVoteType::Min, .weight = 1.f, .focused = true}};
+
+ EXPECT_EQ(kModeId90, selector.getBestFrameRateMode(layers).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, primaryVsAppRequestPolicy) {
+ auto selector = createSelector(kModes_30_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+
+ struct Args {
+ bool touch = false;
+ bool focused = true;
+ };
+
+ // Returns the mode selected by getBestFrameRateMode for a single layer with the given
+ // arguments.
+ const auto getFrameRate = [&](LayerVoteType voteType, Fps fps,
+ Args args = {}) -> DisplayModeId {
+ layers[0].vote = voteType;
+ layers[0].desiredRefreshRate = fps;
+ layers[0].focused = args.focused;
+ return selector.getBestFrameRateMode(layers, {.touch = args.touch}).modePtr->getId();
+ };
+
+ constexpr FpsRange k30_60 = {30_Hz, 60_Hz};
+ constexpr FpsRange k30_90 = {30_Hz, 90_Hz};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {k30_60, k30_60}, {k30_90, k30_90}}));
+
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode().modePtr->getId());
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
+ EXPECT_EQ(kModeId30, getFrameRate(LayerVoteType::Min, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
+ EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
+
+ // Unfocused layers are not allowed to override primary range.
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.focused = false}));
+ EXPECT_EQ(kModeId60,
+ getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.focused = false}));
+
+ // Touch boost should be restricted to the primary range.
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz, {.touch = true}));
+
+ // When we're higher than the primary range max due to a layer frame rate setting, touch boost
+ // shouldn't drag us back down to the primary range max.
+ EXPECT_EQ(kModeId90, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz, {.touch = true}));
+ EXPECT_EQ(kModeId60,
+ getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz, {.touch = true}));
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::NoVote, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Min, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Max, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::Heuristic, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitDefault, 90_Hz));
+ EXPECT_EQ(kModeId60, getFrameRate(LayerVoteType::ExplicitExactOrMultiple, 90_Hz));
+}
+
+TEST_P(RefreshRateSelectorTest, idle) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+
+ const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+ bool touchActive) -> DisplayModeId {
+ layers[0].vote = voteType;
+ layers[0].desiredRefreshRate = 90_Hz;
+
+ const auto [ranking, signals] =
+ selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
+
+ // Refresh rate will be chosen by either touch state or idle state.
+ EXPECT_EQ(!touchActive, signals.idle);
+ return ranking.front().frameRateMode.modePtr->getId();
+ };
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}));
+
+ // Idle should be lower priority than touch boost.
+ {
+ constexpr bool kTouchActive = true;
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+ EXPECT_EQ(kModeId90, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+ EXPECT_EQ(kModeId90,
+ getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+ }
+
+ // With no layers, idle should still be lower priority than touch boost.
+ EXPECT_EQ(kModeId90,
+ selector.getBestFrameRateMode({}, {.touch = true, .idle = true}).modePtr->getId());
+
+ // Idle should be higher precedence than other layer frame rate considerations.
+ selector.setActiveMode(kModeId90, 90_Hz);
+
+ {
+ constexpr bool kTouchActive = false;
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+ EXPECT_EQ(kModeId60, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+ EXPECT_EQ(kModeId60,
+ getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+ }
+
+ // Idle should be applied rather than the active mode when there are no layers.
+ EXPECT_EQ(kModeId60, selector.getBestFrameRateMode({}, {.idle = true}).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, findClosestKnownFrameRate) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ for (float fps = 1.0f; fps <= 120.0f; fps += 0.1f) {
+ const auto knownFrameRate = selector.findClosestKnownFrameRate(Fps::fromValue(fps));
+ const Fps expectedFrameRate = [fps] {
+ if (fps < 26.91f) return 24_Hz;
+ if (fps < 37.51f) return 30_Hz;
+ if (fps < 52.51f) return 45_Hz;
+ if (fps < 66.01f) return 60_Hz;
+ if (fps < 81.01f) return 72_Hz;
+ return 90_Hz;
+ }();
+
+ EXPECT_EQ(expectedFrameRate, knownFrameRate);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_KnownFrameRate) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ struct Expectation {
+ Fps fps;
+ ftl::NonNull<DisplayModePtr> mode;
+ };
+
+ const std::initializer_list<Expectation> knownFrameRatesExpectations = {
+ {24_Hz, kMode60}, {30_Hz, kMode60}, {45_Hz, kMode90},
+ {60_Hz, kMode60}, {72_Hz, kMode90}, {90_Hz, kMode90},
+ };
+
+ // Make sure the test tests all the known frame rate
+ const auto& knownFrameRates = selector.knownFrameRates();
+ const bool equal = std::equal(knownFrameRates.begin(), knownFrameRates.end(),
+ knownFrameRatesExpectations.begin(),
+ [](Fps fps, const Expectation& expected) {
+ return isApproxEqual(fps, expected.fps);
+ });
+ EXPECT_TRUE(equal);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+ layer.vote = LayerVoteType::Heuristic;
+
+ for (const auto& [fps, mode] : knownFrameRatesExpectations) {
+ layer.desiredRefreshRate = fps;
+ EXPECT_EQ(mode, selector.getBestFrameRateMode(layers).modePtr);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExact) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+ auto& explicitExactLayer = layers[0];
+ auto& explicitExactOrMultipleLayer = layers[1];
+
+ explicitExactLayer.vote = LayerVoteType::ExplicitExact;
+ explicitExactLayer.name = "ExplicitExact";
+ explicitExactLayer.desiredRefreshRate = 30_Hz;
+
+ explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
+ explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
+ explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
+
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ EXPECT_FRAME_RATE_MODE(kMode30, 30_Hz,
+ selector.getBestScoredFrameRate(layers, {.touch = true})
+ .frameRateMode);
+
+ } else {
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+ selector.getBestScoredFrameRate(layers, {.touch = true})
+ .frameRateMode);
+ }
+
+ explicitExactOrMultipleLayer.desiredRefreshRate = 120_Hz;
+ explicitExactLayer.desiredRefreshRate = 60_Hz;
+
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ } else {
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ }
+
+ explicitExactLayer.desiredRefreshRate = 72_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode72, 72_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+
+ explicitExactLayer.desiredRefreshRate = 90_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+
+ explicitExactLayer.desiredRefreshRate = 120_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ReadsCache) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+ using GlobalSignals = RefreshRateSelector::GlobalSignals;
+ const auto args = std::make_pair(std::vector<LayerRequirement>{},
+ GlobalSignals{.touch = true, .idle = true});
+
+ const RefreshRateSelector::RankedFrameRates result = {{RefreshRateSelector::ScoredFrameRate{
+ {90_Hz, kMode90}}},
+ GlobalSignals{.touch = true}};
+
+ selector.mutableGetRankedRefreshRatesCache() = {args, result};
+
+ EXPECT_EQ(result, selector.getRankedFrameRates(args.first, args.second));
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_WritesCache) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId60);
+
+ EXPECT_FALSE(selector.mutableGetRankedRefreshRatesCache());
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+ RefreshRateSelector::GlobalSignals globalSignals{.touch = true, .idle = true};
+
+ const auto result = selector.getRankedFrameRates(layers, globalSignals);
+
+ const auto& cache = selector.mutableGetRankedRefreshRatesCache();
+ ASSERT_TRUE(cache);
+
+ EXPECT_EQ(cache->arguments, std::make_pair(layers, globalSignals));
+ EXPECT_EQ(cache->result, result);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_ExplicitExactTouchBoost) {
+ auto selector = createSelector(kModes_60_120, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}, {.weight = 0.5f}};
+ auto& explicitExactLayer = layers[0];
+ auto& explicitExactOrMultipleLayer = layers[1];
+
+ explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
+ explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
+ explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
+
+ explicitExactLayer.vote = LayerVoteType::ExplicitExact;
+ explicitExactLayer.name = "ExplicitExact";
+ explicitExactLayer.desiredRefreshRate = 30_Hz;
+
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+ } else {
+ EXPECT_EQ(kMode120, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+ }
+
+ explicitExactOrMultipleLayer.vote = LayerVoteType::NoVote;
+
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers, {.touch = true}).modePtr);
+}
+
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_FractionalRefreshRates_ExactAndDefault) {
+ auto selector = createSelector(kModes_24_25_30_50_60_Frac, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 0.5f}, {.weight = 0.5f}};
+ auto& explicitDefaultLayer = layers[0];
+ auto& explicitExactOrMultipleLayer = layers[1];
+
+ explicitExactOrMultipleLayer.vote = LayerVoteType::ExplicitExactOrMultiple;
+ explicitExactOrMultipleLayer.name = "ExplicitExactOrMultiple";
+ explicitExactOrMultipleLayer.desiredRefreshRate = 60_Hz;
+
+ explicitDefaultLayer.vote = LayerVoteType::ExplicitDefault;
+ explicitDefaultLayer.name = "ExplicitDefault";
+ explicitDefaultLayer.desiredRefreshRate = 59.94_Hz;
+
+ EXPECT_EQ(kMode60, selector.getBestFrameRateMode(layers).modePtr);
+}
+
+// b/190578904
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_withCloseRefreshRates) {
+ if (g_noSlowTests) {
+ GTEST_SKIP();
+ }
+
+ const int kMinRefreshRate = RefreshRateSelector::kMinSupportedFrameRate.getIntValue();
+ constexpr int kMaxRefreshRate = 240;
+
+ DisplayModes displayModes;
+ for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+ const DisplayModeId modeId(fps);
+ displayModes.try_emplace(modeId,
+ createDisplayMode(modeId,
+ Fps::fromValue(static_cast<float>(fps))));
+ }
+
+ const auto selector = createSelector(std::move(displayModes), DisplayModeId(kMinRefreshRate));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ const auto testRefreshRate = [&](Fps fps, LayerVoteType vote) {
+ layers[0].desiredRefreshRate = fps;
+ layers[0].vote = vote;
+ EXPECT_EQ(fps.getIntValue(),
+ selector.getBestFrameRateMode(layers).modePtr->getPeakFps().getIntValue())
+ << "Failed for " << ftl::enum_string(vote);
+ };
+
+ for (int fps = kMinRefreshRate; fps < kMaxRefreshRate; fps++) {
+ const auto refreshRate = Fps::fromValue(static_cast<float>(fps));
+ testRefreshRate(refreshRate, LayerVoteType::Heuristic);
+ testRefreshRate(refreshRate, LayerVoteType::ExplicitDefault);
+ testRefreshRate(refreshRate, LayerVoteType::ExplicitExactOrMultiple);
+ testRefreshRate(refreshRate, LayerVoteType::ExplicitExact);
+ }
+}
+
+// b/190578904
+TEST_P(RefreshRateSelectorTest, getBestFrameRateMode_conflictingVotes) {
+ constexpr DisplayModeId kActiveModeId{0};
+ DisplayModes displayModes = makeModes(createDisplayMode(kActiveModeId, 43_Hz),
+ createDisplayMode(DisplayModeId(1), 53_Hz),
+ createDisplayMode(DisplayModeId(2), 55_Hz),
+ createDisplayMode(DisplayModeId(3), 60_Hz));
+
+ const RefreshRateSelector::GlobalSignals globalSignals = {.touch = false, .idle = false};
+ const auto selector = createSelector(std::move(displayModes), kActiveModeId);
+
+ const std::vector<LayerRequirement> layers = {
+ {
+ .vote = LayerVoteType::ExplicitDefault,
+ .desiredRefreshRate = 43_Hz,
+ .seamlessness = Seamlessness::SeamedAndSeamless,
+ .weight = 0.41f,
+ },
+ {
+ .vote = LayerVoteType::ExplicitExactOrMultiple,
+ .desiredRefreshRate = 53_Hz,
+ .seamlessness = Seamlessness::SeamedAndSeamless,
+ .weight = 0.41f,
+ },
+ };
+
+ EXPECT_EQ(53_Hz, selector.getBestFrameRateMode(layers, globalSignals).modePtr->getPeakFps());
+}
+
+TEST_P(RefreshRateSelectorTest, modeComparison) {
+ EXPECT_LT(kMode60->getPeakFps(), kMode90->getPeakFps());
+ EXPECT_GE(kMode60->getPeakFps(), kMode60->getPeakFps());
+ EXPECT_GE(kMode90->getPeakFps(), kMode90->getPeakFps());
+}
+
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerAction) {
+ using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+
+ auto selector = createSelector(kModes_60_90, kModeId90);
+
+ EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 90_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId90, {90_Hz, 90_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+}
+
+TEST_P(RefreshRateSelectorTest, testKernelIdleTimerActionFor120Hz) {
+ using KernelIdleTimerAction = RefreshRateSelector::KernelIdleTimerAction;
+
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 60_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 60_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {60_Hz, 120_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOn, selector.getIdleTimerAction());
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId120, {120_Hz, 120_Hz}}));
+ EXPECT_EQ(KernelIdleTimerAction::TurnOff, selector.getIdleTimerAction());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateDivisor) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId30);
+
+ const auto frameRate = 30_Hz;
+ Fps displayRefreshRate = selector.getActiveMode().getPeakFps();
+ EXPECT_EQ(1, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+ selector.setActiveMode(kModeId60, 60_Hz);
+ displayRefreshRate = selector.getActiveMode().getPeakFps();
+ EXPECT_EQ(2, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+ selector.setActiveMode(kModeId72, 72_Hz);
+ displayRefreshRate = selector.getActiveMode().getPeakFps();
+ EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+ displayRefreshRate = selector.getActiveMode().getPeakFps();
+ EXPECT_EQ(3, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+ selector.setActiveMode(kModeId120, 120_Hz);
+ displayRefreshRate = selector.getActiveMode().getPeakFps();
+ EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, frameRate));
+
+ selector.setActiveMode(kModeId90, 90_Hz);
+ displayRefreshRate = selector.getActiveMode().getPeakFps();
+ EXPECT_EQ(4, RefreshRateSelector::getFrameRateDivisor(displayRefreshRate, 22.5_Hz));
+
+ EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 25_Hz));
+ EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(24_Hz, 23.976_Hz));
+ EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(30_Hz, 29.97_Hz));
+ EXPECT_EQ(0, RefreshRateSelector::getFrameRateDivisor(60_Hz, 59.94_Hz));
+}
+
+TEST_P(RefreshRateSelectorTest, isFractionalPairOrMultiple) {
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(23.976_Hz, 24_Hz));
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 23.976_Hz));
+
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 30_Hz));
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(30_Hz, 29.97_Hz));
+
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(59.94_Hz, 60_Hz));
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(60_Hz, 59.94_Hz));
+
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 60_Hz));
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(60_Hz, 29.97_Hz));
+
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(59.94_Hz, 30_Hz));
+ EXPECT_TRUE(RefreshRateSelector::isFractionalPairOrMultiple(30_Hz, 59.94_Hz));
+
+ const auto refreshRates = {23.976_Hz, 24_Hz, 25_Hz, 29.97_Hz, 30_Hz, 50_Hz, 59.94_Hz, 60_Hz};
+ for (auto refreshRate : refreshRates) {
+ EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(refreshRate, refreshRate));
+ }
+
+ EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(24_Hz, 25_Hz));
+ EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(23.978_Hz, 25_Hz));
+ EXPECT_FALSE(RefreshRateSelector::isFractionalPairOrMultiple(29.97_Hz, 59.94_Hz));
+}
+
+TEST_P(RefreshRateSelectorTest, test23976Chooses120) {
+ auto selector = createSelector(kModes_60_90_120, kModeId120);
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "23.976 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 23.976_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, test23976Chooses60IfThresholdIs120) {
+ auto selector =
+ createSelector(kModes_60_90_120, kModeId120, {.frameRateMultipleThreshold = 120});
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "23.976 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 23.976_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, test25Chooses60) {
+ auto selector = createSelector(kModes_60_90_120, kModeId120);
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "25 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 25.00_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, test2997Chooses60) {
+ auto selector = createSelector(kModes_60_90_120, kModeId120);
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "29.97 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 29.97_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, test50Chooses120) {
+ auto selector = createSelector(kModes_60_90_120, kModeId120);
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "50 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 50.00_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, test50Chooses60IfThresholdIs120) {
+ auto selector =
+ createSelector(kModes_60_90_120, kModeId120, {.frameRateMultipleThreshold = 120});
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "50 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 50.00_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, test5994Chooses60) {
+ auto selector = createSelector(kModes_60_90_120, kModeId120);
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "59.94 ExplicitExactOrMultiple";
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ layers[0].desiredRefreshRate = 59.94_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_noLayers) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ EXPECT_TRUE(selector.getFrameRateOverrides({}, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_NonExplicit) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+
+ layers[0].vote = LayerVoteType::NoVote;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::Min;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::Max;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::Heuristic;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_Disabled) {
+ if (GetParam() != Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+
+ layers[0].vote = LayerVoteType::ExplicitExact;
+ EXPECT_TRUE(selector.getFrameRateOverrides(layers, 120_Hz, {}).empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_60on120) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExact;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_twoUids) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f},
+ {.ownerUid = 5678, .weight = 1.f}};
+
+ layers[0].name = "Test layer 1234";
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ layers[1].name = "Test layer 5678";
+ layers[1].desiredRefreshRate = 30_Hz;
+ layers[1].vote = LayerVoteType::ExplicitDefault;
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+
+ EXPECT_EQ(2u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+ ASSERT_EQ(1u, frameRateOverrides.count(5678));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(5678));
+
+ layers[1].vote = LayerVoteType::Heuristic;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[1].ownerUid = 1234;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_touch) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.ownerUid = 1234, .weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExact;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {.touch = true});
+ EXPECT_TRUE(frameRateOverrides.empty());
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_DivisorIsNotDisplayRefreshRate) {
+ if (GetParam() == Config::FrameRateOverride::Disabled) {
+ return;
+ }
+
+ ASSERT_TRUE(GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ||
+ GetParam() == Config::FrameRateOverride::AppOverride ||
+ GetParam() == Config::FrameRateOverride::Enabled);
+
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 30_Hz;
+
+ const auto expetedFps =
+ GetParam() == Config::FrameRateOverride::AppOverrideNativeRefreshRates ? 60_Hz : 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
+
+ layers[0].vote = LayerVoteType::ExplicitExact;
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(expetedFps, frameRateOverrides.at(1234));
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRateInvalidPolicy) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ // The render frame rate cannot be greater than the physical refresh rate
+ {
+ const FpsRange physical = {60_Hz, 60_Hz};
+ const FpsRange render = {60_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Invalid,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {physical, render}, {physical, render}}));
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRateRestrictsPhysicalRefreshRate) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ {
+ const FpsRange physical = {0_Hz, 120_Hz};
+ const FpsRange render = {0_Hz, 60_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {physical, render}, {physical, render}}));
+ const auto expectedMaxMode =
+ GetParam() == Config::FrameRateOverride::Enabled ? kMode120 : kMode60;
+ EXPECT_EQ(expectedMaxMode, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
+ }
+
+ {
+ const FpsRange physical = {0_Hz, 120_Hz};
+ const FpsRange render = {120_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {physical, render}, {physical, render}}));
+ EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode120, selector.getMinRefreshRateByPolicy());
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverrides_InPolicy) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ {
+ const FpsRange physical = {120_Hz, 120_Hz};
+ const FpsRange render = {60_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {physical, render}, {physical, render}}));
+ }
+
+ layers[0].name = "30Hz";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ EXPECT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(60_Hz, frameRateOverrides.at(1234));
+
+ {
+ const FpsRange physical = {120_Hz, 120_Hz};
+ const FpsRange render = {30_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {physical, render}, {physical, render}}));
+ }
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ EXPECT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+
+ {
+ const FpsRange physical = {120_Hz, 120_Hz};
+ const FpsRange render = {30_Hz, 30_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {physical, render}, {physical, render}}));
+ }
+
+ layers[0].name = "60Hz";
+ layers[0].ownerUid = 1234;
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].vote = LayerVoteType::ExplicitDefault;
+
+ frameRateOverrides = selector.getFrameRateOverrides(layers, 120_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ EXPECT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(30_Hz, frameRateOverrides.at(1234));
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRates) {
+ auto selector = createSelector(kModes_30_60_72_90_120, kModeId120);
+
+ // [renderRate, refreshRate]
+ const auto expected = []() -> std::vector<std::pair<Fps, Fps>> {
+ switch (GetParam()) {
+ case Config::FrameRateOverride::Disabled:
+ case Config::FrameRateOverride::AppOverrideNativeRefreshRates:
+ case Config::FrameRateOverride::AppOverride:
+ return {{30_Hz, 30_Hz},
+ {60_Hz, 60_Hz},
+ {72_Hz, 72_Hz},
+ {90_Hz, 90_Hz},
+ {120_Hz, 120_Hz}};
+ case Config::FrameRateOverride::Enabled:
+ return {{30_Hz, 30_Hz}, {36_Hz, 72_Hz}, {40_Hz, 120_Hz}, {45_Hz, 90_Hz},
+ {60_Hz, 60_Hz}, {72_Hz, 72_Hz}, {90_Hz, 90_Hz}, {120_Hz, 120_Hz}};
+ }
+ }();
+
+ const auto& primaryRefreshRates = selector.getPrimaryFrameRates();
+ ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+ for (size_t i = 0; i < expected.size(); i++) {
+ const auto [expectedRenderRate, expectedRefreshRate] = expected[i];
+ EXPECT_EQ(expectedRenderRate, primaryRefreshRates[i].fps);
+ EXPECT_EQ(expectedRefreshRate, primaryRefreshRates[i].modePtr->getPeakFps());
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, refreshRateIsCappedWithRenderFrameRate) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_60_120, kModeId60);
+
+ constexpr FpsRange k0_120Hz = {0_Hz, 120_Hz};
+ constexpr FpsRange k0_60Hz = {0_Hz, 60_Hz};
+
+ constexpr FpsRanges kAppRequest = {/*physical*/ k0_120Hz,
+ /*render*/ k0_120Hz};
+
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate().frameRateMode);
+ {
+ constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+ /*render*/ k0_120Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+ /*primaryRanges*/
+ kPrimary,
+ /*appRequestRanges*/
+ kAppRequest}));
+ }
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz, selector.getBestScoredFrameRate().frameRateMode);
+
+ {
+ constexpr FpsRanges kPrimary = {/*physical*/ k0_60Hz,
+ /*render*/ k0_60Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+ /*primaryRanges*/
+ kPrimary,
+ /*appRequestRanges*/
+ kAppRequest}));
+ }
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate().frameRateMode);
+
+ {
+ constexpr FpsRanges kPrimary = {/*physical*/ k0_120Hz,
+ /*render*/ k0_60Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({/*defaultMode*/ kModeId60,
+ /*primaryRanges*/
+ kPrimary,
+ /*appRequestRanges*/
+ kAppRequest}));
+ }
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate().frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRates_60_120) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ auto& layer = layers[0];
+
+ const auto expectedRenderRate =
+ GetParam() == Config::FrameRateOverride::Enabled ? 30_Hz : 60_Hz;
+
+ layer.name = "30Hz ExplicitDefault";
+ layer.desiredRefreshRate = 30_Hz;
+ layer.vote = LayerVoteType::ExplicitDefault;
+ EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+
+ layer.name = "30Hz Heuristic";
+ layer.desiredRefreshRate = 30_Hz;
+ layer.vote = LayerVoteType::Heuristic;
+ EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+
+ layer.name = "30Hz ExplicitExactOrMultiple";
+ layer.desiredRefreshRate = 30_Hz;
+ layer.vote = LayerVoteType::ExplicitExactOrMultiple;
+ EXPECT_FRAME_RATE_MODE(kMode60, expectedRenderRate,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, idleWhenLowestRefreshRateIsNotDivisor) {
+ auto selector = createSelector(kModes_35_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+
+ const auto getIdleDisplayModeId = [&](LayerVoteType voteType,
+ bool touchActive) -> DisplayModeId {
+ layers[0].vote = voteType;
+ layers[0].desiredRefreshRate = 90_Hz;
+
+ const auto [ranking, signals] =
+ selector.getRankedFrameRates(layers, {.touch = touchActive, .idle = true});
+
+ // Refresh rate will be chosen by either touch state or idle state.
+ EXPECT_EQ(!touchActive, signals.idle);
+ return ranking.front().frameRateMode.modePtr->getId();
+ };
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({kModeId60, {0_Hz, 90_Hz}}));
+
+ // With no layers, idle should still be lower priority than touch boost.
+ EXPECT_EQ(kModeId90,
+ selector.getBestFrameRateMode({}, {.touch = true, .idle = true}).modePtr->getId());
+
+ // Idle should be higher precedence than other layer frame rate considerations.
+ selector.setActiveMode(kModeId90, 90_Hz);
+ {
+ constexpr bool kTouchActive = false;
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::NoVote, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Min, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Max, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::Heuristic, kTouchActive));
+ EXPECT_EQ(kModeId35, getIdleDisplayModeId(LayerVoteType::ExplicitDefault, kTouchActive));
+ EXPECT_EQ(kModeId35,
+ getIdleDisplayModeId(LayerVoteType::ExplicitExactOrMultiple, kTouchActive));
+ }
+
+ // Idle should be applied rather than the active mode when there are no layers.
+ EXPECT_EQ(kModeId35, selector.getBestFrameRateMode({}, {.idle = true}).modePtr->getId());
+}
+
+TEST_P(RefreshRateSelectorTest, policyCanBeInfinity) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ constexpr Fps inf = Fps::fromValue(std::numeric_limits<float>::infinity());
+
+ using namespace fps_approx_ops;
+ selector.setDisplayManagerPolicy({kModeId60, {0_Hz, inf}});
+
+ // With no layers, idle should still be lower priority than touch boost.
+ EXPECT_EQ(kMode120, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode60, selector.getMinRefreshRateByPolicy());
+}
+
+TEST_P(RefreshRateSelectorTest, SupportsLowPhysicalRefreshRates) {
+ auto selector = createSelector(kModes_1_5_10, kModeId10);
+
+ EXPECT_EQ(kMode10, selector.getMaxRefreshRateByPolicy());
+ EXPECT_EQ(kMode1, selector.getMinRefreshRateByPolicy());
+}
+
+// TODO(b/266481656): Once this bug is fixed, we can remove this test
+// And test for VRR when we remove this work around for VRR.
+TEST_P(RefreshRateSelectorTest, noLowerFrameRateOnMinVote) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].vote = LayerVoteType::Min;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+
+ constexpr FpsRanges kCappedAt60 = {{30_Hz, 90_Hz}, {30_Hz, 60_Hz}};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {DisplayModeId(kModeId60), kCappedAt60, kCappedAt60}));
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateIsCappedByPolicy) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ constexpr FpsRanges kCappedAt30 = {{60_Hz, 90_Hz}, {30_Hz, 30_Hz}};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {DisplayModeId(kModeId60), kCappedAt30, kCappedAt30}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].vote = LayerVoteType::Min;
+ EXPECT_FRAME_RATE_MODE(kMode60, 30_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateNotInRange) {
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ constexpr FpsRanges k60Only = {{60_Hz, 90_Hz}, {60_Hz, 60_Hz}};
+ constexpr FpsRanges kAll = {{0_Hz, 90_Hz}, {0_Hz, 90_Hz}};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy({DisplayModeId(kModeId60), k60Only, kAll}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].vote = LayerVoteType::Heuristic;
+ layers[0].desiredRefreshRate = 45_Hz;
+ EXPECT_FRAME_RATE_MODE(kMode60, 60_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateIsLowerThanMinSupported) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ auto selector = createSelector(kModes_60_90, kModeId60);
+
+ constexpr Fps kMin = RefreshRateSelector::kMinSupportedFrameRate;
+ constexpr FpsRanges kLowerThanMin = {{60_Hz, 90_Hz}, {kMin / 2, kMin / 2}};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {DisplayModeId(kModeId60), kLowerThanMin, kLowerThanMin}));
+}
+
+// b/296079213
+TEST_P(RefreshRateSelectorTest, frameRateOverrideInBlockingZone60_120) {
+ auto selector = createSelector(kModes_60_120, kModeId120);
+
+ const FpsRange only120 = {120_Hz, 120_Hz};
+ const FpsRange allRange = {0_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, allRange}, {allRange, allRange}}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "30Hz ExplicitExactOrMultiple";
+ layers[0].desiredRefreshRate = 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ EXPECT_FRAME_RATE_MODE(kMode120, 120_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ } else {
+ EXPECT_FRAME_RATE_MODE(kMode120, 30_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateOverrideInBlockingZone60_90) {
+ auto selector = createSelector(kModes_60_90, kModeId90);
+
+ const FpsRange only90 = {90_Hz, 90_Hz};
+ const FpsRange allRange = {0_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId90, {only90, allRange}, {allRange, allRange}}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "30Hz ExplicitExactOrMultiple";
+ layers[0].desiredRefreshRate = 30_Hz;
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ } else {
+ EXPECT_FRAME_RATE_MODE(kMode90, 30_Hz,
+ selector.getBestScoredFrameRate(layers).frameRateMode);
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, frameRateOverrideInBlockingZone60_90_NonDivisor) {
+ auto selector = createSelector(kModes_60_90, kModeId90);
+
+ const FpsRange only90 = {90_Hz, 90_Hz};
+ const FpsRange allRange = {0_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId90, {only90, allRange}, {allRange, allRange}}));
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "60Hz ExplicitExactOrMultiple";
+ layers[0].desiredRefreshRate = 60_Hz;
+ layers[0].vote = LayerVoteType::ExplicitExactOrMultiple;
+
+ EXPECT_FRAME_RATE_MODE(kMode90, 90_Hz, selector.getBestScoredFrameRate(layers).frameRateMode);
+}
+
+// VRR tests
+TEST_P(RefreshRateSelectorTest, singleMinMaxRateForVrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ auto selector = createSelector(kVrrMode_120, kModeId120);
+ EXPECT_TRUE(selector.supportsFrameRateOverride());
+
+ const auto minRate = selector.getMinSupportedRefreshRate();
+ const auto performanceRate = selector.getMaxSupportedRefreshRate();
+ const auto minRateByPolicy = selector.getMinRefreshRateByPolicy();
+ const auto performanceRateByPolicy = selector.getMaxRefreshRateByPolicy();
+
+ EXPECT_EQ(kVrrMode120TE240, minRate);
+ EXPECT_EQ(kVrrMode120TE240, performanceRate);
+ EXPECT_EQ(kVrrMode120TE240, minRateByPolicy);
+ EXPECT_EQ(kVrrMode120TE240, performanceRateByPolicy);
+}
+
+TEST_P(RefreshRateSelectorTest, renderRateChangesWithPolicyChangeForVrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+ const FpsRange only120 = {120_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, only120}, {only120, only120}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 120_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ const FpsRange range120 = {0_Hz, 120_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range120}, {only120, range120}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 120_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ const FpsRange range90 = {0_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range90}, {only120, range90}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 80_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ const FpsRange range80 = {0_Hz, 80_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range80}, {only120, range80}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 80_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ const FpsRange range60 = {0_Hz, 60_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range60}, {only120, range60}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 60_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ const FpsRange range48 = {0_Hz, 48_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range48}, {only120, range48}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 48_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ const FpsRange range30 = {0_Hz, 30_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range30}, {only120, range30}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 30_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, modeChangesWithPolicyChangeForVrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ auto selector = createSelector(kVrrModes_60_120, kModeId120);
+
+ const FpsRange range120 = {0_Hz, 120_Hz};
+ const FpsRange range60 = {0_Hz, 60_Hz};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {range120, range60}, {range120, range60}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode120TE240, 60_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId60, {range60, range60}, {range60, range60}}));
+ EXPECT_FRAME_RATE_MODE(kVrrMode60TE120, 60_Hz,
+ selector.getBestScoredFrameRate({}).frameRateMode);
+}
+
+TEST_P(RefreshRateSelectorTest, getFrameRateOverridesForVrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ auto selector = createSelector(kVrrMode_120, kModeId120);
+ // TODO(b/297600226) Run at lower than 30 Fps for dVRR
+ const std::vector<Fps> desiredRefreshRates = {30_Hz, 34.285_Hz, 40_Hz, 48_Hz,
+ 60_Hz, 80_Hz, 120_Hz};
+ const std::vector<LayerVoteType> layerVotes = {LayerVoteType::ExplicitDefault,
+ LayerVoteType::ExplicitExactOrMultiple,
+ LayerVoteType::ExplicitExact};
+
+ std::vector<LayerRequirement> layers = {{.weight = 1.f}};
+ layers[0].name = "Test layer";
+ layers[0].ownerUid = 1234;
+
+ for (auto desiredRefreshRate : desiredRefreshRates) {
+ layers[0].desiredRefreshRate = desiredRefreshRate;
+ for (auto vote : layerVotes) {
+ layers[0].vote = vote;
+ auto frameRateOverrides = selector.getFrameRateOverrides(layers, 240_Hz, {});
+ EXPECT_EQ(1u, frameRateOverrides.size());
+ ASSERT_EQ(1u, frameRateOverrides.count(1234));
+ EXPECT_EQ(desiredRefreshRate, frameRateOverrides.at(1234));
+ }
+ }
+}
+
+TEST_P(RefreshRateSelectorTest, renderFrameRatesForVrr) {
+ if (GetParam() != Config::FrameRateOverride::Enabled) {
+ return;
+ }
+
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+ auto selector = createSelector(kVrrMode_120, kModeId120);
+ const FpsRange only120 = {120_Hz, 120_Hz};
+ const FpsRange range120 = {0_Hz, 120_Hz};
+
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {only120, range120}, {only120, range120}}));
+
+ std::vector<Fps> expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz,
+ 40_Hz, 48_Hz, 60_Hz, 80_Hz, 120_Hz};
+
+ auto primaryRefreshRates = selector.getPrimaryFrameRates();
+ ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+
+ for (size_t i = 0; i < expected.size(); i++) {
+ EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+ EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+ }
+
+ // Render range (0,90)
+ const FpsRange range90 = {0_Hz, 90_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {range120, range90}, {range120, range90}}));
+
+ expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz, 40_Hz, 48_Hz, 60_Hz, 80_Hz};
+
+ primaryRefreshRates = selector.getPrimaryFrameRates();
+ ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+ for (size_t i = 0; i < expected.size(); i++) {
+ EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+ EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+ }
+
+ // Render range (0,60)
+ const FpsRange range60 = {0_Hz, 60_Hz};
+ EXPECT_EQ(SetPolicyResult::Changed,
+ selector.setDisplayManagerPolicy(
+ {kModeId120, {range120, range60}, {range120, range60}}));
+ expected = {20_Hz, 21.818_Hz, 24_Hz, 26.666_Hz, 30_Hz, 34.285_Hz, 40_Hz, 48_Hz, 60_Hz};
+
+ primaryRefreshRates = selector.getPrimaryFrameRates();
+ ASSERT_EQ(expected.size(), primaryRefreshRates.size());
+ for (size_t i = 0; i < expected.size(); i++) {
+ EXPECT_EQ(expected[i], primaryRefreshRates[i].fps);
+ EXPECT_EQ(120_Hz, primaryRefreshRates[i].modePtr->getPeakFps());
+ }
+}
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp b/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp
index f19e55409c..409e1ef5d7 100644
--- a/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/RegionSamplingTest.cpp
@@ -106,40 +106,6 @@ TEST_F(RegionSamplingTest, bounds_checking) {
testing::Eq(0.0));
}
-// workaround for b/133849373
-TEST_F(RegionSamplingTest, orientation_90) {
- std::generate(buffer.begin(), buffer.end(),
- [n = 0]() mutable { return (n++ > (kStride * kHeight >> 1)) ? kBlack : kWhite; });
-
- Rect tl_region{0, 0, 4, 4};
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_0,
- tl_region),
- testing::Eq(1.0));
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_180,
- tl_region),
- testing::Eq(1.0));
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_90,
- tl_region),
- testing::Eq(0.0));
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_270,
- tl_region),
- testing::Eq(0.0));
-
- Rect br_region{kWidth - 4, kHeight - 4, kWidth, kHeight};
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_0,
- br_region),
- testing::Eq(0.0));
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_180,
- br_region),
- testing::Eq(0.0));
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_90,
- br_region),
- testing::Eq(1.0));
- EXPECT_THAT(sampleArea(buffer.data(), kWidth, kHeight, kStride, ui::Transform::ROT_270,
- br_region),
- testing::Eq(1.0));
-}
-
} // namespace android
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
index 93c809e2fd..e515895a1a 100644
--- a/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SchedulerTest.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <common/test/FlagUtils.h>
+#include <ftl/fake_guard.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <log/log.h>
@@ -21,7 +23,8 @@
#include <mutex>
#include "Scheduler/EventThread.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/RefreshRateSelector.h"
+#include "Scheduler/VSyncPredictor.h"
#include "TestableScheduler.h"
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockDisplayMode.h"
@@ -29,9 +32,17 @@
#include "mock/MockLayer.h"
#include "mock/MockSchedulerCallback.h"
+#include <FrontEnd/LayerHierarchy.h>
+
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include "FpsOps.h"
+
+using namespace com::android::graphics::surfaceflinger;
+
namespace android::scheduler {
using android::mock::createDisplayMode;
+using android::mock::createVrrDisplayMode;
using testing::_;
using testing::Return;
@@ -41,14 +52,16 @@ namespace {
using MockEventThread = android::mock::EventThread;
using MockLayer = android::mock::MockLayer;
-constexpr PhysicalDisplayId PHYSICAL_DISPLAY_ID = PhysicalDisplayId::fromPort(255u);
+using LayerHierarchy = surfaceflinger::frontend::LayerHierarchy;
+using LayerHierarchyBuilder = surfaceflinger::frontend::LayerHierarchyBuilder;
+using RequestedLayerState = surfaceflinger::frontend::RequestedLayerState;
class SchedulerTest : public testing::Test {
protected:
class MockEventThreadConnection : public android::EventThreadConnection {
public:
explicit MockEventThreadConnection(EventThread* eventThread)
- : EventThreadConnection(eventThread, /*callingUid=*/0, ResyncCallback()) {}
+ : EventThreadConnection(eventThread, /*callingUid*/ static_cast<uid_t>(0)) {}
~MockEventThreadConnection() = default;
MOCK_METHOD1(stealReceiveChannel, binder::Status(gui::BitTube* outChannel));
@@ -58,14 +71,34 @@ protected:
SchedulerTest();
- static inline const DisplayModePtr kMode60 = createDisplayMode(DisplayModeId(0), 60_Hz);
- static inline const DisplayModePtr kMode120 = createDisplayMode(DisplayModeId(1), 120_Hz);
+ static constexpr PhysicalDisplayId kDisplayId1 = PhysicalDisplayId::fromPort(255u);
+ static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode60 =
+ ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(0), 60_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kDisplay1Mode120 =
+ ftl::as_non_null(createDisplayMode(kDisplayId1, DisplayModeId(1), 120_Hz));
+ static inline const DisplayModes kDisplay1Modes = makeModes(kDisplay1Mode60, kDisplay1Mode120);
+
+ static constexpr PhysicalDisplayId kDisplayId2 = PhysicalDisplayId::fromPort(254u);
+ static inline const ftl::NonNull<DisplayModePtr> kDisplay2Mode60 =
+ ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(0), 60_Hz));
+ static inline const ftl::NonNull<DisplayModePtr> kDisplay2Mode120 =
+ ftl::as_non_null(createDisplayMode(kDisplayId2, DisplayModeId(1), 120_Hz));
+ static inline const DisplayModes kDisplay2Modes = makeModes(kDisplay2Mode60, kDisplay2Mode120);
- std::shared_ptr<RefreshRateConfigs> mConfigs =
- std::make_shared<RefreshRateConfigs>(makeModes(kMode60), kMode60->getId());
+ static constexpr PhysicalDisplayId kDisplayId3 = PhysicalDisplayId::fromPort(253u);
+ static inline const ftl::NonNull<DisplayModePtr> kDisplay3Mode60 =
+ ftl::as_non_null(createDisplayMode(kDisplayId3, DisplayModeId(0), 60_Hz));
+ static inline const DisplayModes kDisplay3Modes = makeModes(kDisplay3Mode60);
+
+ std::shared_ptr<RefreshRateSelector> mSelector =
+ std::make_shared<RefreshRateSelector>(makeModes(kDisplay1Mode60),
+ kDisplay1Mode60->getId());
mock::SchedulerCallback mSchedulerCallback;
- TestableScheduler* mScheduler = new TestableScheduler{mConfigs, mSchedulerCallback};
+ mock::VsyncTrackerCallback mVsyncTrackerCallback;
+ TestableScheduler* mScheduler =
+ new TestableScheduler{mSelector, mSchedulerCallback, mVsyncTrackerCallback};
+ surfaceflinger::frontend::LayerHierarchyBuilder mLayerHierarchyBuilder{{}};
ConnectionHandle mConnectionHandle;
MockEventThread* mEventThread;
@@ -79,7 +112,7 @@ SchedulerTest::SchedulerTest() {
mEventThread = eventThread.get();
EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
- mEventThreadConnection = new MockEventThreadConnection(mEventThread);
+ mEventThreadConnection = sp<MockEventThreadConnection>::make(mEventThread);
// createConnection call to scheduler makes a createEventConnection call to EventThread. Make
// sure that call gets executed and returns an EventThread::Connection object.
@@ -104,13 +137,7 @@ TEST_F(SchedulerTest, invalidConnectionHandle) {
// The EXPECT_CALLS make sure we don't call the functions on the subsequent event threads.
EXPECT_CALL(*mEventThread, onHotplugReceived(_, _)).Times(0);
- mScheduler->onHotplugReceived(handle, PHYSICAL_DISPLAY_ID, false);
-
- EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(0);
- mScheduler->onScreenAcquired(handle);
-
- EXPECT_CALL(*mEventThread, onScreenReleased()).Times(0);
- mScheduler->onScreenReleased(handle);
+ mScheduler->onHotplugReceived(handle, kDisplayId1, false);
std::string output;
EXPECT_CALL(*mEventThread, dump(_)).Times(0);
@@ -128,14 +155,8 @@ TEST_F(SchedulerTest, validConnectionHandle) {
ASSERT_EQ(mEventThreadConnection, connection);
EXPECT_TRUE(mScheduler->getEventConnection(mConnectionHandle));
- EXPECT_CALL(*mEventThread, onHotplugReceived(PHYSICAL_DISPLAY_ID, false)).Times(1);
- mScheduler->onHotplugReceived(mConnectionHandle, PHYSICAL_DISPLAY_ID, false);
-
- EXPECT_CALL(*mEventThread, onScreenAcquired()).Times(1);
- mScheduler->onScreenAcquired(mConnectionHandle);
-
- EXPECT_CALL(*mEventThread, onScreenReleased()).Times(1);
- mScheduler->onScreenReleased(mConnectionHandle);
+ EXPECT_CALL(*mEventThread, onHotplugReceived(kDisplayId1, false)).Times(1);
+ mScheduler->onHotplugReceived(mConnectionHandle, kDisplayId1, false);
std::string output("dump");
EXPECT_CALL(*mEventThread, dump(output)).Times(1);
@@ -144,10 +165,33 @@ TEST_F(SchedulerTest, validConnectionHandle) {
EXPECT_CALL(*mEventThread, setDuration(10ns, 20ns)).Times(1);
mScheduler->setDuration(mConnectionHandle, 10ns, 20ns);
+}
+
+TEST_F(SchedulerTest, registerDisplay) FTL_FAKE_GUARD(kMainThreadContext) {
+ // Hardware VSYNC should not change if the display is already registered.
+ EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId1, false)).Times(0);
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
- static constexpr size_t kEventConnections = 5;
- EXPECT_CALL(*mEventThread, getEventThreadConnectionCount()).WillOnce(Return(kEventConnections));
- EXPECT_EQ(kEventConnections, mScheduler->getEventThreadConnectionCount(mConnectionHandle));
+ // TODO(b/241285191): Restore once VsyncSchedule::getPendingHardwareVsyncState is called by
+ // Scheduler::setDisplayPowerMode rather than SF::setPowerModeInternal.
+#if 0
+ // Hardware VSYNC should be disabled for newly registered displays.
+ EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId2, false)).Times(1);
+ EXPECT_CALL(mSchedulerCallback, requestHardwareVsync(kDisplayId3, false)).Times(1);
+#endif
+
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
+ mScheduler->registerDisplay(kDisplayId3,
+ std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+ kDisplay3Mode60->getId()));
+
+ EXPECT_FALSE(mScheduler->getVsyncSchedule(kDisplayId1)->getPendingHardwareVsyncState());
+ EXPECT_FALSE(mScheduler->getVsyncSchedule(kDisplayId2)->getPendingHardwareVsyncState());
+ EXPECT_FALSE(mScheduler->getVsyncSchedule(kDisplayId3)->getPendingHardwareVsyncState());
}
TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSupported) {
@@ -156,17 +200,19 @@ TEST_F(SchedulerTest, chooseRefreshRateForContentIsNoopWhenModeSwitchingIsNotSup
// recordLayerHistory should be a noop
ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
- mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
+ mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
+ LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
- mScheduler->setDisplayPowerMode(kPowerModeOn);
+ FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn));
constexpr uint32_t kDisplayArea = 999'999;
mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
- EXPECT_CALL(mSchedulerCallback, requestDisplayMode(_, _)).Times(0);
- mScheduler->chooseRefreshRateForContent();
+ EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0);
+ mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr,
+ /*updateAttachedChoreographer*/ false);
}
TEST_F(SchedulerTest, updateDisplayModes) {
@@ -174,11 +220,14 @@ TEST_F(SchedulerTest, updateDisplayModes) {
sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
ASSERT_EQ(1u, mScheduler->layerHistorySize());
- mScheduler->setRefreshRateConfigs(
- std::make_shared<RefreshRateConfigs>(makeModes(kMode60, kMode120), kMode60->getId()));
+ // Replace `mSelector` with a new `RefreshRateSelector` that has different display modes.
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
ASSERT_EQ(0u, mScheduler->getNumActiveLayers());
- mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
+ mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, 0,
+ LayerHistory::LayerUpdateType::Buffer);
ASSERT_EQ(1u, mScheduler->getNumActiveLayers());
}
@@ -192,14 +241,16 @@ TEST_F(SchedulerTest, dispatchCachedReportedMode) {
TEST_F(SchedulerTest, onNonPrimaryDisplayModeChanged_invalidParameters) {
const auto mode = DisplayMode::Builder(hal::HWConfigId(0))
.setId(DisplayModeId(111))
- .setPhysicalDisplayId(PHYSICAL_DISPLAY_ID)
+ .setPhysicalDisplayId(kDisplayId1)
.setVsyncPeriod(111111)
.build();
// If the handle is incorrect, the function should return before
// onModeChange is called.
ConnectionHandle invalidHandle = {.id = 123};
- EXPECT_NO_FATAL_FAILURE(mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle, mode));
+ EXPECT_NO_FATAL_FAILURE(
+ mScheduler->onNonPrimaryDisplayModeChanged(invalidHandle,
+ {90_Hz, ftl::as_non_null(mode)}));
EXPECT_CALL(*mEventThread, onModeChanged(_)).Times(0);
}
@@ -211,33 +262,769 @@ TEST_F(SchedulerTest, calculateMaxAcquiredBufferCount) {
EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 40ms));
EXPECT_EQ(1, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
+
+ const auto savedMinAcquiredBuffers = mFlinger.mutableMinAcquiredBuffers();
+ mFlinger.mutableMinAcquiredBuffers() = 2;
+ EXPECT_EQ(2, mFlinger.calculateMaxAcquiredBufferCount(60_Hz, 10ms));
+ mFlinger.mutableMinAcquiredBuffers() = savedMinAcquiredBuffers;
}
MATCHER(Is120Hz, "") {
- return isApproxEqual(arg->getFps(), 120_Hz);
+ return isApproxEqual(arg.front().mode.fps, 120_Hz);
}
TEST_F(SchedulerTest, chooseRefreshRateForContentSelectsMaxRefreshRate) {
- mScheduler->setRefreshRateConfigs(
- std::make_shared<RefreshRateConfigs>(makeModes(kMode60, kMode120), kMode60->getId()));
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
EXPECT_CALL(*layer, isVisible()).WillOnce(Return(true));
- mScheduler->recordLayerHistory(layer.get(), 0, LayerHistory::LayerUpdateType::Buffer);
+ mScheduler->recordLayerHistory(layer->getSequence(), layer->getLayerProps(), 0, systemTime(),
+ LayerHistory::LayerUpdateType::Buffer);
constexpr hal::PowerMode kPowerModeOn = hal::PowerMode::ON;
- mScheduler->setDisplayPowerMode(kPowerModeOn);
+ FTL_FAKE_GUARD(kMainThreadContext, mScheduler->setDisplayPowerMode(kDisplayId1, kPowerModeOn));
constexpr uint32_t kDisplayArea = 999'999;
mScheduler->onActiveDisplayAreaChanged(kDisplayArea);
- EXPECT_CALL(mSchedulerCallback, requestDisplayMode(Is120Hz(), _)).Times(1);
- mScheduler->chooseRefreshRateForContent();
+ EXPECT_CALL(mSchedulerCallback, requestDisplayModes(Is120Hz())).Times(1);
+ mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr,
+ /*updateAttachedChoreographer*/ false);
// No-op if layer requirements have not changed.
- EXPECT_CALL(mSchedulerCallback, requestDisplayMode(_, _)).Times(0);
- mScheduler->chooseRefreshRateForContent();
+ EXPECT_CALL(mSchedulerCallback, requestDisplayModes(_)).Times(0);
+ mScheduler->chooseRefreshRateForContent(/*LayerHierarchy*/ nullptr,
+ /*updateAttachedChoreographer*/ false);
+}
+
+TEST_F(SchedulerTest, chooseDisplayModesSingleDisplay) {
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
+
+ std::vector<RefreshRateSelector::LayerRequirement> layers =
+ std::vector<RefreshRateSelector::LayerRequirement>({{.weight = 1.f}, {.weight = 1.f}});
+ mScheduler->setContentRequirements(layers);
+ GlobalSignals globalSignals = {.idle = true};
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+
+ auto modeChoices = mScheduler->chooseDisplayModes();
+ ASSERT_EQ(1u, modeChoices.size());
+
+ auto choice = modeChoices.get(kDisplayId1);
+ ASSERT_TRUE(choice);
+ EXPECT_EQ(choice->get(), DisplayModeChoice({60_Hz, kDisplay1Mode60}, globalSignals));
+
+ globalSignals = {.idle = false};
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ modeChoices = mScheduler->chooseDisplayModes();
+ ASSERT_EQ(1u, modeChoices.size());
+
+ choice = modeChoices.get(kDisplayId1);
+ ASSERT_TRUE(choice);
+ EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
+
+ globalSignals = {.touch = true};
+ mScheduler->replaceTouchTimer(10);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ modeChoices = mScheduler->chooseDisplayModes();
+ ASSERT_EQ(1u, modeChoices.size());
+
+ choice = modeChoices.get(kDisplayId1);
+ ASSERT_TRUE(choice);
+ EXPECT_EQ(choice->get(), DisplayModeChoice({120_Hz, kDisplay1Mode120}, globalSignals));
+}
+
+TEST_F(SchedulerTest, chooseDisplayModesMultipleDisplays) {
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
+
+ using DisplayModeChoice = TestableScheduler::DisplayModeChoice;
+ TestableScheduler::DisplayModeChoiceMap expectedChoices;
+
+ {
+ const GlobalSignals globalSignals = {.idle = true};
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1,
+ FrameRateMode{60_Hz, kDisplay1Mode60},
+ globalSignals)(kDisplayId2,
+ FrameRateMode{60_Hz,
+ kDisplay2Mode60},
+ globalSignals);
+
+ std::vector<RefreshRateSelector::LayerRequirement> layers = {{.weight = 1.f},
+ {.weight = 1.f}};
+ mScheduler->setContentRequirements(layers);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
+ }
+ {
+ const GlobalSignals globalSignals = {.idle = false};
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1,
+ FrameRateMode{120_Hz, kDisplay1Mode120},
+ globalSignals)(kDisplayId2,
+ FrameRateMode{120_Hz,
+ kDisplay2Mode120},
+ globalSignals);
+
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
+ }
+ {
+ const GlobalSignals globalSignals = {.touch = true};
+ mScheduler->replaceTouchTimer(10);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ expectedChoices =
+ ftl::init::map<const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1,
+ FrameRateMode{120_Hz, kDisplay1Mode120},
+ globalSignals)(kDisplayId2,
+ FrameRateMode{120_Hz,
+ kDisplay2Mode120},
+ globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
+ }
+ {
+ // The kDisplayId3 does not support 120Hz, The pacesetter display rate is chosen to be 120
+ // Hz. In this case only the display kDisplayId3 choose 60Hz as it does not support 120Hz.
+ mScheduler
+ ->registerDisplay(kDisplayId3,
+ std::make_shared<RefreshRateSelector>(kDisplay3Modes,
+ kDisplay3Mode60->getId()));
+
+ const GlobalSignals globalSignals = {.touch = true};
+ mScheduler->replaceTouchTimer(10);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ expectedChoices = ftl::init::map<
+ const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1, FrameRateMode{120_Hz, kDisplay1Mode120},
+ globalSignals)(kDisplayId2,
+ FrameRateMode{120_Hz, kDisplay2Mode120},
+ globalSignals)(kDisplayId3,
+ FrameRateMode{60_Hz,
+ kDisplay3Mode60},
+ globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
+ }
+ {
+ // We should choose 60Hz despite the touch signal as pacesetter only supports 60Hz
+ mScheduler->setPacesetterDisplay(kDisplayId3);
+ const GlobalSignals globalSignals = {.touch = true};
+ mScheduler->replaceTouchTimer(10);
+ mScheduler->setTouchStateAndIdleTimerPolicy(globalSignals);
+
+ expectedChoices = ftl::init::map<
+ const PhysicalDisplayId&,
+ DisplayModeChoice>(kDisplayId1, FrameRateMode{60_Hz, kDisplay1Mode60},
+ globalSignals)(kDisplayId2,
+ FrameRateMode{60_Hz, kDisplay2Mode60},
+ globalSignals)(kDisplayId3,
+ FrameRateMode{60_Hz,
+ kDisplay3Mode60},
+ globalSignals);
+
+ const auto actualChoices = mScheduler->chooseDisplayModes();
+ EXPECT_EQ(expectedChoices, actualChoices);
+ }
+}
+
+TEST_F(SchedulerTest, onFrameSignalMultipleDisplays) {
+ mScheduler->registerDisplay(kDisplayId1,
+ std::make_shared<RefreshRateSelector>(kDisplay1Modes,
+ kDisplay1Mode60->getId()));
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
+
+ using VsyncIds = std::vector<std::pair<PhysicalDisplayId, VsyncId>>;
+
+ struct Compositor final : ICompositor {
+ explicit Compositor(TestableScheduler& scheduler) : scheduler(scheduler) {}
+
+ TestableScheduler& scheduler;
+
+ struct {
+ PhysicalDisplayId commit;
+ PhysicalDisplayId composite;
+ } pacesetterIds;
+
+ struct {
+ VsyncIds commit;
+ VsyncIds composite;
+ } vsyncIds;
+
+ bool committed = true;
+ bool changePacesetter = false;
+
+ void configure() override {}
+
+ bool commit(PhysicalDisplayId pacesetterId,
+ const scheduler::FrameTargets& targets) override {
+ pacesetterIds.commit = pacesetterId;
+
+ vsyncIds.commit.clear();
+ vsyncIds.composite.clear();
+
+ for (const auto& [id, target] : targets) {
+ vsyncIds.commit.emplace_back(id, target->vsyncId());
+ }
+
+ if (changePacesetter) {
+ scheduler.setPacesetterDisplay(kDisplayId2);
+ }
+
+ return committed;
+ }
+
+ CompositeResultsPerDisplay composite(PhysicalDisplayId pacesetterId,
+ const scheduler::FrameTargeters& targeters) override {
+ pacesetterIds.composite = pacesetterId;
+
+ CompositeResultsPerDisplay results;
+
+ for (const auto& [id, targeter] : targeters) {
+ vsyncIds.composite.emplace_back(id, targeter->target().vsyncId());
+ results.try_emplace(id,
+ CompositeResult{.compositionCoverage =
+ CompositionCoverage::Hwc});
+ }
+
+ return results;
+ }
+
+ void sample() override {}
+ } compositor(*mScheduler);
+
+ mScheduler->doFrameSignal(compositor, VsyncId(42));
+
+ const auto makeVsyncIds = [](VsyncId vsyncId, bool swap = false) -> VsyncIds {
+ if (swap) {
+ return {{kDisplayId2, vsyncId}, {kDisplayId1, vsyncId}};
+ } else {
+ return {{kDisplayId1, vsyncId}, {kDisplayId2, vsyncId}};
+ }
+ };
+
+ EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit);
+ EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.composite);
+ EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds.commit);
+ EXPECT_EQ(makeVsyncIds(VsyncId(42)), compositor.vsyncIds.composite);
+
+ // FrameTargets should be updated despite the skipped commit.
+ compositor.committed = false;
+ mScheduler->doFrameSignal(compositor, VsyncId(43));
+
+ EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit);
+ EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.composite);
+ EXPECT_EQ(makeVsyncIds(VsyncId(43)), compositor.vsyncIds.commit);
+ EXPECT_TRUE(compositor.vsyncIds.composite.empty());
+
+ // The pacesetter may change during commit.
+ compositor.committed = true;
+ compositor.changePacesetter = true;
+ mScheduler->doFrameSignal(compositor, VsyncId(44));
+
+ EXPECT_EQ(kDisplayId1, compositor.pacesetterIds.commit);
+ EXPECT_EQ(kDisplayId2, compositor.pacesetterIds.composite);
+ EXPECT_EQ(makeVsyncIds(VsyncId(44)), compositor.vsyncIds.commit);
+ EXPECT_EQ(makeVsyncIds(VsyncId(44), true), compositor.vsyncIds.composite);
+}
+
+TEST_F(SchedulerTest, nextFrameIntervalTest) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ static constexpr size_t kHistorySize = 10;
+ static constexpr size_t kMinimumSamplesForPrediction = 6;
+ static constexpr size_t kOutlierTolerancePercent = 25;
+ const auto refreshRate = Fps::fromPeriodNsecs(500);
+ auto frameRate = Fps::fromPeriodNsecs(1000);
+
+ const ftl::NonNull<DisplayModePtr> kMode = ftl::as_non_null(
+ createVrrDisplayMode(DisplayModeId(0), refreshRate,
+ hal::VrrConfig{.minFrameIntervalNs = static_cast<int32_t>(
+ frameRate.getPeriodNsecs())}));
+ std::shared_ptr<VSyncPredictor> vrrTracker =
+ std::make_shared<VSyncPredictor>(kMode, kHistorySize, kMinimumSamplesForPrediction,
+ kOutlierTolerancePercent, mVsyncTrackerCallback);
+ std::shared_ptr<RefreshRateSelector> vrrSelectorPtr =
+ std::make_shared<RefreshRateSelector>(makeModes(kMode), kMode->getId());
+ TestableScheduler scheduler{std::make_unique<android::mock::VsyncController>(),
+ vrrTracker,
+ vrrSelectorPtr,
+ sp<VsyncModulator>::make(VsyncConfigSet{}),
+ mSchedulerCallback,
+ mVsyncTrackerCallback};
+
+ scheduler.registerDisplay(kMode->getPhysicalDisplayId(), vrrSelectorPtr, vrrTracker);
+ vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
+ scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+ vrrTracker->addVsyncTimestamp(0);
+
+ // Next frame at refresh rate as no previous frame
+ EXPECT_EQ(refreshRate,
+ scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(), TimePoint::fromNs(0)));
+
+ EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+ scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+ TimePoint::fromNs(500)));
+ EXPECT_EQ(Fps::fromPeriodNsecs(1000),
+ scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+ TimePoint::fromNs(1500)));
+
+ // Change render rate
+ frameRate = Fps::fromPeriodNsecs(2000);
+ vrrSelectorPtr->setActiveMode(kMode->getId(), frameRate);
+ scheduler.setRenderRate(kMode->getPhysicalDisplayId(), frameRate);
+
+ EXPECT_EQ(Fps::fromPeriodNsecs(2000),
+ scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+ TimePoint::fromNs(2500)));
+ EXPECT_EQ(Fps::fromPeriodNsecs(2000),
+ scheduler.getNextFrameInterval(kMode->getPhysicalDisplayId(),
+ TimePoint::fromNs(4500)));
+}
+
+TEST_F(SchedulerTest, resyncAllToHardwareVsync) FTL_FAKE_GUARD(kMainThreadContext) {
+ // resyncAllToHardwareVsync will result in requesting hardware VSYNC on both displays, since
+ // they are both on.
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, true)).Times(1);
+
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
+ mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+ mScheduler->setDisplayPowerMode(kDisplayId2, hal::PowerMode::ON);
+
+ static constexpr bool kDisallow = true;
+ mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+ mScheduler->disableHardwareVsync(kDisplayId2, kDisallow);
+
+ static constexpr bool kAllowToEnable = true;
+ mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+TEST_F(SchedulerTest, resyncAllDoNotAllow) FTL_FAKE_GUARD(kMainThreadContext) {
+ // Without setting allowToEnable to true, resyncAllToHardwareVsync does not
+ // result in requesting hardware VSYNC.
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, _)).Times(0);
+
+ mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+
+ static constexpr bool kDisallow = true;
+ mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+
+ static constexpr bool kAllowToEnable = false;
+ mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+TEST_F(SchedulerTest, resyncAllSkipsOffDisplays) FTL_FAKE_GUARD(kMainThreadContext) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+
+ // resyncAllToHardwareVsync will result in requesting hardware VSYNC on display 1, which is on,
+ // but not on display 2, which is off.
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, _)).Times(0);
+
+ mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
+ ASSERT_EQ(hal::PowerMode::OFF, mScheduler->getDisplayPowerMode(kDisplayId2));
+
+ static constexpr bool kDisallow = true;
+ mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+ mScheduler->disableHardwareVsync(kDisplayId2, kDisallow);
+
+ static constexpr bool kAllowToEnable = true;
+ mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+TEST_F(SchedulerTest, resyncAllLegacyAppliesToOffDisplays) FTL_FAKE_GUARD(kMainThreadContext) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, false);
+
+ // In the legacy code, prior to the flag, resync applied to OFF displays.
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId1, true)).Times(1);
+ EXPECT_CALL(mScheduler->mockRequestHardwareVsync, Call(kDisplayId2, true)).Times(1);
+
+ mScheduler->setDisplayPowerMode(kDisplayId1, hal::PowerMode::ON);
+
+ mScheduler->registerDisplay(kDisplayId2,
+ std::make_shared<RefreshRateSelector>(kDisplay2Modes,
+ kDisplay2Mode60->getId()));
+ ASSERT_EQ(hal::PowerMode::OFF, mScheduler->getDisplayPowerMode(kDisplayId2));
+
+ static constexpr bool kDisallow = true;
+ mScheduler->disableHardwareVsync(kDisplayId1, kDisallow);
+ mScheduler->disableHardwareVsync(kDisplayId2, kDisallow);
+
+ static constexpr bool kAllowToEnable = true;
+ mScheduler->resyncAllToHardwareVsync(kAllowToEnable);
+}
+
+class AttachedChoreographerTest : public SchedulerTest {
+protected:
+ void frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility, Fps displayFps,
+ Fps expectedChoreographerFps);
+};
+
+TEST_F(AttachedChoreographerTest, registerSingle) {
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ const sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+ EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size());
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+ EXPECT_EQ(1u,
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size());
+ EXPECT_FALSE(
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate.isValid());
+}
+
+TEST_F(AttachedChoreographerTest, registerMultipleOnSameLayer) {
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+ const auto handle = layer->getHandle();
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2);
+
+ EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_))
+ .WillOnce(Return(0))
+ .WillOnce(Return(0));
+
+ const auto mockConnection1 = sp<MockEventThreadConnection>::make(mEventThread);
+ const auto mockConnection2 = sp<MockEventThreadConnection>::make(mEventThread);
+ EXPECT_CALL(*mEventThread, createEventConnection(_, _))
+ .WillOnce(Return(mockConnection1))
+ .WillOnce(Return(mockConnection2));
+
+ const sp<IDisplayEventConnection> connection1 =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, handle);
+ const sp<IDisplayEventConnection> connection2 =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, handle);
+
+ EXPECT_EQ(1u, mScheduler->mutableAttachedChoreographers().size());
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+ EXPECT_EQ(2u,
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size());
+ EXPECT_FALSE(
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate.isValid());
+}
+
+TEST_F(AttachedChoreographerTest, registerMultipleOnDifferentLayers) {
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+ const sp<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached).Times(2);
+ const sp<IDisplayEventConnection> connection1 =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer1->getHandle());
+ const sp<IDisplayEventConnection> connection2 =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer2->getHandle());
+
+ EXPECT_EQ(2u, mScheduler->mutableAttachedChoreographers().size());
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer1->getSequence()));
+ EXPECT_EQ(1u,
+ mScheduler->mutableAttachedChoreographers()[layer1->getSequence()]
+ .connections.size());
+ EXPECT_FALSE(
+ mScheduler->mutableAttachedChoreographers()[layer1->getSequence()].frameRate.isValid());
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer2->getSequence()));
+ EXPECT_EQ(1u,
+ mScheduler->mutableAttachedChoreographers()[layer2->getSequence()]
+ .connections.size());
+ EXPECT_FALSE(
+ mScheduler->mutableAttachedChoreographers()[layer2->getSequence()].frameRate.isValid());
+}
+
+TEST_F(AttachedChoreographerTest, removedWhenConnectionIsGone) {
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+ EXPECT_EQ(1u,
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.size());
+
+ // The connection is used all over this test, so it is quite hard to release it from here.
+ // Instead, we just do a small shortcut.
+ {
+ EXPECT_CALL(*mEventThread, registerDisplayEventConnection(_)).WillOnce(Return(0));
+ sp<MockEventThreadConnection> mockConnection =
+ sp<MockEventThreadConnection>::make(mEventThread);
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.clear();
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].connections.emplace(
+ mockConnection);
+ }
+
+ RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+ LayerHierarchy hierarchy(&layerState);
+ mScheduler->updateAttachedChoreographers(hierarchy, 60_Hz);
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+}
+
+TEST_F(AttachedChoreographerTest, removedWhenLayerIsGone) {
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+
+ sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ const sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+ layer.clear();
+ mFlinger.mutableLayersPendingRemoval().clear();
+ EXPECT_TRUE(mScheduler->mutableAttachedChoreographers().empty());
+}
+
+void AttachedChoreographerTest::frameRateTestScenario(Fps layerFps, int8_t frameRateCompatibility,
+ Fps displayFps,
+ Fps expectedChoreographerFps) {
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+ RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+ LayerHierarchy hierarchy(&layerState);
+
+ layerState.frameRate = layerFps.getValue();
+ layerState.frameRateCompatibility = frameRateCompatibility;
+
+ mScheduler->updateAttachedChoreographers(hierarchy, displayFps);
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+ EXPECT_EQ(expectedChoreographerFps,
+ mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate);
+ EXPECT_EQ(expectedChoreographerFps, mEventThreadConnection->frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateDefault) {
+ Fps layerFps = 30_Hz;
+ int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+ Fps displayFps = 60_Hz;
+ Fps expectedChoreographerFps = 30_Hz;
+
+ frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+
+ layerFps = Fps::fromValue(32.7f);
+ frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateExact) {
+ Fps layerFps = 30_Hz;
+ int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_EXACT;
+ Fps displayFps = 60_Hz;
+ Fps expectedChoreographerFps = 30_Hz;
+
+ frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+
+ layerFps = Fps::fromValue(32.7f);
+ expectedChoreographerFps = {};
+ frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateExactOrMultiple) {
+ Fps layerFps = 30_Hz;
+ int8_t frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE;
+ Fps displayFps = 60_Hz;
+ Fps expectedChoreographerFps = 30_Hz;
+
+ frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+
+ layerFps = Fps::fromValue(32.7f);
+ expectedChoreographerFps = {};
+ frameRateTestScenario(layerFps, frameRateCompatibility, displayFps, expectedChoreographerFps);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateParent) {
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+
+ RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+ LayerHierarchy parentHierarchy(&parentState);
+
+ RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+ LayerHierarchy hierarchy(&layerState);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached));
+
+ layerState.frameRate = (30_Hz).getValue();
+ layerState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence()));
+
+ EXPECT_EQ(30_Hz, mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateParent2Children) {
+ const sp<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+
+ RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+ LayerHierarchy parentHierarchy(&parentState);
+
+ RequestedLayerState layer1State(LayerCreationArgs(layer1->getSequence()));
+ LayerHierarchy layer1Hierarchy(&layer1State);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&layer1Hierarchy, LayerHierarchy::Variant::Attached));
+
+ RequestedLayerState layer2State(LayerCreationArgs(layer1->getSequence()));
+ LayerHierarchy layer2Hierarchy(&layer2State);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&layer2Hierarchy, LayerHierarchy::Variant::Attached));
+
+ layer1State.frameRate = (30_Hz).getValue();
+ layer1State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ layer2State.frameRate = (20_Hz).getValue();
+ layer2State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence()));
+
+ EXPECT_EQ(60_Hz, mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateParentConflictingChildren) {
+ const sp<MockLayer> layer1 = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> layer2 = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, parent->getHandle());
+
+ RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+ LayerHierarchy parentHierarchy(&parentState);
+
+ RequestedLayerState layer1State(LayerCreationArgs(layer1->getSequence()));
+ LayerHierarchy layer1Hierarchy(&layer1State);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&layer1Hierarchy, LayerHierarchy::Variant::Attached));
+
+ RequestedLayerState layer2State(LayerCreationArgs(layer1->getSequence()));
+ LayerHierarchy layer2Hierarchy(&layer2State);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&layer2Hierarchy, LayerHierarchy::Variant::Attached));
+
+ layer1State.frameRate = (30_Hz).getValue();
+ layer1State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ layer2State.frameRate = (25_Hz).getValue();
+ layer2State.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(parent->getSequence()));
+
+ EXPECT_EQ(Fps(), mScheduler->mutableAttachedChoreographers()[parent->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateChild) {
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+ RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+ LayerHierarchy parentHierarchy(&parentState);
+
+ RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+ LayerHierarchy hierarchy(&layerState);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached));
+
+ parentState.frameRate = (30_Hz).getValue();
+ parentState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+
+ EXPECT_EQ(30_Hz, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate);
+}
+
+TEST_F(AttachedChoreographerTest, setsFrameRateChildNotOverriddenByParent) {
+ const sp<MockLayer> layer = sp<MockLayer>::make(mFlinger.flinger());
+ const sp<MockLayer> parent = sp<MockLayer>::make(mFlinger.flinger());
+
+ EXPECT_CALL(mSchedulerCallback, onChoreographerAttached);
+ sp<IDisplayEventConnection> connection =
+ mScheduler->createDisplayEventConnection(mConnectionHandle, {}, layer->getHandle());
+
+ RequestedLayerState parentState(LayerCreationArgs(parent->getSequence()));
+ LayerHierarchy parentHierarchy(&parentState);
+
+ RequestedLayerState layerState(LayerCreationArgs(layer->getSequence()));
+ LayerHierarchy hierarchy(&layerState);
+ parentHierarchy.mChildren.push_back(
+ std::make_pair(&hierarchy, LayerHierarchy::Variant::Attached));
+
+ parentState.frameRate = (30_Hz).getValue();
+ parentState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ layerState.frameRate = (60_Hz).getValue();
+ layerState.frameRateCompatibility = ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT;
+
+ mScheduler->updateAttachedChoreographers(parentHierarchy, 120_Hz);
+
+ ASSERT_EQ(1u, mScheduler->mutableAttachedChoreographers().count(layer->getSequence()));
+
+ EXPECT_EQ(60_Hz, mScheduler->mutableAttachedChoreographers()[layer->getSequence()].frameRate);
}
} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
index b9a5f36794..9899d4290b 100644
--- a/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SetFrameRateTest.cpp
@@ -19,13 +19,12 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <gui/FrameRateUtils.h>
#include <gui/LayerMetadata.h>
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
-#include "BufferStateLayer.h"
-#include "EffectLayer.h"
#include "Layer.h"
// TODO(b/129481165): remove the #pragma below and fix conversion issues
#pragma clang diagnostic pop // ignored "-Wconversion"
@@ -78,17 +77,16 @@ SetFrameRateTest::SetFrameRateTest() {
}
void SetFrameRateTest::addChild(sp<Layer> layer, sp<Layer> child) {
- layer.get()->addChild(child.get());
+ layer->addChild(child);
}
void SetFrameRateTest::removeChild(sp<Layer> layer, sp<Layer> child) {
- layer.get()->removeChild(child.get());
+ layer->removeChild(child);
}
void SetFrameRateTest::commitTransaction() {
for (auto layer : mLayers) {
- auto c = layer->getDrawingState();
- layer->commitTransaction(c);
+ layer->commitTransaction();
}
}
@@ -100,7 +98,7 @@ TEST_P(SetFrameRateTest, SetAndGet) {
const auto& layerFactory = GetParam();
auto layer = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
- layer->setFrameRate(FRAME_RATE_VOTE1);
+ layer->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE1, layer->getFrameRateForLayerTree());
}
@@ -117,13 +115,13 @@ TEST_P(SetFrameRateTest, SetAndGetParent) {
addChild(parent, child1);
addChild(child1, child2);
- child2->setFrameRate(FRAME_RATE_VOTE1);
+ child2->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- child2->setFrameRate(FRAME_RATE_NO_VOTE);
+ child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -142,27 +140,27 @@ TEST_P(SetFrameRateTest, SetAndGetParentAllVote) {
addChild(parent, child1);
addChild(child1, child2);
- child2->setFrameRate(FRAME_RATE_VOTE1);
- child1->setFrameRate(FRAME_RATE_VOTE2);
- parent->setFrameRate(FRAME_RATE_VOTE3);
+ child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+ child1->setFrameRate(FRAME_RATE_VOTE2.vote);
+ parent->setFrameRate(FRAME_RATE_VOTE3.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- child2->setFrameRate(FRAME_RATE_NO_VOTE);
+ child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE2, child2->getFrameRateForLayerTree());
- child1->setFrameRate(FRAME_RATE_NO_VOTE);
+ child1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE3, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE3, child2->getFrameRateForLayerTree());
- parent->setFrameRate(FRAME_RATE_NO_VOTE);
+ parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -181,13 +179,13 @@ TEST_P(SetFrameRateTest, SetAndGetChild) {
addChild(parent, child1);
addChild(child1, child2);
- parent->setFrameRate(FRAME_RATE_VOTE1);
+ parent->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- parent->setFrameRate(FRAME_RATE_NO_VOTE);
+ parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -206,27 +204,27 @@ TEST_P(SetFrameRateTest, SetAndGetChildAllVote) {
addChild(parent, child1);
addChild(child1, child2);
- child2->setFrameRate(FRAME_RATE_VOTE1);
- child1->setFrameRate(FRAME_RATE_VOTE2);
- parent->setFrameRate(FRAME_RATE_VOTE3);
+ child2->setFrameRate(FRAME_RATE_VOTE1.vote);
+ child1->setFrameRate(FRAME_RATE_VOTE2.vote);
+ parent->setFrameRate(FRAME_RATE_VOTE3.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE3, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- parent->setFrameRate(FRAME_RATE_NO_VOTE);
+ parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE2, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- child1->setFrameRate(FRAME_RATE_NO_VOTE);
+ child1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- child2->setFrameRate(FRAME_RATE_NO_VOTE);
+ child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -244,7 +242,7 @@ TEST_P(SetFrameRateTest, SetAndGetChildAddAfterVote) {
addChild(parent, child1);
- parent->setFrameRate(FRAME_RATE_VOTE1);
+ parent->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
@@ -256,7 +254,7 @@ TEST_P(SetFrameRateTest, SetAndGetChildAddAfterVote) {
EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
- parent->setFrameRate(FRAME_RATE_NO_VOTE);
+ parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -275,7 +273,7 @@ TEST_P(SetFrameRateTest, SetAndGetChildRemoveAfterVote) {
addChild(parent, child1);
addChild(child1, child2);
- parent->setFrameRate(FRAME_RATE_VOTE1);
+ parent->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_VOTE1, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
@@ -287,7 +285,7 @@ TEST_P(SetFrameRateTest, SetAndGetChildRemoveAfterVote) {
EXPECT_EQ(FRAME_RATE_VOTE1, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
- parent->setFrameRate(FRAME_RATE_NO_VOTE);
+ parent->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -308,14 +306,14 @@ TEST_P(SetFrameRateTest, SetAndGetParentNotInTree) {
addChild(child1, child2);
addChild(child1, child2_1);
- child2->setFrameRate(FRAME_RATE_VOTE1);
+ child2->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_VOTE1, child2->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child2_1->getFrameRateForLayerTree());
- child2->setFrameRate(FRAME_RATE_NO_VOTE);
+ child2->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
@@ -328,74 +326,29 @@ INSTANTIATE_TEST_SUITE_P(PerLayerType, SetFrameRateTest,
std::make_shared<EffectLayerFactory>()),
PrintToStringParamName);
-TEST_F(SetFrameRateTest, ValidateFrameRate) {
- EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS, ""));
- EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
- // Privileged APIs.
- EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_FALSE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
- constexpr bool kPrivileged = true;
- EXPECT_TRUE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
- kPrivileged));
- EXPECT_TRUE(ValidateFrameRate(0.0f, ANATIVEWINDOW_FRAME_RATE_NO_VOTE,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, "",
- kPrivileged));
-
- // Invalid frame rate.
- EXPECT_FALSE(ValidateFrameRate(-1, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_FALSE(ValidateFrameRate(1.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_FALSE(ValidateFrameRate(0.0f / 0.0f, ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT,
- ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
- // Invalid compatibility.
- EXPECT_FALSE(
- ValidateFrameRate(60.0f, -1, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
- EXPECT_FALSE(ValidateFrameRate(60.0f, 2, ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS, ""));
-
- // Invalid change frame rate strategy.
- EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, -1, ""));
- EXPECT_FALSE(ValidateFrameRate(60.0f, ANATIVEWINDOW_FRAME_RATE_EXACT, 2, ""));
-}
-
TEST_P(SetFrameRateTest, SetOnParentActivatesTree) {
const auto& layerFactory = GetParam();
auto parent = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
- if (!parent->isVisible()) {
- // This is a hack as all the test layers except EffectLayer are not visible,
- // but since the logic is unified in Layer, it should be fine.
- return;
- }
auto child = mLayers.emplace_back(layerFactory->createLayer(mFlinger));
addChild(parent, child);
- parent->setFrameRate(FRAME_RATE_VOTE1);
+ parent->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
auto& history = mFlinger.mutableScheduler().mutableLayerHistory();
- history.record(parent.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
- history.record(child.get(), 0, 0, LayerHistory::LayerUpdateType::Buffer);
+ history.record(parent->getSequence(), parent->getLayerProps(), 0, 0,
+ LayerHistory::LayerUpdateType::Buffer);
+ history.record(child->getSequence(), child->getLayerProps(), 0, 0,
+ LayerHistory::LayerUpdateType::Buffer);
- const auto configs = mFlinger.mutableScheduler().refreshRateConfigs();
- const auto summary = history.summarize(*configs, 0);
+ const auto selectorPtr = mFlinger.mutableScheduler().refreshRateSelector();
+ const auto summary = history.summarize(*selectorPtr, 0);
ASSERT_EQ(2u, summary.size());
- EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[0].desiredRefreshRate);
- EXPECT_EQ(FRAME_RATE_VOTE1.rate, summary[1].desiredRefreshRate);
+ EXPECT_EQ(FRAME_RATE_VOTE1.vote.rate, summary[0].desiredRefreshRate);
+ EXPECT_EQ(FRAME_RATE_VOTE1.vote.rate, summary[1].desiredRefreshRate);
}
TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
@@ -411,7 +364,7 @@ TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
addChild(parent, child1);
addChild(child1, childOfChild1);
- childOfChild1->setFrameRate(FRAME_RATE_VOTE1);
+ childOfChild1->setFrameRate(FRAME_RATE_VOTE1.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_TREE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_TREE, child1->getFrameRateForLayerTree());
@@ -425,7 +378,7 @@ TEST_P(SetFrameRateTest, addChildForParentWithTreeVote) {
EXPECT_EQ(FRAME_RATE_VOTE1, childOfChild1->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child2->getFrameRateForLayerTree());
- childOfChild1->setFrameRate(FRAME_RATE_NO_VOTE);
+ childOfChild1->setFrameRate(FRAME_RATE_NO_VOTE.vote);
commitTransaction();
EXPECT_EQ(FRAME_RATE_NO_VOTE, parent->getFrameRateForLayerTree());
EXPECT_EQ(FRAME_RATE_NO_VOTE, child1->getFrameRateForLayerTree());
diff --git a/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
new file mode 100644
index 0000000000..8615035483
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SmallAreaDetectionAllowMappingsTest.cpp
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SmallAreaDetectionAllowMappingsTest"
+
+#include <gtest/gtest.h>
+
+#include "Scheduler/SmallAreaDetectionAllowMappings.h"
+
+namespace android::scheduler {
+
+class SmallAreaDetectionAllowMappingsTest : public testing::Test {
+protected:
+ SmallAreaDetectionAllowMappings mMappings;
+ static constexpr int32_t kAppId1 = 10100;
+ static constexpr int32_t kAppId2 = 10101;
+ static constexpr float kThreshold1 = 0.05f;
+ static constexpr float kThreshold2 = 0.07f;
+};
+
+namespace {
+TEST_F(SmallAreaDetectionAllowMappingsTest, testUpdate) {
+ std::vector<std::pair<int32_t, float>> mappings;
+ mappings.reserve(2);
+ mappings.push_back(std::make_pair(kAppId1, kThreshold1));
+ mappings.push_back(std::make_pair(kAppId2, kThreshold2));
+
+ mMappings.update(mappings);
+ ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1).value(), kThreshold1);
+ ASSERT_EQ(mMappings.getThresholdForAppId(kAppId2).value(), kThreshold2);
+}
+
+TEST_F(SmallAreaDetectionAllowMappingsTest, testSetThresholdForAppId) {
+ mMappings.setThresholdForAppId(kAppId1, kThreshold1);
+ ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), kThreshold1);
+}
+
+TEST_F(SmallAreaDetectionAllowMappingsTest, testAppIdNotInTheMappings) {
+ ASSERT_EQ(mMappings.getThresholdForAppId(kAppId1), std::nullopt);
+}
+
+} // namespace
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
index f7d34ac5da..28162f4d6b 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_CreateDisplayTest.cpp
@@ -17,12 +17,60 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
+#include <scheduler/Fps.h>
+
#include "DisplayTransactionTestHelpers.h"
+#include "FpsOps.h"
namespace android {
namespace {
-class CreateDisplayTest : public DisplayTransactionTest {};
+class CreateDisplayTest : public DisplayTransactionTest {
+public:
+ void createDisplayWithRequestedRefreshRate(const String8& name, uint64_t displayId,
+ float pacesetterDisplayRefreshRate,
+ float requestedRefreshRate,
+ float expectedAdjustedRefreshRate) {
+ // --------------------------------------------------------------------
+ // Call Expectations
+
+ // --------------------------------------------------------------------
+ // Invocation
+
+ sp<IBinder> displayToken = mFlinger.createDisplay(name, false, requestedRefreshRate);
+
+ // --------------------------------------------------------------------
+ // Postconditions
+
+ // The display should have been added to the current state
+ ASSERT_TRUE(hasCurrentDisplayState(displayToken));
+ const auto& display = getCurrentDisplayState(displayToken);
+ EXPECT_TRUE(display.isVirtual());
+ EXPECT_EQ(display.requestedRefreshRate, Fps::fromValue(requestedRefreshRate));
+ EXPECT_EQ(name.c_str(), display.displayName);
+
+ std::optional<VirtualDisplayId> vid =
+ DisplayId::fromValue<VirtualDisplayId>(displayId | DisplayId::FLAG_VIRTUAL);
+ ASSERT_TRUE(vid.has_value());
+
+ sp<DisplayDevice> device =
+ mFlinger.createVirtualDisplayDevice(displayToken, *vid, requestedRefreshRate);
+ EXPECT_TRUE(device->isVirtual());
+ device->adjustRefreshRate(Fps::fromValue(pacesetterDisplayRefreshRate));
+ // verifying desired value
+ EXPECT_EQ(device->getAdjustedRefreshRate(), Fps::fromValue(expectedAdjustedRefreshRate));
+ // verifying rounding up
+ if (requestedRefreshRate < pacesetterDisplayRefreshRate) {
+ EXPECT_GE(device->getAdjustedRefreshRate(), Fps::fromValue(requestedRefreshRate));
+ } else {
+ EXPECT_EQ(device->getAdjustedRefreshRate(),
+ Fps::fromValue(pacesetterDisplayRefreshRate));
+ }
+
+ // --------------------------------------------------------------------
+ // Cleanup conditions
+ }
+};
TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) {
const String8 name("virtual.test");
@@ -30,9 +78,6 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) {
// --------------------------------------------------------------------
// Call Expectations
- // The call should notify the interceptor that a display was created.
- EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1);
-
// --------------------------------------------------------------------
// Invocation
@@ -46,7 +91,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForNonsecureDisplay) {
const auto& display = getCurrentDisplayState(displayToken);
EXPECT_TRUE(display.isVirtual());
EXPECT_FALSE(display.isSecure);
- EXPECT_EQ(name.string(), display.displayName);
+ EXPECT_EQ(name.c_str(), display.displayName);
// --------------------------------------------------------------------
// Cleanup conditions
@@ -61,9 +106,6 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
// --------------------------------------------------------------------
// Call Expectations
- // The call should notify the interceptor that a display was created.
- EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1);
-
// --------------------------------------------------------------------
// Invocation
int64_t oldId = IPCThreadState::self()->clearCallingIdentity();
@@ -81,7 +123,7 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
const auto& display = getCurrentDisplayState(displayToken);
EXPECT_TRUE(display.isVirtual());
EXPECT_TRUE(display.isSecure);
- EXPECT_EQ(name.string(), display.displayName);
+ EXPECT_EQ(name.c_str(), display.displayName);
// --------------------------------------------------------------------
// Cleanup conditions
@@ -90,5 +132,82 @@ TEST_F(CreateDisplayTest, createDisplaySetsCurrentStateForSecureDisplay) {
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
}
+// Requesting 0 tells SF not to do anything, i.e., default to refresh as physical displays
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRate0) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 60.f;
+ const float kRequestedRefreshRate = 0.f;
+ const float kExpectedAdjustedRefreshRate = 0.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting negative refresh rate, will be ignored, same as requesting 0
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNegative) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 60.f;
+ const float kRequestedRefreshRate = -60.f;
+ const float kExpectedAdjustedRefreshRate = 0.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a higher refresh rate than the pacesetter
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateHigh) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 60.f;
+ const float kRequestedRefreshRate = 90.f;
+ const float kExpectedAdjustedRefreshRate = 60.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting the same refresh rate as the pacesetter
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateSame) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 60.f;
+ const float kRequestedRefreshRate = 60.f;
+ const float kExpectedAdjustedRefreshRate = 60.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a divisor (30) of the pacesetter (60) should be honored
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateDivisor) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 60.f;
+ const float kRequestedRefreshRate = 30.f;
+ const float kExpectedAdjustedRefreshRate = 30.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a non divisor (45) of the pacesetter (120) should round up to a divisor (60)
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisor) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 120.f;
+ const float kRequestedRefreshRate = 45.f;
+ const float kExpectedAdjustedRefreshRate = 60.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
+// Requesting a non divisor (75) of the pacesetter (120) should round up to pacesetter (120)
+TEST_F(CreateDisplayTest, createDisplayWithRequestedRefreshRateNoneDivisorMax) {
+ const String8 displayName("virtual.test");
+ const uint64_t displayId = 123ull;
+ const float kPacesetterDisplayRefreshRate = 120.f;
+ const float kRequestedRefreshRate = 75.f;
+ const float kExpectedAdjustedRefreshRate = 120.f;
+ createDisplayWithRequestedRefreshRate(displayName, displayId, kPacesetterDisplayRefreshRate,
+ kRequestedRefreshRate, kExpectedAdjustedRefreshRate);
+}
+
} // namespace
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
index c7e61c96d6..93a3811172 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DestroyDisplayTest.cpp
@@ -37,9 +37,6 @@ TEST_F(DestroyDisplayTest, destroyDisplayClearsCurrentStateForDisplay) {
// --------------------------------------------------------------------
// Call Expectations
- // The call should notify the interceptor that a display was created.
- EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1);
-
// Destroying the display commits a display transaction.
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
@@ -65,7 +62,7 @@ TEST_F(DestroyDisplayTest, destroyDisplayHandlesUnknownDisplay) {
// --------------------------------------------------------------------
// Preconditions
- sp<BBinder> displayToken = new BBinder();
+ sp<BBinder> displayToken = sp<BBinder>::make();
// --------------------------------------------------------------------
// Invocation
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
index 5872a472cd..7ad97a2133 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayModeSwitching.cpp
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-#include "mock/MockEventThread.h"
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
#include "DisplayTransactionTestHelpers.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockDisplayModeSpecs.h"
+#include <ftl/fake_guard.h>
#include <scheduler/Fps.h>
namespace android {
@@ -40,18 +42,28 @@ public:
PrimaryDisplayVariant::setupNativeWindowSurfaceCreationCallExpectations(this);
PrimaryDisplayVariant::setupHwcGetActiveConfigCallExpectations(this);
- mFlinger.onComposerHalHotplug(PrimaryDisplayVariant::HWC_DISPLAY_ID, Connection::CONNECTED);
+ auto selectorPtr = std::make_shared<scheduler::RefreshRateSelector>(kModes, kModeId60);
- {
- DisplayModes modes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
- auto configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
+ setupScheduler(selectorPtr);
- mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
- .setDisplayModes(std::move(modes), kModeId60, std::move(configs))
- .inject();
- }
+ mFlinger.onComposerHalHotplugEvent(PrimaryDisplayVariant::HWC_DISPLAY_ID,
+ DisplayHotplugEvent::CONNECTED);
+ mFlinger.configureAndCommit();
- setupScheduler(mDisplay->holdRefreshRateConfigs());
+ auto vsyncController = std::make_unique<mock::VsyncController>();
+ auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
+
+ EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+ EXPECT_CALL(*vsyncTracker, currentPeriod())
+ .WillRepeatedly(Return(
+ TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+ EXPECT_CALL(*vsyncTracker, minFramePeriod())
+ .WillRepeatedly(Return(Period::fromNs(
+ TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
+
+ mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
+ .setRefreshRateSelector(std::move(selectorPtr))
+ .inject(std::move(vsyncController), std::move(vsyncTracker));
// isVsyncPeriodSwitchSupported should return true, otherwise the SF's HWC proxy
// will call setActiveConfig instead of setActiveConfigWithConstraints.
@@ -59,10 +71,36 @@ public:
.WillByDefault(Return(true));
}
+ static constexpr HWDisplayId kInnerDisplayHwcId = PrimaryDisplayVariant::HWC_DISPLAY_ID;
+ static constexpr HWDisplayId kOuterDisplayHwcId = kInnerDisplayHwcId + 1;
+
+ auto injectOuterDisplay() {
+ constexpr PhysicalDisplayId kOuterDisplayId = PhysicalDisplayId::fromPort(254u);
+
+ constexpr bool kIsPrimary = false;
+ TestableSurfaceFlinger::FakeHwcDisplayInjector(kOuterDisplayId, hal::DisplayType::PHYSICAL,
+ kIsPrimary)
+ .setHwcDisplayId(kOuterDisplayHwcId)
+ .setPowerMode(hal::PowerMode::OFF)
+ .inject(&mFlinger, mComposer);
+
+ mOuterDisplay = mFakeDisplayInjector.injectInternalDisplay(
+ [&](FakeDisplayDeviceInjector& injector) {
+ injector.setPowerMode(hal::PowerMode::OFF);
+ injector.setDisplayModes(mock::cloneForDisplay(kOuterDisplayId, kModes),
+ kModeId120);
+ },
+ {.displayId = kOuterDisplayId,
+ .hwcDisplayId = kOuterDisplayHwcId,
+ .isPrimary = kIsPrimary});
+
+ return std::forward_as_tuple(mDisplay, mOuterDisplay);
+ }
+
protected:
- void setupScheduler(std::shared_ptr<scheduler::RefreshRateConfigs>);
+ void setupScheduler(std::shared_ptr<scheduler::RefreshRateSelector>);
- sp<DisplayDevice> mDisplay;
+ sp<DisplayDevice> mDisplay, mOuterDisplay;
mock::EventThread* mAppEventThread;
static constexpr DisplayModeId kModeId60{0};
@@ -77,50 +115,58 @@ protected:
static constexpr ui::Size kResolution4K{3840, 2160};
static inline const DisplayModePtr kMode90_4K =
createDisplayMode(kModeId90_4K, 90_Hz, 3, kResolution4K);
+
+ static inline const DisplayModes kModes = makeModes(kMode60, kMode90, kMode120, kMode90_4K);
};
void DisplayModeSwitchingTest::setupScheduler(
- std::shared_ptr<scheduler::RefreshRateConfigs> configs) {
+ std::shared_ptr<scheduler::RefreshRateSelector> selectorPtr) {
auto eventThread = std::make_unique<mock::EventThread>();
mAppEventThread = eventThread.get();
auto sfEventThread = std::make_unique<mock::EventThread>();
EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
+ .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
+ mock::EventThread::kCallingUid)));
EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
+ .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
+ mock::EventThread::kCallingUid)));
auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
+ auto vsyncTracker = std::make_shared<mock::VSyncTracker>();
EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
EXPECT_CALL(*vsyncTracker, currentPeriod())
.WillRepeatedly(
Return(TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+ EXPECT_CALL(*vsyncTracker, minFramePeriod())
+ .WillRepeatedly(Return(Period::fromNs(
+ TestableSurfaceFlinger::FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
std::move(eventThread), std::move(sfEventThread),
- TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
- std::move(configs));
+ std::move(selectorPtr),
+ TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp);
}
-TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRequired) {
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithRefreshRequired) {
+ ftl::FakeGuard guard(kMainThreadContext);
- mFlinger.onActiveDisplayChanged(mDisplay);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
- false, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
- ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
+ 120));
+
+ ASSERT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
// Verify that next commit will call setActiveConfigWithConstraints in HWC
const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
@@ -132,31 +178,36 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithRefreshRe
mFlinger.commit();
Mock::VerifyAndClearExpectations(mComposer);
- ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+
+ EXPECT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
// Verify that the next commit will complete the mode change and send
// a onModeChanged event to the framework.
- EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90));
+ EXPECT_CALL(*mAppEventThread,
+ onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
mFlinger.commit();
Mock::VerifyAndClearExpectations(mAppEventThread);
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
}
-TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefreshRequired) {
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
+TEST_F(DisplayModeSwitchingTest, changeRefreshRateOnActiveDisplayWithoutRefreshRequired) {
+ ftl::FakeGuard guard(kMainThreadContext);
- mFlinger.onActiveDisplayChanged(mDisplay);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
- true, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
- ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90);
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(), true, 0,
+ 120));
+
+ ASSERT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90);
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
// Verify that next commit will call setActiveConfigWithConstraints in HWC
// and complete the mode change.
@@ -166,25 +217,29 @@ TEST_F(DisplayModeSwitchingTest, changeRefreshRate_OnActiveDisplay_WithoutRefres
hal::HWConfigId(kModeId90.value()), _, _))
.WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
- EXPECT_CALL(*mAppEventThread, onModeChanged(kMode90));
+ EXPECT_CALL(*mAppEventThread,
+ onModeChanged(scheduler::FrameRateMode{90_Hz, ftl::as_non_null(kMode90)}));
mFlinger.commit();
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90);
}
TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
+ ftl::FakeGuard guard(kMainThreadContext);
+
// Test that if we call setDesiredDisplayModeSpecs while a previous mode change
// is still being processed the later call will be respected.
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
- mFlinger.onActiveDisplayChanged(mDisplay);
+ mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90.value(),
- false, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(), false, 0,
+ 120));
const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
EXPECT_CALL(*mComposer,
@@ -194,11 +249,12 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
mFlinger.commit();
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId120.value(),
- false, 0.f, 180.f, 0.f, 180.f);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId120.value(), false, 0,
+ 180));
- ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
+ ASSERT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
EXPECT_CALL(*mComposer,
setActiveConfigWithConstraints(PrimaryDisplayVariant::HWC_DISPLAY_ID,
@@ -207,27 +263,30 @@ TEST_F(DisplayModeSwitchingTest, twoConsecutiveSetDesiredDisplayModeSpecs) {
mFlinger.commit();
- ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId120);
+ ASSERT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId120);
mFlinger.commit();
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId120);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId120);
}
-TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefreshRequired) {
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+TEST_F(DisplayModeSwitchingTest, changeResolutionOnActiveDisplayWithoutRefreshRequired) {
+ ftl::FakeGuard guard(kMainThreadContext);
- mFlinger.onActiveDisplayChanged(mDisplay);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
- mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(), kModeId90_4K.value(),
- false, 0.f, 120.f, 0.f, 120.f);
+ mFlinger.onActiveDisplayChanged(nullptr, *mDisplay);
- ASSERT_TRUE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getDesiredActiveMode()->mode->getId(), kModeId90_4K);
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId60);
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90_4K.value(), false, 0,
+ 120));
+
+ ASSERT_TRUE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getDesiredMode()->mode.modePtr->getId(), kModeId90_4K);
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId60);
// Verify that next commit will call setActiveConfigWithConstraints in HWC
// and complete the mode change.
@@ -261,8 +320,268 @@ TEST_F(DisplayModeSwitchingTest, changeResolution_OnActiveDisplay_WithoutRefresh
// so we need to update with the new instance.
mDisplay = mFlinger.getDisplay(displayToken);
- ASSERT_FALSE(mDisplay->getDesiredActiveMode().has_value());
- ASSERT_EQ(mDisplay->getActiveMode()->getId(), kModeId90_4K);
+ EXPECT_FALSE(mDisplay->getDesiredMode());
+ EXPECT_EQ(mDisplay->getActiveMode().modePtr->getId(), kModeId90_4K);
+}
+
+MATCHER_P2(ModeSwitchingTo, flinger, modeId, "") {
+ if (!arg->getDesiredMode()) {
+ *result_listener << "No desired mode";
+ return false;
+ }
+
+ if (arg->getDesiredMode()->mode.modePtr->getId() != modeId) {
+ *result_listener << "Unexpected desired mode " << modeId;
+ return false;
+ }
+
+ if (!flinger->scheduler()->vsyncModulator().isVsyncConfigEarly()) {
+ *result_listener << "VsyncModulator did not shift to early phase";
+ return false;
+ }
+
+ return true;
+}
+
+MATCHER_P(ModeSettledTo, modeId, "") {
+ if (const auto desiredOpt = arg->getDesiredMode()) {
+ *result_listener << "Unsettled desired mode " << desiredOpt->mode.modePtr->getId();
+ return false;
+ }
+
+ ftl::FakeGuard guard(kMainThreadContext);
+
+ if (arg->getActiveMode().modePtr->getId() != modeId) {
+ *result_listener << "Settled to unexpected active mode " << modeId;
+ return false;
+ }
+
+ return true;
+}
+
+TEST_F(DisplayModeSwitchingTest, innerXorOuterDisplay) {
+ const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
+
+ EXPECT_TRUE(innerDisplay->isPoweredOn());
+ EXPECT_FALSE(outerDisplay->isPoweredOn());
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ // Only the inner display is powered on.
+ mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId60.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kInnerDisplayHwcId,
+ hal::HWConfigId(kModeId90.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ innerDisplay->setPowerMode(hal::PowerMode::OFF);
+ outerDisplay->setPowerMode(hal::PowerMode::ON);
+
+ // Only the outer display is powered on.
+ mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kOuterDisplayHwcId,
+ hal::HWConfigId(kModeId60.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+}
+
+TEST_F(DisplayModeSwitchingTest, innerAndOuterDisplay) {
+ const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
+
+ EXPECT_TRUE(innerDisplay->isPoweredOn());
+ EXPECT_FALSE(outerDisplay->isPoweredOn());
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ outerDisplay->setPowerMode(hal::PowerMode::ON);
+
+ // Both displays are powered on.
+ mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId60.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+ const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kInnerDisplayHwcId,
+ hal::HWConfigId(kModeId90.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kOuterDisplayHwcId,
+ hal::HWConfigId(kModeId60.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
+}
+
+TEST_F(DisplayModeSwitchingTest, powerOffDuringModeSet) {
+ EXPECT_TRUE(mDisplay->isPoweredOn());
+ EXPECT_THAT(mDisplay, ModeSettledTo(kModeId60));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(mDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+
+ // Power off the display before the mode has been set.
+ mDisplay->setPowerMode(hal::PowerMode::OFF);
+
+ const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kInnerDisplayHwcId,
+ hal::HWConfigId(kModeId90.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ mFlinger.commit();
+
+ // Powering off should not abort the mode set.
+ EXPECT_FALSE(mDisplay->isPoweredOn());
+ EXPECT_THAT(mDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(mDisplay, ModeSettledTo(kModeId90));
+}
+
+TEST_F(DisplayModeSwitchingTest, powerOffDuringConcurrentModeSet) {
+ const auto [innerDisplay, outerDisplay] = injectOuterDisplay();
+
+ EXPECT_TRUE(innerDisplay->isPoweredOn());
+ EXPECT_FALSE(outerDisplay->isPoweredOn());
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ outerDisplay->setPowerMode(hal::PowerMode::ON);
+
+ // Both displays are powered on.
+ mFlinger.onActiveDisplayChanged(nullptr, *innerDisplay);
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId60));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(innerDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId90.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_EQ(NO_ERROR,
+ mFlinger.setDesiredDisplayModeSpecs(outerDisplay->getDisplayToken().promote(),
+ mock::createDisplayModeSpecs(kModeId60.value(),
+ false, 0.f, 120.f)));
+
+ EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+ // Power off the outer display before the mode has been set.
+ outerDisplay->setPowerMode(hal::PowerMode::OFF);
+
+ const VsyncPeriodChangeTimeline timeline{.refreshRequired = true};
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kInnerDisplayHwcId,
+ hal::HWConfigId(kModeId90.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ mFlinger.commit();
+
+ // Powering off the inactive display should abort the mode set.
+ EXPECT_THAT(innerDisplay, ModeSwitchingTo(&mFlinger, kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId120));
+
+ innerDisplay->setPowerMode(hal::PowerMode::OFF);
+ outerDisplay->setPowerMode(hal::PowerMode::ON);
+
+ // Only the outer display is powered on.
+ mFlinger.onActiveDisplayChanged(innerDisplay.get(), *outerDisplay);
+
+ EXPECT_CALL(*mComposer,
+ setActiveConfigWithConstraints(kOuterDisplayHwcId,
+ hal::HWConfigId(kModeId60.value()), _, _))
+ .WillOnce(DoAll(SetArgPointee<3>(timeline), Return(Error::NONE)));
+
+ mFlinger.commit();
+
+ // The mode set should resume once the display becomes active.
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSwitchingTo(&mFlinger, kModeId60));
+
+ mFlinger.commit();
+
+ EXPECT_THAT(innerDisplay, ModeSettledTo(kModeId90));
+ EXPECT_THAT(outerDisplay, ModeSettledTo(kModeId60));
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
index 9ac29071b2..b620830357 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_DisplayTransactionCommitTest.cpp
@@ -90,15 +90,12 @@ void DisplayTransactionCommitTest::setupCommonCallExpectationsForConnectProcessi
Case::HdrSupport::setupComposerCallExpectations(this);
Case::PerFrameMetadataSupport::setupComposerCallExpectations(this);
- EXPECT_CALL(*mSurfaceInterceptor, saveDisplayCreation(_)).Times(1);
expectHotplugReceived<Case, true>(mEventThread);
expectHotplugReceived<Case, true>(mSFEventThread);
}
template <typename Case>
void DisplayTransactionCommitTest::setupCommonCallExpectationsForDisconnectProcessing() {
- EXPECT_CALL(*mSurfaceInterceptor, saveDisplayDeletion(_)).Times(1);
-
expectHotplugReceived<Case, false>(mEventThread);
expectHotplugReceived<Case, false>(mSFEventThread);
}
@@ -113,14 +110,12 @@ void DisplayTransactionCommitTest::verifyDisplayIsConnected(const sp<IBinder>& d
EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), display.isPrimary());
std::optional<DisplayDeviceState::Physical> expectedPhysical;
- if (const auto connectionType = Case::Display::CONNECTION_TYPE::value) {
+ if (Case::Display::CONNECTION_TYPE::value) {
const auto displayId = PhysicalDisplayId::tryCast(Case::Display::DISPLAY_ID::get());
ASSERT_TRUE(displayId);
const auto hwcDisplayId = Case::Display::HWC_DISPLAY_ID_OPT::value;
ASSERT_TRUE(hwcDisplayId);
- expectedPhysical = {.id = *displayId,
- .type = *connectionType,
- .hwcDisplayId = *hwcDisplayId};
+ expectedPhysical = {.id = *displayId, .hwcDisplayId = *hwcDisplayId};
}
// The display should have been set up in the current display state
@@ -145,10 +140,13 @@ void DisplayTransactionCommitTest::verifyPhysicalDisplayIsConnected() {
const auto displayId = Case::Display::DISPLAY_ID::get();
ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
- const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId);
- ASSERT_TRUE(displayTokenOpt);
+ const auto displayOpt = mFlinger.mutablePhysicalDisplays().get(displayId);
+ ASSERT_TRUE(displayOpt);
+
+ const auto& display = displayOpt->get();
+ EXPECT_EQ(Case::Display::CONNECTION_TYPE::value, display.snapshot().connectionType());
- verifyDisplayIsConnected<Case>(displayTokenOpt->get());
+ verifyDisplayIsConnected<Case>(display.token());
}
void DisplayTransactionCommitTest::verifyDisplayIsNotConnected(const sp<IBinder>& displayToken) {
@@ -175,7 +173,7 @@ void DisplayTransactionCommitTest::processesHotplugConnectCommon() {
// --------------------------------------------------------------------
// Invocation
- mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ mFlinger.configureAndCommit();
// --------------------------------------------------------------------
// Postconditions
@@ -204,7 +202,7 @@ void DisplayTransactionCommitTest::ignoresHotplugConnectCommon() {
// --------------------------------------------------------------------
// Invocation
- mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ mFlinger.configureAndCommit();
// --------------------------------------------------------------------
// Postconditions
@@ -239,7 +237,7 @@ void DisplayTransactionCommitTest::processesHotplugDisconnectCommon() {
// --------------------------------------------------------------------
// Invocation
- mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ mFlinger.configureAndCommit();
// --------------------------------------------------------------------
// Postconditions
@@ -247,10 +245,10 @@ void DisplayTransactionCommitTest::processesHotplugDisconnectCommon() {
// HWComposer should not have an entry for the display
EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
- // SF should not have a display token.
+ // SF should not have a PhysicalDisplay.
const auto displayId = Case::Display::DISPLAY_ID::get();
ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
- ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId));
+ ASSERT_FALSE(mFlinger.mutablePhysicalDisplays().contains(displayId));
// The existing token should have been removed.
verifyDisplayIsNotConnected(existing.token());
@@ -321,7 +319,7 @@ TEST_F(DisplayTransactionCommitTest, processesHotplugConnectThenDisconnectPrimar
// --------------------------------------------------------------------
// Invocation
- mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ mFlinger.configureAndCommit();
// --------------------------------------------------------------------
// Postconditions
@@ -329,10 +327,10 @@ TEST_F(DisplayTransactionCommitTest, processesHotplugConnectThenDisconnectPrimar
// HWComposer should not have an entry for the display
EXPECT_FALSE(hasPhysicalHwcDisplay(Case::Display::HWC_DISPLAY_ID));
- // SF should not have a display token.
+ // SF should not have a PhysicalDisplay.
const auto displayId = Case::Display::DISPLAY_ID::get();
ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
- ASSERT_FALSE(mFlinger.mutablePhysicalDisplayTokens().contains(displayId));
+ ASSERT_FALSE(mFlinger.mutablePhysicalDisplays().contains(displayId));
}(),
testing::KilledBySignal(SIGABRT), "Primary display cannot be disconnected.");
}
@@ -366,7 +364,7 @@ TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectThenConnectPrimar
// --------------------------------------------------------------------
// Invocation
- mFlinger.commitTransactionsLocked(eDisplayTransactionNeeded);
+ mFlinger.configureAndCommit();
// --------------------------------------------------------------------
// Postconditions
@@ -376,9 +374,9 @@ TEST_F(DisplayTransactionCommitTest, processesHotplugDisconnectThenConnectPrimar
const auto displayId = Case::Display::DISPLAY_ID::get();
ASSERT_TRUE(PhysicalDisplayId::tryCast(displayId));
- const auto displayTokenOpt = mFlinger.mutablePhysicalDisplayTokens().get(displayId);
- ASSERT_TRUE(displayTokenOpt);
- EXPECT_NE(existing.token(), displayTokenOpt->get());
+ const auto displayOpt = mFlinger.mutablePhysicalDisplays().get(displayId);
+ ASSERT_TRUE(displayOpt);
+ EXPECT_NE(existing.token(), displayOpt->get().token());
// A new display should be connected in its place.
verifyPhysicalDisplayIsConnected<Case>();
@@ -408,12 +406,12 @@ TEST_F(DisplayTransactionCommitTest, processesVirtualDisplayAdded) {
// A virtual display was added to the current state, and it has a
// surface(producer)
- sp<BBinder> displayToken = new BBinder();
+ sp<BBinder> displayToken = sp<BBinder>::make();
DisplayDeviceState state;
state.isSecure = static_cast<bool>(Case::Display::SECURE);
- sp<mock::GraphicBufferProducer> surface{new mock::GraphicBufferProducer()};
+ sp<mock::GraphicBufferProducer> surface{sp<mock::GraphicBufferProducer>::make()};
state.surface = surface;
mFlinger.mutableCurrentState().displays.add(displayToken, state);
@@ -479,7 +477,7 @@ TEST_F(DisplayTransactionCommitTest, processesVirtualDisplayAddedWithNoSurface)
// A virtual display was added to the current state, but it does not have a
// surface.
- sp<BBinder> displayToken = new BBinder();
+ sp<BBinder> displayToken = sp<BBinder>::make();
DisplayDeviceState state;
state.isSecure = static_cast<bool>(Case::Display::SECURE);
@@ -656,9 +654,11 @@ TEST_F(DisplayTransactionCommitTest, processesDisplayWidthChanges) {
// Preconditions
// A display is set up
- auto nativeWindow = new mock::NativeWindow();
- auto displaySurface = new compositionengine::mock::DisplaySurface();
- sp<GraphicBuffer> buf = new GraphicBuffer();
+ auto nativeWindow = sp<mock::NativeWindow>::make();
+ auto displaySurface = sp<compositionengine::mock::DisplaySurface>::make();
+ sp<GraphicBuffer> buf =
+
+ sp<GraphicBuffer>::make();
auto display = Case::Display::makeFakeExistingDisplayInjector(this);
display.setNativeWindow(nativeWindow);
display.setDisplaySurface(displaySurface);
@@ -701,9 +701,9 @@ TEST_F(DisplayTransactionCommitTest, processesDisplayHeightChanges) {
// Preconditions
// A display is set up
- auto nativeWindow = new mock::NativeWindow();
- auto displaySurface = new compositionengine::mock::DisplaySurface();
- sp<GraphicBuffer> buf = new GraphicBuffer();
+ auto nativeWindow = sp<mock::NativeWindow>::make();
+ auto displaySurface = sp<compositionengine::mock::DisplaySurface>::make();
+ sp<GraphicBuffer> buf = sp<GraphicBuffer>::make();
auto display = Case::Display::makeFakeExistingDisplayInjector(this);
display.setNativeWindow(nativeWindow);
display.setDisplaySurface(displaySurface);
@@ -750,9 +750,9 @@ TEST_F(DisplayTransactionCommitTest, processesDisplaySizeDisplayRectAndLayerStac
// Preconditions
// A display is set up
- auto nativeWindow = new mock::NativeWindow();
- auto displaySurface = new compositionengine::mock::DisplaySurface();
- sp<GraphicBuffer> buf = new GraphicBuffer();
+ auto nativeWindow = sp<mock::NativeWindow>::make();
+ auto displaySurface = sp<compositionengine::mock::DisplaySurface>::make();
+ sp<GraphicBuffer> buf = sp<GraphicBuffer>::make();
auto display = Case::Display::makeFakeExistingDisplayInjector(this);
display.setNativeWindow(nativeWindow);
display.setDisplaySurface(displaySurface);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
new file mode 100644
index 0000000000..0e149d2bfb
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_ExcludeDolbyVisionTest.cpp
@@ -0,0 +1,109 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+namespace {
+
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+class ExcludeDolbyVisionTest : public DisplayTransactionTest {
+public:
+ void injectDisplayModes(std::vector<DisplayModePtr> displayModePtrs) {
+ DisplayModes modes;
+ for (DisplayModePtr displayMode : displayModePtrs) {
+ modes.try_emplace(displayMode->getId(), displayMode);
+ }
+
+ mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this)
+ .setDisplayModes(std::move(modes), displayModePtrs[0]->getId())
+ .inject();
+ mDisplay->overrideHdrTypes(types);
+ }
+
+protected:
+ sp<DisplayDevice> mDisplay;
+
+ static constexpr DisplayModeId modeId1080p60{0};
+ static constexpr DisplayModeId modeId4k30{1};
+ static constexpr DisplayModeId modeId4k60{2};
+
+ static inline const DisplayModePtr mode1080p60 =
+ createDisplayMode(modeId1080p60, 60_Hz, 0, ui::Size(1920, 1080));
+ static inline const DisplayModePtr mode4k30 =
+ createDisplayMode(modeId4k30, 30_Hz, 1, ui::Size(3840, 2160));
+ static inline const DisplayModePtr mode4k30NonStandard =
+ createDisplayMode(modeId4k30, 30.1_Hz, 1, ui::Size(3840, 2160));
+ static inline const DisplayModePtr mode4k60 =
+ createDisplayMode(modeId4k60, 60_Hz, 2, ui::Size(3840, 2160));
+
+ const std::vector<ui::Hdr> types = {ui::Hdr::DOLBY_VISION, ui::Hdr::DOLBY_VISION_4K30,
+ ui::Hdr::HDR10_PLUS};
+};
+
+TEST_F(ExcludeDolbyVisionTest, excludesDolbyVisionOnModesHigherThan4k30) {
+ injectDisplayModes({mode4k60});
+ ui::DynamicDisplayInfo info;
+ mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info);
+
+ std::vector<ui::DisplayMode> displayModes = info.supportedDisplayModes;
+
+ ASSERT_EQ(1, displayModes.size());
+ ASSERT_TRUE(std::any_of(displayModes[0].supportedHdrTypes.begin(),
+ displayModes[0].supportedHdrTypes.end(),
+ [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; }));
+ ASSERT_TRUE(displayModes[0].supportedHdrTypes.size() == 1);
+}
+
+TEST_F(ExcludeDolbyVisionTest, includesDolbyVisionOnModesLowerThanOrEqualTo4k30) {
+ injectDisplayModes({mode1080p60, mode4k30, mode4k30NonStandard});
+ ui::DynamicDisplayInfo info;
+ mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info);
+
+ std::vector<ui::DisplayMode> displayModes = info.supportedDisplayModes;
+
+ ASSERT_EQ(2, displayModes.size());
+ for (size_t i = 0; i < displayModes.size(); i++) {
+ ASSERT_TRUE(std::any_of(displayModes[i].supportedHdrTypes.begin(),
+ displayModes[i].supportedHdrTypes.end(),
+ [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; }));
+ ASSERT_TRUE(std::any_of(displayModes[i].supportedHdrTypes.begin(),
+ displayModes[i].supportedHdrTypes.end(),
+ [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION; }));
+ ASSERT_TRUE(displayModes[i].supportedHdrTypes.size() == 2);
+ }
+}
+
+TEST_F(ExcludeDolbyVisionTest, 4k30IsNotReportedAsAValidHdrType) {
+ injectDisplayModes({mode4k60});
+ ui::DynamicDisplayInfo info;
+ mFlinger.getDynamicDisplayInfoFromToken(mDisplay->getDisplayToken().promote(), &info);
+
+ std::vector<ui::Hdr> displayHdrTypes = info.hdrCapabilities.getSupportedHdrTypes();
+
+ ASSERT_EQ(2, displayHdrTypes.size());
+ ASSERT_TRUE(std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(),
+ [](ui::Hdr type) { return type == ui::Hdr::HDR10_PLUS; }));
+ ASSERT_TRUE(std::any_of(displayHdrTypes.begin(), displayHdrTypes.end(),
+ [](ui::Hdr type) { return type == ui::Hdr::DOLBY_VISION; }));
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
new file mode 100644
index 0000000000..41d8f9e4e0
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_FoldableTest.cpp
@@ -0,0 +1,231 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+using namespace com::android::graphics::surfaceflinger;
+
+namespace android {
+namespace {
+
+struct FoldableTest : DisplayTransactionTest {
+ static constexpr bool kWithMockScheduler = false;
+ FoldableTest() : DisplayTransactionTest(kWithMockScheduler) {}
+
+ void SetUp() override {
+ injectMockScheduler(kInnerDisplayId);
+
+ // Inject inner and outer displays with uninitialized power modes.
+ constexpr bool kInitPowerMode = false;
+ {
+ InnerDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+ auto injector = InnerDisplayVariant::makeFakeExistingDisplayInjector(this);
+ injector.setPowerMode(std::nullopt);
+ injector.setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector());
+ mInnerDisplay = injector.inject();
+ }
+ {
+ OuterDisplayVariant::injectHwcDisplay<kInitPowerMode>(this);
+ auto injector = OuterDisplayVariant::makeFakeExistingDisplayInjector(this);
+ injector.setPowerMode(std::nullopt);
+ mOuterDisplay = injector.inject();
+ }
+ }
+
+ static inline PhysicalDisplayId kInnerDisplayId = InnerDisplayVariant::DISPLAY_ID::get();
+ static inline PhysicalDisplayId kOuterDisplayId = OuterDisplayVariant::DISPLAY_ID::get();
+
+ sp<DisplayDevice> mInnerDisplay, mOuterDisplay;
+};
+
+TEST_F(FoldableTest, promotesPacesetterOnBoot) {
+ // When the device boots, the inner display should be the pacesetter.
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+
+ // ...and should still be after powering on.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+}
+
+TEST_F(FoldableTest, promotesPacesetterOnFoldUnfold) {
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+
+ // The outer display should become the pacesetter after folding.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::OFF);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kOuterDisplayId);
+
+ // The inner display should become the pacesetter after unfolding.
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::OFF);
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+}
+
+TEST_F(FoldableTest, promotesPacesetterOnConcurrentPowerOn) {
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+
+ // The inner display should stay the pacesetter if both are powered on.
+ // TODO(b/255635821): The pacesetter should depend on the displays' refresh rates.
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+
+ // The outer display should become the pacesetter if designated.
+ mFlinger.scheduler()->setPacesetterDisplay(kOuterDisplayId);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kOuterDisplayId);
+
+ // The inner display should become the pacesetter if designated.
+ mFlinger.scheduler()->setPacesetterDisplay(kInnerDisplayId);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+}
+
+TEST_F(FoldableTest, promotesPacesetterOnConcurrentPowerOff) {
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+ // The outer display should become the pacesetter if the inner display powers off.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::OFF);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kOuterDisplayId);
+
+ // The outer display should stay the pacesetter if both are powered on.
+ // TODO(b/255635821): The pacesetter should depend on the displays' refresh rates.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kOuterDisplayId);
+
+ // The inner display should become the pacesetter if the outer display powers off.
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::OFF);
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+}
+
+TEST_F(FoldableTest, doesNotRequestHardwareVsyncIfPoweredOff) {
+ // Both displays are powered off.
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kInnerDisplayId, _))
+ .Times(0);
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kOuterDisplayId, _))
+ .Times(0);
+
+ EXPECT_FALSE(mInnerDisplay->isPoweredOn());
+ EXPECT_FALSE(mOuterDisplay->isPoweredOn());
+
+ auto& scheduler = *mFlinger.scheduler();
+ scheduler.onHardwareVsyncRequest(kInnerDisplayId, true);
+ scheduler.onHardwareVsyncRequest(kOuterDisplayId, true);
+}
+
+TEST_F(FoldableTest, requestsHardwareVsyncForInnerDisplay) {
+ // Only inner display is powered on.
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kInnerDisplayId, true))
+ .Times(1);
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kOuterDisplayId, _))
+ .Times(0);
+
+ // The injected VsyncSchedule uses TestableScheduler::mockRequestHardwareVsync, so no calls to
+ // ISchedulerCallback::requestHardwareVsync are expected during setPowerModeInternal.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+
+ EXPECT_TRUE(mInnerDisplay->isPoweredOn());
+ EXPECT_FALSE(mOuterDisplay->isPoweredOn());
+
+ auto& scheduler = *mFlinger.scheduler();
+ scheduler.onHardwareVsyncRequest(kInnerDisplayId, true);
+ scheduler.onHardwareVsyncRequest(kOuterDisplayId, true);
+}
+
+TEST_F(FoldableTest, requestsHardwareVsyncForOuterDisplay) {
+ // Only outer display is powered on.
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kInnerDisplayId, _))
+ .Times(0);
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kOuterDisplayId, true))
+ .Times(1);
+
+ // The injected VsyncSchedule uses TestableScheduler::mockRequestHardwareVsync, so no calls to
+ // ISchedulerCallback::requestHardwareVsync are expected during setPowerModeInternal.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::OFF);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+ EXPECT_FALSE(mInnerDisplay->isPoweredOn());
+ EXPECT_TRUE(mOuterDisplay->isPoweredOn());
+
+ auto& scheduler = *mFlinger.scheduler();
+ scheduler.onHardwareVsyncRequest(kInnerDisplayId, true);
+ scheduler.onHardwareVsyncRequest(kOuterDisplayId, true);
+}
+
+TEST_F(FoldableTest, requestsHardwareVsyncForBothDisplays) {
+ // Both displays are powered on.
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kInnerDisplayId, true))
+ .Times(1);
+ EXPECT_CALL(mFlinger.mockSchedulerCallback(), requestHardwareVsync(kOuterDisplayId, true))
+ .Times(1);
+
+ // The injected VsyncSchedule uses TestableScheduler::mockRequestHardwareVsync, so no calls to
+ // ISchedulerCallback::requestHardwareVsync are expected during setPowerModeInternal.
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+ EXPECT_TRUE(mInnerDisplay->isPoweredOn());
+ EXPECT_TRUE(mOuterDisplay->isPoweredOn());
+
+ auto& scheduler = *mFlinger.scheduler();
+ scheduler.onHardwareVsyncRequest(mInnerDisplay->getPhysicalId(), true);
+ scheduler.onHardwareVsyncRequest(mOuterDisplay->getPhysicalId(), true);
+}
+
+TEST_F(FoldableTest, requestVsyncOnPowerOn) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, true))
+ .Times(1);
+ EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kOuterDisplayId, true))
+ .Times(1);
+
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+}
+
+TEST_F(FoldableTest, disableVsyncOnPowerOffPacesetter) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
+ // When the device boots, the inner display should be the pacesetter.
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kInnerDisplayId);
+
+ testing::InSequence seq;
+ EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, true))
+ .Times(1);
+ EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kOuterDisplayId, true))
+ .Times(1);
+
+ // Turning off the pacesetter will result in disabling VSYNC.
+ EXPECT_CALL(mFlinger.scheduler()->mockRequestHardwareVsync, Call(kInnerDisplayId, false))
+ .Times(1);
+
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::ON);
+ mFlinger.setPowerModeInternal(mOuterDisplay, PowerMode::ON);
+
+ mFlinger.setPowerModeInternal(mInnerDisplay, PowerMode::OFF);
+
+ // Other display is now the pacesetter.
+ ASSERT_EQ(mFlinger.scheduler()->pacesetterDisplayId(), kOuterDisplayId);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayNativePrimariesTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayNativePrimariesTest.cpp
index 0171f1b41d..5951c9893f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayNativePrimariesTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayNativePrimariesTest.cpp
@@ -95,7 +95,7 @@ TEST_F(GetDisplayNativePrimaries, internalDisplayWithPrimariesData) {
}
TEST_F(GetDisplayNativePrimaries, notInternalDisplayToken) {
- sp<BBinder> notInternalDisplayToken = new BBinder();
+ sp<BBinder> notInternalDisplayToken = sp<BBinder>::make();
ui::DisplayPrimaries primaries;
populateDummyDisplayNativePrimaries(primaries);
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
new file mode 100644
index 0000000000..29acfaa1a5
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_GetDisplayStatsTest.cpp
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "SurfaceFlingerGetDisplayStatsTest"
+
+#include <compositionengine/Display.h>
+#include <compositionengine/mock/DisplaySurface.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <renderengine/mock/RenderEngine.h>
+#include <ui/DisplayStatInfo.h>
+#include "TestableSurfaceFlinger.h"
+#include "mock/DisplayHardware/MockComposer.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockTimeStats.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+using namespace android;
+using namespace testing;
+
+namespace android {
+namespace {
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using FakeDisplayDeviceInjector = TestableSurfaceFlinger::FakeDisplayDeviceInjector;
+
+constexpr hal::HWDisplayId HWC_DISPLAY = FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+constexpr int DEFAULT_DISPLAY_WIDTH = 1920;
+constexpr int DEFAULT_DISPLAY_HEIGHT = 1024;
+
+class SurfaceFlingerGetDisplayStatsTest : public Test {
+public:
+ void SetUp() override;
+
+protected:
+ TestableSurfaceFlinger mFlinger;
+ renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
+ sp<DisplayDevice> mDisplay;
+ sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
+ sp<compositionengine::mock::DisplaySurface>::make();
+ sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
+ mock::TimeStats* mTimeStats = new mock::TimeStats();
+ Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
+ Hwc2::mock::Composer* mComposer = nullptr;
+};
+
+void SurfaceFlingerGetDisplayStatsTest::SetUp() {
+ mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
+ mComposer = new Hwc2::mock::Composer();
+ mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
+ mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
+ mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
+ mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
+ mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
+ static constexpr bool kIsPrimary = true;
+ FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
+ .setPowerMode(hal::PowerMode::ON)
+ .inject(&mFlinger, mComposer);
+ auto compostionEngineDisplayArgs =
+ compositionengine::DisplayCreationArgsBuilder()
+ .setId(DEFAULT_DISPLAY_ID)
+ .setPixels({DEFAULT_DISPLAY_WIDTH, DEFAULT_DISPLAY_HEIGHT})
+ .setPowerAdvisor(mPowerAdvisor)
+ .setName("injected display")
+ .build();
+ auto compositionDisplay =
+ compositionengine::impl::createDisplay(mFlinger.getCompositionEngine(),
+ std::move(compostionEngineDisplayArgs));
+ mDisplay =
+ FakeDisplayDeviceInjector(mFlinger, compositionDisplay,
+ ui::DisplayConnectionType::Internal, HWC_DISPLAY, kIsPrimary)
+ .setDisplaySurface(mDisplaySurface)
+ .setNativeWindow(mNativeWindow)
+ .setPowerMode(hal::PowerMode::ON)
+ .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
+ .skipRegisterDisplay()
+ .inject();
+}
+
+// TODO (b/277364366): Clients should be updated to pass in the display they want.
+TEST_F(SurfaceFlingerGetDisplayStatsTest, nullptrSucceeds) {
+ DisplayStatInfo info;
+ status_t status = mFlinger.getDisplayStats(nullptr, &info);
+ EXPECT_EQ(status, NO_ERROR);
+}
+
+TEST_F(SurfaceFlingerGetDisplayStatsTest, explicitToken) {
+ DisplayStatInfo info;
+ status_t status = mFlinger.getDisplayStats(mDisplay->getDisplayToken().promote(), &info);
+ EXPECT_EQ(status, NO_ERROR);
+}
+
+TEST_F(SurfaceFlingerGetDisplayStatsTest, invalidToken) {
+ const String8 displayName("fakeDisplay");
+ sp<IBinder> displayToken = mFlinger.createDisplay(displayName, false);
+ DisplayStatInfo info;
+ status_t status = mFlinger.getDisplayStats(displayToken, &info);
+ EXPECT_EQ(status, NAME_NOT_FOUND);
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
new file mode 100644
index 0000000000..db6df229d5
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HdrOutputControlTest.cpp
@@ -0,0 +1,85 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gtest/gtest.h>
+#include <gui/AidlStatusUtil.h>
+#include <private/gui/ComposerService.h>
+#include <private/gui/ComposerServiceAIDL.h>
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+using GuiHdrConversionStrategyTag = gui::HdrConversionStrategy::Tag;
+using gui::aidl_utils::statusTFromBinderStatus;
+
+TEST(HdrOutputControlTest, testGetHdrOutputConversionSupport) {
+ sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+
+ bool hdrOutputConversionSupport;
+ binder::Status status = sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+
+ ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+}
+
+TEST(HdrOutputControlTest, testGetHdrConversionCapabilities) {
+ sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+
+ bool hdrOutputConversionSupport;
+ binder::Status getSupportStatus =
+ sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+ ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(getSupportStatus));
+
+ std::vector<gui::HdrConversionCapability> capabilities;
+ binder::Status status = sf->getHdrConversionCapabilities(&capabilities);
+
+ if (hdrOutputConversionSupport) {
+ ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ } else {
+ ASSERT_EQ(INVALID_OPERATION, statusTFromBinderStatus(status));
+ }
+}
+
+TEST(HdrOutputControlTest, testSetHdrConversionStrategy) {
+ sp<gui::ISurfaceComposer> sf(ComposerServiceAIDL::getComposerService());
+
+ bool hdrOutputConversionSupport;
+ binder::Status getSupportStatus =
+ sf->getHdrOutputConversionSupport(&hdrOutputConversionSupport);
+ ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(getSupportStatus));
+
+ std::vector<gui::HdrConversionStrategy> strategies = {
+ gui::HdrConversionStrategy::make<GuiHdrConversionStrategyTag::passthrough>(),
+ gui::HdrConversionStrategy::make<GuiHdrConversionStrategyTag::autoAllowedHdrTypes>(),
+ gui::HdrConversionStrategy::make<GuiHdrConversionStrategyTag::forceHdrConversion>(),
+ };
+ int32_t outPreferredHdrOutputType = 0;
+
+ for (const gui::HdrConversionStrategy& strategy : strategies) {
+ binder::Status status = sf->setHdrConversionStrategy(strategy, &outPreferredHdrOutputType);
+
+ if (hdrOutputConversionSupport) {
+ ASSERT_EQ(NO_ERROR, statusTFromBinderStatus(status));
+ } else {
+ ASSERT_EQ(INVALID_OPERATION, statusTFromBinderStatus(status));
+ }
+ }
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
index c9a2b00fa3..a270dc91f2 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_HotplugTest.cpp
@@ -20,41 +20,18 @@
#include "DisplayTransactionTestHelpers.h"
namespace android {
-namespace {
class HotplugTest : public DisplayTransactionTest {};
-TEST_F(HotplugTest, enqueuesEventsForDisplayTransaction) {
- constexpr HWDisplayId hwcDisplayId1 = 456;
- constexpr HWDisplayId hwcDisplayId2 = 654;
-
- // --------------------------------------------------------------------
- // Preconditions
-
- // Set the main thread id so that the current thread does not appear to be
- // the main thread.
- mFlinger.mutableMainThreadId() = std::thread::id();
-
- // --------------------------------------------------------------------
- // Call Expectations
-
- // We expect a scheduled commit for the display transaction.
- EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
-
- // --------------------------------------------------------------------
- // Invocation
+TEST_F(HotplugTest, schedulesConfigureToProcessHotplugEvents) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleConfigure()).Times(2);
- // Simulate two hotplug events (a connect and a disconnect)
- mFlinger.onComposerHalHotplug(hwcDisplayId1, Connection::CONNECTED);
- mFlinger.onComposerHalHotplug(hwcDisplayId2, Connection::DISCONNECTED);
-
- // --------------------------------------------------------------------
- // Postconditions
+ constexpr HWDisplayId hwcDisplayId1 = 456;
+ mFlinger.onComposerHalHotplugEvent(hwcDisplayId1, DisplayHotplugEvent::CONNECTED);
- // The display transaction needed flag should be set.
- EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
+ constexpr HWDisplayId hwcDisplayId2 = 654;
+ mFlinger.onComposerHalHotplugEvent(hwcDisplayId2, DisplayHotplugEvent::DISCONNECTED);
- // All events should be in the pending event queue.
const auto& pendingEvents = mFlinger.mutablePendingHotplugEvents();
ASSERT_EQ(2u, pendingEvents.size());
EXPECT_EQ(hwcDisplayId1, pendingEvents[0].hwcDisplayId);
@@ -63,49 +40,83 @@ TEST_F(HotplugTest, enqueuesEventsForDisplayTransaction) {
EXPECT_EQ(Connection::DISCONNECTED, pendingEvents[1].connection);
}
-TEST_F(HotplugTest, processesEnqueuedEventsIfCalledOnMainThread) {
+TEST_F(HotplugTest, schedulesFrameToCommitDisplayTransaction) {
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleConfigure()).Times(1);
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
constexpr HWDisplayId displayId1 = 456;
+ mFlinger.onComposerHalHotplugEvent(displayId1, DisplayHotplugEvent::DISCONNECTED);
+ mFlinger.configure();
- // --------------------------------------------------------------------
- // Note:
- // --------------------------------------------------------------------
- // This test case is a bit tricky. We want to verify that
- // onComposerHalHotplug() calls processDisplayHotplugEventsLocked(), but we
- // don't really want to provide coverage for everything the later function
- // does as there are specific tests for it.
- // --------------------------------------------------------------------
+ // The configure stage should consume the hotplug queue and produce a display transaction.
+ EXPECT_TRUE(mFlinger.mutablePendingHotplugEvents().empty());
+ EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
+}
- // --------------------------------------------------------------------
- // Preconditions
+TEST_F(HotplugTest, ignoresDuplicateDisconnection) {
+ // Inject a primary display.
+ PrimaryDisplayVariant::injectHwcDisplay(this);
- // Set the main thread id so that the current thread does appear to be the
- // main thread.
- mFlinger.mutableMainThreadId() = std::this_thread::get_id();
+ using ExternalDisplay = ExternalDisplayVariant;
+ ExternalDisplay::setupHwcHotplugCallExpectations(this);
+ ExternalDisplay::setupHwcGetActiveConfigCallExpectations(this);
- // --------------------------------------------------------------------
- // Call Expectations
+ // TODO(b/241286146): Remove this unnecessary call.
+ EXPECT_CALL(*mComposer,
+ setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+ .WillOnce(Return(Error::NONE));
- // We expect a scheduled commit for the display transaction.
+ // A single commit should be scheduled for both configure calls.
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
- // --------------------------------------------------------------------
- // Invocation
+ ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+ mFlinger.configure();
- // Simulate a disconnect on a display id that is not connected. This should
- // be enqueued by onComposerHalHotplug(), and dequeued by
- // processDisplayHotplugEventsLocked(), but then ignored as invalid.
- mFlinger.onComposerHalHotplug(displayId1, Connection::DISCONNECTED);
+ EXPECT_TRUE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
- // --------------------------------------------------------------------
- // Postconditions
+ // Disconnecting a display that was already disconnected should be a no-op.
+ ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+ ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+ ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+ mFlinger.configure();
- // The display transaction needed flag should be set.
- EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
+ // The display should be scheduled for removal during the next commit. At this point, it should
+ // still exist but be marked as disconnected.
+ EXPECT_TRUE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
+ EXPECT_FALSE(mFlinger.getHwComposer().isConnected(ExternalDisplay::DISPLAY_ID::get()));
+}
- // There should be no event queued on return, as it should have been
- // processed.
- EXPECT_TRUE(mFlinger.mutablePendingHotplugEvents().empty());
+TEST_F(HotplugTest, rejectsHotplugIfFailedToLoadDisplayModes) {
+ // Inject a primary display.
+ PrimaryDisplayVariant::injectHwcDisplay(this);
+
+ using ExternalDisplay = ExternalDisplayVariant;
+ constexpr bool kFailedHotplug = true;
+ ExternalDisplay::setupHwcHotplugCallExpectations<kFailedHotplug>(this);
+
+ // Simulate a connect event that fails to load display modes due to HWC already having
+ // disconnected the display but SF yet having to process the queued disconnect event.
+ EXPECT_CALL(*mComposer, getActiveConfig(ExternalDisplay::HWC_DISPLAY_ID, _))
+ .WillRepeatedly(Return(Error::BAD_DISPLAY));
+
+ // TODO(b/241286146): Remove this unnecessary call.
+ EXPECT_CALL(*mComposer,
+ setVsyncEnabled(ExternalDisplay::HWC_DISPLAY_ID, IComposerClient::Vsync::DISABLE))
+ .WillOnce(Return(Error::NONE));
+
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ ExternalDisplay::injectPendingHotplugEvent(this, Connection::CONNECTED);
+ mFlinger.configure();
+
+ // The hotplug should be rejected, so no HWComposer::DisplayData should be created.
+ EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
+
+ // Disconnecting a display that does not exist should be a no-op.
+ ExternalDisplay::injectPendingHotplugEvent(this, Connection::DISCONNECTED);
+ mFlinger.configure();
+
+ EXPECT_FALSE(hasPhysicalHwcDisplay(ExternalDisplay::HWC_DISPLAY_ID));
}
-} // namespace
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
index 37cf05ef64..fc5f2b0b90 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_OnInitializeDisplaysTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_InitializeDisplaysTest.cpp
@@ -22,9 +22,9 @@
namespace android {
namespace {
-class OnInitializeDisplaysTest : public DisplayTransactionTest {};
+class InitializeDisplaysTest : public DisplayTransactionTest {};
-TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) {
+TEST_F(InitializeDisplaysTest, commitsPrimaryDisplay) {
using Case = SimplePrimaryDisplayCase;
// --------------------------------------------------------------------
@@ -38,23 +38,21 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) {
// --------------------------------------------------------------------
// Call Expectations
- // We expect the surface interceptor to possibly be used, but we treat it as
- // disabled since it is called as a side effect rather than directly by this
- // function.
- EXPECT_CALL(*mSurfaceInterceptor, isEnabled()).WillOnce(Return(false));
-
// We expect a call to get the active display config.
Case::Display::setupHwcGetActiveConfigCallExpectations(this);
// We expect a scheduled commit for the display transaction.
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
- EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+ EXPECT_CALL(static_cast<mock::VSyncTracker&>(
+ mFlinger.scheduler()->getVsyncSchedule()->getTracker()),
+ nextAnticipatedVSyncTimeFrom(_))
+ .WillRepeatedly(Return(0));
// --------------------------------------------------------------------
// Invocation
- mFlinger.onInitializeDisplays();
+ FTL_FAKE_GUARD(kMainThreadContext, mFlinger.initializeDisplays());
// --------------------------------------------------------------------
// Postconditions
@@ -78,19 +76,8 @@ TEST_F(OnInitializeDisplaysTest, onInitializeDisplaysSetsUpPrimaryDisplay) {
auto displayDevice = primaryDisplay.mutableDisplayDevice();
EXPECT_EQ(PowerMode::ON, displayDevice->getPowerMode());
- // The display refresh period should be set in the orientedDisplaySpaceRect tracker.
- FrameStats stats;
- mFlinger.getAnimFrameTracker().getStats(&stats);
- EXPECT_EQ(DEFAULT_VSYNC_PERIOD, stats.refreshPeriodNano);
-
// The display transaction needed flag should be set.
EXPECT_TRUE(hasTransactionFlagSet(eDisplayTransactionNeeded));
-
- // The compositor timing should be set to default values
- const auto& compositorTiming = mFlinger.getCompositorTiming();
- EXPECT_EQ(-DEFAULT_VSYNC_PERIOD, compositorTiming.deadline);
- EXPECT_EQ(DEFAULT_VSYNC_PERIOD, compositorTiming.interval);
- EXPECT_EQ(DEFAULT_VSYNC_PERIOD, compositorTiming.presentLatency);
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
new file mode 100644
index 0000000000..7206e2977d
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyExpectedPresentTest.cpp
@@ -0,0 +1,180 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include "DisplayTransactionTestHelpers.h"
+
+namespace android {
+
+using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
+class NotifyExpectedPresentTest : public DisplayTransactionTest {
+public:
+ void SetUp() override {
+ mDisplay = PrimaryDisplayVariant::makeFakeExistingDisplayInjector(this).inject();
+ FakeHwcDisplayInjector(mDisplay->getPhysicalId(), hal::DisplayType::PHYSICAL, kIsPrimary)
+ .setPowerMode(hal::PowerMode::ON)
+ .inject(&mFlinger, mComposer);
+ }
+
+protected:
+ sp<DisplayDevice> mDisplay;
+ static constexpr bool kIsPrimary = true;
+ static constexpr hal::HWDisplayId HWC_DISPLAY_ID =
+ FakeHwcDisplayInjector::DEFAULT_HWC_DISPLAY_ID;
+};
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentTimeout) {
+ const auto physicDisplayId = mDisplay->getPhysicalId();
+ auto expectedPresentTime = systemTime() + ms2ns(10);
+ static constexpr Fps kFps60Hz = 60_Hz;
+ static constexpr int32_t kFrameInterval5HzNs = static_cast<Fps>(5_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameInterval60HzNs = kFps60Hz.getPeriodNsecs();
+ static constexpr int32_t kFrameInterval120HzNs = static_cast<Fps>(120_Hz).getPeriodNsecs();
+ static constexpr Period kVsyncPeriod =
+ Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+ static constexpr Period kTimeoutNs = Period::fromNs(kFrameInterval5HzNs);
+ static constexpr auto kLastExpectedPresentTimestamp = TimePoint::fromNs(0);
+
+ ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+ kLastExpectedPresentTimestamp,
+ kFps60Hz));
+
+ {
+ // Very first ExpectedPresent after idle, no previous timestamp
+ EXPECT_CALL(*mComposer,
+ notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+ kFrameInterval60HzNs))
+ .WillOnce(Return(Error::NONE));
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ kTimeoutNs);
+ }
+ {
+ // Absent timeoutNs
+ expectedPresentTime += 2 * kFrameInterval5HzNs;
+ EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ /*timeoutOpt*/ std::nullopt);
+ }
+ {
+ // Timeout is 0
+ expectedPresentTime += kFrameInterval60HzNs;
+ EXPECT_CALL(*mComposer,
+ notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+ kFrameInterval60HzNs))
+ .WillOnce(Return(Error::NONE));
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ Period::fromNs(0));
+ }
+ {
+ // ExpectedPresent is after the timeoutNs
+ expectedPresentTime += 2 * kFrameInterval5HzNs;
+ EXPECT_CALL(*mComposer,
+ notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+ kFrameInterval60HzNs))
+ .WillOnce(Return(Error::NONE));
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ kTimeoutNs);
+ }
+ {
+ // ExpectedPresent has not changed
+ EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ kTimeoutNs);
+ }
+ {
+ // ExpectedPresent is after the last reported ExpectedPresent.
+ expectedPresentTime += kFrameInterval60HzNs;
+ EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ kTimeoutNs);
+ }
+ {
+ // ExpectedPresent is before the last reported ExpectedPresent but after the timeoutNs,
+ // representing we changed our decision and want to present earlier than previously
+ // reported.
+ expectedPresentTime -= kFrameInterval120HzNs;
+ EXPECT_CALL(*mComposer,
+ notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+ kFrameInterval60HzNs))
+ .WillOnce(Return(Error::NONE));
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime), kFps60Hz,
+ kTimeoutNs);
+ }
+}
+
+TEST_F(NotifyExpectedPresentTest, notifyExpectedPresentRenderRateChanged) {
+ const auto physicDisplayId = mDisplay->getPhysicalId();
+ const auto now = systemTime();
+ auto expectedPresentTime = now;
+ static constexpr Period kTimeoutNs = Period::fromNs(static_cast<Fps>(1_Hz).getPeriodNsecs());
+
+ ASSERT_NO_FATAL_FAILURE(mFlinger.setNotifyExpectedPresentData(physicDisplayId,
+ TimePoint::fromNs(now),
+ Fps::fromValue(0)));
+ static constexpr int32_t kFrameIntervalNs120Hz = static_cast<Fps>(120_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs96Hz = static_cast<Fps>(96_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs80Hz = static_cast<Fps>(80_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs60Hz = static_cast<Fps>(60_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs40Hz = static_cast<Fps>(40_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs30Hz = static_cast<Fps>(30_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs24Hz = static_cast<Fps>(24_Hz).getPeriodNsecs();
+ static constexpr int32_t kFrameIntervalNs20Hz = static_cast<Fps>(20_Hz).getPeriodNsecs();
+ static constexpr Period kVsyncPeriod =
+ Period::fromNs(static_cast<Fps>(240_Hz).getPeriodNsecs());
+
+ struct FrameRateIntervalTestData {
+ int32_t frameIntervalNs;
+ bool callExpectedPresent;
+ };
+ const std::vector<FrameRateIntervalTestData> frameIntervals = {
+ {kFrameIntervalNs60Hz, true}, {kFrameIntervalNs96Hz, true},
+ {kFrameIntervalNs80Hz, true}, {kFrameIntervalNs120Hz, true},
+ {kFrameIntervalNs80Hz, true}, {kFrameIntervalNs60Hz, true},
+ {kFrameIntervalNs60Hz, false}, {kFrameIntervalNs30Hz, false},
+ {kFrameIntervalNs24Hz, true}, {kFrameIntervalNs40Hz, true},
+ {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs60Hz, true},
+ {kFrameIntervalNs20Hz, false}, {kFrameIntervalNs120Hz, true},
+ };
+
+ for (const auto& [frameIntervalNs, callExpectedPresent] : frameIntervals) {
+ {
+ expectedPresentTime += frameIntervalNs;
+ if (callExpectedPresent) {
+ EXPECT_CALL(*mComposer,
+ notifyExpectedPresent(HWC_DISPLAY_ID, expectedPresentTime,
+ frameIntervalNs))
+ .WillOnce(Return(Error::NONE));
+ } else {
+ EXPECT_CALL(*mComposer, notifyExpectedPresent(HWC_DISPLAY_ID, _, _)).Times(0);
+ }
+ mFlinger.notifyExpectedPresentIfRequired(physicDisplayId, kVsyncPeriod,
+ TimePoint::fromNs(expectedPresentTime),
+ Fps::fromPeriodNsecs(frameIntervalNs),
+ kTimeoutNs);
+ }
+ }
+}
+} // namespace android \ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
index ec7e8a7f82..f2e2c8a76f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_NotifyPowerBoostTest.cpp
@@ -21,33 +21,57 @@
#include <thread>
#include "DisplayTransactionTestHelpers.h"
+#include "FakeDisplayInjector.h"
-#include <android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Boost.h>
namespace android {
namespace {
-using android::hardware::power::Boost;
+using aidl::android::hardware::power::Boost;
TEST_F(DisplayTransactionTest, notifyPowerBoostNotifiesTouchEvent) {
using namespace std::chrono_literals;
- mFlinger.scheduler()->replaceTouchTimer(100);
- std::this_thread::sleep_for(10ms); // wait for callback to be triggered
+ std::mutex timerMutex;
+ std::condition_variable cv;
+
+ injectDefaultInternalDisplay([](FakeDisplayDeviceInjector&) {});
+
+ std::unique_lock lock(timerMutex);
+ bool didReset = false; // keeps track of what the most recent call was
+
+ auto waitForTimerReset = [&] { cv.wait_for(lock, 100ms, [&] { return didReset; }); };
+ auto waitForTimerExpired = [&] { cv.wait_for(lock, 100ms, [&] { return !didReset; }); };
+
+ // Add extra logic to unblock the test when the timer callbacks get called
+ mFlinger.scheduler()->replaceTouchTimer(10, [&](bool isReset) {
+ {
+ std::unique_lock lock(timerMutex); // guarantee we're waiting on the cv
+ didReset = isReset;
+ }
+ cv.notify_one(); // wake the cv
+ std::unique_lock lock(timerMutex); // guarantee we finished the cv logic
+ });
+
+ waitForTimerReset();
EXPECT_TRUE(mFlinger.scheduler()->isTouchActive()); // Starting timer activates touch
- std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
- EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+ waitForTimerExpired();
+ EXPECT_FALSE(mFlinger.scheduler()->isTouchActive()); // Stopping timer deactivates touch
EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::CAMERA_SHOT)));
- std::this_thread::sleep_for(10ms); // wait for callback to maybe be triggered
- EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
- std::this_thread::sleep_for(110ms); // wait for reset touch timer to expire and trigger callback
+ EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+ // Wait for the timer to start just in case
+ waitForTimerReset();
+ EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
+ // Wait for the timer to stop, again just in case
+ waitForTimerExpired();
EXPECT_FALSE(mFlinger.scheduler()->isTouchActive());
EXPECT_EQ(NO_ERROR, mFlinger.notifyPowerBoost(static_cast<int32_t>(Boost::INTERACTION)));
- std::this_thread::sleep_for(10ms); // wait for callback to be triggered.
+ waitForTimerReset();
EXPECT_TRUE(mFlinger.scheduler()->isTouchActive());
}
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
index 2c9888dd8e..b80cb6625f 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_PowerHintTest.cpp
@@ -28,9 +28,7 @@
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
#include "mock/DisplayHardware/MockPowerAdvisor.h"
-#include "mock/MockEventThread.h"
#include "mock/MockTimeStats.h"
-#include "mock/MockVsyncController.h"
#include "mock/system/window/MockNativeWindow.h"
using namespace android;
@@ -53,28 +51,25 @@ class SurfaceFlingerPowerHintTest : public Test {
public:
void SetUp() override;
- void setupScheduler();
-
protected:
TestableSurfaceFlinger mFlinger;
renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
sp<DisplayDevice> mDisplay;
sp<compositionengine::mock::DisplaySurface> mDisplaySurface =
- new compositionengine::mock::DisplaySurface();
- mock::NativeWindow* mNativeWindow = new mock::NativeWindow();
+ sp<compositionengine::mock::DisplaySurface>::make();
+ sp<mock::NativeWindow> mNativeWindow = sp<mock::NativeWindow>::make();
mock::TimeStats* mTimeStats = new mock::TimeStats();
Hwc2::mock::PowerAdvisor* mPowerAdvisor = nullptr;
Hwc2::mock::Composer* mComposer = nullptr;
};
void SurfaceFlingerPowerHintTest::SetUp() {
- setupScheduler();
+ mFlinger.setupMockScheduler({.displayId = DEFAULT_DISPLAY_ID});
mComposer = new Hwc2::mock::Composer();
mPowerAdvisor = new Hwc2::mock::PowerAdvisor();
mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
mFlinger.setupTimeStats(std::shared_ptr<TimeStats>(mTimeStats));
mFlinger.setupComposer(std::unique_ptr<Hwc2::Composer>(mComposer));
- mFlinger.setPowerHintSessionMode(true, true);
mFlinger.setupPowerAdvisor(std::unique_ptr<Hwc2::PowerAdvisor>(mPowerAdvisor));
static constexpr bool kIsPrimary = true;
FakeHwcDisplayInjector(DEFAULT_DISPLAY_ID, hal::DisplayType::PHYSICAL, kIsPrimary)
@@ -96,57 +91,28 @@ void SurfaceFlingerPowerHintTest::SetUp() {
.setDisplaySurface(mDisplaySurface)
.setNativeWindow(mNativeWindow)
.setPowerMode(hal::PowerMode::ON)
+ .setRefreshRateSelector(mFlinger.scheduler()->refreshRateSelector())
+ .skipRegisterDisplay()
.inject();
- mFlinger.mutableActiveDisplayToken() = mDisplay->getDisplayToken();
-}
-
-void SurfaceFlingerPowerHintTest::setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
-
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread),
- TestableSurfaceFlinger::SchedulerCallbackImpl::kNoOp,
- TestableSurfaceFlinger::kTwoDisplayModes);
}
TEST_F(SurfaceFlingerPowerHintTest, sendDurationsIncludingHwcWaitTime) {
ON_CALL(*mPowerAdvisor, usePowerHintSession()).WillByDefault(Return(true));
- const std::chrono::nanoseconds mockVsyncPeriod = 15ms;
- EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(1);
-
- const nsecs_t now = systemTime();
- const std::chrono::nanoseconds mockHwcRunTime = 20ms;
+ EXPECT_CALL(*mPowerAdvisor, updateTargetWorkDuration(_)).Times(1);
EXPECT_CALL(*mDisplaySurface,
prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
.Times(1);
- EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
- .WillOnce([mockHwcRunTime] {
- std::this_thread::sleep_for(mockHwcRunTime);
- return hardware::graphics::composer::V2_1::Error::NONE;
- });
- EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(1);
- static constexpr bool kVsyncId = 123; // arbitrary
- mFlinger.commitAndComposite(now, kVsyncId, now + mockVsyncPeriod.count());
+ EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
+ constexpr Duration kMockHwcRunTime = 20ms;
+ std::this_thread::sleep_for(kMockHwcRunTime);
+ return hardware::graphics::composer::V2_1::Error::NONE;
+ });
+ EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(1);
+
+ const TimePoint frameTime = scheduler::SchedulerClock::now();
+ constexpr Period kMockVsyncPeriod = 15ms;
+ mFlinger.commitAndComposite(frameTime, VsyncId{123}, frameTime + kMockVsyncPeriod);
}
TEST_F(SurfaceFlingerPowerHintTest, inactiveOnDisplayDoze) {
@@ -154,22 +120,20 @@ TEST_F(SurfaceFlingerPowerHintTest, inactiveOnDisplayDoze) {
mDisplay->setPowerMode(hal::PowerMode::DOZE);
- const std::chrono::nanoseconds mockVsyncPeriod = 15ms;
- EXPECT_CALL(*mPowerAdvisor, setTargetWorkDuration(_)).Times(0);
-
- const nsecs_t now = systemTime();
- const std::chrono::nanoseconds mockHwcRunTime = 20ms;
+ EXPECT_CALL(*mPowerAdvisor, updateTargetWorkDuration(_)).Times(0);
EXPECT_CALL(*mDisplaySurface,
prepareFrame(compositionengine::DisplaySurface::CompositionType::Hwc))
.Times(1);
- EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _))
- .WillOnce([mockHwcRunTime] {
- std::this_thread::sleep_for(mockHwcRunTime);
- return hardware::graphics::composer::V2_1::Error::NONE;
- });
- EXPECT_CALL(*mPowerAdvisor, sendActualWorkDuration()).Times(0);
- static constexpr bool kVsyncId = 123; // arbitrary
- mFlinger.commitAndComposite(now, kVsyncId, now + mockVsyncPeriod.count());
+ EXPECT_CALL(*mComposer, presentOrValidateDisplay(HWC_DISPLAY, _, _, _, _, _, _)).WillOnce([] {
+ constexpr Duration kMockHwcRunTime = 20ms;
+ std::this_thread::sleep_for(kMockHwcRunTime);
+ return hardware::graphics::composer::V2_1::Error::NONE;
+ });
+ EXPECT_CALL(*mPowerAdvisor, reportActualWorkDuration()).Times(0);
+
+ const TimePoint frameTime = scheduler::SchedulerClock::now();
+ constexpr Period kMockVsyncPeriod = 15ms;
+ mFlinger.commitAndComposite(frameTime, VsyncId{123}, frameTime + kMockVsyncPeriod);
}
} // namespace
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp
index 7d9e22b23e..9c7f55b733 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetDisplayStateTest.cpp
@@ -34,7 +34,7 @@ TEST_F(SetDisplayStateLockedTest, setDisplayStateLockedDoesNothingWithUnknownDis
// Preconditions
// We have an unknown display token not associated with a known display
- sp<BBinder> displayToken = new BBinder();
+ sp<BBinder> displayToken = sp<BBinder>::make();
// The requested display state references the unknown display.
DisplayState state;
@@ -95,7 +95,7 @@ TEST_F(SetDisplayStateLockedTest, setDisplayStateLockedDoesNothingIfSurfaceDidNo
display.inject();
// There is a surface that can be set.
- sp<mock::GraphicBufferProducer> surface = new mock::GraphicBufferProducer();
+ sp<mock::GraphicBufferProducer> surface = sp<mock::GraphicBufferProducer>::make();
// The current display state has the surface set
display.mutableCurrentDisplayState().surface = surface;
@@ -132,7 +132,7 @@ TEST_F(SetDisplayStateLockedTest, setDisplayStateLockedRequestsUpdateIfSurfaceCh
display.inject();
// There is a surface that can be set.
- sp<mock::GraphicBufferProducer> surface = new mock::GraphicBufferProducer();
+ sp<mock::GraphicBufferProducer> surface = sp<mock::GraphicBufferProducer>::make();
// The current display state does not have a surface
display.mutableCurrentDisplayState().surface = nullptr;
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
index 583cf5f836..83e2f980ce 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetPowerModeInternalTest.cpp
@@ -17,14 +17,23 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
+#include <com_android_graphics_surfaceflinger_flags.h>
+#include <common/test/FlagUtils.h>
#include "DisplayTransactionTestHelpers.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+using namespace com::android::graphics::surfaceflinger;
+
namespace android {
namespace {
+MATCHER_P(DisplayModeFps, value, "equals") {
+ using fps_approx_ops::operator==;
+ return arg->getVsyncRate() == value;
+}
+
// Used when we simulate a display that supports doze.
template <typename Display>
struct DozeIsSupportedVariant {
@@ -59,54 +68,48 @@ struct DozeNotSupportedVariant {
};
struct EventThreadBaseSupportedVariant {
- static void setupVsyncAndEventThreadNoCallExpectations(DisplayTransactionTest* test) {
- // The callback should not be notified to toggle VSYNC.
- EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(_)).Times(0);
-
- // The event thread should not be notified.
- EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(0);
- EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(0);
+ static void setupVsyncNoCallExpectations(DisplayTransactionTest* test) {
+ // Expect no change to hardware nor synthetic VSYNC.
+ EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, _)).Times(0);
+ EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0);
}
};
struct EventThreadNotSupportedVariant : public EventThreadBaseSupportedVariant {
- static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) {
- // These calls are only expected for the primary display.
-
- // Instead expect no calls.
- setupVsyncAndEventThreadNoCallExpectations(test);
+ static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) {
+ EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, true)).Times(1);
+ EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0);
}
- static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
- // These calls are only expected for the primary display.
-
- // Instead expect no calls.
- setupVsyncAndEventThreadNoCallExpectations(test);
+ static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) {
+ EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, false)).Times(1);
+ EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(_)).Times(0);
}
};
struct EventThreadIsSupportedVariant : public EventThreadBaseSupportedVariant {
- static void setupAcquireAndEnableVsyncCallExpectations(DisplayTransactionTest* test) {
- // The callback should be notified to enable VSYNC.
- EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(true)).Times(1);
-
- // The event thread should be notified that the screen was acquired.
- EXPECT_CALL(*test->mEventThread, onScreenAcquired()).Times(1);
+ static void setupEnableVsyncCallExpectations(DisplayTransactionTest* test) {
+ // Expect to enable hardware VSYNC and disable synthetic VSYNC.
+ EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, true)).Times(1);
+ EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(false)).Times(1);
}
- static void setupReleaseAndDisableVsyncCallExpectations(DisplayTransactionTest* test) {
- // The callback should be notified to disable VSYNC.
- EXPECT_CALL(test->mFlinger.mockSchedulerCallback(), setVsyncEnabled(false)).Times(1);
-
- // The event thread should not be notified that the screen was released.
- EXPECT_CALL(*test->mEventThread, onScreenReleased()).Times(1);
+ static void setupDisableVsyncCallExpectations(DisplayTransactionTest* test) {
+ // Expect to disable hardware VSYNC and enable synthetic VSYNC.
+ EXPECT_CALL(test->mFlinger.scheduler()->mockRequestHardwareVsync, Call(_, false)).Times(1);
+ EXPECT_CALL(*test->mEventThread, enableSyntheticVsync(true)).Times(1);
}
};
struct DispSyncIsSupportedVariant {
static void setupResetModelCallExpectations(DisplayTransactionTest* test) {
- EXPECT_CALL(*test->mVsyncController, startPeriodTransition(DEFAULT_VSYNC_PERIOD)).Times(1);
- EXPECT_CALL(*test->mVSyncTracker, resetModel()).Times(1);
+ auto vsyncSchedule = test->mFlinger.scheduler()->getVsyncSchedule();
+ EXPECT_CALL(static_cast<mock::VsyncController&>(vsyncSchedule->getController()),
+ onDisplayModeChanged(DisplayModeFps(Fps::fromPeriodNsecs(DEFAULT_VSYNC_PERIOD)),
+ false))
+ .Times(1);
+ EXPECT_CALL(static_cast<mock::VSyncTracker&>(vsyncSchedule->getTracker()), resetModel())
+ .Times(1);
}
};
@@ -133,14 +136,13 @@ struct TransitionOffToOnVariant : public TransitionVariantCommon<PowerMode::OFF,
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
- Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test);
+ Case::EventThread::setupEnableVsyncCallExpectations(test);
Case::DispSync::setupResetModelCallExpectations(test);
Case::setupRepaintEverythingCallExpectations(test);
}
static void verifyPostconditions(DisplayTransactionTest* test) {
EXPECT_TRUE(test->mFlinger.getVisibleRegionsDirty());
- EXPECT_TRUE(test->mFlinger.getHasPoweredOff());
}
};
@@ -149,20 +151,19 @@ struct TransitionOffToDozeSuspendVariant
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND);
- Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+ Case::EventThread::setupVsyncNoCallExpectations(test);
Case::setupRepaintEverythingCallExpectations(test);
}
static void verifyPostconditions(DisplayTransactionTest* test) {
EXPECT_TRUE(test->mFlinger.getVisibleRegionsDirty());
- EXPECT_TRUE(test->mFlinger.getHasPoweredOff());
}
};
struct TransitionOnToOffVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::OFF> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test);
+ Case::EventThread::setupDisableVsyncCallExpectations(test);
Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF);
}
@@ -175,7 +176,7 @@ struct TransitionDozeSuspendToOffVariant
: public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::OFF> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+ Case::EventThread::setupVsyncNoCallExpectations(test);
Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::OFF);
}
@@ -187,7 +188,7 @@ struct TransitionDozeSuspendToOffVariant
struct TransitionOnToDozeVariant : public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+ Case::EventThread::setupVsyncNoCallExpectations(test);
Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE);
}
};
@@ -196,7 +197,7 @@ struct TransitionDozeSuspendToDozeVariant
: public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::DOZE> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test);
+ Case::EventThread::setupEnableVsyncCallExpectations(test);
Case::DispSync::setupResetModelCallExpectations(test);
Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE);
}
@@ -205,7 +206,7 @@ struct TransitionDozeSuspendToDozeVariant
struct TransitionDozeToOnVariant : public TransitionVariantCommon<PowerMode::DOZE, PowerMode::ON> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+ Case::EventThread::setupVsyncNoCallExpectations(test);
Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
}
};
@@ -214,7 +215,7 @@ struct TransitionDozeSuspendToOnVariant
: public TransitionVariantCommon<PowerMode::DOZE_SUSPEND, PowerMode::ON> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupAcquireAndEnableVsyncCallExpectations(test);
+ Case::EventThread::setupEnableVsyncCallExpectations(test);
Case::DispSync::setupResetModelCallExpectations(test);
Case::setupComposerCallExpectations(test, IComposerClient::PowerMode::ON);
}
@@ -224,7 +225,7 @@ struct TransitionOnToDozeSuspendVariant
: public TransitionVariantCommon<PowerMode::ON, PowerMode::DOZE_SUSPEND> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupReleaseAndDisableVsyncCallExpectations(test);
+ Case::EventThread::setupDisableVsyncCallExpectations(test);
Case::setupComposerCallExpectations(test, Case::Doze::ACTUAL_POWER_MODE_FOR_DOZE_SUSPEND);
}
};
@@ -233,7 +234,7 @@ struct TransitionOnToUnknownVariant
: public TransitionVariantCommon<PowerMode::ON, static_cast<PowerMode>(POWER_MODE_LEET)> {
template <typename Case>
static void setupCallExpectations(DisplayTransactionTest* test) {
- Case::EventThread::setupVsyncAndEventThreadNoCallExpectations(test);
+ Case::EventThread::setupVsyncNoCallExpectations(test);
Case::setupNoComposerPowerModeCallExpectations(test);
}
};
@@ -255,34 +256,24 @@ struct DisplayPowerCase {
using DispSync = DispSyncVariant;
using Transition = TransitionVariant;
- static auto injectDisplayWithInitialPowerMode(DisplayTransactionTest* test, PowerMode mode) {
+ static sp<DisplayDevice> injectDisplayWithInitialPowerMode(DisplayTransactionTest* test,
+ PowerMode mode) {
Display::injectHwcDisplayWithNoDefaultCapabilities(test);
- auto display = Display::makeFakeExistingDisplayInjector(test);
- display.inject();
- display.mutableDisplayDevice()->setPowerMode(mode);
- if (display.mutableDisplayDevice()->isInternal()) {
- test->mFlinger.mutableActiveDisplayToken() =
- display.mutableDisplayDevice()->getDisplayToken();
- }
-
+ auto injector = Display::makeFakeExistingDisplayInjector(test);
+ const auto display = injector.inject();
+ display->setPowerMode(mode);
return display;
}
- static void setInitialPrimaryHWVsyncEnabled(DisplayTransactionTest* test, bool enabled) {
- test->mFlinger.scheduler()->mutablePrimaryHWVsyncEnabled() = enabled;
+ static void setInitialHwVsyncEnabled(DisplayTransactionTest* test, PhysicalDisplayId id,
+ bool enabled) {
+ test->mFlinger.scheduler()->setInitialHwVsyncEnabled(id, enabled);
}
static void setupRepaintEverythingCallExpectations(DisplayTransactionTest* test) {
EXPECT_CALL(*test->mFlinger.scheduler(), scheduleFrame()).Times(1);
}
- static void setupSurfaceInterceptorCallExpectations(DisplayTransactionTest* test,
- PowerMode mode) {
- EXPECT_CALL(*test->mSurfaceInterceptor, isEnabled()).WillOnce(Return(true));
- EXPECT_CALL(*test->mSurfaceInterceptor, savePowerModeUpdate(_, static_cast<int32_t>(mode)))
- .Times(1);
- }
-
static void setupComposerCallExpectations(DisplayTransactionTest* test, PowerMode mode) {
// Any calls to get the active config will return a default value.
EXPECT_CALL(*test->mComposer, getActiveConfig(Display::HWC_DISPLAY_ID, _))
@@ -313,6 +304,11 @@ using PrimaryDisplayPowerCase =
// A sample configuration for the external display.
// In addition to not having event thread support, we emulate not having doze
// support.
+// TODO (b/267483230): ExternalDisplay supports the features tracked in
+// DispSyncIsSupportedVariant, but is the follower, so the
+// expectations set by DispSyncIsSupportedVariant don't match (wrong schedule).
+// We need a way to retrieve the proper DisplayId from
+// setupResetModelCallExpectations (or pass it in).
template <typename TransitionVariant>
using ExternalDisplayPowerCase =
DisplayPowerCase<ExternalDisplayVariant, DozeNotSupportedVariant<ExternalDisplayVariant>,
@@ -342,21 +338,22 @@ void SetPowerModeInternalTest::transitionDisplayCommon() {
Case::Doze::setupComposerCallExpectations(this);
auto display =
Case::injectDisplayWithInitialPowerMode(this, Case::Transition::INITIAL_POWER_MODE);
- Case::setInitialPrimaryHWVsyncEnabled(this,
- PowerModeInitialVSyncEnabled<
- Case::Transition::INITIAL_POWER_MODE>::value);
+ auto displayId = display->getId();
+ if (auto physicalDisplayId = PhysicalDisplayId::tryCast(displayId)) {
+ Case::setInitialHwVsyncEnabled(this, *physicalDisplayId,
+ PowerModeInitialVSyncEnabled<
+ Case::Transition::INITIAL_POWER_MODE>::value);
+ }
// --------------------------------------------------------------------
// Call Expectations
- Case::setupSurfaceInterceptorCallExpectations(this, Case::Transition::TARGET_POWER_MODE);
Case::Transition::template setupCallExpectations<Case>(this);
// --------------------------------------------------------------------
// Invocation
- mFlinger.setPowerModeInternal(display.mutableDisplayDevice(),
- Case::Transition::TARGET_POWER_MODE);
+ mFlinger.setPowerModeInternal(display, Case::Transition::TARGET_POWER_MODE);
// --------------------------------------------------------------------
// Postconditions
@@ -460,6 +457,7 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToUnknownPrimaryDisplay
}
TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToOnExternalDisplay) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOffToOnVariant>>();
}
@@ -468,6 +466,7 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOffToDozeSuspendExternalD
}
TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToOffExternalDisplay) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToOffVariant>>();
}
@@ -480,6 +479,7 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToDozeExternalDisplay)
}
TEST_F(SetPowerModeInternalTest, transitionsDisplayFromDozeSuspendToDozeExternalDisplay) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionDozeSuspendToDozeVariant>>();
}
@@ -488,10 +488,12 @@ TEST_F(SetPowerModeInternalTest, transitionsDisplayFromDozeToOnExternalDisplay)
}
TEST_F(SetPowerModeInternalTest, transitionsDisplayFromDozeSuspendToOnExternalDisplay) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionDozeSuspendToOnVariant>>();
}
TEST_F(SetPowerModeInternalTest, transitionsDisplayFromOnToDozeSuspendExternalDisplay) {
+ SET_FLAG_FOR_TEST(flags::multithreaded_present, true);
transitionDisplayCommon<ExternalDisplayPowerCase<TransitionOnToDozeSuspendVariant>>();
}
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
index a0e078bf5e..c0796df6cb 100644
--- a/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_SetupNewDisplayDeviceInternalTest.cpp
@@ -17,6 +17,8 @@
#undef LOG_TAG
#define LOG_TAG "LibSurfaceFlingerUnittests"
+#include <ftl/fake_guard.h>
+
#include "DisplayHardware/DisplayMode.h"
#include "DisplayTransactionTestHelpers.h"
@@ -34,16 +36,13 @@ struct WideColorP3ColorimetricSupportedVariant {
static constexpr bool WIDE_COLOR_SUPPORTED = true;
static void injectConfigChange(DisplayTransactionTest* test) {
- test->mFlinger.mutableHasWideColorDisplay() = true;
+ test->mFlinger.mutableSupportsWideColor() = true;
test->mFlinger.mutableDisplayColorSetting() = DisplayColorSetting::kUnmanaged;
}
static void setupComposerCallExpectations(DisplayTransactionTest* test) {
EXPECT_CALL(*test->mNativeWindow, perform(NATIVE_WINDOW_SET_BUFFERS_DATASPACE)).Times(1);
- EXPECT_CALL(*test->mComposer, getColorModes(Display::HWC_DISPLAY_ID, _))
- .WillOnce(DoAll(SetArgPointee<1>(std::vector<ColorMode>({ColorMode::DISPLAY_P3})),
- Return(Error::NONE)));
EXPECT_CALL(*test->mComposer,
getRenderIntents(Display::HWC_DISPLAY_ID, ColorMode::DISPLAY_P3, _))
.WillOnce(DoAll(SetArgPointee<2>(
@@ -197,10 +196,10 @@ public:
template <typename Case>
void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() {
- const sp<BBinder> displayToken = new BBinder();
+ const sp<BBinder> displayToken = sp<BBinder>::make();
const sp<compositionengine::mock::DisplaySurface> displaySurface =
- new compositionengine::mock::DisplaySurface();
- const sp<mock::GraphicBufferProducer> producer = new mock::GraphicBufferProducer();
+ sp<compositionengine::mock::DisplaySurface>::make();
+ const auto producer = sp<mock::GraphicBufferProducer>::make();
// --------------------------------------------------------------------
// Preconditions
@@ -246,11 +245,20 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() {
.setDpiY(DEFAULT_DPI)
.setGroup(0)
.build();
+
state.physical = {.id = *displayId,
- .type = *connectionType,
.hwcDisplayId = *hwcDisplayId,
- .supportedModes = makeModes(activeMode),
- .activeMode = std::move(activeMode)};
+ .activeMode = activeMode};
+
+ ui::ColorModes colorModes;
+ if constexpr (Case::WideColorSupport::WIDE_COLOR_SUPPORTED) {
+ colorModes.push_back(ColorMode::DISPLAY_P3);
+ }
+
+ mFlinger.mutablePhysicalDisplays().emplace_or_replace(*displayId, displayToken, *displayId,
+ *connectionType,
+ makeModes(activeMode),
+ std::move(colorModes), std::nullopt);
}
state.isSecure = static_cast<bool>(Case::Display::SECURE);
@@ -262,9 +270,8 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() {
// --------------------------------------------------------------------
// Postconditions
- ASSERT_TRUE(device != nullptr);
+ ASSERT_NE(nullptr, device);
EXPECT_EQ(Case::Display::DISPLAY_ID::get(), device->getId());
- EXPECT_EQ(Case::Display::CONNECTION_TYPE::value, device->getConnectionType());
EXPECT_EQ(static_cast<bool>(Case::Display::VIRTUAL), device->isVirtual());
EXPECT_EQ(static_cast<bool>(Case::Display::SECURE), device->isSecure());
EXPECT_EQ(static_cast<bool>(Case::Display::PRIMARY), device->isPrimary());
@@ -280,9 +287,8 @@ void SetupNewDisplayDeviceInternalTest::setupNewDisplayDeviceInternalTest() {
device->receivesInput());
if constexpr (Case::Display::CONNECTION_TYPE::value) {
- EXPECT_EQ(1, device->getSupportedModes().size());
- EXPECT_NE(nullptr, device->getActiveMode());
- EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode()->getHwcId());
+ ftl::FakeGuard guard(kMainThreadContext);
+ EXPECT_EQ(Case::Display::HWC_ACTIVE_CONFIG_ID, device->getActiveMode().modePtr->getHwcId());
}
}
diff --git a/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
new file mode 100644
index 0000000000..0e5f1ea789
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/SurfaceFlinger_UpdateLayerMetadataSnapshotTest.cpp
@@ -0,0 +1,194 @@
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <gui/LayerMetadata.h>
+
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+using testing::_;
+using testing::Return;
+
+class SurfaceFlingerUpdateLayerMetadataSnapshotTest : public testing::Test {
+public:
+ SurfaceFlingerUpdateLayerMetadataSnapshotTest() { mFlinger.setupMockScheduler(); }
+
+protected:
+ sp<Layer> createLayer(const char* name, LayerMetadata& inOutlayerMetadata) {
+ LayerCreationArgs args =
+ LayerCreationArgs{mFlinger.flinger(), nullptr, name, 0, inOutlayerMetadata};
+ inOutlayerMetadata = args.metadata;
+ return sp<Layer>::make(args);
+ }
+
+ TestableSurfaceFlinger mFlinger;
+};
+
+class LayerMetadataBuilder {
+public:
+ LayerMetadataBuilder(LayerMetadata layerMetadata = {}) : mLayerMetadata(layerMetadata) {}
+
+ LayerMetadataBuilder& setInt32(uint32_t key, int32_t value) {
+ mLayerMetadata.setInt32(key, value);
+ return *this;
+ }
+
+ LayerMetadata build() { return mLayerMetadata; }
+
+private:
+ LayerMetadata mLayerMetadata;
+};
+
+bool operator==(const LayerMetadata& lhs, const LayerMetadata& rhs) {
+ return lhs.mMap == rhs.mMap;
+}
+
+std::ostream& operator<<(std::ostream& stream, const LayerMetadata& layerMetadata) {
+ stream << "LayerMetadata{";
+ for (auto it = layerMetadata.mMap.cbegin(); it != layerMetadata.mMap.cend(); it++) {
+ if (it != layerMetadata.mMap.cbegin()) {
+ stream << ", ";
+ }
+ stream << layerMetadata.itemToString(it->first, ":");
+ }
+ return stream << "}";
+}
+
+// Test that the snapshot's layer metadata is set.
+TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesSnapshotMetadata) {
+ auto layerMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 1).build();
+ auto layer = createLayer("layer", layerMetadata);
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layer);
+
+ mFlinger.updateLayerMetadataSnapshot();
+
+ EXPECT_EQ(layer->getLayerSnapshot()->layerMetadata, layerMetadata);
+}
+
+// Test that snapshot layer metadata is set by merging the child's metadata on top of its
+// parent's metadata.
+TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, mergesSnapshotMetadata) {
+ auto layerAMetadata = LayerMetadataBuilder()
+ .setInt32(METADATA_OWNER_UID, 1)
+ .setInt32(METADATA_TASK_ID, 2)
+ .build();
+ auto layerA = createLayer("parent", layerAMetadata);
+ auto layerBMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 3).build();
+ auto layerB = createLayer("child", layerBMetadata);
+ layerA->addChild(layerB);
+ layerA->commitChildList();
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
+
+ mFlinger.updateLayerMetadataSnapshot();
+
+ EXPECT_EQ(layerA->getLayerSnapshot()->layerMetadata, layerAMetadata);
+ auto expectedChildMetadata =
+ LayerMetadataBuilder(layerAMetadata).setInt32(METADATA_TASK_ID, 3).build();
+ EXPECT_EQ(layerB->getLayerSnapshot()->layerMetadata, expectedChildMetadata);
+}
+
+// Test that snapshot relative layer metadata is set to the parent's layer metadata merged on top of
+// that parent's relative layer metadata.
+TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadata) {
+ auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 1).build();
+ auto layerA = createLayer("relative-parent", layerAMetadata);
+ auto layerAHandle = layerA->getHandle();
+ auto layerBMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 2).build();
+ auto layerB = createLayer("relative-child", layerBMetadata);
+ layerB->setRelativeLayer(layerAHandle, 1);
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerB);
+
+ mFlinger.updateLayerMetadataSnapshot();
+
+ EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
+ EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
+}
+
+// Test that snapshot relative layer metadata is set correctly when a layer is interleaved within
+// two other layers.
+//
+// Layer
+// A
+// / \
+// B D
+// /
+// C
+//
+// Z-order Relatives
+// B <- D <- C
+TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest, updatesRelativeMetadataInterleaved) {
+ auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build();
+ auto layerA = createLayer("layer-a", layerAMetadata);
+ auto layerBMetadata = LayerMetadataBuilder()
+ .setInt32(METADATA_TASK_ID, 2)
+ .setInt32(METADATA_OWNER_PID, 3)
+ .build();
+ auto layerB = createLayer("layer-b", layerBMetadata);
+ auto layerBHandle = layerB->getHandle();
+ LayerMetadata layerCMetadata;
+ auto layerC = createLayer("layer-c", layerCMetadata);
+ auto layerDMetadata = LayerMetadataBuilder().setInt32(METADATA_TASK_ID, 4).build();
+ auto layerD = createLayer("layer-d", layerDMetadata);
+ auto layerDHandle = layerD->getHandle();
+ layerB->addChild(layerC);
+ layerA->addChild(layerB);
+ layerA->addChild(layerD);
+ layerC->setRelativeLayer(layerDHandle, 1);
+ layerD->setRelativeLayer(layerBHandle, 1);
+ layerA->commitChildList();
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
+
+ mFlinger.updateLayerMetadataSnapshot();
+
+ auto expectedLayerDRelativeMetadata =
+ LayerMetadataBuilder()
+ // From layer A, parent of relative parent
+ .setInt32(METADATA_OWNER_UID, 1)
+ // From layer B, relative parent
+ .setInt32(METADATA_TASK_ID, 2)
+ .setInt32(METADATA_OWNER_PID, 3)
+ // added by layer creation args
+ .setInt32(gui::METADATA_CALLING_UID,
+ layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0))
+ .build();
+ EXPECT_EQ(layerD->getLayerSnapshot()->relativeLayerMetadata, expectedLayerDRelativeMetadata);
+ auto expectedLayerCRelativeMetadata =
+ LayerMetadataBuilder()
+ // From layer A, parent of relative parent
+ .setInt32(METADATA_OWNER_UID, 1)
+ // From layer B, relative parent of relative parent
+ .setInt32(METADATA_OWNER_PID, 3)
+ // From layer D, relative parent
+ .setInt32(METADATA_TASK_ID, 4)
+ // added by layer creation args
+ .setInt32(gui::METADATA_CALLING_UID,
+ layerDMetadata.getInt32(gui::METADATA_CALLING_UID, 0))
+ .build();
+ EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, expectedLayerCRelativeMetadata);
+}
+
+TEST_F(SurfaceFlingerUpdateLayerMetadataSnapshotTest,
+ updatesRelativeMetadataMultipleRelativeChildren) {
+ auto layerAMetadata = LayerMetadataBuilder().setInt32(METADATA_OWNER_UID, 1).build();
+ auto layerA = createLayer("layer-a", layerAMetadata);
+ auto layerAHandle = layerA->getHandle();
+ LayerMetadata layerBMetadata;
+ auto layerB = createLayer("layer-b", layerBMetadata);
+ LayerMetadata layerCMetadata;
+ auto layerC = createLayer("layer-c", layerCMetadata);
+ layerB->setRelativeLayer(layerAHandle, 1);
+ layerC->setRelativeLayer(layerAHandle, 2);
+ layerA->commitChildList();
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerA);
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerB);
+ mFlinger.mutableDrawingState().layersSortedByZ.add(layerC);
+
+ mFlinger.updateLayerMetadataSnapshot();
+
+ EXPECT_EQ(layerA->getLayerSnapshot()->relativeLayerMetadata, LayerMetadata{});
+ EXPECT_EQ(layerB->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
+ EXPECT_EQ(layerC->getLayerSnapshot()->relativeLayerMetadata, layerAMetadata);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TestableScheduler.h b/services/surfaceflinger/tests/unittests/TestableScheduler.h
index 4708572dc4..c0255d3e98 100644
--- a/services/surfaceflinger/tests/unittests/TestableScheduler.h
+++ b/services/surfaceflinger/tests/unittests/TestableScheduler.h
@@ -16,15 +16,19 @@
#pragma once
-#include <Scheduler/Scheduler.h>
+#include <ftl/fake_guard.h>
#include <gmock/gmock.h>
#include <gui/ISurfaceComposer.h>
+#include <scheduler/interface/ICompositor.h>
+
#include "Scheduler/EventThread.h"
#include "Scheduler/LayerHistory.h"
#include "Scheduler/Scheduler.h"
#include "Scheduler/VSyncTracker.h"
#include "Scheduler/VsyncController.h"
+#include "Scheduler/VsyncSchedule.h"
+#include "mock/MockVSyncDispatch.h"
#include "mock/MockVSyncTracker.h"
#include "mock/MockVsyncController.h"
@@ -32,17 +36,24 @@ namespace android::scheduler {
class TestableScheduler : public Scheduler, private ICompositor {
public:
- TestableScheduler(std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback& callback)
+ TestableScheduler(RefreshRateSelectorPtr selectorPtr, ISchedulerCallback& callback,
+ IVsyncTrackerCallback& vsyncTrackerCallback)
: TestableScheduler(std::make_unique<mock::VsyncController>(),
- std::make_unique<mock::VSyncTracker>(), std::move(configs),
- callback) {}
+ std::make_shared<mock::VSyncTracker>(), std::move(selectorPtr),
+ sp<VsyncModulator>::make(VsyncConfigSet{}), callback,
+ vsyncTrackerCallback) {}
TestableScheduler(std::unique_ptr<VsyncController> controller,
- std::unique_ptr<VSyncTracker> tracker,
- std::shared_ptr<RefreshRateConfigs> configs, ISchedulerCallback& callback)
- : Scheduler(*this, callback, Feature::kContentDetection) {
- mVsyncSchedule.emplace(VsyncSchedule(std::move(tracker), nullptr, std::move(controller)));
- setRefreshRateConfigs(std::move(configs));
+ std::shared_ptr<VSyncTracker> tracker, RefreshRateSelectorPtr selectorPtr,
+ sp<VsyncModulator> modulatorPtr, ISchedulerCallback& schedulerCallback,
+ IVsyncTrackerCallback& vsyncTrackerCallback)
+ : Scheduler(*this, schedulerCallback,
+ (FeatureFlags)Feature::kContentDetection |
+ Feature::kSmallDirtyContentDetection,
+ std::move(modulatorPtr), vsyncTrackerCallback) {
+ const auto displayId = selectorPtr->getActiveMode().modePtr->getPhysicalDisplayId();
+ registerDisplay(displayId, std::move(selectorPtr), std::move(controller),
+ std::move(tracker));
ON_CALL(*this, postMessage).WillByDefault([](sp<MessageHandler>&& handler) {
// Execute task to prevent broken promise exception on destruction.
@@ -50,41 +61,100 @@ public:
});
}
+ MOCK_METHOD(void, scheduleConfigure, (), (override));
MOCK_METHOD(void, scheduleFrame, (), (override));
MOCK_METHOD(void, postMessage, (sp<MessageHandler>&&), (override));
+ void doFrameSignal(ICompositor& compositor, VsyncId vsyncId) {
+ ftl::FakeGuard guard1(kMainThreadContext);
+ ftl::FakeGuard guard2(mDisplayLock);
+ Scheduler::onFrameSignal(compositor, vsyncId, TimePoint());
+ }
+
// Used to inject mock event thread.
ConnectionHandle createConnection(std::unique_ptr<EventThread> eventThread) {
return Scheduler::createConnection(std::move(eventThread));
}
- /* ------------------------------------------------------------------------
- * Read-write access to private data to set up preconditions and assert
- * post-conditions.
- */
- auto& mutablePrimaryHWVsyncEnabled() { return mPrimaryHWVsyncEnabled; }
- auto& mutableHWVsyncAvailable() { return mHWVsyncAvailable; }
+ auto refreshRateSelector() { return pacesetterSelectorPtr(); }
+
+ void registerDisplay(
+ PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+ std::shared_ptr<VSyncTracker> vsyncTracker = std::make_shared<mock::VSyncTracker>()) {
+ registerDisplay(displayId, std::move(selectorPtr),
+ std::make_unique<mock::VsyncController>(), vsyncTracker);
+ }
+
+ void registerDisplay(PhysicalDisplayId displayId, RefreshRateSelectorPtr selectorPtr,
+ std::unique_ptr<VsyncController> controller,
+ std::shared_ptr<VSyncTracker> tracker) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ Scheduler::registerDisplayInternal(displayId, std::move(selectorPtr),
+ std::shared_ptr<VsyncSchedule>(
+ new VsyncSchedule(displayId, std::move(tracker),
+ std::make_shared<
+ mock::VSyncDispatch>(),
+ std::move(controller),
+ mockRequestHardwareVsync
+ .AsStdFunction())));
+ }
+
+ testing::MockFunction<void(PhysicalDisplayId, bool)> mockRequestHardwareVsync;
+
+ void unregisterDisplay(PhysicalDisplayId displayId) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ Scheduler::unregisterDisplay(displayId);
+ }
+
+ std::optional<PhysicalDisplayId> pacesetterDisplayId() const NO_THREAD_SAFETY_ANALYSIS {
+ return mPacesetterDisplayId;
+ }
+
+ void setPacesetterDisplay(PhysicalDisplayId displayId) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ Scheduler::setPacesetterDisplay(displayId);
+ }
+
+ std::optional<hal::PowerMode> getDisplayPowerMode(PhysicalDisplayId id) {
+ ftl::FakeGuard guard1(kMainThreadContext);
+ ftl::FakeGuard guard2(mDisplayLock);
+ return mDisplays.get(id).transform(
+ [](const Display& display) { return display.powerMode; });
+ }
+
+ using Scheduler::resyncAllToHardwareVsync;
+ auto& mutableAppConnectionHandle() { return mAppConnectionHandle; }
auto& mutableLayerHistory() { return mLayerHistory; }
+ auto& mutableAttachedChoreographers() { return mAttachedChoreographers; }
size_t layerHistorySize() NO_THREAD_SAFETY_ANALYSIS {
return mLayerHistory.mActiveLayerInfos.size() + mLayerHistory.mInactiveLayerInfos.size();
}
- auto refreshRateConfigs() { return holdRefreshRateConfigs(); }
-
size_t getNumActiveLayers() NO_THREAD_SAFETY_ANALYSIS {
return mLayerHistory.mActiveLayerInfos.size();
}
- void replaceTouchTimer(int64_t millis) {
+ void replaceTouchTimer(int64_t millis,
+ std::function<void(bool isReset)>&& testCallback = nullptr) {
if (mTouchTimer) {
mTouchTimer.reset();
}
mTouchTimer.emplace(
"Testable Touch timer", std::chrono::milliseconds(millis),
- [this] { touchTimerCallback(TimerState::Reset); },
- [this] { touchTimerCallback(TimerState::Expired); });
+ [this, testCallback] {
+ touchTimerCallback(TimerState::Reset);
+ if (testCallback != nullptr) {
+ testCallback(true);
+ }
+ },
+ [this, testCallback] {
+ touchTimerCallback(TimerState::Expired);
+ if (testCallback != nullptr) {
+ testCallback(false);
+ }
+ });
mTouchTimer->start();
}
@@ -93,9 +163,27 @@ public:
return mPolicy.touch == Scheduler::TouchState::Active;
}
+ void setTouchStateAndIdleTimerPolicy(GlobalSignals globalSignals) {
+ std::lock_guard<std::mutex> lock(mPolicyLock);
+ mPolicy.touch = globalSignals.touch ? TouchState::Active : TouchState::Inactive;
+ mPolicy.idleTimer = globalSignals.idle ? TimerState::Expired : TimerState::Reset;
+ }
+
+ void setContentRequirements(std::vector<RefreshRateSelector::LayerRequirement> layers) {
+ std::lock_guard<std::mutex> lock(mPolicyLock);
+ mPolicy.contentRequirements = std::move(layers);
+ }
+
+ using Scheduler::DisplayModeChoice;
+ using Scheduler::DisplayModeChoiceMap;
+
+ DisplayModeChoiceMap chooseDisplayModes() NO_THREAD_SAFETY_ANALYSIS {
+ return Scheduler::chooseDisplayModes();
+ }
+
void dispatchCachedReportedMode() {
std::lock_guard<std::mutex> lock(mPolicyLock);
- return Scheduler::dispatchCachedReportedMode();
+ Scheduler::dispatchCachedReportedMode();
}
void clearCachedReportedMode() {
@@ -103,14 +191,33 @@ public:
mPolicy.cachedModeChangedParams.reset();
}
- void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, DisplayModePtr mode) {
- return Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
+ void onNonPrimaryDisplayModeChanged(ConnectionHandle handle, const FrameRateMode& mode) {
+ Scheduler::onNonPrimaryDisplayModeChanged(handle, mode);
+ }
+
+ void setInitialHwVsyncEnabled(PhysicalDisplayId id, bool enabled) {
+ auto schedule = getVsyncSchedule(id);
+ std::lock_guard<std::mutex> lock(schedule->mHwVsyncLock);
+ schedule->mHwVsyncState = enabled ? VsyncSchedule::HwVsyncState::Enabled
+ : VsyncSchedule::HwVsyncState::Disabled;
}
+ void updateAttachedChoreographers(
+ const surfaceflinger::frontend::LayerHierarchy& layerHierarchy,
+ Fps displayRefreshRate) {
+ Scheduler::updateAttachedChoreographers(layerHierarchy, displayRefreshRate);
+ }
+
+ using Scheduler::onHardwareVsyncRequest;
+
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(PhysicalDisplayId, const scheduler::FrameTargets&) override { return false; }
+ CompositeResultsPerDisplay composite(PhysicalDisplayId,
+ const scheduler::FrameTargeters&) override {
+ return {};
+ }
void sample() override {}
};
diff --git a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
index 283f9ca77b..22cb24bb6f 100644
--- a/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
+++ b/services/surfaceflinger/tests/unittests/TestableSurfaceFlinger.h
@@ -20,6 +20,11 @@
#include <chrono>
#include <variant>
+#include <ftl/fake_guard.h>
+#include <ftl/match.h>
+#include <gui/ScreenCaptureResults.h>
+#include <ui/DynamicDisplayInfo.h>
+
#include <compositionengine/Display.h>
#include <compositionengine/LayerFECompositionState.h>
#include <compositionengine/OutputLayer.h>
@@ -27,33 +32,39 @@
#include <compositionengine/impl/Display.h>
#include <compositionengine/impl/OutputLayerCompositionState.h>
#include <compositionengine/mock/DisplaySurface.h>
-#include <gui/ScreenCaptureResults.h>
-#include "BufferQueueLayer.h"
-#include "BufferStateLayer.h"
-#include "ContainerLayer.h"
#include "DisplayDevice.h"
-#include "EffectLayer.h"
#include "FakeVsyncConfiguration.h"
#include "FrameTracer/FrameTracer.h"
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/LayerHandle.h"
#include "Layer.h"
#include "NativeWindowSurface.h"
+#include "RenderArea.h"
#include "Scheduler/MessageQueue.h"
-#include "Scheduler/RefreshRateConfigs.h"
+#include "Scheduler/RefreshRateSelector.h"
#include "StartPropertySetThread.h"
#include "SurfaceFlinger.h"
-#include "SurfaceFlingerDefaultFactory.h"
-#include "SurfaceInterceptor.h"
#include "TestableScheduler.h"
#include "mock/DisplayHardware/MockComposer.h"
#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/DisplayHardware/MockPowerAdvisor.h"
+#include "mock/MockEventThread.h"
#include "mock/MockFrameTimeline.h"
#include "mock/MockFrameTracer.h"
#include "mock/MockSchedulerCallback.h"
+#include "mock/MockVsyncTrackerCallback.h"
+#include "mock/system/window/MockNativeWindow.h"
+
+#include "Scheduler/VSyncTracker.h"
+#include "Scheduler/VsyncController.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+#include "mock/MockVsyncController.h"
namespace android {
-class EventThread;
+struct DisplayStatInfo;
namespace renderengine {
@@ -84,22 +95,18 @@ public:
return std::make_unique<scheduler::FakePhaseOffsets>();
}
- sp<SurfaceInterceptor> createSurfaceInterceptor() override {
- return new android::impl::SurfaceInterceptor();
- }
-
sp<StartPropertySetThread> createStartPropertySetThread(bool timestampPropertyValue) override {
- return new StartPropertySetThread(timestampPropertyValue);
+ return sp<StartPropertySetThread>::make(timestampPropertyValue);
}
sp<DisplayDevice> createDisplayDevice(DisplayDeviceCreationArgs& creationArgs) override {
- return new DisplayDevice(creationArgs);
+ return sp<DisplayDevice>::make(creationArgs);
}
sp<GraphicBuffer> 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<GraphicBuffer>::make(width, height, format, layerCount, usage, requestorName);
}
void createBufferQueue(sp<IGraphicBufferProducer>* outProducer,
@@ -112,18 +119,6 @@ public:
mCreateBufferQueue(outProducer, outConsumer, consumerIsSurfaceFlinger);
}
- sp<IGraphicBufferProducer> createMonitoredProducer(const sp<IGraphicBufferProducer>& producer,
- const sp<SurfaceFlinger>& flinger,
- const wp<Layer>& layer) override {
- return new MonitoredProducer(producer, flinger, layer);
- }
-
- sp<BufferLayerConsumer> createBufferLayerConsumer(const sp<IGraphicBufferConsumer>& consumer,
- renderengine::RenderEngine& renderEngine,
- uint32_t textureName, Layer* layer) override {
- return new BufferLayerConsumer(consumer, renderEngine, textureName, layer);
- }
-
std::unique_ptr<surfaceflinger::NativeWindowSurface> createNativeWindowSurface(
const sp<IGraphicBufferProducer>& producer) override {
if (!mCreateNativeWindowSurface) return nullptr;
@@ -134,18 +129,12 @@ public:
return compositionengine::impl::createCompositionEngine();
}
- sp<BufferQueueLayer> createBufferQueueLayer(const LayerCreationArgs&) override {
- return nullptr;
- }
-
- sp<BufferStateLayer> createBufferStateLayer(const LayerCreationArgs&) override {
- return nullptr;
- }
+ sp<Layer> createBufferStateLayer(const LayerCreationArgs&) override { return nullptr; }
- sp<EffectLayer> createEffectLayer(const LayerCreationArgs&) override { return nullptr; }
+ sp<Layer> createEffectLayer(const LayerCreationArgs&) override { return nullptr; }
- sp<ContainerLayer> createContainerLayer(const LayerCreationArgs&) override {
- return nullptr;
+ sp<LayerFE> createLayerFE(const std::string& layerName) override {
+ return sp<LayerFE>::make(layerName);
}
std::unique_ptr<FrameTracer> createFrameTracer() override {
@@ -173,6 +162,11 @@ public:
CreateCompositionEngineFunction mCreateCompositionEngine;
};
+struct MockSchedulerOptions {
+ PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0);
+ bool useNiceMock = false;
+};
+
} // namespace surfaceflinger::test
class TestableSurfaceFlinger {
@@ -183,7 +177,6 @@ public:
if (!mFlinger) {
mFlinger = sp<SurfaceFlinger>::make(mFactory, SurfaceFlinger::SkipInitialization);
}
- mFlinger->mAnimationTransactionTimeout = ms2ns(10);
}
SurfaceFlinger* flinger() { return mFlinger.get(); }
@@ -193,7 +186,8 @@ public:
// functions.
void setupRenderEngine(std::unique_ptr<renderengine::RenderEngine> renderEngine) {
- mFlinger->mCompositionEngine->setRenderEngine(std::move(renderEngine));
+ mFlinger->mRenderEngine = std::move(renderEngine);
+ mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
}
void setupComposer(std::unique_ptr<Hwc2::Composer> composer) {
@@ -211,77 +205,122 @@ public:
enum class SchedulerCallbackImpl { kNoOp, kMock };
- static constexpr struct OneDisplayMode {
- } kOneDisplayMode;
-
- static constexpr struct TwoDisplayModes {
- } kTwoDisplayModes;
-
- using RefreshRateConfigsPtr = std::shared_ptr<scheduler::RefreshRateConfigs>;
-
- using DisplayModesVariant =
- std::variant<OneDisplayMode, TwoDisplayModes, RefreshRateConfigsPtr>;
-
- void setupScheduler(std::unique_ptr<scheduler::VsyncController> vsyncController,
- std::unique_ptr<scheduler::VSyncTracker> vsyncTracker,
- std::unique_ptr<EventThread> appEventThread,
- std::unique_ptr<EventThread> sfEventThread,
- SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
- DisplayModesVariant modesVariant = kOneDisplayMode,
- bool useNiceMock = false) {
- RefreshRateConfigsPtr configs;
- if (std::holds_alternative<RefreshRateConfigsPtr>(modesVariant)) {
- configs = std::move(std::get<RefreshRateConfigsPtr>(modesVariant));
- } else {
- constexpr DisplayModeId kModeId60{0};
- DisplayModes modes = makeModes(mock::createDisplayMode(kModeId60, 60_Hz));
+ enum class VsyncTrackerCallbackImpl { kNoOp, kMock };
- if (std::holds_alternative<TwoDisplayModes>(modesVariant)) {
- constexpr DisplayModeId kModeId90{1};
- modes.try_emplace(kModeId90, mock::createDisplayMode(kModeId90, 90_Hz));
- }
-
- configs = std::make_shared<scheduler::RefreshRateConfigs>(modes, kModeId60);
- }
+ struct DefaultDisplayMode {
+ // The ID of the injected RefreshRateSelector and its default display mode.
+ PhysicalDisplayId displayId;
+ };
- const auto fps = configs->getActiveMode()->getFps();
+ using RefreshRateSelectorPtr = scheduler::Scheduler::RefreshRateSelectorPtr;
+
+ using DisplayModesVariant = std::variant<DefaultDisplayMode, RefreshRateSelectorPtr>;
+
+ void setupScheduler(
+ std::unique_ptr<scheduler::VsyncController> vsyncController,
+ std::shared_ptr<scheduler::VSyncTracker> vsyncTracker,
+ std::unique_ptr<EventThread> appEventThread, std::unique_ptr<EventThread> sfEventThread,
+ DisplayModesVariant modesVariant,
+ SchedulerCallbackImpl callbackImpl = SchedulerCallbackImpl::kNoOp,
+ VsyncTrackerCallbackImpl vsyncTrackerCallbackImpl = VsyncTrackerCallbackImpl::kNoOp,
+ bool useNiceMock = false) {
+ RefreshRateSelectorPtr selectorPtr = ftl::match(
+ modesVariant,
+ [](DefaultDisplayMode arg) {
+ constexpr DisplayModeId kModeId60{0};
+ return std::make_shared<scheduler::RefreshRateSelector>(
+ makeModes(mock::createDisplayMode(arg.displayId, kModeId60, 60_Hz)),
+ kModeId60);
+ },
+ [](RefreshRateSelectorPtr selectorPtr) { return selectorPtr; });
+
+ const auto fps = selectorPtr->getActiveMode().fps;
mFlinger->mVsyncConfiguration = mFactory.createVsyncConfiguration(fps);
- mFlinger->mVsyncModulator = sp<scheduler::VsyncModulator>::make(
- mFlinger->mVsyncConfiguration->getCurrentConfigs());
mFlinger->mRefreshRateStats =
std::make_unique<scheduler::RefreshRateStats>(*mFlinger->mTimeStats, fps,
hal::PowerMode::OFF);
- using Callback = scheduler::ISchedulerCallback;
- Callback& callback = callbackImpl == SchedulerCallbackImpl::kNoOp
- ? static_cast<Callback&>(mNoOpSchedulerCallback)
- : static_cast<Callback&>(mSchedulerCallback);
+ mTokenManager = std::make_unique<frametimeline::impl::TokenManager>();
+
+ using ISchedulerCallback = scheduler::ISchedulerCallback;
+ ISchedulerCallback& schedulerCallback = callbackImpl == SchedulerCallbackImpl::kNoOp
+ ? static_cast<ISchedulerCallback&>(mNoOpSchedulerCallback)
+ : static_cast<ISchedulerCallback&>(mSchedulerCallback);
+
+ using VsyncTrackerCallback = scheduler::IVsyncTrackerCallback;
+ VsyncTrackerCallback& vsyncTrackerCallback =
+ vsyncTrackerCallbackImpl == VsyncTrackerCallbackImpl::kNoOp
+ ? static_cast<VsyncTrackerCallback&>(mNoOpVsyncTrackerCallback)
+ : static_cast<VsyncTrackerCallback&>(mVsyncTrackerCallback);
+
+ auto modulatorPtr = sp<scheduler::VsyncModulator>::make(
+ mFlinger->mVsyncConfiguration->getCurrentConfigs());
if (useNiceMock) {
mScheduler =
new testing::NiceMock<scheduler::TestableScheduler>(std::move(vsyncController),
std::move(vsyncTracker),
- std::move(configs),
- callback);
+ std::move(selectorPtr),
+ std::move(modulatorPtr),
+ schedulerCallback,
+ vsyncTrackerCallback);
} else {
mScheduler = new scheduler::TestableScheduler(std::move(vsyncController),
std::move(vsyncTracker),
- std::move(configs), callback);
+ std::move(selectorPtr),
+ std::move(modulatorPtr),
+ schedulerCallback, vsyncTrackerCallback);
}
- mFlinger->mAppConnectionHandle = mScheduler->createConnection(std::move(appEventThread));
+ mScheduler->initVsync(mScheduler->getVsyncSchedule()->getDispatch(), *mTokenManager, 0ms);
+
+ mScheduler->mutableAppConnectionHandle() =
+ mScheduler->createConnection(std::move(appEventThread));
+
+ mFlinger->mAppConnectionHandle = mScheduler->mutableAppConnectionHandle();
mFlinger->mSfConnectionHandle = mScheduler->createConnection(std::move(sfEventThread));
resetScheduler(mScheduler);
}
+ void setupMockScheduler(surfaceflinger::test::MockSchedulerOptions options = {}) {
+ using testing::_;
+ using testing::Return;
+
+ auto eventThread = makeMock<mock::EventThread>(options.useNiceMock);
+ auto sfEventThread = makeMock<mock::EventThread>(options.useNiceMock);
+
+ EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
+ EXPECT_CALL(*eventThread, createEventConnection(_, _))
+ .WillOnce(Return(sp<EventThreadConnection>::make(eventThread.get(),
+ mock::EventThread::kCallingUid)));
+
+ EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
+ EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
+ .WillOnce(Return(sp<EventThreadConnection>::make(sfEventThread.get(),
+ mock::EventThread::kCallingUid)));
+
+ auto vsyncController = makeMock<mock::VsyncController>(options.useNiceMock);
+ auto vsyncTracker = makeSharedMock<mock::VSyncTracker>(options.useNiceMock);
+
+ EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+ EXPECT_CALL(*vsyncTracker, currentPeriod())
+ .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
+ EXPECT_CALL(*vsyncTracker, minFramePeriod())
+ .WillRepeatedly(
+ Return(Period::fromNs(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD)));
+ EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
+ setupScheduler(std::move(vsyncController), std::move(vsyncTracker), std::move(eventThread),
+ std::move(sfEventThread), DefaultDisplayMode{options.displayId},
+ SchedulerCallbackImpl::kNoOp, VsyncTrackerCallbackImpl::kNoOp,
+ options.useNiceMock);
+ }
+
void resetScheduler(scheduler::Scheduler* scheduler) { mFlinger->mScheduler.reset(scheduler); }
scheduler::TestableScheduler& mutableScheduler() { return *mScheduler; }
scheduler::mock::SchedulerCallback& mockSchedulerCallback() { return mSchedulerCallback; }
- auto& mutableVsyncModulator() { return mFlinger->mVsyncModulator; }
-
using CreateBufferQueueFunction = surfaceflinger::test::Factory::CreateBufferQueueFunction;
void setCreateBufferQueueFunction(CreateBufferQueueFunction f) {
mFactory.mCreateBufferQueue = f;
@@ -310,7 +349,7 @@ public:
const sp<NativeHandle>& sidebandStream) {
layer->mDrawingState.sidebandStream = sidebandStream;
layer->mSidebandStream = sidebandStream;
- layer->editCompositionState()->sidebandStream = sidebandStream;
+ layer->editLayerSnapshot()->sidebandStream = sidebandStream;
}
void setLayerCompositionType(const sp<Layer>& layer,
@@ -330,39 +369,74 @@ public:
layer->mDrawingParent = drawingParent;
}
- void setPowerHintSessionMode(bool early, bool late) {
- mFlinger->mPowerHintSessionMode = {.late = late, .early = early};
- }
-
/* ------------------------------------------------------------------------
* Forwarding for functions being tested
*/
- nsecs_t commit(nsecs_t frameTime, int64_t vsyncId, nsecs_t expectedVSyncTime) {
- mFlinger->commit(frameTime, vsyncId, expectedVSyncTime);
- return frameTime;
+ void configure() {
+ ftl::FakeGuard guard(kMainThreadContext);
+ mFlinger->configure();
+ }
+
+ void configureAndCommit() {
+ configure();
+ commitTransactionsLocked(eDisplayTransactionNeeded);
+ }
+
+ void commit(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime,
+ bool composite = false) {
+ ftl::FakeGuard guard(kMainThreadContext);
+
+ const auto displayIdOpt = mScheduler->pacesetterDisplayId();
+ LOG_ALWAYS_FATAL_IF(!displayIdOpt);
+ const auto displayId = *displayIdOpt;
+
+ constexpr bool kBackpressureGpuComposition = true;
+ scheduler::FrameTargeter frameTargeter(displayId, kBackpressureGpuComposition);
+
+ frameTargeter.beginFrame({.frameBeginTime = frameTime,
+ .vsyncId = vsyncId,
+ .expectedVsyncTime = expectedVsyncTime,
+ .sfWorkDuration = 10ms},
+ *mScheduler->getVsyncSchedule());
+
+ scheduler::FrameTargets targets;
+ scheduler::FrameTargeters targeters;
+
+ for (const auto& [id, display] :
+ FTL_FAKE_GUARD(mFlinger->mStateLock, mFlinger->mPhysicalDisplays)) {
+ targets.try_emplace(id, &frameTargeter.target());
+ targeters.try_emplace(id, &frameTargeter);
+ }
+
+ mFlinger->commit(displayId, targets);
+
+ if (composite) {
+ mFlinger->composite(displayId, targeters);
+ }
}
- nsecs_t commit(nsecs_t frameTime, int64_t vsyncId) {
- std::chrono::nanoseconds period = 10ms;
- return commit(frameTime, vsyncId, frameTime + period.count());
+ void commit(TimePoint frameTime, VsyncId vsyncId, bool composite = false) {
+ return commit(frameTime, vsyncId, frameTime + Period(10ms), composite);
}
- nsecs_t commit() {
- const nsecs_t now = systemTime();
- const nsecs_t expectedVsyncTime = now + 10'000'000;
- return commit(now, kVsyncId, expectedVsyncTime);
+ void commit(bool composite = false) {
+ const TimePoint frameTime = scheduler::SchedulerClock::now();
+ commit(frameTime, kVsyncId, composite);
}
- void commitAndComposite(const nsecs_t frameTime, const int64_t vsyncId,
- const nsecs_t expectedVsyncTime) {
- mFlinger->composite(commit(frameTime, vsyncId, expectedVsyncTime), kVsyncId);
+ void commitAndComposite(TimePoint frameTime, VsyncId vsyncId, TimePoint expectedVsyncTime) {
+ constexpr bool kComposite = true;
+ commit(frameTime, vsyncId, expectedVsyncTime, kComposite);
}
- void commitAndComposite() { mFlinger->composite(commit(), kVsyncId); }
+ void commitAndComposite() {
+ constexpr bool kComposite = true;
+ commit(kComposite);
+ }
- auto createDisplay(const String8& displayName, bool secure) {
- return mFlinger->createDisplay(displayName, secure);
+ auto createDisplay(const String8& displayName, bool secure, float requestedRefreshRate = 0.0f) {
+ return mFlinger->createDisplay(displayName, secure, requestedRefreshRate);
}
auto destroyDisplay(const sp<IBinder>& displayToken) {
@@ -386,13 +460,14 @@ public:
dispSurface, producer);
}
- auto commitTransactionsLocked(uint32_t transactionFlags) {
+ void commitTransactionsLocked(uint32_t transactionFlags) {
Mutex::Autolock lock(mFlinger->mStateLock);
- return mFlinger->commitTransactionsLocked(transactionFlags);
+ ftl::FakeGuard guard(kMainThreadContext);
+ mFlinger->commitTransactionsLocked(transactionFlags);
}
- void onComposerHalHotplug(hal::HWDisplayId hwcDisplayId, hal::Connection connection) {
- mFlinger->onComposerHalHotplug(hwcDisplayId, connection);
+ void onComposerHalHotplugEvent(hal::HWDisplayId hwcDisplayId, DisplayHotplugEvent event) {
+ mFlinger->onComposerHalHotplugEvent(hwcDisplayId, event);
}
auto setDisplayStateLocked(const DisplayState& s) {
@@ -400,10 +475,7 @@ public:
return mFlinger->setDisplayStateLocked(s);
}
- // 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(); }
auto notifyPowerBoost(int32_t boostId) { return mFlinger->notifyPowerBoost(boostId); }
@@ -418,19 +490,23 @@ public:
return mFlinger->setPowerModeInternal(display, mode);
}
- auto renderScreenImpl(const RenderArea& renderArea,
- SurfaceFlinger::TraverseLayersFunction traverseLayers,
- const std::shared_ptr<renderengine::ExternalTexture>& buffer,
- bool forSystem, bool regionSampling) {
+ auto renderScreenImpl(std::shared_ptr<const RenderArea> renderArea,
+ SurfaceFlinger::GetLayerSnapshotsFunction traverseLayers,
+ const std::shared_ptr<renderengine::ExternalTexture>& buffer,
+ bool regionSampling) {
ScreenCaptureResults captureResults;
- return mFlinger->renderScreenImpl(renderArea, traverseLayers, buffer, forSystem,
- regionSampling, false /* grayscale */,
- captureResults);
+ return FTL_FAKE_GUARD(kMainThreadContext,
+ mFlinger->renderScreenImpl(std::move(renderArea), traverseLayers,
+ buffer, regionSampling,
+ false /* grayscale */,
+ false /* isProtected */, captureResults));
}
auto traverseLayersInLayerStack(ui::LayerStack layerStack, int32_t uid,
+ std::unordered_set<uint32_t> excludeLayerIds,
const LayerVector::Visitor& visitor) {
- return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid, visitor);
+ return mFlinger->SurfaceFlinger::traverseLayersInLayerStack(layerStack, uid,
+ excludeLayerIds, visitor);
}
auto getDisplayNativePrimaries(const sp<IBinder>& displayToken,
@@ -438,107 +514,157 @@ public:
return mFlinger->SurfaceFlinger::getDisplayNativePrimaries(displayToken, primaries);
}
- auto& getTransactionQueue() { return mFlinger->mTransactionQueue; }
- auto& getPendingTransactionQueue() { return mFlinger->mPendingTransactionQueues; }
- auto& getTransactionCommittedSignals() { return mFlinger->mTransactionCommittedSignals; }
+ auto& getTransactionQueue() { return mFlinger->mTransactionHandler.mLocklessTransactionQueue; }
+ auto& getPendingTransactionQueue() {
+ return mFlinger->mTransactionHandler.mPendingTransactionQueues;
+ }
+ size_t getPendingTransactionCount() {
+ return mFlinger->mTransactionHandler.mPendingTransactionCount.load();
+ }
auto setTransactionState(
- const FrameTimelineInfo& frameTimelineInfo, const Vector<ComposerState>& states,
+ const FrameTimelineInfo& frameTimelineInfo, Vector<ComposerState>& states,
const Vector<DisplayState>& displays, uint32_t flags, const sp<IBinder>& applyToken,
const InputWindowCommands& inputWindowCommands, int64_t desiredPresentTime,
- bool isAutoTimestamp, const client_cache_t& uncacheBuffer, bool hasListenerCallbacks,
- std::vector<ListenerCallbacks>& listenerCallbacks, uint64_t transactionId) {
+ bool isAutoTimestamp, const std::vector<client_cache_t>& uncacheBuffers,
+ bool hasListenerCallbacks, std::vector<ListenerCallbacks>& listenerCallbacks,
+ uint64_t transactionId, const std::vector<uint64_t>& 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 setTransactionStateInternal(TransactionState& transaction) {
+ return mFlinger->mTransactionHandler.queueTransaction(std::move(transaction));
+ }
+
+ auto flushTransactionQueues() {
+ return FTL_FAKE_GUARD(kMainThreadContext, mFlinger->flushTransactionQueues(kVsyncId));
+ }
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 {
return SurfaceFlinger::calculateMaxAcquiredBufferCount(refreshRate, presentLatency);
}
- auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken, ui::DisplayModeId defaultMode,
- bool allowGroupSwitching, float primaryRefreshRateMin,
- float primaryRefreshRateMax, float appRequestRefreshRateMin,
- float appRequestRefreshRateMax) {
- return mFlinger->setDesiredDisplayModeSpecs(displayToken, defaultMode, allowGroupSwitching,
- primaryRefreshRateMin, primaryRefreshRateMax,
- appRequestRefreshRateMin,
- appRequestRefreshRateMax);
+ auto setDesiredDisplayModeSpecs(const sp<IBinder>& displayToken,
+ const gui::DisplayModeSpecs& specs) {
+ return mFlinger->setDesiredDisplayModeSpecs(displayToken, specs);
}
- void onActiveDisplayChanged(const sp<DisplayDevice>& activeDisplay) {
+ void onActiveDisplayChanged(const DisplayDevice* inactiveDisplayPtr,
+ const DisplayDevice& activeDisplay) {
Mutex::Autolock lock(mFlinger->mStateLock);
- mFlinger->onActiveDisplayChangedLocked(activeDisplay);
+ ftl::FakeGuard guard(kMainThreadContext);
+ mFlinger->onActiveDisplayChangedLocked(inactiveDisplayPtr, activeDisplay);
}
- auto createLayer(LayerCreationArgs& args, sp<IBinder>* outHandle,
- const sp<IBinder>& parentHandle, int32_t* outLayerId,
- const sp<Layer>& parentLayer, uint32_t* outTransformHint) {
- return mFlinger->createLayer(args, outHandle, parentHandle, outLayerId, parentLayer,
- outTransformHint);
+ auto createLayer(LayerCreationArgs& args, const sp<IBinder>& parentHandle,
+ gui::CreateSurfaceResult& outResult) {
+ args.parentHandle = parentHandle;
+ return mFlinger->createLayer(args, outResult);
}
auto mirrorLayer(const LayerCreationArgs& args, const sp<IBinder>& mirrorFromHandle,
- sp<IBinder>* outHandle, int32_t* outLayerId) {
- return mFlinger->mirrorLayer(args, mirrorFromHandle, outHandle, outLayerId);
+ gui::CreateSurfaceResult& outResult) {
+ return mFlinger->mirrorLayer(args, mirrorFromHandle, outResult);
+ }
+
+ void updateLayerMetadataSnapshot() { mFlinger->updateLayerMetadataSnapshot(); }
+
+ void getDynamicDisplayInfoFromToken(const sp<IBinder>& displayToken,
+ ui::DynamicDisplayInfo* dynamicDisplayInfo) {
+ mFlinger->getDynamicDisplayInfoFromToken(displayToken, dynamicDisplayInfo);
+ }
+
+ sp<DisplayDevice> createVirtualDisplayDevice(const sp<IBinder> displayToken,
+ VirtualDisplayId displayId,
+ float requestedRefreshRate) {
+ constexpr ui::Size kResolution = {1080, 1920};
+ auto compositionDisplay = compositionengine::impl::
+ createDisplay(mFlinger->getCompositionEngine(),
+ compositionengine::DisplayCreationArgsBuilder()
+ .setId(displayId)
+ .setPixels(kResolution)
+ .setPowerAdvisor(&mPowerAdvisor)
+ .build());
+ DisplayDeviceCreationArgs creationArgs(mFlinger, mFlinger->getHwComposer(), displayToken,
+ compositionDisplay);
+ creationArgs.requestedRefreshRate = Fps::fromValue(requestedRefreshRate);
+ creationArgs.nativeWindow = sp<mock::NativeWindow>::make();
+ return sp<DisplayDevice>::make(creationArgs);
+ }
+
+ status_t getDisplayStats(const sp<IBinder>& displayToken, DisplayStatInfo* outInfo) {
+ return mFlinger->getDisplayStats(displayToken, outInfo);
}
/* ------------------------------------------------------------------------
* Read-only access to private data to assert post-conditions.
*/
- const auto& getAnimFrameTracker() const { return mFlinger->mAnimFrameTracker; }
- const auto& getHasPoweredOff() const { return mFlinger->mHasPoweredOff; }
const auto& getVisibleRegionsDirty() const { return mFlinger->mVisibleRegionsDirty; }
auto& getHwComposer() const {
return static_cast<impl::HWComposer&>(mFlinger->getHwComposer());
}
auto& getCompositionEngine() const { return mFlinger->getCompositionEngine(); }
- const auto& getCompositorTiming() const { return mFlinger->getBE().mCompositorTiming; }
-
mock::FrameTracer* getFrameTracer() const {
return static_cast<mock::FrameTracer*>(mFlinger->mFrameTracer.get());
}
- nsecs_t getAnimationTransactionTimeout() const {
- return mFlinger->mAnimationTransactionTimeout;
- }
+ void injectLegacyLayer(sp<Layer> layer) {
+ mFlinger->mLegacyLayers[static_cast<uint32_t>(layer->sequence)] = layer;
+ };
+ void releaseLegacyLayer(uint32_t sequence) { mFlinger->mLegacyLayers.erase(sequence); };
+
+ auto setLayerHistoryDisplayArea(uint32_t displayArea) {
+ return mFlinger->mScheduler->onActiveDisplayAreaChanged(displayArea);
+ };
+ auto updateLayerHistory(nsecs_t now) { return mFlinger->updateLayerHistory(now); };
+ auto setDaltonizerType(ColorBlindnessType type) {
+ mFlinger->mDaltonizer.setType(type);
+ return mFlinger->updateColorMatrixLocked();
+ }
+ auto updateLayerSnapshots(VsyncId vsyncId, nsecs_t frameTimeNs, bool transactionsFlushed,
+ bool& out) {
+ ftl::FakeGuard guard(kMainThreadContext);
+ return mFlinger->updateLayerSnapshots(vsyncId, frameTimeNs, transactionsFlushed, out);
+ }
/* ------------------------------------------------------------------------
* Read-write access to private data to set up preconditions and assert
* post-conditions.
*/
const auto& displays() const { return mFlinger->mDisplays; }
+ const auto& physicalDisplays() const { return mFlinger->mPhysicalDisplays; }
const auto& currentState() const { return mFlinger->mCurrentState; }
const auto& drawingState() const { return mFlinger->mDrawingState; }
const auto& transactionFlags() const { return mFlinger->mTransactionFlags; }
+
const auto& hwcPhysicalDisplayIdMap() const { return getHwComposer().mPhysicalDisplayIdMap; }
+ const auto& hwcDisplayData() const { return getHwComposer().mDisplayData; }
- auto& mutableHasWideColorDisplay() { return SurfaceFlinger::hasWideColorDisplay; }
+ auto& mutableSupportsWideColor() { return mFlinger->mSupportsWideColor; }
auto& mutableCurrentState() { return mFlinger->mCurrentState; }
auto& mutableDisplayColorSetting() { return mFlinger->mDisplayColorSetting; }
auto& mutableDisplays() { return mFlinger->mDisplays; }
+ auto& mutablePhysicalDisplays() { return mFlinger->mPhysicalDisplays; }
auto& mutableDrawingState() { return mFlinger->mDrawingState; }
auto& mutableGeometryDirty() { return mFlinger->mGeometryDirty; }
- auto& mutableInterceptor() { return mFlinger->mInterceptor; }
+ auto& mutableVisibleRegionsDirty() { return mFlinger->mVisibleRegionsDirty; }
auto& mutableMainThreadId() { return mFlinger->mMainThreadId; }
auto& mutablePendingHotplugEvents() { return mFlinger->mPendingHotplugEvents; }
- auto& mutablePhysicalDisplayTokens() { return mFlinger->mPhysicalDisplayTokens; }
- auto& mutableTexturePool() { return mFlinger->mTexturePool; }
auto& mutableTransactionFlags() { return mFlinger->mTransactionFlags; }
auto& mutableDebugDisableHWC() { return mFlinger->mDebugDisableHWC; }
auto& mutableMaxRenderTargetSize() { return mFlinger->mMaxRenderTargetSize; }
@@ -546,10 +672,42 @@ public:
auto& mutableHwcDisplayData() { return getHwComposer().mDisplayData; }
auto& mutableHwcPhysicalDisplayIdMap() { return getHwComposer().mPhysicalDisplayIdMap; }
auto& mutablePrimaryHwcDisplayId() { return getHwComposer().mPrimaryHwcDisplayId; }
- auto& mutableActiveDisplayToken() { return mFlinger->mActiveDisplayToken; }
+ auto& mutableActiveDisplayId() { return mFlinger->mActiveDisplayId; }
+ auto& mutablePreviouslyComposedLayers() { return mFlinger->mPreviouslyComposedLayers; }
- auto fromHandle(const sp<IBinder>& handle) {
- return mFlinger->fromHandle(handle);
+ auto& mutableActiveDisplayRotationFlags() {
+ return SurfaceFlinger::sActiveDisplayRotationFlags;
+ }
+
+ auto& mutableMinAcquiredBuffers() { return SurfaceFlinger::minAcquiredBuffers; }
+ auto& mutableLayersPendingRemoval() { return mFlinger->mLayersPendingRemoval; }
+ auto& mutableLayerSnapshotBuilder() { return mFlinger->mLayerSnapshotBuilder; };
+
+ auto fromHandle(const sp<IBinder>& handle) { return LayerHandle::getLayer(handle); }
+
+ auto initTransactionTraceWriter() {
+ mFlinger->mTransactionTracing.emplace();
+ return mFlinger->initTransactionTraceWriter();
+ }
+
+ void enableNewFrontEnd() {
+ mFlinger->mLayerLifecycleManagerEnabled = true;
+ mFlinger->mLegacyFrontEndEnabled = false;
+ }
+
+ void notifyExpectedPresentIfRequired(PhysicalDisplayId displayId, Period vsyncPeriod,
+ TimePoint expectedPresentTime, Fps frameInterval,
+ std::optional<Period> timeoutOpt) {
+ mFlinger->notifyExpectedPresentIfRequired(displayId, vsyncPeriod, expectedPresentTime,
+ frameInterval, timeoutOpt);
+ }
+
+ void setNotifyExpectedPresentData(PhysicalDisplayId displayId,
+ TimePoint lastExpectedPresentTimestamp,
+ Fps lastFrameInterval) {
+ auto& displayData = mFlinger->mNotifyExpectedPresentMap[displayId];
+ displayData.lastExpectedPresentTimestamp = lastExpectedPresentTimestamp;
+ displayData.lastFrameInterval = lastFrameInterval;
}
~TestableSurfaceFlinger() {
@@ -560,11 +718,12 @@ public:
mutableDisplays().clear();
mutableCurrentState().displays.clear();
mutableDrawingState().displays.clear();
- mutableInterceptor().clear();
+ mFlinger->mLayersPendingRemoval.clear();
mFlinger->mScheduler.reset();
mFlinger->mCompositionEngine->setHwComposer(std::unique_ptr<HWComposer>());
- mFlinger->mCompositionEngine->setRenderEngine(
- std::unique_ptr<renderengine::RenderEngine>());
+ mFlinger->mRenderEngine = std::unique_ptr<renderengine::RenderEngine>();
+ mFlinger->mCompositionEngine->setRenderEngine(mFlinger->mRenderEngine.get());
+ mFlinger->mTransactionTracing.reset();
}
/* ------------------------------------------------------------------------
@@ -638,7 +797,7 @@ public:
return *this;
}
- auto& setPowerMode(hal::PowerMode mode) {
+ auto& setPowerMode(std::optional<hal::PowerMode> mode) {
mPowerMode = mode;
return *this;
}
@@ -660,16 +819,18 @@ public:
// is much longer lived.
auto display = std::make_unique<HWC2Display>(*composer, *mCapabilities, mHwcDisplayId,
mHwcDisplayType);
-
display->mutableIsConnected() = true;
- display->setPowerMode(mPowerMode);
+
+ if (mPowerMode) {
+ display->setPowerMode(*mPowerMode);
+ }
+
flinger->mutableHwcDisplayData()[mDisplayId].hwcDisplay = std::move(display);
EXPECT_CALL(*composer, getDisplayConfigs(mHwcDisplayId, _))
.WillRepeatedly(
DoAll(SetArgPointee<1>(std::vector<hal::HWConfigId>{mActiveConfig}),
Return(hal::Error::NONE)));
-
EXPECT_CALL(*composer,
getDisplayAttribute(mHwcDisplayId, mActiveConfig, hal::Attribute::WIDTH, _))
.WillRepeatedly(DoAll(SetArgPointee<3>(mResolution.getWidth()),
@@ -728,7 +889,7 @@ public:
int32_t mDpiY = DEFAULT_DPI;
int32_t mConfigGroup = DEFAULT_CONFIG_GROUP;
hal::HWConfigId mActiveConfig = DEFAULT_ACTIVE_CONFIG;
- hal::PowerMode mPowerMode = DEFAULT_POWER_MODE;
+ std::optional<hal::PowerMode> mPowerMode = DEFAULT_POWER_MODE;
const std::unordered_set<aidl::android::hardware::graphics::composer3::Capability>*
mCapabilities = nullptr;
};
@@ -740,16 +901,22 @@ public:
std::optional<ui::DisplayConnectionType> connectionType,
std::optional<hal::HWDisplayId> hwcDisplayId, bool isPrimary)
: mFlinger(flinger),
- mCreationArgs(flinger.mFlinger.get(), flinger.mFlinger->getHwComposer(),
- mDisplayToken, display),
+ mCreationArgs(flinger.mFlinger, flinger.mFlinger->getHwComposer(), mDisplayToken,
+ display),
+ mConnectionType(connectionType),
mHwcDisplayId(hwcDisplayId) {
- mCreationArgs.connectionType = connectionType;
mCreationArgs.isPrimary = isPrimary;
mCreationArgs.initialPowerMode = hal::PowerMode::ON;
}
sp<IBinder> token() const { return mDisplayToken; }
+ auto physicalDisplay() const {
+ return ftl::Optional(mCreationArgs.compositionDisplay->getDisplayId())
+ .and_then(&PhysicalDisplayId::tryCast)
+ .and_then(display::getPhysicalDisplay(mFlinger.physicalDisplays()));
+ }
+
DisplayDeviceState& mutableDrawingDisplayState() {
return mFlinger.mutableDrawingState().displays.editValueFor(mDisplayToken);
}
@@ -770,16 +937,17 @@ public:
return mFlinger.mutableDisplays().get(mDisplayToken)->get();
}
- // If `configs` is nullptr, the injector creates RefreshRateConfigs from the `modes`.
- // Otherwise, it uses `configs`, which the caller must create using the same `modes`.
- //
- // TODO(b/182939859): Once `modes` can be retrieved from RefreshRateConfigs, remove
- // the `configs` parameter in favor of an alternative setRefreshRateConfigs API.
- auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId,
- std::shared_ptr<scheduler::RefreshRateConfigs> configs = nullptr) {
- mCreationArgs.supportedModes = std::move(modes);
+ auto& setDisplayModes(DisplayModes modes, DisplayModeId activeModeId) {
+ mDisplayModes = std::move(modes);
mCreationArgs.activeModeId = activeModeId;
- mCreationArgs.refreshRateConfigs = std::move(configs);
+ mCreationArgs.refreshRateSelector = nullptr;
+ return *this;
+ }
+
+ auto& setRefreshRateSelector(RefreshRateSelectorPtr selectorPtr) {
+ mDisplayModes = selectorPtr->displayModes();
+ mCreationArgs.activeModeId = selectorPtr->getActiveMode().modePtr->getId();
+ mCreationArgs.refreshRateSelector = std::move(selectorPtr);
return *this;
}
@@ -798,7 +966,7 @@ public:
return *this;
}
- auto& setPowerMode(hal::PowerMode mode) {
+ auto& setPowerMode(std::optional<hal::PowerMode> mode) {
mCreationArgs.initialPowerMode = mode;
return *this;
}
@@ -820,13 +988,25 @@ public:
return *this;
}
+ auto& skipRegisterDisplay() {
+ mRegisterDisplay = false;
+ return *this;
+ }
+
sp<DisplayDevice> inject() NO_THREAD_SAFETY_ANALYSIS {
+ return inject(std::make_unique<mock::VsyncController>(),
+ std::make_shared<mock::VSyncTracker>());
+ }
+
+ sp<DisplayDevice> inject(std::unique_ptr<android::scheduler::VsyncController> controller,
+ std::shared_ptr<android::scheduler::VSyncTracker> tracker)
+ NO_THREAD_SAFETY_ANALYSIS {
const auto displayId = mCreationArgs.compositionDisplay->getDisplayId();
- auto& modes = mCreationArgs.supportedModes;
+ auto& modes = mDisplayModes;
auto& activeModeId = mCreationArgs.activeModeId;
- if (displayId && !mCreationArgs.refreshRateConfigs) {
+ if (displayId && !mCreationArgs.refreshRateSelector) {
if (const auto physicalId = PhysicalDisplayId::tryCast(*displayId)) {
if (modes.empty()) {
constexpr DisplayModeId kModeId{0};
@@ -846,63 +1026,91 @@ public:
activeModeId = kModeId;
}
- mCreationArgs.refreshRateConfigs =
- std::make_shared<scheduler::RefreshRateConfigs>(modes, activeModeId);
+ mCreationArgs.refreshRateSelector =
+ std::make_shared<scheduler::RefreshRateSelector>(modes, activeModeId);
}
}
+ sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
+ mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
+
DisplayDeviceState state;
- if (const auto type = mCreationArgs.connectionType) {
+ state.isSecure = mCreationArgs.isSecure;
+
+ if (mConnectionType) {
LOG_ALWAYS_FATAL_IF(!displayId);
- const auto physicalId = PhysicalDisplayId::tryCast(*displayId);
- LOG_ALWAYS_FATAL_IF(!physicalId);
+ const auto physicalIdOpt = PhysicalDisplayId::tryCast(*displayId);
+ LOG_ALWAYS_FATAL_IF(!physicalIdOpt);
+ const auto physicalId = *physicalIdOpt;
+
+ if (mCreationArgs.isPrimary) {
+ mFlinger.mutableActiveDisplayId() = physicalId;
+ }
+
LOG_ALWAYS_FATAL_IF(!mHwcDisplayId);
const auto activeMode = modes.get(activeModeId);
LOG_ALWAYS_FATAL_IF(!activeMode);
+ const auto fps = activeMode->get()->getPeakFps();
- state.physical = {.id = *physicalId,
- .type = *type,
+ state.physical = {.id = physicalId,
.hwcDisplayId = *mHwcDisplayId,
- .deviceProductInfo = {},
- .supportedModes = modes,
.activeMode = activeMode->get()};
- }
- state.isSecure = mCreationArgs.isSecure;
+ mFlinger.mutablePhysicalDisplays().emplace_or_replace(physicalId, mDisplayToken,
+ physicalId, *mConnectionType,
+ std::move(modes),
+ ui::ColorModes(),
+ std::nullopt);
+
+ if (mFlinger.scheduler() && mRegisterDisplay) {
+ mFlinger.scheduler()->registerDisplay(physicalId,
+ display->holdRefreshRateSelector(),
+ std::move(controller),
+ std::move(tracker));
+ }
- sp<DisplayDevice> display = sp<DisplayDevice>::make(mCreationArgs);
- if (!display->isVirtual()) {
- display->setActiveMode(activeModeId);
+ display->setActiveMode(activeModeId, fps, fps);
}
- mFlinger.mutableDisplays().emplace_or_replace(mDisplayToken, display);
mFlinger.mutableCurrentState().displays.add(mDisplayToken, state);
mFlinger.mutableDrawingState().displays.add(mDisplayToken, state);
- if (const auto& physical = state.physical) {
- mFlinger.mutablePhysicalDisplayTokens().emplace_or_replace(physical->id,
- mDisplayToken);
- }
-
return display;
}
private:
TestableSurfaceFlinger& mFlinger;
- sp<BBinder> mDisplayToken = new BBinder();
+ sp<BBinder> mDisplayToken = sp<BBinder>::make();
DisplayDeviceCreationArgs mCreationArgs;
+ DisplayModes mDisplayModes;
+ bool mRegisterDisplay = true;
+ const std::optional<ui::DisplayConnectionType> mConnectionType;
const std::optional<hal::HWDisplayId> mHwcDisplayId;
};
private:
- constexpr static int64_t kVsyncId = 123;
+ template <typename T>
+ static std::unique_ptr<T> makeMock(bool useNiceMock) {
+ return useNiceMock ? std::make_unique<testing::NiceMock<T>>() : std::make_unique<T>();
+ }
+
+ template <typename T>
+ static std::shared_ptr<T> makeSharedMock(bool useNiceMock) {
+ return useNiceMock ? std::make_shared<testing::NiceMock<T>>() : std::make_shared<T>();
+ }
+
+ static constexpr VsyncId kVsyncId{123};
surfaceflinger::test::Factory mFactory;
sp<SurfaceFlinger> mFlinger;
scheduler::mock::SchedulerCallback mSchedulerCallback;
scheduler::mock::NoOpSchedulerCallback mNoOpSchedulerCallback;
+ scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
+ scheduler::mock::NoOpVsyncTrackerCallback mNoOpVsyncTrackerCallback;
+ std::unique_ptr<frametimeline::impl::TokenManager> mTokenManager;
scheduler::TestableScheduler* mScheduler = nullptr;
+ Hwc2::mock::PowerAdvisor mPowerAdvisor;
};
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
index 6ffc0396d7..86ed233a0a 100644
--- a/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TimeStatsTest.cpp
@@ -44,11 +44,14 @@ namespace android {
namespace {
using testing::_;
+using testing::AllOf;
using testing::AnyNumber;
using testing::Contains;
+using testing::ElementsAre;
using testing::HasSubstr;
using testing::InSequence;
using testing::Not;
+using testing::Property;
using testing::SizeIs;
using testing::StrEq;
using testing::UnorderedElementsAre;
@@ -645,7 +648,7 @@ TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) {
ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
ASSERT_EQ(1, globalProto.stats_size());
- const SFTimeStatsLayerProto& layerProto = globalProto.stats().Get(0);
+ const SFTimeStatsLayerProto& layerProto = globalProto.stats(0);
ASSERT_TRUE(layerProto.has_layer_name());
EXPECT_EQ(genLayerName(LAYER_ID_0), layerProto.layer_name());
ASSERT_TRUE(layerProto.has_total_frames());
@@ -653,7 +656,7 @@ TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) {
ASSERT_EQ(6, layerProto.deltas_size());
for (const SFTimeStatsDeltaProto& deltaProto : layerProto.deltas()) {
ASSERT_EQ(1, deltaProto.histograms_size());
- const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms().Get(0);
+ const SFTimeStatsHistogramBucketProto& histogramProto = deltaProto.histograms(0);
EXPECT_EQ(1, histogramProto.frame_count());
if ("post2acquire" == deltaProto.delta_name()) {
EXPECT_EQ(1, histogramProto.time_millis());
@@ -673,6 +676,46 @@ TEST_F(TimeStatsTest, canInsertOneLayerTimeStats) {
}
}
+using LayerProto = SFTimeStatsLayerProto;
+using DeltaProto = SFTimeStatsDeltaProto;
+using BucketProto = SFTimeStatsHistogramBucketProto;
+
+TEST_F(TimeStatsTest, canComputeLayerStabilityHistogram) {
+ EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
+
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 2, 2000000);
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000); // 0ms delta
+ // Slightly unstable frames
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 5000000); // 1ms delta
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 6000000); // 1ms delta
+
+ SFTimeStatsGlobalProto globalProto;
+ ASSERT_TRUE(globalProto.ParseFromString(inputCommand(InputCommand::DUMP_ALL, FMT_PROTO)));
+
+ EXPECT_THAT(globalProto.stats(),
+ ElementsAre(AllOf(
+ Property(&LayerProto::layer_name, genLayerName(LAYER_ID_0)),
+ Property(&LayerProto::total_frames, 4),
+ Property(&LayerProto::deltas,
+ Contains(AllOf(Property(&DeltaProto::delta_name,
+ "present2presentDelta"),
+ Property(&DeltaProto::histograms,
+ UnorderedElementsAre(
+ AllOf(Property(&BucketProto::
+ time_millis,
+ 0),
+ Property(&BucketProto::
+ frame_count,
+ 1)),
+ AllOf(Property(&BucketProto::
+ time_millis,
+ 1),
+ Property(&BucketProto::
+ frame_count,
+ 2))))))))));
+}
+
TEST_F(TimeStatsTest, canNotInsertInvalidLayerNameTimeStats) {
EXPECT_TRUE(inputCommand(InputCommand::ENABLE, FMT_STRING).empty());
@@ -1036,7 +1079,6 @@ TEST_F(TimeStatsTest, globalStatsCallback) {
constexpr size_t TOTAL_FRAMES = 5;
constexpr size_t MISSED_FRAMES = 4;
constexpr size_t CLIENT_COMPOSITION_FRAMES = 3;
- constexpr size_t DISPLAY_EVENT_CONNECTIONS = 14;
constexpr nsecs_t DISPLAY_DEADLINE_DELTA = 1'000'000;
constexpr nsecs_t DISPLAY_PRESENT_JITTER = 2'000'000;
constexpr nsecs_t APP_DEADLINE_DELTA = 3'000'000;
@@ -1057,7 +1099,6 @@ TEST_F(TimeStatsTest, globalStatsCallback) {
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 1, 1000000);
- mTimeStats->recordDisplayEventConnectionCount(DISPLAY_EVENT_CONNECTIONS);
mTimeStats->setPowerMode(PowerMode::ON);
mTimeStats->recordFrameDuration(1000000, 3000000);
mTimeStats->recordRenderEngineDuration(2000000, 4000000);
@@ -1099,8 +1140,10 @@ TEST_F(TimeStatsTest, globalStatsCallback) {
kGameMode, JankType::None, DISPLAY_DEADLINE_DELTA,
DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA});
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10062 /*SURFACEFLINGER_STATS_GLOBAL_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
android::surfaceflinger::SurfaceflingerStatsGlobalInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1112,7 +1155,6 @@ TEST_F(TimeStatsTest, globalStatsCallback) {
EXPECT_EQ(atom.client_composition_frames(), CLIENT_COMPOSITION_FRAMES);
// Display on millis is not checked.
EXPECT_EQ(atom.animation_millis(), 2);
- EXPECT_EQ(atom.event_connection_count(), DISPLAY_EVENT_CONNECTIONS);
EXPECT_THAT(atom.frame_duration(), HistogramEq(buildExpectedHistogram({2}, {1})));
EXPECT_THAT(atom.render_engine_timing(), HistogramEq(buildExpectedHistogram({1, 2}, {1, 1})));
EXPECT_EQ(atom.total_timeline_frames(), 9);
@@ -1234,8 +1276,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_pullsAllAndClears) {
GameMode::Standard, JankType::None, DISPLAY_DEADLINE_DELTA,
DISPLAY_PRESENT_JITTER, APP_DEADLINE_DELTA_3MS});
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1320,16 +1364,19 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) {
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 3000000, {}, GameMode::Performance);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 4000000, {}, GameMode::Battery);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 5, 4000000, {}, GameMode::Battery);
+ insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 6, 5000000, {}, GameMode::Custom);
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
// The first time record is never uploaded to stats.
- ASSERT_EQ(atomList.atom_size(), 3);
+ ASSERT_EQ(atomList.atom_size(), 4);
// Layers are ordered based on the hash in LayerStatsKey. For this test, the order happens to
- // be: 0 - Battery 1 - Performance 2 - Standard
+ // be: 0 - Battery 1 - Custom 2 - Performance 3 - Standard
const SurfaceflingerStatsLayerInfo& atom0 = atomList.atom(0);
EXPECT_EQ(atom0.layer_name(), genLayerName(LAYER_ID_0));
@@ -1364,7 +1411,7 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) {
EXPECT_EQ(atom1.uid(), UID_0);
EXPECT_EQ(atom1.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
EXPECT_EQ(atom1.render_rate_bucket(), RENDER_RATE_BUCKET_0);
- EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+ EXPECT_EQ(atom1.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_CUSTOM);
const SurfaceflingerStatsLayerInfo& atom2 = atomList.atom(2);
@@ -1377,12 +1424,30 @@ TEST_F(TimeStatsTest, layerStatsCallback_multipleGameModes) {
EXPECT_THAT(atom2.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
EXPECT_THAT(atom2.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
EXPECT_THAT(atom2.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
- EXPECT_EQ(atom2.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
- EXPECT_EQ(atom2.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+ EXPECT_EQ(atom2.late_acquire_frames(), 0);
+ EXPECT_EQ(atom2.bad_desired_present_frames(), 0);
EXPECT_EQ(atom2.uid(), UID_0);
EXPECT_EQ(atom2.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
EXPECT_EQ(atom2.render_rate_bucket(), RENDER_RATE_BUCKET_0);
- EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
+ EXPECT_EQ(atom2.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_PERFORMANCE);
+
+ const SurfaceflingerStatsLayerInfo& atom3 = atomList.atom(3);
+
+ EXPECT_EQ(atom3.layer_name(), genLayerName(LAYER_ID_0));
+ EXPECT_EQ(atom3.total_frames(), 1);
+ EXPECT_EQ(atom3.dropped_frames(), 0);
+ EXPECT_THAT(atom3.present_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+ EXPECT_THAT(atom3.post_to_present(), HistogramEq(buildExpectedHistogram({4}, {1})));
+ EXPECT_THAT(atom3.acquire_to_present(), HistogramEq(buildExpectedHistogram({3}, {1})));
+ EXPECT_THAT(atom3.latch_to_present(), HistogramEq(buildExpectedHistogram({2}, {1})));
+ EXPECT_THAT(atom3.desired_to_present(), HistogramEq(buildExpectedHistogram({1}, {1})));
+ EXPECT_THAT(atom3.post_to_acquire(), HistogramEq(buildExpectedHistogram({1}, {1})));
+ EXPECT_EQ(atom3.late_acquire_frames(), LATE_ACQUIRE_FRAMES);
+ EXPECT_EQ(atom3.bad_desired_present_frames(), BAD_DESIRED_PRESENT_FRAMES);
+ EXPECT_EQ(atom3.uid(), UID_0);
+ EXPECT_EQ(atom3.display_refresh_rate_bucket(), REFRESH_RATE_BUCKET_0);
+ EXPECT_EQ(atom3.render_rate_bucket(), RENDER_RATE_BUCKET_0);
+ EXPECT_EQ(atom3.game_mode(), SurfaceflingerStatsLayerInfo::GAME_MODE_STANDARD);
}
TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) {
@@ -1393,8 +1458,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleLayers) {
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 1, 2000000);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 3000000);
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1418,8 +1485,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_pullsMultipleBuckets) {
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(3000000));
mTimeStats->setPresentFenceGlobal(std::make_shared<FenceTime>(5000000));
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1437,8 +1506,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_limitsHistogramBuckets) {
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 3, 4000000);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_0, 4, 5000000);
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
@@ -1457,8 +1528,10 @@ TEST_F(TimeStatsTest, layerStatsCallback_limitsLayers) {
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 2, 3000000);
insertTimeRecord(NORMAL_SEQUENCE, LAYER_ID_1, 4, 5000000);
+ std::vector<uint8_t> pulledBytes;
+ EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledBytes));
std::string pulledData;
- EXPECT_TRUE(mTimeStats->onPullAtom(10063 /*SURFACEFLINGER_STATS_LAYER_INFO*/, &pulledData));
+ pulledData.assign(pulledBytes.begin(), pulledBytes.end());
SurfaceflingerStatsLayerInfoWrapper atomList;
ASSERT_TRUE(atomList.ParseFromString(pulledData));
diff --git a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
index ded75318ed..1f2a1edb77 100644
--- a/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionApplicationTest.cpp
@@ -14,30 +14,34 @@
* limitations under the License.
*/
-
#undef LOG_TAG
-#define LOG_TAG "CompositionTest"
+#define LOG_TAG "TransactionApplicationTest"
#include <compositionengine/Display.h>
#include <compositionengine/mock/DisplaySurface.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <gui/LayerState.h>
#include <gui/SurfaceComposerClient.h>
+#include <gui/fake/BufferData.h>
#include <log/log.h>
#include <ui/MockFence.h>
#include <utils/String8.h>
+#include <vector>
+#include <binder/Binder.h>
+#include "FrontEnd/TransactionHandler.h"
#include "TestableSurfaceFlinger.h"
-#include "mock/MockEventThread.h"
-#include "mock/MockVsyncController.h"
+#include "TransactionState.h"
namespace android {
using testing::_;
using testing::Return;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+using frontend::TransactionHandler;
+constexpr nsecs_t TRANSACTION_TIMEOUT = s2ns(5);
class TransactionApplicationTest : public testing::Test {
public:
TransactionApplicationTest() {
@@ -45,7 +49,9 @@ public:
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- setupScheduler();
+ mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
+ mFlinger.setupMockScheduler();
+ mFlinger.flinger()->addTransactionReadyFilters();
}
~TransactionApplicationTest() {
@@ -54,35 +60,8 @@ public:
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
- void setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*mVSyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*mVSyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
-
- mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
- mFlinger.setupScheduler(std::unique_ptr<mock::VsyncController>(mVsyncController),
- std::unique_ptr<mock::VSyncTracker>(mVSyncTracker),
- std::move(eventThread), std::move(sfEventThread));
- }
-
TestableSurfaceFlinger mFlinger;
- mock::VsyncController* mVsyncController = new mock::VsyncController();
- mock::VSyncTracker* mVSyncTracker = new mock::VSyncTracker();
-
struct TransactionInfo {
Vector<ComposerState> states;
Vector<DisplayState> displays;
@@ -92,8 +71,9 @@ public:
int64_t desiredPresentTime = 0;
bool isAutoTimestamp = true;
FrameTimelineInfo frameTimelineInfo;
- client_cache_t uncacheBuffer;
+ std::vector<client_cache_t> uncacheBuffers;
uint64_t id = static_cast<uint64_t>(-1);
+ std::vector<uint64_t> mergedTransactionIds;
static_assert(0xffffffffffffffff == static_cast<uint64_t>(-1));
};
@@ -107,22 +87,20 @@ public:
EXPECT_EQ(info.desiredPresentTime, state.desiredPresentTime);
}
- void setupSingle(TransactionInfo& transaction, uint32_t flags, bool syncInputWindows,
- int64_t desiredPresentTime, bool isAutoTimestamp,
- const FrameTimelineInfo& frameTimelineInfo) {
+ void setupSingle(TransactionInfo& transaction, uint32_t flags, int64_t desiredPresentTime,
+ bool isAutoTimestamp, const FrameTimelineInfo& frameTimelineInfo) {
mTransactionNumber++;
- transaction.flags |= flags; // ISurfaceComposer::eSynchronous;
- transaction.inputWindowCommands.syncInputWindows = syncInputWindows;
+ transaction.flags |= flags;
transaction.desiredPresentTime = desiredPresentTime;
transaction.isAutoTimestamp = isAutoTimestamp;
transaction.frameTimelineInfo = frameTimelineInfo;
}
- void NotPlacedOnTransactionQueue(uint32_t flags, bool syncInputWindows) {
- ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+ void NotPlacedOnTransactionQueue(uint32_t flags) {
+ ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
TransactionInfo transaction;
- setupSingle(transaction, flags, syncInputWindows,
+ setupSingle(transaction, flags,
/*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
FrameTimelineInfo{});
nsecs_t applicationTime = systemTime();
@@ -130,87 +108,71 @@ public:
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
- transaction.id);
+ transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transaction.id, transaction.mergedTransactionIds);
- // If transaction is synchronous or syncs input windows, SF
- // applyTransactionState should time out (5s) wating for SF to commit
- // the transaction or to receive a signal that syncInputWindows has
- // completed. If this is animation, it should not time out waiting.
+ // If transaction is synchronous, SF applyTransactionState should time out (5s) wating for
+ // SF to commit the transaction. If this is animation, it should not time out waiting.
nsecs_t returnedTime = systemTime();
- if (flags & ISurfaceComposer::eSynchronous || syncInputWindows) {
- EXPECT_GE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout());
- } else {
- EXPECT_LE(returnedTime, applicationTime + mFlinger.getAnimationTransactionTimeout());
- }
+ EXPECT_LE(returnedTime, applicationTime + TRANSACTION_TIMEOUT);
// Each transaction should have been placed on the transaction queue
- auto transactionQueue = mFlinger.getTransactionQueue();
- EXPECT_EQ(1u, transactionQueue.size());
+ auto& transactionQueue = mFlinger.getTransactionQueue();
+ EXPECT_FALSE(transactionQueue.isEmpty());
}
- void PlaceOnTransactionQueue(uint32_t flags, bool syncInputWindows) {
- ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+ void PlaceOnTransactionQueue(uint32_t flags) {
+ ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
// first check will see desired present time has not passed,
// but afterwards it will look like the desired present time has passed
nsecs_t time = systemTime();
TransactionInfo transaction;
- setupSingle(transaction, flags, syncInputWindows,
- /*desiredPresentTime*/ time + s2ns(1), false, FrameTimelineInfo{});
+ setupSingle(transaction, flags, /*desiredPresentTime*/ time + s2ns(1), false,
+ FrameTimelineInfo{});
nsecs_t applicationSentTime = systemTime();
mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states,
transaction.displays, transaction.flags,
transaction.applyToken, transaction.inputWindowCommands,
transaction.desiredPresentTime, transaction.isAutoTimestamp,
- transaction.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
- transaction.id);
+ transaction.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transaction.id, transaction.mergedTransactionIds);
nsecs_t returnedTime = systemTime();
- if ((flags & ISurfaceComposer::eSynchronous) || syncInputWindows) {
- EXPECT_GE(systemTime(),
- applicationSentTime + mFlinger.getAnimationTransactionTimeout());
- } else {
- EXPECT_LE(returnedTime,
- applicationSentTime + mFlinger.getAnimationTransactionTimeout());
- }
+ EXPECT_LE(returnedTime, applicationSentTime + TRANSACTION_TIMEOUT);
// This transaction should have been placed on the transaction queue
- auto transactionQueue = mFlinger.getTransactionQueue();
- EXPECT_EQ(1u, transactionQueue.size());
+ auto& transactionQueue = mFlinger.getTransactionQueue();
+ EXPECT_FALSE(transactionQueue.isEmpty());
}
- void BlockedByPriorTransaction(uint32_t flags, bool syncInputWindows) {
- ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+ void BlockedByPriorTransaction(uint32_t flags) {
+ ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
nsecs_t time = systemTime();
- if (!syncInputWindows) {
- EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2);
- } else {
- EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
- }
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(2);
+
// transaction that should go on the pending thread
TransactionInfo transactionA;
- setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
- /*desiredPresentTime*/ time + s2ns(1), false, FrameTimelineInfo{});
+ setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ time + s2ns(1), false,
+ FrameTimelineInfo{});
// transaction that would not have gone on the pending thread if not
// blocked
TransactionInfo transactionB;
- setupSingle(transactionB, flags, syncInputWindows,
- /*desiredPresentTime*/ systemTime(), /*isAutoTimestamp*/ true,
- FrameTimelineInfo{});
+ setupSingle(transactionB, flags, /*desiredPresentTime*/ systemTime(),
+ /*isAutoTimestamp*/ true, FrameTimelineInfo{});
nsecs_t applicationSentTime = systemTime();
mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
transactionA.displays, transactionA.flags,
transactionA.applyToken, transactionA.inputWindowCommands,
transactionA.desiredPresentTime, transactionA.isAutoTimestamp,
- transactionA.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
- transactionA.id);
+ transactionA.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transactionA.id, transactionA.mergedTransactionIds);
// This thread should not have been blocked by the above transaction
// (5s is the timeout period that applyTransactionState waits for SF to
// commit the transaction)
- EXPECT_LE(systemTime(), applicationSentTime + mFlinger.getAnimationTransactionTimeout());
+ EXPECT_LE(systemTime(), applicationSentTime + TRANSACTION_TIMEOUT);
// transaction that would goes to pending transaciton queue.
mFlinger.flushTransactionQueues();
@@ -219,21 +181,14 @@ public:
transactionB.displays, transactionB.flags,
transactionB.applyToken, transactionB.inputWindowCommands,
transactionB.desiredPresentTime, transactionB.isAutoTimestamp,
- transactionB.uncacheBuffer, mHasListenerCallbacks, mCallbacks,
- transactionB.id);
+ transactionB.uncacheBuffers, mHasListenerCallbacks, mCallbacks,
+ transactionB.id, transactionB.mergedTransactionIds);
// this thread should have been blocked by the above transaction
// if this is an animation, this thread should be blocked for 5s
// in setTransactionState waiting for transactionA to flush. Otherwise,
// the transaction should be placed on the pending queue
- if (flags & (ISurfaceComposer::eSynchronous) ||
- syncInputWindows) {
- EXPECT_GE(systemTime(),
- applicationSentTime + mFlinger.getAnimationTransactionTimeout());
- } else {
- EXPECT_LE(systemTime(),
- applicationSentTime + mFlinger.getAnimationTransactionTimeout());
- }
+ EXPECT_LE(systemTime(), applicationSentTime + TRANSACTION_TIMEOUT);
// transaction that would goes to pending transaciton queue.
mFlinger.flushTransactionQueues();
@@ -243,29 +198,53 @@ public:
EXPECT_EQ(0u, transactionQueue.size());
}
+ void modulateVsync() {
+ static_cast<void>(
+ mFlinger.mutableScheduler().vsyncModulator().onRefreshRateChangeInitiated());
+ }
+
bool mHasListenerCallbacks = false;
std::vector<ListenerCallbacks> mCallbacks;
int mTransactionNumber = 0;
};
-TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
- ASSERT_EQ(0u, mFlinger.getTransactionQueue().size());
+TEST_F(TransactionApplicationTest, AddToPendingQueue) {
+ ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
TransactionInfo transactionA; // transaction to go on pending queue
- setupSingle(transactionA, /*flags*/ 0, /*syncInputWindows*/ false,
- /*desiredPresentTime*/ s2ns(1), false, FrameTimelineInfo{});
+ setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
+ FrameTimelineInfo{});
mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
transactionA.displays, transactionA.flags, transactionA.applyToken,
transactionA.inputWindowCommands, transactionA.desiredPresentTime,
- transactionA.isAutoTimestamp, transactionA.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, transactionA.id);
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
+ mHasListenerCallbacks, mCallbacks, transactionA.id,
+ transactionA.mergedTransactionIds);
auto& transactionQueue = mFlinger.getTransactionQueue();
- ASSERT_EQ(1u, transactionQueue.size());
+ ASSERT_FALSE(transactionQueue.isEmpty());
- auto& transactionState = transactionQueue.front();
+ auto transactionState = transactionQueue.pop().value();
checkEqual(transactionA, transactionState);
+}
+
+TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
+ ASSERT_TRUE(mFlinger.getTransactionQueue().isEmpty());
+ EXPECT_CALL(*mFlinger.scheduler(), scheduleFrame()).Times(1);
+
+ TransactionInfo transactionA; // transaction to go on pending queue
+ setupSingle(transactionA, /*flags*/ 0, /*desiredPresentTime*/ s2ns(1), false,
+ FrameTimelineInfo{});
+ mFlinger.setTransactionState(transactionA.frameTimelineInfo, transactionA.states,
+ transactionA.displays, transactionA.flags, transactionA.applyToken,
+ transactionA.inputWindowCommands, transactionA.desiredPresentTime,
+ transactionA.isAutoTimestamp, transactionA.uncacheBuffers,
+ mHasListenerCallbacks, mCallbacks, transactionA.id,
+ transactionA.mergedTransactionIds);
+
+ auto& transactionQueue = mFlinger.getTransactionQueue();
+ ASSERT_FALSE(transactionQueue.isEmpty());
// because flushing uses the cached expected present time, we send an empty
// transaction here (sending a null applyToken to fake it as from a
@@ -275,47 +254,97 @@ TEST_F(TransactionApplicationTest, Flush_RemovesFromQueue) {
mFlinger.setTransactionState(empty.frameTimelineInfo, empty.states, empty.displays, empty.flags,
empty.applyToken, empty.inputWindowCommands,
empty.desiredPresentTime, empty.isAutoTimestamp,
- empty.uncacheBuffer, mHasListenerCallbacks, mCallbacks, empty.id);
+ empty.uncacheBuffers, mHasListenerCallbacks, mCallbacks, empty.id,
+ empty.mergedTransactionIds);
// flush transaction queue should flush as desiredPresentTime has
// passed
mFlinger.flushTransactionQueues();
- EXPECT_EQ(0u, transactionQueue.size());
-}
-
-TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_Synchronous) {
- NotPlacedOnTransactionQueue(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false);
+ EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
}
TEST_F(TransactionApplicationTest, NotPlacedOnTransactionQueue_SyncInputWindows) {
- NotPlacedOnTransactionQueue(/*flags*/ 0, /*syncInputWindows*/ true);
-}
-
-TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_Synchronous) {
- PlaceOnTransactionQueue(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false);
+ NotPlacedOnTransactionQueue(/*flags*/ 0);
}
TEST_F(TransactionApplicationTest, PlaceOnTransactionQueue_SyncInputWindows) {
- PlaceOnTransactionQueue(/*flags*/ 0, /*syncInputWindows*/ true);
+ PlaceOnTransactionQueue(/*flags*/ 0);
}
-TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_Synchronous) {
- BlockedByPriorTransaction(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false);
+TEST_F(TransactionApplicationTest, FromHandle) {
+ sp<IBinder> badHandle;
+ auto ret = mFlinger.fromHandle(badHandle);
+ EXPECT_EQ(nullptr, ret.get());
}
-TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_Animation) {
- BlockedByPriorTransaction(ISurfaceComposer::eSynchronous, /*syncInputWindows*/ false);
-}
+class FakeExternalTexture : public renderengine::ExternalTexture {
+ const sp<GraphicBuffer> mEmptyBuffer = nullptr;
+ uint32_t mWidth;
+ uint32_t mHeight;
+ uint64_t mId;
+ PixelFormat mPixelFormat;
+ uint64_t mUsage;
-TEST_F(TransactionApplicationTest, BlockWithPriorTransaction_SyncInputWindows) {
- BlockedByPriorTransaction(/*flags*/ 0, /*syncInputWindows*/ true);
-}
+public:
+ FakeExternalTexture(BufferData& bufferData)
+ : mWidth(bufferData.getWidth()),
+ mHeight(bufferData.getHeight()),
+ mId(bufferData.getId()),
+ mPixelFormat(bufferData.getPixelFormat()),
+ mUsage(bufferData.getUsage()) {}
+ const sp<GraphicBuffer>& 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;
+};
-TEST_F(TransactionApplicationTest, FromHandle) {
- sp<IBinder> badHandle;
- auto ret = mFlinger.fromHandle(badHandle);
- EXPECT_EQ(nullptr, ret.promote().get());
+TEST_F(TransactionApplicationTest, ApplyTokensUseDifferentQueues) {
+ auto applyToken1 = sp<BBinder>::make();
+ auto applyToken2 = sp<BBinder>::make();
+
+ // Transaction 1 has a buffer with an unfired fence. It should not be ready to be applied.
+ TransactionState transaction1;
+ transaction1.applyToken = applyToken1;
+ transaction1.id = 42069;
+ transaction1.states.emplace_back();
+ transaction1.states[0].state.what |= layer_state_t::eBufferChanged;
+ transaction1.states[0].state.bufferData =
+ std::make_shared<fake::BufferData>(/* bufferId */ 1, /* width */ 1, /* height */ 1,
+ /* pixelFormat */ 0, /* outUsage */ 0);
+ transaction1.states[0].externalTexture =
+ std::make_shared<FakeExternalTexture>(*transaction1.states[0].state.bufferData);
+ transaction1.states[0].state.surface =
+ sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
+ ->getHandle();
+ auto fence = sp<mock::MockFence>::make();
+ EXPECT_CALL(*fence, getStatus()).WillRepeatedly(Return(Fence::Status::Unsignaled));
+ transaction1.states[0].state.bufferData->acquireFence = std::move(fence);
+ transaction1.states[0].state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
+ transaction1.isAutoTimestamp = true;
+
+ // Transaction 2 should be ready to be applied.
+ TransactionState transaction2;
+ transaction2.applyToken = applyToken2;
+ transaction2.id = 2;
+ transaction2.isAutoTimestamp = true;
+
+ mFlinger.setTransactionStateInternal(transaction1);
+ mFlinger.setTransactionStateInternal(transaction2);
+ mFlinger.flushTransactionQueues();
+ auto transactionQueues = mFlinger.getPendingTransactionQueue();
+
+ // Transaction 1 is still in its queue.
+ EXPECT_EQ(transactionQueues[applyToken1].size(), 1u);
+ // Transaction 2 has been dequeued.
+ EXPECT_EQ(transactionQueues[applyToken2].size(), 0u);
}
class LatchUnsignaledTest : public TransactionApplicationTest {
@@ -323,9 +352,10 @@ public:
void TearDown() override {
// Clear all transaction queues to release all transactions we sent
// in the tests. Otherwise, gmock complains about memory leaks.
- mFlinger.getTransactionQueue().clear();
+ while (!mFlinger.getTransactionQueue().isEmpty()) {
+ mFlinger.getTransactionQueue().pop();
+ }
mFlinger.getPendingTransactionQueue().clear();
- mFlinger.getTransactionCommittedSignals().clear();
mFlinger.commitTransactionsLocked(eTransactionMask);
mFlinger.mutableCurrentState().layersSortedByZ.clear();
mFlinger.mutableDrawingState().layersSortedByZ.clear();
@@ -337,35 +367,41 @@ public:
return fence;
}
- ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what) {
+ ComposerState createComposerState(int layerId, sp<Fence> fence, uint64_t what,
+ std::optional<sp<IBinder>> layerHandle = std::nullopt) {
ComposerState state;
- state.state.bufferData = std::make_shared<BufferData>();
+ state.state.bufferData =
+ std::make_shared<fake::BufferData>(/* bufferId */ 123L, /* width */ 1,
+ /* height */ 2, /* pixelFormat */ 0,
+ /* outUsage */ 0);
state.state.bufferData->acquireFence = std::move(fence);
state.state.layerId = layerId;
- state.state.surface =
- sp<BufferStateLayer>::make(
- LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
- ->getHandle();
+ state.state.surface = layerHandle.value_or(
+ sp<Layer>::make(LayerCreationArgs(mFlinger.flinger(), nullptr, "TestLayer", 0, {}))
+ ->getHandle());
state.state.bufferData->flags = BufferData::BufferDataChange::fenceChanged;
state.state.what = what;
if (what & layer_state_t::eCropChanged) {
state.state.crop = Rect(1, 2, 3, 4);
}
+ if (what & layer_state_t::eFlagsChanged) {
+ state.state.flags = layer_state_t::eEnableBackpressure;
+ state.state.mask = layer_state_t::eEnableBackpressure;
+ }
+
return state;
}
TransactionInfo createTransactionInfo(const sp<IBinder>& applyToken,
const std::vector<ComposerState>& states) {
TransactionInfo transaction;
- const uint32_t kFlags = ISurfaceComposer::eSynchronous;
- const bool kSyncInputWindows = false;
+ const uint32_t kFlags = 0;
const nsecs_t kDesiredPresentTime = systemTime();
const bool kIsAutoTimestamp = true;
const auto kFrameTimelineInfo = FrameTimelineInfo{};
- setupSingle(transaction, kFlags, kSyncInputWindows, kDesiredPresentTime, kIsAutoTimestamp,
- kFrameTimelineInfo);
+ setupSingle(transaction, kFlags, kDesiredPresentTime, kIsAutoTimestamp, kFrameTimelineInfo);
transaction.applyToken = applyToken;
for (const auto& state : states) {
transaction.states.push_back(state);
@@ -375,23 +411,35 @@ public:
}
void setTransactionStates(const std::vector<TransactionInfo>& transactions,
- size_t expectedTransactionsApplied,
size_t expectedTransactionsPending) {
- EXPECT_EQ(0u, mFlinger.getTransactionQueue().size());
+ EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
EXPECT_EQ(0u, mFlinger.getPendingTransactionQueue().size());
- for (const auto& transaction : transactions) {
- mFlinger.setTransactionState(transaction.frameTimelineInfo, transaction.states,
- transaction.displays, transaction.flags,
- transaction.applyToken, transaction.inputWindowCommands,
- transaction.desiredPresentTime,
- transaction.isAutoTimestamp, transaction.uncacheBuffer,
- mHasListenerCallbacks, mCallbacks, transaction.id);
+ for (auto transaction : transactions) {
+ std::vector<ResolvedComposerState> resolvedStates;
+ resolvedStates.reserve(transaction.states.size());
+ for (auto& state : transaction.states) {
+ ResolvedComposerState resolvedState;
+ resolvedState.state = std::move(state.state);
+ resolvedState.externalTexture =
+ std::make_shared<FakeExternalTexture>(*resolvedState.state.bufferData);
+ resolvedStates.emplace_back(resolvedState);
+ }
+
+ TransactionState transactionState(transaction.frameTimelineInfo, resolvedStates,
+ transaction.displays, transaction.flags,
+ transaction.applyToken,
+ transaction.inputWindowCommands,
+ transaction.desiredPresentTime,
+ transaction.isAutoTimestamp, {}, systemTime(),
+ mHasListenerCallbacks, mCallbacks, getpid(),
+ static_cast<int>(getuid()), transaction.id,
+ transaction.mergedTransactionIds);
+ mFlinger.setTransactionStateInternal(transactionState);
}
mFlinger.flushTransactionQueues();
- EXPECT_EQ(0u, mFlinger.getTransactionQueue().size());
- EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionQueue().size());
- EXPECT_EQ(expectedTransactionsApplied, mFlinger.getTransactionCommittedSignals().size());
+ EXPECT_TRUE(mFlinger.getTransactionQueue().isEmpty());
+ EXPECT_EQ(expectedTransactionsPending, mFlinger.getPendingTransactionCount());
}
};
@@ -407,22 +455,19 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleSignaledFromTheQue
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 0u;
const auto signaledTransaction =
createTransactionInfo(kApplyToken,
{createComposerState(kLayerId, fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged)});
- setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({signaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleUnSignaledFromTheQueue) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 0u;
const auto unsignaledTransaction =
@@ -432,15 +477,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSingleUnSignaledFromTheQ
fence(Fence::Status::Unsignaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferCropChange) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -448,17 +491,17 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu
{
createComposerState(kLayerId,
fence(Fence::Status::Unsignaled),
- layer_state_t::eCropChanged),
+ layer_state_t::eCropChanged |
+ layer_state_t::
+ eBufferChanged),
});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBufferChangeClubed) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -470,15 +513,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsUnSignaledInTheQueue_NonBu
layer_state_t::
eBufferChanged),
});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueueSameApplyTokenMultiState) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto mixedTransaction =
@@ -491,8 +532,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueueSameApplyTokenMu
fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({mixedTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTransaction) {
@@ -500,7 +540,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTr
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto mixedTransaction =
@@ -513,8 +552,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsInTheQueue_MultipleStateTr
fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({mixedTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) {
@@ -522,7 +560,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) {
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
const auto kExpectedTransactionsPending = 0u;
const auto signaledTransaction =
@@ -539,8 +576,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_RemovesSignaledFromTheQueue) {
fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest,
@@ -551,7 +587,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest,
const sp<IBinder> kApplyToken3 = sp<BBinder>::make();
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -578,43 +613,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest,
});
setTransactionStates({unsignaledTransaction, signaledTransaction, signaledTransaction2},
- kExpectedTransactionsApplied, kExpectedTransactionsPending);
-}
-
-TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) {
- const sp<IBinder> kApplyToken1 =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
- const sp<IBinder> kApplyToken3 = sp<BBinder>::make();
- const auto kLayerId1 = 1;
- const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
- const auto kExpectedTransactionsPending = 1u;
-
- const auto signaledTransaction =
- createTransactionInfo(kApplyToken1,
- {
- createComposerState(kLayerId1,
- fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged),
- });
- const auto signaledTransaction2 =
- createTransactionInfo(kApplyToken2,
- {
- createComposerState(kLayerId1,
- fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged),
- });
- const auto unsignaledTransaction =
- createTransactionInfo(kApplyToken3,
- {
- createComposerState(kLayerId2,
- fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- });
-
- setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction},
- kExpectedTransactionsApplied, kExpectedTransactionsPending);
+ kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameApplyToken) {
@@ -622,7 +621,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameA
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -639,7 +637,7 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueueSameA
fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied,
+ setTransactionStates({unsignaledTransaction, signaledTransaction},
kExpectedTransactionsPending);
}
@@ -649,7 +647,6 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueue) {
const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -667,14 +664,13 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, Flush_KeepsTransactionInTheQueue) {
layer_state_t::eBufferChanged),
});
setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
- kExpectedTransactionsApplied, kExpectedTransactionsPending);
+ kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -685,10 +681,42 @@ TEST_F(LatchUnsignaledAutoSingleLayerTest, DontLatchUnsignaledWhenEarlyOffset) {
layer_state_t::eBufferChanged),
});
- // Get VsyncModulator out of the default config
- static_cast<void>(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated());
+ modulateVsync();
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
+}
+
+TEST_F(LatchUnsignaledAutoSingleLayerTest, UnsignaledNotAppliedWhenThereAreSignaled_SignaledFirst) {
+ const sp<IBinder> kApplyToken1 =
+ IInterface::asBinder(TransactionCompletedListener::getIInstance());
+ const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
+ const sp<IBinder> kApplyToken3 = sp<BBinder>::make();
+ const auto kLayerId1 = 1;
+ const auto kLayerId2 = 2;
+ const auto kExpectedTransactionsPending = 1u;
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
+ const auto signaledTransaction =
+ createTransactionInfo(kApplyToken1,
+ {
+ createComposerState(kLayerId1,
+ fence(Fence::Status::Signaled),
+ layer_state_t::eBufferChanged),
+ });
+ const auto signaledTransaction2 =
+ createTransactionInfo(kApplyToken2,
+ {
+ createComposerState(kLayerId1,
+ fence(Fence::Status::Signaled),
+ layer_state_t::eBufferChanged),
+ });
+ const auto unsignaledTransaction =
+ createTransactionInfo(kApplyToken3,
+ {
+ createComposerState(kLayerId2,
+ fence(Fence::Status::Unsignaled),
+ layer_state_t::eBufferChanged),
+ });
+
+ setTransactionStates({signaledTransaction, signaledTransaction2, unsignaledTransaction},
kExpectedTransactionsPending);
}
@@ -704,22 +732,19 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 0u;
const auto signaledTransaction =
createTransactionInfo(kApplyToken,
{createComposerState(kLayerId, fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged)});
- setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({signaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueue) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -729,15 +754,13 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueue) {
fence(Fence::Status::Unsignaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueSameLayerId) {
const sp<IBinder> kApplyToken =
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -750,8 +773,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueSameLayerId) {
fence(Fence::Status::Unsignaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) {
@@ -759,7 +781,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) {
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 0u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -772,8 +793,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepsInTheQueueDifferentLayerId) {
fence(Fence::Status::Unsignaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({unsignaledTransaction}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) {
@@ -781,7 +801,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLa
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
const auto kExpectedTransactionsPending = 0u;
const auto signaledTransaction =
@@ -798,8 +817,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_RemovesSignaledFromTheQueue_MultipleLa
fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsPending);
}
TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) {
@@ -808,7 +826,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) {
const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 1u;
const auto unsignaledTransaction =
@@ -825,7 +842,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueDifferentApplyToken) {
fence(Fence::Status::Signaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied,
+ setTransactionStates({unsignaledTransaction, signaledTransaction},
kExpectedTransactionsPending);
}
@@ -834,7 +851,6 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueSameApplyToken) {
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 1u;
const auto kExpectedTransactionsPending = 1u;
const auto signaledTransaction =
@@ -851,7 +867,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheQueueSameApplyToken) {
fence(Fence::Status::Unsignaled),
layer_state_t::eBufferChanged),
});
- setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsApplied,
+ setTransactionStates({signaledTransaction, unsignaledTransaction},
kExpectedTransactionsPending);
}
@@ -860,8 +876,7 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) {
IInterface::asBinder(TransactionCompletedListener::getIInstance());
const auto kLayerId1 = 1;
const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 0u;
- const auto kExpectedTransactionsPending = 1u;
+ const auto kExpectedTransactionsPending = 2u;
const auto unsignaledTransaction =
createTransactionInfo(kApplyToken,
@@ -878,208 +893,153 @@ TEST_F(LatchUnsignaledDisabledTest, Flush_KeepInTheUnsignaledTheQueue) {
layer_state_t::eBufferChanged),
});
setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
- kExpectedTransactionsApplied, kExpectedTransactionsPending);
+ kExpectedTransactionsPending);
}
-class LatchUnsignaledAlwaysTest : public LatchUnsignaledTest {
-public:
- void SetUp() override {
- LatchUnsignaledTest::SetUp();
- SurfaceFlinger::enableLatchUnsignaledConfig = LatchUnsignaledConfig::Always;
- }
-};
+TEST(TransactionHandlerTest, QueueTransaction) {
+ TransactionHandler handler;
+ TransactionState transaction;
+ transaction.applyToken = sp<BBinder>::make();
+ transaction.id = 42;
+ handler.queueTransaction(std::move(transaction));
+ handler.collectTransactions();
+ std::vector<TransactionState> transactionsReadyToBeApplied = handler.flushTransactions();
+
+ EXPECT_EQ(transactionsReadyToBeApplied.size(), 1u);
+ EXPECT_EQ(transactionsReadyToBeApplied.front().id, 42u);
+}
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
- const auto kExpectedTransactionsPending = 0u;
+TEST(TransactionHandlerTest, TransactionsKeepTrackOfDirectMerges) {
+ SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4;
- const auto signaledTransaction =
- createTransactionInfo(kApplyToken,
- {createComposerState(kLayerId, fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged)});
- setTransactionStates({signaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
-}
+ uint64_t transaction2Id = transaction2.getId();
+ uint64_t transaction3Id = transaction3.getId();
+ EXPECT_NE(transaction2Id, transaction3Id);
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueue) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
- const auto kExpectedTransactionsPending = 0u;
+ transaction1.merge(std::move(transaction2));
+ transaction1.merge(std::move(transaction3));
- const auto unsignaledTransaction =
- createTransactionInfo(kApplyToken,
- {createComposerState(kLayerId, fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged)});
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction2Id);
}
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueSameLayerId) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
- const auto kExpectedTransactionsPending = 0u;
+TEST(TransactionHandlerTest, TransactionsKeepTrackOfIndirectMerges) {
+ SurfaceComposerClient::Transaction transaction1, transaction2, transaction3, transaction4;
- const auto mixedTransaction =
- createTransactionInfo(kApplyToken,
- {createComposerState(kLayerId, fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- createComposerState(kLayerId, fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged)});
- setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
-}
+ uint64_t transaction2Id = transaction2.getId();
+ uint64_t transaction3Id = transaction3.getId();
+ uint64_t transaction4Id = transaction4.getId();
+ EXPECT_NE(transaction2Id, transaction3Id);
+ EXPECT_NE(transaction2Id, transaction4Id);
+ EXPECT_NE(transaction3Id, transaction4Id);
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentLayerId) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId1 = 1;
- const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 1u;
- const auto kExpectedTransactionsPending = 0u;
+ transaction4.merge(std::move(transaction2));
+ transaction4.merge(std::move(transaction3));
- const auto mixedTransaction =
- createTransactionInfo(kApplyToken,
- {createComposerState(kLayerId1, fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- createComposerState(kLayerId2, fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged)});
- setTransactionStates({mixedTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
-}
+ EXPECT_EQ(transaction4.getMergedTransactionIds().size(), 2u);
+ EXPECT_EQ(transaction4.getMergedTransactionIds()[0], transaction3Id);
+ EXPECT_EQ(transaction4.getMergedTransactionIds()[1], transaction2Id);
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesSignaledFromTheQueue_MultipleLayers) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId1 = 1;
- const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
- const auto kExpectedTransactionsPending = 0u;
+ transaction1.merge(std::move(transaction4));
- const auto signaledTransaction =
- createTransactionInfo(kApplyToken,
- {
- createComposerState(kLayerId1,
- fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged),
- });
- const auto signaledTransaction2 =
- createTransactionInfo(kApplyToken,
- {
- createComposerState(kLayerId2,
- fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged),
- });
- setTransactionStates({signaledTransaction, signaledTransaction2}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 3u);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction4Id);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[1], transaction3Id);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[2], transaction2Id);
}
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesFromTheQueueDifferentApplyToken) {
- const sp<IBinder> kApplyToken1 =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
- const auto kLayerId1 = 1;
- const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
- const auto kExpectedTransactionsPending = 0u;
+TEST(TransactionHandlerTest, TransactionMergesAreCleared) {
+ SurfaceComposerClient::Transaction transaction1, transaction2, transaction3;
- const auto signaledTransaction =
- createTransactionInfo(kApplyToken1,
- {
- createComposerState(kLayerId1,
- fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged),
- });
- const auto unsignaledTransaction =
- createTransactionInfo(kApplyToken2,
- {
- createComposerState(kLayerId2,
- fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- });
- setTransactionStates({signaledTransaction, unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
-}
+ transaction1.merge(std::move(transaction2));
+ transaction1.merge(std::move(transaction3));
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueueSameApplyToken) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId1 = 1;
- const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
- const auto kExpectedTransactionsPending = 0u;
+ EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 2u);
- const auto unsignaledTransaction =
- createTransactionInfo(kApplyToken,
- {
- createComposerState(kLayerId1,
- fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- });
- const auto signaledTransaction =
- createTransactionInfo(kApplyToken,
- {
- createComposerState(kLayerId2,
- fence(Fence::Status::Signaled),
- layer_state_t::eBufferChanged),
- });
- setTransactionStates({unsignaledTransaction, signaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ transaction1.clear();
+
+ EXPECT_EQ(transaction1.getMergedTransactionIds().empty(), true);
}
-TEST_F(LatchUnsignaledAlwaysTest, Flush_RemovesUnsignaledFromTheQueue) {
- const sp<IBinder> kApplyToken1 =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const sp<IBinder> kApplyToken2 = sp<BBinder>::make();
- const auto kLayerId1 = 1;
- const auto kLayerId2 = 2;
- const auto kExpectedTransactionsApplied = 2u;
- const auto kExpectedTransactionsPending = 0u;
+TEST(TransactionHandlerTest, TransactionMergesAreCapped) {
+ SurfaceComposerClient::Transaction transaction;
+ std::vector<uint64_t> mergedTransactionIds;
- const auto unsignaledTransaction =
- createTransactionInfo(kApplyToken1,
- {
- createComposerState(kLayerId1,
- fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- });
- const auto unsignaledTransaction2 =
- createTransactionInfo(kApplyToken2,
- {
- createComposerState(kLayerId2,
- fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- });
- setTransactionStates({unsignaledTransaction, unsignaledTransaction2},
- kExpectedTransactionsApplied, kExpectedTransactionsPending);
+ for (uint i = 0; i < 20u; i++) {
+ SurfaceComposerClient::Transaction transactionToMerge;
+ mergedTransactionIds.push_back(transactionToMerge.getId());
+ transaction.merge(std::move(transactionToMerge));
+ }
+
+ // Keeps latest 10 merges in order of merge recency
+ EXPECT_EQ(transaction.getMergedTransactionIds().size(), 10u);
+ for (uint i = 0; i < 10u; i++) {
+ EXPECT_EQ(transaction.getMergedTransactionIds()[i],
+ mergedTransactionIds[mergedTransactionIds.size() - 1 - i]);
+ }
}
-TEST_F(LatchUnsignaledAlwaysTest, LatchUnsignaledWhenEarlyOffset) {
- const sp<IBinder> kApplyToken =
- IInterface::asBinder(TransactionCompletedListener::getIInstance());
- const auto kLayerId = 1;
- const auto kExpectedTransactionsApplied = 1u;
- const auto kExpectedTransactionsPending = 0u;
+TEST(TransactionHandlerTest, KeepsMergesFromMoreRecentMerge) {
+ SurfaceComposerClient::Transaction transaction1, transaction2, transaction3;
+ std::vector<uint64_t> mergedTransactionIds1, mergedTransactionIds2, mergedTransactionIds3;
+ uint64_t transaction2Id = transaction2.getId();
+ uint64_t transaction3Id = transaction3.getId();
- const auto unsignaledTransaction =
- createTransactionInfo(kApplyToken,
- {
- createComposerState(kLayerId,
- fence(Fence::Status::Unsignaled),
- layer_state_t::eBufferChanged),
- });
+ for (uint i = 0; i < 20u; i++) {
+ SurfaceComposerClient::Transaction transactionToMerge;
+ mergedTransactionIds1.push_back(transactionToMerge.getId());
+ transaction1.merge(std::move(transactionToMerge));
+ }
- // Get VsyncModulator out of the default config
- static_cast<void>(mFlinger.mutableVsyncModulator()->onRefreshRateChangeInitiated());
+ for (uint i = 0; i < 5u; i++) {
+ SurfaceComposerClient::Transaction transactionToMerge;
+ mergedTransactionIds2.push_back(transactionToMerge.getId());
+ transaction2.merge(std::move(transactionToMerge));
+ }
- setTransactionStates({unsignaledTransaction}, kExpectedTransactionsApplied,
- kExpectedTransactionsPending);
+ transaction1.merge(std::move(transaction2));
+ EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction2Id);
+ for (uint i = 0; i < 5u; i++) {
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1u],
+ mergedTransactionIds2[mergedTransactionIds2.size() - 1 - i]);
+ }
+ for (uint i = 0; i < 4u; i++) {
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 6u],
+ mergedTransactionIds1[mergedTransactionIds1.size() - 1 - i]);
+ }
+
+ for (uint i = 0; i < 20u; i++) {
+ SurfaceComposerClient::Transaction transactionToMerge;
+ mergedTransactionIds3.push_back(transactionToMerge.getId());
+ transaction3.merge(std::move(transactionToMerge));
+ }
+
+ transaction1.merge(std::move(transaction3));
+ EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[0], transaction3Id);
+ for (uint i = 0; i < 9u; i++) {
+ EXPECT_EQ(transaction1.getMergedTransactionIds()[i + 1],
+ mergedTransactionIds3[mergedTransactionIds3.size() - 1 - i]);
+ }
+}
+
+TEST(TransactionHandlerTest, CanAddTransactionWithFullMergedIds) {
+ SurfaceComposerClient::Transaction transaction1, transaction2;
+ for (uint i = 0; i < 20u; i++) {
+ SurfaceComposerClient::Transaction transactionToMerge;
+ transaction1.merge(std::move(transactionToMerge));
+ }
+
+ EXPECT_EQ(transaction1.getMergedTransactionIds().size(), 10u);
+
+ auto transaction1Id = transaction1.getId();
+ transaction2.merge(std::move(transaction1));
+ EXPECT_EQ(transaction2.getMergedTransactionIds().size(), 10u);
+ auto mergedTransactionIds = transaction2.getMergedTransactionIds();
+ EXPECT_TRUE(std::count(mergedTransactionIds.begin(), mergedTransactionIds.end(),
+ transaction1Id) > 0);
}
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
index 5364630400..d4d5b32341 100644
--- a/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionFrameTracerTest.cpp
@@ -28,15 +28,13 @@
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
-#include "mock/MockVsyncController.h"
namespace android {
using testing::_;
using testing::Mock;
using testing::Return;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
using PresentState = frametimeline::SurfaceFrame::PresentState;
class TransactionFrameTracerTest : public testing::Test {
@@ -45,7 +43,7 @@ public:
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());
- setupScheduler();
+ mFlinger.setupMockScheduler();
mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
}
@@ -56,42 +54,14 @@ public:
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
- sp<BufferStateLayer> createBufferStateLayer() {
+ sp<Layer> createLayer() {
sp<Client> client;
LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0,
LayerMetadata());
- return new BufferStateLayer(args);
- }
-
- void commitTransaction(Layer* layer) {
- auto c = layer->getDrawingState();
- layer->commitTransaction(c);
+ return sp<Layer>::make(args);
}
- void setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread));
- }
+ void commitTransaction(Layer* layer) { layer->commitTransaction(); }
TestableSurfaceFlinger mFlinger;
renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
@@ -99,9 +69,9 @@ public:
FenceToFenceTimeMap fenceFactory;
void BLASTTransactionSendsFrameTracerEvents() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
+ sp<Layer> layer = createLayer();
- sp<Fence> fence(new Fence());
+ sp<Fence> fence(sp<Fence>::make());
int32_t layerId = layer->getSequence();
uint64_t bufferId = 42;
uint64_t frameNumber = 5;
@@ -127,7 +97,6 @@ public:
dequeueTime, FrameTimelineInfo{});
commitTransaction(layer.get());
- bool computeVisisbleRegions;
nsecs_t latchTime = 25;
EXPECT_CALL(*mFlinger.getFrameTracer(),
traceFence(layerId, bufferId, frameNumber, _,
@@ -135,7 +104,7 @@ public:
EXPECT_CALL(*mFlinger.getFrameTracer(),
traceTimestamp(layerId, bufferId, frameNumber, latchTime,
FrameTracer::FrameEvent::LATCH, /*duration*/ 0));
- layer->updateTexImage(computeVisisbleRegions, latchTime, /*expectedPresentTime*/ 0);
+ layer->updateTexImage(latchTime);
auto glDoneFence = fenceFactory.createFenceTimeForTest(fence);
auto presentFence = fenceFactory.createFenceTimeForTest(fence);
@@ -143,7 +112,7 @@ public:
EXPECT_CALL(*mFlinger.getFrameTracer(),
traceFence(layerId, bufferId, frameNumber, presentFence,
FrameTracer::FrameEvent::PRESENT_FENCE, /*startTime*/ 0));
- layer->onPostComposition(nullptr, glDoneFence, presentFence, compositorTiming);
+ layer->onCompositionPresented(nullptr, glDoneFence, presentFence, compositorTiming);
}
};
diff --git a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
index f5e3b77ca6..cbb597af69 100644
--- a/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionProtoParserTest.cpp
@@ -19,6 +19,8 @@
#include <limits> // std::numeric_limits
#include <gui/SurfaceComposerClient.h>
+#include <ui/Rotation.h>
+#include "LayerProtoHelper.h"
#include "Tracing/TransactionProtoParser.h"
@@ -27,8 +29,7 @@ using namespace android::surfaceflinger;
namespace android {
TEST(TransactionProtoParserTest, parse) {
- const sp<IBinder> layerHandle = new BBinder();
- const sp<IBinder> displayHandle = new BBinder();
+ const sp<IBinder> displayHandle = sp<BBinder>::make();
TransactionState t1;
t1.originPid = 1;
t1.originUid = 2;
@@ -37,7 +38,6 @@ TEST(TransactionProtoParserTest, parse) {
t1.postTime = 5;
layer_state_t layer;
- layer.layerId = 6;
layer.what = std::numeric_limits<uint64_t>::max();
layer.what &= ~static_cast<uint64_t>(layer_state_t::eBufferChanged);
layer.x = 7;
@@ -46,14 +46,13 @@ TEST(TransactionProtoParserTest, parse) {
size_t layerCount = 2;
t1.states.reserve(layerCount);
for (uint32_t i = 0; i < layerCount; i++) {
- ComposerState s;
+ ResolvedComposerState s;
if (i == 1) {
- layer.parentSurfaceControlForChild =
- new SurfaceControl(SurfaceComposerClient::getDefault(), layerHandle, nullptr,
- 42);
+ s.parentId = 42;
}
+ s.layerId = 6 + i;
s.state = layer;
- t1.states.add(s);
+ t1.states.emplace_back(s);
}
size_t displayCount = 2;
@@ -72,18 +71,10 @@ TEST(TransactionProtoParserTest, parse) {
class TestMapper : public TransactionProtoParser::FlingerDataMapper {
public:
- sp<IBinder> layerHandle;
sp<IBinder> displayHandle;
- TestMapper(sp<IBinder> layerHandle, sp<IBinder> displayHandle)
- : layerHandle(layerHandle), displayHandle(displayHandle) {}
+ TestMapper(sp<IBinder> displayHandle) : displayHandle(displayHandle) {}
- sp<IBinder> getLayerHandle(int32_t id) const override {
- return (id == 42) ? layerHandle : nullptr;
- }
- int64_t getLayerId(const sp<IBinder>& handle) const override {
- return (handle == layerHandle) ? 42 : -1;
- }
sp<IBinder> getDisplayHandle(int32_t id) const {
return (id == 43) ? displayHandle : nullptr;
}
@@ -92,9 +83,9 @@ TEST(TransactionProtoParserTest, parse) {
}
};
- TransactionProtoParser parser(std::make_unique<TestMapper>(layerHandle, displayHandle));
+ TransactionProtoParser parser(std::make_unique<TestMapper>(displayHandle));
- proto::TransactionState proto = parser.toProto(t1);
+ perfetto::protos::TransactionState proto = parser.toProto(t1);
TransactionState t2 = parser.fromProto(proto);
ASSERT_EQ(t1.originPid, t2.originPid);
@@ -105,12 +96,56 @@ TEST(TransactionProtoParserTest, parse) {
ASSERT_EQ(t1.states.size(), t2.states.size());
ASSERT_EQ(t1.states[0].state.x, t2.states[0].state.x);
ASSERT_EQ(t1.states[0].state.matrix.dsdx, t2.states[0].state.matrix.dsdx);
- ASSERT_EQ(t1.states[1].state.parentSurfaceControlForChild->getHandle(),
- t2.states[1].state.parentSurfaceControlForChild->getHandle());
+ ASSERT_EQ(t1.states[1].layerId, t2.states[1].layerId);
+ ASSERT_EQ(t1.states[1].parentId, t2.states[1].parentId);
ASSERT_EQ(t1.displays.size(), t2.displays.size());
ASSERT_EQ(t1.displays[1].width, t2.displays[1].width);
ASSERT_EQ(t1.displays[0].token, t2.displays[0].token);
}
+TEST(TransactionProtoParserTest, parseDisplayInfo) {
+ frontend::DisplayInfo d1;
+ d1.info.displayId = 42;
+ d1.info.logicalWidth = 43;
+ d1.info.logicalHeight = 44;
+ d1.info.transform.set(1, 2, 3, 4);
+ d1.transform = d1.info.transform.inverse();
+ d1.receivesInput = true;
+ d1.isSecure = false;
+ d1.isPrimary = true;
+ d1.isVirtual = false;
+ d1.rotationFlags = ui::Transform::ROT_180;
+ d1.transformHint = ui::Transform::ROT_90;
+
+ const uint32_t layerStack = 2;
+ google::protobuf::RepeatedPtrField<perfetto::protos::DisplayInfo> displayProtos;
+ auto displayInfoProto = displayProtos.Add();
+ *displayInfoProto = TransactionProtoParser::toProto(d1, layerStack);
+ frontend::DisplayInfos displayInfos;
+ TransactionProtoParser::fromProto(displayProtos, displayInfos);
+
+ ASSERT_TRUE(displayInfos.contains(ui::LayerStack::fromValue(layerStack)));
+ frontend::DisplayInfo d2 = displayInfos.get(ui::LayerStack::fromValue(layerStack))->get();
+ EXPECT_EQ(d1.info.displayId, d2.info.displayId);
+ EXPECT_EQ(d1.info.logicalWidth, d2.info.logicalWidth);
+ EXPECT_EQ(d1.info.logicalHeight, d2.info.logicalHeight);
+
+ EXPECT_EQ(d1.info.transform.dsdx(), d2.info.transform.dsdx());
+ EXPECT_EQ(d1.info.transform.dsdy(), d2.info.transform.dsdy());
+ EXPECT_EQ(d1.info.transform.dtdx(), d2.info.transform.dtdx());
+ EXPECT_EQ(d1.info.transform.dtdy(), d2.info.transform.dtdy());
+
+ EXPECT_EQ(d1.transform.dsdx(), d2.transform.dsdx());
+ EXPECT_EQ(d1.transform.dsdy(), d2.transform.dsdy());
+ EXPECT_EQ(d1.transform.dtdx(), d2.transform.dtdx());
+ EXPECT_EQ(d1.transform.dtdy(), d2.transform.dtdy());
+
+ EXPECT_EQ(d1.receivesInput, d2.receivesInput);
+ EXPECT_EQ(d1.isSecure, d2.isSecure);
+ EXPECT_EQ(d1.isVirtual, d2.isVirtual);
+ EXPECT_EQ(d1.rotationFlags, d2.rotationFlags);
+ EXPECT_EQ(d1.transformHint, d2.transformHint);
+}
+
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
index 5bb4c92a8e..5046a86304 100644
--- a/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionSurfaceFrameTest.cpp
@@ -28,15 +28,13 @@
#include "TestableSurfaceFlinger.h"
#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
-#include "mock/MockVsyncController.h"
namespace android {
using testing::_;
using testing::Mock;
using testing::Return;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
+
using PresentState = frametimeline::SurfaceFrame::PresentState;
class TransactionSurfaceFrameTest : public testing::Test {
@@ -45,7 +43,7 @@ public:
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());
- setupScheduler();
+ mFlinger.setupMockScheduler();
mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
mFlinger.setupRenderEngine(std::unique_ptr<renderengine::RenderEngine>(mRenderEngine));
}
@@ -56,42 +54,13 @@ public:
ALOGD("**** Tearing down after %s.%s\n", test_info->test_case_name(), test_info->name());
}
- sp<BufferStateLayer> createBufferStateLayer() {
+ sp<Layer> createLayer() {
sp<Client> client;
- LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", 0,
- LayerMetadata());
- return new BufferStateLayer(args);
- }
-
- void commitTransaction(Layer* layer) {
- auto c = layer->getDrawingState();
- layer->commitTransaction(c);
+ LayerCreationArgs args(mFlinger.flinger(), client, "layer", 0, LayerMetadata());
+ return sp<Layer>::make(args);
}
- void setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread));
- }
+ void commitTransaction(Layer* layer) { layer->commitTransaction(); }
TestableSurfaceFlinger mFlinger;
renderengine::mock::RenderEngine* mRenderEngine = new renderengine::mock::RenderEngine();
@@ -99,9 +68,11 @@ public:
FenceToFenceTimeMap fenceFactory;
void PresentedSurfaceFrameForBufferlessTransaction() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
- layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0},
- 10);
+ sp<Layer> layer = createLayer();
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_TRUE(layer->mDrawingState.bufferSurfaceFrameTX == nullptr);
const auto surfaceFrame = layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*token*/ 1);
@@ -112,8 +83,8 @@ public:
}
void PresentedSurfaceFrameForBufferTransaction() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
- sp<Fence> fence(new Fence());
+ sp<Layer> layer = createLayer();
+ sp<Fence> fence(sp<Fence>::make());
auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
BufferData bufferData;
bufferData.acquireFence = fence;
@@ -125,8 +96,10 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
acquireFence->signalForTest(12);
commitTransaction(layer.get());
@@ -136,8 +109,7 @@ public:
// Buffers are presented only at latch time.
EXPECT_EQ(PresentState::Unknown, surfaceFrame->getPresentState());
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
EXPECT_EQ(1, surfaceFrame->getToken());
EXPECT_EQ(true, surfaceFrame->getIsBuffer());
@@ -145,9 +117,9 @@ public:
}
void DroppedSurfaceFrameForBufferTransaction() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
+ sp<Layer> layer = createLayer();
- sp<Fence> fence1(new Fence());
+ sp<Fence> fence1(sp<Fence>::make());
auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
BufferData bufferData;
bufferData.acquireFence = fence1;
@@ -159,13 +131,15 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
- sp<Fence> fence2(new Fence());
+ sp<Fence> fence2(sp<Fence>::make());
auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
nsecs_t start = systemTime();
bufferData.acquireFence = fence2;
@@ -177,8 +151,7 @@ public:
2ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
nsecs_t end = systemTime();
acquireFence2->signalForTest(12);
@@ -187,8 +160,7 @@ public:
const auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
commitTransaction(layer.get());
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
EXPECT_EQ(1, droppedSurfaceFrame->getToken());
EXPECT_EQ(true, droppedSurfaceFrame->getIsBuffer());
@@ -203,15 +175,17 @@ public:
}
void BufferlessSurfaceFramePromotedToBufferSurfaceFrame() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
+ sp<Layer> layer = createLayer();
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
- layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0},
- 10);
+ layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
- sp<Fence> fence(new Fence());
+ sp<Fence> fence(sp<Fence>::make());
auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
BufferData bufferData;
bufferData.acquireFence = fence;
@@ -223,8 +197,7 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
acquireFence->signalForTest(12);
EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
@@ -237,15 +210,14 @@ public:
// Buffers are presented only at latch time.
EXPECT_EQ(PresentState::Unknown, surfaceFrame->getPresentState());
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
EXPECT_EQ(PresentState::Presented, surfaceFrame->getPresentState());
}
void BufferlessSurfaceFrameNotCreatedIfBufferSufaceFrameExists() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
- sp<Fence> fence(new Fence());
+ sp<Layer> layer = createLayer();
+ sp<Fence> fence(sp<Fence>::make());
auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
BufferData bufferData;
bufferData.acquireFence = fence;
@@ -257,33 +229,38 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
- layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0},
- 10);
+ layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
}
void MultipleSurfaceFramesPresentedTogether() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
- layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 1, /*inputEventId*/ 0},
- 10);
+ sp<Layer> layer = createLayer();
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo, 10);
EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto bufferlessSurfaceFrame1 =
layer->mDrawingState.bufferlessSurfaceFramesTX.at(/*token*/ 1);
- layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 4, /*inputEventId*/ 0},
- 10);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = 4;
+ ftInfo2.inputEventId = 0;
+ layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10);
EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_EQ(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto bufferlessSurfaceFrame2 = layer->mDrawingState.bufferlessSurfaceFramesTX[4];
- sp<Fence> fence(new Fence());
+ sp<Fence> fence(sp<Fence>::make());
auto acquireFence = fenceFactory.createFenceTimeForTest(fence);
BufferData bufferData;
bufferData.acquireFence = fence;
@@ -295,8 +272,10 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 3, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo3;
+ ftInfo3.vsyncId = 3;
+ ftInfo3.inputEventId = 0;
+ layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo3);
EXPECT_EQ(2u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto bufferSurfaceFrameTX = layer->mDrawingState.bufferSurfaceFrameTX;
@@ -318,16 +297,15 @@ public:
// Buffers are presented only at latch time.
EXPECT_EQ(PresentState::Unknown, bufferSurfaceFrameTX->getPresentState());
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
EXPECT_EQ(PresentState::Presented, bufferSurfaceFrameTX->getPresentState());
}
void PendingSurfaceFramesRemovedAfterClassification() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
+ sp<Layer> layer = createLayer();
- sp<Fence> fence1(new Fence());
+ sp<Fence> fence1(sp<Fence>::make());
auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
BufferData bufferData;
bufferData.acquireFence = fence1;
@@ -339,12 +317,14 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto droppedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
- sp<Fence> fence2(new Fence());
+ sp<Fence> fence2(sp<Fence>::make());
auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
bufferData.acquireFence = fence2;
bufferData.frameNumber = 1;
@@ -355,21 +335,19 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfo);
acquireFence2->signalForTest(12);
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
commitTransaction(layer.get());
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
// Both the droppedSurfaceFrame and presentedSurfaceFrame should be in
// pendingJankClassifications.
EXPECT_EQ(2u, layer->mPendingJankClassifications.size());
- presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
+ presentedSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
/*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
layer->releasePendingBuffer(25);
@@ -377,9 +355,9 @@ public:
}
void BufferSurfaceFrame_ReplaceValidTokenBufferWithInvalidTokenBuffer() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
+ sp<Layer> layer = createLayer();
- sp<Fence> fence1(new Fence());
+ sp<Fence> fence1(sp<Fence>::make());
auto acquireFence1 = fenceFactory.createFenceTimeForTest(fence1);
BufferData bufferData;
bufferData.acquireFence = fence1;
@@ -391,13 +369,15 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setBuffer(externalTexture1, bufferData, 10, 20, false, std::nullopt, ftInfo);
EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto droppedSurfaceFrame1 = layer->mDrawingState.bufferSurfaceFrameTX;
- sp<Fence> fence2(new Fence());
+ sp<Fence> fence2(sp<Fence>::make());
auto acquireFence2 = fenceFactory.createFenceTimeForTest(fence2);
auto dropStartTime1 = systemTime();
bufferData.acquireFence = fence2;
@@ -409,14 +389,16 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ FrameTimelineInfo::INVALID_VSYNC_ID, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfoInv;
+ ftInfoInv.vsyncId = FrameTimelineInfo::INVALID_VSYNC_ID;
+ ftInfoInv.inputEventId = 0;
+ layer->setBuffer(externalTexture2, bufferData, 10, 20, false, std::nullopt, ftInfoInv);
auto dropEndTime1 = systemTime();
EXPECT_EQ(0u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
const auto droppedSurfaceFrame2 = layer->mDrawingState.bufferSurfaceFrameTX;
- sp<Fence> fence3(new Fence());
+ sp<Fence> fence3(sp<Fence>::make());
auto acquireFence3 = fenceFactory.createFenceTimeForTest(fence3);
auto dropStartTime2 = systemTime();
bufferData.acquireFence = fence3;
@@ -428,8 +410,10 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 2, /*inputEventId*/ 0});
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = 2;
+ ftInfo2.inputEventId = 0;
+ layer->setBuffer(externalTexture3, bufferData, 10, 20, false, std::nullopt, ftInfo2);
auto dropEndTime2 = systemTime();
acquireFence3->signalForTest(12);
@@ -438,8 +422,7 @@ public:
const auto presentedSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
commitTransaction(layer.get());
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
EXPECT_EQ(1, droppedSurfaceFrame1->getToken());
EXPECT_EQ(true, droppedSurfaceFrame1->getIsBuffer());
@@ -461,11 +444,11 @@ public:
}
void MultipleCommitsBeforeLatch() {
- sp<BufferStateLayer> layer = createBufferStateLayer();
+ sp<Layer> layer = createLayer();
uint32_t surfaceFramesPendingClassification = 0;
std::vector<std::shared_ptr<frametimeline::SurfaceFrame>> bufferlessSurfaceFrames;
for (int i = 0; i < 10; i += 2) {
- sp<Fence> fence(new Fence());
+ sp<Fence> fence(sp<Fence>::make());
BufferData bufferData;
bufferData.acquireFence = fence;
bufferData.frameNumber = 1;
@@ -476,11 +459,14 @@ public:
1ULL /* bufferId */,
HAL_PIXEL_FORMAT_RGBA_8888,
0ULL /*usage*/);
- layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt,
- {/*vsyncId*/ 1, /*inputEventId*/ 0});
- layer->setFrameTimelineVsyncForBufferlessTransaction({/*vsyncId*/ 2,
- /*inputEventId*/ 0},
- 10);
+ FrameTimelineInfo ftInfo;
+ ftInfo.vsyncId = 1;
+ ftInfo.inputEventId = 0;
+ layer->setBuffer(externalTexture, bufferData, 10, 20, false, std::nullopt, ftInfo);
+ FrameTimelineInfo ftInfo2;
+ ftInfo2.vsyncId = 2;
+ ftInfo2.inputEventId = 0;
+ layer->setFrameTimelineVsyncForBufferlessTransaction(ftInfo2, 10);
ASSERT_NE(nullptr, layer->mDrawingState.bufferSurfaceFrameTX);
EXPECT_EQ(1u, layer->mDrawingState.bufferlessSurfaceFramesTX.size());
auto& bufferlessSurfaceFrame =
@@ -494,15 +480,14 @@ public:
}
auto presentedBufferSurfaceFrame = layer->mDrawingState.bufferSurfaceFrameTX;
- bool computeVisisbleRegions;
- layer->updateTexImage(computeVisisbleRegions, 15, 0);
+ layer->updateTexImage(15);
// BufferlessSurfaceFrames are immediately set to presented and added to the DisplayFrame.
// Since we don't have access to DisplayFrame here, trigger an onPresent directly.
for (auto& surfaceFrame : bufferlessSurfaceFrames) {
- surfaceFrame->onPresent(20, JankType::None, 90_Hz,
+ surfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
/*displayDeadlineDelta*/ 0, /*displayPresentDelta*/ 0);
}
- presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz,
+ presentedBufferSurfaceFrame->onPresent(20, JankType::None, 90_Hz, 90_Hz,
/*displayDeadlineDelta*/ 0,
/*displayPresentDelta*/ 0);
diff --git a/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
new file mode 100644
index 0000000000..4a83d445fc
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/TransactionTraceWriterTest.cpp
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "TransactionTraceWriterTest"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+#include <filesystem>
+
+#include "TestableSurfaceFlinger.h"
+
+namespace android {
+
+class TransactionTraceWriterTest : public testing::Test {
+protected:
+ std::string mFilename = "/data/local/tmp/testfile_transaction_trace.winscope";
+
+ void SetUp() { mFlinger.initTransactionTraceWriter(); }
+ void TearDown() { std::filesystem::remove(mFilename); }
+
+ void verifyTraceFile() {
+ std::fstream file(mFilename, std::ios::in);
+ ASSERT_TRUE(file.is_open());
+ std::string line;
+ char magicNumber[8];
+ file.read(magicNumber, 8);
+ EXPECT_EQ("\tTNXTRAC", std::string(magicNumber, magicNumber + 8));
+ }
+
+ TestableSurfaceFlinger mFlinger;
+};
+
+// Check that a new file is written if overwrite=true and no file exists.
+TEST_F(TransactionTraceWriterTest, canWriteToFile_overwriteTrue) {
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+ EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+ verifyTraceFile();
+}
+
+// Check that a new file is written if overwrite=false and no file exists.
+TEST_F(TransactionTraceWriterTest, canWriteToFile_overwriteFalse) {
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+ EXPECT_EQ(access(mFilename.c_str(), F_OK), 0);
+ verifyTraceFile();
+}
+
+// Check that an existing file is overwritten when overwrite=true.
+TEST_F(TransactionTraceWriterTest, canOverwriteFile) {
+ std::string testLine = "test";
+ {
+ std::ofstream file(mFilename, std::ios::out);
+ file << testLine;
+ }
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ true);
+ verifyTraceFile();
+}
+
+// Check that an existing file isn't overwritten when it is new and overwrite=false.
+TEST_F(TransactionTraceWriterTest, doNotOverwriteFile) {
+ std::string testLine = "test";
+ {
+ std::ofstream file(mFilename, std::ios::out);
+ file << testLine;
+ }
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+ {
+ std::fstream file(mFilename, std::ios::in);
+ ASSERT_TRUE(file.is_open());
+ std::string line;
+ std::getline(file, line);
+ EXPECT_EQ(line, testLine);
+ }
+}
+
+// Check that an existing file is overwritten when it is old and overwrite=false.
+TEST_F(TransactionTraceWriterTest, overwriteOldFile) {
+ std::string testLine = "test";
+ {
+ std::ofstream file(mFilename, std::ios::out);
+ file << testLine;
+ }
+
+ // Update file modification time to 15 minutes ago.
+ using Clock = std::filesystem::file_time_type::clock;
+ std::error_code error;
+ std::filesystem::last_write_time(mFilename, Clock::now() - std::chrono::minutes{15}, error);
+ ASSERT_EQ(error.value(), 0);
+
+ TransactionTraceWriter::getInstance().invokeForTest(mFilename, /* overwrite */ false);
+ verifyTraceFile();
+}
+
+} // namespace android \ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
index 61b72a0b9b..fb4ef70450 100644
--- a/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TransactionTracingTest.cpp
@@ -18,8 +18,13 @@
#include <gtest/gtest.h>
#include <gui/SurfaceComposerClient.h>
+#include <cstdint>
+#include "Client.h"
-#include "Tracing/RingBuffer.h"
+#include <layerproto/LayerProtoHeader.h>
+#include "FrontEnd/LayerCreationArgs.h"
+#include "FrontEnd/Update.h"
+#include "Tracing/LayerTracing.h"
#include "Tracing/TransactionTracing.h"
using namespace android::surfaceflinger;
@@ -31,39 +36,58 @@ protected:
static constexpr size_t SMALL_BUFFER_SIZE = 1024;
TransactionTracing mTracing;
- void flush(int64_t vsyncId) { mTracing.flush(vsyncId); }
- proto::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
+ void flush() { mTracing.flush(); }
+ perfetto::protos::TransactionTraceFile writeToProto() { return mTracing.writeToProto(); }
- proto::TransactionTraceEntry bufferFront() {
+ perfetto::protos::TransactionTraceEntry bufferFront() {
std::scoped_lock<std::mutex> lock(mTracing.mTraceLock);
- proto::TransactionTraceEntry entry;
+ perfetto::protos::TransactionTraceEntry entry;
entry.ParseFromString(mTracing.mBuffer.front());
return entry;
}
void queueAndCommitTransaction(int64_t vsyncId) {
+ frontend::Update update;
TransactionState transaction;
transaction.id = static_cast<uint64_t>(vsyncId * 3);
transaction.originUid = 1;
transaction.originPid = 2;
mTracing.addQueuedTransaction(transaction);
std::vector<TransactionState> transactions;
- transactions.emplace_back(transaction);
- mTracing.addCommittedTransactions(transactions, vsyncId);
- flush(vsyncId);
+ update.transactions.emplace_back(transaction);
+ mTracing.addCommittedTransactions(vsyncId, 0, update, {}, false);
+ flush();
}
- void verifyEntry(const proto::TransactionTraceEntry& actualProto,
+ void verifyEntry(const perfetto::protos::TransactionTraceEntry& actualProto,
const std::vector<TransactionState>& expectedTransactions,
int64_t expectedVsyncId) {
EXPECT_EQ(actualProto.vsync_id(), expectedVsyncId);
- EXPECT_EQ(actualProto.transactions().size(),
+ ASSERT_EQ(actualProto.transactions().size(),
static_cast<int32_t>(expectedTransactions.size()));
for (uint32_t i = 0; i < expectedTransactions.size(); i++) {
- EXPECT_EQ(actualProto.transactions(static_cast<int32_t>(i)).pid(),
- expectedTransactions[i].originPid);
+ const auto expectedTransaction = expectedTransactions[i];
+ const auto protoTransaction = actualProto.transactions(static_cast<int32_t>(i));
+ EXPECT_EQ(protoTransaction.transaction_id(), expectedTransaction.id);
+ EXPECT_EQ(protoTransaction.pid(), expectedTransaction.originPid);
+ for (uint32_t i = 0; i < expectedTransaction.mergedTransactionIds.size(); i++) {
+ EXPECT_EQ(protoTransaction.merged_transaction_ids(static_cast<int32_t>(i)),
+ expectedTransaction.mergedTransactionIds[i]);
+ }
}
}
+
+ LayerCreationArgs getLayerCreationArgs(uint32_t layerId, uint32_t parentId,
+ uint32_t layerIdToMirror, uint32_t flags,
+ bool addToRoot) {
+ LayerCreationArgs args;
+ args.sequence = layerId;
+ args.parentId = parentId;
+ args.layerIdToMirror = layerIdToMirror;
+ args.flags = flags;
+ args.addToRoot = addToRoot;
+ return args;
+ }
};
TEST_F(TransactionTracingTest, addTransactions) {
@@ -73,62 +97,67 @@ TEST_F(TransactionTracingTest, addTransactions) {
TransactionState transaction;
transaction.id = i;
transaction.originPid = static_cast<int32_t>(i);
+ transaction.mergedTransactionIds = std::vector<uint64_t>{i + 100, i + 102};
transactions.emplace_back(transaction);
mTracing.addQueuedTransaction(transaction);
}
// Split incoming transactions into two and commit them in reverse order to test out of order
// commits.
- std::vector<TransactionState> firstTransactionSet =
- std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
int64_t firstTransactionSetVsyncId = 42;
- mTracing.addCommittedTransactions(firstTransactionSet, firstTransactionSetVsyncId);
+ frontend::Update firstUpdate;
+ firstUpdate.transactions =
+ std::vector<TransactionState>(transactions.begin() + 50, transactions.end());
+ mTracing.addCommittedTransactions(firstTransactionSetVsyncId, 0, firstUpdate, {}, false);
int64_t secondTransactionSetVsyncId = 43;
- std::vector<TransactionState> secondTransactionSet =
+ frontend::Update secondUpdate;
+ secondUpdate.transactions =
std::vector<TransactionState>(transactions.begin(), transactions.begin() + 50);
- mTracing.addCommittedTransactions(secondTransactionSet, secondTransactionSetVsyncId);
- flush(secondTransactionSetVsyncId);
+ mTracing.addCommittedTransactions(secondTransactionSetVsyncId, 0, secondUpdate, {}, false);
+ flush();
- proto::TransactionTraceFile proto = writeToProto();
- EXPECT_EQ(proto.entry().size(), 2);
- verifyEntry(proto.entry(0), firstTransactionSet, firstTransactionSetVsyncId);
- verifyEntry(proto.entry(1), secondTransactionSet, secondTransactionSetVsyncId);
+ perfetto::protos::TransactionTraceFile proto = writeToProto();
+ ASSERT_EQ(proto.entry().size(), 2);
+ verifyEntry(proto.entry(0), firstUpdate.transactions, firstTransactionSetVsyncId);
+ verifyEntry(proto.entry(1), secondUpdate.transactions, secondTransactionSetVsyncId);
}
class TransactionTracingLayerHandlingTest : public TransactionTracingTest {
protected:
void SetUp() override {
- // add layers
mTracing.setBufferSize(SMALL_BUFFER_SIZE);
- const sp<IBinder> fakeLayerHandle = new BBinder();
- mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mParentLayerId, "parent",
- 123 /* flags */, -1 /* parentId */);
- const sp<IBinder> fakeChildLayerHandle = new BBinder();
- mTracing.onLayerAdded(fakeChildLayerHandle->localBinder(), mChildLayerId, "child",
- 456 /* flags */, mParentLayerId);
-
- // add some layer transaction
+
+ // add layers and add some layer transaction
{
+ frontend::Update update;
+ update.layerCreationArgs.emplace_back(std::move(
+ getLayerCreationArgs(mParentLayerId, /*parentId=*/UNASSIGNED_LAYER_ID,
+ /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123,
+ /*addToRoot=*/true)));
+ update.layerCreationArgs.emplace_back(std::move(
+ getLayerCreationArgs(mChildLayerId, mParentLayerId,
+ /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/456,
+ /*addToRoot=*/true)));
TransactionState transaction;
transaction.id = 50;
- ComposerState layerState;
- layerState.state.surface = fakeLayerHandle;
+ ResolvedComposerState layerState;
+ layerState.layerId = mParentLayerId;
layerState.state.what = layer_state_t::eLayerChanged;
layerState.state.z = 42;
- transaction.states.add(layerState);
- ComposerState childState;
- childState.state.surface = fakeChildLayerHandle;
+ transaction.states.emplace_back(layerState);
+ ResolvedComposerState childState;
+ childState.layerId = mChildLayerId;
childState.state.what = layer_state_t::eLayerChanged;
childState.state.z = 43;
- transaction.states.add(childState);
+ transaction.states.emplace_back(childState);
mTracing.addQueuedTransaction(transaction);
- std::vector<TransactionState> transactions;
- transactions.emplace_back(transaction);
+ update.transactions.emplace_back(transaction);
VSYNC_ID_FIRST_LAYER_CHANGE = ++mVsyncId;
- mTracing.addCommittedTransactions(transactions, VSYNC_ID_FIRST_LAYER_CHANGE);
- flush(VSYNC_ID_FIRST_LAYER_CHANGE);
+ mTracing.addCommittedTransactions(VSYNC_ID_FIRST_LAYER_CHANGE, 0, update, {}, false);
+
+ flush();
}
// add transactions that modify the layer state further so we can test that layer state
@@ -136,19 +165,19 @@ protected:
{
TransactionState transaction;
transaction.id = 51;
- ComposerState layerState;
- layerState.state.surface = fakeLayerHandle;
+ ResolvedComposerState layerState;
+ layerState.layerId = mParentLayerId;
layerState.state.what = layer_state_t::eLayerChanged | layer_state_t::ePositionChanged;
layerState.state.z = 41;
layerState.state.x = 22;
- transaction.states.add(layerState);
+ transaction.states.emplace_back(layerState);
mTracing.addQueuedTransaction(transaction);
- std::vector<TransactionState> transactions;
- transactions.emplace_back(transaction);
+ frontend::Update update;
+ update.transactions.emplace_back(transaction);
VSYNC_ID_SECOND_LAYER_CHANGE = ++mVsyncId;
- mTracing.addCommittedTransactions(transactions, VSYNC_ID_SECOND_LAYER_CHANGE);
- flush(VSYNC_ID_SECOND_LAYER_CHANGE);
+ mTracing.addCommittedTransactions(VSYNC_ID_SECOND_LAYER_CHANGE, 0, update, {}, false);
+ flush();
}
// remove child layer
@@ -161,8 +190,8 @@ protected:
queueAndCommitTransaction(++mVsyncId);
}
- int mParentLayerId = 1;
- int mChildLayerId = 2;
+ uint32_t mParentLayerId = 1;
+ uint32_t mChildLayerId = 2;
int64_t mVsyncId = 0;
int64_t VSYNC_ID_FIRST_LAYER_CHANGE;
int64_t VSYNC_ID_SECOND_LAYER_CHANGE;
@@ -174,7 +203,7 @@ TEST_F(TransactionTracingLayerHandlingTest, addStartingState) {
while (bufferFront().vsync_id() <= VSYNC_ID_FIRST_LAYER_CHANGE) {
queueAndCommitTransaction(++mVsyncId);
}
- proto::TransactionTraceFile proto = writeToProto();
+ perfetto::protos::TransactionTraceFile proto = writeToProto();
// verify we can still retrieve the layer change from the first entry containing starting
// states.
EXPECT_GT(proto.entry().size(), 0);
@@ -185,6 +214,7 @@ TEST_F(TransactionTracingLayerHandlingTest, addStartingState) {
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 42);
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).layer_id(), mChildLayerId);
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
+ EXPECT_TRUE(proto.entry(0).displays_changed());
}
TEST_F(TransactionTracingLayerHandlingTest, updateStartingState) {
@@ -192,9 +222,10 @@ TEST_F(TransactionTracingLayerHandlingTest, updateStartingState) {
while (bufferFront().vsync_id() <= VSYNC_ID_SECOND_LAYER_CHANGE) {
queueAndCommitTransaction(++mVsyncId);
}
- proto::TransactionTraceFile proto = writeToProto();
+ perfetto::protos::TransactionTraceFile proto = writeToProto();
// verify starting states are updated correctly
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).z(), 41);
+ EXPECT_TRUE(proto.entry(0).displays_changed());
}
TEST_F(TransactionTracingLayerHandlingTest, removeStartingState) {
@@ -202,10 +233,11 @@ TEST_F(TransactionTracingLayerHandlingTest, removeStartingState) {
while (bufferFront().vsync_id() <= VSYNC_ID_CHILD_LAYER_REMOVED) {
queueAndCommitTransaction(++mVsyncId);
}
- proto::TransactionTraceFile proto = writeToProto();
+ perfetto::protos::TransactionTraceFile proto = writeToProto();
// verify the child layer has been removed from the trace
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+ EXPECT_TRUE(proto.entry(0).displays_changed());
}
TEST_F(TransactionTracingLayerHandlingTest, startingStateSurvivesBufferFlush) {
@@ -213,7 +245,7 @@ TEST_F(TransactionTracingLayerHandlingTest, startingStateSurvivesBufferFlush) {
while (bufferFront().vsync_id() <= VSYNC_ID_SECOND_LAYER_CHANGE) {
queueAndCommitTransaction(++mVsyncId);
}
- proto::TransactionTraceFile proto = writeToProto();
+ perfetto::protos::TransactionTraceFile proto = writeToProto();
// verify we have two starting states
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
@@ -225,45 +257,48 @@ TEST_F(TransactionTracingLayerHandlingTest, startingStateSurvivesBufferFlush) {
// verify we still have the parent layer state
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 1);
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(0).layer_id(), mParentLayerId);
+ EXPECT_TRUE(proto.entry(0).displays_changed());
}
class TransactionTracingMirrorLayerTest : public TransactionTracingTest {
protected:
void SetUp() override {
- // add layers
mTracing.setBufferSize(SMALL_BUFFER_SIZE);
- const sp<IBinder> fakeLayerHandle = new BBinder();
- mTracing.onLayerAdded(fakeLayerHandle->localBinder(), mLayerId, "Test Layer",
- 123 /* flags */, -1 /* parentId */);
- const sp<IBinder> fakeMirrorLayerHandle = new BBinder();
- mTracing.onMirrorLayerAdded(fakeMirrorLayerHandle->localBinder(), mMirrorLayerId, "Mirror",
- mLayerId);
-
- // add some layer transaction
+
+ // add layers and some layer transaction
{
+ frontend::Update update;
+ update.layerCreationArgs.emplace_back(
+ getLayerCreationArgs(mLayerId, /*parentId=*/UNASSIGNED_LAYER_ID,
+ /*layerIdToMirror=*/UNASSIGNED_LAYER_ID, /*flags=*/123,
+ /*addToRoot=*/true));
+ update.layerCreationArgs.emplace_back(
+ getLayerCreationArgs(mMirrorLayerId, UNASSIGNED_LAYER_ID,
+ /*layerIdToMirror=*/mLayerId, /*flags=*/0,
+ /*addToRoot=*/false));
+
TransactionState transaction;
transaction.id = 50;
- ComposerState layerState;
- layerState.state.surface = fakeLayerHandle;
+ ResolvedComposerState layerState;
+ layerState.layerId = mLayerId;
layerState.state.what = layer_state_t::eLayerChanged;
layerState.state.z = 42;
- transaction.states.add(layerState);
- ComposerState mirrorState;
- mirrorState.state.surface = fakeMirrorLayerHandle;
+ transaction.states.emplace_back(layerState);
+ ResolvedComposerState mirrorState;
+ mirrorState.layerId = mMirrorLayerId;
mirrorState.state.what = layer_state_t::eLayerChanged;
mirrorState.state.z = 43;
- transaction.states.add(mirrorState);
+ transaction.states.emplace_back(mirrorState);
mTracing.addQueuedTransaction(transaction);
- std::vector<TransactionState> transactions;
- transactions.emplace_back(transaction);
- mTracing.addCommittedTransactions(transactions, ++mVsyncId);
- flush(mVsyncId);
+ update.transactions.emplace_back(transaction);
+ mTracing.addCommittedTransactions(mVsyncId, 0, update, {}, false);
+ flush();
}
}
- int mLayerId = 5;
- int mMirrorLayerId = 55;
+ uint32_t mLayerId = 5;
+ uint32_t mMirrorLayerId = 55;
int64_t mVsyncId = 0;
int64_t VSYNC_ID_FIRST_LAYER_CHANGE;
int64_t VSYNC_ID_SECOND_LAYER_CHANGE;
@@ -271,7 +306,7 @@ protected:
};
TEST_F(TransactionTracingMirrorLayerTest, canAddMirrorLayers) {
- proto::TransactionTraceFile proto = writeToProto();
+ perfetto::protos::TransactionTraceFile proto = writeToProto();
// We don't have any starting states since no layer was removed from.
EXPECT_EQ(proto.entry().size(), 1);
@@ -282,4 +317,24 @@ TEST_F(TransactionTracingMirrorLayerTest, canAddMirrorLayers) {
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes().size(), 2);
EXPECT_EQ(proto.entry(0).transactions(0).layer_changes(1).z(), 43);
}
+
+// Verify we can write the layers traces by entry to reduce mem pressure
+// on the system when generating large traces.
+TEST(LayerTraceTest, canStreamLayersTrace) {
+ perfetto::protos::LayersTraceFileProto inProto = LayerTracing::createTraceFileProto();
+ inProto.add_entry();
+ inProto.add_entry();
+
+ std::string output;
+ inProto.SerializeToString(&output);
+ perfetto::protos::LayersTraceFileProto inProto2 = LayerTracing::createTraceFileProto();
+ inProto2.add_entry();
+ std::string output2;
+ inProto2.SerializeToString(&output2);
+
+ perfetto::protos::LayersTraceFileProto outProto;
+ outProto.ParseFromString(output + output2);
+ // magic?
+ EXPECT_EQ(outProto.entry().size(), 3);
+}
} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
index 15fea9c6b2..1cf14ae519 100644
--- a/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
+++ b/services/surfaceflinger/tests/unittests/TunnelModeEnabledReporterTest.cpp
@@ -22,11 +22,9 @@
#include <gtest/gtest.h>
#include <gui/LayerMetadata.h>
-#include "BufferStateLayer.h"
#include "TestableSurfaceFlinger.h"
#include "TunnelModeEnabledReporter.h"
#include "mock/DisplayHardware/MockComposer.h"
-#include "mock/MockEventThread.h"
namespace android {
@@ -37,8 +35,6 @@ using testing::Return;
using android::Hwc2::IComposer;
using android::Hwc2::IComposerClient;
-using FakeHwcDisplayInjector = TestableSurfaceFlinger::FakeHwcDisplayInjector;
-
constexpr int DEFAULT_SIDEBAND_STREAM = 51;
struct TestableTunnelModeEnabledListener : public gui::BnTunnelModeEnabledListener {
@@ -62,9 +58,7 @@ protected:
static constexpr uint32_t HEIGHT = 100;
static constexpr uint32_t LAYER_FLAGS = 0;
- void setupScheduler();
- void setupComposer(uint32_t virtualDisplayCount);
- sp<BufferStateLayer> createBufferStateLayer(LayerMetadata metadata);
+ sp<Layer> createBufferStateLayer(LayerMetadata metadata);
TestableSurfaceFlinger mFlinger;
Hwc2::mock::Composer* mComposer = nullptr;
@@ -81,7 +75,7 @@ TunnelModeEnabledReporterTest::TunnelModeEnabledReporterTest() {
::testing::UnitTest::GetInstance()->current_test_info();
ALOGD("**** Setting up for %s.%s\n", test_info->test_case_name(), test_info->name());
- setupScheduler();
+ mFlinger.setupMockScheduler();
mFlinger.setupComposer(std::make_unique<Hwc2::mock::Composer>());
mFlinger.flinger()->mTunnelModeEnabledReporter = mTunnelModeEnabledReporter;
mTunnelModeEnabledReporter->dispatchTunnelModeEnabled(false);
@@ -95,36 +89,10 @@ TunnelModeEnabledReporterTest::~TunnelModeEnabledReporterTest() {
mTunnelModeEnabledReporter->removeListener(mTunnelModeEnabledListener);
}
-sp<BufferStateLayer> TunnelModeEnabledReporterTest::createBufferStateLayer(
- LayerMetadata metadata = {}) {
+sp<Layer> TunnelModeEnabledReporterTest::createBufferStateLayer(LayerMetadata metadata = {}) {
sp<Client> client;
LayerCreationArgs args(mFlinger.flinger(), client, "buffer-state-layer", LAYER_FLAGS, metadata);
- return new BufferStateLayer(args);
-}
-
-void TunnelModeEnabledReporterTest::setupScheduler() {
- auto eventThread = std::make_unique<mock::EventThread>();
- auto sfEventThread = std::make_unique<mock::EventThread>();
-
- EXPECT_CALL(*eventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*eventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(eventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- EXPECT_CALL(*sfEventThread, registerDisplayEventConnection(_));
- EXPECT_CALL(*sfEventThread, createEventConnection(_, _))
- .WillOnce(Return(new EventThreadConnection(sfEventThread.get(), /*callingUid=*/0,
- ResyncCallback())));
-
- auto vsyncController = std::make_unique<mock::VsyncController>();
- auto vsyncTracker = std::make_unique<mock::VSyncTracker>();
-
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- EXPECT_CALL(*vsyncTracker, currentPeriod())
- .WillRepeatedly(Return(FakeHwcDisplayInjector::DEFAULT_VSYNC_PERIOD));
- EXPECT_CALL(*vsyncTracker, nextAnticipatedVSyncTimeFrom(_)).WillRepeatedly(Return(0));
- mFlinger.setupScheduler(std::move(vsyncController), std::move(vsyncTracker),
- std::move(eventThread), std::move(sfEventThread));
+ return sp<Layer>::make(args);
}
namespace {
@@ -159,7 +127,7 @@ TEST_F(TunnelModeEnabledReporterTest, callsNewListenerWithFreshInformation) {
sp<NativeHandle> stream =
NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
false);
- layer->setSidebandStream(stream);
+ layer->setSidebandStream(stream, FrameTimelineInfo{}, 20);
mFlinger.mutableCurrentState().layersSortedByZ.add(layer);
mTunnelModeEnabledReporter->updateTunnelModeStatus();
mTunnelModeEnabledReporter->addListener(mTunnelModeEnabledListener);
@@ -183,7 +151,7 @@ TEST_F(TunnelModeEnabledReporterTest, layerWithSidebandStreamTriggersUpdate) {
sp<NativeHandle> stream =
NativeHandle::create(reinterpret_cast<native_handle_t*>(DEFAULT_SIDEBAND_STREAM),
false);
- layerWithSidebandStream->setSidebandStream(stream);
+ layerWithSidebandStream->setSidebandStream(stream, FrameTimelineInfo{}, 20);
mFlinger.mutableCurrentState().layersSortedByZ.add(simpleLayer);
mFlinger.mutableCurrentState().layersSortedByZ.add(layerWithSidebandStream);
diff --git a/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp b/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp
new file mode 100644
index 0000000000..69b3861a69
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/VSyncCallbackRegistrationTest.cpp
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include "Scheduler/VSyncDispatch.h"
+#include "mock/MockVSyncDispatch.h"
+
+using namespace testing;
+
+namespace android::scheduler {
+
+class VSyncCallbackRegistrationTest : public Test {
+protected:
+ VSyncDispatch::Callback mCallback = [](nsecs_t, nsecs_t, nsecs_t) {};
+
+ std::shared_ptr<mock::VSyncDispatch> mVsyncDispatch = std::make_shared<mock::VSyncDispatch>();
+ VSyncDispatch::CallbackToken mCallbackToken{7};
+ std::string mCallbackName = "callback";
+
+ std::shared_ptr<mock::VSyncDispatch> mVsyncDispatch2 = std::make_shared<mock::VSyncDispatch>();
+ VSyncDispatch::CallbackToken mCallbackToken2{42};
+ std::string mCallbackName2 = "callback2";
+
+ void assertDispatch(const VSyncCallbackRegistration& registration,
+ std::shared_ptr<VSyncDispatch> dispatch) {
+ ASSERT_EQ(registration.mDispatch, dispatch);
+ }
+
+ void assertToken(const VSyncCallbackRegistration& registration,
+ const std::optional<VSyncDispatch::CallbackToken>& token) {
+ ASSERT_EQ(registration.mToken, token);
+ }
+};
+
+TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnDestruction) {
+ // TODO (b/279581095): With ftl::Function, `_` can be replaced with
+ // `mCallback`, here and in other calls to `registerCallback, since the
+ // ftl version has an operator==, unlike std::function.
+ EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+ .WillOnce(Return(mCallbackToken));
+ EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+
+ VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch));
+ ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnPointerMove) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+ .WillOnce(Return(mCallbackToken));
+ EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2))
+ .WillOnce(Return(mCallbackToken2));
+ EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1);
+ EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+ }
+
+ auto registration =
+ std::make_unique<VSyncCallbackRegistration>(mVsyncDispatch, mCallback, mCallbackName);
+
+ auto registration2 =
+ std::make_unique<VSyncCallbackRegistration>(mVsyncDispatch2, mCallback, mCallbackName2);
+
+ registration2 = std::move(registration);
+
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(*registration2.get(), mVsyncDispatch));
+ ASSERT_NO_FATAL_FAILURE(assertToken(*registration2.get(), mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, unregistersCallbackOnMoveOperator) {
+ {
+ InSequence seq;
+ EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+ .WillOnce(Return(mCallbackToken));
+ EXPECT_CALL(*mVsyncDispatch2, registerCallback(_, mCallbackName2))
+ .WillOnce(Return(mCallbackToken2));
+ EXPECT_CALL(*mVsyncDispatch2, unregisterCallback(mCallbackToken2)).Times(1);
+ EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+ }
+
+ VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+
+ VSyncCallbackRegistration registration2(mVsyncDispatch2, mCallback, mCallbackName2);
+
+ registration2 = std::move(registration);
+
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr));
+ ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt));
+
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch));
+ ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, moveConstructor) {
+ EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+ .WillOnce(Return(mCallbackToken));
+ EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+
+ VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+ VSyncCallbackRegistration registration2(std::move(registration));
+
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, nullptr));
+ ASSERT_NO_FATAL_FAILURE(assertToken(registration, std::nullopt));
+
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(registration2, mVsyncDispatch));
+ ASSERT_NO_FATAL_FAILURE(assertToken(registration2, mCallbackToken));
+}
+
+TEST_F(VSyncCallbackRegistrationTest, moveOperatorEqualsSelf) {
+ EXPECT_CALL(*mVsyncDispatch, registerCallback(_, mCallbackName))
+ .WillOnce(Return(mCallbackToken));
+ EXPECT_CALL(*mVsyncDispatch, unregisterCallback(mCallbackToken)).Times(1);
+
+ VSyncCallbackRegistration registration(mVsyncDispatch, mCallback, mCallbackName);
+
+ // Use a reference so the compiler doesn't realize that registration is
+ // being moved to itself.
+ VSyncCallbackRegistration& registrationRef = registration;
+ registration = std::move(registrationRef);
+
+ ASSERT_NO_FATAL_FAILURE(assertDispatch(registration, mVsyncDispatch));
+ ASSERT_NO_FATAL_FAILURE(assertToken(registration, mCallbackToken));
+}
+
+} // namespace android::scheduler
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
index 2da266bd33..6a5635305a 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchRealtimeTest.cpp
@@ -34,37 +34,48 @@ constexpr nsecs_t toNs(std::chrono::duration<Rep, Per> const& tp) {
return std::chrono::duration_cast<std::chrono::nanoseconds>(tp).count();
}
-class FixedRateIdealStubTracker : public VSyncTracker {
+class StubTracker : public VSyncTracker {
public:
- FixedRateIdealStubTracker() : mPeriod{toNs(3ms)} {}
+ StubTracker(nsecs_t period) : mPeriod(period) {}
bool addVsyncTimestamp(nsecs_t) final { return true; }
- nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
- auto const floor = timePoint % mPeriod;
- if (floor == 0) {
- return timePoint;
- }
- return timePoint - floor + mPeriod;
+ nsecs_t currentPeriod() const final {
+ std::lock_guard lock(mMutex);
+ return mPeriod;
}
- nsecs_t currentPeriod() const final { return mPeriod; }
-
- void setPeriod(nsecs_t) final {}
+ Period minFramePeriod() const final { return Period::fromNs(currentPeriod()); }
void resetModel() final {}
bool needsMoreSamples() const final { return false; }
bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
+ void setDisplayModePtr(ftl::NonNull<DisplayModePtr>) final {}
+ void setRenderRate(Fps) final {}
+ void onFrameBegin(TimePoint, TimePoint) final {}
+ void onFrameMissed(TimePoint) final {}
void dump(std::string&) const final {}
-private:
- nsecs_t const mPeriod;
+protected:
+ std::mutex mutable mMutex;
+ nsecs_t mPeriod;
};
-class VRRStubTracker : public VSyncTracker {
+class FixedRateIdealStubTracker : public StubTracker {
public:
- VRRStubTracker(nsecs_t period) : mPeriod{period} {}
+ FixedRateIdealStubTracker() : StubTracker{toNs(3ms)} {}
- bool addVsyncTimestamp(nsecs_t) final { return true; }
+ nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t timePoint) const final {
+ auto const floor = timePoint % mPeriod;
+ if (floor == 0) {
+ return timePoint;
+ }
+ return timePoint - floor + mPeriod;
+ }
+};
+
+class VRRStubTracker : public StubTracker {
+public:
+ VRRStubTracker(nsecs_t period) : StubTracker(period) {}
nsecs_t nextAnticipatedVSyncTimeFrom(nsecs_t time_point) const final {
std::lock_guard lock(mMutex);
@@ -82,20 +93,7 @@ public:
mBase = last_known;
}
- nsecs_t currentPeriod() const final {
- std::lock_guard lock(mMutex);
- return mPeriod;
- }
-
- void setPeriod(nsecs_t) final {}
- void resetModel() final {}
- bool needsMoreSamples() const final { return false; }
- bool isVSyncInPhase(nsecs_t, Fps) const final { return false; }
- void dump(std::string&) const final {}
-
private:
- std::mutex mutable mMutex;
- nsecs_t mPeriod;
nsecs_t mBase = 0;
};
@@ -107,7 +105,8 @@ struct VSyncDispatchRealtimeTest : testing::Test {
class RepeatingCallbackReceiver {
public:
- RepeatingCallbackReceiver(VSyncDispatch& dispatch, nsecs_t workload, nsecs_t readyDuration)
+ RepeatingCallbackReceiver(std::shared_ptr<VSyncDispatch> dispatch, nsecs_t workload,
+ nsecs_t readyDuration)
: mWorkload(workload),
mReadyDuration(readyDuration),
mCallback(
@@ -164,9 +163,10 @@ private:
};
TEST_F(VSyncDispatchRealtimeTest, triple_alarm) {
- FixedRateIdealStubTracker tracker;
- VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold,
- mVsyncMoveThreshold);
+ auto tracker = std::make_shared<FixedRateIdealStubTracker>();
+ auto dispatch =
+ std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+ mDispatchGroupThreshold, mVsyncMoveThreshold);
static size_t constexpr num_clients = 3;
std::array<RepeatingCallbackReceiver, num_clients>
@@ -193,14 +193,15 @@ TEST_F(VSyncDispatchRealtimeTest, triple_alarm) {
// starts at 333hz, slides down to 43hz
TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) {
auto next_vsync_interval = toNs(3ms);
- VRRStubTracker tracker(next_vsync_interval);
- VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold,
- mVsyncMoveThreshold);
+ auto tracker = std::make_shared<VRRStubTracker>(next_vsync_interval);
+ auto dispatch =
+ std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+ mDispatchGroupThreshold, mVsyncMoveThreshold);
RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms));
auto const on_each_frame = [&](nsecs_t last_known) {
- tracker.set_interval(next_vsync_interval += toNs(1ms), last_known);
+ tracker->set_interval(next_vsync_interval += toNs(1ms), last_known);
};
std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); });
@@ -211,9 +212,10 @@ TEST_F(VSyncDispatchRealtimeTest, vascillating_vrr) {
// starts at 333hz, jumps to 200hz at frame 10
TEST_F(VSyncDispatchRealtimeTest, fixed_jump) {
- VRRStubTracker tracker(toNs(3ms));
- VSyncDispatchTimerQueue dispatch(std::make_unique<Timer>(), tracker, mDispatchGroupThreshold,
- mVsyncMoveThreshold);
+ auto tracker = std::make_shared<VRRStubTracker>(toNs(3ms));
+ auto dispatch =
+ std::make_shared<VSyncDispatchTimerQueue>(std::make_unique<Timer>(), tracker,
+ mDispatchGroupThreshold, mVsyncMoveThreshold);
RepeatingCallbackReceiver cb_receiver(dispatch, toNs(1ms), toNs(5ms));
@@ -221,7 +223,7 @@ TEST_F(VSyncDispatchRealtimeTest, fixed_jump) {
auto constexpr jump_frame_at = 10u;
auto const on_each_frame = [&](nsecs_t last_known) {
if (jump_frame_counter++ == jump_frame_at) {
- tracker.set_interval(toNs(5ms), last_known);
+ tracker->set_interval(toNs(5ms), last_known);
}
};
std::thread eventThread([&] { cb_receiver.repeatedly_schedule(mIterations, on_each_frame); });
diff --git a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
index b7f968dc17..2047018a15 100644
--- a/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncDispatchTimerQueueTest.cpp
@@ -30,31 +30,29 @@
#include <scheduler/TimeKeeper.h>
+#include <common/test/FlagUtils.h>
#include "Scheduler/VSyncDispatchTimerQueue.h"
#include "Scheduler/VSyncTracker.h"
+#include "mock/MockVSyncTracker.h"
+
+#include <com_android_graphics_surfaceflinger_flags.h>
using namespace testing;
using namespace std::literals;
namespace android::scheduler {
+using namespace com::android::graphics::surfaceflinger;
-class MockVSyncTracker : public VSyncTracker {
+class MockVSyncTracker : public mock::VSyncTracker {
public:
MockVSyncTracker(nsecs_t period) : mPeriod{period} {
ON_CALL(*this, nextAnticipatedVSyncTimeFrom(_))
.WillByDefault(Invoke(this, &MockVSyncTracker::nextVSyncTime));
ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true));
+ ON_CALL(*this, currentPeriod())
+ .WillByDefault(Invoke(this, &MockVSyncTracker::getCurrentPeriod));
}
- MOCK_METHOD1(addVsyncTimestamp, bool(nsecs_t));
- MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
- MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
- MOCK_METHOD1(setPeriod, void(nsecs_t));
- MOCK_METHOD0(resetModel, void());
- MOCK_CONST_METHOD0(needsMoreSamples, bool());
- MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
- MOCK_CONST_METHOD1(dump, void(std::string&));
-
nsecs_t nextVSyncTime(nsecs_t timePoint) const {
if (timePoint % mPeriod == 0) {
return timePoint;
@@ -62,6 +60,8 @@ public:
return (timePoint - (timePoint % mPeriod) + mPeriod);
}
+ nsecs_t getCurrentPeriod() const { return mPeriod; }
+
protected:
nsecs_t const mPeriod;
};
@@ -111,13 +111,14 @@ private:
class CountingCallback {
public:
- CountingCallback(VSyncDispatch& dispatch)
- : mDispatch(dispatch),
- mToken(dispatch.registerCallback(std::bind(&CountingCallback::counter, this,
- std::placeholders::_1, std::placeholders::_2,
- std::placeholders::_3),
- "test")) {}
- ~CountingCallback() { mDispatch.unregisterCallback(mToken); }
+ CountingCallback(std::shared_ptr<VSyncDispatch> dispatch)
+ : mDispatch(std::move(dispatch)),
+ mToken(mDispatch->registerCallback(std::bind(&CountingCallback::counter, this,
+ std::placeholders::_1,
+ std::placeholders::_2,
+ std::placeholders::_3),
+ "test")) {}
+ ~CountingCallback() { mDispatch->unregisterCallback(mToken); }
operator VSyncDispatch::CallbackToken() const { return mToken; }
@@ -127,7 +128,7 @@ public:
mReadyTime.push_back(readyTime);
}
- VSyncDispatch& mDispatch;
+ std::shared_ptr<VSyncDispatch> mDispatch;
VSyncDispatch::CallbackToken mToken;
std::vector<nsecs_t> mCalls;
std::vector<nsecs_t> mWakeupTime;
@@ -136,12 +137,12 @@ public:
class PausingCallback {
public:
- PausingCallback(VSyncDispatch& dispatch, std::chrono::milliseconds pauseAmount)
- : mDispatch(dispatch),
- mToken(dispatch.registerCallback(std::bind(&PausingCallback::pause, this,
- std::placeholders::_1,
- std::placeholders::_2),
- "test")),
+ PausingCallback(std::shared_ptr<VSyncDispatch> dispatch, std::chrono::milliseconds pauseAmount)
+ : mDispatch(std::move(dispatch)),
+ mToken(mDispatch->registerCallback(std::bind(&PausingCallback::pause, this,
+ std::placeholders::_1,
+ std::placeholders::_2),
+ "test")),
mRegistered(true),
mPauseAmount(pauseAmount) {}
~PausingCallback() { unregister(); }
@@ -176,12 +177,12 @@ public:
void unregister() {
if (mRegistered) {
- mDispatch.unregisterCallback(mToken);
+ mDispatch->unregisterCallback(mToken);
mRegistered = false;
}
}
- VSyncDispatch& mDispatch;
+ std::shared_ptr<VSyncDispatch> mDispatch;
VSyncDispatch::CallbackToken mToken;
bool mRegistered = true;
@@ -226,22 +227,26 @@ protected:
static nsecs_t constexpr mDispatchGroupThreshold = 5;
nsecs_t const mPeriod = 1000;
nsecs_t const mVsyncMoveThreshold = 300;
- NiceMock<MockVSyncTracker> mStubTracker{mPeriod};
- VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold,
- mVsyncMoveThreshold};
+ std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker =
+ std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod);
+ std::shared_ptr<VSyncDispatch> mDispatch =
+ std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker,
+ mDispatchGroupThreshold, mVsyncMoveThreshold);
};
TEST_F(VSyncDispatchTimerQueueTest, unregistersSetAlarmOnDestruction) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
EXPECT_CALL(mMockClock, alarmCancel());
{
- VSyncDispatchTimerQueue mDispatch{createTimeKeeper(), mStubTracker, mDispatchGroupThreshold,
- mVsyncMoveThreshold};
+ std::shared_ptr<VSyncDispatch> mDispatch =
+ std::make_shared<VSyncDispatchTimerQueue>(createTimeKeeper(), mStubTracker,
+ mDispatchGroupThreshold,
+ mVsyncMoveThreshold);
CountingCallback cb(mDispatch);
- const auto result = mDispatch.schedule(cb,
- {.workDuration = 100,
- .readyDuration = 0,
- .earliestVsync = 1000});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, *result);
}
@@ -252,10 +257,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFuture) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
CountingCallback cb(mDispatch);
- const auto result = mDispatch.schedule(cb,
- {.workDuration = 100,
- .readyDuration = 0,
- .earliestVsync = intended});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = intended});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, *result);
@@ -265,12 +270,50 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFuture) {
EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
}
+TEST_F(VSyncDispatchTimerQueueTest, updateAlarmSettingFuture) {
+ auto intended = mPeriod - 230;
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 700)).InSequence(seq);
+
+ CountingCallback cb(mDispatch);
+ auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = intended});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(900, *result);
+
+ result =
+ mDispatch->update(cb,
+ {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(700, *result);
+
+ advanceToNextCallback();
+
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+ EXPECT_THAT(cb.mCalls[0], Eq(mPeriod));
+ EXPECT_THAT(cb.mWakeupTime[0], Eq(700));
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, updateDoesntSchedule) {
+ auto intended = mPeriod - 230;
+ EXPECT_CALL(mMockClock, alarmAt(_, _)).Times(0);
+
+ CountingCallback cb(mDispatch);
+ const auto result =
+ mDispatch->update(cb,
+ {.workDuration = 300, .readyDuration = 0, .earliestVsync = intended});
+ EXPECT_FALSE(result.has_value());
+}
+
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithAdjustmentToTrueVsync) {
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000)).WillOnce(Return(1150));
EXPECT_CALL(mMockClock, alarmAt(_, 1050));
CountingCallback cb(mDispatch);
- mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
@@ -281,15 +324,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingAdjustmentPast) {
auto const now = 234;
mMockClock.advanceBy(234);
auto const workDuration = 10 * mPeriod;
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + workDuration))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + workDuration))
.WillOnce(Return(mPeriod * 11));
EXPECT_CALL(mMockClock, alarmAt(_, mPeriod));
CountingCallback cb(mDispatch);
- const auto result = mDispatch.schedule(cb,
- {.workDuration = workDuration,
- .readyDuration = 0,
- .earliestVsync = mPeriod});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = workDuration,
+ .readyDuration = 0,
+ .earliestVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod, *result);
}
@@ -299,12 +342,13 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancel) {
EXPECT_CALL(mMockClock, alarmCancel());
CountingCallback cb(mDispatch);
- const auto result =
- mDispatch.schedule(cb,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, *result);
- EXPECT_EQ(mDispatch.cancel(cb), CancelResult::Cancelled);
+ EXPECT_EQ(mDispatch->cancel(cb), CancelResult::Cancelled);
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) {
@@ -312,13 +356,14 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLate) {
EXPECT_CALL(mMockClock, alarmCancel());
CountingCallback cb(mDispatch);
- const auto result =
- mDispatch.schedule(cb,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, *result);
mMockClock.advanceBy(950);
- EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate);
+ EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
}
TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) {
@@ -326,15 +371,16 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmCancelTooLateWhenRunning) {
EXPECT_CALL(mMockClock, alarmCancel());
PausingCallback cb(mDispatch, std::chrono::duration_cast<std::chrono::milliseconds>(1s));
- const auto result =
- mDispatch.schedule(cb,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, *result);
std::thread pausingThread([&] { mMockClock.advanceToNextCallback(); });
EXPECT_TRUE(cb.waitForPause());
- EXPECT_EQ(mDispatch.cancel(cb), CancelResult::TooLate);
+ EXPECT_EQ(mDispatch->cancel(cb), CancelResult::TooLate);
cb.unpause();
pausingThread.join();
}
@@ -347,9 +393,10 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) {
PausingCallback cb(mDispatch, 50ms);
cb.stashResource(resource);
- const auto result =
- mDispatch.schedule(cb,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = mPeriod});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod - 100, *result);
@@ -366,7 +413,7 @@ TEST_F(VSyncDispatchTimerQueueTest, unregisterSynchronizes) {
}
TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
.Times(4)
.WillOnce(Return(1055))
.WillOnce(Return(1063))
@@ -381,8 +428,8 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
- mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+ mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod});
+ mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
advanceToNextCallback();
advanceToNextCallback();
@@ -393,8 +440,45 @@ TEST_F(VSyncDispatchTimerQueueTest, basicTwoAlarmSetting) {
EXPECT_THAT(cb1.mCalls[0], Eq(1063));
}
+TEST_F(VSyncDispatchTimerQueueTest, noCloseCallbacksAfterPeriodChange) {
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
+ .Times(4)
+ .WillOnce(Return(1000))
+ .WillOnce(Return(2000))
+ .WillOnce(Return(2500))
+ .WillOnce(Return(4000));
+
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 3900)).InSequence(seq);
+
+ CountingCallback cb(mDispatch);
+
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 0});
+
+ advanceToNextCallback();
+
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+ EXPECT_THAT(cb.mCalls[0], Eq(1000));
+
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+
+ advanceToNextCallback();
+
+ ASSERT_THAT(cb.mCalls.size(), Eq(2));
+ EXPECT_THAT(cb.mCalls[1], Eq(2000));
+
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+
+ advanceToNextCallback();
+
+ ASSERT_THAT(cb.mCalls.size(), Eq(3));
+ EXPECT_THAT(cb.mCalls[2], Eq(4000));
+}
+
TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
.Times(4)
.WillOnce(Return(10000))
.WillOnce(Return(1000))
@@ -409,10 +493,10 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsFaroutTimeoutWhenCancellingCloseOne) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
- mDispatch.schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
- mDispatch.cancel(cb1);
+ mDispatch->schedule(cb0,
+ {.workDuration = 100, .readyDuration = 0, .earliestVsync = mPeriod * 10});
+ mDispatch->schedule(cb1, {.workDuration = 250, .readyDuration = 0, .earliestVsync = mPeriod});
+ mDispatch->cancel(cb1);
}
TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) {
@@ -423,9 +507,9 @@ TEST_F(VSyncDispatchTimerQueueTest, noUnnecessaryRearmsWhenRescheduling) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 300, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
}
@@ -438,9 +522,9 @@ TEST_F(VSyncDispatchTimerQueueTest, necessaryRearmsWhenModifying) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
}
@@ -458,10 +542,10 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1,
- {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1,
+ {.workDuration = closeOffset, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
ASSERT_THAT(cb0.mCalls.size(), Eq(1));
@@ -469,9 +553,11 @@ TEST_F(VSyncDispatchTimerQueueTest, modifyIntoGroup) {
ASSERT_THAT(cb1.mCalls.size(), Eq(1));
EXPECT_THAT(cb1.mCalls[0], Eq(mPeriod));
- mDispatch.schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
- mDispatch.schedule(cb1,
- {.workDuration = notCloseOffset, .readyDuration = 0, .earliestVsync = 2000});
+ mDispatch->schedule(cb0, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 2000});
+ mDispatch->schedule(cb1,
+ {.workDuration = notCloseOffset,
+ .readyDuration = 0,
+ .earliestVsync = 2000});
advanceToNextCallback();
ASSERT_THAT(cb1.mCalls.size(), Eq(2));
EXPECT_THAT(cb1.mCalls[1], Eq(2000));
@@ -491,32 +577,32 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenEndingAndDoesntCancel) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
- EXPECT_EQ(mDispatch.cancel(cb0), CancelResult::Cancelled);
+ EXPECT_EQ(mDispatch->cancel(cb0), CancelResult::Cancelled);
}
TEST_F(VSyncDispatchTimerQueueTest, setAlarmCallsAtCorrectTimeWithChangingVsync) {
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
.Times(3)
.WillOnce(Return(950))
.WillOnce(Return(1975))
.WillOnce(Return(2950));
CountingCallback cb(mDispatch);
- mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 920});
mMockClock.advanceBy(850);
EXPECT_THAT(cb.mCalls.size(), Eq(1));
- mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1900});
mMockClock.advanceBy(900);
EXPECT_THAT(cb.mCalls.size(), Eq(1));
mMockClock.advanceBy(125);
EXPECT_THAT(cb.mCalls.size(), Eq(2));
- mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2900});
mMockClock.advanceBy(975);
EXPECT_THAT(cb.mCalls.size(), Eq(3));
}
@@ -527,48 +613,48 @@ TEST_F(VSyncDispatchTimerQueueTest, callbackReentrancy) {
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
VSyncDispatch::CallbackToken tmp;
- tmp = mDispatch.registerCallback(
+ tmp = mDispatch->registerCallback(
[&](auto, auto, auto) {
- mDispatch.schedule(tmp,
- {.workDuration = 100,
- .readyDuration = 0,
- .earliestVsync = 2000});
+ mDispatch->schedule(tmp,
+ {.workDuration = 100,
+ .readyDuration = 0,
+ .earliestVsync = 2000});
},
"o.o");
- mDispatch.schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(tmp, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
}
TEST_F(VSyncDispatchTimerQueueTest, callbackReentrantWithPastWakeup) {
VSyncDispatch::CallbackToken tmp;
std::optional<nsecs_t> lastTarget;
- tmp = mDispatch.registerCallback(
+ tmp = mDispatch->registerCallback(
[&](auto timestamp, auto, auto) {
auto result =
- mDispatch.schedule(tmp,
- {.workDuration = 400,
- .readyDuration = 0,
- .earliestVsync = timestamp - mVsyncMoveThreshold});
- EXPECT_TRUE(result.has_value());
- EXPECT_EQ(mPeriod + timestamp - 400, *result);
- result = mDispatch.schedule(tmp,
+ mDispatch->schedule(tmp,
{.workDuration = 400,
.readyDuration = 0,
- .earliestVsync = timestamp});
+ .earliestVsync = timestamp - mVsyncMoveThreshold});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod + timestamp - 400, *result);
- result = mDispatch.schedule(tmp,
- {.workDuration = 400,
- .readyDuration = 0,
- .earliestVsync = timestamp + mVsyncMoveThreshold});
+ result = mDispatch->schedule(tmp,
+ {.workDuration = 400,
+ .readyDuration = 0,
+ .earliestVsync = timestamp});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(mPeriod + timestamp - 400, *result);
+ result = mDispatch->schedule(tmp,
+ {.workDuration = 400,
+ .readyDuration = 0,
+ .earliestVsync = timestamp + mVsyncMoveThreshold});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(mPeriod + timestamp - 400, *result);
lastTarget = timestamp;
},
"oo");
- mDispatch.schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(tmp, {.workDuration = 999, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
EXPECT_THAT(lastTarget, Eq(1000));
@@ -584,16 +670,16 @@ TEST_F(VSyncDispatchTimerQueueTest, modificationsAroundVsyncTime) {
EXPECT_CALL(mMockClock, alarmAt(_, 1900)).InSequence(seq);
CountingCallback cb(mDispatch);
- mDispatch.schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb, {.workDuration = 0, .readyDuration = 0, .earliestVsync = 1000});
mMockClock.advanceBy(750);
- mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
- mDispatch.schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
+ mDispatch->schedule(cb, {.workDuration = 50, .readyDuration = 0, .earliestVsync = 2000});
mMockClock.advanceBy(800);
- mDispatch.schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+ mDispatch->schedule(cb, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
}
TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
@@ -606,12 +692,12 @@ TEST_F(VSyncDispatchTimerQueueTest, lateModifications) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
- mDispatch.schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
- mDispatch.schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 200, .readyDuration = 0, .earliestVsync = 2000});
+ mDispatch->schedule(cb1, {.workDuration = 150, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
advanceToNextCallback();
@@ -623,8 +709,8 @@ TEST_F(VSyncDispatchTimerQueueTest, doesntCancelPriorValidTimerForFutureMod) {
CountingCallback cb0(mDispatch);
CountingCallback cb1(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
+ mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 20000});
}
TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
@@ -634,66 +720,94 @@ TEST_F(VSyncDispatchTimerQueueTest, setsTimerAfterCancellation) {
EXPECT_CALL(mMockClock, alarmAt(_, 900)).InSequence(seq);
CountingCallback cb0(mDispatch);
- mDispatch.schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.cancel(cb0);
- mDispatch.schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0, {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->cancel(cb0);
+ mDispatch->schedule(cb0, {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
}
TEST_F(VSyncDispatchTimerQueueTest, makingUpIdsError) {
VSyncDispatch::CallbackToken token(100);
- EXPECT_FALSE(mDispatch
- .schedule(token,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
- .has_value());
- EXPECT_THAT(mDispatch.cancel(token), Eq(CancelResult::Error));
+ EXPECT_FALSE(
+ mDispatch
+ ->schedule(token,
+ {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000})
+ .has_value());
+ EXPECT_THAT(mDispatch->cancel(token), Eq(CancelResult::Error));
}
TEST_F(VSyncDispatchTimerQueueTest, canMoveCallbackBackwardsInTime) {
CountingCallback cb0(mDispatch);
auto result =
- mDispatch.schedule(cb0,
- {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, *result);
- result = mDispatch.schedule(cb0,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
+ result = mDispatch->schedule(cb0,
+ {.workDuration = 100, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, *result);
}
// b/1450138150
TEST_F(VSyncDispatchTimerQueueTest, doesNotMoveCallbackBackwardsAndSkipAScheduledTargetVSync) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
EXPECT_CALL(mMockClock, alarmAt(_, 500));
CountingCallback cb(mDispatch);
auto result =
- mDispatch.schedule(cb,
- {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, *result);
mMockClock.advanceBy(400);
- result = mDispatch.schedule(cb,
- {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+ result = mDispatch->schedule(cb,
+ {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1200, *result);
+
+ advanceToNextCallback();
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+}
+
+// b/1450138150
+TEST_F(VSyncDispatchTimerQueueTest, movesCallbackBackwardsAndSkipAScheduledTargetVSync) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 400)).InSequence(seq);
+ CountingCallback cb(mDispatch);
+ auto result =
+ mDispatch->schedule(cb,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(500, *result);
+ mMockClock.advanceBy(400);
+
+ result = mDispatch->schedule(cb,
+ {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(400, *result);
+
advanceToNextCallback();
ASSERT_THAT(cb.mCalls.size(), Eq(1));
}
TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedule) {
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
.Times(2)
.WillOnce(Return(1000))
.WillOnce(Return(1002));
CountingCallback cb(mDispatch);
auto result =
- mDispatch.schedule(cb,
- {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, *result);
mMockClock.advanceBy(400);
- result = mDispatch.schedule(cb,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ result = mDispatch->schedule(cb,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(602, *result);
}
@@ -701,13 +815,13 @@ TEST_F(VSyncDispatchTimerQueueTest, targetOffsetMovingBackALittleCanStillSchedul
TEST_F(VSyncDispatchTimerQueueTest, canScheduleNegativeOffsetAgainstDifferentPeriods) {
CountingCallback cb0(mDispatch);
auto result =
- mDispatch.schedule(cb0,
- {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, *result);
advanceToNextCallback();
- result = mDispatch.schedule(cb0,
- {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
+ result = mDispatch->schedule(cb0,
+ {.workDuration = 1100, .readyDuration = 0, .earliestVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, *result);
}
@@ -718,32 +832,56 @@ TEST_F(VSyncDispatchTimerQueueTest, canScheduleLargeNegativeOffset) {
EXPECT_CALL(mMockClock, alarmAt(_, 1100)).InSequence(seq);
CountingCallback cb0(mDispatch);
auto result =
- mDispatch.schedule(cb0,
- {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb0,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(500, *result);
advanceToNextCallback();
- result = mDispatch.schedule(cb0,
- {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
+ result = mDispatch->schedule(cb0,
+ {.workDuration = 1900, .readyDuration = 0, .earliestVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1100, *result);
}
TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesNotAffectSchedulingState) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
EXPECT_CALL(mMockClock, alarmAt(_, 600));
CountingCallback cb(mDispatch);
auto result =
- mDispatch.schedule(cb,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
- result = mDispatch.schedule(cb,
- {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+ result = mDispatch->schedule(cb,
+ {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(600, *result);
+
+ advanceToNextCallback();
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, scheduleUpdatesDoesAffectSchedulingState) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 0)).InSequence(seq);
+
+ CountingCallback cb(mDispatch);
+ auto result =
+ mDispatch->schedule(cb,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
+ result = mDispatch->schedule(cb,
+ {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(0, *result);
+
advanceToNextCallback();
}
@@ -786,16 +924,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent
CountingCallback cb2(mDispatch);
auto result =
- mDispatch.schedule(cb1,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
mMockClock.setLag(100);
mMockClock.advanceBy(620);
- result = mDispatch.schedule(cb2,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+ result = mDispatch->schedule(cb2,
+ {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1900, *result);
mMockClock.advanceBy(80);
@@ -814,16 +952,16 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsSchedulingIfTimerReschedulingIsImminent
CountingCallback cb(mDispatch);
auto result =
- mDispatch.schedule(cb,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
mMockClock.setLag(100);
mMockClock.advanceBy(620);
- result = mDispatch.schedule(cb,
- {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
+ result = mDispatch->schedule(cb,
+ {.workDuration = 370, .readyDuration = 0, .earliestVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1630, *result);
mMockClock.advanceBy(80);
@@ -840,19 +978,19 @@ TEST_F(VSyncDispatchTimerQueueTest, skipsRearmingWhenNotNextScheduled) {
CountingCallback cb2(mDispatch);
auto result =
- mDispatch.schedule(cb1,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
- result = mDispatch.schedule(cb2,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+ result = mDispatch->schedule(cb2,
+ {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1900, *result);
mMockClock.setLag(100);
mMockClock.advanceBy(620);
- EXPECT_EQ(mDispatch.cancel(cb2), CancelResult::Cancelled);
+ EXPECT_EQ(mDispatch->cancel(cb2), CancelResult::Cancelled);
mMockClock.advanceBy(80);
@@ -869,19 +1007,19 @@ TEST_F(VSyncDispatchTimerQueueTest, rearmsWhenCancelledAndIsNextScheduled) {
CountingCallback cb2(mDispatch);
auto result =
- mDispatch.schedule(cb1,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
- result = mDispatch.schedule(cb2,
- {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
+ result = mDispatch->schedule(cb2,
+ {.workDuration = 100, .readyDuration = 0, .earliestVsync = 2000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(1900, *result);
mMockClock.setLag(100);
mMockClock.advanceBy(620);
- EXPECT_EQ(mDispatch.cancel(cb1), CancelResult::Cancelled);
+ EXPECT_EQ(mDispatch->cancel(cb1), CancelResult::Cancelled);
EXPECT_THAT(cb1.mCalls.size(), Eq(0));
EXPECT_THAT(cb2.mCalls.size(), Eq(0));
@@ -896,21 +1034,21 @@ TEST_F(VSyncDispatchTimerQueueTest, laggedTimerGroupsCallbacksWithinLag) {
CountingCallback cb2(mDispatch);
Sequence seq;
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
.InSequence(seq)
.WillOnce(Return(1000));
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000))
.InSequence(seq)
.WillOnce(Return(1000));
auto result =
- mDispatch.schedule(cb1,
- {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb1,
+ {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(600, *result);
- result = mDispatch.schedule(cb2,
- {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
+ result = mDispatch->schedule(cb2,
+ {.workDuration = 390, .readyDuration = 0, .earliestVsync = 1000});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(610, *result);
@@ -932,10 +1070,10 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) {
EXPECT_CALL(mMockClock, alarmAt(_, 900));
CountingCallback cb(mDispatch);
- const auto result = mDispatch.schedule(cb,
- {.workDuration = 70,
- .readyDuration = 30,
- .earliestVsync = intended});
+ const auto result = mDispatch->schedule(cb,
+ {.workDuration = 70,
+ .readyDuration = 30,
+ .earliestVsync = intended});
EXPECT_TRUE(result.has_value());
EXPECT_EQ(900, *result);
advanceToNextCallback();
@@ -949,13 +1087,15 @@ TEST_F(VSyncDispatchTimerQueueTest, basicAlarmSettingFutureWithReadyDuration) {
}
TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
Sequence seq;
EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
CountingCallback cb(mDispatch);
- mDispatch.schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
- mDispatch.schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
advanceToNextCallback();
@@ -969,11 +1109,85 @@ TEST_F(VSyncDispatchTimerQueueTest, updatesVsyncTimeForCloseWakeupTime) {
EXPECT_THAT(cb.mReadyTime[0], Eq(2000));
}
+TEST_F(VSyncDispatchTimerQueueTest, doesNotUpdatesVsyncTimeForCloseWakeupTime) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 600)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 0)).InSequence(seq);
+
+ CountingCallback cb(mDispatch);
+
+ mDispatch->schedule(cb, {.workDuration = 400, .readyDuration = 0, .earliestVsync = 1000});
+ mDispatch->schedule(cb, {.workDuration = 1400, .readyDuration = 0, .earliestVsync = 1000});
+
+ advanceToNextCallback();
+
+ advanceToNextCallback();
+
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+ EXPECT_THAT(cb.mCalls[0], Eq(1000));
+ ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
+ EXPECT_THAT(cb.mWakeupTime[0], Eq(0));
+ ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
+ EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, skipAVsyc) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, false);
+
+ EXPECT_CALL(mMockClock, alarmAt(_, 500));
+ CountingCallback cb(mDispatch);
+ auto result =
+ mDispatch->schedule(cb,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(500, *result);
+ mMockClock.advanceBy(300);
+
+ result = mDispatch->schedule(cb,
+ {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(1200, *result);
+
+ advanceToNextCallback();
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+}
+
+TEST_F(VSyncDispatchTimerQueueTest, dontskipAVsyc) {
+ SET_FLAG_FOR_TEST(flags::dont_skip_on_early, true);
+
+ Sequence seq;
+ EXPECT_CALL(mMockClock, alarmAt(_, 500)).InSequence(seq);
+ EXPECT_CALL(mMockClock, alarmAt(_, 300)).InSequence(seq);
+ CountingCallback cb(mDispatch);
+ auto result =
+ mDispatch->schedule(cb,
+ {.workDuration = 500, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(500, *result);
+ mMockClock.advanceBy(300);
+
+ result = mDispatch->schedule(cb,
+ {.workDuration = 800, .readyDuration = 0, .earliestVsync = 1000});
+ EXPECT_TRUE(result.has_value());
+ EXPECT_EQ(300, *result);
+
+ advanceToNextCallback();
+ ASSERT_THAT(cb.mCalls.size(), Eq(1));
+ EXPECT_THAT(cb.mCalls[0], Eq(1000));
+ ASSERT_THAT(cb.mWakeupTime.size(), Eq(1));
+ EXPECT_THAT(cb.mWakeupTime[0], Eq(300));
+ ASSERT_THAT(cb.mReadyTime.size(), Eq(1));
+ EXPECT_THAT(cb.mReadyTime[0], Eq(1000));
+}
+
class VSyncDispatchTimerQueueEntryTest : public testing::Test {
protected:
nsecs_t const mPeriod = 1000;
nsecs_t const mVsyncMoveThreshold = 200;
- NiceMock<MockVSyncTracker> mStubTracker{mPeriod};
+ std::shared_ptr<NiceMock<MockVSyncTracker>> mStubTracker =
+ std::make_shared<NiceMock<MockVSyncTracker>>(mPeriod);
};
TEST_F(VSyncDispatchTimerQueueEntryTest, stateAfterInitialization) {
@@ -991,7 +1205,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateScheduling) {
EXPECT_FALSE(entry.wakeupTime());
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
@@ -1005,7 +1219,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency)
auto const duration = 500;
auto const now = 8750;
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(now + duration))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(now + duration))
.Times(1)
.WillOnce(Return(10000));
VSyncDispatchTimerQueueEntry entry(
@@ -1013,7 +1227,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, stateSchedulingReallyLongWakeupLatency)
EXPECT_FALSE(entry.wakeupTime());
EXPECT_TRUE(entry.schedule({.workDuration = 500, .readyDuration = 0, .earliestVsync = 994},
- mStubTracker, now)
+ *mStubTracker.get(), now)
.has_value());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
@@ -1036,7 +1250,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) {
mVsyncMoveThreshold);
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
@@ -1058,7 +1272,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallback) {
}
TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(_))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(_))
.Times(2)
.WillOnce(Return(1000))
.WillOnce(Return(1020));
@@ -1067,17 +1281,17 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, updateCallback) {
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_FALSE(entry.wakeupTime());
- entry.update(mStubTracker, 0);
+ entry.update(*mStubTracker.get(), 0);
EXPECT_FALSE(entry.wakeupTime());
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
auto wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(wakeup, Eq(900));
- entry.update(mStubTracker, 0);
+ entry.update(*mStubTracker.get(), 0);
wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
EXPECT_THAT(*wakeup, Eq(920));
@@ -1087,9 +1301,9 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, skipsUpdateIfJustScheduled) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
- entry.update(mStubTracker, 0);
+ entry.update(*mStubTracker.get(), 0);
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
@@ -1100,24 +1314,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, willSnapToNextTargettableVSync) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
entry.executing(); // 1000 is executing
// had 1000 not been executing, this could have been scheduled for time 800.
EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
EXPECT_THAT(*entry.readyTime(), Eq(2000));
EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
EXPECT_THAT(*entry.wakeupTime(), Eq(1950));
EXPECT_THAT(*entry.readyTime(), Eq(2000));
EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 1001},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
EXPECT_THAT(*entry.wakeupTime(), Eq(1800));
EXPECT_THAT(*entry.readyTime(), Eq(2000));
@@ -1129,24 +1343,24 @@ TEST_F(VSyncDispatchTimerQueueEntryTest,
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
Sequence seq;
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
.InSequence(seq)
.WillOnce(Return(1000));
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(500))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(500))
.InSequence(seq)
.WillOnce(Return(1000));
- EXPECT_CALL(mStubTracker, nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
+ EXPECT_CALL(*mStubTracker.get(), nextAnticipatedVSyncTimeFrom(1000 + mVsyncMoveThreshold))
.InSequence(seq)
.WillOnce(Return(2000));
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
entry.executing(); // 1000 is executing
EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
}
@@ -1154,16 +1368,16 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, reportsScheduledIfStillTime) {
VSyncDispatchTimerQueueEntry entry(
"test", [](auto, auto, auto) {}, mVsyncMoveThreshold);
EXPECT_TRUE(entry.schedule({.workDuration = 100, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
EXPECT_TRUE(entry.schedule({.workDuration = 200, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
EXPECT_TRUE(entry.schedule({.workDuration = 50, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
EXPECT_TRUE(entry.schedule({.workDuration = 1200, .readyDuration = 0, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
}
@@ -1176,7 +1390,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, storesPendingUpdatesUntilUpdate) {
entry.addPendingWorkloadUpdate(
{.workDuration = effectualOffset, .readyDuration = 0, .earliestVsync = 400});
EXPECT_TRUE(entry.hasPendingWorkloadUpdate());
- entry.update(mStubTracker, 0);
+ entry.update(*mStubTracker.get(), 0);
EXPECT_FALSE(entry.hasPendingWorkloadUpdate());
EXPECT_THAT(*entry.wakeupTime(), Eq(mPeriod - effectualOffset));
}
@@ -1197,7 +1411,7 @@ TEST_F(VSyncDispatchTimerQueueEntryTest, runCallbackWithReadyDuration) {
mVsyncMoveThreshold);
EXPECT_TRUE(entry.schedule({.workDuration = 70, .readyDuration = 30, .earliestVsync = 500},
- mStubTracker, 0)
+ *mStubTracker.get(), 0)
.has_value());
auto const wakeup = entry.wakeupTime();
ASSERT_TRUE(wakeup);
diff --git a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
index 74d2b7d9b6..7a498c9d29 100644
--- a/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncPredictorTest.cpp
@@ -23,7 +23,10 @@
#define LOG_TAG "LibSurfaceFlingerUnittests"
#define LOG_NDEBUG 0
+#include <common/test/FlagUtils.h>
#include "Scheduler/VSyncPredictor.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockVsyncTrackerCallback.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
@@ -31,15 +34,31 @@
#include <chrono>
#include <utility>
+#include <com_android_graphics_surfaceflinger_flags.h>
+
using namespace testing;
using namespace std::literals;
+using namespace com::android::graphics::surfaceflinger;
+
+using NotifyExpectedPresentConfig =
+ ::aidl::android::hardware::graphics::composer3::VrrConfig::NotifyExpectedPresentConfig;
+
+using android::mock::createDisplayMode;
+using android::mock::createDisplayModeBuilder;
+using android::mock::createVrrDisplayMode;
namespace android::scheduler {
+namespace {
MATCHER_P2(IsCloseTo, value, tolerance, "is within tolerance") {
return arg <= value + tolerance && arg >= value - tolerance;
}
+MATCHER_P(FpsMatcher, value, "equals") {
+ using fps_approx_ops::operator==;
+ return arg == value;
+}
+
std::vector<nsecs_t> generateVsyncTimestamps(size_t count, nsecs_t period, nsecs_t bias) {
std::vector<nsecs_t> vsyncs(count);
std::generate(vsyncs.begin(), vsyncs.end(),
@@ -47,16 +66,29 @@ std::vector<nsecs_t> generateVsyncTimestamps(size_t count, nsecs_t period, nsecs
return vsyncs;
}
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+
+ftl::NonNull<DisplayModePtr> displayMode(nsecs_t period) {
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto refreshRate = Fps::fromPeriodNsecs(period);
+ return ftl::as_non_null(createDisplayMode(DisplayModeId(0), refreshRate, kGroup, kResolution,
+ DEFAULT_DISPLAY_ID));
+}
+} // namespace
+
struct VSyncPredictorTest : testing::Test {
nsecs_t mNow = 0;
nsecs_t mPeriod = 1000;
+ ftl::NonNull<DisplayModePtr> mMode = displayMode(mPeriod);
+ scheduler::mock::VsyncTrackerCallback mVsyncTrackerCallback;
static constexpr size_t kHistorySize = 10;
static constexpr size_t kMinimumSamplesForPrediction = 6;
static constexpr size_t kOutlierTolerancePercent = 25;
static constexpr nsecs_t mMaxRoundingError = 100;
- VSyncPredictor tracker{mPeriod, kHistorySize, kMinimumSamplesForPrediction,
- kOutlierTolerancePercent};
+ VSyncPredictor tracker{mMode, kHistorySize, kMinimumSamplesForPrediction,
+ kOutlierTolerancePercent, mVsyncTrackerCallback};
};
TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
@@ -66,7 +98,7 @@ TEST_F(VSyncPredictorTest, reportsAnticipatedPeriod) {
EXPECT_THAT(model.intercept, Eq(0));
auto const changedPeriod = 2000;
- tracker.setPeriod(changedPeriod);
+ tracker.setDisplayModePtr(displayMode(changedPeriod));
model = tracker.getVSyncPredictionModel();
EXPECT_THAT(model.slope, Eq(changedPeriod));
EXPECT_THAT(model.intercept, Eq(0));
@@ -87,7 +119,7 @@ TEST_F(VSyncPredictorTest, reportsSamplesNeededAfterExplicitRateChange) {
EXPECT_FALSE(tracker.needsMoreSamples());
auto const changedPeriod = mPeriod * 2;
- tracker.setPeriod(changedPeriod);
+ tracker.setDisplayModePtr(displayMode(changedPeriod));
EXPECT_TRUE(tracker.needsMoreSamples());
for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
@@ -121,7 +153,7 @@ TEST_F(VSyncPredictorTest, uponNotifiedOfInaccuracyUsesSynthetic) {
}
EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + slightlyLessPeriod));
- tracker.setPeriod(changedPeriod);
+ tracker.setDisplayModePtr(displayMode(changedPeriod));
EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + changedPeriod));
}
@@ -167,7 +199,7 @@ TEST_F(VSyncPredictorTest, adaptsToFenceTimelines_60hzHighVariance) {
auto constexpr expectedPeriod = 16639242;
auto constexpr expectedIntercept = 1049341;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -186,7 +218,7 @@ TEST_F(VSyncPredictorTest, adaptsToFenceTimelines_90hzLowVariance) {
auto expectedPeriod = 11089413;
auto expectedIntercept = 94421;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -213,7 +245,7 @@ TEST_F(VSyncPredictorTest, adaptsToFenceTimelinesDiscontinuous_22hzLowVariance)
auto expectedPeriod = 45450152;
auto expectedIntercept = 469647;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -239,7 +271,7 @@ TEST_F(VSyncPredictorTest, againstOutliersDiscontinuous_500hzLowVariance) {
auto expectedPeriod = 1999892;
auto expectedIntercept = 86342;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -259,7 +291,7 @@ TEST_F(VSyncPredictorTest, handlesVsyncChange) {
auto const simulatedVsyncsSlow =
generateVsyncTimestamps(kMinimumSamplesForPrediction, slowPeriod, slowTimeBase);
- tracker.setPeriod(fastPeriod);
+ tracker.setDisplayModePtr(displayMode(fastPeriod));
for (auto const& timestamp : simulatedVsyncsFast) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -269,7 +301,7 @@ TEST_F(VSyncPredictorTest, handlesVsyncChange) {
EXPECT_THAT(model.slope, IsCloseTo(fastPeriod, mMaxRoundingError));
EXPECT_THAT(model.intercept, IsCloseTo(0, mMaxRoundingError));
- tracker.setPeriod(slowPeriod);
+ tracker.setDisplayModePtr(displayMode(slowPeriod));
for (auto const& timestamp : simulatedVsyncsSlow) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -293,7 +325,7 @@ TEST_F(VSyncPredictorTest, willBeAccurateUsingPriorResultsForRate) {
generateVsyncTimestamps(kMinimumSamplesForPrediction, fastPeriod2, fastTimeBase);
auto idealPeriod = 100000;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncsFast) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -301,14 +333,14 @@ TEST_F(VSyncPredictorTest, willBeAccurateUsingPriorResultsForRate) {
EXPECT_THAT(model.slope, Eq(fastPeriod));
EXPECT_THAT(model.intercept, Eq(0));
- tracker.setPeriod(slowPeriod);
+ tracker.setDisplayModePtr(displayMode(slowPeriod));
for (auto const& timestamp : simulatedVsyncsSlow) {
tracker.addVsyncTimestamp(timestamp);
}
// we had a model for 100ns mPeriod before, use that until the new samples are
// sufficiently built up
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
model = tracker.getVSyncPredictionModel();
EXPECT_THAT(model.slope, Eq(fastPeriod));
EXPECT_THAT(model.intercept, Eq(0));
@@ -357,7 +389,7 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) {
auto const expectedPeriod = 11113919;
auto const expectedIntercept = -1195945;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -376,7 +408,9 @@ TEST_F(VSyncPredictorTest, doesNotPredictBeforeTimePointWithHigherIntercept) {
// See b/151146131
TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
- VSyncPredictor tracker{mPeriod, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent};
+ const auto mode = displayMode(mPeriod);
+ VSyncPredictor tracker{mode, 20, kMinimumSamplesForPrediction, kOutlierTolerancePercent,
+ mVsyncTrackerCallback};
std::vector<nsecs_t> const simulatedVsyncs{840873348817, 840890049444, 840906762675,
840923581635, 840940161584, 840956868096,
840973702473, 840990256277, 841007116851,
@@ -390,7 +424,7 @@ TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
auto const expectedPeriod = 16698426;
auto const expectedIntercept = 58055;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -403,7 +437,7 @@ TEST_F(VSyncPredictorTest, hasEnoughPrecision) {
TEST_F(VSyncPredictorTest, resetsWhenInstructed) {
auto const idealPeriod = 10000;
auto const realPeriod = 10500;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto i = 0; i < kMinimumSamplesForPrediction; i++) {
tracker.addVsyncTimestamp(i * realPeriod);
}
@@ -468,7 +502,7 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) {
const auto maxPeriods = 15;
for (int divisor = 1; divisor < maxDivisor; divisor++) {
for (int i = 0; i < maxPeriods; i++) {
- const bool expectedInPhase = (i % divisor) == 0;
+ const bool expectedInPhase = ((kMinimumSamplesForPrediction - 1 + i) % divisor) == 0;
EXPECT_THAT(expectedInPhase,
tracker.isVSyncInPhase(mNow + i * mPeriod - bias,
Fps::fromPeriodNsecs(divisor * mPeriod)))
@@ -478,6 +512,28 @@ TEST_F(VSyncPredictorTest, isVSyncInPhase) {
}
}
+TEST_F(VSyncPredictorTest, isVSyncInPhaseForDivisors) {
+ auto last = mNow;
+ for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+ mNow += mPeriod;
+ last = mNow;
+ tracker.addVsyncTimestamp(mNow);
+ }
+
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
+
+ EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 1 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2)));
+ EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 2 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2)));
+ EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 2)));
+
+ EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 5 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4)));
+ EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 3 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4)));
+ EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 4 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4)));
+ EXPECT_FALSE(tracker.isVSyncInPhase(mNow + 6 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4)));
+ EXPECT_TRUE(tracker.isVSyncInPhase(mNow + 7 * mPeriod, Fps::fromPeriodNsecs(mPeriod * 4)));
+}
+
TEST_F(VSyncPredictorTest, inconsistentVsyncValueIsFlushedEventually) {
EXPECT_TRUE(tracker.addVsyncTimestamp(600));
EXPECT_TRUE(tracker.needsMoreSamples());
@@ -523,7 +579,7 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) {
auto constexpr expectedPeriod = 16'644'742;
auto constexpr expectedIntercept = 125'626;
- tracker.setPeriod(idealPeriod);
+ tracker.setDisplayModePtr(displayMode(idealPeriod));
for (auto const& timestamp : simulatedVsyncs) {
tracker.addVsyncTimestamp(timestamp);
}
@@ -532,6 +588,149 @@ TEST_F(VSyncPredictorTest, robustToDuplicateTimestamps_60hzRealTraceData) {
EXPECT_THAT(intercept, IsCloseTo(expectedIntercept, mMaxRoundingError));
}
+TEST_F(VSyncPredictorTest, setRenderRateIsRespected) {
+ auto last = mNow;
+ for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+ mNow += mPeriod;
+ last = mNow;
+ tracker.addVsyncTimestamp(mNow);
+ }
+
+ tracker.setRenderRate(Fps::fromPeriodNsecs(3 * mPeriod));
+
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 4 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 4 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 7 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 7 * mPeriod));
+}
+
+TEST_F(VSyncPredictorTest, setRenderRateOfDivisorIsInPhase) {
+ auto last = mNow;
+ for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+ mNow += mPeriod;
+ last = mNow;
+ tracker.addVsyncTimestamp(mNow);
+ }
+
+ const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
+
+ tracker.setRenderRate(refreshRate / 4);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 3 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 7 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 11 * mPeriod));
+
+ tracker.setRenderRate(refreshRate / 2);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 3 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3 * mPeriod), Eq(mNow + 5 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5 * mPeriod), Eq(mNow + 7 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 7 * mPeriod), Eq(mNow + 9 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 9 * mPeriod), Eq(mNow + 11 * mPeriod));
+
+ tracker.setRenderRate(refreshRate / 6);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + 1 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod), Eq(mNow + 7 * mPeriod));
+}
+
+TEST_F(VSyncPredictorTest, setRenderRateIsIgnoredIfNotDivisor) {
+ auto last = mNow;
+ for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+ mNow += mPeriod;
+ last = mNow;
+ tracker.addVsyncTimestamp(mNow);
+ }
+
+ tracker.setRenderRate(Fps::fromPeriodNsecs(3.5f * mPeriod));
+
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(mNow + mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 100), Eq(mNow + mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1100), Eq(mNow + 2 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 2100), Eq(mNow + 3 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 3100), Eq(mNow + 4 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 4100), Eq(mNow + 5 * mPeriod));
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 5100), Eq(mNow + 6 * mPeriod));
+}
+
+TEST_F(VSyncPredictorTest, vsyncTrackerCallback) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const auto refreshRate = Fps::fromPeriodNsecs(mPeriod);
+ NotifyExpectedPresentConfig notifyExpectedPresentConfig;
+ notifyExpectedPresentConfig.notifyExpectedPresentTimeoutNs = Period::fromNs(30).ns();
+
+ hal::VrrConfig vrrConfig;
+ vrrConfig.notifyExpectedPresentConfig = notifyExpectedPresentConfig;
+ vrrConfig.minFrameIntervalNs = refreshRate.getPeriodNsecs();
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto mode =
+ ftl::as_non_null(createVrrDisplayMode(DisplayModeId(0), refreshRate, vrrConfig, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID));
+
+ tracker.setDisplayModePtr(mode);
+ auto last = mNow;
+ for (auto i = 0u; i < kMinimumSamplesForPrediction; i++) {
+ EXPECT_CALL(mVsyncTrackerCallback,
+ onVsyncGenerated(TimePoint::fromNs(last + mPeriod), mode,
+ FpsMatcher(refreshRate)))
+ .Times(1);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow), Eq(last + mPeriod));
+ mNow += mPeriod;
+ last = mNow;
+ tracker.addVsyncTimestamp(mNow);
+ }
+
+ tracker.setRenderRate(refreshRate / 2);
+ {
+ // out of render rate phase
+ EXPECT_CALL(mVsyncTrackerCallback,
+ onVsyncGenerated(TimePoint::fromNs(mNow + 3 * mPeriod), mode,
+ FpsMatcher(refreshRate / 2)))
+ .Times(1);
+ EXPECT_THAT(tracker.nextAnticipatedVSyncTimeFrom(mNow + 1 * mPeriod),
+ Eq(mNow + 3 * mPeriod));
+ }
+}
+
+TEST_F(VSyncPredictorTest, adjustsVrrTimeline) {
+ SET_FLAG_FOR_TEST(flags::vrr_config, true);
+
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto refreshRate = Fps::fromPeriodNsecs(500);
+ const auto minFrameRate = Fps::fromPeriodNsecs(1000);
+ hal::VrrConfig vrrConfig;
+ vrrConfig.minFrameIntervalNs = minFrameRate.getPeriodNsecs();
+ const ftl::NonNull<DisplayModePtr> kMode =
+ ftl::as_non_null(createDisplayModeBuilder(DisplayModeId(0), refreshRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID)
+ .setVrrConfig(std::move(vrrConfig))
+ .build());
+
+ VSyncPredictor vrrTracker{kMode, kHistorySize, kMinimumSamplesForPrediction,
+ kOutlierTolerancePercent, mVsyncTrackerCallback};
+
+ vrrTracker.setRenderRate(minFrameRate);
+ vrrTracker.addVsyncTimestamp(0);
+ EXPECT_EQ(1000, vrrTracker.nextAnticipatedVSyncTimeFrom(700));
+ EXPECT_EQ(2000, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
+
+ vrrTracker.onFrameBegin(TimePoint::fromNs(2000), TimePoint::fromNs(1500));
+ EXPECT_EQ(1500, vrrTracker.nextAnticipatedVSyncTimeFrom(1300));
+ EXPECT_EQ(2500, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
+
+ vrrTracker.onFrameMissed(TimePoint::fromNs(2500));
+ EXPECT_EQ(3000, vrrTracker.nextAnticipatedVSyncTimeFrom(2300));
+ EXPECT_EQ(4000, vrrTracker.nextAnticipatedVSyncTimeFrom(3300));
+}
+
} // namespace android::scheduler
// TODO(b/129481165): remove the #pragma below and fix conversion issues
diff --git a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
index 30a3f9a4a7..8d9623de1c 100644
--- a/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VSyncReactorTest.cpp
@@ -31,6 +31,9 @@
#include <scheduler/TimeKeeper.h>
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockVSyncTracker.h"
+
#include "Scheduler/VSyncDispatch.h"
#include "Scheduler/VSyncReactor.h"
#include "Scheduler/VSyncTracker.h"
@@ -40,19 +43,7 @@ using namespace std::literals;
namespace android::scheduler {
-class MockVSyncTracker : public VSyncTracker {
-public:
- MockVSyncTracker() { ON_CALL(*this, addVsyncTimestamp(_)).WillByDefault(Return(true)); }
- MOCK_METHOD1(addVsyncTimestamp, bool(nsecs_t));
- MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
- MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
- MOCK_METHOD1(setPeriod, void(nsecs_t));
- MOCK_METHOD0(resetModel, void());
- MOCK_CONST_METHOD0(needsMoreSamples, bool());
- MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
- MOCK_CONST_METHOD1(dump, void(std::string&));
-};
-
+namespace {
class MockClock : public Clock {
public:
MOCK_CONST_METHOD0(now, nsecs_t());
@@ -68,21 +59,13 @@ private:
std::shared_ptr<Clock> const mClock;
};
-struct MockVSyncDispatch : VSyncDispatch {
- MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
- MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
- MOCK_METHOD(ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
- MOCK_METHOD(CancelResult, cancel, (CallbackToken), (override));
- MOCK_METHOD(void, dump, (std::string&), (const, override));
-};
-
std::shared_ptr<android::FenceTime> generateInvalidFence() {
- sp<Fence> fence = new Fence();
+ sp<Fence> fence = sp<Fence>::make();
return std::make_shared<android::FenceTime>(fence);
}
std::shared_ptr<android::FenceTime> generatePendingFence() {
- sp<Fence> fence = new Fence(dup(fileno(tmpfile())));
+ sp<Fence> fence = sp<Fence>::make(dup(fileno(tmpfile())));
return std::make_shared<android::FenceTime>(fence);
}
@@ -92,24 +75,41 @@ void signalFenceWithTime(std::shared_ptr<android::FenceTime> const& fence, nsecs
}
std::shared_ptr<android::FenceTime> generateSignalledFenceWithTime(nsecs_t time) {
- sp<Fence> fence = new Fence(dup(fileno(tmpfile())));
+ sp<Fence> fence = sp<Fence>::make(dup(fileno(tmpfile())));
std::shared_ptr<android::FenceTime> ft = std::make_shared<android::FenceTime>(fence);
signalFenceWithTime(ft, time);
return ft;
}
+constexpr PhysicalDisplayId DEFAULT_DISPLAY_ID = PhysicalDisplayId::fromPort(42u);
+
+ftl::NonNull<DisplayModePtr> displayMode(nsecs_t vsyncPeriod) {
+ const int32_t kGroup = 0;
+ const auto kResolution = ui::Size(1920, 1080);
+ const auto refreshRate = Fps::fromPeriodNsecs(vsyncPeriod);
+ return ftl::as_non_null(mock::createDisplayMode(DisplayModeId(0), refreshRate, kGroup,
+ kResolution, DEFAULT_DISPLAY_ID));
+}
+
+MATCHER_P(DisplayModeMatcher, value, "display mode equals") {
+ return arg->getId() == value->getId() && equalsExceptDisplayModeId(*arg, *value);
+}
+
+} // namespace
+
class VSyncReactorTest : public testing::Test {
protected:
VSyncReactorTest()
- : mMockTracker(std::make_shared<NiceMock<MockVSyncTracker>>()),
+ : mMockTracker(std::make_shared<NiceMock<mock::VSyncTracker>>()),
mMockClock(std::make_shared<NiceMock<MockClock>>()),
- mReactor(std::make_unique<ClockWrapper>(mMockClock), *mMockTracker, kPendingLimit,
- false /* supportKernelIdleTimer */) {
+ mReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock), *mMockTracker,
+ kPendingLimit, false /* supportKernelIdleTimer */) {
ON_CALL(*mMockClock, now()).WillByDefault(Return(mFakeNow));
ON_CALL(*mMockTracker, currentPeriod()).WillByDefault(Return(period));
+ ON_CALL(*mMockTracker, addVsyncTimestamp(_)).WillByDefault(Return(true));
}
- std::shared_ptr<MockVSyncTracker> mMockTracker;
+ std::shared_ptr<mock::VSyncTracker> mMockTracker;
std::shared_ptr<MockClock> mMockClock;
static constexpr size_t kPendingLimit = 3;
static constexpr nsecs_t mDummyTime = 47;
@@ -199,7 +199,8 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) {
mReactor.setIgnorePresentFences(true);
nsecs_t const newPeriod = 5000;
- mReactor.startPeriodTransition(newPeriod);
+
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
@@ -211,8 +212,8 @@ TEST_F(VSyncReactorTest, ignoresProperlyAfterAPeriodConfirmation) {
TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
nsecs_t const newPeriod = 5000;
- EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
- mReactor.startPeriodTransition(newPeriod);
+ EXPECT_CALL(*mMockTracker, setDisplayModePtr(_)).Times(0);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
bool periodFlushed = true;
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed));
@@ -222,7 +223,7 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
EXPECT_FALSE(periodFlushed);
Mock::VerifyAndClearExpectations(mMockTracker.get());
- EXPECT_CALL(*mMockTracker, setPeriod(newPeriod)).Times(1);
+ EXPECT_CALL(*mMockTracker, setDisplayModePtr(/*displayMode(newPeriod)*/ _)).Times(1);
EXPECT_FALSE(mReactor.addHwVsyncTimestamp(25000, std::nullopt, &periodFlushed));
EXPECT_TRUE(periodFlushed);
@@ -231,7 +232,7 @@ TEST_F(VSyncReactorTest, setPeriodCalledOnceConfirmedChange) {
TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
nsecs_t sampleTime = 0;
nsecs_t const newPeriod = 5000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
bool periodFlushed = true;
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
@@ -239,7 +240,7 @@ TEST_F(VSyncReactorTest, changingPeriodBackAbortsConfirmationProcess) {
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
- mReactor.startPeriodTransition(period);
+ mReactor.onDisplayModeChanged(displayMode(period), false);
EXPECT_FALSE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
}
@@ -249,13 +250,13 @@ TEST_F(VSyncReactorTest, changingToAThirdPeriodWillWaitForLastPeriod) {
nsecs_t const secondPeriod = 5000;
nsecs_t const thirdPeriod = 2000;
- mReactor.startPeriodTransition(secondPeriod);
+ mReactor.onDisplayModeChanged(displayMode(secondPeriod), false);
bool periodFlushed = true;
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(sampleTime += period, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
- mReactor.startPeriodTransition(thirdPeriod);
+ mReactor.onDisplayModeChanged(displayMode(thirdPeriod), false);
EXPECT_TRUE(
mReactor.addHwVsyncTimestamp(sampleTime += secondPeriod, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
@@ -296,21 +297,22 @@ TEST_F(VSyncReactorTest, reportedBadTimestampFromPredictorWillReactivateHwVSyncP
TEST_F(VSyncReactorTest, presentFenceAdditionDoesNotInterruptConfirmationProcess) {
nsecs_t const newPeriod = 5000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
EXPECT_TRUE(mReactor.addPresentFence(generateSignalledFenceWithTime(0)));
}
TEST_F(VSyncReactorTest, setPeriodCalledFirstTwoEventsNewPeriod) {
nsecs_t const newPeriod = 5000;
- EXPECT_CALL(*mMockTracker, setPeriod(_)).Times(0);
- mReactor.startPeriodTransition(newPeriod);
+ EXPECT_CALL(*mMockTracker, setDisplayModePtr(_)).Times(0);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
bool periodFlushed = true;
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(5000, std::nullopt, &periodFlushed));
EXPECT_FALSE(periodFlushed);
Mock::VerifyAndClearExpectations(mMockTracker.get());
- EXPECT_CALL(*mMockTracker, setPeriod(newPeriod)).Times(1);
+ EXPECT_CALL(*mMockTracker, setDisplayModePtr(DisplayModeMatcher(displayMode(newPeriod))))
+ .Times(1);
EXPECT_FALSE(mReactor.addHwVsyncTimestamp(10000, std::nullopt, &periodFlushed));
EXPECT_TRUE(periodFlushed);
}
@@ -328,7 +330,7 @@ TEST_F(VSyncReactorTest, addResyncSamplePeriodChanges) {
bool periodFlushed = false;
nsecs_t const newPeriod = 4000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
auto time = 0;
auto constexpr numTimestampSubmissions = 10;
@@ -353,7 +355,7 @@ TEST_F(VSyncReactorTest, addHwVsyncTimestampDozePreempt) {
bool periodFlushed = false;
nsecs_t const newPeriod = 4000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
auto time = 0;
// If the power mode is not DOZE or DOZE_SUSPEND, it is still collecting timestamps.
@@ -370,7 +372,7 @@ TEST_F(VSyncReactorTest, addPresentFenceWhileAwaitingPeriodConfirmationRequestsH
auto time = 0;
bool periodFlushed = false;
nsecs_t const newPeriod = 4000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
time += period;
mReactor.addHwVsyncTimestamp(time, std::nullopt, &periodFlushed);
@@ -386,7 +388,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTracker) {
auto time = 0;
bool periodFlushed = false;
nsecs_t const newPeriod = 4000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
static auto constexpr numSamplesWithNewPeriod = 4;
Sequence seq;
@@ -413,7 +415,7 @@ TEST_F(VSyncReactorTest, hwVsyncturnsOffOnConfirmationWhenTrackerDoesntRequest)
auto time = 0;
bool periodFlushed = false;
nsecs_t const newPeriod = 4000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
Sequence seq;
EXPECT_CALL(*mMockTracker, needsMoreSamples())
@@ -433,7 +435,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) {
nsecs_t const newPeriod1 = 4000;
nsecs_t const newPeriod2 = 7000;
- mReactor.startPeriodTransition(newPeriod1);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod1), false);
Sequence seq;
EXPECT_CALL(*mMockTracker, needsMoreSamples())
@@ -452,7 +454,7 @@ TEST_F(VSyncReactorTest, hwVsyncIsRequestedForTrackerMultiplePeriodChanges) {
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
- mReactor.startPeriodTransition(newPeriod2);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod2), false);
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod1, std::nullopt, &periodFlushed));
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed));
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(time += newPeriod2, std::nullopt, &periodFlushed));
@@ -465,7 +467,7 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) {
mReactor.setIgnorePresentFences(true);
nsecs_t const newPeriod = 5000;
- mReactor.startPeriodTransition(newPeriod);
+ mReactor.onDisplayModeChanged(displayMode(newPeriod), false);
EXPECT_TRUE(mReactor.addHwVsyncTimestamp(0, 0, &periodFlushed));
EXPECT_FALSE(periodFlushed);
@@ -479,8 +481,9 @@ TEST_F(VSyncReactorTest, periodChangeWithGivenVsyncPeriod) {
TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
// Create a reactor which supports the kernel idle timer
- auto idleReactor = VSyncReactor(std::make_unique<ClockWrapper>(mMockClock), *mMockTracker,
- kPendingLimit, true /* supportKernelIdleTimer */);
+ auto idleReactor =
+ VSyncReactor(DEFAULT_DISPLAY_ID, std::make_unique<ClockWrapper>(mMockClock),
+ *mMockTracker, kPendingLimit, true /* supportKernelIdleTimer */);
bool periodFlushed = true;
EXPECT_CALL(*mMockTracker, addVsyncTimestamp(_)).Times(4);
@@ -488,7 +491,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
// First, set the same period, which should only be confirmed when we receive two
// matching callbacks
- idleReactor.startPeriodTransition(10000);
+ idleReactor.onDisplayModeChanged(displayMode(10000), false);
EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(0, 0, &periodFlushed));
EXPECT_FALSE(periodFlushed);
// Correct period but incorrect timestamp delta
@@ -501,7 +504,7 @@ TEST_F(VSyncReactorTest, periodIsMeasuredIfIgnoringComposer) {
// Then, set a new period, which should be confirmed as soon as we receive a callback
// reporting the new period
nsecs_t const newPeriod = 5000;
- idleReactor.startPeriodTransition(newPeriod);
+ idleReactor.onDisplayModeChanged(displayMode(newPeriod), false);
// Incorrect timestamp delta and period
EXPECT_TRUE(idleReactor.addHwVsyncTimestamp(20000, 10000, &periodFlushed));
EXPECT_FALSE(periodFlushed);
diff --git a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
index b5195826b1..8acbd6fa03 100644
--- a/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
+++ b/services/surfaceflinger/tests/unittests/VsyncModulatorTest.cpp
@@ -57,17 +57,14 @@ protected:
using Schedule = scheduler::TransactionSchedule;
using nanos = std::chrono::nanoseconds;
- const VsyncModulator::VsyncConfig kEarly{SF_OFFSET_EARLY, APP_OFFSET_EARLY,
- nanos(SF_DURATION_LATE), nanos(APP_DURATION_LATE)};
- const VsyncModulator::VsyncConfig kEarlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU,
- nanos(SF_DURATION_EARLY),
- nanos(APP_DURATION_EARLY)};
- const VsyncModulator::VsyncConfig kLate{SF_OFFSET_LATE, APP_OFFSET_LATE,
- nanos(SF_DURATION_EARLY_GPU),
- nanos(APP_DURATION_EARLY_GPU)};
-
- const VsyncModulator::VsyncConfigSet mOffsets = {kEarly, kEarlyGpu, kLate,
- nanos(HWC_MIN_WORK_DURATION)};
+ const VsyncConfig kEarly{SF_OFFSET_EARLY, APP_OFFSET_EARLY, nanos(SF_DURATION_LATE),
+ nanos(APP_DURATION_LATE)};
+ const VsyncConfig kEarlyGpu{SF_OFFSET_EARLY_GPU, APP_OFFSET_EARLY_GPU, nanos(SF_DURATION_EARLY),
+ nanos(APP_DURATION_EARLY)};
+ const VsyncConfig kLate{SF_OFFSET_LATE, APP_OFFSET_LATE, nanos(SF_DURATION_EARLY_GPU),
+ nanos(APP_DURATION_EARLY_GPU)};
+
+ const VsyncConfigSet mOffsets = {kEarly, kEarlyGpu, kLate, nanos(HWC_MIN_WORK_DURATION)};
sp<TestableVsyncModulator> mVsyncModulator = sp<TestableVsyncModulator>::make(mOffsets, Now);
void SetUp() override { EXPECT_EQ(kLate, mVsyncModulator->setVsyncConfigSet(mOffsets)); }
diff --git a/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
new file mode 100644
index 0000000000..bfdd5963f3
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/VsyncScheduleTest.cpp
@@ -0,0 +1,272 @@
+/*
+ * 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.
+ */
+
+#undef LOG_TAG
+#define LOG_TAG "LibSurfaceFlingerUnittests"
+
+#include <ftl/fake_guard.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <log/log.h>
+
+#include <scheduler/Fps.h>
+#include "Scheduler/VsyncSchedule.h"
+#include "ThreadContext.h"
+#include "mock/DisplayHardware/MockDisplayMode.h"
+#include "mock/MockVSyncDispatch.h"
+#include "mock/MockVSyncTracker.h"
+#include "mock/MockVsyncController.h"
+
+using android::mock::createDisplayMode;
+using testing::_;
+
+namespace android {
+
+constexpr PhysicalDisplayId kDisplayId = PhysicalDisplayId::fromPort(42u);
+
+class VsyncScheduleTest : public testing::Test {
+protected:
+ VsyncScheduleTest();
+ ~VsyncScheduleTest() override;
+
+ testing::MockFunction<void(PhysicalDisplayId, bool)> mRequestHardwareVsync;
+
+ const std::unique_ptr<scheduler::VsyncSchedule> mVsyncSchedule =
+ std::unique_ptr<scheduler::VsyncSchedule>(
+ new scheduler::VsyncSchedule(kDisplayId, std::make_shared<mock::VSyncTracker>(),
+ std::make_shared<mock::VSyncDispatch>(),
+ std::make_unique<mock::VsyncController>(),
+ mRequestHardwareVsync.AsStdFunction()));
+
+ mock::VsyncController& getController() {
+ return *static_cast<mock::VsyncController*>(&mVsyncSchedule->getController());
+ }
+};
+
+VsyncScheduleTest::VsyncScheduleTest() {
+ 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());
+}
+
+VsyncScheduleTest::~VsyncScheduleTest() {
+ 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());
+}
+
+namespace {
+
+using namespace testing;
+
+TEST_F(VsyncScheduleTest, InitiallyDisallowed) {
+ ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, EnableDoesNothingWhenDisallowed) {
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+
+ mVsyncSchedule->enableHardwareVsync();
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed) {
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+
+ mVsyncSchedule->disableHardwareVsync(false /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisallowed2) {
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+
+ mVsyncSchedule->disableHardwareVsync(true /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, MakeAllowed) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+
+ mVsyncSchedule->disableHardwareVsync(false /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNothingWhenDisabled2) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+
+ mVsyncSchedule->disableHardwareVsync(true /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, EnableWorksWhenDisabled) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
+
+ mVsyncSchedule->enableHardwareVsync();
+}
+
+TEST_F(VsyncScheduleTest, EnableWorksOnce) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
+
+ mVsyncSchedule->enableHardwareVsync();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+ mVsyncSchedule->enableHardwareVsync();
+}
+
+TEST_F(VsyncScheduleTest, AllowedIsSticky) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, EnableDisable) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
+
+ mVsyncSchedule->enableHardwareVsync();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, false));
+ mVsyncSchedule->disableHardwareVsync(false /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, EnableDisable2) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
+
+ mVsyncSchedule->enableHardwareVsync();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, false));
+ mVsyncSchedule->disableHardwareVsync(true /* disallow */);
+}
+
+TEST_F(VsyncScheduleTest, StartPeriodTransition) {
+ // Note: startPeriodTransition is only called when hardware vsyncs are
+ // allowed.
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+
+ const auto mode = ftl::as_non_null(createDisplayMode(DisplayModeId(0), 60_Hz));
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
+ EXPECT_CALL(getController(), onDisplayModeChanged(mode, false));
+
+ mVsyncSchedule->onDisplayModeChanged(mode, false);
+}
+
+TEST_F(VsyncScheduleTest, StartPeriodTransitionAlreadyEnabled) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ mVsyncSchedule->enableHardwareVsync();
+
+ const auto mode = ftl::as_non_null(createDisplayMode(DisplayModeId(0), 60_Hz));
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+ EXPECT_CALL(getController(), onDisplayModeChanged(mode, false));
+
+ mVsyncSchedule->onDisplayModeChanged(mode, false);
+}
+
+TEST_F(VsyncScheduleTest, StartPeriodTransitionForce) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+
+ const auto mode = ftl::as_non_null(createDisplayMode(DisplayModeId(0), 60_Hz));
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, true));
+ EXPECT_CALL(getController(), onDisplayModeChanged(mode, true));
+
+ mVsyncSchedule->onDisplayModeChanged(mode, true);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleDisallowed) {
+ const Period period = (60_Hz).getPeriod();
+ const auto timestamp = TimePoint::now();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+ EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0);
+
+ mVsyncSchedule->addResyncSample(timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleDisabled) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ const Period period = (60_Hz).getPeriod();
+ const auto timestamp = TimePoint::now();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+ EXPECT_CALL(getController(), addHwVsyncTimestamp(_, _, _)).Times(0);
+
+ mVsyncSchedule->addResyncSample(timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleReturnsTrue) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ mVsyncSchedule->enableHardwareVsync();
+
+ const Period period = (60_Hz).getPeriod();
+ const auto timestamp = TimePoint::now();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(_, _)).Times(0);
+ EXPECT_CALL(getController(),
+ addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _))
+ .WillOnce(Return(true));
+
+ mVsyncSchedule->addResyncSample(timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, AddResyncSampleReturnsFalse) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ mVsyncSchedule->enableHardwareVsync();
+
+ const Period period = (60_Hz).getPeriod();
+ const auto timestamp = TimePoint::now();
+
+ EXPECT_CALL(mRequestHardwareVsync, Call(kDisplayId, false));
+ EXPECT_CALL(getController(),
+ addHwVsyncTimestamp(timestamp.ns(), std::optional<nsecs_t>(period.ns()), _))
+ .WillOnce(Return(false));
+
+ mVsyncSchedule->addResyncSample(timestamp, period);
+}
+
+TEST_F(VsyncScheduleTest, PendingState) FTL_FAKE_GUARD(kMainThreadContext) {
+ ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState());
+ mVsyncSchedule->setPendingHardwareVsyncState(true);
+ ASSERT_TRUE(mVsyncSchedule->getPendingHardwareVsyncState());
+
+ mVsyncSchedule->setPendingHardwareVsyncState(false);
+ ASSERT_FALSE(mVsyncSchedule->getPendingHardwareVsyncState());
+}
+
+TEST_F(VsyncScheduleTest, DisableDoesNotMakeAllowed) {
+ ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+ mVsyncSchedule->disableHardwareVsync(false /* disallow */);
+ ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, DisallowMakesNotAllowed) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ mVsyncSchedule->disableHardwareVsync(true /* disallow */);
+ ASSERT_FALSE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+TEST_F(VsyncScheduleTest, StillAllowedAfterDisable) {
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(true /* makeAllowed */));
+ mVsyncSchedule->disableHardwareVsync(false /* disallow */);
+ ASSERT_TRUE(mVsyncSchedule->isHardwareVsyncAllowed(false /* makeAllowed */));
+}
+
+} // namespace
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
new file mode 100644
index 0000000000..cfb047c877
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/WindowInfosListenerInvokerTest.cpp
@@ -0,0 +1,286 @@
+#include <android/gui/BnWindowInfosListener.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include <gui/WindowInfosUpdate.h>
+#include <condition_variable>
+
+#include "BackgroundExecutor.h"
+#include "WindowInfosListenerInvoker.h"
+#include "android/gui/IWindowInfosReportedListener.h"
+
+namespace android {
+
+class WindowInfosListenerInvokerTest : public testing::Test {
+protected:
+ WindowInfosListenerInvokerTest() : mInvoker(sp<WindowInfosListenerInvoker>::make()) {}
+
+ ~WindowInfosListenerInvokerTest() {
+ // Flush the BackgroundExecutor thread to ensure any scheduled tasks are complete.
+ // Otherwise, references those tasks hold may go out of scope before they are done
+ // executing.
+ BackgroundExecutor::getInstance().flushQueue();
+ }
+
+ sp<WindowInfosListenerInvoker> mInvoker;
+};
+
+using WindowInfosUpdateConsumer = std::function<void(const gui::WindowInfosUpdate&)>;
+
+class Listener : public gui::BnWindowInfosListener {
+public:
+ Listener(WindowInfosUpdateConsumer consumer) : mConsumer(std::move(consumer)) {}
+
+ binder::Status onWindowInfosChanged(const gui::WindowInfosUpdate& update) override {
+ mConsumer(update);
+ return binder::Status::ok();
+ }
+
+private:
+ WindowInfosUpdateConsumer mConsumer;
+};
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged calls a single window infos listener.
+TEST_F(WindowInfosListenerInvokerTest, callsSingleListener) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate& update) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+
+ listenerInfo.windowInfosPublisher
+ ->ackWindowInfosReceived(update.vsyncId,
+ listenerInfo.listenerId);
+ }),
+ &listenerInfo);
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[this]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ std::unique_lock<std::mutex> lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 1; });
+ EXPECT_EQ(callCount, 1);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged calls multiple window infos listeners.
+TEST_F(WindowInfosListenerInvokerTest, callsMultipleListeners) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ size_t callCount = 0;
+ const size_t expectedCallCount = 3;
+ std::vector<gui::WindowInfosListenerInfo> listenerInfos{expectedCallCount,
+ gui::WindowInfosListenerInfo{}};
+
+ for (size_t i = 0; i < expectedCallCount; i++) {
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&, &listenerInfo = listenerInfos[i]](
+ const gui::WindowInfosUpdate&
+ update) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ if (callCount == expectedCallCount) {
+ cv.notify_one();
+ }
+
+ listenerInfo.windowInfosPublisher
+ ->ackWindowInfosReceived(update.vsyncId,
+ listenerInfo
+ .listenerId);
+ }),
+ &listenerInfos[i]);
+ }
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ std::unique_lock<std::mutex> lock{mutex};
+ cv.wait(lock, [&]() { return callCount == expectedCallCount; });
+ EXPECT_EQ(callCount, expectedCallCount);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged delays sending a second message until
+// after the WindowInfosReportedListener is called.
+TEST_F(WindowInfosListenerInvokerTest, delaysUnackedCall) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+
+ // Simulate a slow ack by not calling IWindowInfosPublisher.ackWindowInfosReceived
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate&) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+ }),
+ &listenerInfo);
+
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 0, 0}, {},
+ false);
+ mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 1, 0}, {},
+ false);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 1; });
+ }
+ EXPECT_EQ(callCount, 1);
+
+ // Ack the first message.
+ listenerInfo.windowInfosPublisher->ackWindowInfosReceived(0, listenerInfo.listenerId);
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 2; });
+ }
+ EXPECT_EQ(callCount, 2);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged immediately sends a second message when
+// forceImmediateCall is true.
+TEST_F(WindowInfosListenerInvokerTest, sendsForcedMessage) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+ const int expectedCallCount = 2;
+
+ // Simulate a slow ack by not calling IWindowInfosPublisher.ackWindowInfosReceived
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate&) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ if (callCount == expectedCallCount) {
+ cv.notify_one();
+ }
+ }),
+ &listenerInfo);
+
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 0, 0}, {},
+ false);
+ mInvoker->windowInfosChanged(gui::WindowInfosUpdate{{}, {}, /* vsyncId= */ 1, 0}, {}, true);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == expectedCallCount; });
+ }
+ EXPECT_EQ(callCount, expectedCallCount);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged skips old messages when more than one
+// message is delayed.
+TEST_F(WindowInfosListenerInvokerTest, skipsDelayedMessage) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int64_t lastUpdateId = -1;
+
+ // Simulate a slow ack by not calling IWindowInfosPublisher.ackWindowInfosReceived
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate& update) {
+ std::scoped_lock lock{mutex};
+ lastUpdateId = update.vsyncId;
+ cv.notify_one();
+ }),
+ &listenerInfo);
+
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 1, 0}, {}, false);
+ mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 2, 0}, {}, false);
+ mInvoker->windowInfosChanged({{}, {}, /* vsyncId= */ 3, 0}, {}, false);
+ }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return lastUpdateId == 1; });
+ }
+ EXPECT_EQ(lastUpdateId, 1);
+
+ // Ack the first message. The third update should be sent.
+ listenerInfo.windowInfosPublisher->ackWindowInfosReceived(1, listenerInfo.listenerId);
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return lastUpdateId == 3; });
+ }
+ EXPECT_EQ(lastUpdateId, 3);
+}
+
+// Test that WindowInfosListenerInvoker#windowInfosChanged immediately calls listener after a call
+// where no listeners were configured.
+TEST_F(WindowInfosListenerInvokerTest, noListeners) {
+ std::mutex mutex;
+ std::condition_variable cv;
+
+ int callCount = 0;
+
+ // Test that calling windowInfosChanged without any listeners doesn't cause the next call to be
+ // delayed.
+ BackgroundExecutor::getInstance().sendCallbacks({[&]() {
+ mInvoker->windowInfosChanged({}, {}, false);
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate&) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+ }),
+ &listenerInfo);
+ }});
+ BackgroundExecutor::getInstance().flushQueue();
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 1; });
+ }
+ EXPECT_EQ(callCount, 1);
+}
+
+// Test that WindowInfosListenerInvoker#removeWindowInfosListener acks any unacked messages for
+// the removed listener.
+TEST_F(WindowInfosListenerInvokerTest, removeListenerAcks) {
+ // Don't ack in this listener to ensure there's an unacked message when the listener is later
+ // removed.
+ gui::WindowInfosListenerInfo listenerToBeRemovedInfo;
+ auto listenerToBeRemoved = sp<Listener>::make([](const gui::WindowInfosUpdate&) {});
+ mInvoker->addWindowInfosListener(listenerToBeRemoved, &listenerToBeRemovedInfo);
+
+ std::mutex mutex;
+ std::condition_variable cv;
+ int callCount = 0;
+ gui::WindowInfosListenerInfo listenerInfo;
+ mInvoker->addWindowInfosListener(sp<Listener>::make([&](const gui::WindowInfosUpdate& update) {
+ std::scoped_lock lock{mutex};
+ callCount++;
+ cv.notify_one();
+ listenerInfo.windowInfosPublisher
+ ->ackWindowInfosReceived(update.vsyncId,
+ listenerInfo.listenerId);
+ }),
+ &listenerInfo);
+
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+ mInvoker->removeWindowInfosListener(listenerToBeRemoved);
+ BackgroundExecutor::getInstance().sendCallbacks(
+ {[&]() { mInvoker->windowInfosChanged({}, {}, false); }});
+
+ // Verify that the second listener is called twice. If unacked messages aren't removed when the
+ // first listener is removed, this will fail.
+ {
+ std::unique_lock lock{mutex};
+ cv.wait(lock, [&]() { return callCount == 2; });
+ }
+ EXPECT_EQ(callCount, 2);
+}
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
deleted file mode 100644
index 657ced3d08..0000000000
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#pragma once
-
-#include <gmock/gmock.h>
-
-#include "DisplayHardware/PowerAdvisor.h"
-
-namespace android {
-namespace hardware {
-namespace power {
-class IPower;
-}
-} // namespace hardware
-} // namespace android
-
-namespace android::Hwc2::mock {
-
-class MockAidlPowerHalWrapper : public Hwc2::impl::AidlPowerHalWrapper {
-public:
- MockAidlPowerHalWrapper();
- ~MockAidlPowerHalWrapper() override;
- MOCK_METHOD(bool, setExpensiveRendering, (bool enabled), (override));
- MOCK_METHOD(bool, notifyDisplayUpdateImminent, (), (override));
- MOCK_METHOD(bool, supportsPowerHintSession, (), (override));
- MOCK_METHOD(bool, isPowerHintSessionRunning, (), (override));
- MOCK_METHOD(void, restartPowerHintSession, (), (override));
- MOCK_METHOD(void, setPowerHintSessionThreadIds, (const std::vector<int32_t>& threadIds),
- (override));
- MOCK_METHOD(bool, startPowerHintSession, (), (override));
- MOCK_METHOD(void, setTargetWorkDuration, (nsecs_t targetDuration), (override));
- MOCK_METHOD(void, sendActualWorkDuration, (nsecs_t actualDuration, nsecs_t timestamp),
- (override));
- MOCK_METHOD(bool, shouldReconnectHAL, (), (override));
-};
-
-} // namespace android::Hwc2::mock \ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
index aa8b5211e9..184dada32e 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockComposer.h
@@ -32,7 +32,6 @@ using android::hardware::graphics::common::V1_0::Transform;
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;
using android::hardware::graphics::composer::V2_1::Config;
@@ -52,12 +51,12 @@ public:
~Composer() override;
MOCK_METHOD(bool, isSupported, (OptionalFeature), (const, override));
+ MOCK_METHOD(bool, isVrrSupported, (), (const, override));
MOCK_METHOD0(getCapabilities,
std::vector<aidl::android::hardware::graphics::composer3::Capability>());
MOCK_METHOD0(dumpDebugInfo, std::string());
MOCK_METHOD1(registerCallback, void(HWC2::ComposerCallback&));
- MOCK_METHOD0(resetCommands, void());
- MOCK_METHOD0(executeCommands, Error());
+ MOCK_METHOD1(executeCommands, Error(Display));
MOCK_METHOD0(getMaxVirtualDisplayCount, uint32_t());
MOCK_METHOD4(createVirtualDisplay, Error(uint32_t, uint32_t, PixelFormat*, Display*));
MOCK_METHOD1(destroyVirtualDisplay, Error(Display));
@@ -72,6 +71,8 @@ public:
MOCK_METHOD4(getDisplayAttribute,
Error(Display, Config config, IComposerClient::Attribute, int32_t*));
MOCK_METHOD2(getDisplayConfigs, Error(Display, std::vector<Config>*));
+ MOCK_METHOD3(getDisplayConfigurations,
+ Error(Display, int32_t, std::vector<DisplayConfiguration>*));
MOCK_METHOD2(getDisplayName, Error(Display, std::string*));
MOCK_METHOD4(getDisplayRequests,
Error(Display, uint32_t*, std::vector<Layer>*, std::vector<uint32_t>*));
@@ -85,20 +86,23 @@ public:
MOCK_METHOD3(getReleaseFences, Error(Display, std::vector<Layer>*, std::vector<int>*));
MOCK_METHOD2(presentDisplay, Error(Display, int*));
MOCK_METHOD2(setActiveConfig, Error(Display, Config));
- MOCK_METHOD6(setClientTarget,
- Error(Display, uint32_t, const sp<GraphicBuffer>&, int, Dataspace,
- const std::vector<IComposerClient::Rect>&));
+ MOCK_METHOD(Error, setClientTarget,
+ (Display, uint32_t, const sp<GraphicBuffer>&, int, Dataspace,
+ const std::vector<IComposerClient::Rect>&, float),
+ (override));
MOCK_METHOD3(setColorMode, Error(Display, ColorMode, RenderIntent));
MOCK_METHOD2(setColorTransform, Error(Display, const float*));
MOCK_METHOD3(setOutputBuffer, Error(Display, const native_handle_t*, int));
MOCK_METHOD2(setPowerMode, Error(Display, IComposerClient::PowerMode));
MOCK_METHOD2(setVsyncEnabled, Error(Display, IComposerClient::Vsync));
MOCK_METHOD1(setClientTargetSlotCount, Error(Display));
- MOCK_METHOD4(validateDisplay, Error(Display, nsecs_t, uint32_t*, uint32_t*));
- MOCK_METHOD6(presentOrValidateDisplay,
- Error(Display, nsecs_t, uint32_t*, uint32_t*, int*, uint32_t*));
+ MOCK_METHOD(Error, validateDisplay, (Display, nsecs_t, int32_t, uint32_t*, uint32_t*));
+ MOCK_METHOD(Error, presentOrValidateDisplay,
+ (Display, nsecs_t, int32_t, uint32_t*, uint32_t*, int*, uint32_t*));
MOCK_METHOD4(setCursorPosition, Error(Display, Layer, int32_t, int32_t));
MOCK_METHOD5(setLayerBuffer, Error(Display, Layer, uint32_t, const sp<GraphicBuffer>&, int));
+ MOCK_METHOD4(setLayerBufferSlotsToClear,
+ Error(Display, Layer, const std::vector<uint32_t>&, uint32_t));
MOCK_METHOD3(setLayerSurfaceDamage,
Error(Display, Layer, const std::vector<IComposerClient::Rect>&));
MOCK_METHOD3(setLayerBlendMode, Error(Display, Layer, IComposerClient::BlendMode));
@@ -144,6 +148,12 @@ public:
MOCK_METHOD2(setBootDisplayConfig, Error(Display, Config));
MOCK_METHOD1(clearBootDisplayConfig, Error(Display));
MOCK_METHOD2(getPreferredBootDisplayConfig, Error(Display, Config*));
+ MOCK_METHOD1(getHdrConversionCapabilities,
+ Error(std::vector<
+ aidl::android::hardware::graphics::common::HdrConversionCapability>*));
+ MOCK_METHOD2(setHdrConversionStrategy,
+ Error(aidl::android::hardware::graphics::common::HdrConversionStrategy,
+ aidl::android::hardware::graphics::common::Hdr*));
MOCK_METHOD2(getSupportedContentTypes,
V2_4::Error(Display, std::vector<IComposerClient::ContentType>*));
MOCK_METHOD2(setContentType, V2_4::Error(Display, IComposerClient::ContentType));
@@ -164,6 +174,12 @@ public:
MOCK_METHOD2(setIdleTimerEnabled, Error(Display, std::chrono::milliseconds));
MOCK_METHOD2(hasDisplayIdleTimerCapability, Error(Display, bool*));
MOCK_METHOD2(getPhysicalDisplayOrientation, Error(Display, AidlTransform*));
+ MOCK_METHOD1(getOverlaySupport,
+ Error(aidl::android::hardware::graphics::composer3::OverlayProperties*));
+ MOCK_METHOD1(onHotplugConnect, void(Display));
+ MOCK_METHOD1(onHotplugDisconnect, void(Display));
+ MOCK_METHOD(Error, setRefreshRateChangedCallbackDebugEnabled, (Display, bool));
+ MOCK_METHOD(Error, notifyExpectedPresent, (Display, nsecs_t, int32_t));
};
} // namespace Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
index a83ecbca26..5bcce501e6 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockDisplayMode.h
@@ -20,17 +20,57 @@
namespace android::mock {
-inline DisplayModePtr createDisplayMode(
- DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+inline DisplayMode::Builder createDisplayModeBuilder(
+ DisplayModeId modeId, Fps displayRefreshRate, int32_t group = 0,
ui::Size resolution = ui::Size(1920, 1080),
PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
return DisplayMode::Builder(hal::HWConfigId(modeId.value()))
.setId(modeId)
.setPhysicalDisplayId(displayId)
- .setVsyncPeriod(refreshRate.getPeriodNsecs())
+ .setVsyncPeriod(displayRefreshRate.getPeriodNsecs())
.setGroup(group)
- .setResolution(resolution)
+ .setResolution(resolution);
+}
+
+inline DisplayModePtr createDisplayMode(
+ DisplayModeId modeId, Fps refreshRate, int32_t group = 0,
+ ui::Size resolution = ui::Size(1920, 1080),
+ PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+ return createDisplayModeBuilder(modeId, refreshRate, group, resolution, displayId).build();
+}
+
+inline DisplayModePtr createDisplayMode(PhysicalDisplayId displayId, DisplayModeId modeId,
+ Fps refreshRate) {
+ return createDisplayMode(modeId, refreshRate, {}, {}, displayId);
+}
+
+inline DisplayModePtr createVrrDisplayMode(
+ DisplayModeId modeId, Fps displayRefreshRate, hal::VrrConfig vrrConfig, int32_t group = 0,
+ ui::Size resolution = ui::Size(1920, 1080),
+ PhysicalDisplayId displayId = PhysicalDisplayId::fromPort(0)) {
+ return createDisplayModeBuilder(modeId, displayRefreshRate, group, resolution, displayId)
+ .setVrrConfig(std::move(vrrConfig))
.build();
}
+inline DisplayModePtr cloneForDisplay(PhysicalDisplayId displayId, const DisplayModePtr& modePtr) {
+ return DisplayMode::Builder(modePtr->getHwcId())
+ .setId(modePtr->getId())
+ .setPhysicalDisplayId(displayId)
+ .setVsyncPeriod(modePtr->getVsyncRate().getPeriodNsecs())
+ .setGroup(modePtr->getGroup())
+ .setResolution(modePtr->getResolution())
+ .build();
+}
+
+inline DisplayModes cloneForDisplay(PhysicalDisplayId displayId, const DisplayModes& modes) {
+ DisplayModes clones;
+
+ for (const auto& [id, modePtr] : modes) {
+ clones.try_emplace(id, cloneForDisplay(displayId, modePtr));
+ }
+
+ return clones;
+}
+
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
index 07cd15da93..7413235c19 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockHWC2.h
@@ -66,8 +66,8 @@ public:
((std::unordered_map<Layer *, android::sp<android::Fence>> *)), (const, override));
MOCK_METHOD(hal::Error, present, (android::sp<android::Fence> *), (override));
MOCK_METHOD(hal::Error, setClientTarget,
- (uint32_t, const android::sp<android::GraphicBuffer> &,
- const android::sp<android::Fence> &, hal::Dataspace),
+ (uint32_t, const android::sp<android::GraphicBuffer>&,
+ const android::sp<android::Fence>&, hal::Dataspace, float),
(override));
MOCK_METHOD(hal::Error, setColorMode, (hal::ColorMode, hal::RenderIntent), (override));
MOCK_METHOD(hal::Error, setColorTransform, (const android::mat4 &), (override));
@@ -76,9 +76,9 @@ public:
(override));
MOCK_METHOD(hal::Error, setPowerMode, (hal::PowerMode), (override));
MOCK_METHOD(hal::Error, setVsyncEnabled, (hal::Vsync), (override));
- MOCK_METHOD(hal::Error, validate, (nsecs_t, uint32_t *, uint32_t *), (override));
+ MOCK_METHOD(hal::Error, validate, (nsecs_t, int32_t, uint32_t*, uint32_t*), (override));
MOCK_METHOD(hal::Error, presentOrValidate,
- (nsecs_t, uint32_t *, uint32_t *, android::sp<android::Fence> *, uint32_t *),
+ (nsecs_t, int32_t, uint32_t*, uint32_t*, android::sp<android::Fence>*, uint32_t*),
(override));
MOCK_METHOD(ftl::Future<hal::Error>, setDisplayBrightness,
(float, float, const Hwc2::Composer::DisplayBrightnessOptions &), (override));
@@ -105,6 +105,9 @@ public:
MOCK_METHOD(bool, hasDisplayIdleTimerCapability, (), (const override));
MOCK_METHOD(hal::Error, getPhysicalDisplayOrientation, (Hwc2::AidlTransform *),
(const override));
+ MOCK_METHOD(hal::Error, getOverlaySupport,
+ (aidl::android::hardware::graphics::composer3::OverlayProperties *),
+ (const override));
};
class Layer : public HWC2::Layer {
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
index 0ddc90d585..a088aabc11 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPower.h
@@ -18,14 +18,14 @@
#include "binder/Status.h"
-#include <android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPower.h>
#include <gmock/gmock.h>
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::IPower;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::Mode;
using android::binder::Status;
-using android::hardware::power::Boost;
-using android::hardware::power::IPower;
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::Mode;
namespace android::Hwc2::mock {
@@ -33,18 +33,19 @@ class MockIPower : public IPower {
public:
MockIPower();
- MOCK_METHOD(Status, isBoostSupported, (Boost boost, bool* ret), (override));
- MOCK_METHOD(Status, setBoost, (Boost boost, int32_t durationMs), (override));
- MOCK_METHOD(Status, isModeSupported, (Mode mode, bool* ret), (override));
- MOCK_METHOD(Status, setMode, (Mode mode, bool enabled), (override));
- MOCK_METHOD(Status, createHintSession,
+ MOCK_METHOD(ndk::ScopedAStatus, isBoostSupported, (Boost boost, bool* ret), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, setBoost, (Boost boost, int32_t durationMs), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, isModeSupported, (Mode mode, bool* ret), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, setMode, (Mode mode, bool enabled), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, createHintSession,
(int32_t tgid, int32_t uid, const std::vector<int32_t>& threadIds,
- int64_t durationNanos, sp<IPowerHintSession>* session),
+ int64_t durationNanos, std::shared_ptr<IPowerHintSession>* session),
(override));
- MOCK_METHOD(Status, getHintSessionPreferredRate, (int64_t * rate), (override));
- MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
- MOCK_METHOD(std::string, getInterfaceHash, (), (override));
- MOCK_METHOD(IBinder*, onAsBinder, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getHintSessionPreferredRate, (int64_t * rate), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
+ MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+ MOCK_METHOD(bool, isRemote, (), (override));
};
} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
index 439f6f4e75..364618d61a 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockIPowerHintSession.h
@@ -18,13 +18,15 @@
#include "binder/Status.h"
-#include <android/hardware/power/IPower.h>
+#include <aidl/android/hardware/power/IPower.h>
#include <gmock/gmock.h>
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::SessionMode;
using android::binder::Status;
-using android::hardware::power::IPowerHintSession;
-using namespace android::hardware::power;
+using namespace aidl::android::hardware::power;
namespace android::Hwc2::mock {
@@ -32,14 +34,19 @@ class MockIPowerHintSession : public IPowerHintSession {
public:
MockIPowerHintSession();
- MOCK_METHOD(IBinder*, onAsBinder, (), (override));
- MOCK_METHOD(Status, pause, (), (override));
- MOCK_METHOD(Status, resume, (), (override));
- MOCK_METHOD(Status, close, (), (override));
- MOCK_METHOD(int32_t, getInterfaceVersion, (), (override));
- MOCK_METHOD(std::string, getInterfaceHash, (), (override));
- MOCK_METHOD(Status, updateTargetWorkDuration, (int64_t), (override));
- MOCK_METHOD(Status, reportActualWorkDuration, (const ::std::vector<WorkDuration>&), (override));
+ MOCK_METHOD(ndk::SpAIBinder, asBinder, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, pause, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, resume, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, close, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getInterfaceVersion, (int32_t * version), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, getInterfaceHash, (std::string * hash), (override));
+ MOCK_METHOD(bool, isRemote, (), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, updateTargetWorkDuration, (int64_t), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, reportActualWorkDuration, (const ::std::vector<WorkDuration>&),
+ (override));
+ MOCK_METHOD(ndk::ScopedAStatus, sendHint, (SessionHint), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, setThreads, (const ::std::vector<int32_t>&), (override));
+ MOCK_METHOD(ndk::ScopedAStatus, setMode, (SessionMode, bool), (override));
};
} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
index aede250db5..8e8eb1dc1f 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerAdvisor.h
@@ -32,37 +32,35 @@ public:
MOCK_METHOD(void, setExpensiveRenderingExpected, (DisplayId displayId, bool expected),
(override));
MOCK_METHOD(bool, isUsingExpensiveRendering, (), (override));
- MOCK_METHOD(void, notifyDisplayUpdateImminent, (), (override));
+ MOCK_METHOD(void, notifyCpuLoadUp, (), (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, startPowerHintSession, (const std::vector<int32_t>& threadIds), (override));
+ MOCK_METHOD(void, updateTargetWorkDuration, (Duration targetDuration), (override));
+ MOCK_METHOD(void, reportActualWorkDuration, (), (override));
+ MOCK_METHOD(void, enablePowerHintSession, (bool enabled), (override));
+ MOCK_METHOD(bool, startPowerHintSession, (std::vector<int32_t> && threadIds), (override));
MOCK_METHOD(void, setGpuFenceTime,
(DisplayId displayId, std::unique_ptr<FenceTime>&& 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<DisplayId> & displayIds), (override));
- MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (int64_t targetDuration), (override));
+ MOCK_METHOD(void, setTotalFrameTargetWorkDuration, (Duration targetDuration), (override));
};
} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp
index 5049b1d367..3ec5c2d09b 100644
--- a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockAidlPowerHalWrapper.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2022 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,13 +14,11 @@
* limitations under the License.
*/
-#include "MockAidlPowerHalWrapper.h"
-#include "MockIPower.h"
+#include "MockPowerHalController.h"
namespace android::Hwc2::mock {
-MockAidlPowerHalWrapper::MockAidlPowerHalWrapper()
- : AidlPowerHalWrapper(sp<testing::NiceMock<MockIPower>>::make()){};
-MockAidlPowerHalWrapper::~MockAidlPowerHalWrapper() = default;
+MockPowerHalController::MockPowerHalController() = default;
+MockPowerHalController::~MockPowerHalController() = default;
} // namespace android::Hwc2::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
new file mode 100644
index 0000000000..68fe3c52d4
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/DisplayHardware/MockPowerHalController.h
@@ -0,0 +1,51 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <scheduler/Time.h>
+
+#include <powermanager/PowerHalController.h>
+
+namespace android {
+namespace hardware {
+namespace power {
+class IPower;
+}
+} // namespace hardware
+} // namespace android
+
+namespace android::Hwc2::mock {
+
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
+using android::power::HalResult;
+
+class MockPowerHalController : public power::PowerHalController {
+public:
+ MockPowerHalController();
+ ~MockPowerHalController() override;
+ MOCK_METHOD(void, init, (), (override));
+ MOCK_METHOD(HalResult<void>, setBoost, (Boost, int32_t), (override));
+ MOCK_METHOD(HalResult<void>, setMode, (Mode, bool), (override));
+ MOCK_METHOD(HalResult<std::shared_ptr<aidl::android::hardware::power::IPowerHintSession>>,
+ createHintSession, (int32_t, int32_t, const std::vector<int32_t>&, int64_t),
+ (override));
+ MOCK_METHOD(HalResult<int64_t>, getHintSessionPreferredRate, (), (override));
+};
+
+} // namespace android::Hwc2::mock \ No newline at end of file
diff --git a/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.h
new file mode 100644
index 0000000000..a71e82cc75
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockDisplayModeSpecs.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 <android/gui/DisplayModeSpecs.h>
+
+namespace android::mock {
+
+inline gui::DisplayModeSpecs createDisplayModeSpecs(int32_t defaultMode, bool allowGroupSwitching,
+ float minFps, float maxFps) {
+ gui::DisplayModeSpecs specs;
+ specs.defaultMode = defaultMode;
+ specs.allowGroupSwitching = allowGroupSwitching;
+ specs.primaryRanges.physical.min = minFps;
+ specs.primaryRanges.physical.max = maxFps;
+ specs.primaryRanges.render = specs.primaryRanges.physical;
+ specs.appRequestRanges = specs.primaryRanges;
+ return specs;
+}
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
index c5ca86a651..866af3bbd0 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockEventThread.h
@@ -24,31 +24,41 @@ namespace android::mock {
class EventThread : public android::EventThread {
public:
+ static constexpr auto kCallingUid = static_cast<uid_t>(0);
+
EventThread();
~EventThread() override;
- MOCK_CONST_METHOD2(createEventConnection,
- sp<EventThreadConnection>(ResyncCallback,
- ISurfaceComposer::EventRegistrationFlags));
- MOCK_METHOD0(onScreenReleased, void());
- MOCK_METHOD0(onScreenAcquired, void());
- MOCK_METHOD2(onHotplugReceived, void(PhysicalDisplayId, bool));
- MOCK_METHOD1(onModeChanged, void(DisplayModePtr));
- MOCK_METHOD2(onFrameRateOverridesChanged,
- void(PhysicalDisplayId, std::vector<FrameRateOverride>));
- MOCK_CONST_METHOD1(dump, void(std::string&));
- MOCK_METHOD2(setDuration,
- void(std::chrono::nanoseconds workDuration,
- std::chrono::nanoseconds readyDuration));
- MOCK_METHOD1(registerDisplayEventConnection,
- status_t(const sp<android::EventThreadConnection> &));
- MOCK_METHOD2(setVsyncRate, void(uint32_t, const sp<android::EventThreadConnection> &));
- MOCK_METHOD1(requestNextVsync, void(const sp<android::EventThreadConnection> &));
+ // TODO(b/302035909): workaround otherwise gtest complains about
+ // error: no viable conversion from
+ // 'tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration> &&>' to 'const
+ // tuple<android::ftl::Flags<android::gui::ISurfaceComposer::EventRegistration>>'
+ sp<EventThreadConnection> createEventConnection(EventRegistrationFlags flags) const override {
+ return createEventConnection(false, flags);
+ }
+ MOCK_METHOD(sp<EventThreadConnection>, createEventConnection, (bool, EventRegistrationFlags),
+ (const));
+
+ MOCK_METHOD(void, enableSyntheticVsync, (bool), (override));
+ MOCK_METHOD(void, onHotplugReceived, (PhysicalDisplayId, bool), (override));
+ MOCK_METHOD(void, onHotplugConnectionError, (int32_t), (override));
+ MOCK_METHOD(void, onModeChanged, (const scheduler::FrameRateMode&), (override));
+ MOCK_METHOD(void, onFrameRateOverridesChanged,
+ (PhysicalDisplayId, std::vector<FrameRateOverride>), (override));
+ MOCK_METHOD(void, dump, (std::string&), (const, override));
+ MOCK_METHOD(void, setDuration,
+ (std::chrono::nanoseconds workDuration, std::chrono::nanoseconds readyDuration),
+ (override));
+ MOCK_METHOD(status_t, registerDisplayEventConnection,
+ (const sp<android::EventThreadConnection>&), (override));
+ MOCK_METHOD(void, setVsyncRate, (uint32_t, const sp<android::EventThreadConnection>&),
+ (override));
+ MOCK_METHOD(void, requestNextVsync, (const sp<android::EventThreadConnection>&), (override));
MOCK_METHOD(VsyncEventData, getLatestVsyncEventData,
- (const sp<android::EventThreadConnection> &), (const));
- MOCK_METHOD1(requestLatestConfig, void(const sp<android::EventThreadConnection> &));
- MOCK_METHOD1(pauseVsyncCallback, void(bool));
- MOCK_METHOD0(getEventThreadConnectionCount, size_t());
+ (const sp<android::EventThreadConnection>&), (const, override));
+ MOCK_METHOD(void, requestLatestConfig, (const sp<android::EventThreadConnection>&));
+ MOCK_METHOD(void, pauseVsyncCallback, (bool));
+ MOCK_METHOD(void, onNewVsyncSchedule, (std::shared_ptr<scheduler::VsyncSchedule>), (override));
};
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
new file mode 100644
index 0000000000..bfcdd9bbd5
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameRateMode.h
@@ -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.
+ */
+
+#pragma once
+
+#include <scheduler/FrameRateMode.h>
+
+// Use a C style macro to keep the line numbers printed in gtest
+#define EXPECT_FRAME_RATE_MODE(_modePtr, _fps, _mode) \
+ EXPECT_EQ((scheduler::FrameRateMode{(_fps), (_modePtr)}), (_mode)) \
+ << "Expected " << (_fps) << " (" << (_modePtr)->getVsyncRate() << ") but was " \
+ << (_mode).fps << " (" << (_mode).modePtr->getVsyncRate() << ")"
diff --git a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
index 5dc48c3703..3f2a917ddc 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockFrameTimeline.h
@@ -31,7 +31,7 @@ public:
MOCK_METHOD0(onBootFinished, void());
MOCK_METHOD1(addSurfaceFrame, void(std::shared_ptr<frametimeline::SurfaceFrame>));
- MOCK_METHOD3(setSfWakeUp, void(int64_t, nsecs_t, Fps));
+ MOCK_METHOD4(setSfWakeUp, void(int64_t, nsecs_t, Fps, Fps));
MOCK_METHOD3(setSfPresent,
void(nsecs_t, const std::shared_ptr<FenceTime>&,
const std::shared_ptr<FenceTime>&));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockLayer.h b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
index 0840a2f77c..4204aa0001 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockLayer.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockLayer.h
@@ -17,24 +17,35 @@
#pragma once
#include <gmock/gmock.h>
-
-#include "Layer.h"
+#include <optional>
namespace android::mock {
class MockLayer : public Layer {
public:
MockLayer(SurfaceFlinger* flinger, std::string name)
- : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {}
+ : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {})) {
+ EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
+ .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
+ }
+
+ MockLayer(SurfaceFlinger* flinger, std::string name, std::optional<uint32_t> uid)
+ : Layer(LayerCreationArgs(flinger, nullptr, std::move(name), 0, {}, uid)) {
+ EXPECT_CALL(*this, getDefaultFrameRateCompatibility())
+ .WillOnce(testing::Return(scheduler::FrameRateCompatibility::Default));
+ }
+
explicit MockLayer(SurfaceFlinger* flinger) : MockLayer(flinger, "TestLayer") {}
MOCK_CONST_METHOD0(getType, const char*());
MOCK_METHOD0(getFrameSelectionPriority, int32_t());
MOCK_CONST_METHOD0(isVisible, bool());
- MOCK_METHOD0(createClone, sp<Layer>());
+ MOCK_METHOD1(createClone, sp<Layer>(uint32_t));
MOCK_CONST_METHOD0(getFrameRateForLayerTree, FrameRate());
+ MOCK_CONST_METHOD0(getDefaultFrameRateCompatibility, scheduler::FrameRateCompatibility());
MOCK_CONST_METHOD0(getOwnerUid, uid_t());
MOCK_CONST_METHOD0(getDataSpace, ui::Dataspace());
+ MOCK_METHOD(bool, isFrontBuffered, (), (const, override));
};
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
index 52675869fa..22b2cccf03 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockSchedulerCallback.h
@@ -18,22 +18,24 @@
#include <gmock/gmock.h>
-#include "Scheduler/Scheduler.h"
+#include "Scheduler/ISchedulerCallback.h"
namespace android::scheduler::mock {
struct SchedulerCallback final : ISchedulerCallback {
- MOCK_METHOD(void, setVsyncEnabled, (bool), (override));
- MOCK_METHOD(void, requestDisplayMode, (DisplayModePtr, DisplayModeEvent), (override));
+ MOCK_METHOD(void, requestHardwareVsync, (PhysicalDisplayId, bool), (override));
+ MOCK_METHOD(void, requestDisplayModes, (std::vector<display::DisplayModeRequest>), (override));
MOCK_METHOD(void, kernelTimerChanged, (bool), (override));
MOCK_METHOD(void, triggerOnFrameRateOverridesChanged, (), (override));
+ MOCK_METHOD(void, onChoreographerAttached, (), (override));
};
struct NoOpSchedulerCallback final : ISchedulerCallback {
- void setVsyncEnabled(bool) override {}
- void requestDisplayMode(DisplayModePtr, DisplayModeEvent) override {}
+ void requestHardwareVsync(PhysicalDisplayId, bool) override {}
+ void requestDisplayModes(std::vector<display::DisplayModeRequest>) override {}
void kernelTimerChanged(bool) override {}
void triggerOnFrameRateOverridesChanged() override {}
+ void onChoreographerAttached() override {}
};
} // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp b/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp
deleted file mode 100644
index 0a0e7b5861..0000000000
--- a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.cpp
+++ /dev/null
@@ -1,32 +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"
-
-#include "mock/MockSurfaceInterceptor.h"
-
-namespace android::mock {
-
-// Explicit default instantiation is recommended.
-SurfaceInterceptor::SurfaceInterceptor() = default;
-SurfaceInterceptor::~SurfaceInterceptor() = default;
-
-} // namespace android::mock
-
-// TODO(b/129481165): remove the #pragma below and fix conversion issues
-#pragma clang diagnostic pop // ignored "-Wconversion"
diff --git a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h b/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h
deleted file mode 100644
index b085027397..0000000000
--- a/services/surfaceflinger/tests/unittests/mock/MockSurfaceInterceptor.h
+++ /dev/null
@@ -1,50 +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 <gmock/gmock.h>
-
-#include "SurfaceInterceptor.h"
-
-namespace android::mock {
-
-class SurfaceInterceptor : public android::SurfaceInterceptor {
-public:
- SurfaceInterceptor();
- ~SurfaceInterceptor() override;
-
- MOCK_METHOD2(enable,
- void(const SortedVector<sp<Layer>>&,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>&));
- MOCK_METHOD0(disable, void());
- MOCK_METHOD0(isEnabled, bool());
- MOCK_METHOD1(addTransactionTraceListener, void(const sp<gui::ITransactionTraceListener>&));
- MOCK_METHOD1(binderDied, void(const wp<IBinder>&));
- MOCK_METHOD7(saveTransaction,
- void(const Vector<ComposerState>&,
- const DefaultKeyedVector<wp<IBinder>, DisplayDeviceState>&,
- const Vector<DisplayState>&, uint32_t, int, int, uint64_t));
- MOCK_METHOD1(saveSurfaceCreation, void(const sp<const Layer>&));
- MOCK_METHOD1(saveSurfaceDeletion, void(const sp<const Layer>&));
- MOCK_METHOD4(saveBufferUpdate, void(int32_t, uint32_t, uint32_t, uint64_t));
- MOCK_METHOD1(saveDisplayCreation, void(const DisplayDeviceState&));
- MOCK_METHOD1(saveDisplayDeletion, void(int32_t));
- MOCK_METHOD2(savePowerModeUpdate, void(int32_t, int32_t));
- MOCK_METHOD1(saveVSyncEvent, void(nsecs_t));
-};
-
-} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
index 0dee800558..c82e45b5e8 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockTimeStats.h
@@ -27,14 +27,13 @@ public:
TimeStats();
~TimeStats() override;
- MOCK_METHOD2(onPullAtom, bool(const int, std::string*));
+ MOCK_METHOD2(onPullAtom, bool(const int, std::vector<uint8_t>*));
MOCK_METHOD3(parseArgs, void(bool, const Vector<String16>&, std::string&));
MOCK_METHOD0(isEnabled, bool());
MOCK_METHOD0(miniDump, std::string());
MOCK_METHOD0(incrementTotalFrames, void());
MOCK_METHOD0(incrementMissedFrames, void());
MOCK_METHOD0(incrementRefreshRateSwitches, void());
- MOCK_METHOD1(recordDisplayEventConnectionCount, void(int32_t));
MOCK_METHOD2(recordFrameDuration, void(nsecs_t, nsecs_t));
MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, nsecs_t));
MOCK_METHOD2(recordRenderEngineDuration, void(nsecs_t, const std::shared_ptr<FenceTime>&));
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp
new file mode 100644
index 0000000000..2817514db6
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.cpp
@@ -0,0 +1,25 @@
+/*
+ * 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 "mock/MockVSyncDispatch.h"
+
+namespace android::mock {
+
+// Explicit default instantiation is recommended.
+VSyncDispatch::VSyncDispatch() = default;
+VSyncDispatch::~VSyncDispatch() = default;
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
new file mode 100644
index 0000000000..dc32ff969c
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncDispatch.h
@@ -0,0 +1,38 @@
+/*
+ * 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 <gmock/gmock.h>
+
+#include "Scheduler/VSyncDispatch.h"
+
+namespace android::mock {
+
+class VSyncDispatch : public android::scheduler::VSyncDispatch {
+public:
+ VSyncDispatch();
+ ~VSyncDispatch() override;
+
+ MOCK_METHOD(CallbackToken, registerCallback, (Callback, std::string), (override));
+ MOCK_METHOD(void, unregisterCallback, (CallbackToken), (override));
+ MOCK_METHOD(scheduler::ScheduleResult, schedule, (CallbackToken, ScheduleTiming), (override));
+ MOCK_METHOD(scheduler::ScheduleResult, update, (CallbackToken, ScheduleTiming), (override));
+ MOCK_METHOD(scheduler::CancelResult, cancel, (CallbackToken token), (override));
+ MOCK_METHOD(void, dump, (std::string&), (const, override));
+};
+
+} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp
index bcccae5b1b..cc24f42d2e 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.cpp
@@ -17,9 +17,13 @@
#include "mock/MockVSyncTracker.h"
namespace android::mock {
+using testing::Return;
// Explicit default instantiation is recommended.
-VSyncTracker::VSyncTracker() = default;
VSyncTracker::~VSyncTracker() = default;
+VSyncTracker::VSyncTracker() {
+ ON_CALL(*this, minFramePeriod()).WillByDefault(Return(Period::fromNs(0)));
+}
+
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
index 5b0c1f38be..e588bb9a3f 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVSyncTracker.h
@@ -27,14 +27,18 @@ public:
VSyncTracker();
~VSyncTracker() override;
- MOCK_METHOD1(addVsyncTimestamp, bool(nsecs_t));
- MOCK_CONST_METHOD1(nextAnticipatedVSyncTimeFrom, nsecs_t(nsecs_t));
- MOCK_CONST_METHOD0(currentPeriod, nsecs_t());
- MOCK_METHOD1(setPeriod, void(nsecs_t));
- MOCK_METHOD0(resetModel, void());
- MOCK_CONST_METHOD0(needsMoreSamples, bool());
- MOCK_CONST_METHOD2(isVSyncInPhase, bool(nsecs_t, Fps));
- MOCK_CONST_METHOD1(dump, void(std::string&));
+ MOCK_METHOD(bool, addVsyncTimestamp, (nsecs_t), (override));
+ MOCK_METHOD(nsecs_t, nextAnticipatedVSyncTimeFrom, (nsecs_t), (const, override));
+ MOCK_METHOD(nsecs_t, currentPeriod, (), (const, override));
+ MOCK_METHOD(Period, minFramePeriod, (), (const, override));
+ MOCK_METHOD(void, resetModel, (), (override));
+ MOCK_METHOD(bool, needsMoreSamples, (), (const, override));
+ MOCK_METHOD(bool, isVSyncInPhase, (nsecs_t, Fps), (const, override));
+ MOCK_METHOD(void, setDisplayModePtr, (ftl::NonNull<DisplayModePtr>), (override));
+ MOCK_METHOD(void, setRenderRate, (Fps), (override));
+ MOCK_METHOD(void, onFrameBegin, (TimePoint, TimePoint), (override));
+ MOCK_METHOD(void, onFrameMissed, (TimePoint), (override));
+ MOCK_METHOD(void, dump, (std::string&), (const, override));
};
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
index 4ef91dacb2..f7433908b0 100644
--- a/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncController.h
@@ -28,12 +28,12 @@ public:
~VsyncController() override;
MOCK_METHOD(bool, addPresentFence, (std::shared_ptr<FenceTime>), (override));
- MOCK_METHOD3(addHwVsyncTimestamp, bool(nsecs_t, std::optional<nsecs_t>, bool*));
- MOCK_METHOD1(startPeriodTransition, void(nsecs_t));
- MOCK_METHOD1(setIgnorePresentFences, void(bool));
+ MOCK_METHOD(bool, addHwVsyncTimestamp, (nsecs_t, std::optional<nsecs_t>, bool*), (override));
+ MOCK_METHOD(void, onDisplayModeChanged, (ftl::NonNull<DisplayModePtr>, bool), (override));
+ MOCK_METHOD(void, setIgnorePresentFences, (bool), (override));
MOCK_METHOD(void, setDisplayPowerMode, (hal::PowerMode), (override));
- MOCK_CONST_METHOD1(dump, void(std::string&));
+ MOCK_METHOD(void, dump, (std::string&), (const, override));
};
} // namespace android::mock
diff --git a/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
new file mode 100644
index 0000000000..b48529f4ac
--- /dev/null
+++ b/services/surfaceflinger/tests/unittests/mock/MockVsyncTrackerCallback.h
@@ -0,0 +1,32 @@
+/*
+ * 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 <gmock/gmock.h>
+
+#include "Scheduler/VSyncTracker.h"
+
+namespace android::scheduler::mock {
+
+struct VsyncTrackerCallback final : IVsyncTrackerCallback {
+ MOCK_METHOD(void, onVsyncGenerated, (TimePoint, ftl::NonNull<DisplayModePtr>, Fps), (override));
+};
+
+struct NoOpVsyncTrackerCallback final : IVsyncTrackerCallback {
+ void onVsyncGenerated(TimePoint, ftl::NonNull<DisplayModePtr>, Fps) override{};
+};
+} // namespace android::scheduler::mock
diff --git a/services/surfaceflinger/tests/utils/ColorUtils.h b/services/surfaceflinger/tests/utils/ColorUtils.h
index 07916b60a7..38c422a26d 100644
--- a/services/surfaceflinger/tests/utils/ColorUtils.h
+++ b/services/surfaceflinger/tests/utils/ColorUtils.h
@@ -33,6 +33,10 @@ struct Color {
static const Color WHITE;
static const Color BLACK;
static const Color TRANSPARENT;
+
+ half3 toHalf3() { return half3{r / 255.0f, g / 255.0f, b / 255.0f}; }
+
+ half4 toHalf4() { return half4{r / 255.0f, g / 255.0f, b / 255.0f, a / 255.0f}; }
};
const Color Color::RED{255, 0, 0, 255};
@@ -81,6 +85,14 @@ public:
}
color = ret;
}
+
+ static half3 toHalf3(const Color& color) {
+ return half3{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f};
+ }
+
+ static half4 toHalf4(const Color& color) {
+ return half4{color.r / 255.0f, color.g / 255.0f, color.b / 255.0f, color.a / 255.0f};
+ }
};
} // namespace
} // namespace android
diff --git a/services/surfaceflinger/tests/utils/ScreenshotUtils.h b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
index f879430db0..1675584f5a 100644
--- a/services/surfaceflinger/tests/utils/ScreenshotUtils.h
+++ b/services/surfaceflinger/tests/utils/ScreenshotUtils.h
@@ -15,8 +15,10 @@
*/
#pragma once
+#include <gui/AidlStatusUtil.h>
#include <gui/SyncScreenCaptureListener.h>
#include <private/gui/ComposerServiceAIDL.h>
+#include <ui/FenceResult.h>
#include <ui/Rect.h>
#include <utils/String8.h>
#include <functional>
@@ -24,6 +26,8 @@
namespace android {
+using gui::aidl_utils::statusTFromBinderStatus;
+
namespace {
// A ScreenCapture is a screenshot from SurfaceFlinger that can be used to check
@@ -36,18 +40,22 @@ public:
SurfaceComposerClient::Transaction().apply(true);
captureArgs.dataspace = ui::Dataspace::V0_SRGB;
- const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
+ const sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
binder::Status status = sf->captureDisplay(captureArgs, captureListener);
-
- if (status.transactionError() != NO_ERROR) {
- return status.transactionError();
+ status_t err = statusTFromBinderStatus(status);
+ if (err != NO_ERROR) {
+ return err;
}
captureResults = captureListener->waitForResults();
- return captureResults.result;
+ return fenceStatus(captureResults.fenceResult);
}
static void captureScreen(std::unique_ptr<ScreenCapture>* sc) {
- captureScreen(sc, SurfaceComposerClient::getInternalDisplayToken());
+ const auto ids = SurfaceComposerClient::getPhysicalDisplayIds();
+ // TODO(b/248317436): extend to cover all displays for multi-display devices
+ const auto display =
+ ids.empty() ? nullptr : SurfaceComposerClient::getPhysicalDisplayToken(ids.front());
+ captureScreen(sc, display);
}
static void captureScreen(std::unique_ptr<ScreenCapture>* sc, sp<IBinder> displayToken) {
@@ -70,13 +78,14 @@ public:
SurfaceComposerClient::Transaction().apply(true);
captureArgs.dataspace = ui::Dataspace::V0_SRGB;
- const sp<SyncScreenCaptureListener> captureListener = new SyncScreenCaptureListener();
+ const sp<SyncScreenCaptureListener> captureListener = sp<SyncScreenCaptureListener>::make();
binder::Status status = sf->captureLayers(captureArgs, captureListener);
- if (status.transactionError() != NO_ERROR) {
- return status.transactionError();
+ status_t err = statusTFromBinderStatus(status);
+ if (err != NO_ERROR) {
+ return err;
}
captureResults = captureListener->waitForResults();
- return captureResults.result;
+ return fenceStatus(captureResults.fenceResult);
}
static void captureLayers(std::unique_ptr<ScreenCapture>* sc, LayerCaptureArgs& captureArgs) {
@@ -161,7 +170,7 @@ public:
String8 err(String8::format("pixel @ (%3d, %3d): "
"expected [%3d, %3d, %3d], got [%3d, %3d, %3d]",
x, y, r, g, b, pixel[0], pixel[1], pixel[2]));
- EXPECT_EQ(String8(), err) << err.string();
+ EXPECT_EQ(String8(), err) << err.c_str();
}
}
diff --git a/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
new file mode 100644
index 0000000000..11723c7509
--- /dev/null
+++ b/services/surfaceflinger/tests/utils/WindowInfosListenerUtils.h
@@ -0,0 +1,77 @@
+/*
+ * 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 <android-base/properties.h>
+#include <gtest/gtest.h>
+#include <gui/SurfaceComposerClient.h>
+#include <private/android_filesystem_config.h>
+#include <cstdint>
+#include <future>
+
+namespace android {
+
+using base::HwTimeoutMultiplier;
+using gui::DisplayInfo;
+using gui::WindowInfo;
+
+using WindowInfosPredicate = std::function<bool(const std::vector<WindowInfo>&)>;
+
+class WindowInfosListenerUtils {
+public:
+ WindowInfosListenerUtils() { mClient = sp<SurfaceComposerClient>::make(); }
+
+ bool waitForWindowInfosPredicate(const WindowInfosPredicate& predicate) {
+ std::promise<void> promise;
+ auto listener = sp<WindowInfosListener>::make(std::move(predicate), promise);
+ mClient->addWindowInfosListener(listener);
+ auto future = promise.get_future();
+ bool satisfied = future.wait_for(std::chrono::seconds{5 * HwTimeoutMultiplier()}) ==
+ std::future_status::ready;
+ mClient->removeWindowInfosListener(listener);
+ return satisfied;
+ }
+
+ static const WindowInfo* findMatchingWindowInfo(const WindowInfo& targetWindowInfo,
+ const std::vector<WindowInfo>& windowInfos) {
+ for (const WindowInfo& windowInfo : windowInfos) {
+ if (windowInfo.token == targetWindowInfo.token) {
+ return &windowInfo;
+ }
+ }
+ return nullptr;
+ }
+
+private:
+ struct WindowInfosListener : public gui::WindowInfosListener {
+ public:
+ WindowInfosListener(WindowInfosPredicate predicate, std::promise<void>& promise)
+ : mPredicate(std::move(predicate)), mPromise(promise) {}
+
+ void onWindowInfosChanged(const gui::WindowInfosUpdate& update) override {
+ if (mPredicate(update.windowInfos)) {
+ mPromise.set_value();
+ }
+ }
+
+ private:
+ WindowInfosPredicate mPredicate;
+ std::promise<void>& mPromise;
+ };
+
+ sp<SurfaceComposerClient> mClient;
+};
+
+} // namespace android
diff --git a/services/surfaceflinger/tests/vsync/vsync.cpp b/services/surfaceflinger/tests/vsync/vsync.cpp
index 667dfb92d5..8b4a6be1bf 100644
--- a/services/surfaceflinger/tests/vsync/vsync.cpp
+++ b/services/surfaceflinger/tests/vsync/vsync.cpp
@@ -18,7 +18,13 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wconversion"
+// 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 <android/looper.h>
+
#include <gui/DisplayEventReceiver.h>
#include <utils/Looper.h>
@@ -55,8 +61,7 @@ int main(int /*argc*/, char** /*argv*/)
{
DisplayEventReceiver myDisplayEvent;
-
- sp<Looper> loop = new Looper(false);
+ sp<Looper> loop = sp<Looper>::make(false);
loop->addFd(myDisplayEvent.getFd(), 0, ALOOPER_EVENT_INPUT, receiver,
&myDisplayEvent);
diff --git a/services/vibratorservice/Android.bp b/services/vibratorservice/Android.bp
index 5403baf2e7..2002bdf628 100644
--- a/services/vibratorservice/Android.bp
+++ b/services/vibratorservice/Android.bp
@@ -59,12 +59,6 @@ cc_library_shared {
"-Wunreachable-code",
],
- // FIXME: Workaround LTO build breakage
- // http://b/241699694
- lto: {
- never: true,
- },
-
local_include_dirs: ["include"],
export_include_dirs: ["include"],
diff --git a/services/vibratorservice/OWNERS b/services/vibratorservice/OWNERS
index d073e2bd46..031b333fab 100644
--- a/services/vibratorservice/OWNERS
+++ b/services/vibratorservice/OWNERS
@@ -1 +1,3 @@
+# Bug component: 345036
+
include platform/frameworks/base:/services/core/java/com/android/server/vibrator/OWNERS
diff --git a/services/vibratorservice/TEST_MAPPING b/services/vibratorservice/TEST_MAPPING
index b033adbd05..63a2bd0059 100644
--- a/services/vibratorservice/TEST_MAPPING
+++ b/services/vibratorservice/TEST_MAPPING
@@ -1,7 +1,23 @@
{
"presubmit": [
{
+ "name": "libvibratorservice_test",
+ "options": [
+ // TODO(b/293603710): Fix flakiness
+ {
+ "exclude-filter": "VibratorCallbackSchedulerTest#TestScheduleRunsOnlyAfterDelay"
+ }
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
"name": "libvibratorservice_test"
}
+ ],
+ "imports": [
+ {
+ "path": "cts/tests/vibrator"
+ }
]
}
diff --git a/services/vibratorservice/VibratorCallbackScheduler.cpp b/services/vibratorservice/VibratorCallbackScheduler.cpp
index f2870b021d..b2b1988d97 100644
--- a/services/vibratorservice/VibratorCallbackScheduler.cpp
+++ b/services/vibratorservice/VibratorCallbackScheduler.cpp
@@ -29,8 +29,11 @@ bool DelayedCallback::isExpired() const {
return mExpiration <= std::chrono::steady_clock::now();
}
-DelayedCallback::Timestamp DelayedCallback::getExpiration() const {
- return mExpiration;
+std::chrono::milliseconds DelayedCallback::getWaitForExpirationDuration() const {
+ std::chrono::milliseconds delta = std::chrono::duration_cast<std::chrono::milliseconds>(
+ mExpiration - std::chrono::steady_clock::now());
+ // Return zero if this is already expired.
+ return delta > delta.zero() ? delta : delta.zero();
}
void DelayedCallback::run() const {
@@ -84,11 +87,13 @@ void CallbackScheduler::loop() {
lock.lock();
}
if (mQueue.empty()) {
- // Wait until a new callback is scheduled.
- mCondition.wait(mMutex);
+ // Wait until a new callback is scheduled or destructor was called.
+ mCondition.wait(lock, [this] { return mFinished || !mQueue.empty(); });
} else {
- // Wait until next callback expires, or a new one is scheduled.
- mCondition.wait_until(mMutex, mQueue.top().getExpiration());
+ // Wait until next callback expires or a new one is scheduled.
+ // Use the monotonic steady clock to wait for the measured delay interval via wait_for
+ // instead of using a wall clock via wait_until.
+ mCondition.wait_for(lock, mQueue.top().getWaitForExpirationDuration());
}
}
}
diff --git a/services/vibratorservice/benchmarks/Android.bp b/services/vibratorservice/benchmarks/Android.bp
index a468146bc0..5437995899 100644
--- a/services/vibratorservice/benchmarks/Android.bp
+++ b/services/vibratorservice/benchmarks/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_haptics_framework",
// 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"
diff --git a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
index 53f3daf969..971a0b960e 100644
--- a/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
+++ b/services/vibratorservice/benchmarks/VibratorHalControllerBenchmarks.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "PowerHalControllerBenchmarks"
+#define LOG_TAG "VibratorHalControllerBenchmarks"
#include <benchmark/benchmark.h>
#include <vibratorservice/VibratorHalController.h>
@@ -183,7 +183,7 @@ BENCHMARK_WRAPPER(VibratorBench, setAmplitudeCached, {
return;
}
- auto duration = 6000s;
+ auto duration = 60s;
auto callback = []() {};
auto amplitude = 1.0f;
diff --git a/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h b/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
index 2c194b5526..c8ec15d414 100644
--- a/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
+++ b/services/vibratorservice/include/vibratorservice/VibratorCallbackScheduler.h
@@ -30,15 +30,13 @@ namespace vibrator {
// Wrapper for a callback to be executed after a delay.
class DelayedCallback {
public:
- using Timestamp = std::chrono::time_point<std::chrono::steady_clock>;
-
DelayedCallback(std::function<void()> callback, std::chrono::milliseconds delay)
: mCallback(callback), mExpiration(std::chrono::steady_clock::now() + delay) {}
~DelayedCallback() = default;
void run() const;
bool isExpired() const;
- Timestamp getExpiration() const;
+ std::chrono::milliseconds getWaitForExpirationDuration() const;
// Compare by expiration time, where A < B when A expires first.
bool operator<(const DelayedCallback& other) const;
@@ -46,7 +44,9 @@ public:
private:
std::function<void()> mCallback;
- Timestamp mExpiration;
+ // Use a steady monotonic clock to calculate the duration until expiration.
+ // This clock is not related to wall clock time and is most suitable for measuring intervals.
+ std::chrono::time_point<std::chrono::steady_clock> mExpiration;
};
// Schedules callbacks to be executed after a delay.
diff --git a/services/vibratorservice/test/Android.bp b/services/vibratorservice/test/Android.bp
index 32947246d8..be71dc2ce3 100644
--- a/services/vibratorservice/test/Android.bp
+++ b/services/vibratorservice/test/Android.bp
@@ -13,6 +13,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_haptics_framework",
// 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"
diff --git a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
index aaeb8f990a..881e32152c 100644
--- a/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
+++ b/services/vibratorservice/test/VibratorCallbackSchedulerTest.cpp
@@ -14,20 +14,13 @@
* limitations under the License.
*/
-#define LOG_TAG "VibratorHalWrapperAidlTest"
-
-#include <android-base/thread_annotations.h>
-#include <android/hardware/vibrator/IVibrator.h>
-#include <condition_variable>
-
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include <utils/Log.h>
-#include <thread>
-
#include <vibratorservice/VibratorCallbackScheduler.h>
+#include "test_utils.h"
+
using std::chrono::milliseconds;
using std::chrono::steady_clock;
using std::chrono::time_point;
@@ -38,27 +31,26 @@ using namespace testing;
// -------------------------------------------------------------------------------------------------
+// Delay allowed for the scheduler to process callbacks during this test.
+static const auto TEST_TIMEOUT = 100ms;
+
class VibratorCallbackSchedulerTest : public Test {
public:
- void SetUp() override {
- mScheduler = std::make_unique<vibrator::CallbackScheduler>();
- std::lock_guard<std::mutex> lock(mMutex);
- mExpiredCallbacks.clear();
- }
+ void SetUp() override { mScheduler = std::make_unique<vibrator::CallbackScheduler>(); }
protected:
std::mutex mMutex;
- std::condition_variable_any mCondition;
std::unique_ptr<vibrator::CallbackScheduler> mScheduler = nullptr;
+ vibrator::TestCounter mCallbackCounter;
std::vector<int32_t> mExpiredCallbacks GUARDED_BY(mMutex);
std::function<void()> createCallback(int32_t id) {
- return [=]() {
+ return [this, id]() {
{
std::lock_guard<std::mutex> lock(mMutex);
mExpiredCallbacks.push_back(id);
}
- mCondition.notify_all();
+ mCallbackCounter.increment();
};
}
@@ -67,58 +59,43 @@ protected:
return std::vector<int32_t>(mExpiredCallbacks);
}
- bool waitForCallbacks(uint32_t callbackCount, milliseconds timeout) {
- time_point<steady_clock> expiration = steady_clock::now() + timeout;
- while (steady_clock::now() < expiration) {
- std::lock_guard<std::mutex> lock(mMutex);
- if (callbackCount <= mExpiredCallbacks.size()) {
- return true;
- }
- mCondition.wait_until(mMutex, expiration);
- }
- return false;
+ int32_t waitForCallbacks(int32_t callbackCount, milliseconds timeout) {
+ mCallbackCounter.tryWaitUntilCountIsAtLeast(callbackCount, timeout);
+ return mCallbackCounter.get();
}
};
// -------------------------------------------------------------------------------------------------
TEST_F(VibratorCallbackSchedulerTest, TestScheduleRunsOnlyAfterDelay) {
- mScheduler->schedule(createCallback(1), 15ms);
+ auto callbackDuration = 50ms;
+ time_point<steady_clock> startTime = steady_clock::now();
+ mScheduler->schedule(createCallback(1), callbackDuration);
- // Not triggered before delay.
- ASSERT_FALSE(waitForCallbacks(1, 10ms));
- ASSERT_TRUE(getExpiredCallbacks().empty());
+ ASSERT_THAT(waitForCallbacks(1, callbackDuration + TEST_TIMEOUT), Eq(1));
+ time_point<steady_clock> callbackTime = steady_clock::now();
- ASSERT_TRUE(waitForCallbacks(1, 10ms));
- ASSERT_THAT(getExpiredCallbacks(), ElementsAre(1));
+ // Callback took at least the required duration to trigger.
+ ASSERT_THAT(callbackTime, Ge(startTime + callbackDuration));
}
TEST_F(VibratorCallbackSchedulerTest, TestScheduleMultipleCallbacksRunsInDelayOrder) {
- mScheduler->schedule(createCallback(1), 10ms);
- mScheduler->schedule(createCallback(2), 5ms);
- mScheduler->schedule(createCallback(3), 1ms);
+ // Schedule first callbacks long enough that all 3 will be scheduled together and run in order.
+ mScheduler->schedule(createCallback(1), 50ms + 2 * TEST_TIMEOUT);
+ mScheduler->schedule(createCallback(2), 50ms + TEST_TIMEOUT);
+ mScheduler->schedule(createCallback(3), 50ms);
- ASSERT_TRUE(waitForCallbacks(3, 15ms));
+ // Callbacks triggered in the expected order based on the requested durations.
+ ASSERT_THAT(waitForCallbacks(3, 50ms + 3 * TEST_TIMEOUT), Eq(3));
ASSERT_THAT(getExpiredCallbacks(), ElementsAre(3, 2, 1));
}
-TEST_F(VibratorCallbackSchedulerTest, TestScheduleInParallelRunsInDelayOrder) {
- std::vector<std::thread> threads;
- for (int i = 0; i < 5; i++) {
- threads.push_back(std::thread(
- [=]() { mScheduler->schedule(createCallback(i), milliseconds(10 + 2 * i)); }));
- }
- std::for_each(threads.begin(), threads.end(), [](std::thread& t) { t.join(); });
-
- ASSERT_TRUE(waitForCallbacks(5, 25ms));
- ASSERT_THAT(getExpiredCallbacks(), ElementsAre(0, 1, 2, 3, 4));
-}
-
TEST_F(VibratorCallbackSchedulerTest, TestDestructorDropsPendingCallbacksAndKillsThread) {
- mScheduler->schedule(createCallback(1), 5ms);
+ // Schedule callback long enough that scheduler will be destroyed while it's still scheduled.
+ mScheduler->schedule(createCallback(1), 100ms);
mScheduler.reset(nullptr);
- // Should time out waiting for callback to run.
- ASSERT_FALSE(waitForCallbacks(1, 10ms));
- ASSERT_TRUE(getExpiredCallbacks().empty());
+ // Should timeout waiting for callback to run.
+ ASSERT_THAT(waitForCallbacks(1, 100ms + TEST_TIMEOUT), Eq(0));
+ ASSERT_THAT(getExpiredCallbacks(), IsEmpty());
}
diff --git a/services/vibratorservice/test/test_utils.h b/services/vibratorservice/test/test_utils.h
index 1933a118ef..c08cfc6bfa 100644
--- a/services/vibratorservice/test/test_utils.h
+++ b/services/vibratorservice/test/test_utils.h
@@ -85,6 +85,34 @@ private:
~TestFactory() = delete;
};
+class TestCounter {
+public:
+ TestCounter(int32_t init = 0) : mMutex(), mCondVar(), mCount(init) {}
+
+ int32_t get() {
+ std::lock_guard<std::mutex> lock(mMutex);
+ return mCount;
+ }
+
+ void increment() {
+ {
+ std::lock_guard<std::mutex> lock(mMutex);
+ mCount += 1;
+ }
+ mCondVar.notify_all();
+ }
+
+ void tryWaitUntilCountIsAtLeast(int32_t count, std::chrono::milliseconds timeout) {
+ std::unique_lock<std::mutex> lock(mMutex);
+ mCondVar.wait_for(lock, timeout, [&] { return mCount >= count; });
+ }
+
+private:
+ std::mutex mMutex;
+ std::condition_variable mCondVar;
+ int32_t mCount GUARDED_BY(mMutex);
+};
+
// -------------------------------------------------------------------------------------------------
} // namespace vibrator
diff --git a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
index 523f890ce7..d0a9da1ff5 100644
--- a/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
+++ b/services/vr/virtual_touchpad/VirtualTouchpadService.cpp
@@ -113,7 +113,7 @@ status_t VirtualTouchpadService::dump(
static_cast<long>(client_pid_));
touchpad_->dumpInternal(result);
}
- write(fd, result.string(), result.size());
+ write(fd, result.c_str(), result.size());
return OK;
}
diff --git a/vulkan/include/vulkan/vk_android_native_buffer.h b/vulkan/include/vulkan/vk_android_native_buffer.h
index 40cf9fbd67..159b2d5305 100644
--- a/vulkan/include/vulkan/vk_android_native_buffer.h
+++ b/vulkan/include/vulkan/vk_android_native_buffer.h
@@ -55,7 +55,18 @@ extern "C" {
* This version of the extension is largely designed to clean up the mix of
* GrallocUsage and GrallocUsage2
*/
-#define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 9
+/*
+ * NOTE ON VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 10
+ *
+ * This version of the extension cleans up a bug introduced in version 9
+ */
+/*
+ * NOTE ON VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
+ *
+ * This version of the extension deprecates the last of grallocusage and
+ * extends VkNativeBufferANDROID to support passing AHardwareBuffer*
+ */
+#define VK_ANDROID_NATIVE_BUFFER_SPEC_VERSION 11
#define VK_ANDROID_NATIVE_BUFFER_EXTENSION_NAME "VK_ANDROID_native_buffer"
#define VK_ANDROID_NATIVE_BUFFER_ENUM(type, id) \
@@ -69,6 +80,8 @@ extern "C" {
VK_ANDROID_NATIVE_BUFFER_ENUM(VkStructureType, 2)
#define VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID \
VK_ANDROID_NATIVE_BUFFER_ENUM(VkStructureType, 3)
+#define VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID \
+ VK_ANDROID_NATIVE_BUFFER_ENUM(VkStructureType, 4)
/* clang-format off */
typedef enum VkSwapchainImageUsageFlagBitsANDROID {
@@ -99,6 +112,9 @@ typedef struct {
* usage: gralloc usage requested when the buffer was allocated
* usage2: gralloc usage requested when the buffer was allocated
* usage3: gralloc usage requested when the buffer was allocated
+ * ahb: The AHardwareBuffer* from the actual ANativeWindowBuffer. Caller
+ * maintains ownership of resource. AHardwareBuffer pointer is only valid
+ * for the duration of the function call
*/
typedef struct {
VkStructureType sType;
@@ -109,6 +125,7 @@ typedef struct {
int usage; /* DEPRECATED in SPEC_VERSION 6 */
VkNativeBufferUsage2ANDROID usage2; /* DEPRECATED in SPEC_VERSION 9 */
uint64_t usage3; /* ADDED in SPEC_VERSION 9 */
+ struct AHardwareBuffer* ahb; /* ADDED in SPEC_VERSION 11 */
} VkNativeBufferANDROID;
/*
@@ -144,6 +161,8 @@ typedef struct {
* pNext: NULL or a pointer to a structure extending this structure
* format: value specifying the format the image will be created with
* imageUsage: bitmask of VkImageUsageFlagBits describing intended usage
+ *
+ * DEPRECATED in SPEC_VERSION 10
*/
typedef struct {
VkStructureType sType;
@@ -152,6 +171,25 @@ typedef struct {
VkImageUsageFlags imageUsage;
} VkGrallocUsageInfoANDROID;
+/*
+ * struct VkGrallocUsageInfo2ANDROID
+ *
+ * sType: VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID
+ * pNext: NULL or a pointer to a structure extending this structure
+ * format: value specifying the format the image will be created with
+ * imageUsage: bitmask of VkImageUsageFlagBits describing intended usage
+ * swapchainImageUsage: is a bitmask of VkSwapchainImageUsageFlagsANDROID
+ *
+ * DEPRECATED in SPEC_VERSION 11
+ */
+typedef struct {
+ VkStructureType sType;
+ const void* pNext;
+ VkFormat format;
+ VkImageUsageFlags imageUsage;
+ VkSwapchainImageUsageFlagsANDROID swapchainImageUsage;
+} VkGrallocUsageInfo2ANDROID;
+
/* DEPRECATED in SPEC_VERSION 6 */
typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsageANDROID)(
VkDevice device,
@@ -168,12 +206,18 @@ typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsage2ANDROID)(
uint64_t* grallocConsumerUsage,
uint64_t* grallocProducerUsage);
-/* ADDED in SPEC_VERSION 9 */
+/* DEPRECATED in SPEC_VERSION 10 */
typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsage3ANDROID)(
VkDevice device,
const VkGrallocUsageInfoANDROID* grallocUsageInfo,
uint64_t* grallocUsage);
+/* DEPRECATED in SPEC_VERSION 11 */
+typedef VkResult (VKAPI_PTR *PFN_vkGetSwapchainGrallocUsage4ANDROID)(
+ VkDevice device,
+ const VkGrallocUsageInfo2ANDROID* grallocUsageInfo,
+ uint64_t* grallocUsage);
+
typedef VkResult (VKAPI_PTR *PFN_vkAcquireImageANDROID)(
VkDevice device,
VkImage image,
@@ -208,13 +252,20 @@ VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainGrallocUsage2ANDROID(
uint64_t* grallocProducerUsage
);
-/* ADDED in SPEC_VERSION 9 */
+/* DEPRECATED in SPEC_VERSION 10 */
VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainGrallocUsage3ANDROID(
VkDevice device,
const VkGrallocUsageInfoANDROID* grallocUsageInfo,
uint64_t* grallocUsage
);
+/* DEPRECATED in SPEC_VERSION 11 */
+VKAPI_ATTR VkResult VKAPI_CALL vkGetSwapchainGrallocUsage4ANDROID(
+ VkDevice device,
+ const VkGrallocUsageInfo2ANDROID* grallocUsageInfo,
+ uint64_t* grallocUsage
+);
+
VKAPI_ATTR VkResult VKAPI_CALL vkAcquireImageANDROID(
VkDevice device,
VkImage image,
diff --git a/vulkan/libvulkan/Android.bp b/vulkan/libvulkan/Android.bp
index a87f82fcbd..436e6c6b7c 100644
--- a/vulkan/libvulkan/Android.bp
+++ b/vulkan/libvulkan/Android.bp
@@ -109,6 +109,7 @@ cc_library_shared {
"libnativeloader_lazy",
"libnativewindow",
"libvndksupport",
+ "libdl_android",
"android.hardware.graphics.common@1.0",
"libSurfaceFlingerProp",
],
diff --git a/vulkan/libvulkan/driver.cpp b/vulkan/libvulkan/driver.cpp
index a99355f047..0e45d2d8c4 100644
--- a/vulkan/libvulkan/driver.cpp
+++ b/vulkan/libvulkan/driver.cpp
@@ -46,6 +46,8 @@
using namespace android::hardware::configstore;
using namespace android::hardware::configstore::V1_0;
+extern "C" android_namespace_t* android_get_exported_namespace(const char*);
+
// #define ENABLE_ALLOC_CALLSTACKS 1
#if ENABLE_ALLOC_CALLSTACKS
#include <utils/CallStack.h>
@@ -159,6 +161,7 @@ const std::array<const char*, 2> HAL_SUBNAME_KEY_PROPERTIES = {{
"ro.board.platform",
}};
constexpr int LIB_DL_FLAGS = RTLD_LOCAL | RTLD_NOW;
+constexpr char RO_VULKAN_APEX_PROPERTY[] = "ro.vulkan.apex";
// LoadDriver returns:
// * 0 when succeed, or
@@ -166,6 +169,7 @@ constexpr int LIB_DL_FLAGS = RTLD_LOCAL | RTLD_NOW;
// * -EINVAL when fail to find HAL_MODULE_INFO_SYM_AS_STR or
// HWVULKAN_HARDWARE_MODULE_ID in the library.
int LoadDriver(android_namespace_t* library_namespace,
+ const char* ns_name,
const hwvulkan_module_t** module) {
ATRACE_CALL();
@@ -183,8 +187,10 @@ int LoadDriver(android_namespace_t* library_namespace,
.library_namespace = library_namespace,
};
so = android_dlopen_ext(lib_name.c_str(), LIB_DL_FLAGS, &dlextinfo);
- ALOGE("Could not load %s from updatable gfx driver namespace: %s.",
- lib_name.c_str(), dlerror());
+ if (!so) {
+ ALOGE("Could not load %s from %s namespace: %s.",
+ lib_name.c_str(), ns_name, dlerror());
+ }
} else {
// load built-in driver
so = android_load_sphal_library(lib_name.c_str(), LIB_DL_FLAGS);
@@ -211,12 +217,30 @@ int LoadDriver(android_namespace_t* library_namespace,
return 0;
}
+int LoadDriverFromApex(const hwvulkan_module_t** module) {
+ ATRACE_CALL();
+
+ auto apex_name = android::base::GetProperty(RO_VULKAN_APEX_PROPERTY, "");
+ if (apex_name == "") {
+ return -ENOENT;
+ }
+ // Get linker namespace for Vulkan APEX
+ std::replace(apex_name.begin(), apex_name.end(), '.', '_');
+ auto ns = android_get_exported_namespace(apex_name.c_str());
+ if (!ns) {
+ return -ENOENT;
+ }
+ android::GraphicsEnv::getInstance().setDriverToLoad(
+ android::GpuStatsInfo::Driver::VULKAN);
+ return LoadDriver(ns, apex_name.c_str(), module);
+}
+
int LoadBuiltinDriver(const hwvulkan_module_t** module) {
ATRACE_CALL();
android::GraphicsEnv::getInstance().setDriverToLoad(
android::GpuStatsInfo::Driver::VULKAN);
- return LoadDriver(nullptr, module);
+ return LoadDriver(nullptr, nullptr, module);
}
int LoadUpdatedDriver(const hwvulkan_module_t** module) {
@@ -227,7 +251,7 @@ int LoadUpdatedDriver(const hwvulkan_module_t** module) {
return -ENOENT;
android::GraphicsEnv::getInstance().setDriverToLoad(
android::GpuStatsInfo::Driver::VULKAN_UPDATED);
- int result = LoadDriver(ns, module);
+ int result = LoadDriver(ns, "updatable gfx driver", module);
if (result != 0) {
LOG_ALWAYS_FATAL(
"couldn't find an updated Vulkan implementation from %s",
@@ -256,6 +280,9 @@ bool Hal::Open() {
result = LoadUpdatedDriver(&module);
if (result == -ENOENT) {
+ result = LoadDriverFromApex(&module);
+ }
+ if (result == -ENOENT) {
result = LoadBuiltinDriver(&module);
}
if (result != 0) {
@@ -636,6 +663,7 @@ void CreateInfoWrapper::FilterExtension(const char* name) {
case ProcHook::EXT_swapchain_colorspace:
case ProcHook::KHR_get_surface_capabilities2:
case ProcHook::GOOGLE_surfaceless_query:
+ case ProcHook::EXT_surface_maintenance1:
hook_extensions_.set(ext_bit);
// return now as these extensions do not require HAL support
return;
@@ -657,9 +685,11 @@ void CreateInfoWrapper::FilterExtension(const char* name) {
case ProcHook::KHR_shared_presentable_image:
case ProcHook::KHR_swapchain:
case ProcHook::EXT_hdr_metadata:
+ case ProcHook::EXT_swapchain_maintenance1:
case ProcHook::ANDROID_external_memory_android_hardware_buffer:
case ProcHook::ANDROID_native_buffer:
case ProcHook::GOOGLE_display_timing:
+ case ProcHook::KHR_external_fence_fd:
case ProcHook::EXTENSION_CORE_1_0:
case ProcHook::EXTENSION_CORE_1_1:
case ProcHook::EXTENSION_CORE_1_2:
@@ -690,16 +720,22 @@ void CreateInfoWrapper::FilterExtension(const char* name) {
ext_bit = ProcHook::ANDROID_native_buffer;
break;
case ProcHook::KHR_incremental_present:
- case ProcHook::GOOGLE_display_timing:
case ProcHook::KHR_shared_presentable_image:
+ case ProcHook::GOOGLE_display_timing:
hook_extensions_.set(ext_bit);
// return now as these extensions do not require HAL support
return;
+ case ProcHook::EXT_swapchain_maintenance1:
+ // map VK_KHR_swapchain_maintenance1 to KHR_external_fence_fd
+ name = VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME;
+ ext_bit = ProcHook::KHR_external_fence_fd;
+ break;
case ProcHook::EXT_hdr_metadata:
case ProcHook::KHR_bind_memory2:
hook_extensions_.set(ext_bit);
break;
case ProcHook::ANDROID_external_memory_android_hardware_buffer:
+ case ProcHook::KHR_external_fence_fd:
case ProcHook::EXTENSION_UNKNOWN:
// Extensions we don't need to do anything about at this level
break;
@@ -715,6 +751,7 @@ void CreateInfoWrapper::FilterExtension(const char* name) {
case ProcHook::KHR_surface_protected_capabilities:
case ProcHook::EXT_debug_report:
case ProcHook::EXT_swapchain_colorspace:
+ case ProcHook::EXT_surface_maintenance1:
case ProcHook::GOOGLE_surfaceless_query:
case ProcHook::ANDROID_native_buffer:
case ProcHook::EXTENSION_CORE_1_0:
@@ -747,10 +784,29 @@ void CreateInfoWrapper::FilterExtension(const char* name) {
if (strcmp(name, props.extensionName) != 0)
continue;
+ if (ext_bit != ProcHook::EXTENSION_UNKNOWN &&
+ hal_extensions_.test(ext_bit)) {
+ ALOGI("CreateInfoWrapper::FilterExtension: already have '%s'.", name);
+ continue;
+ }
+
+ // Ignore duplicate extensions (see: b/288929054)
+ bool duplicate_entry = false;
+ for (uint32_t j = 0; j < filter.name_count; j++) {
+ if (strcmp(name, filter.names[j]) == 0) {
+ duplicate_entry = true;
+ break;
+ }
+ }
+ if (duplicate_entry == true)
+ continue;
+
filter.names[filter.name_count++] = name;
if (ext_bit != ProcHook::EXTENSION_UNKNOWN) {
if (ext_bit == ProcHook::ANDROID_native_buffer)
hook_extensions_.set(ProcHook::KHR_swapchain);
+ if (ext_bit == ProcHook::KHR_external_fence_fd)
+ hook_extensions_.set(ProcHook::EXT_swapchain_maintenance1);
hal_extensions_.set(ext_bit);
}
@@ -940,6 +996,9 @@ VkResult EnumerateInstanceExtensionProperties(
VK_KHR_GET_SURFACE_CAPABILITIES_2_SPEC_VERSION});
loader_extensions.push_back({VK_GOOGLE_SURFACELESS_QUERY_EXTENSION_NAME,
VK_GOOGLE_SURFACELESS_QUERY_SPEC_VERSION});
+ loader_extensions.push_back({
+ VK_EXT_SURFACE_MAINTENANCE_1_EXTENSION_NAME,
+ VK_EXT_SURFACE_MAINTENANCE_1_SPEC_VERSION});
static const VkExtensionProperties loader_debug_report_extension = {
VK_EXT_DEBUG_REPORT_EXTENSION_NAME, VK_EXT_DEBUG_REPORT_SPEC_VERSION,
@@ -1072,6 +1131,33 @@ VkResult GetAndroidNativeBufferSpecVersion9Support(
return result;
}
+bool CanSupportSwapchainMaintenance1Extension(VkPhysicalDevice physicalDevice) {
+ const auto& driver = GetData(physicalDevice).driver;
+ if (!driver.GetPhysicalDeviceExternalFenceProperties)
+ return false;
+
+ // Requires support for external fences imported from sync fds.
+ // This is _almost_ universal on Android, but may be missing on
+ // some extremely old drivers, or on strange implementations like
+ // cuttlefish.
+ VkPhysicalDeviceExternalFenceInfo fenceInfo = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_FENCE_INFO,
+ nullptr,
+ VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT
+ };
+ VkExternalFenceProperties fenceProperties = {
+ VK_STRUCTURE_TYPE_EXTERNAL_FENCE_PROPERTIES,
+ nullptr,
+ 0, 0, 0
+ };
+
+ GetPhysicalDeviceExternalFenceProperties(physicalDevice, &fenceInfo, &fenceProperties);
+ if (fenceProperties.externalFenceFeatures & VK_EXTERNAL_FENCE_FEATURE_IMPORTABLE_BIT)
+ return true;
+
+ return false;
+}
+
VkResult EnumerateDeviceExtensionProperties(
VkPhysicalDevice physicalDevice,
const char* pLayerName,
@@ -1149,6 +1235,12 @@ VkResult EnumerateDeviceExtensionProperties(
VK_EXT_IMAGE_COMPRESSION_CONTROL_SWAPCHAIN_SPEC_VERSION});
}
+ if (CanSupportSwapchainMaintenance1Extension(physicalDevice)) {
+ loader_extensions.push_back({
+ VK_EXT_SWAPCHAIN_MAINTENANCE_1_EXTENSION_NAME,
+ VK_EXT_SWAPCHAIN_MAINTENANCE_1_SPEC_VERSION});
+ }
+
// enumerate our extensions first
if (!pLayerName && pProperties) {
uint32_t count = std::min(
@@ -1266,6 +1358,27 @@ VkResult CreateInstance(const VkInstanceCreateInfo* pCreateInfo,
return VK_ERROR_INCOMPATIBLE_DRIVER;
}
+ // TODO(b/259516419) avoid getting stats from hwui
+ // const bool reportStats = (pCreateInfo->pApplicationInfo == nullptr )
+ // || (strcmp("android framework",
+ // pCreateInfo->pApplicationInfo->pEngineName) != 0);
+ const bool reportStats = true;
+ if (reportStats) {
+ // Set stats for Vulkan api version requested with application info
+ if (pCreateInfo->pApplicationInfo) {
+ const uint32_t vulkanApiVersion =
+ pCreateInfo->pApplicationInfo->apiVersion;
+ android::GraphicsEnv::getInstance().setTargetStats(
+ android::GpuStatsInfo::Stats::CREATED_VULKAN_API_VERSION,
+ vulkanApiVersion);
+ }
+
+ // Update stats for the extensions requested
+ android::GraphicsEnv::getInstance().setVulkanInstanceExtensions(
+ pCreateInfo->enabledExtensionCount,
+ pCreateInfo->ppEnabledExtensionNames);
+ }
+
*pInstance = instance;
return VK_SUCCESS;
@@ -1347,13 +1460,15 @@ VkResult CreateDevice(VkPhysicalDevice physicalDevice,
if ((wrapper.GetHalExtensions()[ProcHook::ANDROID_native_buffer]) &&
!data->driver.GetSwapchainGrallocUsageANDROID &&
!data->driver.GetSwapchainGrallocUsage2ANDROID &&
- !data->driver.GetSwapchainGrallocUsage3ANDROID) {
+ !data->driver.GetSwapchainGrallocUsage3ANDROID &&
+ !data->driver.GetSwapchainGrallocUsage4ANDROID) {
ALOGE(
"Driver's implementation of ANDROID_native_buffer is broken;"
" must expose at least one of "
"vkGetSwapchainGrallocUsageANDROID or "
"vkGetSwapchainGrallocUsage2ANDROID or "
- "vkGetSwapchainGrallocUsage3ANDROID");
+ "vkGetSwapchainGrallocUsage3ANDROID or "
+ "vkGetSwapchainGrallocUsage4ANDROID");
data->driver.DestroyDevice(dev, pAllocator);
FreeDeviceData(data, data_allocator);
@@ -1368,9 +1483,69 @@ VkResult CreateDevice(VkPhysicalDevice physicalDevice,
}
data->driver_device = dev;
+ data->driver_physical_device = physicalDevice;
*pDevice = dev;
+ // TODO(b/259516419) avoid getting stats from hwui
+ const bool reportStats = true;
+ if (reportStats) {
+ android::GraphicsEnv::getInstance().setTargetStats(
+ android::GpuStatsInfo::Stats::CREATED_VULKAN_DEVICE);
+
+ // Set stats for creating a Vulkan device and report features in use
+ const VkPhysicalDeviceFeatures* pEnabledFeatures =
+ pCreateInfo->pEnabledFeatures;
+ if (!pEnabledFeatures) {
+ // Use features from the chained VkPhysicalDeviceFeatures2
+ // structure, if given
+ const VkPhysicalDeviceFeatures2* features2 =
+ reinterpret_cast<const VkPhysicalDeviceFeatures2*>(
+ pCreateInfo->pNext);
+ while (features2 &&
+ features2->sType !=
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2) {
+ features2 = reinterpret_cast<const VkPhysicalDeviceFeatures2*>(
+ features2->pNext);
+ }
+ if (features2) {
+ pEnabledFeatures = &features2->features;
+ }
+ }
+ const VkBool32* pFeatures =
+ reinterpret_cast<const VkBool32*>(pEnabledFeatures);
+ if (pFeatures) {
+ // VkPhysicalDeviceFeatures consists of VkBool32 values, go over all
+ // of them using pointer arithmetic here and save the features in a
+ // 64-bit bitfield
+ static_assert(
+ (sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32)) <= 64,
+ "VkPhysicalDeviceFeatures has too many elements for bitfield "
+ "packing");
+ static_assert(
+ (sizeof(VkPhysicalDeviceFeatures) % sizeof(VkBool32)) == 0,
+ "VkPhysicalDeviceFeatures has invalid size for bitfield "
+ "packing");
+ const int numFeatures =
+ sizeof(VkPhysicalDeviceFeatures) / sizeof(VkBool32);
+
+ uint64_t enableFeatureBits = 0;
+ for (int i = 0; i < numFeatures; i++) {
+ if (pFeatures[i] != VK_FALSE) {
+ enableFeatureBits |= (uint64_t(1) << i);
+ }
+ }
+ android::GraphicsEnv::getInstance().setTargetStats(
+ android::GpuStatsInfo::Stats::VULKAN_DEVICE_FEATURES_ENABLED,
+ enableFeatureBits);
+ }
+
+ // Update stats for the extensions requested
+ android::GraphicsEnv::getInstance().setVulkanDeviceExtensions(
+ pCreateInfo->enabledExtensionCount,
+ pCreateInfo->ppEnabledExtensionNames);
+ }
+
return VK_SUCCESS;
}
@@ -1564,6 +1739,12 @@ void GetPhysicalDeviceFeatures2(VkPhysicalDevice physicalDevice,
imageCompressionControlSwapchainInChain = true;
} break;
+ case VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SWAPCHAIN_MAINTENANCE_1_FEATURES_EXT: {
+ auto smf = reinterpret_cast<VkPhysicalDeviceSwapchainMaintenance1FeaturesEXT *>(
+ pFeats);
+ smf->swapchainMaintenance1 = true;
+ } break;
+
default:
break;
}
diff --git a/vulkan/libvulkan/driver.h b/vulkan/libvulkan/driver.h
index 4d2bbd69e3..4b855e5999 100644
--- a/vulkan/libvulkan/driver.h
+++ b/vulkan/libvulkan/driver.h
@@ -98,6 +98,7 @@ struct DeviceData {
VkDevice driver_device;
DeviceDriverTable driver;
+ VkPhysicalDevice driver_physical_device;
};
bool OpenHAL();
diff --git a/vulkan/libvulkan/driver_gen.cpp b/vulkan/libvulkan/driver_gen.cpp
index de98aa7e61..8f090083f8 100644
--- a/vulkan/libvulkan/driver_gen.cpp
+++ b/vulkan/libvulkan/driver_gen.cpp
@@ -162,6 +162,15 @@ VKAPI_ATTR void checkedGetDeviceQueue2(VkDevice device, const VkDeviceQueueInfo2
}
}
+VKAPI_ATTR VkResult checkedReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) {
+ if (GetData(device).hook_extensions[ProcHook::EXT_swapchain_maintenance1]) {
+ return ReleaseSwapchainImagesEXT(device, pReleaseInfo);
+ } else {
+ Logger(device).Err(device, "VK_EXT_swapchain_maintenance1 not enabled. vkReleaseSwapchainImagesEXT not executed.");
+ return VK_SUCCESS;
+ }
+}
+
// clang-format on
const ProcHook g_proc_hooks[] = {
@@ -503,6 +512,13 @@ const ProcHook g_proc_hooks[] = {
nullptr,
},
{
+ "vkGetSwapchainGrallocUsage4ANDROID",
+ ProcHook::DEVICE,
+ ProcHook::ANDROID_native_buffer,
+ nullptr,
+ nullptr,
+ },
+ {
"vkGetSwapchainGrallocUsageANDROID",
ProcHook::DEVICE,
ProcHook::ANDROID_native_buffer,
@@ -545,6 +561,13 @@ const ProcHook g_proc_hooks[] = {
nullptr,
},
{
+ "vkReleaseSwapchainImagesEXT",
+ ProcHook::DEVICE,
+ ProcHook::EXT_swapchain_maintenance1,
+ reinterpret_cast<PFN_vkVoidFunction>(ReleaseSwapchainImagesEXT),
+ reinterpret_cast<PFN_vkVoidFunction>(checkedReleaseSwapchainImagesEXT),
+ },
+ {
"vkSetHdrMetadataEXT",
ProcHook::DEVICE,
ProcHook::EXT_hdr_metadata,
@@ -580,6 +603,8 @@ ProcHook::Extension GetProcHookExtension(const char* name) {
if (strcmp(name, "VK_KHR_surface") == 0) return ProcHook::KHR_surface;
if (strcmp(name, "VK_KHR_surface_protected_capabilities") == 0) return ProcHook::KHR_surface_protected_capabilities;
if (strcmp(name, "VK_KHR_swapchain") == 0) return ProcHook::KHR_swapchain;
+ if (strcmp(name, "VK_EXT_swapchain_maintenance1") == 0) return ProcHook::EXT_swapchain_maintenance1;
+ if (strcmp(name, "VK_EXT_surface_maintenance1") == 0) return ProcHook::EXT_surface_maintenance1;
if (strcmp(name, "VK_ANDROID_external_memory_android_hardware_buffer") == 0) return ProcHook::ANDROID_external_memory_android_hardware_buffer;
if (strcmp(name, "VK_KHR_bind_memory2") == 0) return ProcHook::KHR_bind_memory2;
if (strcmp(name, "VK_KHR_get_physical_device_properties2") == 0) return ProcHook::KHR_get_physical_device_properties2;
@@ -587,6 +612,7 @@ ProcHook::Extension GetProcHookExtension(const char* name) {
if (strcmp(name, "VK_KHR_external_memory_capabilities") == 0) return ProcHook::KHR_external_memory_capabilities;
if (strcmp(name, "VK_KHR_external_semaphore_capabilities") == 0) return ProcHook::KHR_external_semaphore_capabilities;
if (strcmp(name, "VK_KHR_external_fence_capabilities") == 0) return ProcHook::KHR_external_fence_capabilities;
+ if (strcmp(name, "VK_KHR_external_fence_fd") == 0) return ProcHook::KHR_external_fence_fd;
// clang-format on
return ProcHook::EXTENSION_UNKNOWN;
}
@@ -666,12 +692,14 @@ bool InitDriverTable(VkDevice dev,
INIT_PROC(true, dev, CreateImage);
INIT_PROC(true, dev, DestroyImage);
INIT_PROC(true, dev, AllocateCommandBuffers);
+ INIT_PROC_EXT(KHR_external_fence_fd, true, dev, ImportFenceFdKHR);
INIT_PROC(false, dev, BindImageMemory2);
INIT_PROC_EXT(KHR_bind_memory2, true, dev, BindImageMemory2KHR);
INIT_PROC(false, dev, GetDeviceQueue2);
INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsageANDROID);
INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsage2ANDROID);
INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsage3ANDROID);
+ INIT_PROC_EXT(ANDROID_native_buffer, false, dev, GetSwapchainGrallocUsage4ANDROID);
INIT_PROC_EXT(ANDROID_native_buffer, true, dev, AcquireImageANDROID);
INIT_PROC_EXT(ANDROID_native_buffer, true, dev, QueueSignalReleaseImageANDROID);
// clang-format on
diff --git a/vulkan/libvulkan/driver_gen.h b/vulkan/libvulkan/driver_gen.h
index 2f60086a27..4527214c3f 100644
--- a/vulkan/libvulkan/driver_gen.h
+++ b/vulkan/libvulkan/driver_gen.h
@@ -49,6 +49,8 @@ struct ProcHook {
KHR_surface,
KHR_surface_protected_capabilities,
KHR_swapchain,
+ EXT_swapchain_maintenance1,
+ EXT_surface_maintenance1,
ANDROID_external_memory_android_hardware_buffer,
KHR_bind_memory2,
KHR_get_physical_device_properties2,
@@ -56,6 +58,7 @@ struct ProcHook {
KHR_external_memory_capabilities,
KHR_external_semaphore_capabilities,
KHR_external_fence_capabilities,
+ KHR_external_fence_fd,
EXTENSION_CORE_1_0,
EXTENSION_CORE_1_1,
@@ -118,12 +121,14 @@ struct DeviceDriverTable {
PFN_vkCreateImage CreateImage;
PFN_vkDestroyImage DestroyImage;
PFN_vkAllocateCommandBuffers AllocateCommandBuffers;
+ PFN_vkImportFenceFdKHR ImportFenceFdKHR;
PFN_vkBindImageMemory2 BindImageMemory2;
PFN_vkBindImageMemory2KHR BindImageMemory2KHR;
PFN_vkGetDeviceQueue2 GetDeviceQueue2;
PFN_vkGetSwapchainGrallocUsageANDROID GetSwapchainGrallocUsageANDROID;
PFN_vkGetSwapchainGrallocUsage2ANDROID GetSwapchainGrallocUsage2ANDROID;
PFN_vkGetSwapchainGrallocUsage3ANDROID GetSwapchainGrallocUsage3ANDROID;
+ PFN_vkGetSwapchainGrallocUsage4ANDROID GetSwapchainGrallocUsage4ANDROID;
PFN_vkAcquireImageANDROID AcquireImageANDROID;
PFN_vkQueueSignalReleaseImageANDROID QueueSignalReleaseImageANDROID;
// clang-format on
diff --git a/vulkan/libvulkan/layers_extensions.cpp b/vulkan/libvulkan/layers_extensions.cpp
index a14fed222a..c073579d3d 100644
--- a/vulkan/libvulkan/layers_extensions.cpp
+++ b/vulkan/libvulkan/layers_extensions.cpp
@@ -23,6 +23,7 @@
#include <dlfcn.h>
#include <string.h>
#include <sys/prctl.h>
+#include <unistd.h>
#include <mutex>
#include <string>
@@ -73,6 +74,7 @@ class LayerLibrary {
filename_(filename),
dlhandle_(nullptr),
native_bridge_(false),
+ opened_with_native_loader_(false),
refcount_(0) {}
LayerLibrary(LayerLibrary&& other) noexcept
@@ -80,6 +82,7 @@ class LayerLibrary {
filename_(std::move(other.filename_)),
dlhandle_(other.dlhandle_),
native_bridge_(other.native_bridge_),
+ opened_with_native_loader_(other.opened_with_native_loader_),
refcount_(other.refcount_) {
other.dlhandle_ = nullptr;
other.refcount_ = 0;
@@ -119,6 +122,7 @@ class LayerLibrary {
std::mutex mutex_;
void* dlhandle_;
bool native_bridge_;
+ bool opened_with_native_loader_;
size_t refcount_;
};
@@ -135,7 +139,7 @@ bool LayerLibrary::Open() {
if (app_namespace &&
!android::base::StartsWith(path_, kSystemLayerLibraryDir)) {
char* error_msg = nullptr;
- dlhandle_ = OpenNativeLibraryInNamespace(
+ dlhandle_ = android::OpenNativeLibraryInNamespace(
app_namespace, path_.c_str(), &native_bridge_, &error_msg);
if (!dlhandle_) {
ALOGE("failed to load layer library '%s': %s", path_.c_str(), error_msg);
@@ -143,14 +147,16 @@ bool LayerLibrary::Open() {
refcount_ = 0;
return false;
}
+ opened_with_native_loader_ = true;
} else {
- dlhandle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL);
+ dlhandle_ = dlopen(path_.c_str(), RTLD_NOW | RTLD_LOCAL);
if (!dlhandle_) {
ALOGE("failed to load layer library '%s': %s", path_.c_str(),
dlerror());
refcount_ = 0;
return false;
}
+ opened_with_native_loader_ = false;
}
}
return true;
@@ -160,14 +166,25 @@ void LayerLibrary::Close() {
std::lock_guard<std::mutex> lock(mutex_);
if (--refcount_ == 0) {
ALOGV("closing layer library '%s'", path_.c_str());
- char* error_msg = nullptr;
- if (!android::CloseNativeLibrary(dlhandle_, native_bridge_, &error_msg)) {
- ALOGE("failed to unload library '%s': %s", path_.c_str(), error_msg);
- android::NativeLoaderFreeErrorMessage(error_msg);
- refcount_++;
+ // we close the .so same way as we opened. It's importain, because
+ // android::CloseNativeLibrary lives in libnativeloader.so, which is
+ // not accessible for early loaded services like SurfaceFlinger
+ if (opened_with_native_loader_) {
+ char* error_msg = nullptr;
+ if (!android::CloseNativeLibrary(dlhandle_, native_bridge_, &error_msg)) {
+ ALOGE("failed to unload library '%s': %s", path_.c_str(), error_msg);
+ android::NativeLoaderFreeErrorMessage(error_msg);
+ refcount_++;
+ return;
+ }
} else {
- dlhandle_ = nullptr;
+ if (dlclose(dlhandle_) != 0) {
+ ALOGE("failed to unload library '%s': %s", path_.c_str(), dlerror());
+ refcount_++;
+ return;
+ }
}
+ dlhandle_ = nullptr;
}
}
@@ -362,6 +379,7 @@ template <typename Functor>
void ForEachFileInZip(const std::string& zipname,
const std::string& dir_in_zip,
Functor functor) {
+ static const size_t kPageSize = getpagesize();
int32_t err;
ZipArchiveHandle zip = nullptr;
if ((err = OpenArchive(zipname.c_str(), &zip)) != 0) {
@@ -389,7 +407,7 @@ void ForEachFileInZip(const std::string& zipname,
// the APK. Loading still may fail for other reasons, but this at least
// lets us avoid failed-to-load log messages in the typical case of
// compressed and/or unaligned libraries.
- if (entry.method != kCompressStored || entry.offset % PAGE_SIZE != 0)
+ if (entry.method != kCompressStored || entry.offset % kPageSize != 0)
continue;
functor(filename);
}
diff --git a/vulkan/libvulkan/swapchain.cpp b/vulkan/libvulkan/swapchain.cpp
index e9935e5781..0df5e77181 100644
--- a/vulkan/libvulkan/swapchain.cpp
+++ b/vulkan/libvulkan/swapchain.cpp
@@ -16,7 +16,10 @@
#define ATRACE_TAG ATRACE_TAG_GRAPHICS
+#include <aidl/android/hardware/graphics/common/Dataspace.h>
+#include <aidl/android/hardware/graphics/common/PixelFormat.h>
#include <android/hardware/graphics/common/1.0/types.h>
+#include <android/hardware_buffer.h>
#include <grallocusage/GrallocUsageConversion.h>
#include <graphicsenv/GraphicsEnv.h>
#include <hardware/gralloc.h>
@@ -25,8 +28,6 @@
#include <sync/sync.h>
#include <system/window.h>
#include <ui/BufferQueueDefs.h>
-#include <ui/DebugUtils.h>
-#include <ui/PixelFormat.h>
#include <utils/StrongPointer.h>
#include <utils/Timers.h>
#include <utils/Trace.h>
@@ -37,6 +38,8 @@
#include "driver.h"
+using PixelFormat = aidl::android::hardware::graphics::common::PixelFormat;
+using DataSpace = aidl::android::hardware::graphics::common::Dataspace;
using android::hardware::graphics::common::V1_0::BufferUsage;
namespace vulkan {
@@ -227,6 +230,10 @@ struct Surface {
android::sp<ANativeWindow> window;
VkSwapchainKHR swapchain_handle;
uint64_t consumer_usage;
+
+ // Indicate whether this surface has been used by a swapchain, no matter the
+ // swapchain is still current or has been destroyed.
+ bool used_by_swapchain;
};
VkSurfaceKHR HandleFromSurface(Surface* surface) {
@@ -243,33 +250,40 @@ enum { MAX_TIMING_INFOS = 10 };
// syncronous requests to Surface Flinger):
enum { MIN_NUM_FRAMES_AGO = 5 };
+bool IsSharedPresentMode(VkPresentModeKHR mode) {
+ return mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
+ mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR;
+}
+
struct Swapchain {
Swapchain(Surface& surface_,
uint32_t num_images_,
VkPresentModeKHR present_mode,
- int pre_transform_)
+ int pre_transform_,
+ int64_t refresh_duration_)
: surface(surface_),
num_images(num_images_),
mailbox_mode(present_mode == VK_PRESENT_MODE_MAILBOX_KHR),
pre_transform(pre_transform_),
frame_timestamps_enabled(false),
+ refresh_duration(refresh_duration_),
acquire_next_image_timeout(-1),
- shared(present_mode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
- present_mode ==
- VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) {
- ANativeWindow* window = surface.window.get();
- native_window_get_refresh_cycle_duration(
- window,
- &refresh_duration);
+ shared(IsSharedPresentMode(present_mode)) {
}
- uint64_t get_refresh_duration()
+
+ VkResult get_refresh_duration(uint64_t& outRefreshDuration)
{
ANativeWindow* window = surface.window.get();
- native_window_get_refresh_cycle_duration(
+ int err = native_window_get_refresh_cycle_duration(
window,
&refresh_duration);
- return static_cast<uint64_t>(refresh_duration);
-
+ if (err != android::OK) {
+ ALOGE("%s:native_window_get_refresh_cycle_duration failed: %s (%d)",
+ __func__, strerror(-err), err );
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ outRefreshDuration = refresh_duration;
+ return VK_SUCCESS;
}
Surface& surface;
@@ -288,6 +302,9 @@ struct Swapchain {
release_fence(-1),
dequeued(false) {}
VkImage image;
+ // If the image is bound to memory, an sp to the underlying gralloc buffer.
+ // Otherwise, nullptr; the image will be bound to memory as part of
+ // AcquireNextImage.
android::sp<ANativeWindowBuffer> buffer;
// The fence is only valid when the buffer is dequeued, and should be
// -1 any other time. When valid, we own the fd, and must ensure it is
@@ -489,28 +506,27 @@ void copy_ready_timings(Swapchain& swapchain,
*count = num_copied;
}
-android::PixelFormat GetNativePixelFormat(VkFormat format) {
- android::PixelFormat native_format = android::PIXEL_FORMAT_RGBA_8888;
+PixelFormat GetNativePixelFormat(VkFormat format) {
+ PixelFormat native_format = PixelFormat::RGBA_8888;
switch (format) {
case VK_FORMAT_R8G8B8A8_UNORM:
case VK_FORMAT_R8G8B8A8_SRGB:
- native_format = android::PIXEL_FORMAT_RGBA_8888;
+ native_format = PixelFormat::RGBA_8888;
break;
case VK_FORMAT_R5G6B5_UNORM_PACK16:
- native_format = android::PIXEL_FORMAT_RGB_565;
+ native_format = PixelFormat::RGB_565;
break;
case VK_FORMAT_R16G16B16A16_SFLOAT:
- native_format = android::PIXEL_FORMAT_RGBA_FP16;
+ native_format = PixelFormat::RGBA_FP16;
break;
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
- native_format = android::PIXEL_FORMAT_RGBA_1010102;
+ native_format = PixelFormat::RGBA_1010102;
break;
case VK_FORMAT_R8_UNORM:
- native_format = android::PIXEL_FORMAT_R_8;
+ native_format = PixelFormat::R_8;
break;
- // TODO: Do we need to query for VK_EXT_rgba10x6_formats here?
case VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16:
- native_format = android::PIXEL_FORMAT_RGBA_10101010;
+ native_format = PixelFormat::RGBA_10101010;
break;
default:
ALOGV("unsupported swapchain format %d", format);
@@ -519,55 +535,51 @@ android::PixelFormat GetNativePixelFormat(VkFormat format) {
return native_format;
}
-android_dataspace GetNativeDataspace(VkColorSpaceKHR colorspace) {
+DataSpace GetNativeDataspace(VkColorSpaceKHR colorspace,
+ PixelFormat pixelFormat) {
switch (colorspace) {
case VK_COLOR_SPACE_SRGB_NONLINEAR_KHR:
- return HAL_DATASPACE_V0_SRGB;
+ return DataSpace::SRGB;
case VK_COLOR_SPACE_DISPLAY_P3_NONLINEAR_EXT:
- return HAL_DATASPACE_DISPLAY_P3;
+ return DataSpace::DISPLAY_P3;
case VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT:
- return HAL_DATASPACE_V0_SCRGB_LINEAR;
+ return DataSpace::SCRGB_LINEAR;
case VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT:
- return HAL_DATASPACE_V0_SCRGB;
+ return DataSpace::SCRGB;
case VK_COLOR_SPACE_DCI_P3_LINEAR_EXT:
- return HAL_DATASPACE_DCI_P3_LINEAR;
+ return DataSpace::DCI_P3_LINEAR;
case VK_COLOR_SPACE_DCI_P3_NONLINEAR_EXT:
- return HAL_DATASPACE_DCI_P3;
+ return DataSpace::DCI_P3;
case VK_COLOR_SPACE_BT709_LINEAR_EXT:
- return HAL_DATASPACE_V0_SRGB_LINEAR;
+ return DataSpace::SRGB_LINEAR;
case VK_COLOR_SPACE_BT709_NONLINEAR_EXT:
- return HAL_DATASPACE_V0_SRGB;
+ return DataSpace::SRGB;
case VK_COLOR_SPACE_BT2020_LINEAR_EXT:
- return HAL_DATASPACE_BT2020_LINEAR;
+ if (pixelFormat == PixelFormat::RGBA_FP16) {
+ return DataSpace::BT2020_LINEAR_EXTENDED;
+ } else {
+ return DataSpace::BT2020_LINEAR;
+ }
case VK_COLOR_SPACE_HDR10_ST2084_EXT:
- return static_cast<android_dataspace>(
- HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_ST2084 |
- HAL_DATASPACE_RANGE_FULL);
+ return DataSpace::BT2020_PQ;
case VK_COLOR_SPACE_DOLBYVISION_EXT:
- return static_cast<android_dataspace>(
- HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_ST2084 |
- HAL_DATASPACE_RANGE_FULL);
+ return DataSpace::BT2020_PQ;
case VK_COLOR_SPACE_HDR10_HLG_EXT:
- return static_cast<android_dataspace>(
- HAL_DATASPACE_STANDARD_BT2020 | HAL_DATASPACE_TRANSFER_HLG |
- HAL_DATASPACE_RANGE_FULL);
+ return DataSpace::BT2020_HLG;
case VK_COLOR_SPACE_ADOBERGB_LINEAR_EXT:
- return static_cast<android_dataspace>(
- HAL_DATASPACE_STANDARD_ADOBE_RGB |
- HAL_DATASPACE_TRANSFER_LINEAR | HAL_DATASPACE_RANGE_FULL);
+ return DataSpace::ADOBE_RGB_LINEAR;
case VK_COLOR_SPACE_ADOBERGB_NONLINEAR_EXT:
- return HAL_DATASPACE_ADOBE_RGB;
-
+ return DataSpace::ADOBE_RGB;
// Pass through is intended to allow app to provide data that is passed
// to the display system without modification.
case VK_COLOR_SPACE_PASS_THROUGH_EXT:
- return HAL_DATASPACE_ARBITRARY;
+ return DataSpace::ARBITRARY;
default:
// This indicates that we don't know about the
// dataspace specified and we should indicate that
// it's unsupported
- return HAL_DATASPACE_UNKNOWN;
+ return DataSpace::UNKNOWN;
}
}
@@ -592,6 +604,7 @@ VkResult CreateAndroidSurfaceKHR(
surface->window = pCreateInfo->window;
surface->swapchain_handle = VK_NULL_HANDLE;
+ surface->used_by_swapchain = false;
int err = native_window_get_consumer_usage(surface->window.get(),
&surface->consumer_usage);
if (err != android::OK) {
@@ -653,100 +666,40 @@ VkResult GetPhysicalDeviceSurfaceCapabilitiesKHR(
VkSurfaceCapabilitiesKHR* capabilities) {
ATRACE_CALL();
- int err;
- int width, height;
- int transform_hint;
- int max_buffer_count;
- if (surface == VK_NULL_HANDLE) {
- const InstanceData& instance_data = GetData(pdev);
- ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
- bool surfaceless_enabled =
- instance_data.hook_extensions.test(surfaceless);
- if (!surfaceless_enabled) {
- // It is an error to pass a surface==VK_NULL_HANDLE unless the
- // VK_GOOGLE_surfaceless_query extension is enabled
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this
- // extension for this function is for
- // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following
- // four values cannot be known without a surface. Default values will
- // be supplied anyway, but cannot be relied upon.
- width = 0xFFFFFFFF;
- height = 0xFFFFFFFF;
- transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
- capabilities->minImageCount = 0xFFFFFFFF;
- capabilities->maxImageCount = 0xFFFFFFFF;
- } else {
- ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
-
- err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
-
- err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
- &transform_hint);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
-
- err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT,
- &max_buffer_count);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)",
- strerror(-err), err);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- capabilities->minImageCount = std::min(max_buffer_count, 3);
- capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
- }
+ // Implement in terms of GetPhysicalDeviceSurfaceCapabilities2KHR
- capabilities->currentExtent =
- VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+ VkPhysicalDeviceSurfaceInfo2KHR info2 = {
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SURFACE_INFO_2_KHR,
+ nullptr,
+ surface
+ };
- // TODO(http://b/134182502): Figure out what the max extent should be.
- capabilities->minImageExtent = VkExtent2D{1, 1};
- capabilities->maxImageExtent = VkExtent2D{4096, 4096};
+ VkSurfaceCapabilities2KHR caps2 = {
+ VK_STRUCTURE_TYPE_SURFACE_CAPABILITIES_2_KHR,
+ nullptr,
+ {},
+ };
- if (capabilities->maxImageExtent.height <
- capabilities->currentExtent.height) {
- capabilities->maxImageExtent.height =
- capabilities->currentExtent.height;
- }
+ VkResult result = GetPhysicalDeviceSurfaceCapabilities2KHR(pdev, &info2, &caps2);
+ *capabilities = caps2.surfaceCapabilities;
+ return result;
+}
- if (capabilities->maxImageExtent.width <
- capabilities->currentExtent.width) {
- capabilities->maxImageExtent.width = capabilities->currentExtent.width;
+// Does the call-twice and VK_INCOMPLETE handling for querying lists
+// of things, where we already have the full set built in a vector.
+template <typename T>
+VkResult CopyWithIncomplete(std::vector<T> const& things,
+ T* callerPtr, uint32_t* callerCount) {
+ VkResult result = VK_SUCCESS;
+ if (callerPtr) {
+ if (things.size() > *callerCount)
+ result = VK_INCOMPLETE;
+ *callerCount = std::min(uint32_t(things.size()), *callerCount);
+ std::copy(things.begin(), things.begin() + *callerCount, callerPtr);
+ } else {
+ *callerCount = things.size();
}
-
- capabilities->maxImageArrayLayers = 1;
-
- capabilities->supportedTransforms = kSupportedTransforms;
- capabilities->currentTransform =
- TranslateNativeToVulkanTransform(transform_hint);
-
- // On Android, window composition is a WindowManager property, not something
- // associated with the bufferqueue. It can't be changed from here.
- capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
-
- capabilities->supportedUsageFlags =
- VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
- VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
- VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
- VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
-
- return VK_SUCCESS;
+ return result;
}
VKAPI_ATTR
@@ -859,9 +812,23 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev,
}
}
- // TODO query VK_EXT_rgba10x6_formats support
+ bool rgba10x6_formats_ext = false;
+ uint32_t exts_count;
+ const auto& driver = GetData(pdev).driver;
+ driver.EnumerateDeviceExtensionProperties(pdev, nullptr, &exts_count,
+ nullptr);
+ std::vector<VkExtensionProperties> props(exts_count);
+ driver.EnumerateDeviceExtensionProperties(pdev, nullptr, &exts_count,
+ props.data());
+ for (uint32_t i = 0; i < exts_count; i++) {
+ VkExtensionProperties prop = props[i];
+ if (strcmp(prop.extensionName,
+ VK_EXT_RGBA10X6_FORMATS_EXTENSION_NAME) == 0) {
+ rgba10x6_formats_ext = true;
+ }
+ }
desc.format = AHARDWAREBUFFER_FORMAT_R10G10B10A10_UNORM;
- if (AHardwareBuffer_isSupported(&desc)) {
+ if (AHardwareBuffer_isSupported(&desc) && rgba10x6_formats_ext) {
all_formats.emplace_back(
VkSurfaceFormatKHR{VK_FORMAT_R10X6G10X6B10X6A10X6_UNORM_4PACK16,
VK_COLOR_SPACE_SRGB_NONLINEAR_KHR});
@@ -879,21 +846,7 @@ VkResult GetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice pdev,
// Android users. This includes the ANGLE team (a layered implementation of
// OpenGL-ES).
- VkResult result = VK_SUCCESS;
- if (formats) {
- uint32_t transfer_count = all_formats.size();
- if (transfer_count > *count) {
- transfer_count = *count;
- result = VK_INCOMPLETE;
- }
- std::copy(all_formats.begin(), all_formats.begin() + transfer_count,
- formats);
- *count = transfer_count;
- } else {
- *count = all_formats.size();
- }
-
- return result;
+ return CopyWithIncomplete(all_formats, formats, count);
}
VKAPI_ATTR
@@ -903,19 +856,166 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR(
VkSurfaceCapabilities2KHR* pSurfaceCapabilities) {
ATRACE_CALL();
- VkResult result = GetPhysicalDeviceSurfaceCapabilitiesKHR(
- physicalDevice, pSurfaceInfo->surface,
- &pSurfaceCapabilities->surfaceCapabilities);
+ auto surface = pSurfaceInfo->surface;
+ auto capabilities = &pSurfaceCapabilities->surfaceCapabilities;
- VkSurfaceCapabilities2KHR* caps = pSurfaceCapabilities;
- while (caps->pNext) {
- caps = reinterpret_cast<VkSurfaceCapabilities2KHR*>(caps->pNext);
+ VkSurfacePresentModeEXT const *pPresentMode = nullptr;
+ for (auto pNext = reinterpret_cast<VkBaseInStructure const *>(pSurfaceInfo->pNext);
+ pNext; pNext = reinterpret_cast<VkBaseInStructure const *>(pNext->pNext)) {
+ switch (pNext->sType) {
+ case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_EXT:
+ pPresentMode = reinterpret_cast<VkSurfacePresentModeEXT const *>(pNext);
+ break;
- switch (caps->sType) {
+ default:
+ break;
+ }
+ }
+
+ int err;
+ int width, height;
+ int transform_hint;
+ int max_buffer_count;
+ int min_undequeued_buffers;
+ if (surface == VK_NULL_HANDLE) {
+ const InstanceData& instance_data = GetData(physicalDevice);
+ ProcHook::Extension surfaceless = ProcHook::GOOGLE_surfaceless_query;
+ bool surfaceless_enabled =
+ instance_data.hook_extensions.test(surfaceless);
+ if (!surfaceless_enabled) {
+ // It is an error to pass a surface==VK_NULL_HANDLE unless the
+ // VK_GOOGLE_surfaceless_query extension is enabled
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ // Support for VK_GOOGLE_surfaceless_query. The primary purpose of this
+ // extension for this function is for
+ // VkSurfaceProtectedCapabilitiesKHR::supportsProtected. The following
+ // four values cannot be known without a surface. Default values will
+ // be supplied anyway, but cannot be relied upon.
+ width = 0xFFFFFFFF;
+ height = 0xFFFFFFFF;
+ transform_hint = VK_SURFACE_TRANSFORM_INHERIT_BIT_KHR;
+ capabilities->minImageCount = 0xFFFFFFFF;
+ capabilities->maxImageCount = 0xFFFFFFFF;
+ } else {
+ ANativeWindow* window = SurfaceFromHandle(surface)->window.get();
+
+ err = window->query(window, NATIVE_WINDOW_DEFAULT_WIDTH, &width);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ err = window->query(window, NATIVE_WINDOW_DEFAULT_HEIGHT, &height);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_DEFAULT_WIDTH query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
+ err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
+ &transform_hint);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
+ err = window->query(window, NATIVE_WINDOW_MAX_BUFFER_COUNT,
+ &max_buffer_count);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_MAX_BUFFER_COUNT query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
+ err = window->query(window, NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS,
+ &min_undequeued_buffers);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_MIN_UNDEQUEUED_BUFFERS query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+
+ // Additional buffer count over min_undequeued_buffers in vulkan came from 2 total
+ // being technically enough for fifo (although a poor experience) vs 3 being the
+ // absolute minimum for mailbox to be useful. So min_undequeued_buffers + 2 is sensible
+ static constexpr int default_additional_buffers = 2;
+
+ if(pPresentMode != nullptr) {
+ switch (pPresentMode->presentMode) {
+ case VK_PRESENT_MODE_IMMEDIATE_KHR:
+ ALOGE("Swapchain present mode VK_PRESENT_MODE_IMMEDIATE_KHR is not supported");
+ break;
+ case VK_PRESENT_MODE_MAILBOX_KHR:
+ case VK_PRESENT_MODE_FIFO_KHR:
+ capabilities->minImageCount = std::min(max_buffer_count,
+ min_undequeued_buffers + default_additional_buffers);
+ capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+ break;
+ case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
+ ALOGE("Swapchain present mode VK_PRESENT_MODE_FIFO_RELEAXED_KHR "
+ "is not supported");
+ break;
+ case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
+ case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
+ capabilities->minImageCount = 1;
+ capabilities->maxImageCount = 1;
+ break;
+
+ default:
+ ALOGE("Unrecognized swapchain present mode %u is not supported",
+ pPresentMode->presentMode);
+ break;
+ }
+ } else {
+ capabilities->minImageCount = std::min(max_buffer_count,
+ min_undequeued_buffers + default_additional_buffers);
+ capabilities->maxImageCount = static_cast<uint32_t>(max_buffer_count);
+ }
+ }
+
+ capabilities->currentExtent =
+ VkExtent2D{static_cast<uint32_t>(width), static_cast<uint32_t>(height)};
+
+ // TODO(http://b/134182502): Figure out what the max extent should be.
+ capabilities->minImageExtent = VkExtent2D{1, 1};
+ capabilities->maxImageExtent = VkExtent2D{4096, 4096};
+
+ if (capabilities->maxImageExtent.height <
+ capabilities->currentExtent.height) {
+ capabilities->maxImageExtent.height =
+ capabilities->currentExtent.height;
+ }
+
+ if (capabilities->maxImageExtent.width <
+ capabilities->currentExtent.width) {
+ capabilities->maxImageExtent.width = capabilities->currentExtent.width;
+ }
+
+ capabilities->maxImageArrayLayers = 1;
+
+ capabilities->supportedTransforms = kSupportedTransforms;
+ capabilities->currentTransform =
+ TranslateNativeToVulkanTransform(transform_hint);
+
+ // On Android, window composition is a WindowManager property, not something
+ // associated with the bufferqueue. It can't be changed from here.
+ capabilities->supportedCompositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR;
+
+ capabilities->supportedUsageFlags =
+ VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT |
+ VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT |
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT |
+ VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT;
+
+ for (auto pNext = reinterpret_cast<VkBaseOutStructure*>(pSurfaceCapabilities->pNext);
+ pNext; pNext = reinterpret_cast<VkBaseOutStructure*>(pNext->pNext)) {
+
+ switch (pNext->sType) {
case VK_STRUCTURE_TYPE_SHARED_PRESENT_SURFACE_CAPABILITIES_KHR: {
VkSharedPresentSurfaceCapabilitiesKHR* shared_caps =
- reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(
- caps);
+ reinterpret_cast<VkSharedPresentSurfaceCapabilitiesKHR*>(pNext);
// Claim same set of usage flags are supported for
// shared present modes as for other modes.
shared_caps->sharedPresentSupportedUsageFlags =
@@ -925,17 +1025,64 @@ VkResult GetPhysicalDeviceSurfaceCapabilities2KHR(
case VK_STRUCTURE_TYPE_SURFACE_PROTECTED_CAPABILITIES_KHR: {
VkSurfaceProtectedCapabilitiesKHR* protected_caps =
- reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(caps);
+ reinterpret_cast<VkSurfaceProtectedCapabilitiesKHR*>(pNext);
protected_caps->supportsProtected = VK_TRUE;
} break;
+ case VK_STRUCTURE_TYPE_SURFACE_PRESENT_SCALING_CAPABILITIES_EXT: {
+ VkSurfacePresentScalingCapabilitiesEXT* scaling_caps =
+ reinterpret_cast<VkSurfacePresentScalingCapabilitiesEXT*>(pNext);
+ // By default, Android stretches the buffer to fit the window,
+ // without preserving aspect ratio. Other modes are technically possible
+ // but consult with CoGS team before exposing them here!
+ scaling_caps->supportedPresentScaling = VK_PRESENT_SCALING_STRETCH_BIT_EXT;
+
+ // Since we always scale, we don't support any gravity.
+ scaling_caps->supportedPresentGravityX = 0;
+ scaling_caps->supportedPresentGravityY = 0;
+
+ // Scaled image limits are just the basic image limits
+ scaling_caps->minScaledImageExtent = capabilities->minImageExtent;
+ scaling_caps->maxScaledImageExtent = capabilities->maxImageExtent;
+ } break;
+
+ case VK_STRUCTURE_TYPE_SURFACE_PRESENT_MODE_COMPATIBILITY_EXT: {
+ VkSurfacePresentModeCompatibilityEXT* mode_caps =
+ reinterpret_cast<VkSurfacePresentModeCompatibilityEXT*>(pNext);
+
+ ALOG_ASSERT(pPresentMode,
+ "querying VkSurfacePresentModeCompatibilityEXT "
+ "requires VkSurfacePresentModeEXT to be provided");
+ std::vector<VkPresentModeKHR> compatibleModes;
+ compatibleModes.push_back(pPresentMode->presentMode);
+
+ switch (pPresentMode->presentMode) {
+ // Shared modes are both compatible with each other.
+ case VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR:
+ compatibleModes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
+ break;
+ case VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR:
+ compatibleModes.push_back(VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR);
+ break;
+ default:
+ // Other modes are only compatible with themselves.
+ // TODO: consider whether switching between FIFO and MAILBOX is reasonable
+ break;
+ }
+
+ // Note: this does not generate VK_INCOMPLETE since we're nested inside
+ // a larger query and there would be no way to determine exactly where it came from.
+ CopyWithIncomplete(compatibleModes, mode_caps->pPresentModes,
+ &mode_caps->presentModeCount);
+ } break;
+
default:
// Ignore all other extension structs
break;
}
}
- return result;
+ return VK_SUCCESS;
}
VKAPI_ATTR
@@ -988,12 +1135,17 @@ VkResult GetPhysicalDeviceSurfaceFormats2KHR(
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2;
imageFormatInfo.format =
pSurfaceFormats[i].surfaceFormat.format;
+ imageFormatInfo.type = VK_IMAGE_TYPE_2D;
+ imageFormatInfo.usage =
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
imageFormatInfo.pNext = nullptr;
VkImageCompressionControlEXT compressionControl = {};
compressionControl.sType =
VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT;
compressionControl.pNext = imageFormatInfo.pNext;
+ compressionControl.flags =
+ VK_IMAGE_COMPRESSION_FIXED_RATE_DEFAULT_EXT;
imageFormatInfo.pNext = &compressionControl;
@@ -1094,18 +1246,7 @@ VkResult GetPhysicalDeviceSurfacePresentModesKHR(VkPhysicalDevice pdev,
present_modes.push_back(VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
}
- uint32_t num_modes = uint32_t(present_modes.size());
-
- VkResult result = VK_SUCCESS;
- if (modes) {
- if (*count < num_modes)
- result = VK_INCOMPLETE;
- *count = std::min(*count, num_modes);
- std::copy_n(present_modes.data(), *count, modes);
- } else {
- *count = num_modes;
- }
- return result;
+ return CopyWithIncomplete(present_modes, modes, count);
}
VKAPI_ATTR
@@ -1219,6 +1360,210 @@ static void DestroySwapchainInternal(VkDevice device,
allocator->pfnFree(allocator->pUserData, swapchain);
}
+static VkResult getProducerUsage(const VkDevice& device,
+ const VkSwapchainCreateInfoKHR* create_info,
+ const VkSwapchainImageUsageFlagsANDROID swapchain_image_usage,
+ bool create_protected_swapchain,
+ uint64_t* producer_usage) {
+ // Get the physical device to query the appropriate producer usage
+ const VkPhysicalDevice& pdev = GetData(device).driver_physical_device;
+ const InstanceData& instance_data = GetData(pdev);
+ const InstanceDriverTable& instance_dispatch = instance_data.driver;
+ if (!instance_dispatch.GetPhysicalDeviceImageFormatProperties2 &&
+ !instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR) {
+ uint64_t native_usage = 0;
+ void* usage_info_pNext = nullptr;
+ VkResult result;
+ VkImageCompressionControlEXT image_compression = {};
+ const auto& dispatch = GetData(device).driver;
+ if (dispatch.GetSwapchainGrallocUsage4ANDROID) {
+ ATRACE_BEGIN("GetSwapchainGrallocUsage4ANDROID");
+ VkGrallocUsageInfo2ANDROID gralloc_usage_info = {};
+ gralloc_usage_info.sType =
+ VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_2_ANDROID;
+ gralloc_usage_info.format = create_info->imageFormat;
+ gralloc_usage_info.imageUsage = create_info->imageUsage;
+ gralloc_usage_info.swapchainImageUsage = swapchain_image_usage;
+
+ // Look through the pNext chain for an image compression control struct
+ // if one is found AND the appropriate extensions are enabled,
+ // append it to be the gralloc usage pNext chain
+ const VkSwapchainCreateInfoKHR* create_infos = create_info;
+ while (create_infos->pNext) {
+ create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
+ create_infos->pNext);
+ switch (create_infos->sType) {
+ case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+ const VkImageCompressionControlEXT* compression_infos =
+ reinterpret_cast<const VkImageCompressionControlEXT*>(
+ create_infos);
+ image_compression = *compression_infos;
+ image_compression.pNext = nullptr;
+ usage_info_pNext = &image_compression;
+ } break;
+
+ default:
+ // Ignore all other info structs
+ break;
+ }
+ }
+ gralloc_usage_info.pNext = usage_info_pNext;
+
+ result = dispatch.GetSwapchainGrallocUsage4ANDROID(
+ device, &gralloc_usage_info, &native_usage);
+ ATRACE_END();
+ if (result != VK_SUCCESS) {
+ ALOGE("vkGetSwapchainGrallocUsage4ANDROID failed: %d", result);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ } else if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
+ ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
+ VkGrallocUsageInfoANDROID gralloc_usage_info = {};
+ gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
+ gralloc_usage_info.format = create_info->imageFormat;
+ gralloc_usage_info.imageUsage = create_info->imageUsage;
+
+ // Look through the pNext chain for an image compression control struct
+ // if one is found AND the appropriate extensions are enabled,
+ // append it to be the gralloc usage pNext chain
+ const VkSwapchainCreateInfoKHR* create_infos = create_info;
+ while (create_infos->pNext) {
+ create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
+ create_infos->pNext);
+ switch (create_infos->sType) {
+ case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+ const VkImageCompressionControlEXT* compression_infos =
+ reinterpret_cast<const VkImageCompressionControlEXT*>(
+ create_infos);
+ image_compression = *compression_infos;
+ image_compression.pNext = nullptr;
+ usage_info_pNext = &image_compression;
+ } break;
+
+ default:
+ // Ignore all other info structs
+ break;
+ }
+ }
+ gralloc_usage_info.pNext = usage_info_pNext;
+
+ result = dispatch.GetSwapchainGrallocUsage3ANDROID(
+ device, &gralloc_usage_info, &native_usage);
+ ATRACE_END();
+ if (result != VK_SUCCESS) {
+ ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
+ uint64_t consumer_usage, producer_usage;
+ ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
+ result = dispatch.GetSwapchainGrallocUsage2ANDROID(
+ device, create_info->imageFormat, create_info->imageUsage,
+ swapchain_image_usage, &consumer_usage, &producer_usage);
+ ATRACE_END();
+ if (result != VK_SUCCESS) {
+ ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ native_usage =
+ convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
+ } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
+ ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
+ int32_t legacy_usage = 0;
+ result = dispatch.GetSwapchainGrallocUsageANDROID(
+ device, create_info->imageFormat, create_info->imageUsage,
+ &legacy_usage);
+ ATRACE_END();
+ if (result != VK_SUCCESS) {
+ ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ native_usage = static_cast<uint64_t>(legacy_usage);
+ }
+ *producer_usage = native_usage;
+
+ return VK_SUCCESS;
+ }
+
+ // Look through the create_info pNext chain passed to createSwapchainKHR
+ // for an image compression control struct.
+ // if one is found AND the appropriate extensions are enabled, create a
+ // VkImageCompressionControlEXT structure to pass on to GetPhysicalDeviceImageFormatProperties2
+ void* compression_control_pNext = nullptr;
+ VkImageCompressionControlEXT image_compression = {};
+ const VkSwapchainCreateInfoKHR* create_infos = create_info;
+ while (create_infos->pNext) {
+ create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext);
+ switch (create_infos->sType) {
+ case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+ const VkImageCompressionControlEXT* compression_infos =
+ reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos);
+ image_compression = *compression_infos;
+ image_compression.pNext = nullptr;
+ compression_control_pNext = &image_compression;
+ } break;
+ default:
+ // Ignore all other info structs
+ break;
+ }
+ }
+
+ // call GetPhysicalDeviceImageFormatProperties2KHR
+ VkPhysicalDeviceExternalImageFormatInfo external_image_format_info = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_IMAGE_FORMAT_INFO,
+ .pNext = compression_control_pNext,
+ .handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
+ };
+
+ // AHB does not have an sRGB format so we can't pass it to GPDIFP
+ // We need to convert the format to unorm if it is srgb
+ VkFormat format = create_info->imageFormat;
+ if (format == VK_FORMAT_R8G8B8A8_SRGB) {
+ format = VK_FORMAT_R8G8B8A8_UNORM;
+ }
+
+ VkPhysicalDeviceImageFormatInfo2 image_format_info = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_IMAGE_FORMAT_INFO_2,
+ .pNext = &external_image_format_info,
+ .format = format,
+ .type = VK_IMAGE_TYPE_2D,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = create_info->imageUsage,
+ .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
+ };
+
+ VkAndroidHardwareBufferUsageANDROID ahb_usage;
+ ahb_usage.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_USAGE_ANDROID;
+ ahb_usage.pNext = nullptr;
+
+ VkImageFormatProperties2 image_format_properties;
+ image_format_properties.sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_PROPERTIES_2;
+ image_format_properties.pNext = &ahb_usage;
+
+ if (instance_dispatch.GetPhysicalDeviceImageFormatProperties2) {
+ VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2(
+ pdev, &image_format_info, &image_format_properties);
+ if (result != VK_SUCCESS) {
+ ALOGE("VkGetPhysicalDeviceImageFormatProperties2 for AHB usage failed: %d", result);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ }
+ else {
+ VkResult result = instance_dispatch.GetPhysicalDeviceImageFormatProperties2KHR(
+ pdev, &image_format_info,
+ &image_format_properties);
+ if (result != VK_SUCCESS) {
+ ALOGE("VkGetPhysicalDeviceImageFormatProperties2KHR for AHB usage failed: %d",
+ result);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
+ }
+
+ *producer_usage = ahb_usage.androidHardwareBufferUsage;
+
+ return VK_SUCCESS;
+}
+
VKAPI_ATTR
VkResult CreateSwapchainKHR(VkDevice device,
const VkSwapchainCreateInfoKHR* create_info,
@@ -1243,11 +1588,11 @@ VkResult CreateSwapchainKHR(VkDevice device,
if (!allocator)
allocator = &GetData(device).allocator;
- android::PixelFormat native_pixel_format =
+ PixelFormat native_pixel_format =
GetNativePixelFormat(create_info->imageFormat);
- android_dataspace native_dataspace =
- GetNativeDataspace(create_info->imageColorSpace);
- if (native_dataspace == HAL_DATASPACE_UNKNOWN) {
+ DataSpace native_dataspace =
+ GetNativeDataspace(create_info->imageColorSpace, native_pixel_format);
+ if (native_dataspace == DataSpace::UNKNOWN) {
ALOGE(
"CreateSwapchainKHR(VkSwapchainCreateInfoKHR.imageColorSpace = %d) "
"failed: Unsupported color space",
@@ -1294,14 +1639,20 @@ VkResult CreateSwapchainKHR(VkDevice device,
// orphans the previous buffers, getting us back to the state where we can
// dequeue all buffers.
//
+ // This is not necessary if the surface was never used previously.
+ //
// TODO(http://b/134186185) recycle swapchain images more efficiently
ANativeWindow* window = surface.window.get();
- err = native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
- ALOGW_IF(err != android::OK, "native_window_api_disconnect failed: %s (%d)",
- strerror(-err), err);
- err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
- ALOGW_IF(err != android::OK, "native_window_api_connect failed: %s (%d)",
- strerror(-err), err);
+ if (surface.used_by_swapchain) {
+ err = native_window_api_disconnect(window, NATIVE_WINDOW_API_EGL);
+ ALOGW_IF(err != android::OK,
+ "native_window_api_disconnect failed: %s (%d)", strerror(-err),
+ err);
+ err = native_window_api_connect(window, NATIVE_WINDOW_API_EGL);
+ ALOGW_IF(err != android::OK,
+ "native_window_api_connect failed: %s (%d)", strerror(-err),
+ err);
+ }
err =
window->perform(window, NATIVE_WINDOW_SET_DEQUEUE_TIMEOUT, nsecs_t{-1});
@@ -1338,16 +1689,18 @@ VkResult CreateSwapchainKHR(VkDevice device,
const auto& dispatch = GetData(device).driver;
- err = native_window_set_buffers_format(window, native_pixel_format);
+ err = native_window_set_buffers_format(
+ window, static_cast<int>(native_pixel_format));
if (err != android::OK) {
ALOGE("native_window_set_buffers_format(%s) failed: %s (%d)",
- decodePixelFormat(native_pixel_format).c_str(), strerror(-err), err);
+ toString(native_pixel_format).c_str(), strerror(-err), err);
return VK_ERROR_SURFACE_LOST_KHR;
}
/* Respect consumer default dataspace upon HAL_DATASPACE_ARBITRARY. */
- if (native_dataspace != HAL_DATASPACE_ARBITRARY) {
- err = native_window_set_buffers_data_space(window, native_dataspace);
+ if (native_dataspace != DataSpace::ARBITRARY) {
+ err = native_window_set_buffers_data_space(
+ window, static_cast<android_dataspace_t>(native_dataspace));
if (err != android::OK) {
ALOGE("native_window_set_buffers_data_space(%d) failed: %s (%d)",
native_dataspace, strerror(-err), err);
@@ -1391,8 +1744,7 @@ VkResult CreateSwapchainKHR(VkDevice device,
}
VkSwapchainImageUsageFlagsANDROID swapchain_image_usage = 0;
- if (create_info->presentMode == VK_PRESENT_MODE_SHARED_DEMAND_REFRESH_KHR ||
- create_info->presentMode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR) {
+ if (IsSharedPresentMode(create_info->presentMode)) {
swapchain_image_usage |= VK_SWAPCHAIN_IMAGE_USAGE_SHARED_BIT_ANDROID;
err = native_window_set_shared_buffer_mode(window, true);
if (err != android::OK) {
@@ -1417,22 +1769,26 @@ VkResult CreateSwapchainKHR(VkDevice device,
query_value);
return VK_ERROR_SURFACE_LOST_KHR;
}
- uint32_t min_undequeued_buffers = static_cast<uint32_t>(query_value);
- const auto mailbox_num_images = std::max(3u, create_info->minImageCount);
- const auto requested_images =
- swap_interval ? create_info->minImageCount : mailbox_num_images;
- uint32_t num_images = requested_images - 1 + min_undequeued_buffers;
+ const uint32_t min_undequeued_buffers = static_cast<uint32_t>(query_value);
// Lower layer insists that we have at least min_undequeued_buffers + 1
// buffers. This is wasteful and we'd like to relax it in the shared case,
// but not all the pieces are in place for that to work yet. Note we only
// lie to the lower layer--we don't want to give the app back a swapchain
// with extra images (which they can't actually use!).
- uint32_t min_buffer_count = min_undequeued_buffers + 1;
- err = native_window_set_buffer_count(
- window, std::max(min_buffer_count, num_images));
+ const uint32_t min_buffer_count = min_undequeued_buffers + 1;
+
+ uint32_t num_images;
+ if (create_info->presentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
+ num_images = std::max(3u, create_info->minImageCount);
+ } else {
+ num_images = create_info->minImageCount;
+ }
+
+ const uint32_t buffer_count = std::max(min_buffer_count, num_images);
+ err = native_window_set_buffer_count(window, buffer_count);
if (err != android::OK) {
- ALOGE("native_window_set_buffer_count(%d) failed: %s (%d)", num_images,
+ ALOGE("native_window_set_buffer_count(%d) failed: %s (%d)", buffer_count,
strerror(-err), err);
return VK_ERROR_SURFACE_LOST_KHR;
}
@@ -1443,80 +1799,48 @@ VkResult CreateSwapchainKHR(VkDevice device,
num_images = 1;
}
+ // Look through the create_info pNext chain passed to createSwapchainKHR
+ // for an image compression control struct.
+ // if one is found AND the appropriate extensions are enabled, create a
+ // VkImageCompressionControlEXT structure to pass on to VkImageCreateInfo
+ // TODO check for imageCompressionControlSwapchain feature is enabled
void* usage_info_pNext = nullptr;
VkImageCompressionControlEXT image_compression = {};
- uint64_t native_usage = 0;
- if (dispatch.GetSwapchainGrallocUsage3ANDROID) {
- ATRACE_BEGIN("GetSwapchainGrallocUsage3ANDROID");
- VkGrallocUsageInfoANDROID gralloc_usage_info = {};
- gralloc_usage_info.sType = VK_STRUCTURE_TYPE_GRALLOC_USAGE_INFO_ANDROID;
- gralloc_usage_info.format = create_info->imageFormat;
- gralloc_usage_info.imageUsage = create_info->imageUsage;
-
- // Look through the pNext chain for an image compression control struct
- // if one is found AND the appropriate extensions are enabled,
- // append it to be the gralloc usage pNext chain
- const VkSwapchainCreateInfoKHR* create_infos = create_info;
- while (create_infos->pNext) {
- create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(
- create_infos->pNext);
- switch (create_infos->sType) {
- case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
- const VkImageCompressionControlEXT* compression_infos =
- reinterpret_cast<const VkImageCompressionControlEXT*>(
- create_infos);
- image_compression = *compression_infos;
- image_compression.pNext = nullptr;
- usage_info_pNext = &image_compression;
- } break;
-
- default:
- // Ignore all other info structs
- break;
- }
- }
- gralloc_usage_info.pNext = usage_info_pNext;
+ const VkSwapchainCreateInfoKHR* create_infos = create_info;
+ while (create_infos->pNext) {
+ create_infos = reinterpret_cast<const VkSwapchainCreateInfoKHR*>(create_infos->pNext);
+ switch (create_infos->sType) {
+ case VK_STRUCTURE_TYPE_IMAGE_COMPRESSION_CONTROL_EXT: {
+ const VkImageCompressionControlEXT* compression_infos =
+ reinterpret_cast<const VkImageCompressionControlEXT*>(create_infos);
+ image_compression = *compression_infos;
+ image_compression.pNext = nullptr;
+ usage_info_pNext = &image_compression;
+ } break;
- result = dispatch.GetSwapchainGrallocUsage3ANDROID(
- device, &gralloc_usage_info, &native_usage);
- ATRACE_END();
- if (result != VK_SUCCESS) {
- ALOGE("vkGetSwapchainGrallocUsage3ANDROID failed: %d", result);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- } else if (dispatch.GetSwapchainGrallocUsage2ANDROID) {
- uint64_t consumer_usage, producer_usage;
- ATRACE_BEGIN("GetSwapchainGrallocUsage2ANDROID");
- result = dispatch.GetSwapchainGrallocUsage2ANDROID(
- device, create_info->imageFormat, create_info->imageUsage,
- swapchain_image_usage, &consumer_usage, &producer_usage);
- ATRACE_END();
- if (result != VK_SUCCESS) {
- ALOGE("vkGetSwapchainGrallocUsage2ANDROID failed: %d", result);
- return VK_ERROR_SURFACE_LOST_KHR;
- }
- native_usage =
- convertGralloc1ToBufferUsage(producer_usage, consumer_usage);
- } else if (dispatch.GetSwapchainGrallocUsageANDROID) {
- ATRACE_BEGIN("GetSwapchainGrallocUsageANDROID");
- int32_t legacy_usage = 0;
- result = dispatch.GetSwapchainGrallocUsageANDROID(
- device, create_info->imageFormat, create_info->imageUsage,
- &legacy_usage);
- ATRACE_END();
- if (result != VK_SUCCESS) {
- ALOGE("vkGetSwapchainGrallocUsageANDROID failed: %d", result);
- return VK_ERROR_SURFACE_LOST_KHR;
+ default:
+ // Ignore all other info structs
+ break;
}
- native_usage = static_cast<uint64_t>(legacy_usage);
}
- native_usage |= surface.consumer_usage;
- bool createProtectedSwapchain = false;
+ // Get the appropriate native_usage for the images
+ // Get the consumer usage
+ uint64_t native_usage = surface.consumer_usage;
+ // Determine if the swapchain is protected
+ bool create_protected_swapchain = false;
if (create_info->flags & VK_SWAPCHAIN_CREATE_PROTECTED_BIT_KHR) {
- createProtectedSwapchain = true;
+ create_protected_swapchain = true;
native_usage |= BufferUsage::PROTECTED;
}
+ // Get the producer usage
+ uint64_t producer_usage;
+ result = getProducerUsage(device, create_info, swapchain_image_usage, create_protected_swapchain, &producer_usage);
+ if (result != VK_SUCCESS) {
+ return result;
+ }
+ native_usage |= producer_usage;
+
err = native_window_set_usage(window, native_usage);
if (err != android::OK) {
ALOGE("native_window_set_usage failed: %s (%d)", strerror(-err), err);
@@ -1531,20 +1855,27 @@ VkResult CreateSwapchainKHR(VkDevice device,
return VK_ERROR_SURFACE_LOST_KHR;
}
+ int64_t refresh_duration;
+ err = native_window_get_refresh_cycle_duration(window, &refresh_duration);
+ if (err != android::OK) {
+ ALOGE("native_window_get_refresh_cycle_duration query failed: %s (%d)",
+ strerror(-err), err);
+ return VK_ERROR_SURFACE_LOST_KHR;
+ }
// -- Allocate our Swapchain object --
// After this point, we must deallocate the swapchain on error.
void* mem = allocator->pfnAllocation(allocator->pUserData,
sizeof(Swapchain), alignof(Swapchain),
VK_SYSTEM_ALLOCATION_SCOPE_OBJECT);
+
if (!mem)
return VK_ERROR_OUT_OF_HOST_MEMORY;
+
Swapchain* swapchain = new (mem)
Swapchain(surface, num_images, create_info->presentMode,
- TranslateVulkanToNativeTransform(create_info->preTransform));
- // -- Dequeue all buffers and create a VkImage for each --
- // Any failures during or after this must cancel the dequeued buffers.
-
+ TranslateVulkanToNativeTransform(create_info->preTransform),
+ refresh_duration);
VkSwapchainImageCreateInfoANDROID swapchain_image_create = {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast"
@@ -1560,13 +1891,18 @@ VkResult CreateSwapchainKHR(VkDevice device,
#pragma clang diagnostic pop
.pNext = &swapchain_image_create,
};
+
VkImageCreateInfo image_create = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
- .pNext = &image_native_buffer,
- .flags = createProtectedSwapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
+ .pNext = nullptr,
+ .flags = create_protected_swapchain ? VK_IMAGE_CREATE_PROTECTED_BIT : 0u,
.imageType = VK_IMAGE_TYPE_2D,
.format = create_info->imageFormat,
- .extent = {0, 0, 1},
+ .extent = {
+ create_info->imageExtent.width,
+ create_info->imageExtent.height,
+ 1
+ },
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
@@ -1577,60 +1913,89 @@ VkResult CreateSwapchainKHR(VkDevice device,
.pQueueFamilyIndices = create_info->pQueueFamilyIndices,
};
- for (uint32_t i = 0; i < num_images; i++) {
- Swapchain::Image& img = swapchain->images[i];
+ // Note: don't do deferred allocation for shared present modes. There's only one buffer
+ // involved so very little benefit.
+ if ((create_info->flags & VK_SWAPCHAIN_CREATE_DEFERRED_MEMORY_ALLOCATION_BIT_EXT) &&
+ !IsSharedPresentMode(create_info->presentMode)) {
+ // Don't want to touch the underlying gralloc buffers yet;
+ // instead just create unbound VkImages which will later be bound to memory inside
+ // AcquireNextImage.
+ VkImageSwapchainCreateInfoKHR image_swapchain_create = {
+ .sType = VK_STRUCTURE_TYPE_IMAGE_SWAPCHAIN_CREATE_INFO_KHR,
+ .pNext = nullptr,
+ .swapchain = HandleFromSwapchain(swapchain),
+ };
+ image_create.pNext = &image_swapchain_create;
- ANativeWindowBuffer* buffer;
- err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence);
- if (err != android::OK) {
- ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
- switch (-err) {
- case ENOMEM:
- result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
- break;
- default:
- result = VK_ERROR_SURFACE_LOST_KHR;
- break;
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
+ img.buffer = nullptr;
+ img.dequeued = false;
+
+ result = dispatch.CreateImage(device, &image_create, nullptr, &img.image);
+ if (result != VK_SUCCESS) {
+ ALOGD("vkCreateImage w/ for deferred swapchain image failed: %u", result);
+ break;
}
- break;
}
- img.buffer = buffer;
- img.dequeued = true;
-
- image_create.extent =
- VkExtent3D{static_cast<uint32_t>(img.buffer->width),
- static_cast<uint32_t>(img.buffer->height),
- 1};
- image_native_buffer.handle = img.buffer->handle;
- image_native_buffer.stride = img.buffer->stride;
- image_native_buffer.format = img.buffer->format;
- image_native_buffer.usage = int(img.buffer->usage);
- android_convertGralloc0To1Usage(int(img.buffer->usage),
- &image_native_buffer.usage2.producer,
- &image_native_buffer.usage2.consumer);
- image_native_buffer.usage3 = img.buffer->usage;
-
- ATRACE_BEGIN("CreateImage");
- result =
- dispatch.CreateImage(device, &image_create, nullptr, &img.image);
- ATRACE_END();
- if (result != VK_SUCCESS) {
- ALOGD("vkCreateImage w/ native buffer failed: %u", result);
- break;
+ } else {
+ // -- Dequeue all buffers and create a VkImage for each --
+ // Any failures during or after this must cancel the dequeued buffers.
+
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
+
+ ANativeWindowBuffer* buffer;
+ err = window->dequeueBuffer(window, &buffer, &img.dequeue_fence);
+ if (err != android::OK) {
+ ALOGE("dequeueBuffer[%u] failed: %s (%d)", i, strerror(-err), err);
+ switch (-err) {
+ case ENOMEM:
+ result = VK_ERROR_OUT_OF_DEVICE_MEMORY;
+ break;
+ default:
+ result = VK_ERROR_SURFACE_LOST_KHR;
+ break;
+ }
+ break;
+ }
+ img.buffer = buffer;
+ img.dequeued = true;
+
+ image_native_buffer.handle = img.buffer->handle;
+ image_native_buffer.stride = img.buffer->stride;
+ image_native_buffer.format = img.buffer->format;
+ image_native_buffer.usage = int(img.buffer->usage);
+ android_convertGralloc0To1Usage(int(img.buffer->usage),
+ &image_native_buffer.usage2.producer,
+ &image_native_buffer.usage2.consumer);
+ image_native_buffer.usage3 = img.buffer->usage;
+ image_native_buffer.ahb =
+ ANativeWindowBuffer_getHardwareBuffer(img.buffer.get());
+ image_create.pNext = &image_native_buffer;
+
+ ATRACE_BEGIN("CreateImage");
+ result =
+ dispatch.CreateImage(device, &image_create, nullptr, &img.image);
+ ATRACE_END();
+ if (result != VK_SUCCESS) {
+ ALOGD("vkCreateImage w/ native buffer failed: %u", result);
+ break;
+ }
}
- }
- // -- Cancel all buffers, returning them to the queue --
- // If an error occurred before, also destroy the VkImage and release the
- // buffer reference. Otherwise, we retain a strong reference to the buffer.
- for (uint32_t i = 0; i < num_images; i++) {
- Swapchain::Image& img = swapchain->images[i];
- if (img.dequeued) {
- if (!swapchain->shared) {
- window->cancelBuffer(window, img.buffer.get(),
- img.dequeue_fence);
- img.dequeue_fence = -1;
- img.dequeued = false;
+ // -- Cancel all buffers, returning them to the queue --
+ // If an error occurred before, also destroy the VkImage and release the
+ // buffer reference. Otherwise, we retain a strong reference to the buffer.
+ for (uint32_t i = 0; i < num_images; i++) {
+ Swapchain::Image& img = swapchain->images[i];
+ if (img.dequeued) {
+ if (!swapchain->shared) {
+ window->cancelBuffer(window, img.buffer.get(),
+ img.dequeue_fence);
+ img.dequeue_fence = -1;
+ img.dequeued = false;
+ }
}
}
}
@@ -1647,6 +2012,11 @@ VkResult CreateSwapchainKHR(VkDevice device,
android::GpuStatsInfo::Stats::FALSE_PREROTATION);
}
+ // Set stats for creating a Vulkan swapchain
+ android::GraphicsEnv::getInstance().setTargetStats(
+ android::GpuStatsInfo::Stats::CREATED_VULKAN_SWAPCHAIN);
+
+ surface.used_by_swapchain = true;
surface.swapchain_handle = HandleFromSwapchain(swapchain);
*swapchain_handle = surface.swapchain_handle;
return VK_SUCCESS;
@@ -1749,6 +2119,69 @@ VkResult AcquireNextImageKHR(VkDevice device,
break;
}
}
+
+ // If this is a deferred alloc swapchain, this may be the first time we've
+ // seen a particular buffer. If so, there should be an empty slot. Find it,
+ // and bind the gralloc buffer to the VkImage for that slot. If there is no
+ // empty slot, then we dequeued an unexpected buffer. Non-deferred swapchains
+ // will also take this path, but will never have an empty slot since we
+ // populated them all upfront.
+ if (idx == swapchain.num_images) {
+ for (idx = 0; idx < swapchain.num_images; idx++) {
+ if (!swapchain.images[idx].buffer) {
+ // Note: this structure is technically required for
+ // Vulkan correctness, even though the driver is probably going
+ // to use everything from the VkNativeBufferANDROID below.
+ // This is kindof silly, but it's how we did the ANB
+ // side of VK_KHR_swapchain v69, so we're stuck with it unless
+ // we want to go tinkering with the ANB spec some more.
+ VkBindImageMemorySwapchainInfoKHR bimsi = {
+ .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_SWAPCHAIN_INFO_KHR,
+ .pNext = nullptr,
+ .swapchain = swapchain_handle,
+ .imageIndex = idx,
+ };
+ VkNativeBufferANDROID nb = {
+ .sType = VK_STRUCTURE_TYPE_NATIVE_BUFFER_ANDROID,
+ .pNext = &bimsi,
+ .handle = buffer->handle,
+ .stride = buffer->stride,
+ .format = buffer->format,
+ .usage = int(buffer->usage),
+ .usage3 = buffer->usage,
+ .ahb = ANativeWindowBuffer_getHardwareBuffer(buffer),
+ };
+ android_convertGralloc0To1Usage(int(buffer->usage),
+ &nb.usage2.producer,
+ &nb.usage2.consumer);
+ VkBindImageMemoryInfo bimi = {
+ .sType = VK_STRUCTURE_TYPE_BIND_IMAGE_MEMORY_INFO,
+ .pNext = &nb,
+ .image = swapchain.images[idx].image,
+ .memory = VK_NULL_HANDLE,
+ .memoryOffset = 0,
+ };
+ result = GetData(device).driver.BindImageMemory2(device, 1, &bimi);
+ if (result != VK_SUCCESS) {
+ // This shouldn't really happen. If it does, something is probably
+ // unrecoverably wrong with the swapchain and its images. Cancel
+ // the buffer and declare the swapchain broken.
+ ALOGE("failed to do deferred gralloc buffer bind");
+ window->cancelBuffer(window, buffer, fence_fd);
+ return VK_ERROR_OUT_OF_DATE_KHR;
+ }
+
+ swapchain.images[idx].dequeued = true;
+ swapchain.images[idx].dequeue_fence = fence_fd;
+ swapchain.images[idx].buffer = buffer;
+ break;
+ }
+ }
+ }
+
+ // The buffer doesn't match any slot. This shouldn't normally happen, but is
+ // possible if the bufferqueue is reconfigured behind libvulkan's back. If this
+ // happens, just declare the swapchain to be broken and the app will recreate it.
if (idx == swapchain.num_images) {
ALOGE("dequeueBuffer returned unrecognized buffer");
window->cancelBuffer(window, buffer, fence_fd);
@@ -1816,6 +2249,214 @@ static VkResult WorstPresentResult(VkResult a, VkResult b) {
return a != VK_SUCCESS ? a : b;
}
+// KHR_incremental_present aspect of QueuePresentKHR
+static void SetSwapchainSurfaceDamage(ANativeWindow *window, const VkPresentRegionKHR *pRegion) {
+ std::vector<android_native_rect_t> rects(pRegion->rectangleCount);
+ for (auto i = 0u; i < pRegion->rectangleCount; i++) {
+ auto const& rect = pRegion->pRectangles[i];
+ if (rect.layer > 0) {
+ ALOGV("vkQueuePresentKHR ignoring invalid layer (%u); using layer 0 instead",
+ rect.layer);
+ }
+
+ rects[i].left = rect.offset.x;
+ rects[i].bottom = rect.offset.y;
+ rects[i].right = rect.offset.x + rect.extent.width;
+ rects[i].top = rect.offset.y + rect.extent.height;
+ }
+ native_window_set_surface_damage(window, rects.data(), rects.size());
+}
+
+// GOOGLE_display_timing aspect of QueuePresentKHR
+static void SetSwapchainFrameTimestamp(Swapchain &swapchain, const VkPresentTimeGOOGLE *pTime) {
+ ANativeWindow *window = swapchain.surface.window.get();
+
+ // We don't know whether the app will actually use GOOGLE_display_timing
+ // with a particular swapchain until QueuePresent; enable it on the BQ
+ // now if needed
+ if (!swapchain.frame_timestamps_enabled) {
+ ALOGV("Calling native_window_enable_frame_timestamps(true)");
+ native_window_enable_frame_timestamps(window, true);
+ swapchain.frame_timestamps_enabled = true;
+ }
+
+ // Record the nativeFrameId so it can be later correlated to
+ // this present.
+ uint64_t nativeFrameId = 0;
+ int err = native_window_get_next_frame_id(
+ window, &nativeFrameId);
+ if (err != android::OK) {
+ ALOGE("Failed to get next native frame ID.");
+ }
+
+ // Add a new timing record with the user's presentID and
+ // the nativeFrameId.
+ swapchain.timing.emplace_back(pTime, nativeFrameId);
+ if (swapchain.timing.size() > MAX_TIMING_INFOS) {
+ swapchain.timing.erase(
+ swapchain.timing.begin(),
+ swapchain.timing.begin() + swapchain.timing.size() - MAX_TIMING_INFOS);
+ }
+ if (pTime->desiredPresentTime) {
+ ALOGV(
+ "Calling native_window_set_buffers_timestamp(%" PRId64 ")",
+ pTime->desiredPresentTime);
+ native_window_set_buffers_timestamp(
+ window,
+ static_cast<int64_t>(pTime->desiredPresentTime));
+ }
+}
+
+// EXT_swapchain_maintenance1 present mode change
+static bool SetSwapchainPresentMode(ANativeWindow *window, VkPresentModeKHR mode) {
+ // There is no dynamic switching between non-shared present modes.
+ // All we support is switching between demand and continuous refresh.
+ if (!IsSharedPresentMode(mode))
+ return true;
+
+ int err = native_window_set_auto_refresh(window,
+ mode == VK_PRESENT_MODE_SHARED_CONTINUOUS_REFRESH_KHR);
+ if (err != android::OK) {
+ ALOGE("native_window_set_auto_refresh() failed: %s (%d)",
+ strerror(-err), err);
+ return false;
+ }
+
+ return true;
+}
+
+static VkResult PresentOneSwapchain(
+ VkQueue queue,
+ Swapchain& swapchain,
+ uint32_t imageIndex,
+ const VkPresentRegionKHR *pRegion,
+ const VkPresentTimeGOOGLE *pTime,
+ VkFence presentFence,
+ const VkPresentModeKHR *pPresentMode,
+ uint32_t waitSemaphoreCount,
+ const VkSemaphore *pWaitSemaphores) {
+
+ VkDevice device = GetData(queue).driver_device;
+ const auto& dispatch = GetData(queue).driver;
+
+ Swapchain::Image& img = swapchain.images[imageIndex];
+ VkResult swapchain_result = VK_SUCCESS;
+ VkResult result;
+ int err;
+
+ // XXX: long standing issue: QueueSignalReleaseImageANDROID consumes the
+ // wait semaphores, so this doesn't actually work for the multiple swapchain
+ // case.
+ int fence = -1;
+ result = dispatch.QueueSignalReleaseImageANDROID(
+ queue, waitSemaphoreCount,
+ pWaitSemaphores, img.image, &fence);
+ if (result != VK_SUCCESS) {
+ ALOGE("QueueSignalReleaseImageANDROID failed: %d", result);
+ swapchain_result = result;
+ }
+ if (img.release_fence >= 0)
+ close(img.release_fence);
+ img.release_fence = fence < 0 ? -1 : dup(fence);
+
+ if (swapchain.surface.swapchain_handle == HandleFromSwapchain(&swapchain)) {
+ ANativeWindow* window = swapchain.surface.window.get();
+ if (swapchain_result == VK_SUCCESS) {
+
+ if (presentFence != VK_NULL_HANDLE) {
+ int fence_copy = fence < 0 ? -1 : dup(fence);
+ VkImportFenceFdInfoKHR iffi = {
+ VK_STRUCTURE_TYPE_IMPORT_FENCE_FD_INFO_KHR,
+ nullptr,
+ presentFence,
+ VK_FENCE_IMPORT_TEMPORARY_BIT,
+ VK_EXTERNAL_FENCE_HANDLE_TYPE_SYNC_FD_BIT,
+ fence_copy,
+ };
+ if (VK_SUCCESS != dispatch.ImportFenceFdKHR(device, &iffi) && fence_copy >= 0) {
+ // ImportFenceFdKHR takes ownership only if it succeeds
+ close(fence_copy);
+ }
+ }
+
+ if (pRegion) {
+ SetSwapchainSurfaceDamage(window, pRegion);
+ }
+ if (pTime) {
+ SetSwapchainFrameTimestamp(swapchain, pTime);
+ }
+ if (pPresentMode) {
+ if (!SetSwapchainPresentMode(window, *pPresentMode))
+ swapchain_result = WorstPresentResult(swapchain_result,
+ VK_ERROR_SURFACE_LOST_KHR);
+ }
+
+ err = window->queueBuffer(window, img.buffer.get(), fence);
+ // queueBuffer always closes fence, even on error
+ if (err != android::OK) {
+ ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err);
+ swapchain_result = WorstPresentResult(
+ swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
+ } else {
+ if (img.dequeue_fence >= 0) {
+ close(img.dequeue_fence);
+ img.dequeue_fence = -1;
+ }
+ img.dequeued = false;
+ }
+
+ // If the swapchain is in shared mode, immediately dequeue the
+ // buffer so it can be presented again without an intervening
+ // call to AcquireNextImageKHR. We expect to get the same buffer
+ // back from every call to dequeueBuffer in this mode.
+ if (swapchain.shared && swapchain_result == VK_SUCCESS) {
+ ANativeWindowBuffer* buffer;
+ int fence_fd;
+ err = window->dequeueBuffer(window, &buffer, &fence_fd);
+ if (err != android::OK) {
+ ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err);
+ swapchain_result = WorstPresentResult(swapchain_result,
+ VK_ERROR_SURFACE_LOST_KHR);
+ } else if (img.buffer != buffer) {
+ ALOGE("got wrong image back for shared swapchain");
+ swapchain_result = WorstPresentResult(swapchain_result,
+ VK_ERROR_SURFACE_LOST_KHR);
+ } else {
+ img.dequeue_fence = fence_fd;
+ img.dequeued = true;
+ }
+ }
+ }
+ if (swapchain_result != VK_SUCCESS) {
+ OrphanSwapchain(device, &swapchain);
+ }
+ // Android will only return VK_SUBOPTIMAL_KHR for vkQueuePresentKHR,
+ // and only when the window's transform/rotation changes. Extent
+ // changes will not cause VK_SUBOPTIMAL_KHR because of the
+ // application issues that were caused when the following transform
+ // change was added.
+ int window_transform_hint;
+ err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
+ &window_transform_hint);
+ if (err != android::OK) {
+ ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
+ strerror(-err), err);
+ swapchain_result = WorstPresentResult(
+ swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
+ }
+ if (swapchain.pre_transform != window_transform_hint) {
+ swapchain_result =
+ WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR);
+ }
+ } else {
+ ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence,
+ img, true);
+ swapchain_result = VK_ERROR_OUT_OF_DATE_KHR;
+ }
+
+ return swapchain_result;
+}
+
VKAPI_ATTR
VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
ATRACE_CALL();
@@ -1824,13 +2465,14 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
"vkQueuePresentKHR: invalid VkPresentInfoKHR structure type %d",
present_info->sType);
- VkDevice device = GetData(queue).driver_device;
- const auto& dispatch = GetData(queue).driver;
VkResult final_result = VK_SUCCESS;
// Look at the pNext chain for supported extension structs:
const VkPresentRegionsKHR* present_regions = nullptr;
const VkPresentTimesInfoGOOGLE* present_times = nullptr;
+ const VkSwapchainPresentFenceInfoEXT* present_fences = nullptr;
+ const VkSwapchainPresentModeInfoEXT* present_modes = nullptr;
+
const VkPresentRegionsKHR* next =
reinterpret_cast<const VkPresentRegionsKHR*>(present_info->pNext);
while (next) {
@@ -1842,6 +2484,14 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
present_times =
reinterpret_cast<const VkPresentTimesInfoGOOGLE*>(next);
break;
+ case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_FENCE_INFO_EXT:
+ present_fences =
+ reinterpret_cast<const VkSwapchainPresentFenceInfoEXT*>(next);
+ break;
+ case VK_STRUCTURE_TYPE_SWAPCHAIN_PRESENT_MODE_INFO_EXT:
+ present_modes =
+ reinterpret_cast<const VkSwapchainPresentModeInfoEXT*>(next);
+ break;
default:
ALOGV("QueuePresentKHR ignoring unrecognized pNext->sType = %x",
next->sType);
@@ -1857,174 +2507,34 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
present_times->swapchainCount != present_info->swapchainCount,
"VkPresentTimesInfoGOOGLE::swapchainCount != "
"VkPresentInfo::swapchainCount");
+ ALOGV_IF(present_fences &&
+ present_fences->swapchainCount != present_info->swapchainCount,
+ "VkSwapchainPresentFenceInfoEXT::swapchainCount != "
+ "VkPresentInfo::swapchainCount");
+ ALOGV_IF(present_modes &&
+ present_modes->swapchainCount != present_info->swapchainCount,
+ "VkSwapchainPresentModeInfoEXT::swapchainCount != "
+ "VkPresentInfo::swapchainCount");
+
const VkPresentRegionKHR* regions =
(present_regions) ? present_regions->pRegions : nullptr;
const VkPresentTimeGOOGLE* times =
(present_times) ? present_times->pTimes : nullptr;
- const VkAllocationCallbacks* allocator = &GetData(device).allocator;
- android_native_rect_t* rects = nullptr;
- uint32_t nrects = 0;
for (uint32_t sc = 0; sc < present_info->swapchainCount; sc++) {
Swapchain& swapchain =
*SwapchainFromHandle(present_info->pSwapchains[sc]);
- uint32_t image_idx = present_info->pImageIndices[sc];
- Swapchain::Image& img = swapchain.images[image_idx];
- const VkPresentRegionKHR* region =
- (regions && !swapchain.mailbox_mode) ? &regions[sc] : nullptr;
- const VkPresentTimeGOOGLE* time = (times) ? &times[sc] : nullptr;
- VkResult swapchain_result = VK_SUCCESS;
- VkResult result;
- int err;
-
- int fence = -1;
- result = dispatch.QueueSignalReleaseImageANDROID(
- queue, present_info->waitSemaphoreCount,
- present_info->pWaitSemaphores, img.image, &fence);
- if (result != VK_SUCCESS) {
- ALOGE("QueueSignalReleaseImageANDROID failed: %d", result);
- swapchain_result = result;
- }
- if (img.release_fence >= 0)
- close(img.release_fence);
- img.release_fence = fence < 0 ? -1 : dup(fence);
-
- if (swapchain.surface.swapchain_handle ==
- present_info->pSwapchains[sc]) {
- ANativeWindow* window = swapchain.surface.window.get();
- if (swapchain_result == VK_SUCCESS) {
- if (region) {
- // Process the incremental-present hint for this swapchain:
- uint32_t rcount = region->rectangleCount;
- if (rcount > nrects) {
- android_native_rect_t* new_rects =
- static_cast<android_native_rect_t*>(
- allocator->pfnReallocation(
- allocator->pUserData, rects,
- sizeof(android_native_rect_t) * rcount,
- alignof(android_native_rect_t),
- VK_SYSTEM_ALLOCATION_SCOPE_COMMAND));
- if (new_rects) {
- rects = new_rects;
- nrects = rcount;
- } else {
- rcount = 0; // Ignore the hint for this swapchain
- }
- }
- for (uint32_t r = 0; r < rcount; ++r) {
- if (region->pRectangles[r].layer > 0) {
- ALOGV(
- "vkQueuePresentKHR ignoring invalid layer "
- "(%u); using layer 0 instead",
- region->pRectangles[r].layer);
- }
- int x = region->pRectangles[r].offset.x;
- int y = region->pRectangles[r].offset.y;
- int width = static_cast<int>(
- region->pRectangles[r].extent.width);
- int height = static_cast<int>(
- region->pRectangles[r].extent.height);
- android_native_rect_t* cur_rect = &rects[r];
- cur_rect->left = x;
- cur_rect->top = y + height;
- cur_rect->right = x + width;
- cur_rect->bottom = y;
- }
- native_window_set_surface_damage(window, rects, rcount);
- }
- if (time) {
- if (!swapchain.frame_timestamps_enabled) {
- ALOGV(
- "Calling "
- "native_window_enable_frame_timestamps(true)");
- native_window_enable_frame_timestamps(window, true);
- swapchain.frame_timestamps_enabled = true;
- }
-
- // Record the nativeFrameId so it can be later correlated to
- // this present.
- uint64_t nativeFrameId = 0;
- err = native_window_get_next_frame_id(
- window, &nativeFrameId);
- if (err != android::OK) {
- ALOGE("Failed to get next native frame ID.");
- }
-
- // Add a new timing record with the user's presentID and
- // the nativeFrameId.
- swapchain.timing.emplace_back(time, nativeFrameId);
- while (swapchain.timing.size() > MAX_TIMING_INFOS) {
- swapchain.timing.erase(swapchain.timing.begin());
- }
- if (time->desiredPresentTime) {
- // Set the desiredPresentTime:
- ALOGV(
- "Calling "
- "native_window_set_buffers_timestamp(%" PRId64 ")",
- time->desiredPresentTime);
- native_window_set_buffers_timestamp(
- window,
- static_cast<int64_t>(time->desiredPresentTime));
- }
- }
-
- err = window->queueBuffer(window, img.buffer.get(), fence);
- // queueBuffer always closes fence, even on error
- if (err != android::OK) {
- ALOGE("queueBuffer failed: %s (%d)", strerror(-err), err);
- swapchain_result = WorstPresentResult(
- swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
- } else {
- if (img.dequeue_fence >= 0) {
- close(img.dequeue_fence);
- img.dequeue_fence = -1;
- }
- img.dequeued = false;
- }
- // If the swapchain is in shared mode, immediately dequeue the
- // buffer so it can be presented again without an intervening
- // call to AcquireNextImageKHR. We expect to get the same buffer
- // back from every call to dequeueBuffer in this mode.
- if (swapchain.shared && swapchain_result == VK_SUCCESS) {
- ANativeWindowBuffer* buffer;
- int fence_fd;
- err = window->dequeueBuffer(window, &buffer, &fence_fd);
- if (err != android::OK) {
- ALOGE("dequeueBuffer failed: %s (%d)", strerror(-err), err);
- swapchain_result = WorstPresentResult(swapchain_result,
- VK_ERROR_SURFACE_LOST_KHR);
- } else if (img.buffer != buffer) {
- ALOGE("got wrong image back for shared swapchain");
- swapchain_result = WorstPresentResult(swapchain_result,
- VK_ERROR_SURFACE_LOST_KHR);
- } else {
- img.dequeue_fence = fence_fd;
- img.dequeued = true;
- }
- }
- }
- if (swapchain_result != VK_SUCCESS) {
- OrphanSwapchain(device, &swapchain);
- }
- int window_transform_hint;
- err = window->query(window, NATIVE_WINDOW_TRANSFORM_HINT,
- &window_transform_hint);
- if (err != android::OK) {
- ALOGE("NATIVE_WINDOW_TRANSFORM_HINT query failed: %s (%d)",
- strerror(-err), err);
- swapchain_result = WorstPresentResult(
- swapchain_result, VK_ERROR_SURFACE_LOST_KHR);
- }
- if (swapchain.pre_transform != window_transform_hint) {
- swapchain_result =
- WorstPresentResult(swapchain_result, VK_SUBOPTIMAL_KHR);
- }
- } else {
- ReleaseSwapchainImage(device, swapchain.shared, nullptr, fence,
- img, true);
- swapchain_result = VK_ERROR_OUT_OF_DATE_KHR;
- }
+ VkResult swapchain_result = PresentOneSwapchain(
+ queue,
+ swapchain,
+ present_info->pImageIndices[sc],
+ (regions && !swapchain.mailbox_mode) ? &regions[sc] : nullptr,
+ times ? &times[sc] : nullptr,
+ present_fences ? present_fences->pFences[sc] : VK_NULL_HANDLE,
+ present_modes ? &present_modes->pPresentModes[sc] : nullptr,
+ present_info->waitSemaphoreCount,
+ present_info->pWaitSemaphores);
if (present_info->pResults)
present_info->pResults[sc] = swapchain_result;
@@ -2032,9 +2542,6 @@ VkResult QueuePresentKHR(VkQueue queue, const VkPresentInfoKHR* present_info) {
if (swapchain_result != final_result)
final_result = WorstPresentResult(final_result, swapchain_result);
}
- if (rects) {
- allocator->pfnFree(allocator->pUserData, rects);
- }
return final_result;
}
@@ -2047,9 +2554,7 @@ VkResult GetRefreshCycleDurationGOOGLE(
ATRACE_CALL();
Swapchain& swapchain = *SwapchainFromHandle(swapchain_handle);
- VkResult result = VK_SUCCESS;
-
- pDisplayTimingProperties->refreshDuration = swapchain.get_refresh_duration();
+ VkResult result = swapchain.get_refresh_duration(pDisplayTimingProperties->refreshDuration);
return result;
}
@@ -2194,7 +2699,12 @@ static void InterceptBindImageMemory2(
.stride = buffer->stride,
.format = buffer->format,
.usage = int(buffer->usage),
+ .usage3 = buffer->usage,
+ .ahb = ANativeWindowBuffer_getHardwareBuffer(buffer),
};
+ android_convertGralloc0To1Usage(int(buffer->usage),
+ &native_buffer.usage2.producer,
+ &native_buffer.usage2.consumer);
// Reserve enough space to avoid letting re-allocation invalidate the
// addresses of the elements inside.
out_native_buffers->reserve(bind_info_count);
@@ -2250,5 +2760,35 @@ VkResult BindImageMemory2KHR(VkDevice device,
out_bind_infos.empty() ? pBindInfos : out_bind_infos.data());
}
+VKAPI_ATTR
+VkResult ReleaseSwapchainImagesEXT(VkDevice /*device*/,
+ const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo) {
+ ATRACE_CALL();
+
+ Swapchain& swapchain = *SwapchainFromHandle(pReleaseInfo->swapchain);
+ ANativeWindow* window = swapchain.surface.window.get();
+
+ // If in shared present mode, don't actually release the image back to the BQ.
+ // Both sides share it forever.
+ if (swapchain.shared)
+ return VK_SUCCESS;
+
+ for (uint32_t i = 0; i < pReleaseInfo->imageIndexCount; i++) {
+ Swapchain::Image& img = swapchain.images[pReleaseInfo->pImageIndices[i]];
+ window->cancelBuffer(window, img.buffer.get(), img.dequeue_fence);
+
+ // cancelBuffer has taken ownership of the dequeue fence
+ img.dequeue_fence = -1;
+ // if we're still holding a release fence, get rid of it now
+ if (img.release_fence >= 0) {
+ close(img.release_fence);
+ img.release_fence = -1;
+ }
+ img.dequeued = false;
+ }
+
+ return VK_SUCCESS;
+}
+
} // namespace driver
} // namespace vulkan
diff --git a/vulkan/libvulkan/swapchain.h b/vulkan/libvulkan/swapchain.h
index 4912ef1a33..280fe9b5a3 100644
--- a/vulkan/libvulkan/swapchain.h
+++ b/vulkan/libvulkan/swapchain.h
@@ -46,6 +46,7 @@ VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceCapabilities2KHR(VkPhysicalDevice ph
VKAPI_ATTR VkResult GetPhysicalDeviceSurfaceFormats2KHR(VkPhysicalDevice physicalDevice, const VkPhysicalDeviceSurfaceInfo2KHR* pSurfaceInfo, uint32_t* pSurfaceFormatCount, VkSurfaceFormat2KHR* pSurfaceFormats);
VKAPI_ATTR VkResult BindImageMemory2(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
VKAPI_ATTR VkResult BindImageMemory2KHR(VkDevice device, uint32_t bindInfoCount, const VkBindImageMemoryInfo* pBindInfos);
+VKAPI_ATTR VkResult ReleaseSwapchainImagesEXT(VkDevice device, const VkReleaseSwapchainImagesInfoEXT* pReleaseInfo);
// clang-format on
} // namespace driver
diff --git a/vulkan/nulldrv/Android.bp b/vulkan/nulldrv/Android.bp
index a6d540bb93..5112e14dc8 100644
--- a/vulkan/nulldrv/Android.bp
+++ b/vulkan/nulldrv/Android.bp
@@ -46,5 +46,8 @@ cc_library_shared {
"hwvulkan_headers",
"vulkan_headers",
],
- shared_libs: ["liblog"],
+ shared_libs: [
+ "liblog",
+ "libnativewindow",
+ ],
}
diff --git a/vulkan/nulldrv/null_driver.cpp b/vulkan/nulldrv/null_driver.cpp
index f998b1ad18..973e71c892 100644
--- a/vulkan/nulldrv/null_driver.cpp
+++ b/vulkan/nulldrv/null_driver.cpp
@@ -14,6 +14,7 @@
* limitations under the License.
*/
+#include <android/hardware_buffer.h>
#include <hardware/hwvulkan.h>
#include <errno.h>
@@ -959,6 +960,17 @@ VkResult GetSwapchainGrallocUsage3ANDROID(
return VK_SUCCESS;
}
+VkResult GetSwapchainGrallocUsage4ANDROID(
+ VkDevice,
+ const VkGrallocUsageInfo2ANDROID* grallocUsageInfo,
+ uint64_t* grallocUsage) {
+ // The null driver never reads or writes the gralloc buffer
+ ALOGV("TODO: vk%s - grallocUsageInfo->format:%i", __FUNCTION__,
+ grallocUsageInfo->format);
+ *grallocUsage = 0;
+ return VK_SUCCESS;
+}
+
VkResult AcquireImageANDROID(VkDevice,
VkImage,
int fence,
diff --git a/vulkan/nulldrv/null_driver_gen.cpp b/vulkan/nulldrv/null_driver_gen.cpp
index 0cb7bd3185..d34851e536 100644
--- a/vulkan/nulldrv/null_driver_gen.cpp
+++ b/vulkan/nulldrv/null_driver_gen.cpp
@@ -16,6 +16,8 @@
// WARNING: This file is generated. See ../README.md for instructions.
+#include <android/hardware_buffer.h>
+
#include <algorithm>
#include "null_driver_gen.h"
@@ -262,6 +264,7 @@ const NameProc kInstanceProcs[] = {
{"vkGetSemaphoreCounterValue", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSemaphoreCounterValue>(GetSemaphoreCounterValue))},
{"vkGetSwapchainGrallocUsage2ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage2ANDROID>(GetSwapchainGrallocUsage2ANDROID))},
{"vkGetSwapchainGrallocUsage3ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage3ANDROID>(GetSwapchainGrallocUsage3ANDROID))},
+ {"vkGetSwapchainGrallocUsage4ANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsage4ANDROID>(GetSwapchainGrallocUsage4ANDROID))},
{"vkGetSwapchainGrallocUsageANDROID", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkGetSwapchainGrallocUsageANDROID>(GetSwapchainGrallocUsageANDROID))},
{"vkInvalidateMappedMemoryRanges", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkInvalidateMappedMemoryRanges>(InvalidateMappedMemoryRanges))},
{"vkMapMemory", reinterpret_cast<PFN_vkVoidFunction>(static_cast<PFN_vkMapMemory>(MapMemory))},
diff --git a/vulkan/nulldrv/null_driver_gen.h b/vulkan/nulldrv/null_driver_gen.h
index 5c7fea0fa8..fb3bd05e07 100644
--- a/vulkan/nulldrv/null_driver_gen.h
+++ b/vulkan/nulldrv/null_driver_gen.h
@@ -210,6 +210,7 @@ VKAPI_ATTR void GetDescriptorSetLayoutSupport(VkDevice device, const VkDescripto
VKAPI_ATTR VkResult GetSwapchainGrallocUsageANDROID(VkDevice device, VkFormat format, VkImageUsageFlags imageUsage, int* grallocUsage);
VKAPI_ATTR VkResult GetSwapchainGrallocUsage2ANDROID(VkDevice device, VkFormat format, VkImageUsageFlags imageUsage, VkSwapchainImageUsageFlagsANDROID swapchainImageUsage, uint64_t* grallocConsumerUsage, uint64_t* grallocProducerUsage);
VKAPI_ATTR VkResult GetSwapchainGrallocUsage3ANDROID(VkDevice device, const VkGrallocUsageInfoANDROID* grallocUsageInfo, uint64_t* grallocUsage);
+VKAPI_ATTR VkResult GetSwapchainGrallocUsage4ANDROID(VkDevice device, const VkGrallocUsageInfo2ANDROID* grallocUsageInfo, uint64_t* grallocUsage);
VKAPI_ATTR VkResult AcquireImageANDROID(VkDevice device, VkImage image, int nativeFenceFd, VkSemaphore semaphore, VkFence fence);
VKAPI_ATTR VkResult QueueSignalReleaseImageANDROID(VkQueue queue, uint32_t waitSemaphoreCount, const VkSemaphore* pWaitSemaphores, VkImage image, int* pNativeFenceFd);
VKAPI_ATTR VkResult CreateRenderPass2(VkDevice device, const VkRenderPassCreateInfo2* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkRenderPass* pRenderPass);
diff --git a/vulkan/scripts/driver_generator.py b/vulkan/scripts/driver_generator.py
index af56764a21..78b550c202 100644
--- a/vulkan/scripts/driver_generator.py
+++ b/vulkan/scripts/driver_generator.py
@@ -35,6 +35,8 @@ _INTERCEPTED_EXTENSIONS = [
'VK_KHR_surface',
'VK_KHR_surface_protected_capabilities',
'VK_KHR_swapchain',
+ 'VK_EXT_swapchain_maintenance1',
+ 'VK_EXT_surface_maintenance1',
]
# Extensions known to vulkan::driver level.
@@ -46,6 +48,7 @@ _KNOWN_EXTENSIONS = _INTERCEPTED_EXTENSIONS + [
'VK_KHR_external_memory_capabilities',
'VK_KHR_external_semaphore_capabilities',
'VK_KHR_external_fence_capabilities',
+ 'VK_KHR_external_fence_fd',
]
# Functions needed at vulkan::driver level.
@@ -112,6 +115,9 @@ _NEEDED_COMMANDS = [
# For promoted VK_KHR_external_fence_capabilities
'vkGetPhysicalDeviceExternalFenceProperties',
'vkGetPhysicalDeviceExternalFencePropertiesKHR',
+
+ # VK_KHR_swapchain_maintenance1 requirement
+ 'vkImportFenceFdKHR',
]
# Functions intercepted at vulkan::driver level.
diff --git a/vulkan/scripts/generator_common.py b/vulkan/scripts/generator_common.py
index c25c6cbda0..866c1b7b75 100644
--- a/vulkan/scripts/generator_common.py
+++ b/vulkan/scripts/generator_common.py
@@ -70,6 +70,7 @@ _OPTIONAL_COMMANDS = [
'vkGetSwapchainGrallocUsageANDROID',
'vkGetSwapchainGrallocUsage2ANDROID',
'vkGetSwapchainGrallocUsage3ANDROID',
+ 'vkGetSwapchainGrallocUsage4ANDROID',
]
# Dict for mapping dispatch table to a type.
diff --git a/vulkan/vkjson/Android.bp b/vulkan/vkjson/Android.bp
index b544245a7a..de4271d47d 100644
--- a/vulkan/vkjson/Android.bp
+++ b/vulkan/vkjson/Android.bp
@@ -7,8 +7,19 @@ package {
default_applicable_licenses: ["frameworks_native_license"],
}
-cc_library_shared {
+cc_defaults {
+ name: "libvkjson_deps",
+ shared_libs: [
+ "libjsoncpp",
+ "libvulkan",
+ ],
+}
+
+cc_library_static {
name: "libvkjson",
+ defaults: [
+ "libvkjson_deps",
+ ],
srcs: [
"vkjson.cc",
"vkjson_instance.cc",
@@ -24,10 +35,6 @@ cc_library_shared {
export_include_dirs: [
".",
],
- shared_libs: [
- "libjsoncpp",
- "libvulkan",
- ],
export_shared_lib_headers: [
"libvulkan",
],