From 991c800e43ad682b399207009909148c647f236b Mon Sep 17 00:00:00 2001 From: Steve Berbary Date: Mon, 3 Oct 2022 19:31:52 +0000 Subject: [PATCH 01/18] Update bluejay SVN to 19 Bug: 250644806 Change-Id: Iee0902caddc1f8bbcba8f588819d0404a8a3b902 --- device-bluejay.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bluejay.mk b/device-bluejay.mk index 2f9e699..30a3e30 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -120,7 +120,7 @@ PRODUCT_SOONG_NAMESPACES += \ # Increment the SVN for any official public releases PRODUCT_VENDOR_PROPERTIES += \ - ro.vendor.build.svn=18 + ro.vendor.build.svn=19 # DCK properties based on target PRODUCT_PROPERTY_OVERRIDES += \ From 8a88005ee683c1dbfb70addbf969d6f42ca7cdc4 Mon Sep 17 00:00:00 2001 From: vincenttew Date: Mon, 25 Jul 2022 15:09:46 +0800 Subject: [PATCH 02/18] audio: update audio tuning table Change List : Optimize HH NB TX NS performance. Hardware verification: - Verified Handset/Speaker phone/USB-Headset function work well. Bug: 252916530 Signed-off-by: vincenttew Change-Id: I3a35471fd649d5a94f326aae861f0c121de9dfa3 --- audio/bluejay/tuning/fortemedia/HANDSFREE.dat | Bin 56058 -> 56058 bytes .../bluejay/tuning/fortemedia/HANDSFREE.mods | 56 +++++++++--------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/audio/bluejay/tuning/fortemedia/HANDSFREE.dat b/audio/bluejay/tuning/fortemedia/HANDSFREE.dat index 1b97ddcfe37adb04045b5af38534099add308e9d..5609b52404ca889aa4ac5185f85df866f7025477 100644 GIT binary patch delta 83 zcmV-Z0IdJ|wFCOK1F&9I4G?n001$n~0Pugu006UVRZ9U05C9ti6#yIo5R<=F7YPUe pBLNQpBmoGM-&Q0EU@idwKraCRV3Q12R{=niWLHB0e6y%mQiCaN7lHr) delta 73 zcmV-P0Ji`7wFCOK1F&9IlNO91vwl@L0SOWS8vzvn903xOzg8Cs4FDqn4*(>h0WuS! diff --git a/audio/bluejay/tuning/fortemedia/HANDSFREE.mods b/audio/bluejay/tuning/fortemedia/HANDSFREE.mods index 19fbd36..3648ee1 100644 --- a/audio/bluejay/tuning/fortemedia/HANDSFREE.mods +++ b/audio/bluejay/tuning/fortemedia/HANDSFREE.mods @@ -1,13 +1,13 @@ #PLATFORM_NAME gChip #EXPORT_FLAG HANDSFREE #SINGLE_API_VER 1.1.6 -#SAVE_TIME 2022-03-29 15:50:02 +#SAVE_TIME 2022-07-21 12:24:17 #CASE_NAME HANDSFREE-HANDSFREE-RESERVE1-FB #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -905,7 +905,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x006C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0004 //RX_SAMPLINGFREQ_SIG @@ -1761,7 +1761,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -2659,7 +2659,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x006C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0004 //RX_SAMPLINGFREQ_SIG @@ -3515,7 +3515,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -4413,7 +4413,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x006C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0004 //RX_SAMPLINGFREQ_SIG @@ -5269,7 +5269,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -5467,10 +5467,10 @@ 194 0x0000 //TX_NORMENERTH 195 0x0000 //TX_NORMENERHIGHTH 196 0x0000 //TX_NORMENERHIGHTHL -197 0x7FF0 //TX_DTD_THR1_0 -198 0x7FF0 //TX_DTD_THR1_1 +197 0x7210 //TX_DTD_THR1_0 +198 0x7D10 //TX_DTD_THR1_1 199 0x7FF0 //TX_DTD_THR1_2 -200 0x7FF0 //TX_DTD_THR1_3 +200 0x7F00 //TX_DTD_THR1_3 201 0x7FF0 //TX_DTD_THR1_4 202 0x7FF0 //TX_DTD_THR1_5 203 0x7FF0 //TX_DTD_THR1_6 @@ -5553,17 +5553,17 @@ 280 0x1000 //TX_B_POST_FLT_1 281 0x0010 //TX_NS_LVL_CTRL_0 282 0x0012 //TX_NS_LVL_CTRL_1 -283 0x0012 //TX_NS_LVL_CTRL_2 +283 0x0010 //TX_NS_LVL_CTRL_2 284 0x0015 //TX_NS_LVL_CTRL_3 -285 0x0012 //TX_NS_LVL_CTRL_4 +285 0x0010 //TX_NS_LVL_CTRL_4 286 0x0012 //TX_NS_LVL_CTRL_5 287 0x0011 //TX_NS_LVL_CTRL_6 288 0x0011 //TX_NS_LVL_CTRL_7 289 0x000F //TX_MIN_GAIN_S_0 290 0x000F //TX_MIN_GAIN_S_1 -291 0x000D //TX_MIN_GAIN_S_2 +291 0x0008 //TX_MIN_GAIN_S_2 292 0x000F //TX_MIN_GAIN_S_3 -293 0x000F //TX_MIN_GAIN_S_4 +293 0x0008 //TX_MIN_GAIN_S_4 294 0x000F //TX_MIN_GAIN_S_5 295 0x0010 //TX_MIN_GAIN_S_6 296 0x000F //TX_MIN_GAIN_S_7 @@ -5572,9 +5572,9 @@ 299 0x0000 //TX_NMOS_SUP_MENSA 300 0x7FFF //TX_SNRI_SUP_0 301 0x4000 //TX_SNRI_SUP_1 -302 0x3000 //TX_SNRI_SUP_2 +302 0x6000 //TX_SNRI_SUP_2 303 0x4000 //TX_SNRI_SUP_3 -304 0x2400 //TX_SNRI_SUP_4 +304 0x6000 //TX_SNRI_SUP_4 305 0x4000 //TX_SNRI_SUP_5 306 0x4000 //TX_SNRI_SUP_6 307 0x7FFF //TX_SNRI_SUP_7 @@ -5596,7 +5596,7 @@ 323 0x1000 //TX_B_POST_FILT_1 324 0x1000 //TX_B_POST_FILT_2 325 0x1000 //TX_B_POST_FILT_3 -326 0x5000 //TX_B_POST_FILT_4 +326 0x4000 //TX_B_POST_FILT_4 327 0x3000 //TX_B_POST_FILT_5 328 0x1000 //TX_B_POST_FILT_6 329 0x2000 //TX_B_POST_FILT_7 @@ -5613,7 +5613,7 @@ 340 0x7900 //TX_LAMBDA_PFILT_S_1 341 0x7C00 //TX_LAMBDA_PFILT_S_2 342 0x7900 //TX_LAMBDA_PFILT_S_3 -343 0x7000 //TX_LAMBDA_PFILT_S_4 +343 0x7C00 //TX_LAMBDA_PFILT_S_4 344 0x7D00 //TX_LAMBDA_PFILT_S_5 345 0x7D00 //TX_LAMBDA_PFILT_S_6 346 0x7D00 //TX_LAMBDA_PFILT_S_7 @@ -6167,7 +6167,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x043C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0000 //RX_SAMPLINGFREQ_SIG @@ -7023,7 +7023,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -7921,7 +7921,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x043C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0001 //RX_SAMPLINGFREQ_SIG @@ -8777,7 +8777,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -9675,7 +9675,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x043C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0003 //RX_SAMPLINGFREQ_SIG @@ -10531,7 +10531,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -11429,7 +11429,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x042C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0004 //RX_SAMPLINGFREQ_SIG @@ -12285,7 +12285,7 @@ #PARAM_MODE FULL #PARAM_TYPE TX+RX #TOTAL_CUSTOM_STEP 7 -#TX +#TX 0 0x0001 //TX_OPERATION_MODE_0 1 0x0001 //TX_OPERATION_MODE_1 2 0x0033 //TX_PATCH_REG @@ -13183,7 +13183,7 @@ 894 0xC000 //TX_FASTNS_SSA_THHFL 895 0xCCCC //TX_FASTNS_SSA_THLFH 896 0xD999 //TX_FASTNS_SSA_THHFH -#RX +#RX 0 0x042C //RX_RECVFUNC_MODE_0 1 0x0000 //RX_RECVFUNC_MODE_1 2 0x0004 //RX_SAMPLINGFREQ_SIG From 28b77eb8d718f114a974db239bded9af8c948f6e Mon Sep 17 00:00:00 2001 From: Shawn Lin Date: Mon, 17 Oct 2022 17:27:56 +0800 Subject: [PATCH 03/18] Update status bar height to center the hole punch cutout Bug: 253071694 Test: make Change-Id: I16b512f61d3bf462992b04691eb7dfac40c91461 --- bluejay/overlay/frameworks/base/core/res/res/values/config.xml | 2 +- bluejay/overlay/frameworks/base/core/res/res/values/dimens.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bluejay/overlay/frameworks/base/core/res/res/values/config.xml b/bluejay/overlay/frameworks/base/core/res/res/values/config.xml index a1b0ce3..b61c541 100644 --- a/bluejay/overlay/frameworks/base/core/res/res/values/config.xml +++ b/bluejay/overlay/frameworks/base/core/res/res/values/config.xml @@ -73,7 +73,7 @@ M 480,0 h 145 - v 118 + v 132 h -145 Z @left diff --git a/bluejay/overlay/frameworks/base/core/res/res/values/dimens.xml b/bluejay/overlay/frameworks/base/core/res/res/values/dimens.xml index 9051ca3..011c49d 100644 --- a/bluejay/overlay/frameworks/base/core/res/res/values/dimens.xml +++ b/bluejay/overlay/frameworks/base/core/res/res/values/dimens.xml @@ -30,7 +30,7 @@ Do not read this dimen directly. Use {@link SystemBarUtils#getStatusBarHeight} instead. --> 28dp - 118px + 132px 28dp From 7ad6474a70ebaf64c8b840c1de80354ac4f48743 Mon Sep 17 00:00:00 2001 From: Sayanna Chandula Date: Mon, 31 Oct 2022 12:00:28 -0700 Subject: [PATCH 04/18] thermal: update shutdown thresholds for Tj zones Bug: 256193172 Test: Build and boot on device Change-Id: I5e0a5392fdb5fcbcf53b2ef13ed432bcf965d55f Signed-off-by: Sayanna Chandula --- thermal_info_config_bluejay.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/thermal_info_config_bluejay.json b/thermal_info_config_bluejay.json index 92c197d..7962173 100644 --- a/thermal_info_config_bluejay.json +++ b/thermal_info_config_bluejay.json @@ -1021,7 +1021,7 @@ "NAN", "NAN", "NAN", - 115.0 + "NAN" ], "VrThreshold":"NAN", "Multiplier":0.001 From 99e9f82364c3d06cdd3481bc9be68829935bb780 Mon Sep 17 00:00:00 2001 From: Steve Berbary Date: Thu, 10 Nov 2022 19:36:09 +0000 Subject: [PATCH 05/18] Update bluejay SVN to 20 Bug: 250644806 Change-Id: I47165cbc0d8925d2b529fa3a3d94a9920e8c416a --- device-bluejay.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bluejay.mk b/device-bluejay.mk index ac8110f..d4ecd30 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -122,7 +122,7 @@ PRODUCT_SOONG_NAMESPACES += \ # Increment the SVN for any official public releases PRODUCT_VENDOR_PROPERTIES += \ - ro.vendor.build.svn=19 + ro.vendor.build.svn=20 # DCK properties based on target PRODUCT_PROPERTY_OVERRIDES += \ From dc9d629823010317a145131b06cbe4b98c580108 Mon Sep 17 00:00:00 2001 From: Steve Berbary Date: Fri, 11 Nov 2022 17:40:10 +0000 Subject: [PATCH 06/18] Update bluejay SVN to 21 Bug: 258811679 Change-Id: I8b5f960d0a36dd10f1c374ca9f0628d623845eed --- device-bluejay.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bluejay.mk b/device-bluejay.mk index d4ecd30..6e3a676 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -122,7 +122,7 @@ PRODUCT_SOONG_NAMESPACES += \ # Increment the SVN for any official public releases PRODUCT_VENDOR_PROPERTIES += \ - ro.vendor.build.svn=20 + ro.vendor.build.svn=21 # DCK properties based on target PRODUCT_PROPERTY_OVERRIDES += \ From 136bd6c1cdfd74175a3847483471d5d4b821039a Mon Sep 17 00:00:00 2001 From: Kyle Lin Date: Mon, 14 Nov 2022 12:19:21 +0800 Subject: [PATCH 07/18] powerhint: relax the cap when camera exit Camera hints was canceled at activityStop and applied the rule for a while after LAUNCH hint, exten the LAUNCH HINT to fix it. Bug: 255927500 Test: build and launch/exit camera Change-Id: I48d9c50c36bc974088a55ca70aa87f53ac9d8647 --- powerhint.json | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/powerhint.json b/powerhint.json index 61a6176..3135615 100644 --- a/powerhint.json +++ b/powerhint.json @@ -544,6 +544,29 @@ "Duration": 5000, "Value": "0" }, + { + "PowerHint": "LAUNCH", + "Type": "DoHint", + "Value": "LAUNCH_EXTEND" + }, + { + "PowerHint": "LAUNCH_EXTEND", + "Node": "CPUBigClusterMaxFreq", + "Duration": 2000, + "Value": "9999999" + }, + { + "PowerHint": "LAUNCH_EXTEND", + "Node": "CPUMidClusterMaxFreq", + "Duration": 2000, + "Value": "9999999" + }, + { + "PowerHint": "LAUNCH_EXTEND", + "Node": "CPULittleClusterMaxFreq", + "Duration": 2000, + "Value": "9999999" + }, { "PowerHint": "CAMERA_LAUNCH", "Node": "MemFreq", From 1ef727e4cbef788fa731ae0b4621cf5b3ebc091a Mon Sep 17 00:00:00 2001 From: Rick Yiu Date: Wed, 16 Nov 2022 08:57:12 +0000 Subject: [PATCH 08/18] Limit camera daemon cpuset to little cores when app launch When exiting camera app, camera daemon is still busy in background, which may affect the launch animation of launcher. So, limit the cpuset of camera daemon to little cores for 1 second when there is app launch hint to improve it. This only happens when camera is running. Also bypass this behavior when video recording is active. Bug: 240889279 Test: camera daemon cpuset changed as expected Change-Id: I1d35bacf3d907a3e7ca9f18c213c941b55768500 --- powerhint.json | 110 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 2 deletions(-) diff --git a/powerhint.json b/powerhint.json index 3135615..d3b4df0 100644 --- a/powerhint.json +++ b/powerhint.json @@ -177,7 +177,7 @@ "ResetOnInit": true }, { - "Name": "ReduceRreferIdle", + "Name": "ReducePreferIdle", "Path": "/proc/vendor_sched/reduce_prefer_idle", "Values": [ "0", @@ -198,6 +198,7 @@ "Name": "CDCpuset", "Path": "/dev/cpuset/camera-daemon/cpus", "Values": [ + "0-3", "4-7", "0-7" ], @@ -207,6 +208,7 @@ "Name": "CDHighCpusetCpus", "Path": "/dev/cpuset/camera-daemon-high-group/cpus", "Values": [ + "0-3", "6-7", "0-7" ], @@ -216,6 +218,7 @@ "Name": "CDMidCpusetCpus", "Path": "/dev/cpuset/camera-daemon-mid-group/cpus", "Values": [ + "0-3", "4-5", "0-7" ], @@ -225,6 +228,7 @@ "Name": "CDMidHighCpusetCpus", "Path": "/dev/cpuset/camera-daemon-mid-high-group/cpus", "Values": [ + "0-3", "4-7", "0-7" ], @@ -295,6 +299,16 @@ ], "Type": "Property" }, + { + "Name": "PowerHALCameraRunning", + "Path": "vendor.powerhal.camerarunning", + "Values": [ + "1", + "0" + ], + "Type": "Property", + "ResetOnInit": true + }, { "Name": "INTCAMFreq", "Path": "/sys/devices/platform/17000030.devfreq_intcam/devfreq/17000030.devfreq_intcam/min_freq", @@ -478,6 +492,30 @@ } ], "Actions": [ + { + "PowerHint": "CDCPUSET_RESTRICTED", + "Node": "CDCpuset", + "Duration": 1000, + "Value": "0-3" + }, + { + "PowerHint": "CDHIGHCPUSETCPUS_RESTRICTED", + "Node": "CDHighCpusetCpus", + "Duration": 1000, + "Value": "0-3" + }, + { + "PowerHint": "CDMIDCPUSETCPUS_RESTRICTED", + "Node": "CDMidCpusetCpus", + "Duration": 1000, + "Value": "0-3" + }, + { + "PowerHint": "CDMIDHIGHCPUSETCPUS_RESTRICTED", + "Node": "CDMidHighCpusetCpus", + "Duration": 1000, + "Value": "0-3" + }, { "PowerHint": "LAUNCH", "Node": "UClampThreshold", @@ -486,7 +524,7 @@ }, { "PowerHint": "LAUNCH", - "Node": "ReduceRreferIdle", + "Node": "ReducePreferIdle", "Duration": 5000, "Value": "0" }, @@ -549,6 +587,30 @@ "Type": "DoHint", "Value": "LAUNCH_EXTEND" }, + { + "PowerHint": "LAUNCH", + "Type": "DoHint", + "EnableProperty": "vendor.powerhal.camerarunning", + "Value": "CDCPUSET_RESTRICTED" + }, + { + "PowerHint": "LAUNCH", + "Type": "DoHint", + "EnableProperty": "vendor.powerhal.camerarunning", + "Value": "CDHIGHCPUSETCPUS_RESTRICTED" + }, + { + "PowerHint": "LAUNCH", + "Type": "DoHint", + "EnableProperty": "vendor.powerhal.camerarunning", + "Value": "CDMIDCPUSETCPUS_RESTRICTED" + }, + { + "PowerHint": "LAUNCH", + "Type": "DoHint", + "EnableProperty": "vendor.powerhal.camerarunning", + "Value": "CDMIDHIGHCPUSETCPUS_RESTRICTED" + }, { "PowerHint": "LAUNCH_EXTEND", "Node": "CPUBigClusterMaxFreq", @@ -825,6 +887,12 @@ "Duration": 3000, "Value": "9999999" }, + { + "PowerHint": "CAMERA_STREAMING_EXTREME", + "Node": "PowerHALCameraRunning", + "Duration": 0, + "Value": "1" + }, { "PowerHint": "CAMERA_STREAMING_EXTREME", "Node": "CPULittleClusterMaxFreq", @@ -903,6 +971,12 @@ "Duration": 0, "Value": "0" }, + { + "PowerHint": "CAMERA_STREAMING_HIGH", + "Node": "PowerHALCameraRunning", + "Duration": 0, + "Value": "1" + }, { "PowerHint": "CAMERA_STREAMING_HIGH", "Node": "CPUUtilThreshold", @@ -987,6 +1061,12 @@ "Duration": 0, "Value": "0" }, + { + "PowerHint": "CAMERA_STREAMING_STANDARD", + "Node": "PowerHALCameraRunning", + "Duration": 0, + "Value": "1" + }, { "PowerHint": "CAMERA_STREAMING_STANDARD", "Node": "CDPreferHighCap", @@ -1059,6 +1139,12 @@ "Duration": 0, "Value": "0" }, + { + "PowerHint": "CAMERA_STREAMING_LOW", + "Node": "PowerHALCameraRunning", + "Duration": 0, + "Value": "1" + }, { "PowerHint": "CAMERA_STREAMING_LOW", "Node": "CDPreferHighCap", @@ -1137,6 +1223,26 @@ "Duration": 0, "Value": "0" }, + { + "PowerHint": "CAMERA_VIDEO_RECORDING", + "Type": "MaskHint", + "Value": "CDCPUSET_RESTRICTED" + }, + { + "PowerHint": "CAMERA_VIDEO_RECORDING", + "Type": "MaskHint", + "Value": "CDHIGHCPUSETCPUS_RESTRICTED" + }, + { + "PowerHint": "CAMERA_VIDEO_RECORDING", + "Type": "MaskHint", + "Value": "CDMIDCPUSETCPUS_RESTRICTED" + }, + { + "PowerHint": "CAMERA_VIDEO_RECORDING", + "Type": "MaskHint", + "Value": "CDMIDHIGHCPUSETCPUS_RESTRICTED" + }, { "PowerHint": "CAMERA_FIXED_FPS", "Node": "MemFreq", From 4b6eea9a95cafc4fa05dc7608b183a0765eb00df Mon Sep 17 00:00:00 2001 From: Chris Paulo Date: Thu, 3 Nov 2022 20:48:00 +0000 Subject: [PATCH 09/18] [DO NOT MERGE] device/vibrator: Move vibrator HAL into device folder for l26 Moved L26 portion of vibrator hal from hardware/google/pixel to device/google Bug: 198239103 Test: None Change-Id: I0d3b5c068168ede0cae1e7a99cc8497185b48ec3 Signed-off-by: Chris Paulo --- vibrator/Android.bp | 52 + vibrator/OWNERS | 3 + vibrator/common/Android.bp | 37 + vibrator/common/HardwareBase.cpp | 136 ++ vibrator/common/HardwareBase.h | 221 +++ vibrator/common/utils.h | 173 +++ vibrator/cs40l26/Android.bp | 86 ++ vibrator/cs40l26/Hardware.h | 346 +++++ vibrator/cs40l26/TEST_MAPPING | 10 + vibrator/cs40l26/Vibrator.cpp | 1327 +++++++++++++++++ vibrator/cs40l26/Vibrator.h | 211 +++ ...droid.hardware.vibrator-service.cs40l26.rc | 47 + ...roid.hardware.vibrator-service.cs40l26.xml | 7 + vibrator/cs40l26/device.mk | 6 + vibrator/cs40l26/service.cpp | 55 + vibrator/cs40l26/tests/Android.bp | 35 + vibrator/cs40l26/tests/mocks.h | 80 + vibrator/cs40l26/tests/test-hwapi.cpp | 288 ++++ vibrator/cs40l26/tests/test-hwcal.cpp | 386 +++++ vibrator/cs40l26/tests/test-vibrator.cpp | 682 +++++++++ vibrator/cs40l26/tests/types.h | 33 + vibrator/cs40l26/tests/utils.h | 46 + 22 files changed, 4267 insertions(+) create mode 100644 vibrator/Android.bp create mode 100644 vibrator/OWNERS create mode 100644 vibrator/common/Android.bp create mode 100644 vibrator/common/HardwareBase.cpp create mode 100644 vibrator/common/HardwareBase.h create mode 100644 vibrator/common/utils.h create mode 100644 vibrator/cs40l26/Android.bp create mode 100644 vibrator/cs40l26/Hardware.h create mode 100644 vibrator/cs40l26/TEST_MAPPING create mode 100644 vibrator/cs40l26/Vibrator.cpp create mode 100644 vibrator/cs40l26/Vibrator.h create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc create mode 100644 vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml create mode 100644 vibrator/cs40l26/device.mk create mode 100644 vibrator/cs40l26/service.cpp create mode 100644 vibrator/cs40l26/tests/Android.bp create mode 100644 vibrator/cs40l26/tests/mocks.h create mode 100644 vibrator/cs40l26/tests/test-hwapi.cpp create mode 100644 vibrator/cs40l26/tests/test-hwcal.cpp create mode 100644 vibrator/cs40l26/tests/test-vibrator.cpp create mode 100644 vibrator/cs40l26/tests/types.h create mode 100644 vibrator/cs40l26/tests/utils.h diff --git a/vibrator/Android.bp b/vibrator/Android.bp new file mode 100644 index 0000000..dff6816 --- /dev/null +++ b/vibrator/Android.bp @@ -0,0 +1,52 @@ +// +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_defaults { + name: "PixelVibratorDefaults", + relative_install_path: "hw", + static_libs: [ + "PixelVibratorCommon", + ], + shared_libs: [ + "libbase", + "libbinder_ndk", + "libcutils", + "libhardware", + "liblog", + "libutils", + ], +} + +cc_defaults { + name: "PixelVibratorBinaryDefaults", + defaults: ["PixelVibratorDefaults"], + shared_libs: [ + "android.hardware.vibrator-V2-ndk", + ], +} + +cc_defaults { + name: "PixelVibratorTestDefaults", + defaults: ["PixelVibratorDefaults"], + static_libs: [ + "android.hardware.vibrator-V2-ndk", + ], + test_suites: ["device-tests"], + require_root: true, +} diff --git a/vibrator/OWNERS b/vibrator/OWNERS new file mode 100644 index 0000000..5d4a9c3 --- /dev/null +++ b/vibrator/OWNERS @@ -0,0 +1,3 @@ +chasewu@google.com +michaelwr@google.com +taikuo@google.com diff --git a/vibrator/common/Android.bp b/vibrator/common/Android.bp new file mode 100644 index 0000000..04fbc4d --- /dev/null +++ b/vibrator/common/Android.bp @@ -0,0 +1,37 @@ +// +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_library { + name: "PixelVibratorCommon", + srcs: [ + "HardwareBase.cpp", + ], + shared_libs: [ + "libbase", + "libcutils", + "liblog", + "libutils", + ], + cflags: [ + "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", + "-DLOG_TAG=\"android.hardware.vibrator@1.x-common\"", + ], + export_include_dirs: ["."], + vendor_available: true, +} diff --git a/vibrator/common/HardwareBase.cpp b/vibrator/common/HardwareBase.cpp new file mode 100644 index 0000000..8e07e99 --- /dev/null +++ b/vibrator/common/HardwareBase.cpp @@ -0,0 +1,136 @@ +/* + * 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 "HardwareBase.h" + +#include +#include + +#include +#include + +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +HwApiBase::HwApiBase() { + mPathPrefix = std::getenv("HWAPI_PATH_PREFIX") ?: ""; + if (mPathPrefix.empty()) { + ALOGE("Failed get HWAPI path prefix!"); + } +} + +void HwApiBase::saveName(const std::string &name, const std::ios *stream) { + mNames[stream] = name; +} + +bool HwApiBase::has(const std::ios &stream) { + return !!stream; +} + +void HwApiBase::debug(int fd) { + dprintf(fd, "Kernel:\n"); + + for (auto &entry : utils::pathsFromEnv("HWAPI_DEBUG_PATHS", mPathPrefix)) { + auto &path = entry.first; + auto &stream = entry.second; + std::string line; + + dprintf(fd, " %s:\n", path.c_str()); + while (std::getline(stream, line)) { + dprintf(fd, " %s\n", line.c_str()); + } + } + + mRecordsMutex.lock(); + dprintf(fd, " Records:\n"); + for (auto &r : mRecords) { + if (r == nullptr) { + continue; + } + dprintf(fd, " %s\n", r->toString(mNames).c_str()); + } + mRecordsMutex.unlock(); +} + +HwCalBase::HwCalBase() { + std::ifstream calfile; + auto propertyPrefix = std::getenv("PROPERTY_PREFIX"); + + if (propertyPrefix != NULL) { + mPropertyPrefix = std::string(propertyPrefix); + } else { + ALOGE("Failed get property prefix!"); + } + + utils::fileFromEnv("CALIBRATION_FILEPATH", &calfile); + + for (std::string line; std::getline(calfile, line);) { + if (line.empty() || line[0] == '#') { + continue; + } + std::istringstream is_line(line); + std::string key, value; + if (std::getline(is_line, key, ':') && std::getline(is_line, value)) { + mCalData[utils::trim(key)] = utils::trim(value); + } + } +} + +void HwCalBase::debug(int fd) { + std::ifstream stream; + std::string path; + std::string line; + struct context { + HwCalBase *obj; + int fd; + } context{this, fd}; + + dprintf(fd, "Properties:\n"); + + property_list( + [](const char *key, const char *value, void *cookie) { + struct context *context = static_cast(cookie); + HwCalBase *obj = context->obj; + int fd = context->fd; + const std::string expect{obj->mPropertyPrefix}; + const std::string actual{key, std::min(strlen(key), expect.size())}; + if (actual == expect) { + dprintf(fd, " %s:\n", key); + dprintf(fd, " %s\n", value); + } + }, + &context); + + dprintf(fd, "\n"); + + dprintf(fd, "Persist:\n"); + + utils::fileFromEnv("CALIBRATION_FILEPATH", &stream, &path); + + dprintf(fd, " %s:\n", path.c_str()); + while (std::getline(stream, line)) { + dprintf(fd, " %s\n", line.c_str()); + } +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/common/HardwareBase.h b/vibrator/common/HardwareBase.h new file mode 100644 index 0000000..5b07040 --- /dev/null +++ b/vibrator/common/HardwareBase.h @@ -0,0 +1,221 @@ +/* + * 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. + */ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::android::base::unique_fd; + +class HwApiBase { + private: + using NamesMap = std::map; + + class RecordInterface { + public: + virtual std::string toString(const NamesMap &names) = 0; + virtual ~RecordInterface() {} + }; + template + class Record : public RecordInterface { + public: + Record(const char *func, const T &value, const std::ios *stream) + : mFunc(func), mValue(value), mStream(stream) {} + std::string toString(const NamesMap &names) override; + + private: + const char *mFunc; + const T mValue; + const std::ios *mStream; + }; + using Records = std::list>; + + static constexpr uint32_t RECORDS_SIZE = 32; + + public: + HwApiBase(); + void debug(int fd); + + protected: + void saveName(const std::string &name, const std::ios *stream); + template + void open(const std::string &name, T *stream); + bool has(const std::ios &stream); + template + bool get(T *value, std::istream *stream); + template + bool set(const T &value, std::ostream *stream); + template + bool poll(const T &value, std::istream *stream, const int32_t timeout = -1); + template + void record(const char *func, const T &value, const std::ios *stream); + + private: + std::string mPathPrefix; + NamesMap mNames; + Records mRecords{RECORDS_SIZE}; + std::mutex mRecordsMutex; + std::mutex mIoMutex; +}; + +#define HWAPI_RECORD(args...) HwApiBase::record(__FUNCTION__, ##args) + +template +void HwApiBase::open(const std::string &name, T *stream) { + saveName(name, stream); + utils::openNoCreate(mPathPrefix + name, stream); +} + +template +bool HwApiBase::get(T *value, std::istream *stream) { + ATRACE_NAME("HwApi::get"); + std::scoped_lock ioLock{mIoMutex}; + bool ret; + stream->seekg(0); + *stream >> *value; + if (!(ret = !!*stream)) { + ALOGE("Failed to read %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + } + stream->clear(); + HWAPI_RECORD(*value, stream); + return ret; +} + +template +bool HwApiBase::set(const T &value, std::ostream *stream) { + ATRACE_NAME("HwApi::set"); + using utils::operator<<; + std::scoped_lock ioLock{mIoMutex}; + bool ret; + *stream << value << std::endl; + if (!(ret = !!*stream)) { + ALOGE("Failed to write %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + stream->clear(); + } + HWAPI_RECORD(value, stream); + return ret; +} + +template +bool HwApiBase::poll(const T &value, std::istream *stream, const int32_t timeoutMs) { + ATRACE_NAME("HwApi::poll"); + auto path = mPathPrefix + mNames[stream]; + unique_fd fileFd{::open(path.c_str(), O_RDONLY)}; + unique_fd epollFd{epoll_create(1)}; + epoll_event event = { + .events = EPOLLPRI | EPOLLET, + }; + T actual; + bool ret; + int epollRet; + + if (timeoutMs < -1) { + ALOGE("Invalid polling timeout!"); + return false; + } + + if (epoll_ctl(epollFd, EPOLL_CTL_ADD, fileFd, &event)) { + ALOGE("Failed to poll %s (%d): %s", mNames[stream].c_str(), errno, strerror(errno)); + return false; + } + + while ((ret = get(&actual, stream)) && (actual != value)) { + epollRet = epoll_wait(epollFd, &event, 1, timeoutMs); + if (epollRet <= 0) { + ALOGE("Polling error or timeout! (%d)", epollRet); + return false; + } + } + + HWAPI_RECORD(value, stream); + return ret; +} + +template +void HwApiBase::record(const char *func, const T &value, const std::ios *stream) { + std::lock_guard lock(mRecordsMutex); + mRecords.emplace_back(std::make_unique>(func, value, stream)); + mRecords.pop_front(); +} + +template +std::string HwApiBase::Record::toString(const NamesMap &names) { + using utils::operator<<; + std::stringstream ret; + + ret << mFunc << " '" << names.at(mStream) << "' = '" << mValue << "'"; + + return ret.str(); +} + +class HwCalBase { + public: + HwCalBase(); + void debug(int fd); + + protected: + template + bool getProperty(const char *key, T *value, const T defval); + template + bool getPersist(const char *key, T *value); + + private: + std::string mPropertyPrefix; + std::map mCalData; +}; + +template +bool HwCalBase::getProperty(const char *key, T *outval, const T defval) { + ATRACE_NAME("HwCal::getProperty"); + *outval = utils::getProperty(mPropertyPrefix + key, defval); + return true; +} + +template +bool HwCalBase::getPersist(const char *key, T *value) { + ATRACE_NAME("HwCal::getPersist"); + auto it = mCalData.find(key); + if (it == mCalData.end()) { + ALOGE("Missing %s config!", key); + return false; + } + std::stringstream stream{it->second}; + utils::unpack(stream, value); + if (!stream || !stream.eof()) { + ALOGE("Invalid %s config!", key); + return false; + } + return true; +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/common/utils.h b/vibrator/common/utils.h new file mode 100644 index 0000000..188554d --- /dev/null +++ b/vibrator/common/utils.h @@ -0,0 +1,173 @@ +/* + * 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. + */ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { +namespace utils { + +template +class Is_Iterable { + private: + template + static std::true_type test(typename U::iterator *u); + + template + static std::false_type test(U *u); + + public: + static const bool value = decltype(test(0))::value; +}; + +template +using Enable_If_Iterable = std::enable_if_t::value == B>; + +template +using Enable_If_Signed = std::enable_if_t, U>; + +template +using Enable_If_Unsigned = std::enable_if_t, U>; + +// override for default behavior of printing as a character +inline std::ostream &operator<<(std::ostream &stream, const int8_t value) { + return stream << +value; +} +// override for default behavior of printing as a character +inline std::ostream &operator<<(std::ostream &stream, const uint8_t value) { + return stream << +value; +} + +template +inline auto toUnderlying(const T value) { + return static_cast>(value); +} + +template +inline Enable_If_Iterable unpack(std::istream &stream, T *value) { + for (auto &entry : *value) { + stream >> entry; + } +} + +template +inline Enable_If_Iterable unpack(std::istream &stream, T *value) { + stream >> *value; +} + +template <> +inline void unpack(std::istream &stream, std::string *value) { + *value = std::string(std::istreambuf_iterator(stream), {}); + stream.setstate(std::istream::eofbit); +} + +template +inline Enable_If_Signed getProperty(const std::string &key, const T def) { + if (std::is_floating_point_v) { + float result; + std::string value = ::android::base::GetProperty(key, ""); + if (!value.empty() && ::android::base::ParseFloat(value, &result)) { + return result; + } + return def; + } else { + return ::android::base::GetIntProperty(key, def); + } +} + +template +inline Enable_If_Unsigned getProperty(const std::string &key, const T def) { + return ::android::base::GetUintProperty(key, def); +} + +template <> +inline bool getProperty(const std::string &key, const bool def) { + return ::android::base::GetBoolProperty(key, def); +} + +template +static void openNoCreate(const std::string &file, T *outStream) { + auto mode = std::is_base_of_v ? std::ios_base::out : std::ios_base::in; + + // Force 'in' mode to prevent file creation + outStream->open(file, mode | std::ios_base::in); + if (!*outStream) { + ALOGE("Failed to open %s (%d): %s", file.c_str(), errno, strerror(errno)); + } +} + +template +static void fileFromEnv(const char *env, T *outStream, std::string *outName = nullptr) { + auto file = std::getenv(env); + + if (file == nullptr) { + ALOGE("Failed get env %s", env); + return; + } + + if (outName != nullptr) { + *outName = std::string(file); + } + + openNoCreate(file, outStream); +} + +static ATTRIBUTE_UNUSED auto pathsFromEnv(const char *env, const std::string &prefix = "") { + std::map ret; + auto value = std::getenv(env); + + if (value == nullptr) { + return ret; + } + + std::istringstream paths{value}; + std::string path; + + while (paths >> path) { + ret[path].open(prefix + path); + } + + return ret; +} + +static ATTRIBUTE_UNUSED std::string trim(const std::string &str, + const std::string &whitespace = " \t") { + const auto str_begin = str.find_first_not_of(whitespace); + if (str_begin == std::string::npos) { + return ""; + } + + const auto str_end = str.find_last_not_of(whitespace); + const auto str_range = str_end - str_begin + 1; + + return str.substr(str_begin, str_range); +} + +} // namespace utils +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp new file mode 100644 index 0000000..53764de --- /dev/null +++ b/vibrator/cs40l26/Android.bp @@ -0,0 +1,86 @@ +// +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_defaults { + name: "android.hardware.vibrator-defaults.cs40l26", + cflags: [ + "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", + "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"", + ], + shared_libs: [ + "libbinder", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26BinaryDefaults", + defaults: [ + "PixelVibratorBinaryDefaults", + "android.hardware.vibrator-defaults.cs40l26", + ], + include_dirs: [ + "external/tinyalsa/include", + ], + shared_libs: [ + "libcutils", + "libtinyalsa", + ], +} + +cc_defaults { + name: "VibratorHalCs40l26TestDefaults", + defaults: [ + "PixelVibratorTestDefaults", + "android.hardware.vibrator-defaults.cs40l26", + ], + static_libs: [ + "android.hardware.vibrator-impl.cs40l26", + "libtinyalsa", + ], +} + +cc_library { + name: "android.hardware.vibrator-impl.cs40l26", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + srcs: ["Vibrator.cpp"], + export_include_dirs: ["."], + vendor_available: true, + visibility: [":__subpackages__"], +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"], + srcs: ["service.cpp"], + shared_libs: ["android.hardware.vibrator-impl.cs40l26"], + proprietary: true, +} + +cc_binary { + name: "android.hardware.vibrator-service.cs40l26-dual", + defaults: ["VibratorHalCs40l26BinaryDefaults"], + init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"], + srcs: ["service.cpp"], + shared_libs: ["android.hardware.vibrator-impl.cs40l26"], + cflags: ["-DVIBRATOR_NAME=\"dual\""], + proprietary: true, +} diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h new file mode 100644 index 0000000..ae052ba --- /dev/null +++ b/vibrator/cs40l26/Hardware.h @@ -0,0 +1,346 @@ +/* + * 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 + +#include "HardwareBase.h" +#include "Vibrator.h" + +#define PROC_SND_PCM "/proc/asound/pcm" +#define HAPTIC_PCM_DEVICE_SYMBOL "haptic nohost playback" + +static struct pcm_config haptic_nohost_config = { + .channels = 1, + .rate = 48000, + .period_size = 80, + .period_count = 2, + .format = PCM_FORMAT_S16_LE, +}; + +enum WaveformIndex : uint16_t { + /* Physical waveform */ + WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0, + WAVEFORM_RESERVED_INDEX_1 = 1, + WAVEFORM_CLICK_INDEX = 2, + WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3, + WAVEFORM_THUD_INDEX = 4, + WAVEFORM_SPIN_INDEX = 5, + WAVEFORM_QUICK_RISE_INDEX = 6, + WAVEFORM_SLOW_RISE_INDEX = 7, + WAVEFORM_QUICK_FALL_INDEX = 8, + WAVEFORM_LIGHT_TICK_INDEX = 9, + WAVEFORM_LOW_TICK_INDEX = 10, + WAVEFORM_RESERVED_MFG_1, + WAVEFORM_RESERVED_MFG_2, + WAVEFORM_RESERVED_MFG_3, + WAVEFORM_MAX_PHYSICAL_INDEX, + /* OWT waveform */ + WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX, + WAVEFORM_PWLE, + /* + * Refer to , the WAVEFORM_MAX_INDEX must not exceed 96. + * #define FF_GAIN 0x60 // 96 in decimal + * #define FF_MAX_EFFECTS FF_GAIN + */ + WAVEFORM_MAX_INDEX, +}; + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +class HwApi : public Vibrator::HwApi, private HwApiBase { + public: + HwApi() { + open("calibration/f0_stored", &mF0); + open("default/f0_offset", &mF0Offset); + open("calibration/redc_stored", &mRedc); + open("calibration/q_stored", &mQ); + open("default/vibe_state", &mVibeState); + open("default/num_waves", &mEffectCount); + open("default/owt_free_space", &mOwtFreeSpace); + open("default/f0_comp_enable", &mF0CompEnable); + open("default/redc_comp_enable", &mRedcCompEnable); + open("default/delay_before_stop_playback_us", &mMinOnOffInterval); + } + + bool setF0(std::string value) override { return set(value, &mF0); } + bool setF0Offset(uint32_t value) override { return set(value, &mF0Offset); } + bool setRedc(std::string value) override { return set(value, &mRedc); } + bool setQ(std::string value) override { return set(value, &mQ); } + bool getEffectCount(uint32_t *value) override { return get(value, &mEffectCount); } + bool pollVibeState(uint32_t value, int32_t timeoutMs) override { + return poll(value, &mVibeState, timeoutMs); + } + bool hasOwtFreeSpace() override { return has(mOwtFreeSpace); } + bool getOwtFreeSpace(uint32_t *value) override { return get(value, &mOwtFreeSpace); } + bool setF0CompEnable(bool value) override { return set(value, &mF0CompEnable); } + bool setRedcCompEnable(bool value) override { return set(value, &mRedcCompEnable); } + bool setMinOnOffInterval(uint32_t value) override { return set(value, &mMinOnOffInterval); } + // TODO(b/234338136): Need to add the force feedback HW API test cases + bool setFFGain(int fd, uint16_t value) override { + struct input_event gain = { + .type = EV_FF, + .code = FF_GAIN, + .value = value, + }; + if (write(fd, (const void *)&gain, sizeof(gain)) != sizeof(gain)) { + return false; + } + return true; + } + bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) override { + if (((*effect).replay.length != timeoutMs) || (ioctl(fd, EVIOCSFF, effect) < 0)) { + ALOGE("setFFEffect fail"); + return false; + } else { + return true; + } + } + bool setFFPlay(int fd, int8_t index, bool value) override { + struct input_event play = { + .type = EV_FF, + .code = static_cast(index), + .value = value, + }; + if (write(fd, (const void *)&play, sizeof(play)) != sizeof(play)) { + return false; + } else { + return true; + } + } + bool getHapticAlsaDevice(int *card, int *device) override { + std::string line; + std::ifstream myfile(PROC_SND_PCM); + if (myfile.is_open()) { + while (getline(myfile, line)) { + if (line.find(HAPTIC_PCM_DEVICE_SYMBOL) != std::string::npos) { + std::stringstream ss(line); + std::string currentToken; + std::getline(ss, currentToken, ':'); + sscanf(currentToken.c_str(), "%d-%d", card, device); + return true; + } + } + myfile.close(); + } else { + ALOGE("Failed to read file: %s", PROC_SND_PCM); + } + return false; + } + bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device) override { + int ret = 0; + + if (enable) { + *haptic_pcm = pcm_open(card, device, PCM_OUT, &haptic_nohost_config); + if (!pcm_is_ready(*haptic_pcm)) { + ALOGE("cannot open pcm_out driver: %s", pcm_get_error(*haptic_pcm)); + goto fail; + } + + ret = pcm_prepare(*haptic_pcm); + if (ret < 0) { + ALOGE("cannot prepare haptic_pcm: %s", pcm_get_error(*haptic_pcm)); + goto fail; + } + + ret = pcm_start(*haptic_pcm); + if (ret < 0) { + ALOGE("cannot start haptic_pcm: %s", pcm_get_error(*haptic_pcm)); + goto fail; + } + + return true; + } else { + if (*haptic_pcm) { + pcm_close(*haptic_pcm); + *haptic_pcm = NULL; + } + return true; + } + + fail: + pcm_close(*haptic_pcm); + *haptic_pcm = NULL; + return false; + } + bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect, + uint32_t *outEffectIndex, int *status) override { + (*effect).u.periodic.custom_len = numBytes / sizeof(uint16_t); + delete[] ((*effect).u.periodic.custom_data); + (*effect).u.periodic.custom_data = new int16_t[(*effect).u.periodic.custom_len]{0x0000}; + if ((*effect).u.periodic.custom_data == nullptr) { + ALOGE("Failed to allocate memory for custom data\n"); + *status = EX_NULL_POINTER; + return false; + } + memcpy((*effect).u.periodic.custom_data, owtData, numBytes); + + if ((*effect).id != -1) { + ALOGE("(*effect).id != -1"); + } + + /* Create a new OWT waveform to update the PWLE or composite effect. */ + (*effect).id = -1; + if (ioctl(fd, EVIOCSFF, effect) < 0) { + ALOGE("Failed to upload effect %d (%d): %s", *outEffectIndex, errno, strerror(errno)); + delete[] ((*effect).u.periodic.custom_data); + *status = EX_ILLEGAL_STATE; + return false; + } + + if ((*effect).id >= FF_MAX_EFFECTS || (*effect).id < 0) { + ALOGE("Invalid waveform index after upload OWT effect: %d", (*effect).id); + *status = EX_ILLEGAL_ARGUMENT; + return false; + } + *outEffectIndex = (*effect).id; + *status = 0; + return true; + } + bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector *effect) override { + uint32_t effectCountBefore, effectCountAfter, i, successFlush = 0; + + if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) { + ALOGE("Invalid waveform index for OWT erase: %d", effectIndex); + return false; + } + + if (effectIndex < WAVEFORM_MAX_INDEX) { + /* Normal situation. Only erase the effect which we just played. */ + if (ioctl(fd, EVIOCRMFF, effectIndex) < 0) { + ALOGE("Failed to erase effect %d (%d): %s", effectIndex, errno, strerror(errno)); + } + for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) { + if ((*effect)[i].id == effectIndex) { + (*effect)[i].id = -1; + break; + } + } + } else { + /* Flush all non-prestored effects of ff-core and driver. */ + getEffectCount(&effectCountBefore); + for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < FF_MAX_EFFECTS; i++) { + if (ioctl(fd, EVIOCRMFF, i) >= 0) { + successFlush++; + } + } + getEffectCount(&effectCountAfter); + ALOGW("Flushed effects: ff: %d; driver: %d -> %d; success: %d", effectIndex, + effectCountBefore, effectCountAfter, successFlush); + /* Reset all OWT effect index of HAL. */ + for (i = WAVEFORM_MAX_PHYSICAL_INDEX; i < WAVEFORM_MAX_INDEX; i++) { + (*effect)[i].id = -1; + } + } + return true; + } + + void debug(int fd) override { HwApiBase::debug(fd); } + + private: + std::ofstream mF0; + std::ofstream mF0Offset; + std::ofstream mRedc; + std::ofstream mQ; + std::ifstream mEffectCount; + std::ifstream mVibeState; + std::ifstream mOwtFreeSpace; + std::ofstream mF0CompEnable; + std::ofstream mRedcCompEnable; + std::ofstream mMinOnOffInterval; +}; + +class HwCal : public Vibrator::HwCal, private HwCalBase { + private: + static constexpr char VERSION[] = "version"; + static constexpr char F0_CONFIG[] = "f0_measured"; + static constexpr char REDC_CONFIG[] = "redc_measured"; + static constexpr char Q_CONFIG[] = "q_measured"; + static constexpr char TICK_VOLTAGES_CONFIG[] = "v_tick"; + static constexpr char CLICK_VOLTAGES_CONFIG[] = "v_click"; + static constexpr char LONG_VOLTAGES_CONFIG[] = "v_long"; + + static constexpr uint32_t VERSION_DEFAULT = 2; + static constexpr int32_t DEFAULT_FREQUENCY_SHIFT = 0; + static constexpr std::array V_TICK_DEFAULT = {1, 100}; + static constexpr std::array V_CLICK_DEFAULT = {1, 100}; + static constexpr std::array V_LONG_DEFAULT = {1, 100}; + + public: + HwCal() {} + + bool getVersion(uint32_t *value) override { + if (getPersist(VERSION, value)) { + return true; + } + *value = VERSION_DEFAULT; + return true; + } + bool getLongFrequencyShift(int32_t *value) override { + return getProperty("long.frequency.shift", value, DEFAULT_FREQUENCY_SHIFT); + } + bool getF0(std::string *value) override { return getPersist(F0_CONFIG, value); } + bool getRedc(std::string *value) override { return getPersist(REDC_CONFIG, value); } + bool getQ(std::string *value) override { return getPersist(Q_CONFIG, value); } + bool getTickVolLevels(std::array *value) override { + if (getPersist(TICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_TICK_DEFAULT; + return true; + } + bool getClickVolLevels(std::array *value) override { + if (getPersist(CLICK_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_CLICK_DEFAULT; + return true; + } + bool getLongVolLevels(std::array *value) override { + if (getPersist(LONG_VOLTAGES_CONFIG, value)) { + return true; + } + *value = V_LONG_DEFAULT; + return true; + } + bool isChirpEnabled() override { + bool value; + getProperty("chirp.enabled", &value, false); + return value; + } + bool getSupportedPrimitives(uint32_t *value) override { + return getProperty("supported_primitives", value, (uint32_t)0); + } + bool isF0CompEnabled() override { + bool value; + getProperty("f0.comp.enabled", &value, true); + return value; + } + bool isRedcCompEnabled() override { + bool value; + getProperty("redc.comp.enabled", &value, true); + return value; + } + void debug(int fd) override { HwCalBase::debug(fd); } +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/TEST_MAPPING b/vibrator/cs40l26/TEST_MAPPING new file mode 100644 index 0000000..1d8ff7a --- /dev/null +++ b/vibrator/cs40l26/TEST_MAPPING @@ -0,0 +1,10 @@ +{ + "presubmit": [ + { + "name": "VibratorHalCs40l26TestSuite", + "keywords": [ + "nextgen" + ] + } + ] +} diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp new file mode 100644 index 0000000..3af057c --- /dev/null +++ b/vibrator/cs40l26/Vibrator.cpp @@ -0,0 +1,1327 @@ +/* + * 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 "Vibrator.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) +#endif + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { +static constexpr uint8_t FF_CUSTOM_DATA_LEN = 2; +static constexpr uint16_t FF_CUSTOM_DATA_LEN_MAX_COMP = 2044; // (COMPOSE_SIZE_MAX + 1) * 8 + 4 +static constexpr uint16_t FF_CUSTOM_DATA_LEN_MAX_PWLE = 2302; + +static constexpr uint32_t WAVEFORM_DOUBLE_CLICK_SILENCE_MS = 100; + +static constexpr uint32_t WAVEFORM_LONG_VIBRATION_THRESHOLD_MS = 50; + +static constexpr uint8_t VOLTAGE_SCALE_MAX = 100; + +static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6; // I2C Transaction + DSP Return-From-Standby +static constexpr uint32_t MIN_ON_OFF_INTERVAL_US = 8500; // SVC initialization time +static constexpr int8_t MAX_PAUSE_TIMING_ERROR_MS = 1; // ALERT Irq Handling +static constexpr uint32_t MAX_TIME_MS = UINT16_MAX; + +static constexpr auto ASYNC_COMPLETION_TIMEOUT = std::chrono::milliseconds(100); +static constexpr auto POLLING_TIMEOUT = 20; +static constexpr int32_t COMPOSE_DELAY_MAX_MS = 10000; + +/* nsections is 8 bits. Need to preserve 1 section for the first delay before the first effect. */ +static constexpr int32_t COMPOSE_SIZE_MAX = 254; +static constexpr int32_t COMPOSE_PWLE_SIZE_MAX_DEFAULT = 127; + +// Measured resonant frequency, f0_measured, is represented by Q10.14 fixed +// point format on cs40l26 devices. The expression to calculate f0 is: +// f0 = f0_measured / 2^Q14_BIT_SHIFT +// See the LRA Calibration Support documentation for more details. +static constexpr int32_t Q14_BIT_SHIFT = 14; + +// Measured Q factor, q_measured, is represented by Q8.16 fixed +// point format on cs40l26 devices. The expression to calculate q is: +// q = q_measured / 2^Q16_BIT_SHIFT +// See the LRA Calibration Support documentation for more details. +static constexpr int32_t Q16_BIT_SHIFT = 16; + +static constexpr int32_t COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS = 16383; + +static constexpr uint32_t WT_LEN_CALCD = 0x00800000; +static constexpr uint8_t PWLE_CHIRP_BIT = 0x8; // Dynamic/static frequency and voltage +static constexpr uint8_t PWLE_BRAKE_BIT = 0x4; +static constexpr uint8_t PWLE_AMP_REG_BIT = 0x2; + +static constexpr float PWLE_LEVEL_MIN = 0.0; +static constexpr float PWLE_LEVEL_MAX = 1.0; +static constexpr float CS40L26_PWLE_LEVEL_MIX = -1.0; +static constexpr float CS40L26_PWLE_LEVEL_MAX = 0.9995118; +static constexpr float PWLE_FREQUENCY_RESOLUTION_HZ = 1.00; +static constexpr float PWLE_FREQUENCY_MIN_HZ = 1.00; +static constexpr float PWLE_FREQUENCY_MAX_HZ = 1000.00; +static constexpr float PWLE_BW_MAP_SIZE = + 1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ); + +static uint16_t amplitudeToScale(float amplitude, float maximum) { + float ratio = 100; /* Unit: % */ + if (maximum != 0) + ratio = amplitude / maximum * 100; + + if (maximum == 0 || ratio > 100) + ratio = 100; + + return std::round(ratio); +} + +enum WaveformBankID : uint8_t { + RAM_WVFRM_BANK, + ROM_WVFRM_BANK, + OWT_WVFRM_BANK, +}; + +enum WaveformIndex : uint16_t { + /* Physical waveform */ + WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0, + WAVEFORM_RESERVED_INDEX_1 = 1, + WAVEFORM_CLICK_INDEX = 2, + WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3, + WAVEFORM_THUD_INDEX = 4, + WAVEFORM_SPIN_INDEX = 5, + WAVEFORM_QUICK_RISE_INDEX = 6, + WAVEFORM_SLOW_RISE_INDEX = 7, + WAVEFORM_QUICK_FALL_INDEX = 8, + WAVEFORM_LIGHT_TICK_INDEX = 9, + WAVEFORM_LOW_TICK_INDEX = 10, + WAVEFORM_RESERVED_MFG_1, + WAVEFORM_RESERVED_MFG_2, + WAVEFORM_RESERVED_MFG_3, + WAVEFORM_MAX_PHYSICAL_INDEX, + /* OWT waveform */ + WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX, + WAVEFORM_PWLE, + /* + * Refer to , the WAVEFORM_MAX_INDEX must not exceed 96. + * #define FF_GAIN 0x60 // 96 in decimal + * #define FF_MAX_EFFECTS FF_GAIN + */ + WAVEFORM_MAX_INDEX, +}; + +std::vector defaultSupportedPrimitives = { + ndk::enum_range().begin(), ndk::enum_range().end()}; + +enum vibe_state { + VIBE_STATE_STOPPED = 0, + VIBE_STATE_HAPTIC, + VIBE_STATE_ASP, +}; + +std::mutex mActiveId_mutex; // protects mActiveId + +static int min(int x, int y) { + return x < y ? x : y; +} + +static int floatToUint16(float input, uint16_t *output, float scale, float min, float max) { + if (input < min || input > max) + return -ERANGE; + + *output = roundf(input * scale); + return 0; +} + +struct dspmem_chunk { + uint8_t *head; + uint8_t *current; + uint8_t *max; + int bytes; + + uint32_t cache; + int cachebits; +}; + +static dspmem_chunk *dspmem_chunk_create(void *data, int size) { + auto ch = new dspmem_chunk{ + .head = reinterpret_cast(data), + .current = reinterpret_cast(data), + .max = reinterpret_cast(data) + size, + }; + + return ch; +} + +static bool dspmem_chunk_end(struct dspmem_chunk *ch) { + return ch->current == ch->max; +} + +static int dspmem_chunk_bytes(struct dspmem_chunk *ch) { + return ch->bytes; +} + +static int dspmem_chunk_write(struct dspmem_chunk *ch, int nbits, uint32_t val) { + int nwrite, i; + + nwrite = min(24 - ch->cachebits, nbits); + ch->cache <<= nwrite; + ch->cache |= val >> (nbits - nwrite); + ch->cachebits += nwrite; + nbits -= nwrite; + + if (ch->cachebits == 24) { + if (dspmem_chunk_end(ch)) + return -ENOSPC; + + ch->cache &= 0xFFFFFF; + for (i = 0; i < sizeof(ch->cache); i++, ch->cache <<= 8) + *ch->current++ = (ch->cache & 0xFF000000) >> 24; + + ch->bytes += sizeof(ch->cache); + ch->cachebits = 0; + } + + if (nbits) + return dspmem_chunk_write(ch, nbits, val); + + return 0; +} + +static int dspmem_chunk_flush(struct dspmem_chunk *ch) { + if (!ch->cachebits) + return 0; + + return dspmem_chunk_write(ch, 24 - ch->cachebits, 0); +} + +Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) + : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)), mAsyncHandle(std::async([] {})) { + int32_t longFrequencyShift; + std::string caldata{8, '0'}; + uint32_t calVer; + + const char *inputEventName = std::getenv("INPUT_EVENT_NAME"); + const char *inputEventPathName = std::getenv("INPUT_EVENT_PATH"); + if ((strstr(inputEventName, "cs40l26") != nullptr) || + (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) { + glob_t inputEventPaths; + int fd = -1; + int ret; + uint32_t val = 0; + char str[20] = {0x00}; + for (uint8_t retry = 0; retry < 10; retry++) { + ret = glob(inputEventPathName, 0, nullptr, &inputEventPaths); + if (ret) { + ALOGE("Fail to get input event paths (%d): %s", errno, strerror(errno)); + } else { + for (int i = 0; i < inputEventPaths.gl_pathc; i++) { + fd = TEMP_FAILURE_RETRY(open(inputEventPaths.gl_pathv[i], O_RDWR)); + if (fd > 0) { + if (ioctl(fd, EVIOCGBIT(0, sizeof(val)), &val) > 0 && + (val & (1 << EV_FF)) && ioctl(fd, EVIOCGNAME(sizeof(str)), &str) > 0 && + strstr(str, inputEventName) != nullptr) { + mInputFd.reset(fd); + ALOGI("Control %s through %s", inputEventName, + inputEventPaths.gl_pathv[i]); + break; + } + close(fd); + } + } + } + + if (ret == 0) { + globfree(&inputEventPaths); + } + if (mInputFd.ok()) { + break; + } + + sleep(1); + ALOGW("Retry #%d to search in %zu input devices.", retry, inputEventPaths.gl_pathc); + } + + if (!mInputFd.ok()) { + ALOGE("Fail to get an input event with name %s", inputEventName); + } + } else { + ALOGE("The input name %s is not cs40l26_input or cs40l26_dual_input", inputEventName); + } + + mFfEffects.resize(WAVEFORM_MAX_INDEX); + mEffectDurations.resize(WAVEFORM_MAX_INDEX); + mEffectDurations = { + 1000, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000, + }; /* 11+3 waveforms. The duration must < UINT16_MAX */ + + uint8_t effectIndex; + for (effectIndex = 0; effectIndex < WAVEFORM_MAX_INDEX; effectIndex++) { + if (effectIndex < WAVEFORM_MAX_PHYSICAL_INDEX) { + /* Initialize physical waveforms. */ + mFfEffects[effectIndex] = { + .type = FF_PERIODIC, + .id = -1, + .replay.length = static_cast(mEffectDurations[effectIndex]), + .u.periodic.waveform = FF_CUSTOM, + .u.periodic.custom_data = new int16_t[2]{RAM_WVFRM_BANK, effectIndex}, + .u.periodic.custom_len = FF_CUSTOM_DATA_LEN, + }; + // Bypass the waveform update due to different input name + if ((strstr(inputEventName, "cs40l26") != nullptr) || + (strstr(inputEventName, "cs40l26_dual_input") != nullptr)) { + if (!mHwApi->setFFEffect( + mInputFd, &mFfEffects[effectIndex], + static_cast(mFfEffects[effectIndex].replay.length))) { + ALOGE("Failed upload effect %d (%d): %s", effectIndex, errno, strerror(errno)); + } + } + if (mFfEffects[effectIndex].id != effectIndex) { + ALOGW("Unexpected effect index: %d -> %d", effectIndex, mFfEffects[effectIndex].id); + } + } else { + /* Initiate placeholders for OWT effects. */ + mFfEffects[effectIndex] = { + .type = FF_PERIODIC, + .id = -1, + .replay.length = 0, + .u.periodic.waveform = FF_CUSTOM, + .u.periodic.custom_data = nullptr, + .u.periodic.custom_len = 0, + }; + } + } + + if (mHwCal->getF0(&caldata)) { + mHwApi->setF0(caldata); + } + if (mHwCal->getRedc(&caldata)) { + mHwApi->setRedc(caldata); + } + if (mHwCal->getQ(&caldata)) { + mHwApi->setQ(caldata); + } + + mHwCal->getLongFrequencyShift(&longFrequencyShift); + if (longFrequencyShift > 0) { + mF0Offset = longFrequencyShift * std::pow(2, 14); + } else if (longFrequencyShift < 0) { + mF0Offset = std::pow(2, 24) - std::abs(longFrequencyShift) * std::pow(2, 14); + } else { + mF0Offset = 0; + } + + mHwCal->getVersion(&calVer); + if (calVer == 2) { + mHwCal->getTickVolLevels(&mTickEffectVol); + mHwCal->getClickVolLevels(&mClickEffectVol); + mHwCal->getLongVolLevels(&mLongEffectVol); + } else { + ALOGD("Unsupported calibration version: %u!", calVer); + } + + mHwApi->setF0CompEnable(mHwCal->isF0CompEnabled()); + mHwApi->setRedcCompEnable(mHwCal->isRedcCompEnabled()); + + mIsUnderExternalControl = false; + + mIsChirpEnabled = mHwCal->isChirpEnabled(); + + mHwCal->getSupportedPrimitives(&mSupportedPrimitivesBits); + if (mSupportedPrimitivesBits > 0) { + for (auto e : defaultSupportedPrimitives) { + if (mSupportedPrimitivesBits & (1 << uint32_t(e))) { + mSupportedPrimitives.emplace_back(e); + } + } + } else { + for (auto e : defaultSupportedPrimitives) { + mSupportedPrimitivesBits |= (1 << uint32_t(e)); + } + mSupportedPrimitives = defaultSupportedPrimitives; + } + + mHwApi->setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US); +} + +ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { + ATRACE_NAME("Vibrator::getCapabilities"); + + int32_t ret = IVibrator::CAP_ON_CALLBACK | IVibrator::CAP_PERFORM_CALLBACK | + IVibrator::CAP_AMPLITUDE_CONTROL | IVibrator::CAP_GET_RESONANT_FREQUENCY | + IVibrator::CAP_GET_Q_FACTOR; + if (hasHapticAlsaDevice()) { + ret |= IVibrator::CAP_EXTERNAL_CONTROL; + } else { + ALOGE("No haptics ALSA device"); + } + if (mHwApi->hasOwtFreeSpace()) { + ret |= IVibrator::CAP_COMPOSE_EFFECTS; + if (mIsChirpEnabled) { + ret |= IVibrator::CAP_FREQUENCY_CONTROL | IVibrator::CAP_COMPOSE_PWLE_EFFECTS; + } + } + *_aidl_return = ret; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::off() { + ATRACE_NAME("Vibrator::off"); + bool ret{true}; + const std::scoped_lock lock(mActiveId_mutex); + + if (mActiveId >= 0) { + /* Stop the active effect. */ + if (!mHwApi->setFFPlay(mInputFd, mActiveId, false)) { + ALOGE("Failed to stop effect %d (%d): %s", mActiveId, errno, strerror(errno)); + ret = false; + } + + if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) && + (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) { + ALOGE("Failed to clean up the composed effect %d", mActiveId); + ret = false; + } + } else { + ALOGV("Vibrator is already off"); + } + + mActiveId = -1; + setGlobalAmplitude(false); + if (mF0Offset) { + mHwApi->setF0Offset(0); + } + + if (ret) { + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } +} + +ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs, + const std::shared_ptr &callback) { + ATRACE_NAME("Vibrator::on"); + if (timeoutMs > MAX_TIME_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + const uint16_t index = (timeoutMs < WAVEFORM_LONG_VIBRATION_THRESHOLD_MS) + ? WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX + : WAVEFORM_LONG_VIBRATION_EFFECT_INDEX; + if (MAX_COLD_START_LATENCY_MS <= MAX_TIME_MS - timeoutMs) { + timeoutMs += MAX_COLD_START_LATENCY_MS; + } + setGlobalAmplitude(true); + if (mF0Offset) { + mHwApi->setF0Offset(mF0Offset); + } + return on(timeoutMs, index, nullptr /*ignored*/, callback); +} + +ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *_aidl_return) { + ATRACE_NAME("Vibrator::perform"); + return performEffect(effect, strength, callback, _aidl_return); +} + +ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector *_aidl_return) { + *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK, + Effect::DOUBLE_CLICK}; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::setAmplitude(float amplitude) { + ATRACE_NAME("Vibrator::setAmplitude"); + if (amplitude <= 0.0f || amplitude > 1.0f) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + mLongEffectScale = amplitude; + if (!isUnderExternalControl()) { + return setGlobalAmplitude(true); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) { + ATRACE_NAME("Vibrator::setExternalControl"); + setGlobalAmplitude(enabled); + + if (mHasHapticAlsaDevice || mConfigHapticAlsaDeviceDone || hasHapticAlsaDevice()) { + if (!mHwApi->setHapticPcmAmp(&mHapticPcm, enabled, mCard, mDevice)) { + ALOGE("Failed to %s haptic pcm device: %d", (enabled ? "enable" : "disable"), mDevice); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } else { + ALOGE("No haptics ALSA device"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mIsUnderExternalControl = enabled; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompositionDelayMax(int32_t *maxDelayMs) { + ATRACE_NAME("Vibrator::getCompositionDelayMax"); + *maxDelayMs = COMPOSE_DELAY_MAX_MS; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t *maxSize) { + ATRACE_NAME("Vibrator::getCompositionSizeMax"); + *maxSize = COMPOSE_SIZE_MAX; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getSupportedPrimitives(std::vector *supported) { + *supported = mSupportedPrimitives; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive primitive, + int32_t *durationMs) { + ndk::ScopedAStatus status; + uint32_t effectIndex; + if (primitive != CompositePrimitive::NOOP) { + status = getPrimitiveDetails(primitive, &effectIndex); + if (!status.isOk()) { + return status; + } + + *durationMs = mEffectDurations[effectIndex]; + } else { + *durationMs = 0; + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::compose(const std::vector &composite, + const std::shared_ptr &callback) { + ATRACE_NAME("Vibrator::compose"); + uint16_t size; + uint16_t nextEffectDelay; + + auto ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_COMP]{0x00}, + FF_CUSTOM_DATA_LEN_MAX_COMP); + + if (composite.size() > COMPOSE_SIZE_MAX || composite.empty()) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + /* Check if there is a wait before the first effect. */ + nextEffectDelay = composite.front().delayMs; + if (nextEffectDelay > COMPOSE_DELAY_MAX_MS || nextEffectDelay < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else if (nextEffectDelay > 0) { + size = composite.size() + 1; + } else { + size = composite.size(); + } + + dspmem_chunk_write(ch, 8, 0); /* Padding */ + dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & size)); /* nsections */ + dspmem_chunk_write(ch, 8, 0); /* repeat */ + uint8_t header_count = dspmem_chunk_bytes(ch); + + /* Insert 1 section for a wait before the first effect. */ + if (nextEffectDelay) { + dspmem_chunk_write(ch, 32, 0); /* amplitude, index, repeat & flags */ + dspmem_chunk_write(ch, 16, (uint16_t)(0xFFFF & nextEffectDelay)); /* delay */ + } + + for (uint32_t i_curr = 0, i_next = 1; i_curr < composite.size(); i_curr++, i_next++) { + auto &e_curr = composite[i_curr]; + uint32_t effectIndex = 0; + uint32_t effectVolLevel = 0; + if (e_curr.scale < 0.0f || e_curr.scale > 1.0f) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (e_curr.primitive != CompositePrimitive::NOOP) { + ndk::ScopedAStatus status; + status = getPrimitiveDetails(e_curr.primitive, &effectIndex); + if (!status.isOk()) { + return status; + } + effectVolLevel = intensityToVolLevel(e_curr.scale, effectIndex); + } + + /* Fetch the next composite effect delay and fill into the current section */ + nextEffectDelay = 0; + if (i_next < composite.size()) { + auto &e_next = composite[i_next]; + int32_t delay = e_next.delayMs; + + if (delay > COMPOSE_DELAY_MAX_MS || delay < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + nextEffectDelay = delay; + } + + if (effectIndex == 0 && nextEffectDelay == 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & effectVolLevel)); /* amplitude */ + dspmem_chunk_write(ch, 8, (uint8_t)(0xFF & effectIndex)); /* index */ + dspmem_chunk_write(ch, 8, 0); /* repeat */ + dspmem_chunk_write(ch, 8, 0); /* flags */ + dspmem_chunk_write(ch, 16, (uint16_t)(0xFFFF & nextEffectDelay)); /* delay */ + } + dspmem_chunk_flush(ch); + if (header_count == dspmem_chunk_bytes(ch)) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else { + return performEffect(WAVEFORM_MAX_INDEX /*ignored*/, VOLTAGE_SCALE_MAX /*ignored*/, ch, + callback); + } +} + +ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem_chunk *ch, + const std::shared_ptr &callback) { + ndk::ScopedAStatus status = ndk::ScopedAStatus::ok(); + + if (effectIndex >= FF_MAX_EFFECTS) { + ALOGE("Invalid waveform index %d", effectIndex); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) { + ALOGE("Previous vibration pending: prev: %d, curr: %d", mActiveId, effectIndex); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + if (ch) { + /* Upload OWT effect. */ + if (ch->head == nullptr) { + ALOGE("Invalid OWT bank"); + delete ch; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + bool isPwle = (*reinterpret_cast(ch->head) != 0x0000); + effectIndex = isPwle ? WAVEFORM_PWLE : WAVEFORM_COMPOSE; + + uint32_t freeBytes; + mHwApi->getOwtFreeSpace(&freeBytes); + if (dspmem_chunk_bytes(ch) > freeBytes) { + ALOGE("Invalid OWT length: Effect %d: %d > %d!", effectIndex, dspmem_chunk_bytes(ch), + freeBytes); + delete ch; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + int errorStatus; + if (!mHwApi->uploadOwtEffect(mInputFd, ch->head, dspmem_chunk_bytes(ch), + &mFfEffects[effectIndex], &effectIndex, &errorStatus)) { + delete ch; + ALOGE("Invalid uploadOwtEffect"); + return ndk::ScopedAStatus::fromExceptionCode(errorStatus); + } + delete ch; + + } else if (effectIndex == WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX || + effectIndex == WAVEFORM_LONG_VIBRATION_EFFECT_INDEX) { + /* Update duration for long/short vibration. */ + mFfEffects[effectIndex].replay.length = static_cast(timeoutMs); + if (!mHwApi->setFFEffect(mInputFd, &mFfEffects[effectIndex], + static_cast(timeoutMs))) { + ALOGE("Failed to edit effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + } + + const std::scoped_lock lock(mActiveId_mutex); + mActiveId = effectIndex; + /* Play the event now. */ + if (!mHwApi->setFFPlay(mInputFd, effectIndex, true)) { + ALOGE("Failed to play effect %d (%d): %s", effectIndex, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + + mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) { + uint16_t scale = amplitudeToScale(amplitude, maximum); + if (!mHwApi->setFFGain(mInputFd, scale)) { + ALOGE("Failed to set the gain to %u (%d): %s", scale, errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) { + uint8_t amplitude = set ? roundf(mLongEffectScale * mLongEffectVol[1]) : VOLTAGE_SCALE_MAX; + if (!set) { + mLongEffectScale = 1.0; // Reset the scale for the later new effect. + } + return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX); +} + +ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector * /*_aidl_return*/) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t /*id*/, Effect /*effect*/, + EffectStrength /*strength*/) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} +ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t /*id*/) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); +} + +ndk::ScopedAStatus Vibrator::getResonantFrequency(float *resonantFreqHz) { + std::string caldata{8, '0'}; + if (!mHwCal->getF0(&caldata)) { + ALOGE("Failed to get resonant frequency (%d): %s", errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + *resonantFreqHz = static_cast(std::stoul(caldata, nullptr, 16)) / (1 << Q14_BIT_SHIFT); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getQFactor(float *qFactor) { + std::string caldata{8, '0'}; + if (!mHwCal->getQ(&caldata)) { + ALOGE("Failed to get q factor (%d): %s", errno, strerror(errno)); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); + } + *qFactor = static_cast(std::stoul(caldata, nullptr, 16)) / (1 << Q16_BIT_SHIFT); + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getFrequencyResolution(float *freqResolutionHz) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + *freqResolutionHz = PWLE_FREQUENCY_RESOLUTION_HZ; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getFrequencyMinimum(float *freqMinimumHz) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + *freqMinimumHz = PWLE_FREQUENCY_MIN_HZ; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getBandwidthAmplitudeMap(std::vector *_aidl_return) { + // TODO(b/170919640): complete implementation + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_FREQUENCY_CONTROL) { + std::vector bandwidthAmplitudeMap(PWLE_BW_MAP_SIZE, 1.0); + *_aidl_return = bandwidthAmplitudeMap; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getPwlePrimitiveDurationMax(int32_t *durationMs) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *durationMs = COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getPwleCompositionSizeMax(int32_t *maxSize) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *maxSize = COMPOSE_PWLE_SIZE_MAX_DEFAULT; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +ndk::ScopedAStatus Vibrator::getSupportedBraking(std::vector *supported) { + int32_t capabilities; + Vibrator::getCapabilities(&capabilities); + if (capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) { + *supported = { + Braking::NONE, + }; + return ndk::ScopedAStatus::ok(); + } else { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } +} + +static void resetPreviousEndAmplitudeEndFrequency(float *prevEndAmplitude, + float *prevEndFrequency) { + const float reset = -1.0; + *prevEndAmplitude = reset; + *prevEndFrequency = reset; +} + +static void incrementIndex(int *index) { + *index += 1; +} + +static void constructPwleSegment(dspmem_chunk *ch, uint16_t delay, uint16_t amplitude, + uint16_t frequency, uint8_t flags, uint32_t vbemfTarget = 0) { + dspmem_chunk_write(ch, 16, delay); + dspmem_chunk_write(ch, 12, amplitude); + dspmem_chunk_write(ch, 12, frequency); + /* feature flags to control the chirp, CLAB braking, back EMF amplitude regulation */ + dspmem_chunk_write(ch, 8, (flags | 1) << 4); + if (flags & PWLE_AMP_REG_BIT) { + dspmem_chunk_write(ch, 24, vbemfTarget); /* target back EMF voltage */ + } +} + +static int constructActiveSegment(dspmem_chunk *ch, int duration, float amplitude, float frequency, + bool chirp) { + uint16_t delay = 0; + uint16_t amp = 0; + uint16_t freq = 0; + uint8_t flags = 0x0; + if ((floatToUint16(duration, &delay, 4, 0.0f, COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) < 0) || + (floatToUint16(amplitude, &, 2048, CS40L26_PWLE_LEVEL_MIX, CS40L26_PWLE_LEVEL_MAX) < + 0) || + (floatToUint16(frequency, &freq, 4, PWLE_FREQUENCY_MIN_HZ, PWLE_FREQUENCY_MAX_HZ) < 0)) { + ALOGE("Invalid argument: %d, %f, %f", duration, amplitude, frequency); + return -ERANGE; + } + if (chirp) { + flags |= PWLE_CHIRP_BIT; + } + constructPwleSegment(ch, delay, amp, freq, flags, 0 /*ignored*/); + return 0; +} + +static int constructBrakingSegment(dspmem_chunk *ch, int duration, Braking brakingType) { + uint16_t delay = 0; + uint16_t freq = 0; + uint8_t flags = 0x00; + if (floatToUint16(duration, &delay, 4, 0.0f, COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) < 0) { + ALOGE("Invalid argument: %d", duration); + return -ERANGE; + } + floatToUint16(PWLE_FREQUENCY_MIN_HZ, &freq, 4, PWLE_FREQUENCY_MIN_HZ, PWLE_FREQUENCY_MAX_HZ); + if (static_cast::type>(brakingType)) { + flags |= PWLE_BRAKE_BIT; + } + + constructPwleSegment(ch, delay, 0 /*ignored*/, freq, flags, 0 /*ignored*/); + return 0; +} + +static void updateWLength(dspmem_chunk *ch, uint32_t totalDuration) { + totalDuration *= 8; /* Unit: 0.125 ms (since wlength played @ 8kHz). */ + totalDuration |= WT_LEN_CALCD; /* Bit 23 is for WT_LEN_CALCD; Bit 22 is for WT_INDEFINITE. */ + *(ch->head + 0) = (totalDuration >> 24) & 0xFF; + *(ch->head + 1) = (totalDuration >> 16) & 0xFF; + *(ch->head + 2) = (totalDuration >> 8) & 0xFF; + *(ch->head + 3) = totalDuration & 0xFF; +} + +static void updateNSection(dspmem_chunk *ch, int segmentIdx) { + *(ch->head + 7) |= (0xF0 & segmentIdx) >> 4; /* Bit 4 to 7 */ + *(ch->head + 9) |= (0x0F & segmentIdx) << 4; /* Bit 3 to 0 */ +} + +ndk::ScopedAStatus Vibrator::composePwle(const std::vector &composite, + const std::shared_ptr &callback) { + ATRACE_NAME("Vibrator::composePwle"); + int32_t capabilities; + + Vibrator::getCapabilities(&capabilities); + if ((capabilities & IVibrator::CAP_COMPOSE_PWLE_EFFECTS) == 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + if (composite.empty() || composite.size() > COMPOSE_PWLE_SIZE_MAX_DEFAULT) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + std::vector supported; + Vibrator::getSupportedBraking(&supported); + bool isClabSupported = + std::find(supported.begin(), supported.end(), Braking::CLAB) != supported.end(); + + int segmentIdx = 0; + uint32_t totalDuration = 0; + float prevEndAmplitude; + float prevEndFrequency; + resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency); + auto ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_PWLE]{0x00}, + FF_CUSTOM_DATA_LEN_MAX_PWLE); + bool chirp = false; + + dspmem_chunk_write(ch, 24, 0x000000); /* Waveform length placeholder */ + dspmem_chunk_write(ch, 8, 0); /* Repeat */ + dspmem_chunk_write(ch, 12, 0); /* Wait time between repeats */ + dspmem_chunk_write(ch, 8, 0x00); /* nsections placeholder */ + + for (auto &e : composite) { + switch (e.getTag()) { + case PrimitivePwle::active: { + auto active = e.get(); + if (active.duration < 0 || + active.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (active.startAmplitude < PWLE_LEVEL_MIN || + active.startAmplitude > PWLE_LEVEL_MAX || + active.endAmplitude < PWLE_LEVEL_MIN || active.endAmplitude > PWLE_LEVEL_MAX) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (active.startAmplitude > CS40L26_PWLE_LEVEL_MAX) { + active.startAmplitude = CS40L26_PWLE_LEVEL_MAX; + } + if (active.endAmplitude > CS40L26_PWLE_LEVEL_MAX) { + active.endAmplitude = CS40L26_PWLE_LEVEL_MAX; + } + + if (active.startFrequency < PWLE_FREQUENCY_MIN_HZ || + active.startFrequency > PWLE_FREQUENCY_MAX_HZ || + active.endFrequency < PWLE_FREQUENCY_MIN_HZ || + active.endFrequency > PWLE_FREQUENCY_MAX_HZ) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (!((active.startAmplitude == prevEndAmplitude) && + (active.startFrequency == prevEndFrequency))) { + if (constructActiveSegment(ch, 0, active.startAmplitude, active.startFrequency, + false) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + } + + if (active.startFrequency != active.endFrequency) { + chirp = true; + } + if (constructActiveSegment(ch, active.duration, active.endAmplitude, + active.endFrequency, chirp) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + + prevEndAmplitude = active.endAmplitude; + prevEndFrequency = active.endFrequency; + totalDuration += active.duration; + chirp = false; + break; + } + case PrimitivePwle::braking: { + auto braking = e.get(); + if (braking.braking > Braking::CLAB) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } else if (!isClabSupported && (braking.braking == Braking::CLAB)) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (braking.duration > COMPOSE_PWLE_PRIMITIVE_DURATION_MAX_MS) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (constructBrakingSegment(ch, 0, braking.braking) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + + if (constructBrakingSegment(ch, braking.duration, braking.braking) < 0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + incrementIndex(&segmentIdx); + + resetPreviousEndAmplitudeEndFrequency(&prevEndAmplitude, &prevEndFrequency); + totalDuration += braking.duration; + break; + } + } + + if (segmentIdx > COMPOSE_PWLE_SIZE_MAX_DEFAULT) { + ALOGE("Too many PrimitivePwle section!"); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + } + dspmem_chunk_flush(ch); + + /* Update wlength */ + totalDuration += MAX_COLD_START_LATENCY_MS; + if (totalDuration > 0x7FFFF) { + ALOGE("Total duration is too long (%d)!", totalDuration); + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + updateWLength(ch, totalDuration); + + /* Update nsections */ + updateNSection(ch, segmentIdx); + + return performEffect(WAVEFORM_MAX_INDEX /*ignored*/, VOLTAGE_SCALE_MAX /*ignored*/, ch, + callback); +} + +bool Vibrator::isUnderExternalControl() { + return mIsUnderExternalControl; +} + +binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { + if (fd < 0) { + ALOGE("Called debug() with invalid fd."); + return STATUS_OK; + } + + (void)args; + (void)numArgs; + + dprintf(fd, "AIDL:\n"); + + dprintf(fd, " F0 Offset: %" PRIu32 "\n", mF0Offset); + + dprintf(fd, " Voltage Levels:\n"); + dprintf(fd, " Tick Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mTickEffectVol[0], + mTickEffectVol[1]); + dprintf(fd, " Click Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mClickEffectVol[0], + mClickEffectVol[1]); + dprintf(fd, " Long Effect Min: %" PRIu32 " Max: %" PRIu32 "\n", mLongEffectVol[0], + mLongEffectVol[1]); + + dprintf(fd, " FF effect:\n"); + dprintf(fd, " Physical waveform:\n"); + dprintf(fd, "\tId\tIndex\tt ->\tt'\n"); + for (uint8_t effectId = 0; effectId < WAVEFORM_MAX_PHYSICAL_INDEX; effectId++) { + dprintf(fd, "\t%d\t%d\t%d\t%d\n", mFfEffects[effectId].id, + mFfEffects[effectId].u.periodic.custom_data[1], mEffectDurations[effectId], + mFfEffects[effectId].replay.length); + } + dprintf(fd, " OWT waveform:\n"); + dprintf(fd, "\tId\tBytes\tData\n"); + for (uint8_t effectId = WAVEFORM_MAX_PHYSICAL_INDEX; effectId < WAVEFORM_MAX_INDEX; + effectId++) { + uint32_t numBytes = mFfEffects[effectId].u.periodic.custom_len * 2; + std::stringstream ss; + ss << " "; + for (int i = 0; i < numBytes; i++) { + ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex + << (uint16_t)(*( + reinterpret_cast(mFfEffects[effectId].u.periodic.custom_data) + + i)) + << " "; + } + dprintf(fd, "\t%d\t%d\t{%s}\n", mFfEffects[effectId].id, numBytes, ss.str().c_str()); + } + + dprintf(fd, "\n"); + dprintf(fd, "\n"); + + mHwApi->debug(fd); + + dprintf(fd, "\n"); + + mHwCal->debug(fd); + + fsync(fd); + return STATUS_OK; +} + +bool Vibrator::hasHapticAlsaDevice() { + // We need to call findHapticAlsaDevice once only. Calling in the + // constructor is too early in the boot process and the pcm file contents + // are empty. Hence we make the call here once only right before we need to. + if (!mConfigHapticAlsaDeviceDone) { + if (mHwApi->getHapticAlsaDevice(&mCard, &mDevice)) { + mHasHapticAlsaDevice = true; + mConfigHapticAlsaDeviceDone = true; + } else { + ALOGE("Haptic ALSA device not supported"); + } + } else { + ALOGD("Haptic ALSA device configuration done."); + } + return mHasHapticAlsaDevice; +} + +ndk::ScopedAStatus Vibrator::getSimpleDetails(Effect effect, EffectStrength strength, + uint32_t *outEffectIndex, uint32_t *outTimeMs, + uint32_t *outVolLevel) { + uint32_t effectIndex; + uint32_t timeMs; + float intensity; + uint32_t volLevel; + switch (strength) { + case EffectStrength::LIGHT: + intensity = 0.5f; + break; + case EffectStrength::MEDIUM: + intensity = 0.7f; + break; + case EffectStrength::STRONG: + intensity = 1.0f; + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + switch (effect) { + case Effect::TEXTURE_TICK: + effectIndex = WAVEFORM_LIGHT_TICK_INDEX; + intensity *= 0.5f; + break; + case Effect::TICK: + effectIndex = WAVEFORM_CLICK_INDEX; + intensity *= 0.5f; + break; + case Effect::CLICK: + effectIndex = WAVEFORM_CLICK_INDEX; + intensity *= 0.7f; + break; + case Effect::HEAVY_CLICK: + effectIndex = WAVEFORM_CLICK_INDEX; + intensity *= 1.0f; + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + volLevel = intensityToVolLevel(intensity, effectIndex); + timeMs = mEffectDurations[effectIndex] + MAX_COLD_START_LATENCY_MS; + + *outEffectIndex = effectIndex; + *outTimeMs = timeMs; + *outVolLevel = volLevel; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getCompoundDetails(Effect effect, EffectStrength strength, + uint32_t *outTimeMs, dspmem_chunk *outCh) { + ndk::ScopedAStatus status; + uint32_t timeMs = 0; + uint32_t thisEffectIndex; + uint32_t thisTimeMs; + uint32_t thisVolLevel; + switch (effect) { + case Effect::DOUBLE_CLICK: + dspmem_chunk_write(outCh, 8, 0); /* Padding */ + dspmem_chunk_write(outCh, 8, 2); /* nsections */ + dspmem_chunk_write(outCh, 8, 0); /* repeat */ + + status = getSimpleDetails(Effect::CLICK, strength, &thisEffectIndex, &thisTimeMs, + &thisVolLevel); + if (!status.isOk()) { + return status; + } + timeMs += thisTimeMs; + + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisVolLevel)); /* amplitude */ + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisEffectIndex)); /* index */ + dspmem_chunk_write(outCh, 8, 0); /* repeat */ + dspmem_chunk_write(outCh, 8, 0); /* flags */ + dspmem_chunk_write(outCh, 16, + (uint16_t)(0xFFFF & WAVEFORM_DOUBLE_CLICK_SILENCE_MS)); /* delay */ + + timeMs += WAVEFORM_DOUBLE_CLICK_SILENCE_MS + MAX_PAUSE_TIMING_ERROR_MS; + + status = getSimpleDetails(Effect::HEAVY_CLICK, strength, &thisEffectIndex, &thisTimeMs, + &thisVolLevel); + if (!status.isOk()) { + return status; + } + timeMs += thisTimeMs; + + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisVolLevel)); /* amplitude */ + dspmem_chunk_write(outCh, 8, (uint8_t)(0xFF & thisEffectIndex)); /* index */ + dspmem_chunk_write(outCh, 8, 0); /* repeat */ + dspmem_chunk_write(outCh, 8, 0); /* flags */ + dspmem_chunk_write(outCh, 16, 0); /* delay */ + dspmem_chunk_flush(outCh); + + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + *outTimeMs = timeMs; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::getPrimitiveDetails(CompositePrimitive primitive, + uint32_t *outEffectIndex) { + uint32_t effectIndex; + uint32_t primitiveBit = 1 << int32_t(primitive); + if ((primitiveBit & mSupportedPrimitivesBits) == 0x0) { + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + switch (primitive) { + case CompositePrimitive::NOOP: + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + case CompositePrimitive::CLICK: + effectIndex = WAVEFORM_CLICK_INDEX; + break; + case CompositePrimitive::THUD: + effectIndex = WAVEFORM_THUD_INDEX; + break; + case CompositePrimitive::SPIN: + effectIndex = WAVEFORM_SPIN_INDEX; + break; + case CompositePrimitive::QUICK_RISE: + effectIndex = WAVEFORM_QUICK_RISE_INDEX; + break; + case CompositePrimitive::SLOW_RISE: + effectIndex = WAVEFORM_SLOW_RISE_INDEX; + break; + case CompositePrimitive::QUICK_FALL: + effectIndex = WAVEFORM_QUICK_FALL_INDEX; + break; + case CompositePrimitive::LIGHT_TICK: + effectIndex = WAVEFORM_LIGHT_TICK_INDEX; + break; + case CompositePrimitive::LOW_TICK: + effectIndex = WAVEFORM_LOW_TICK_INDEX; + break; + default: + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + *outEffectIndex = effectIndex; + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *outTimeMs) { + ndk::ScopedAStatus status; + uint32_t effectIndex; + uint32_t timeMs = 0; + uint32_t volLevel; + dspmem_chunk *ch = nullptr; + switch (effect) { + case Effect::TEXTURE_TICK: + // fall-through + case Effect::TICK: + // fall-through + case Effect::CLICK: + // fall-through + case Effect::HEAVY_CLICK: + status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel); + break; + case Effect::DOUBLE_CLICK: + ch = dspmem_chunk_create(new uint8_t[FF_CUSTOM_DATA_LEN_MAX_COMP]{0x00}, + FF_CUSTOM_DATA_LEN_MAX_COMP); + status = getCompoundDetails(effect, strength, &timeMs, ch); + volLevel = VOLTAGE_SCALE_MAX; + break; + default: + status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + break; + } + if (!status.isOk()) { + goto exit; + } + + status = performEffect(effectIndex, volLevel, ch, callback); + +exit: + *outTimeMs = timeMs; + return status; +} + +ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel, + dspmem_chunk *ch, + const std::shared_ptr &callback) { + setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX); + + return on(MAX_TIME_MS, effectIndex, ch, callback); +} + +void Vibrator::waitForComplete(std::shared_ptr &&callback) { + if (!mHwApi->pollVibeState(VIBE_STATE_HAPTIC, POLLING_TIMEOUT)) { + ALOGW("Failed to get state \"Haptic\""); + } + mHwApi->pollVibeState(VIBE_STATE_STOPPED); + + const std::scoped_lock lock(mActiveId_mutex); + if ((mActiveId >= WAVEFORM_MAX_PHYSICAL_INDEX) && + (!mHwApi->eraseOwtEffect(mInputFd, mActiveId, &mFfEffects))) { + ALOGE("Failed to clean up the composed effect %d", mActiveId); + } + mActiveId = -1; + + if (callback) { + auto ret = callback->onComplete(); + if (!ret.isOk()) { + ALOGE("Failed completion callback: %d", ret.getExceptionCode()); + } + } +} + +uint32_t Vibrator::intensityToVolLevel(float intensity, uint32_t effectIndex) { + uint32_t volLevel; + auto calc = [](float intst, std::array v) -> uint32_t { + return std::lround(intst * (v[1] - v[0])) + v[0]; + }; + + switch (effectIndex) { + case WAVEFORM_LIGHT_TICK_INDEX: + volLevel = calc(intensity, mTickEffectVol); + break; + case WAVEFORM_QUICK_RISE_INDEX: + // fall-through + case WAVEFORM_QUICK_FALL_INDEX: + volLevel = calc(intensity, mLongEffectVol); + break; + case WAVEFORM_CLICK_INDEX: + // fall-through + case WAVEFORM_THUD_INDEX: + // fall-through + case WAVEFORM_SPIN_INDEX: + // fall-through + case WAVEFORM_SLOW_RISE_INDEX: + // fall-through + default: + volLevel = calc(intensity, mClickEffectVol); + break; + } + return volLevel; +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h new file mode 100644 index 0000000..220c974 --- /dev/null +++ b/vibrator/cs40l26/Vibrator.h @@ -0,0 +1,211 @@ +/* + * 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 +#include +#include +#include + +#include +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +class Vibrator : public BnVibrator { + public: + // APIs for interfacing with the kernel driver. + class HwApi { + public: + virtual ~HwApi() = default; + // Stores the LRA resonant frequency to be used for PWLE playback + // and click compensation. + virtual bool setF0(std::string value) = 0; + // Stores the frequency offset for long vibrations. + virtual bool setF0Offset(uint32_t value) = 0; + // Stores the LRA series resistance to be used for click + // compensation. + virtual bool setRedc(std::string value) = 0; + // Stores the LRA Q factor to be used for Q-dependent waveform + // selection. + virtual bool setQ(std::string value) = 0; + // Reports the number of effect waveforms loaded in firmware. + virtual bool getEffectCount(uint32_t *value) = 0; + // Blocks until timeout or vibrator reaches desired state + // (2 = ASP enabled, 1 = haptic enabled, 0 = disabled). + virtual bool pollVibeState(uint32_t value, int32_t timeoutMs = -1) = 0; + // Reports whether getOwtFreeSpace() is supported. + virtual bool hasOwtFreeSpace() = 0; + // Reports the available OWT bytes. + virtual bool getOwtFreeSpace(uint32_t *value) = 0; + // Enables/Disables F0 compensation enable status + virtual bool setF0CompEnable(bool value) = 0; + // Enables/Disables Redc compensation enable status + virtual bool setRedcCompEnable(bool value) = 0; + // Stores the minumun delay time between playback and stop effects. + virtual bool setMinOnOffInterval(uint32_t value) = 0; + // Indicates the number of 0.125-dB steps of attenuation to apply to + // waveforms triggered in response to vibration calls from the + // Android vibrator HAL. + virtual bool setFFGain(int fd, uint16_t value) = 0; + // Create/modify custom effects for all physical waveforms. + virtual bool setFFEffect(int fd, struct ff_effect *effect, uint16_t timeoutMs) = 0; + // Activates/deactivates the effect index after setFFGain() and setFFEffect(). + virtual bool setFFPlay(int fd, int8_t index, bool value) = 0; + // Get the Alsa device for the audio coupled haptics effect + virtual bool getHapticAlsaDevice(int *card, int *device) = 0; + // Set haptics PCM amplifier before triggering audio haptics feature + virtual bool setHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, + int device) = 0; + // Set OWT waveform for compose or compose PWLE request + virtual bool uploadOwtEffect(int fd, uint8_t *owtData, uint32_t numBytes, + struct ff_effect *effect, uint32_t *outEffectIndex, + int *status) = 0; + // Erase OWT waveform + virtual bool eraseOwtEffect(int fd, int8_t effectIndex, std::vector *effect) = 0; + // Emit diagnostic information to the given file. + virtual void debug(int fd) = 0; + }; + + // APIs for obtaining calibration/configuration data from persistent memory. + class HwCal { + public: + virtual ~HwCal() = default; + // Obtain the calibration version + virtual bool getVersion(uint32_t *value) = 0; + // Obtains the LRA resonant frequency to be used for PWLE playback + // and click compensation. + virtual bool getF0(std::string *value) = 0; + // Obtains the LRA series resistance to be used for click + // compensation. + virtual bool getRedc(std::string *value) = 0; + // Obtains the LRA Q factor to be used for Q-dependent waveform + // selection. + virtual bool getQ(std::string *value) = 0; + // Obtains frequency shift for long vibrations. + virtual bool getLongFrequencyShift(int32_t *value) = 0; + // Obtains the v0/v1(min/max) voltage levels to be applied for + // tick/click/long in units of 1%. + virtual bool getTickVolLevels(std::array *value) = 0; + virtual bool getClickVolLevels(std::array *value) = 0; + virtual bool getLongVolLevels(std::array *value) = 0; + // Checks if the chirp feature is enabled. + virtual bool isChirpEnabled() = 0; + // Obtains the supported primitive effects. + virtual bool getSupportedPrimitives(uint32_t *value) = 0; + // Checks if the f0 compensation feature needs to be enabled. + virtual bool isF0CompEnabled() = 0; + // Checks if the redc compensation feature needs to be enabled. + virtual bool isRedcCompEnabled() = 0; + // Emit diagnostic information to the given file. + virtual void debug(int fd) = 0; + }; + + public: + Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal); + + ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override; + ndk::ScopedAStatus off() override; + ndk::ScopedAStatus on(int32_t timeoutMs, + const std::shared_ptr &callback) override; + ndk::ScopedAStatus perform(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *_aidl_return) override; + ndk::ScopedAStatus getSupportedEffects(std::vector *_aidl_return) override; + ndk::ScopedAStatus setAmplitude(float amplitude) override; + ndk::ScopedAStatus setExternalControl(bool enabled) override; + ndk::ScopedAStatus getCompositionDelayMax(int32_t *maxDelayMs); + ndk::ScopedAStatus getCompositionSizeMax(int32_t *maxSize); + ndk::ScopedAStatus getSupportedPrimitives(std::vector *supported) override; + ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive, + int32_t *durationMs) override; + ndk::ScopedAStatus compose(const std::vector &composite, + const std::shared_ptr &callback) override; + ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector *_aidl_return) override; + ndk::ScopedAStatus alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) override; + ndk::ScopedAStatus alwaysOnDisable(int32_t id) override; + ndk::ScopedAStatus getResonantFrequency(float *resonantFreqHz) override; + ndk::ScopedAStatus getQFactor(float *qFactor) override; + ndk::ScopedAStatus getFrequencyResolution(float *freqResolutionHz) override; + ndk::ScopedAStatus getFrequencyMinimum(float *freqMinimumHz) override; + ndk::ScopedAStatus getBandwidthAmplitudeMap(std::vector *_aidl_return) override; + ndk::ScopedAStatus getPwlePrimitiveDurationMax(int32_t *durationMs) override; + ndk::ScopedAStatus getPwleCompositionSizeMax(int32_t *maxSize) override; + ndk::ScopedAStatus getSupportedBraking(std::vector *supported) override; + ndk::ScopedAStatus composePwle(const std::vector &composite, + const std::shared_ptr &callback) override; + + binder_status_t dump(int fd, const char **args, uint32_t numArgs) override; + + private: + ndk::ScopedAStatus on(uint32_t timeoutMs, uint32_t effectIndex, struct dspmem_chunk *ch, + const std::shared_ptr &callback); + // set 'amplitude' based on an arbitrary scale determined by 'maximum' + ndk::ScopedAStatus setEffectAmplitude(float amplitude, float maximum); + ndk::ScopedAStatus setGlobalAmplitude(bool set); + // 'simple' effects are those precompiled and loaded into the controller + ndk::ScopedAStatus getSimpleDetails(Effect effect, EffectStrength strength, + uint32_t *outEffectIndex, uint32_t *outTimeMs, + uint32_t *outVolLevel); + // 'compound' effects are those composed by stringing multiple 'simple' effects + ndk::ScopedAStatus getCompoundDetails(Effect effect, EffectStrength strength, + uint32_t *outTimeMs, struct dspmem_chunk *outCh); + ndk::ScopedAStatus getPrimitiveDetails(CompositePrimitive primitive, uint32_t *outEffectIndex); + ndk::ScopedAStatus performEffect(Effect effect, EffectStrength strength, + const std::shared_ptr &callback, + int32_t *outTimeMs); + ndk::ScopedAStatus performEffect(uint32_t effectIndex, uint32_t volLevel, + struct dspmem_chunk *ch, + const std::shared_ptr &callback); + ndk::ScopedAStatus setPwle(const std::string &pwleQueue); + bool isUnderExternalControl(); + void waitForComplete(std::shared_ptr &&callback); + uint32_t intensityToVolLevel(float intensity, uint32_t effectIndex); + bool findHapticAlsaDevice(int *card, int *device); + bool hasHapticAlsaDevice(); + bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device); + + std::unique_ptr mHwApi; + std::unique_ptr mHwCal; + uint32_t mF0Offset; + std::array mTickEffectVol; + std::array mClickEffectVol; + std::array mLongEffectVol; + std::vector mFfEffects; + std::vector mEffectDurations; + std::future mAsyncHandle; + ::android::base::unique_fd mInputFd; + int8_t mActiveId{-1}; + struct pcm *mHapticPcm; + int mCard; + int mDevice; + bool mHasHapticAlsaDevice{false}; + bool mIsUnderExternalControl; + float mLongEffectScale = 1.0; + bool mIsChirpEnabled; + uint32_t mSupportedPrimitivesBits = 0x0; + std::vector mSupportedPrimitives; + bool mConfigHapticAlsaDeviceDone{false}; +}; + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc new file mode 100644 index 0000000..0fcca56 --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc @@ -0,0 +1,47 @@ +on property:vendor.all.modules.ready=1 + wait /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_cal_time_ms + + mkdir /mnt/vendor/persist/haptics 0770 system system + chmod 770 /mnt/vendor/persist/haptics + chmod 440 /mnt/vendor/persist/haptics/cs40l26.cal + chown system system /mnt/vendor/persist/haptics + chown system system /mnt/vendor/persist/haptics/cs40l26.cal + + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/f0_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/q_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/calibration/redc_stored + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/vibe_state + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/num_waves + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_offset + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/owt_free_space + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/f0_comp_enable + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/redc_comp_enable + chown system system /sys/bus/i2c/devices/i2c-cs40l26a/default/delay_before_stop_playback_us + + enable vendor.vibrator.cs40l26 + +service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26 + class hal + user system + group system input + + setenv INPUT_EVENT_NAME cs40l26_input + setenv INPUT_EVENT_PATH /dev/input/event* + setenv PROPERTY_PREFIX ro.vendor.vibrator.hal. + setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l26.cal + + setenv HWAPI_PATH_PREFIX /sys/bus/i2c/devices/i2c-cs40l26a/ + setenv HWAPI_DEBUG_PATHS " + calibration/f0_stored + calibration/redc_stored + calibration/q_stored + default/vibe_state + default/num_waves + default/f0_offset + default/owt_free_space + default/f0_comp_enable + default/redc_comp_enable + default/delay_before_stop_playback_us + " + + disabled diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml new file mode 100644 index 0000000..4db8f8c --- /dev/null +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml @@ -0,0 +1,7 @@ + + + android.hardware.vibrator + 2 + IVibrator/default + + diff --git a/vibrator/cs40l26/device.mk b/vibrator/cs40l26/device.mk new file mode 100644 index 0000000..5a3dd4f --- /dev/null +++ b/vibrator/cs40l26/device.mk @@ -0,0 +1,6 @@ +PRODUCT_PACKAGES += \ + android.hardware.vibrator-service.cs40l26 + +BOARD_SEPOLICY_DIRS += \ + hardware/google/pixel-sepolicy/vibrator/common \ + hardware/google/pixel-sepolicy/vibrator/cs40l26 diff --git a/vibrator/cs40l26/service.cpp b/vibrator/cs40l26/service.cpp new file mode 100644 index 0000000..27173d9 --- /dev/null +++ b/vibrator/cs40l26/service.cpp @@ -0,0 +1,55 @@ +/* + * 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 +#include +#include +#include +#include + +#include "Hardware.h" +#include "Vibrator.h" + +using ::aidl::android::hardware::vibrator::HwApi; +using ::aidl::android::hardware::vibrator::HwCal; +using ::aidl::android::hardware::vibrator::Vibrator; +using ::android::defaultServiceManager; +using ::android::ProcessState; +using ::android::sp; +using ::android::String16; + +#if !defined(VIBRATOR_NAME) +#define VIBRATOR_NAME "default" +#endif + +int main() { + auto svc = ndk::SharedRefBase::make(std::make_unique(), + std::make_unique()); + const auto svcName = std::string() + svc->descriptor + "/" + VIBRATOR_NAME; + + ProcessState::initWithDriver("/dev/vndbinder"); + + auto svcBinder = svc->asBinder(); + binder_status_t status = AServiceManager_addService(svcBinder.get(), svcName.c_str()); + LOG_ALWAYS_FATAL_IF(status != STATUS_OK); + + ProcessState::self()->setThreadPoolMaxThreadCount(1); + ProcessState::self()->startThreadPool(); + + ABinderProcess_setThreadPoolMaxThreadCount(0); + ABinderProcess_joinThreadPool(); + + return EXIT_FAILURE; // should not reach +} diff --git a/vibrator/cs40l26/tests/Android.bp b/vibrator/cs40l26/tests/Android.bp new file mode 100644 index 0000000..93c9a9f --- /dev/null +++ b/vibrator/cs40l26/tests/Android.bp @@ -0,0 +1,35 @@ +// +// 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. + +package { + default_applicable_licenses: ["Android-Apache-2.0"], +} + +cc_test { + name: "VibratorHalCs40l26TestSuite", + defaults: ["VibratorHalCs40l26TestDefaults"], + srcs: [ + "test-hwcal.cpp", + "test-hwapi.cpp", + "test-vibrator.cpp", + ], + static_libs: [ + "libc++fs", + "libgmock", + ], + shared_libs: [ + "libbase", + ], +} diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h new file mode 100644 index 0000000..21466a0 --- /dev/null +++ b/vibrator/cs40l26/tests/mocks.h @@ -0,0 +1,80 @@ +/* + * 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_HARDWARE_VIBRATOR_TEST_MOCKS_H +#define ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H + +#include + +#include "Vibrator.h" + +class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { + public: + MOCK_METHOD0(destructor, void()); + MOCK_METHOD1(setF0, bool(std::string value)); + MOCK_METHOD1(setF0Offset, bool(uint32_t value)); + MOCK_METHOD1(setRedc, bool(std::string value)); + MOCK_METHOD1(setQ, bool(std::string value)); + MOCK_METHOD1(getEffectCount, bool(uint32_t *value)); + MOCK_METHOD2(pollVibeState, bool(uint32_t value, int32_t timeoutMs)); + MOCK_METHOD0(hasOwtFreeSpace, bool()); + MOCK_METHOD1(getOwtFreeSpace, bool(uint32_t *value)); + MOCK_METHOD1(setF0CompEnable, bool(bool value)); + MOCK_METHOD1(setRedcCompEnable, bool(bool value)); + MOCK_METHOD1(setMinOnOffInterval, bool(uint32_t value)); + MOCK_METHOD2(setFFGain, bool(int fd, uint16_t value)); + MOCK_METHOD3(setFFEffect, bool(int fd, struct ff_effect *effect, uint16_t timeoutMs)); + MOCK_METHOD3(setFFPlay, bool(int fd, int8_t index, bool value)); + MOCK_METHOD2(getHapticAlsaDevice, bool(int *card, int *device)); + MOCK_METHOD4(setHapticPcmAmp, bool(struct pcm **haptic_pcm, bool enable, int card, int device)); + MOCK_METHOD6(uploadOwtEffect, + bool(int fd, uint8_t *owtData, uint32_t numBytes, struct ff_effect *effect, + uint32_t *outEffectIndex, int *status)); + MOCK_METHOD3(eraseOwtEffect, bool(int fd, int8_t effectIndex, std::vector *effect)); + MOCK_METHOD1(debug, void(int fd)); + + ~MockApi() override { destructor(); }; +}; + +class MockCal : public ::aidl::android::hardware::vibrator::Vibrator::HwCal { + public: + MOCK_METHOD0(destructor, void()); + MOCK_METHOD1(getVersion, bool(uint32_t *value)); + MOCK_METHOD1(getF0, bool(std::string &value)); + MOCK_METHOD1(getRedc, bool(std::string &value)); + MOCK_METHOD1(getQ, bool(std::string &value)); + MOCK_METHOD1(getLongFrequencyShift, bool(int32_t *value)); + MOCK_METHOD1(getTickVolLevels, bool(std::array *value)); + MOCK_METHOD1(getClickVolLevels, bool(std::array *value)); + MOCK_METHOD1(getLongVolLevels, bool(std::array *value)); + MOCK_METHOD0(isChirpEnabled, bool()); + MOCK_METHOD1(getSupportedPrimitives, bool(uint32_t *value)); + MOCK_METHOD0(isF0CompEnabled, bool()); + MOCK_METHOD0(isRedcCompEnabled, bool()); + MOCK_METHOD1(debug, void(int fd)); + + ~MockCal() override { destructor(); }; + // b/132668253: Workaround gMock Compilation Issue + bool getF0(std::string *value) { return getF0(*value); } + bool getRedc(std::string *value) { return getRedc(*value); } + bool getQ(std::string *value) { return getQ(*value); } +}; + +class MockVibratorCallback : public aidl::android::hardware::vibrator::BnVibratorCallback { + public: + MOCK_METHOD(ndk::ScopedAStatus, onComplete, ()); +}; + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H diff --git a/vibrator/cs40l26/tests/test-hwapi.cpp b/vibrator/cs40l26/tests/test-hwapi.cpp new file mode 100644 index 0000000..cc4d465 --- /dev/null +++ b/vibrator/cs40l26/tests/test-hwapi.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include + +#include +#include + +#include "Hardware.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::Test; +using ::testing::TestParamInfo; +using ::testing::ValuesIn; +using ::testing::WithParamInterface; + +class HwApiTest : public Test { + private: + static constexpr const char *FILE_NAMES[]{ + "calibration/f0_stored", + "default/f0_offset", + "calibration/redc_stored", + "calibration/q_stored", + "default/f0_comp_enable", + "default/redc_comp_enable", + "default/owt_free_space", + "default/num_waves", + "default/delay_before_stop_playback_us", + }; + + public: + void SetUp() override { + std::string prefix; + for (auto n : FILE_NAMES) { + auto name = std::filesystem::path(n); + auto path = std::filesystem::path(mFilesDir.path) / name; + fs_mkdirs(path.c_str(), S_IRWXU); + std::ofstream touch{path}; + mFileMap[name] = path; + } + prefix = std::filesystem::path(mFilesDir.path) / ""; + setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true); + mHwApi = std::make_unique(); + + for (auto n : FILE_NAMES) { + auto name = std::filesystem::path(n); + auto path = std::filesystem::path(mEmptyDir.path) / name; + } + prefix = std::filesystem::path(mEmptyDir.path) / ""; + setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true); + mNoApi = std::make_unique(); + } + + void TearDown() override { verifyContents(); } + + static auto ParamNameFixup(std::string str) { + std::replace(str.begin(), str.end(), '/', '_'); + return str; + } + + protected: + // Set expected file content for a test. + template + void expectContent(const std::string &name, const T &value) { + mExpectedContent[name] << value << std::endl; + } + + // Set actual file content for an input test. + template + void updateContent(const std::string &name, const T &value) { + std::ofstream(mFileMap[name]) << value << std::endl; + } + + template + void expectAndUpdateContent(const std::string &name, const T &value) { + expectContent(name, value); + updateContent(name, value); + } + + // Compare all file contents against expected contents. + void verifyContents() { + for (auto &a : mFileMap) { + std::ifstream file{a.second}; + std::string expect = mExpectedContent[a.first].str(); + std::string actual = std::string(std::istreambuf_iterator(file), + std::istreambuf_iterator()); + EXPECT_EQ(expect, actual) << a.first; + } + } + + protected: + std::unique_ptr mHwApi; + std::unique_ptr mNoApi; + std::map mFileMap; + TemporaryDir mFilesDir; + TemporaryDir mEmptyDir; + std::map mExpectedContent; +}; + +template +class HwApiTypedTest : public HwApiTest, + public WithParamInterface>> { + public: + static auto PrintParam(const TestParamInfo &info) { + return ParamNameFixup(std::get<0>(info.param)); + } + static auto MakeParam(std::string name, std::function func) { + return std::make_tuple(name, func); + } +}; + +using HasTest = HwApiTypedTest; + +TEST_P(HasTest, success_returnsTrue) { + auto param = GetParam(); + auto func = std::get<1>(param); + + EXPECT_TRUE(func(*mHwApi)); +} + +TEST_P(HasTest, success_returnsFalse) { + auto param = GetParam(); + auto func = std::get<1>(param); + + EXPECT_FALSE(func(*mNoApi)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, HasTest, + ValuesIn({ + HasTest::MakeParam("default/owt_free_space", + &Vibrator::HwApi::hasOwtFreeSpace), + }), + HasTest::PrintParam); + +using GetUint32Test = HwApiTypedTest; + +TEST_P(GetUint32Test, success) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + uint32_t expect = std::rand(); + uint32_t actual = ~expect; + + expectAndUpdateContent(name, expect); + + EXPECT_TRUE(func(*mHwApi, &actual)); + EXPECT_EQ(expect, actual); +} + +TEST_P(GetUint32Test, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + uint32_t value; + + EXPECT_FALSE(func(*mNoApi, &value)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, GetUint32Test, + ValuesIn({ + GetUint32Test::MakeParam("default/num_waves", + &Vibrator::HwApi::getEffectCount), + GetUint32Test::MakeParam("default/owt_free_space", + &Vibrator::HwApi::getOwtFreeSpace), + }), + GetUint32Test::PrintParam); + +using SetBoolTest = HwApiTypedTest; + +TEST_P(SetBoolTest, success_returnsTrue) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + + expectContent(name, "1"); + + EXPECT_TRUE(func(*mHwApi, true)); +} + +TEST_P(SetBoolTest, success_returnsFalse) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + + expectContent(name, "0"); + + EXPECT_TRUE(func(*mHwApi, false)); +} + +TEST_P(SetBoolTest, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + + EXPECT_FALSE(func(*mNoApi, true)); + EXPECT_FALSE(func(*mNoApi, false)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, SetBoolTest, + ValuesIn({ + SetBoolTest::MakeParam("default/f0_comp_enable", + &Vibrator::HwApi::setF0CompEnable), + SetBoolTest::MakeParam("default/redc_comp_enable", + &Vibrator::HwApi::setRedcCompEnable), + }), + SetBoolTest::PrintParam); + +using SetUint32Test = HwApiTypedTest; + +TEST_P(SetUint32Test, success) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + uint32_t value = std::rand(); + + expectContent(name, value); + + EXPECT_TRUE(func(*mHwApi, value)); +} + +TEST_P(SetUint32Test, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + uint32_t value = std::rand(); + + EXPECT_FALSE(func(*mNoApi, value)); +} + +INSTANTIATE_TEST_CASE_P(HwApiTests, SetUint32Test, + ValuesIn({ + SetUint32Test::MakeParam("default/f0_offset", + &Vibrator::HwApi::setF0Offset), + SetUint32Test::MakeParam("default/delay_before_stop_playback_us", + &Vibrator::HwApi::setMinOnOffInterval), + }), + SetUint32Test::PrintParam); + +using SetStringTest = HwApiTypedTest; + +TEST_P(SetStringTest, success) { + auto param = GetParam(); + auto name = std::get<0>(param); + auto func = std::get<1>(param); + std::string value = TemporaryFile().path; + + expectContent(name, value); + + EXPECT_TRUE(func(*mHwApi, value)); +} + +TEST_P(SetStringTest, failure) { + auto param = GetParam(); + auto func = std::get<1>(param); + std::string value = TemporaryFile().path; + + EXPECT_FALSE(func(*mNoApi, value)); +} + +INSTANTIATE_TEST_CASE_P( + HwApiTests, SetStringTest, + ValuesIn({ + SetStringTest::MakeParam("calibration/f0_stored", &Vibrator::HwApi::setF0), + SetStringTest::MakeParam("calibration/redc_stored", &Vibrator::HwApi::setRedc), + SetStringTest::MakeParam("calibration/q_stored", &Vibrator::HwApi::setQ), + }), + SetStringTest::PrintParam); + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/tests/test-hwcal.cpp b/vibrator/cs40l26/tests/test-hwcal.cpp new file mode 100644 index 0000000..e482b6c --- /dev/null +++ b/vibrator/cs40l26/tests/test-hwcal.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include + +#include + +#include "Hardware.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::Test; + +class HwCalTest : public Test { + protected: + static constexpr std::array V_TICK_DEFAULT = {1, 100}; + static constexpr std::array V_CLICK_DEFAULT = {1, 100}; + static constexpr std::array V_LONG_DEFAULT = {1, 100}; + + public: + void SetUp() override { setenv("CALIBRATION_FILEPATH", mCalFile.path, true); } + + private: + template + static void pack(std::ostream &stream, const T &value, std::string lpad, std::string rpad) { + stream << lpad << value << rpad; + } + + template ::size_type N> + static void pack(std::ostream &stream, const std::array &value, std::string lpad, + std::string rpad) { + for (auto &entry : value) { + pack(stream, entry, lpad, rpad); + } + } + + protected: + void createHwCal() { mHwCal = std::make_unique(); } + + template + void write(const std::string key, const T &value, std::string lpad = " ", + std::string rpad = "") { + std::ofstream calfile{mCalFile.path, std::ios_base::app}; + calfile << key << ":"; + pack(calfile, value, lpad, rpad); + calfile << std::endl; + } + + void unlink() { ::unlink(mCalFile.path); } + + protected: + std::unique_ptr mHwCal; + TemporaryFile mCalFile; +}; + +TEST_F(HwCalTest, f0_measured) { + uint32_t randInput = std::rand(); + std::string expect = std::to_string(randInput); + std::string actual = std::to_string(~randInput); + + write("f0_measured", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getF0(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, f0_missing) { + std::string actual; + + createHwCal(); + + EXPECT_FALSE(mHwCal->getF0(&actual)); +} + +TEST_F(HwCalTest, redc_measured) { + uint32_t randInput = std::rand(); + std::string expect = std::to_string(randInput); + std::string actual = std::to_string(~randInput); + + write("redc_measured", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getRedc(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, redc_missing) { + std::string actual; + + createHwCal(); + + EXPECT_FALSE(mHwCal->getRedc(&actual)); +} + +TEST_F(HwCalTest, q_measured) { + uint32_t randInput = std::rand(); + std::string expect = std::to_string(randInput); + std::string actual = std::to_string(~randInput); + + write("q_measured", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getQ(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, q_missing) { + std::string actual; + + createHwCal(); + + EXPECT_FALSE(mHwCal->getQ(&actual)); +} + +TEST_F(HwCalTest, v_levels) { + std::array expect; + std::array actual; + + // voltage for tick effects + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("v_tick", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + // voltage for click effects + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("v_click", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + // voltage for long effects + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("v_long", expect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_missing) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_short) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", std::array()); + write("v_click", std::array()); + write("v_long", std::array()); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_long) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", std::array()); + write("v_click", std::array()); + write("v_long", std::array()); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, v_nofile) { + std::array expect = V_TICK_DEFAULT; + std::array actual; + + std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; }); + + write("v_tick", actual); + write("v_click", actual); + write("v_long", actual); + unlink(); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getTickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_CLICK_DEFAULT; + EXPECT_TRUE(mHwCal->getClickVolLevels(&actual)); + EXPECT_EQ(expect, actual); + + expect = V_LONG_DEFAULT; + EXPECT_TRUE(mHwCal->getLongVolLevels(&actual)); + EXPECT_EQ(expect, actual); +} + +TEST_F(HwCalTest, multiple) { + uint32_t randInput = std::rand(); + std::string f0Expect = std::to_string(randInput); + std::string f0Actual = std::to_string(~randInput); + randInput = std::rand(); + std::string redcExpect = std::to_string(randInput); + std::string redcActual = std::to_string(~randInput); + randInput = std::rand(); + std::string qExpect = std::to_string(randInput); + std::string qActual = std::to_string(~randInput); + std::array volTickExpect, volClickExpect, volLongExpect; + std::array volActual; + + std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("f0_measured", f0Expect); + write("redc_measured", redcExpect); + write("q_measured", qExpect); + write("v_tick", volTickExpect); + std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(), + [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_click", volClickExpect); + std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_long", volLongExpect); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getF0(&f0Actual)); + EXPECT_EQ(f0Expect, f0Actual); + EXPECT_TRUE(mHwCal->getRedc(&redcActual)); + EXPECT_EQ(redcExpect, redcActual); + EXPECT_TRUE(mHwCal->getQ(&qActual)); + EXPECT_EQ(qExpect, qActual); + EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual)); + EXPECT_EQ(volTickExpect, volActual); + EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual)); + EXPECT_EQ(volClickExpect, volActual); + EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual)); + EXPECT_EQ(volLongExpect, volActual); +} + +TEST_F(HwCalTest, trimming) { + uint32_t randInput = std::rand(); + std::string f0Expect = std::to_string(randInput); + std::string f0Actual = std::to_string(~randInput); + randInput = std::rand(); + std::string redcExpect = std::to_string(randInput); + std::string redcActual = std::to_string(randInput); + randInput = std::rand(); + std::string qExpect = std::to_string(randInput); + std::string qActual = std::to_string(randInput); + std::array volTickExpect, volClickExpect, volLongExpect; + std::array volActual; + + std::transform(volTickExpect.begin(), volTickExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + + write("f0_measured", f0Expect, " \t", "\t "); + write("redc_measured", redcExpect, " \t", "\t "); + write("q_measured", qExpect, " \t", "\t "); + write("v_tick", volTickExpect, " \t", "\t "); + std::transform(volClickExpect.begin(), volClickExpect.end(), volActual.begin(), + [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_click", volClickExpect, " \t", "\t "); + std::transform(volLongExpect.begin(), volLongExpect.end(), volActual.begin(), [](uint32_t &e) { + e = std::rand(); + return ~e; + }); + write("v_long", volLongExpect, " \t", "\t "); + + createHwCal(); + + EXPECT_TRUE(mHwCal->getF0(&f0Actual)); + EXPECT_EQ(f0Expect, f0Actual); + EXPECT_TRUE(mHwCal->getRedc(&redcActual)); + EXPECT_EQ(redcExpect, redcActual); + EXPECT_TRUE(mHwCal->getQ(&qActual)); + EXPECT_EQ(qExpect, qActual); + EXPECT_TRUE(mHwCal->getTickVolLevels(&volActual)); + EXPECT_EQ(volTickExpect, volActual); + EXPECT_TRUE(mHwCal->getClickVolLevels(&volActual)); + EXPECT_EQ(volClickExpect, volActual); + EXPECT_TRUE(mHwCal->getLongVolLevels(&volActual)); + EXPECT_EQ(volLongExpect, volActual); +} + +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp new file mode 100644 index 0000000..a8bedd5 --- /dev/null +++ b/vibrator/cs40l26/tests/test-vibrator.cpp @@ -0,0 +1,682 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include + +#include + +#include "Vibrator.h" +#include "mocks.h" +#include "types.h" +#include "utils.h" + +namespace aidl { +namespace android { +namespace hardware { +namespace vibrator { + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Assign; +using ::testing::AtLeast; +using ::testing::AtMost; +using ::testing::Combine; +using ::testing::DoAll; +using ::testing::DoDefault; +using ::testing::Exactly; +using ::testing::Expectation; +using ::testing::ExpectationSet; +using ::testing::Ge; +using ::testing::Mock; +using ::testing::MockFunction; +using ::testing::Range; +using ::testing::Return; +using ::testing::Sequence; +using ::testing::SetArgPointee; +using ::testing::SetArgReferee; +using ::testing::Test; +using ::testing::TestParamInfo; +using ::testing::ValuesIn; +using ::testing::WithParamInterface; + +// Forward Declarations + +static EffectQueue Queue(const QueueEffect &effect); +static EffectQueue Queue(const QueueDelay &delay); +template +static EffectQueue Queue(const T &first, const U &second, Args... rest); + +static EffectLevel Level(float intensity, float levelLow, float levelHigh); +static EffectScale Scale(float intensity, float levelLow, float levelHigh); + +// Constants With Arbitrary Values + +static constexpr uint32_t CAL_VERSION = 2; +static constexpr std::array V_TICK_DEFAULT = {1, 100}; +static constexpr std::array V_CLICK_DEFAULT{1, 100}; +static constexpr std::array V_LONG_DEFAULT{1, 100}; +static constexpr std::array EFFECT_DURATIONS{ + 0, 100, 30, 1000, 300, 130, 150, 500, 100, 15, 20, 1000, 1000, 1000}; + +// Constants With Prescribed Values + +static const std::map EFFECT_INDEX{ + {Effect::CLICK, 2}, + {Effect::TICK, 2}, + {Effect::HEAVY_CLICK, 2}, + {Effect::TEXTURE_TICK, 9}, +}; +static constexpr uint32_t MIN_ON_OFF_INTERVAL_US = 8500; +static constexpr uint8_t VOLTAGE_SCALE_MAX = 100; +static constexpr int8_t MAX_COLD_START_LATENCY_MS = 6; // I2C Transaction + DSP Return-From-Standby +static constexpr auto POLLING_TIMEOUT = 20; +enum WaveformIndex : uint16_t { + /* Physical waveform */ + WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0, + WAVEFORM_RESERVED_INDEX_1 = 1, + WAVEFORM_CLICK_INDEX = 2, + WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3, + WAVEFORM_THUD_INDEX = 4, + WAVEFORM_SPIN_INDEX = 5, + WAVEFORM_QUICK_RISE_INDEX = 6, + WAVEFORM_SLOW_RISE_INDEX = 7, + WAVEFORM_QUICK_FALL_INDEX = 8, + WAVEFORM_LIGHT_TICK_INDEX = 9, + WAVEFORM_LOW_TICK_INDEX = 10, + WAVEFORM_RESERVED_MFG_1, + WAVEFORM_RESERVED_MFG_2, + WAVEFORM_RESERVED_MFG_3, + WAVEFORM_MAX_PHYSICAL_INDEX, + /* OWT waveform */ + WAVEFORM_COMPOSE = WAVEFORM_MAX_PHYSICAL_INDEX, + WAVEFORM_PWLE, + /* + * Refer to , the WAVEFORM_MAX_INDEX must not exceed 96. + * #define FF_GAIN 0x60 // 96 in decimal + * #define FF_MAX_EFFECTS FF_GAIN + */ + WAVEFORM_MAX_INDEX, +}; + +static const EffectScale ON_GLOBAL_SCALE{levelToScale(V_LONG_DEFAULT[1])}; +static const EffectIndex ON_EFFECT_INDEX{0}; + +static const std::map EFFECT_SCALE{ + {{Effect::TICK, EffectStrength::LIGHT}, + Scale(0.5f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::TICK, EffectStrength::MEDIUM}, + Scale(0.5f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::TICK, EffectStrength::STRONG}, + Scale(0.5f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::CLICK, EffectStrength::LIGHT}, + Scale(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::CLICK, EffectStrength::MEDIUM}, + Scale(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::CLICK, EffectStrength::STRONG}, + Scale(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::HEAVY_CLICK, EffectStrength::LIGHT}, + Scale(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::HEAVY_CLICK, EffectStrength::MEDIUM}, + Scale(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::HEAVY_CLICK, EffectStrength::STRONG}, + Scale(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + {{Effect::TEXTURE_TICK, EffectStrength::LIGHT}, + Scale(0.5f * 0.5f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])}, + {{Effect::TEXTURE_TICK, EffectStrength::MEDIUM}, + Scale(0.5f * 0.7f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])}, + {{Effect::TEXTURE_TICK, EffectStrength::STRONG}, + Scale(0.5f * 1.0f, V_TICK_DEFAULT[0], V_TICK_DEFAULT[1])}, +}; + +static const std::map EFFECT_QUEUE{ + {{Effect::DOUBLE_CLICK, EffectStrength::LIGHT}, + Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(0.7f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + 100, + QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(1.0f * 0.5f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})}, + {{Effect::DOUBLE_CLICK, EffectStrength::MEDIUM}, + Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(0.7f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + 100, + QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(1.0f * 0.7f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})}, + {{Effect::DOUBLE_CLICK, EffectStrength::STRONG}, + Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(0.7f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])}, + 100, + QueueEffect{EFFECT_INDEX.at(Effect::CLICK), + Level(1.0f * 1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])})}, +}; + +EffectQueue Queue(const QueueEffect &effect) { + auto index = std::get<0>(effect); + auto level = std::get<1>(effect); + auto string = std::to_string(index) + "." + std::to_string(level); + auto duration = EFFECT_DURATIONS[index]; + return {string, duration}; +} + +EffectQueue Queue(const QueueDelay &delay) { + auto string = std::to_string(delay); + return {string, delay}; +} + +template +EffectQueue Queue(const T &first, const U &second, Args... rest) { + auto head = Queue(first); + auto tail = Queue(second, rest...); + auto string = std::get<0>(head) + "," + std::get<0>(tail); + auto duration = std::get<1>(head) + std::get<1>(tail); + return {string, duration}; +} + +static EffectLevel Level(float intensity, float levelLow, float levelHigh) { + return std::lround(intensity * (levelHigh - levelLow)) + levelLow; +} + +static EffectScale Scale(float intensity, float levelLow, float levelHigh) { + return levelToScale(Level(intensity, levelLow, levelHigh)); +} + +class VibratorTest : public Test { + public: + void SetUp() override { + setenv("INPUT_EVENT_NAME", "CS40L26TestSuite", true); + std::unique_ptr mockapi; + std::unique_ptr mockcal; + + createMock(&mockapi, &mockcal); + createVibrator(std::move(mockapi), std::move(mockcal)); + } + + void TearDown() override { deleteVibrator(); } + + protected: + void createMock(std::unique_ptr *mockapi, std::unique_ptr *mockcal) { + *mockapi = std::make_unique(); + *mockcal = std::make_unique(); + + mMockApi = mockapi->get(); + mMockCal = mockcal->get(); + + ON_CALL(*mMockApi, destructor()).WillByDefault(Assign(&mMockApi, nullptr)); + + ON_CALL(*mMockApi, setFFGain(_, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, setFFEffect(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, setFFPlay(_, _, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, pollVibeState(_, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)).WillByDefault(Return(true)); + ON_CALL(*mMockApi, eraseOwtEffect(_, _, _)).WillByDefault(Return(true)); + + ON_CALL(*mMockApi, getOwtFreeSpace(_)) + .WillByDefault(DoAll(SetArgPointee<0>(11504), Return(true))); + + ON_CALL(*mMockCal, destructor()).WillByDefault(Assign(&mMockCal, nullptr)); + + ON_CALL(*mMockCal, getVersion(_)) + .WillByDefault(DoAll(SetArgPointee<0>(CAL_VERSION), Return(true))); + + ON_CALL(*mMockCal, getTickVolLevels(_)) + .WillByDefault(DoAll(SetArgPointee<0>(V_TICK_DEFAULT), Return(true))); + ON_CALL(*mMockCal, getClickVolLevels(_)) + .WillByDefault(DoAll(SetArgPointee<0>(V_CLICK_DEFAULT), Return(true))); + ON_CALL(*mMockCal, getLongVolLevels(_)) + .WillByDefault(DoAll(SetArgPointee<0>(V_LONG_DEFAULT), Return(true))); + + relaxMock(false); + } + + void createVibrator(std::unique_ptr mockapi, std::unique_ptr mockcal, + bool relaxed = true) { + if (relaxed) { + relaxMock(true); + } + mVibrator = ndk::SharedRefBase::make(std::move(mockapi), std::move(mockcal)); + if (relaxed) { + relaxMock(false); + } + } + + void deleteVibrator(bool relaxed = true) { + if (relaxed) { + relaxMock(true); + } + mVibrator.reset(); + } + + private: + void relaxMock(bool relax) { + auto times = relax ? AnyNumber() : Exactly(0); + + Mock::VerifyAndClearExpectations(mMockApi); + Mock::VerifyAndClearExpectations(mMockCal); + + EXPECT_CALL(*mMockApi, destructor()).Times(times); + EXPECT_CALL(*mMockApi, setF0(_)).Times(times); + EXPECT_CALL(*mMockApi, setF0Offset(_)).Times(times); + EXPECT_CALL(*mMockApi, setRedc(_)).Times(times); + EXPECT_CALL(*mMockApi, setQ(_)).Times(times); + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).Times(times); + EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).Times(times); + EXPECT_CALL(*mMockApi, setF0CompEnable(_)).Times(times); + EXPECT_CALL(*mMockApi, setRedcCompEnable(_)).Times(times); + EXPECT_CALL(*mMockApi, pollVibeState(_, _)).Times(times); + EXPECT_CALL(*mMockApi, setFFGain(_, _)).Times(times); + EXPECT_CALL(*mMockApi, setFFEffect(_, _, _)).Times(times); + EXPECT_CALL(*mMockApi, setFFPlay(_, _, _)).Times(times); + EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times); + + EXPECT_CALL(*mMockApi, debug(_)).Times(times); + + EXPECT_CALL(*mMockCal, destructor()).Times(times); + EXPECT_CALL(*mMockCal, getF0(_)).Times(times); + EXPECT_CALL(*mMockCal, getRedc(_)).Times(times); + EXPECT_CALL(*mMockCal, getQ(_)).Times(times); + EXPECT_CALL(*mMockCal, getTickVolLevels(_)).Times(times); + EXPECT_CALL(*mMockCal, getClickVolLevels(_)).Times(times); + EXPECT_CALL(*mMockCal, getLongVolLevels(_)).Times(times); + EXPECT_CALL(*mMockCal, isChirpEnabled()).Times(times); + EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).Times(times); + EXPECT_CALL(*mMockCal, isF0CompEnabled()).Times(times); + EXPECT_CALL(*mMockCal, isRedcCompEnabled()).Times(times); + EXPECT_CALL(*mMockCal, debug(_)).Times(times); + } + + protected: + MockApi *mMockApi; + MockCal *mMockCal; + std::shared_ptr mVibrator; + uint32_t mEffectIndex; +}; + +TEST_F(VibratorTest, Constructor) { + std::unique_ptr mockapi; + std::unique_ptr mockcal; + std::string f0Val = std::to_string(std::rand()); + std::string redcVal = std::to_string(std::rand()); + std::string qVal = std::to_string(std::rand()); + uint32_t calVer; + uint32_t supportedPrimitivesBits = 0x0; + Expectation volGet; + Sequence f0Seq, redcSeq, qSeq, supportedPrimitivesSeq; + + EXPECT_CALL(*mMockApi, destructor()).WillOnce(DoDefault()); + EXPECT_CALL(*mMockCal, destructor()).WillOnce(DoDefault()); + + deleteVibrator(false); + + createMock(&mockapi, &mockcal); + + EXPECT_CALL(*mMockCal, getF0(_)) + .InSequence(f0Seq) + .WillOnce(DoAll(SetArgReferee<0>(f0Val), Return(true))); + EXPECT_CALL(*mMockApi, setF0(f0Val)).InSequence(f0Seq).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, getRedc(_)) + .InSequence(redcSeq) + .WillOnce(DoAll(SetArgReferee<0>(redcVal), Return(true))); + EXPECT_CALL(*mMockApi, setRedc(redcVal)).InSequence(redcSeq).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, getQ(_)) + .InSequence(qSeq) + .WillOnce(DoAll(SetArgReferee<0>(qVal), Return(true))); + EXPECT_CALL(*mMockApi, setQ(qVal)).InSequence(qSeq).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).WillOnce(Return(true)); + + mMockCal->getVersion(&calVer); + if (calVer == 2) { + volGet = EXPECT_CALL(*mMockCal, getTickVolLevels(_)).WillOnce(DoDefault()); + volGet = EXPECT_CALL(*mMockCal, getClickVolLevels(_)).WillOnce(DoDefault()); + volGet = EXPECT_CALL(*mMockCal, getLongVolLevels(_)).WillOnce(DoDefault()); + } + + EXPECT_CALL(*mMockCal, isF0CompEnabled()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setF0CompEnable(true)).WillOnce(Return(true)); + EXPECT_CALL(*mMockCal, isRedcCompEnabled()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setRedcCompEnable(true)).WillOnce(Return(true)); + + EXPECT_CALL(*mMockCal, isChirpEnabled()).WillOnce(Return(true)); + EXPECT_CALL(*mMockCal, getSupportedPrimitives(_)) + .InSequence(supportedPrimitivesSeq) + .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitivesBits), Return(true))); + + EXPECT_CALL(*mMockApi, setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US)).WillOnce(Return(true)); + createVibrator(std::move(mockapi), std::move(mockcal), false); +} + +TEST_F(VibratorTest, on) { + Sequence s1, s2; + uint16_t duration = std::rand() + 1; + + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, setFFEffect(_, _, duration + MAX_COLD_START_LATENCY_MS)) + .InSequence(s2) + .WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, setFFPlay(_, ON_EFFECT_INDEX, true)) + .InSequence(s1, s2) + .WillOnce(DoDefault()); + EXPECT_TRUE(mVibrator->on(duration, nullptr).isOk()); +} + +TEST_F(VibratorTest, off) { + Sequence s1; + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault()); + EXPECT_TRUE(mVibrator->off().isOk()); +} + +TEST_F(VibratorTest, supportsAmplitudeControl_supported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_GT(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0); +} + +TEST_F(VibratorTest, supportsExternalAmplitudeControl_unsupported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL, 0); +} + +TEST_F(VibratorTest, setAmplitude_supported) { + EffectAmplitude amplitude = static_cast(std::rand()) / RAND_MAX ?: 1.0f; + + EXPECT_CALL(*mMockApi, setFFGain(_, amplitudeToScale(amplitude))).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setAmplitude(amplitude).isOk()); +} + +TEST_F(VibratorTest, supportsExternalControl_supported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_GT(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0); +} + +TEST_F(VibratorTest, supportsExternalControl_unsupported) { + int32_t capabilities; + EXPECT_CALL(*mMockApi, hasOwtFreeSpace()).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).WillOnce(Return(false)); + + EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk()); + EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0); +} + +TEST_F(VibratorTest, setExternalControl_enable) { + Sequence s1, s2; + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _)) + .InSequence(s1, s2) + .WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setExternalControl(true).isOk()); +} + +TEST_F(VibratorTest, setExternalControl_disable) { + Sequence s1, s2, s3, s4; + + // The default mIsUnderExternalControl is false, so it needs to turn on the External Control + // to make mIsUnderExternalControl become true. + EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)) + .InSequence(s1) + .InSequence(s1) + .WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).InSequence(s2).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, true, _, _)).InSequence(s3).WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setExternalControl(true).isOk()); + + EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(VOLTAGE_SCALE_MAX))) + .InSequence(s4) + .WillOnce(DoDefault()); + EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, false, _, _)) + .InSequence(s1, s2, s3, s4) + .WillOnce(Return(true)); + + EXPECT_TRUE(mVibrator->setExternalControl(false).isOk()); +} + +class EffectsTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &info) { + auto param = info.param; + auto effect = std::get<0>(param); + auto strength = std::get<1>(param); + return toString(effect) + "_" + toString(strength); + } +}; + +TEST_P(EffectsTest, perform) { + auto param = GetParam(); + auto effect = std::get<0>(param); + auto strength = std::get<1>(param); + auto scale = EFFECT_SCALE.find(param); + auto queue = EFFECT_QUEUE.find(param); + EffectDuration duration; + auto callback = ndk::SharedRefBase::make(); + std::promise promise; + std::future future{promise.get_future()}; + auto complete = [&promise] { + promise.set_value(); + return ndk::ScopedAStatus::ok(); + }; + bool composeEffect; + + ExpectationSet eSetup; + Expectation eActivate, ePollHaptics, ePollStop, eEraseDone; + + if (scale != EFFECT_SCALE.end()) { + EffectIndex index = EFFECT_INDEX.at(effect); + duration = EFFECT_DURATIONS[index]; + + eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, levelToScale(scale->second))) + .WillOnce(DoDefault()); + eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, index, true)) + .After(eSetup) + .WillOnce(DoDefault()); + } else if (queue != EFFECT_QUEUE.end()) { + duration = std::get<1>(queue->second); + eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)) + .After(eSetup) + .WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)) + .After(eSetup) + .WillOnce(DoDefault()); + eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true)) + .After(eSetup) + .WillOnce(DoDefault()); + composeEffect = true; + } else { + duration = 0; + } + + if (duration) { + ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT)) + .After(eActivate) + .WillOnce(DoDefault()); + ePollStop = EXPECT_CALL(*mMockApi, pollVibeState(0, -1)) + .After(ePollHaptics) + .WillOnce(DoDefault()); + if (composeEffect) { + eEraseDone = EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _)) + .After(ePollStop) + .WillOnce(DoDefault()); + EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete); + } else { + EXPECT_CALL(*callback, onComplete()).After(ePollStop).WillOnce(complete); + } + } + + int32_t lengthMs; + ndk::ScopedAStatus status = mVibrator->perform(effect, strength, callback, &lengthMs); + if (status.isOk()) { + EXPECT_LE(duration, lengthMs); + } else { + EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode()); + EXPECT_EQ(0, lengthMs); + } + + if (duration) { + EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready); + } +} + +const std::vector kEffects{ndk::enum_range().begin(), + ndk::enum_range().end()}; +const std::vector kEffectStrengths{ndk::enum_range().begin(), + ndk::enum_range().end()}; + +INSTANTIATE_TEST_CASE_P(VibratorTests, EffectsTest, + Combine(ValuesIn(kEffects.begin(), kEffects.end()), + ValuesIn(kEffectStrengths.begin(), kEffectStrengths.end())), + EffectsTest::PrintParam); + +struct PrimitiveParam { + CompositePrimitive primitive; + EffectIndex index; +}; + +class PrimitiveTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &info) { + return toString(info.param.primitive); + } +}; + +const std::vector kPrimitiveParams = { + {CompositePrimitive::CLICK, 2}, {CompositePrimitive::THUD, 4}, + {CompositePrimitive::SPIN, 5}, {CompositePrimitive::QUICK_RISE, 6}, + {CompositePrimitive::SLOW_RISE, 7}, {CompositePrimitive::QUICK_FALL, 8}, + {CompositePrimitive::LIGHT_TICK, 9}, {CompositePrimitive::LOW_TICK, 10}, +}; + +TEST_P(PrimitiveTest, getPrimitiveDuration) { + auto param = GetParam(); + auto primitive = param.primitive; + auto index = param.index; + int32_t duration; + + EXPECT_EQ(EX_NONE, mVibrator->getPrimitiveDuration(primitive, &duration).getExceptionCode()); + EXPECT_EQ(EFFECT_DURATIONS[index], duration); +} + +INSTANTIATE_TEST_CASE_P(VibratorTests, PrimitiveTest, + ValuesIn(kPrimitiveParams.begin(), kPrimitiveParams.end()), + PrimitiveTest::PrintParam); + +struct ComposeParam { + std::string name; + std::vector composite; + EffectQueue queue; +}; + +class ComposeTest : public VibratorTest, public WithParamInterface { + public: + static auto PrintParam(const TestParamInfo &info) { return info.param.name; } +}; + +TEST_P(ComposeTest, compose) { + auto param = GetParam(); + auto composite = param.composite; + auto queue = std::get<0>(param.queue); + ExpectationSet eSetup; + Expectation eActivate, ePollHaptics, ePollStop, eEraseDone; + auto callback = ndk::SharedRefBase::make(); + std::promise promise; + std::future future{promise.get_future()}; + auto complete = [&promise] { + promise.set_value(); + return ndk::ScopedAStatus::ok(); + }; + + eSetup += EXPECT_CALL(*mMockApi, setFFGain(_, ON_GLOBAL_SCALE)) + .After(eSetup) + .WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, getOwtFreeSpace(_)).WillOnce(DoDefault()); + eSetup += EXPECT_CALL(*mMockApi, uploadOwtEffect(_, _, _, _, _, _)) + .After(eSetup) + .WillOnce(DoDefault()); + eActivate = EXPECT_CALL(*mMockApi, setFFPlay(_, WAVEFORM_COMPOSE, true)) + .After(eSetup) + .WillOnce(DoDefault()); + + ePollHaptics = EXPECT_CALL(*mMockApi, pollVibeState(1, POLLING_TIMEOUT)) + .After(eActivate) + .WillOnce(DoDefault()); + ePollStop = + EXPECT_CALL(*mMockApi, pollVibeState(0, -1)).After(ePollHaptics).WillOnce(DoDefault()); + eEraseDone = + EXPECT_CALL(*mMockApi, eraseOwtEffect(_, _, _)).After(ePollStop).WillOnce(DoDefault()); + EXPECT_CALL(*callback, onComplete()).After(eEraseDone).WillOnce(complete); + + EXPECT_EQ(EX_NONE, mVibrator->compose(composite, callback).getExceptionCode()); + + EXPECT_EQ(future.wait_for(std::chrono::milliseconds(100)), std::future_status::ready); +} + +const std::vector kComposeParams = { + {"click", + {{0, CompositePrimitive::CLICK, 1.0f}}, + Queue(QueueEffect(2, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"thud", + {{1, CompositePrimitive::THUD, 0.8f}}, + Queue(1, QueueEffect(4, Level(0.8f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"spin", + {{2, CompositePrimitive::SPIN, 0.6f}}, + Queue(2, QueueEffect(5, Level(0.6f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"quick_rise", + {{3, CompositePrimitive::QUICK_RISE, 0.4f}}, + Queue(3, QueueEffect(6, Level(0.4f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)}, + {"slow_rise", + {{4, CompositePrimitive::SLOW_RISE, 0.0f}}, + Queue(4, QueueEffect(7, Level(0.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"quick_fall", + {{5, CompositePrimitive::QUICK_FALL, 1.0f}}, + Queue(5, QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)}, + {"pop", + {{6, CompositePrimitive::SLOW_RISE, 1.0f}, {50, CompositePrimitive::THUD, 1.0f}}, + Queue(6, QueueEffect(7, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 50, + QueueEffect(4, Level(1.0f, V_CLICK_DEFAULT[0], V_CLICK_DEFAULT[1])), 0)}, + {"snap", + {{7, CompositePrimitive::QUICK_RISE, 1.0f}, {0, CompositePrimitive::QUICK_FALL, 1.0f}}, + Queue(7, QueueEffect(6, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), + QueueEffect(8, Level(1.0f, V_LONG_DEFAULT[0], V_LONG_DEFAULT[1])), 0)}, +}; + +INSTANTIATE_TEST_CASE_P(VibratorTests, ComposeTest, + ValuesIn(kComposeParams.begin(), kComposeParams.end()), + ComposeTest::PrintParam); +} // namespace vibrator +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/vibrator/cs40l26/tests/types.h b/vibrator/cs40l26/tests/types.h new file mode 100644 index 0000000..e05c648 --- /dev/null +++ b/vibrator/cs40l26/tests/types.h @@ -0,0 +1,33 @@ +/* + * 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_HARDWARE_VIBRATOR_TEST_TYPES_H +#define ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H + +#include + +using EffectIndex = uint16_t; +using EffectLevel = uint32_t; +using EffectAmplitude = float; +using EffectScale = uint16_t; +using EffectDuration = uint32_t; +using EffectQueue = std::tuple; +using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect, + ::aidl::android::hardware::vibrator::EffectStrength>; + +using QueueEffect = std::tuple; +using QueueDelay = uint32_t; + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H diff --git a/vibrator/cs40l26/tests/utils.h b/vibrator/cs40l26/tests/utils.h new file mode 100644 index 0000000..e7f6055 --- /dev/null +++ b/vibrator/cs40l26/tests/utils.h @@ -0,0 +1,46 @@ +/* + * 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_HARDWARE_VIBRATOR_TEST_UTILS_H +#define ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H + +#include + +#include "types.h" + +static inline EffectScale toScale(float amplitude, float maximum) { + float ratio = 100; /* Unit: % */ + if (maximum != 0) + ratio = amplitude / maximum * 100; + + if (maximum == 0 || ratio > 100) + ratio = 100; + + return std::round(ratio); +} + +static inline EffectScale levelToScale(EffectLevel level) { + return toScale(level, 100); +} + +static inline EffectScale amplitudeToScale(EffectAmplitude amplitude) { + return toScale(amplitude, 1.0f); +} + +static inline uint32_t msToCycles(EffectDuration ms) { + return ms * 48; +} + +#endif // ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H From 59662424ecb5fde98cd0c450382e379bf176015e Mon Sep 17 00:00:00 2001 From: Chris Paulo Date: Thu, 3 Nov 2022 20:58:39 +0000 Subject: [PATCH 10/18] [DO NOT MERGE] device/vibrator: Add contextual haptics feature Add capability for vibrator HAL to detect whether the device is face-up and adjust/scale haptic alerts to avoid loud and startling buzzing when there is no case on the device Bug: 198239103 Test: Verified tests and functionality Change-Id: I93875c3c1e6128d275cb8007f8e8870a7842938a Signed-off-by: Chris Paulo --- conf/init.bluejay.rc | 7 + device-bluejay.mk | 9 +- vibrator/Android.bp | 12 +- vibrator/OWNERS | 1 + vibrator/common/Android.bp | 2 +- vibrator/cs40l26/Android.bp | 81 ++++--- vibrator/cs40l26/CapoDetector.cpp | 216 ++++++++++++++++++ vibrator/cs40l26/Hardware.h | 16 ++ vibrator/cs40l26/Vibrator.cpp | 136 ++++++++++- vibrator/cs40l26/Vibrator.h | 26 ++- ...brator-service.cs40l26-private-bluejay.rc} | 4 +- ...rator-service.cs40l26-private-bluejay.xml} | 0 vibrator/cs40l26/device.mk | 2 +- vibrator/cs40l26/inc/CapoDetector.h | 107 +++++++++ vibrator/cs40l26/proto/capo.proto | 148 ++++++++++++ vibrator/cs40l26/tests/Android.bp | 4 +- vibrator/cs40l26/tests/mocks.h | 5 + vibrator/cs40l26/tests/test-vibrator.cpp | 10 + 18 files changed, 730 insertions(+), 56 deletions(-) create mode 100644 vibrator/cs40l26/CapoDetector.cpp rename vibrator/cs40l26/{android.hardware.vibrator-service.cs40l26.rc => android.hardware.vibrator-service.cs40l26-private-bluejay.rc} (96%) rename vibrator/cs40l26/{android.hardware.vibrator-service.cs40l26.xml => android.hardware.vibrator-service.cs40l26-private-bluejay.xml} (100%) create mode 100644 vibrator/cs40l26/inc/CapoDetector.h create mode 100644 vibrator/cs40l26/proto/capo.proto diff --git a/conf/init.bluejay.rc b/conf/init.bluejay.rc index 1ee067c..d45d696 100644 --- a/conf/init.bluejay.rc +++ b/conf/init.bluejay.rc @@ -38,3 +38,10 @@ on property:sys.boot_completed=1 on override-sf-uclamp write /proc/vendor_sched/rt_uclamp_min 0 write /proc/vendor_sched/sf_uclamp_min 0 + +# Route vibrator.adaptive_haptics.enabled to persist +on property:vibrator.adaptive_haptics.enabled=0 + setprop persist.vendor.vibrator.hal.context.enable false + +on property:vibrator.adaptive_haptics.enabled=1 + setprop persist.vendor.vibrator.hal.context.enable true diff --git a/device-bluejay.mk b/device-bluejay.mk index 6e3a676..1345940 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -33,7 +33,7 @@ include device/google/bluejay/audio/bluejay/audio-tables.mk include device/google/gs101/device-shipping-common.mk include device/google/gs101/fingerprint/udfps_common.mk include device/google/gs101/telephony/pktrouter.mk -include hardware/google/pixel/vibrator/cs40l26/device.mk +include device/google/bluejay/vibrator/cs40l26/device.mk include device/google/gs101/bluetooth/bluetooth.mk ifeq ($(filter factory_bluejay, $(TARGET_PRODUCT)),) @@ -226,7 +226,12 @@ PRODUCT_SHIPPING_API_LEVEL := 32 PRODUCT_VENDOR_PROPERTIES += \ ro.vendor.vibrator.hal.supported_primitives=243 \ ro.vendor.vibrator.hal.f0.comp.enabled=0 \ - ro.vendor.vibrator.hal.redc.comp.enabled=0 + ro.vendor.vibrator.hal.redc.comp.enabled=0 \ + persist.vendor.vibrator.hal.context.enable=false \ + persist.vendor.vibrator.hal.context.scale=40 \ + persist.vendor.vibrator.hal.context.fade=true \ + persist.vendor.vibrator.hal.context.cooldowntime=1600 \ + persist.vendor.vibrator.hal.context.settlingtime=5000 # Device features PRODUCT_COPY_FILES += \ diff --git a/vibrator/Android.bp b/vibrator/Android.bp index dff6816..6ba5042 100644 --- a/vibrator/Android.bp +++ b/vibrator/Android.bp @@ -18,10 +18,10 @@ package { } cc_defaults { - name: "PixelVibratorDefaults", + name: "PixelVibratorDefaultsPrivateBluejay", relative_install_path: "hw", static_libs: [ - "PixelVibratorCommon", + "PixelVibratorCommonPrivateBluejay", ], shared_libs: [ "libbase", @@ -34,16 +34,16 @@ cc_defaults { } cc_defaults { - name: "PixelVibratorBinaryDefaults", - defaults: ["PixelVibratorDefaults"], + name: "PixelVibratorBinaryDefaultsPrivateBluejay", + defaults: ["PixelVibratorDefaultsPrivateBluejay"], shared_libs: [ "android.hardware.vibrator-V2-ndk", ], } cc_defaults { - name: "PixelVibratorTestDefaults", - defaults: ["PixelVibratorDefaults"], + name: "PixelVibratorTestDefaultsPrivateBluejay", + defaults: ["PixelVibratorDefaultsPrivateBluejay"], static_libs: [ "android.hardware.vibrator-V2-ndk", ], diff --git a/vibrator/OWNERS b/vibrator/OWNERS index 5d4a9c3..899224c 100644 --- a/vibrator/OWNERS +++ b/vibrator/OWNERS @@ -1,3 +1,4 @@ chasewu@google.com michaelwr@google.com taikuo@google.com +chrispaulo@google.com diff --git a/vibrator/common/Android.bp b/vibrator/common/Android.bp index 04fbc4d..6eb2456 100644 --- a/vibrator/common/Android.bp +++ b/vibrator/common/Android.bp @@ -18,7 +18,7 @@ package { } cc_library { - name: "PixelVibratorCommon", + name: "PixelVibratorCommonPrivateBluejay", srcs: [ "HardwareBase.cpp", ], diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp index 53764de..722f0e5 100644 --- a/vibrator/cs40l26/Android.bp +++ b/vibrator/cs40l26/Android.bp @@ -1,5 +1,5 @@ // -// Copyright (C) 2021 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. @@ -18,7 +18,7 @@ package { } cc_defaults { - name: "android.hardware.vibrator-defaults.cs40l26", + name: "android.hardware.vibrator-defaults.cs40l26-private-bluejay", cflags: [ "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)", "-DLOG_TAG=\"android.hardware.vibrator-cs40l26\"", @@ -29,10 +29,10 @@ cc_defaults { } cc_defaults { - name: "VibratorHalCs40l26BinaryDefaults", + name: "VibratorHalCs40l26BinaryDefaultsPrivateBluejay", defaults: [ - "PixelVibratorBinaryDefaults", - "android.hardware.vibrator-defaults.cs40l26", + "PixelVibratorBinaryDefaultsPrivateBluejay", + "android.hardware.vibrator-defaults.cs40l26-private-bluejay", ], include_dirs: [ "external/tinyalsa/include", @@ -44,43 +44,64 @@ cc_defaults { } cc_defaults { - name: "VibratorHalCs40l26TestDefaults", + name: "VibratorHalCs40l26TestDefaultsPrivateBluejay", defaults: [ - "PixelVibratorTestDefaults", - "android.hardware.vibrator-defaults.cs40l26", + "PixelVibratorTestDefaultsPrivateBluejay", + "android.hardware.vibrator-defaults.cs40l26-private-bluejay", ], static_libs: [ - "android.hardware.vibrator-impl.cs40l26", "libtinyalsa", ], + shared_libs: ["android.hardware.vibrator-impl.cs40l26-private-bluejay"], } -cc_library { - name: "android.hardware.vibrator-impl.cs40l26", - defaults: ["VibratorHalCs40l26BinaryDefaults"], - srcs: ["Vibrator.cpp"], - export_include_dirs: ["."], +cc_library_shared { + name: "libvibecapo_proto_bluejay", + vendor_available: true, + owner: "google", + srcs: [ + "proto/capo.proto", + ], + shared_libs: [ + "libprotobuf-cpp-full", + ], + export_include_dirs: [ + "inc", + ], + proto: { + type: "lite", + export_proto_headers: true, + }, +} + +cc_library_shared { + name: "android.hardware.vibrator-impl.cs40l26-private-bluejay", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivateBluejay"], + srcs: [ + "Vibrator.cpp", + "CapoDetector.cpp", + ], + static_libs: [ + "chre_client", + ], + shared_libs: [ + "libvibecapo_proto_bluejay", + "libprotobuf-cpp-full", + ], + export_include_dirs: [ + ".", + "inc", + ], vendor_available: true, visibility: [":__subpackages__"], } cc_binary { - name: "android.hardware.vibrator-service.cs40l26", - defaults: ["VibratorHalCs40l26BinaryDefaults"], - init_rc: ["android.hardware.vibrator-service.cs40l26.rc"], - vintf_fragments: ["android.hardware.vibrator-service.cs40l26.xml"], + name: "android.hardware.vibrator-service.cs40l26-private-bluejay", + defaults: ["VibratorHalCs40l26BinaryDefaultsPrivateBluejay"], + init_rc: ["android.hardware.vibrator-service.cs40l26-private-bluejay.rc"], + vintf_fragments: ["android.hardware.vibrator-service.cs40l26-private-bluejay.xml"], srcs: ["service.cpp"], - shared_libs: ["android.hardware.vibrator-impl.cs40l26"], - proprietary: true, -} - -cc_binary { - name: "android.hardware.vibrator-service.cs40l26-dual", - defaults: ["VibratorHalCs40l26BinaryDefaults"], - init_rc: ["android.hardware.vibrator-service.cs40l26-dual.rc"], - vintf_fragments: ["android.hardware.vibrator-service.cs40l26-dual.xml"], - srcs: ["service.cpp"], - shared_libs: ["android.hardware.vibrator-impl.cs40l26"], - cflags: ["-DVIBRATOR_NAME=\"dual\""], + shared_libs: ["android.hardware.vibrator-impl.cs40l26-private-bluejay"], proprietary: true, } diff --git a/vibrator/cs40l26/CapoDetector.cpp b/vibrator/cs40l26/CapoDetector.cpp new file mode 100644 index 0000000..1b8ba89 --- /dev/null +++ b/vibrator/cs40l26/CapoDetector.cpp @@ -0,0 +1,216 @@ +/* + * Copyright 2022 Google LLC. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT 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 "CapoDetector.h" +#include +#include +#include + +#include + +#ifdef LOG_TAG +#undef LOG_TAG +#define LOG_TAG "CapoDetector" +#endif + +namespace android { +namespace chre { + +namespace { // anonymous namespace for file-local definitions + +static capo::ConfigureDetector_ConfigData config_data = capo::ConfigureDetector_ConfigData(); +static capo::ConfigureDetector msg = capo::ConfigureDetector(); + + +/** + * Called when onConnected() to send NanoappList request. + */ +void requestNanoappList(SocketClient &client) { + flatbuffers::FlatBufferBuilder builder; + HostProtocolHost::encodeNanoappListRequest(builder); + if (!client.sendMessage(builder.GetBufferPointer(), builder.GetSize())) { + ALOGE("Failed to send NanoappList request"); + } +} + +} // namespace + +/** + * Called when initializing connection with CHRE socket. + */ +sp CapoDetector::start() { + sp listener = new CapoDetector(); + if (!listener->connectInBackground(kChreSocketName, listener)) { + ALOGE("Couldn't connect to CHRE socket"); + return nullptr; + } + ALOGI("%s connect to CHRE socket.", __func__); + + return listener; +} + +/** + * Called when the socket is successfully (re-)connected. + * Reset the position and try to send NanoappList request. + */ +void CapoDetector::onConnected() { + flatbuffers::FlatBufferBuilder builder; + + // Reset the last position type. + last_position_type_ = capo::PositionType::UNKNOWN; + requestNanoappList(*this); +} + +/** + * Called when we have failed to (re-)connect the socket after many attempts + * and are giving up. + */ +void CapoDetector::onConnectionAborted() { + ALOGE("%s, Capo Aborting Connection!", __func__); +} + +/** + * Invoked when the socket is disconnected, and this connection loss was not + * the result of an explicit call to disconnect(). + * Reset the position while disconnecting. + */ + +void CapoDetector::onDisconnected() { + last_position_type_ = capo::PositionType::UNKNOWN; +} + +/** + * Decode unix socket msgs to CHRE messages, and call the appropriate + * callback depending on the CHRE message. + */ +void CapoDetector::onMessageReceived(const void *data, size_t length) { + if (!HostProtocolHost::decodeMessageFromChre(data, length, *this)) { + ALOGE("Failed to decode message"); + } +} + +/** + * Listen for messages from capo nanoapp and handle the message. + */ +void CapoDetector::handleNanoappMessage(const fbs::NanoappMessageT &message) { + ALOGI("%s, Id %" PRIu64 ", type %d, size %d", __func__, message.app_id, message.message_type, + static_cast(message.message.size())); + // Exclude the message with unmatched nanoapp id. + if (message.app_id != kCapoNanoappId) + return; + + // Handle the message with message_type. + switch (message.message_type) { + case capo::MessageType::ACK_NOTIFICATION: { + capo::AckNotification gd; + gd.set_notification_type(static_cast(message.message[1])); + ALOGD("%s, get notification event from capo nanoapp, type %d", __func__, + gd.notification_type()); + break; + } + case capo::MessageType::POSITION_DETECTED: { + capo::PositionDetected gd; + gd.set_position_type(static_cast(message.message[1])); + ALOGD("%s, get position event from capo nanoapp, type %d", __func__, + gd.position_type()); + + // Callback to function while getting carried position event. + if (callback_func_ != nullptr) { + last_position_type_ = gd.position_type(); + ALOGD("%s, sent position type %d to callback function", __func__, + last_position_type_); + callback_func_(last_position_type_); + } + break; + } + default: + ALOGE("%s, get invalid message, type: %" PRIu32 ", from capo nanoapp.", __func__, + message.message_type); + break; + } +} + +/** + * Handle the response of a NanoappList request. + * Ensure that capo nanoapp is running. + */ +void CapoDetector::handleNanoappListResponse(const fbs::NanoappListResponseT &response) { + for (const std::unique_ptr &nanoapp : response.nanoapps) { + if (nanoapp->app_id == kCapoNanoappId) { + if (nanoapp->enabled) + enable(); + else + ALOGE("Capo nanoapp not enabled"); + return; + } + } + ALOGE("Capo nanoapp not found"); +} + +/** + * Send enabling message to the nanoapp. + */ +void CapoDetector::enable() { + // Create CHRE message with serialized message + flatbuffers::FlatBufferBuilder builder, config_builder, force_builder; + + config_data.set_still_time_threshold_nanosecond(mCapoDetectorMDParameters.still_time_threshold_ns); + config_data.set_window_width_nanosecond(mCapoDetectorMDParameters.window_width_ns); + config_data.set_motion_confidence_threshold(mCapoDetectorMDParameters.motion_confidence_threshold); + config_data.set_still_confidence_threshold(mCapoDetectorMDParameters.still_confidence_threshold); + config_data.set_var_threshold(mCapoDetectorMDParameters.var_threshold); + config_data.set_var_threshold_delta(mCapoDetectorMDParameters.var_threshold_delta); + + msg.set_allocated_config_data(&config_data); + + auto pb_size = msg.ByteSizeLong(); + auto pb_data = std::make_unique(pb_size); + + if (!msg.SerializeToArray(pb_data.get(), pb_size)) { + ALOGE("Failed to serialize message."); + } + + ALOGI("Configuring CapoDetector"); + // Configure the detector from host-side + android::chre::HostProtocolHost::encodeNanoappMessage( + config_builder, getNanoppAppId(), capo::MessageType::CONFIGURE_DETECTOR, getHostEndPoint(), + pb_data.get(), pb_size); + ALOGI("Sending capo config message to Nanoapp, %" PRIu32 " bytes", config_builder.GetSize()); + if (!sendMessage(config_builder.GetBufferPointer(), config_builder.GetSize())) { + ALOGE("Failed to send config event for capo nanoapp"); + } + + ALOGI("Enabling CapoDetector"); + android::chre::HostProtocolHost::encodeNanoappMessage( + builder, getNanoppAppId(), capo::MessageType::ENABLE_DETECTOR, getHostEndPoint(), + /*messageData*/ nullptr, /*messageDataLenbuffer*/ 0); + ALOGI("Sending enable message to Nanoapp, %" PRIu32 " bytes", builder.GetSize()); + if (!sendMessage(builder.GetBufferPointer(), builder.GetSize())) { + ALOGE("Failed to send enable event for capo nanoapp"); + } + + ALOGI("Forcing CapoDetector to update state"); + // Force an updated state upon connection + android::chre::HostProtocolHost::encodeNanoappMessage( + force_builder, getNanoppAppId(), capo::MessageType::FORCE_UPDATE, getHostEndPoint(), + /*messageData*/ nullptr, /*messageDataLenbuffer*/ 0); + ALOGI("Sending force-update message to Nanoapp, %" PRIu32 " bytes", force_builder.GetSize()); + if (!sendMessage(force_builder.GetBufferPointer(), force_builder.GetSize())) { + ALOGE("Failed to send force-update event for capo nanoapp"); + } +} + +} // namespace chre +} // namespace android diff --git a/vibrator/cs40l26/Hardware.h b/vibrator/cs40l26/Hardware.h index ae052ba..be97594 100644 --- a/vibrator/cs40l26/Hardware.h +++ b/vibrator/cs40l26/Hardware.h @@ -92,6 +92,22 @@ class HwApi : public Vibrator::HwApi, private HwApiBase { bool setF0CompEnable(bool value) override { return set(value, &mF0CompEnable); } bool setRedcCompEnable(bool value) override { return set(value, &mRedcCompEnable); } bool setMinOnOffInterval(uint32_t value) override { return set(value, &mMinOnOffInterval); } + uint32_t getContextScale() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.scale", 100); + } + bool getContextEnable() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.enable", false); + } + uint32_t getContextSettlingTime() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.settlingtime", 3000); + } + uint32_t getContextCooldownTime() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.cooldowntime", 1000); + } + bool getContextFadeEnable() override { + return utils::getProperty("persist.vendor.vibrator.hal.context.fade", false); + } + // TODO(b/234338136): Need to add the force feedback HW API test cases bool setFFGain(int fd, uint16_t value) override { struct input_event gain = { diff --git a/vibrator/cs40l26/Vibrator.cpp b/vibrator/cs40l26/Vibrator.cpp index 3af057c..21a1682 100644 --- a/vibrator/cs40l26/Vibrator.cpp +++ b/vibrator/cs40l26/Vibrator.cpp @@ -28,11 +28,22 @@ #include #include #include +#include +#include + +#include "CapoDetector.h" #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0])) #endif +#ifdef LOG_TAG +#undef LOG_TAG +#define LOG_TAG "Vibrator" +#endif + +using CapoDetector = android::chre::CapoDetector; + namespace aidl { namespace android { namespace hardware { @@ -89,15 +100,34 @@ static constexpr float PWLE_FREQUENCY_MAX_HZ = 1000.00; static constexpr float PWLE_BW_MAP_SIZE = 1 + ((PWLE_FREQUENCY_MAX_HZ - PWLE_FREQUENCY_MIN_HZ) / PWLE_FREQUENCY_RESOLUTION_HZ); -static uint16_t amplitudeToScale(float amplitude, float maximum) { - float ratio = 100; /* Unit: % */ - if (maximum != 0) - ratio = amplitude / maximum * 100; +#ifndef DISABLE_ADAPTIVE_HAPTICS_FEATURE +static constexpr bool mAdaptiveHapticsEnable = true; +#else +static constexpr bool mAdaptiveHapticsEnable = false; +#endif /* DISABLE_ADAPTIVE_HAPTICS_FEATURE */ - if (maximum == 0 || ratio > 100) - ratio = 100; +static sp vibeContextListener; +uint8_t mCapoDeviceState = 0; +uint32_t mLastFaceUpEvent = 0; +uint32_t mLastEffectPlayedTime = 0; +float mLastPlayedScale = 0; - return std::round(ratio); +static uint32_t getCurrentTimeInMs(void) { + return std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); +} + +static void capoEventCallback(uint8_t eventId) { + ALOGD("Vibrator %s, From: 0x%x To: 0x%x", __func__, mCapoDeviceState, (uint32_t)eventId); + // Record the last moment we were in FACE_UP state + if (mCapoDeviceState == capo::PositionType::ON_TABLE_FACE_UP || + eventId == capo::PositionType::ON_TABLE_FACE_UP) { + mLastFaceUpEvent = getCurrentTimeInMs(); + } + mCapoDeviceState = eventId; +} + +static uint8_t getDeviceState(void) { + return mCapoDeviceState; } enum WaveformBankID : uint8_t { @@ -366,6 +396,18 @@ Vibrator::Vibrator(std::unique_ptr hwapi, std::unique_ptr hwcal) } mHwApi->setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US); + + if (mAdaptiveHapticsEnable) { + vibeContextListener = CapoDetector::start(); + if (vibeContextListener == nullptr) { + ALOGE("%s, CapoDetector failed to start", __func__); + } else { + ALOGD("%s, CapoDetector started successfully! NanoAppID: 0x%x", __func__, + (uint32_t)vibeContextListener->getNanoppAppId()); + vibeContextListener->setCallback(capoEventCallback); + ALOGD("%s, CapoDetector Set Callback function from vibe", __func__); + } + } } ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) { @@ -666,8 +708,70 @@ ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex, dspmem return ndk::ScopedAStatus::ok(); } -ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) { - uint16_t scale = amplitudeToScale(amplitude, maximum); +uint16_t Vibrator::amplitudeToScale(float amplitude, float maximum, bool scalable) { + float ratio = 100; /* Unit: % */ + + if (maximum != 0) + ratio = amplitude / maximum * 100; + + if (maximum == 0 || ratio > 100) + ratio = 100; + + if (scalable && mContextEnable & mAdaptiveHapticsEnable) { + uint32_t now = getCurrentTimeInMs(); + uint32_t last_played = mLastEffectPlayedTime; + float context_scale = 1.0; + bool device_face_up = getDeviceState() == capo::PositionType::ON_TABLE_FACE_UP; + float pre_scaled_ratio = ratio; + mLastEffectPlayedTime = now; + + ALOGD("Vibrator Now: %u, Last: %u, ScaleTime: %u, Since? %d", now, mLastFaceUpEvent, mScaleTime, (now < mLastFaceUpEvent + mScaleTime)); + /* If the device is face-up or within the fade scaling range, find new scaling factor */ + if (device_face_up || now < mLastFaceUpEvent + mScaleTime) { + /* Device is face-up, so we will scale it down. Start with highest scaling factor */ + context_scale = mScalingFactor <= 100 ? static_cast(mScalingFactor)/100 : 1.0; + if (mFadeEnable && mScaleTime > 0 && (context_scale < 1.0) && (now < mLastFaceUpEvent + mScaleTime) && !device_face_up) { + float fade_scale = static_cast(now - mLastFaceUpEvent)/static_cast(mScaleTime); + context_scale += ((1.0 - context_scale)*fade_scale); + ALOGD("Vibrator fade scale applied: %f", fade_scale); + } + ratio *= context_scale; + ALOGD("Vibrator adjusting for face-up: pre: %f, post: %f", + std::round(pre_scaled_ratio), std::round(ratio)); + } + + /* If we haven't played an effect within the cooldown time, save the scaling factor */ + if ((now - last_played) > mScaleCooldown) { + ALOGD("Vibrator updating lastplayed scale, old: %f, new: %f", mLastPlayedScale, context_scale); + mLastPlayedScale = context_scale; + } + else { + /* Override the scale to match previously played scale */ + ratio = mLastPlayedScale * pre_scaled_ratio; + ALOGD("Vibrator repeating last scale: %f, new ratio: %f, duration since last: %u", mLastPlayedScale, ratio, (now - last_played)); + } + } + + return std::round(ratio); +} + +void Vibrator::updateContext() { + mContextEnable = mHwApi->getContextEnable(); + mFadeEnable = mHwApi->getContextFadeEnable(); + mScalingFactor = mHwApi->getContextScale(); + mScaleTime = mHwApi->getContextSettlingTime(); + mScaleCooldown = mHwApi->getContextCooldownTime(); +} + +ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum, bool scalable) { + uint16_t scale; + + if (mAdaptiveHapticsEnable && scalable) { + updateContext(); + } + + scale = amplitudeToScale(amplitude, maximum, scalable); + if (!mHwApi->setFFGain(mInputFd, scale)) { ALOGE("Failed to set the gain to %u (%d): %s", scale, errno, strerror(errno)); return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE); @@ -680,7 +784,7 @@ ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) { if (!set) { mLongEffectScale = 1.0; // Reset the scale for the later new effect. } - return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX); + return setEffectAmplitude(amplitude, VOLTAGE_SCALE_MAX, true); } ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector * /*_aidl_return*/) { @@ -1054,6 +1158,16 @@ binder_status_t Vibrator::dump(int fd, const char **args, uint32_t numArgs) { mHwCal->debug(fd); + dprintf(fd, "Capo Info\n"); + if (vibeContextListener) { + dprintf(fd, "Capo ID: 0x%x\n", (uint32_t)(vibeContextListener->getNanoppAppId())); + dprintf(fd, "Capo State: %d DetectedState: %d\n", vibeContextListener->getCarriedPosition(), + getDeviceState()); + } else { + dprintf(fd, "Capo ID: 0x%x\n", (uint32_t)(0xdeadbeef)); + dprintf(fd, "Capo State: %d DetectedState: %d\n", (uint32_t)0x454545, getDeviceState()); + } + fsync(fd); return STATUS_OK; } @@ -1265,7 +1379,7 @@ exit: ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel, dspmem_chunk *ch, const std::shared_ptr &callback) { - setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX); + setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX, false); return on(MAX_TIME_MS, effectIndex, ch, callback); } diff --git a/vibrator/cs40l26/Vibrator.h b/vibrator/cs40l26/Vibrator.h index 220c974..7c9e99c 100644 --- a/vibrator/cs40l26/Vibrator.h +++ b/vibrator/cs40l26/Vibrator.h @@ -23,6 +23,8 @@ #include #include #include +#include +#include namespace aidl { namespace android { @@ -61,6 +63,21 @@ class Vibrator : public BnVibrator { virtual bool setRedcCompEnable(bool value) = 0; // Stores the minumun delay time between playback and stop effects. virtual bool setMinOnOffInterval(uint32_t value) = 0; + // Gets the scaling factor for contextual haptic events. + virtual uint32_t getContextScale() = 0; + // Gets the enable status for contextual haptic events. + virtual bool getContextEnable() = 0; + // Gets the settling time for contextual haptic events. + // This will allow the device to stay face up for the duration given, + // even if InMotion events were detected. + virtual uint32_t getContextSettlingTime() = 0; + // Gets the cooldown time for contextual haptic events. + // This is used to avoid changing the scale of close playback events. + virtual uint32_t getContextCooldownTime() = 0; + // Checks the enable status for contextual haptics fade feature. When enabled + // this feature will cause the scaling factor to fade back up to max over + // the setting time set, instead of instantaneously changing it back to max. + virtual bool getContextFadeEnable() = 0; // Indicates the number of 0.125-dB steps of attenuation to apply to // waveforms triggered in response to vibration calls from the // Android vibrator HAL. @@ -158,7 +175,7 @@ class Vibrator : public BnVibrator { ndk::ScopedAStatus on(uint32_t timeoutMs, uint32_t effectIndex, struct dspmem_chunk *ch, const std::shared_ptr &callback); // set 'amplitude' based on an arbitrary scale determined by 'maximum' - ndk::ScopedAStatus setEffectAmplitude(float amplitude, float maximum); + ndk::ScopedAStatus setEffectAmplitude(float amplitude, float maximum, bool scalable); ndk::ScopedAStatus setGlobalAmplitude(bool set); // 'simple' effects are those precompiled and loaded into the controller ndk::ScopedAStatus getSimpleDetails(Effect effect, EffectStrength strength, @@ -181,6 +198,8 @@ class Vibrator : public BnVibrator { bool findHapticAlsaDevice(int *card, int *device); bool hasHapticAlsaDevice(); bool enableHapticPcmAmp(struct pcm **haptic_pcm, bool enable, int card, int device); + uint16_t amplitudeToScale(float amplitude, float maximum, bool scalable); + void updateContext(); std::unique_ptr mHwApi; std::unique_ptr mHwCal; @@ -200,6 +219,11 @@ class Vibrator : public BnVibrator { bool mIsUnderExternalControl; float mLongEffectScale = 1.0; bool mIsChirpEnabled; + uint32_t mScaleTime; + bool mFadeEnable; + uint32_t mScalingFactor; + uint32_t mScaleCooldown; + bool mContextEnable; uint32_t mSupportedPrimitivesBits = 0x0; std::vector mSupportedPrimitives; bool mConfigHapticAlsaDeviceDone{false}; diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-bluejay.rc similarity index 96% rename from vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc rename to vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-bluejay.rc index 0fcca56..094ba0d 100644 --- a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.rc +++ b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-bluejay.rc @@ -20,10 +20,10 @@ on property:vendor.all.modules.ready=1 enable vendor.vibrator.cs40l26 -service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26 +service vendor.vibrator.cs40l26 /vendor/bin/hw/android.hardware.vibrator-service.cs40l26-private-bluejay class hal user system - group system input + group system input context_hub setenv INPUT_EVENT_NAME cs40l26_input setenv INPUT_EVENT_PATH /dev/input/event* diff --git a/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml b/vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-bluejay.xml similarity index 100% rename from vibrator/cs40l26/android.hardware.vibrator-service.cs40l26.xml rename to vibrator/cs40l26/android.hardware.vibrator-service.cs40l26-private-bluejay.xml diff --git a/vibrator/cs40l26/device.mk b/vibrator/cs40l26/device.mk index 5a3dd4f..6b95057 100644 --- a/vibrator/cs40l26/device.mk +++ b/vibrator/cs40l26/device.mk @@ -1,5 +1,5 @@ PRODUCT_PACKAGES += \ - android.hardware.vibrator-service.cs40l26 + android.hardware.vibrator-service.cs40l26-private-bluejay BOARD_SEPOLICY_DIRS += \ hardware/google/pixel-sepolicy/vibrator/common \ diff --git a/vibrator/cs40l26/inc/CapoDetector.h b/vibrator/cs40l26/inc/CapoDetector.h new file mode 100644 index 0000000..4b898ef --- /dev/null +++ b/vibrator/cs40l26/inc/CapoDetector.h @@ -0,0 +1,107 @@ +/* + * Copyright 2022 Google LLC. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include +#include + +#include "proto/capo.pb.h" + +using android::sp; +using android::chre::HostProtocolHost; +using android::chre::IChreMessageHandlers; +using android::chre::SocketClient; + +// following convention of CHRE code. +namespace fbs = ::chre::fbs; + +namespace android { +namespace chre { + +#define NS_FROM_MS(x) ((x)*1000000) + +struct CapoMDParams { + uint64_t still_time_threshold_ns; + uint32_t window_width_ns; + float motion_confidence_threshold; + float still_confidence_threshold; + float var_threshold; + float var_threshold_delta; +}; + +class CapoDetector : public android::chre::SocketClient::ICallbacks, + public android::chre::IChreMessageHandlers, + public android::chre::SocketClient { + public: + // Typedef declaration for callback function. + typedef std::function cb_fn_t; + + // Called when initializing connection with CHRE socket. + static android::sp start(); + // Called when the socket is successfully (re-)connected. + // Reset the position and try to send NanoappList request. + void onConnected() override; + // Called when we have failed to (re-)connect the socket after many attempts + // and are giving up. + void onConnectionAborted() override; + // Invoked when the socket is disconnected, and this connection loss + // was not the result of an explicit call to disconnect(). + // Reset the position while disconnecting. + void onDisconnected() override; + // Decode unix socket msgs to CHRE messages, and call the appropriate + // callback depending on the CHRE message. + void onMessageReceived(const void *data, size_t length) override; + // Listen for messages from capo nanoapp and handle the message. + void handleNanoappMessage(const ::chre::fbs::NanoappMessageT &message) override; + // Handle the response of a NanoappList request. + // Ensure that capo nanoapp is running. + void handleNanoappListResponse(const ::chre::fbs::NanoappListResponseT &response) override; + // Send enabling message to the nanoapp. + void enable(); + + // Get last carried position type. + uint8_t getCarriedPosition() { return last_position_type_; } + // Get the host endpoint. + uint16_t getHostEndPoint() { return kHostEndpoint; } + // Get the capo nanoapp ID. + uint64_t getNanoppAppId() { return kCapoNanoappId; } + // Set up callback_func_ if needed. + void setCallback(cb_fn_t cb) { callback_func_ = cb; } + + private: + // Nanoapp ID of capo, ref: go/nanoapp-id-tracker. + static constexpr uint64_t kCapoNanoappId = 0x476f6f676c001020ULL; + // String of socket name for connecting chre. + static constexpr char kChreSocketName[] = "chre"; + // The host endpoint we use when sending message. + // Set with 0x9020 based on 0x8000 AND capo_app_id(1020). + // Ref: go/host-endpoint-id-tracker. + static constexpr uint16_t kHostEndpoint = 0x9020; + // Using for hal layer callback function. + cb_fn_t callback_func_ = nullptr; + // Last carried position received from the nano app + capo::PositionType last_position_type_ = capo::PositionType::UNKNOWN; + // Motion detector parameters for host-driven capo config + const struct CapoMDParams mCapoDetectorMDParameters { + .still_time_threshold_ns = NS_FROM_MS(500), + .window_width_ns = NS_FROM_MS(100), + .motion_confidence_threshold = 0.98f, + .still_confidence_threshold = 0.99f, + .var_threshold = 0.0125f, + .var_threshold_delta = 0.0125f, + }; +}; + +} // namespace chre +} // namespace android diff --git a/vibrator/cs40l26/proto/capo.proto b/vibrator/cs40l26/proto/capo.proto new file mode 100644 index 0000000..00b3186 --- /dev/null +++ b/vibrator/cs40l26/proto/capo.proto @@ -0,0 +1,148 @@ +syntax = "proto3"; + +package capo; + +// The message types used in capo nanoapp. Some of them are H2C +// (Host-To-CHRE) and others are C2H (CHRE-To-Host). One message type must be +// either H2C or C2H. Each message type can choose to have payload or not. +enum MessageType { + // Explicitly prevents 0 from being used as a valid message type. + // Doing so protects from obscure bugs caused by default-initialized values. + INVALID = 0; + + // Detector configuration related message start from 100. + // Signal for host to acknowledge the notification. + // It contains AckNotification payload. + ACK_NOTIFICATION = 100; + + // Signal to enable the carried position detector for device. No payload. + ENABLE_DETECTOR = 101; + + // Signal to disable the carried position detector for device. No payload. + DISABLE_DETECTOR = 102; + + // Signal to request most recent carried position detector state. No payload. + REQUEST_UPDATE = 103; + + // Signal to force carried position detector to refresh state. No payload. + FORCE_UPDATE = 104; + + // Configure the detector with desired parameters. ConfigureDetector payload. + CONFIGURE_DETECTOR = 105; + + // Position Detection related message start from 200. + // Signal while carried position of device detected. + // It contains PositionDetected payload. + POSITION_DETECTED = 200; +} + +// Notification Type. +enum NotificationType { + // Explicitly prevents 0 from being used as a valid notification type. + // Doing so protects from obscure bugs caused by default-initialized values. + INVALID_NOTIFICATION = 0; + + // Notification of enabling the carried position detector for device. + ENABLE_NOTIFICATION = 1; + + // Notification of disabling the carried position detector for device. + DISABLE_NOTIFICATION = 2; + + // Notification of request update from the carried position detector. + REQUEST_UPDATE_NOTIFICATION = 3; + + // Notification of force update from the carried position detector. + FORCE_UPDATE_NOTIFICATION = 4; + + // Notification of configure message. + CONFIGURE_NOTIFICATION = 5; +} + +// This message type used for host to acknowledge the notification. +message AckNotification { + // Sent a notification type for host to acknowledge. + NotificationType notification_type = 1; +} + +// Position type. +enum PositionType { + // Explicitly prevents 0 from being used as a valid carried position type. + // Doing so protects from obscure bugs caused by default-initialized values. + UNKNOWN = 0; + + // Carried position while device is in motion. + IN_MOTION = 1; + + // Carried position while device is on table and faces up. + ON_TABLE_FACE_UP = 2; + + // Carried position while device is on table and faces down. + ON_TABLE_FACE_DOWN = 3; + + // Carried position while device is stationary in unknown orientation. + STATIONARY_UNKNOWN = 4; +} + +// This message type used to notify host a position was a detected. +message PositionDetected { + // Sent a position type that is defined in PositionTypes. + PositionType position_type = 1; +} + +// Predefined configurations for detector. +enum ConfigPresetType { + // Explicitly prevents 0 from being used as a valid type. + // Doing so protects from obscure bugs caused by default-initialized values. + CONFIG_PRESET_UNSPECIFIED = 0; + + // Default preset. + CONFIG_PRESET_DEFAULT = 1; + + // Preset for sticky-stationary behavior. + CONFIG_PRESET_STICKY_STATIONARY = 2; +} + +message ConfigureDetector { + // Ref: cs/location/lbs/contexthub/nanoapps/motiondetector/motion_detector.h + message ConfigData { + // These algo parameters are exposed to enable tuning via server flags. + // The amount of time that the algorithm's computed stillness confidence + // must exceed still_confidence_threshold before entering the stationary + // state. Increasing this value will make the algorithm take longer to + // transition from the in motion state to the stationary state. + uint64 still_time_threshold_nanosecond = 1; + + // The amount of time in which the variance should be averaged. Increasing + // this value will effectively smooth the input data, making the algorithm + // less likely to transition between states. + uint32 window_width_nanosecond = 2; + + // The required confidence that the device is in motion before entering the + // motion state. Valid range is [0.0, 1.0], where 1.0 indicates that the + // algorithm must be 100% certain that the device is moving before entering + // the motion state. If the Instant Motion sensor is triggered, this value + // is ignored and the algorithm is immediately transitioned into the in + // motion state. + float motion_confidence_threshold = 3; + + // The required confidence that the device is stationary before entering the + // stationary state. Valid range is [0.0, 1.0], where 1.0 indicates that the + // algorithm must be 100% certain that the device is stationary before + // entering the stationary state. + float still_confidence_threshold = 4; + + // The variance threshold for the StillnessDetector algorithm. Increasing + // this value causes the algorithm to be less likely to detect motion. + float var_threshold = 5; + + // The variance threshold delta for the StillnessDetector algorithm about + // which the stationary confidence is calculated. Valid range is + // [0.0, var_threshold]. + float var_threshold_delta = 6; + } + + oneof type { + ConfigPresetType preset_type = 1; + ConfigData config_data = 2; + } +} diff --git a/vibrator/cs40l26/tests/Android.bp b/vibrator/cs40l26/tests/Android.bp index 93c9a9f..a3f90f2 100644 --- a/vibrator/cs40l26/tests/Android.bp +++ b/vibrator/cs40l26/tests/Android.bp @@ -18,8 +18,8 @@ package { } cc_test { - name: "VibratorHalCs40l26TestSuite", - defaults: ["VibratorHalCs40l26TestDefaults"], + name: "VibratorHalCs40l26TestSuitePrivateBluejay", + defaults: ["VibratorHalCs40l26TestDefaultsPrivateBluejay"], srcs: [ "test-hwcal.cpp", "test-hwapi.cpp", diff --git a/vibrator/cs40l26/tests/mocks.h b/vibrator/cs40l26/tests/mocks.h index 21466a0..5ba0270 100644 --- a/vibrator/cs40l26/tests/mocks.h +++ b/vibrator/cs40l26/tests/mocks.h @@ -34,6 +34,11 @@ class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi { MOCK_METHOD1(setF0CompEnable, bool(bool value)); MOCK_METHOD1(setRedcCompEnable, bool(bool value)); MOCK_METHOD1(setMinOnOffInterval, bool(uint32_t value)); + MOCK_METHOD0(getContextScale, uint32_t()); + MOCK_METHOD0(getContextEnable, bool()); + MOCK_METHOD0(getContextSettlingTime, uint32_t()); + MOCK_METHOD0(getContextCooldownTime, uint32_t()); + MOCK_METHOD0(getContextFadeEnable, bool()); MOCK_METHOD2(setFFGain, bool(int fd, uint16_t value)); MOCK_METHOD3(setFFEffect, bool(int fd, struct ff_effect *effect, uint16_t timeoutMs)); MOCK_METHOD3(setFFPlay, bool(int fd, int8_t index, bool value)); diff --git a/vibrator/cs40l26/tests/test-vibrator.cpp b/vibrator/cs40l26/tests/test-vibrator.cpp index a8bedd5..603ef5e 100644 --- a/vibrator/cs40l26/tests/test-vibrator.cpp +++ b/vibrator/cs40l26/tests/test-vibrator.cpp @@ -284,6 +284,11 @@ class VibratorTest : public Test { EXPECT_CALL(*mMockApi, setFFEffect(_, _, _)).Times(times); EXPECT_CALL(*mMockApi, setFFPlay(_, _, _)).Times(times); EXPECT_CALL(*mMockApi, setMinOnOffInterval(_)).Times(times); + EXPECT_CALL(*mMockApi, getContextScale()).Times(times); + EXPECT_CALL(*mMockApi, getContextEnable()).Times(times); + EXPECT_CALL(*mMockApi, getContextSettlingTime()).Times(times); + EXPECT_CALL(*mMockApi, getContextCooldownTime()).Times(times); + EXPECT_CALL(*mMockApi, getContextFadeEnable()).Times(times); EXPECT_CALL(*mMockApi, getHapticAlsaDevice(_, _)).Times(times); EXPECT_CALL(*mMockApi, setHapticPcmAmp(_, _, _, _)).Times(times); @@ -363,6 +368,11 @@ TEST_F(VibratorTest, Constructor) { .WillOnce(DoAll(SetArgPointee<0>(supportedPrimitivesBits), Return(true))); EXPECT_CALL(*mMockApi, setMinOnOffInterval(MIN_ON_OFF_INTERVAL_US)).WillOnce(Return(true)); + EXPECT_CALL(*mMockApi, getContextScale()).WillOnce(Return(0)); + EXPECT_CALL(*mMockApi, getContextEnable()).WillOnce(Return(false)); + EXPECT_CALL(*mMockApi, getContextSettlingTime()).WillOnce(Return(0)); + EXPECT_CALL(*mMockApi, getContextCooldownTime()).WillOnce(Return(0)); + EXPECT_CALL(*mMockApi, getContextFadeEnable()).WillOnce(Return(false)); createVibrator(std::move(mockapi), std::move(mockcal), false); } From b1acfc42d6ea23ee96d66536d6133465d3400514 Mon Sep 17 00:00:00 2001 From: Chris Paulo Date: Wed, 23 Nov 2022 20:03:29 +0000 Subject: [PATCH 11/18] [DO NOT MERGE] device/vibrator: Disable adaptive haptics feature for b3 Disable the adaptive haptics feature temporarily for qpr2 release Bug: 260247920 Test: Verified feature disabled on device Change-Id: If9929744ac798f69cf8ee73529f16badcfcb302a Signed-off-by: Chris Paulo --- vibrator/cs40l26/Android.bp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vibrator/cs40l26/Android.bp b/vibrator/cs40l26/Android.bp index 722f0e5..1867087 100644 --- a/vibrator/cs40l26/Android.bp +++ b/vibrator/cs40l26/Android.bp @@ -81,6 +81,9 @@ cc_library_shared { "Vibrator.cpp", "CapoDetector.cpp", ], + cflags: [ + "-DDISABLE_ADAPTIVE_HAPTICS_FEATURE", + ], static_libs: [ "chre_client", ], From 1df796b0cc5151f18fb1e790dd189af7d898ed43 Mon Sep 17 00:00:00 2001 From: millerliang Date: Tue, 6 Dec 2022 15:29:39 +0800 Subject: [PATCH 12/18] audio: add missed camcorder be_cfg in dsp latency Some 3rd party app may use one or three microhpne(s) to do the camera recording. Add these two configurations to UC_AUDIO_RECORD to sync the audio and video of the recoreded video. Bug: 260824124 Test: Reporter verified Change-Id: Iaaf160070a26926a3cf6e18a184750d154abef6d --- audio/bluejay/config/audio_platform_configuration.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/audio/bluejay/config/audio_platform_configuration.xml b/audio/bluejay/config/audio_platform_configuration.xml index f510d08..f1232f6 100644 --- a/audio/bluejay/config/audio_platform_configuration.xml +++ b/audio/bluejay/config/audio_platform_configuration.xml @@ -224,6 +224,8 @@ + + From bbab3bdc4f2df8df3f272bfec5514d05a2bc6d51 Mon Sep 17 00:00:00 2001 From: Steve Berbary Date: Wed, 7 Dec 2022 20:59:40 +0000 Subject: [PATCH 13/18] Update bluejay SVN to 22 Bug: 261738635 Change-Id: Ibe9ee074eb3ad1df5b61bf5e053a0bbdae6abbba --- device-bluejay.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bluejay.mk b/device-bluejay.mk index 1345940..8d9f930 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -122,7 +122,7 @@ PRODUCT_SOONG_NAMESPACES += \ # Increment the SVN for any official public releases PRODUCT_VENDOR_PROPERTIES += \ - ro.vendor.build.svn=21 + ro.vendor.build.svn=22 # DCK properties based on target PRODUCT_PROPERTY_OVERRIDES += \ From 47ddbad03e4e97f37c350a2f8ccb1011c399dae4 Mon Sep 17 00:00:00 2001 From: Chungkai Mei Date: Thu, 8 Dec 2022 09:46:28 +0000 Subject: [PATCH 14/18] Remove UClampThreshold hint the file node was removed so also remove these dummy setting Bug: 260806701 Change-Id: I23044f7f32cf4b2b2cc93205ec5751525f05ee7f Signed-off-by: Chungkai Mei --- powerhint.json | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/powerhint.json b/powerhint.json index d3b4df0..22f81f3 100644 --- a/powerhint.json +++ b/powerhint.json @@ -167,15 +167,6 @@ "DefaultIndex": 0, "ResetOnInit": true }, - { - "Name": "UClampThreshold", - "Path": "/proc/vendor_sched/uclamp_threshold", - "Values": [ - "0", - "8" - ], - "ResetOnInit": true - }, { "Name": "ReducePreferIdle", "Path": "/proc/vendor_sched/reduce_prefer_idle", @@ -516,12 +507,6 @@ "Duration": 1000, "Value": "0-3" }, - { - "PowerHint": "LAUNCH", - "Node": "UClampThreshold", - "Duration": 5000, - "Value": "0" - }, { "PowerHint": "LAUNCH", "Node": "ReducePreferIdle", From 8f4e8161ee22dd446e4341cce8719b006d0ac277 Mon Sep 17 00:00:00 2001 From: Chris Paulo Date: Thu, 15 Dec 2022 21:19:02 +0000 Subject: [PATCH 15/18] [DO NOT MERGE] bluejay/vibrator: Fix Capodetector on AoC restart Update pointers used in protobuf interface for configuring the detector. When AoC has an SSR, it causes CapoDetector to go down by null pointer dereference. Bug: 261349596 Test: Verified 10x forced AoC SSR Change-Id: Ifd7fcbbff4639f552c6708b5b171f0b3c9164c03 Signed-off-by: Chris Paulo --- vibrator/cs40l26/CapoDetector.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/vibrator/cs40l26/CapoDetector.cpp b/vibrator/cs40l26/CapoDetector.cpp index 1b8ba89..0fc4d4b 100644 --- a/vibrator/cs40l26/CapoDetector.cpp +++ b/vibrator/cs40l26/CapoDetector.cpp @@ -30,10 +30,6 @@ namespace chre { namespace { // anonymous namespace for file-local definitions -static capo::ConfigureDetector_ConfigData config_data = capo::ConfigureDetector_ConfigData(); -static capo::ConfigureDetector msg = capo::ConfigureDetector(); - - /** * Called when onConnected() to send NanoappList request. */ @@ -166,19 +162,22 @@ void CapoDetector::enable() { // Create CHRE message with serialized message flatbuffers::FlatBufferBuilder builder, config_builder, force_builder; - config_data.set_still_time_threshold_nanosecond(mCapoDetectorMDParameters.still_time_threshold_ns); - config_data.set_window_width_nanosecond(mCapoDetectorMDParameters.window_width_ns); - config_data.set_motion_confidence_threshold(mCapoDetectorMDParameters.motion_confidence_threshold); - config_data.set_still_confidence_threshold(mCapoDetectorMDParameters.still_confidence_threshold); - config_data.set_var_threshold(mCapoDetectorMDParameters.var_threshold); - config_data.set_var_threshold_delta(mCapoDetectorMDParameters.var_threshold_delta); + auto config_data = std::make_unique(); + auto msg = std::make_unique(); - msg.set_allocated_config_data(&config_data); + config_data->set_still_time_threshold_nanosecond(mCapoDetectorMDParameters.still_time_threshold_ns); + config_data->set_window_width_nanosecond(mCapoDetectorMDParameters.window_width_ns); + config_data->set_motion_confidence_threshold(mCapoDetectorMDParameters.motion_confidence_threshold); + config_data->set_still_confidence_threshold(mCapoDetectorMDParameters.still_confidence_threshold); + config_data->set_var_threshold(mCapoDetectorMDParameters.var_threshold); + config_data->set_var_threshold_delta(mCapoDetectorMDParameters.var_threshold_delta); - auto pb_size = msg.ByteSizeLong(); + msg->set_allocated_config_data(config_data.release()); + + auto pb_size = msg->ByteSizeLong(); auto pb_data = std::make_unique(pb_size); - if (!msg.SerializeToArray(pb_data.get(), pb_size)) { + if (!msg->SerializeToArray(pb_data.get(), pb_size)) { ALOGE("Failed to serialize message."); } From 36730c7be29d7d8db97f95e01d1c4962cbe93baf Mon Sep 17 00:00:00 2001 From: Paul Scovanner Date: Thu, 22 Dec 2022 02:11:25 +0000 Subject: [PATCH 16/18] Update bluejay SVN to 23 Bug: 258811679 Change-Id: Iff3dd7e32914ce7b1ac7662846609c430ab043ab --- device-bluejay.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bluejay.mk b/device-bluejay.mk index 8d9f930..4665442 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -122,7 +122,7 @@ PRODUCT_SOONG_NAMESPACES += \ # Increment the SVN for any official public releases PRODUCT_VENDOR_PROPERTIES += \ - ro.vendor.build.svn=22 + ro.vendor.build.svn=23 # DCK properties based on target PRODUCT_PROPERTY_OVERRIDES += \ From a1e02997c8bc719b653ae7e5a2d4d9e4fc073b50 Mon Sep 17 00:00:00 2001 From: Edwin Tung Date: Thu, 29 Dec 2022 13:15:41 +0800 Subject: [PATCH 17/18] gps: Enable Vzw SUPL OTDOA Bug: 263852801 Test: OTDOA pass Change-Id: Ia4e60851f212972ddebc5774c22c1eb7233fffbe --- gps.xml.b3 | 1 + gps_user.xml.b3 | 1 + 2 files changed, 2 insertions(+) diff --git a/gps.xml.b3 b/gps.xml.b3 index 99e3b58..e119f41 100644 --- a/gps.xml.b3 +++ b/gps.xml.b3 @@ -35,6 +35,7 @@ SuplVersion="2" SuplMinorVersion="0" SuplOtdoaCapable="true" + SuplOtdoaCapable2="true" SuplGlonassCapable = "true" SuplGalileoCapable = "true" SuplBdsCapable = "true" diff --git a/gps_user.xml.b3 b/gps_user.xml.b3 index f105fa1..83af222 100644 --- a/gps_user.xml.b3 +++ b/gps_user.xml.b3 @@ -34,6 +34,7 @@ SuplVersion="2" SuplMinorVersion="0" SuplOtdoaCapable="true" + SuplOtdoaCapable2="true" SuplGlonassCapable = "true" SuplGalileoCapable = "true" SuplBdsCapable = "true" From 79dd1bc1fc4af42ceb37d9458159b4d5ce2a1bb4 Mon Sep 17 00:00:00 2001 From: Steve Berbary Date: Thu, 5 Jan 2023 21:30:17 +0000 Subject: [PATCH 18/18] Update bluejay SVN to 24 Bug: 261738635 Change-Id: I0c5d7a4f1d9ff3f9ad352da381ddf89b8a2f42d2 --- device-bluejay.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/device-bluejay.mk b/device-bluejay.mk index 4665442..de9d892 100644 --- a/device-bluejay.mk +++ b/device-bluejay.mk @@ -122,7 +122,7 @@ PRODUCT_SOONG_NAMESPACES += \ # Increment the SVN for any official public releases PRODUCT_VENDOR_PROPERTIES += \ - ro.vendor.build.svn=23 + ro.vendor.build.svn=24 # DCK properties based on target PRODUCT_PROPERTY_OVERRIDES += \