summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXin Li <delphij@google.com>2020-08-27 10:16:41 -0700
committerXin Li <delphij@google.com>2020-08-27 10:16:41 -0700
commite2c629010dfa50f7be8759179452308d1468474d (patch)
tree18ff8b7c2bbe4be5996d1916c561b648b3c688fc
parent7a489f84aba93b2f6da99afabd6554045f6252bd (diff)
parentcbf5fb9908e0ba4b129a6288f272a04285b2f6c8 (diff)
downloadplatform_hardware_google_pixel-e2c629010dfa50f7be8759179452308d1468474d.tar.gz
platform_hardware_google_pixel-e2c629010dfa50f7be8759179452308d1468474d.tar.bz2
platform_hardware_google_pixel-e2c629010dfa50f7be8759179452308d1468474d.zip
Merge Android R (rvc-dev-plus-aosp-without-vendor@6692709)
Bug: 166295507 Merged-In: I55e81cf4741d8e7e31891caa151eda5f8cb2b7d7 Change-Id: I9b4071d1f54f4867c29d013b8c0eaf80bbf087d0
-rw-r--r--CleanSpec.mk60
-rw-r--r--atrace/OWNERS2
-rw-r--r--bootctrl/Android.bp29
-rw-r--r--bootctrl/BootControlShared.cpp60
-rw-r--r--bootctrl/BootControlShared.h46
-rw-r--r--bootctrl/LegacyBootControl.cpp122
-rw-r--r--bootctrl/LegacyBootControl.h56
-rw-r--r--common/fstab.firmware4
-rw-r--r--common/fstab.persist4
-rwxr-xr-xcommon/init.insmod.sh38
-rw-r--r--common/init.pixel.rc34
-rw-r--r--common/pixel-common-device.mk11
-rw-r--r--health/Android.bp39
-rw-r--r--health/BatteryDefender.cpp318
-rw-r--r--health/include/pixelhealth/BatteryDefender.h145
-rw-r--r--health/include/pixelhealth/CycleCountBackupRestore.h2
-rw-r--r--health/test/TestBatteryDefender.cpp416
-rw-r--r--mm/Android.bp8
-rw-r--r--mm/device.mk9
-rw-r--r--mm/device_legacy.mk9
-rwxr-xr-xmm/init.mm.logging.sh34
-rw-r--r--mm/pixel-mm-legacy.rc42
-rw-r--r--mm/pixel-mm-logd.rc19
-rw-r--r--mm/pixel-mm.rc41
-rw-r--r--pixelstats/Android.bp4
-rw-r--r--pixelstats/BatteryCapacityReporter.cpp182
-rw-r--r--pixelstats/DropDetect.cpp95
-rw-r--r--pixelstats/OWNERS3
-rw-r--r--pixelstats/OrientationCollector.cpp140
-rw-r--r--pixelstats/SysfsCollector.cpp310
-rw-r--r--pixelstats/UeventListener.cpp97
-rw-r--r--pixelstats/WlcReporter.cpp152
-rw-r--r--pixelstats/device.mk5
-rw-r--r--pixelstats/include/pixelstats/BatteryCapacityReporter.h82
-rw-r--r--pixelstats/include/pixelstats/OrientationCollector.h52
-rw-r--r--pixelstats/include/pixelstats/SysfsCollector.h24
-rw-r--r--pixelstats/include/pixelstats/UeventListener.h17
-rw-r--r--pixelstats/include/pixelstats/WlcReporter.h63
-rw-r--r--pixelstats/pixelatoms.proto122
-rw-r--r--power-libperfmgr/Android.bp52
-rw-r--r--power-libperfmgr/OWNERS4
-rw-r--r--power-libperfmgr/aidl/Power.cpp256
-rw-r--r--power-libperfmgr/aidl/Power.h63
-rw-r--r--power-libperfmgr/aidl/PowerExt.cpp87
-rw-r--r--power-libperfmgr/aidl/PowerExt.h56
-rw-r--r--power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc17
-rw-r--r--power-libperfmgr/aidl/android.hardware.power-service.pixel.xml6
-rw-r--r--power-libperfmgr/aidl/device.mk5
-rw-r--r--power-libperfmgr/aidl/service.cpp78
-rw-r--r--power-libperfmgr/disp-power/DisplayLowPower.cpp73
-rw-r--r--power-libperfmgr/disp-power/DisplayLowPower.h37
-rw-r--r--power-libperfmgr/disp-power/InteractionHandler.cpp (renamed from power-libperfmgr/InteractionHandler.cpp)2
-rw-r--r--power-libperfmgr/disp-power/InteractionHandler.h (renamed from power-libperfmgr/InteractionHandler.h)0
-rw-r--r--power-libperfmgr/display-helper.cpp74
-rw-r--r--power-libperfmgr/hidl/AudioStreaming.h (renamed from power-libperfmgr/AudioStreaming.h)0
-rw-r--r--power-libperfmgr/hidl/CameraMode.h (renamed from power-libperfmgr/CameraMode.h)0
-rw-r--r--power-libperfmgr/hidl/Power.cpp (renamed from power-libperfmgr/Power.cpp)38
-rw-r--r--power-libperfmgr/hidl/Power.h (renamed from power-libperfmgr/Power.h)4
-rw-r--r--power-libperfmgr/hidl/android.hardware.power@1.3-service.pixel-libperfmgr.rc (renamed from power-libperfmgr/android.hardware.power@1.3-service.pixel-libperfmgr.rc)16
-rw-r--r--power-libperfmgr/hidl/android.hardware.power@1.3-service.pixel.xml (renamed from power-libperfmgr/android.hardware.power@1.3-service.pixel.xml)0
-rw-r--r--power-libperfmgr/hidl/device.mk5
-rw-r--r--power-libperfmgr/hidl/service.cpp (renamed from power-libperfmgr/service.cpp)0
-rw-r--r--power-libperfmgr/libperfmgr/Android.bp78
-rw-r--r--power-libperfmgr/libperfmgr/FileNode.cc122
-rw-r--r--power-libperfmgr/libperfmgr/HintManager.cc392
-rw-r--r--power-libperfmgr/libperfmgr/Node.cc97
-rw-r--r--power-libperfmgr/libperfmgr/NodeLooperThread.cc153
-rw-r--r--power-libperfmgr/libperfmgr/PropertyNode.cc84
-rw-r--r--power-libperfmgr/libperfmgr/RequestGroup.cc84
-rw-r--r--power-libperfmgr/libperfmgr/TEST_MAPPING12
-rw-r--r--power-libperfmgr/libperfmgr/config_schema.json126
-rw-r--r--power-libperfmgr/libperfmgr/include/perfmgr/FileNode.h56
-rw-r--r--power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h98
-rw-r--r--power-libperfmgr/libperfmgr/include/perfmgr/Node.h92
-rw-r--r--power-libperfmgr/libperfmgr/include/perfmgr/NodeLooperThread.h104
-rw-r--r--power-libperfmgr/libperfmgr/include/perfmgr/PropertyNode.h48
-rw-r--r--power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h67
-rw-r--r--power-libperfmgr/libperfmgr/tests/FileNodeTest.cc234
-rw-r--r--power-libperfmgr/libperfmgr/tests/HintManagerTest.cc575
-rw-r--r--power-libperfmgr/libperfmgr/tests/NodeLooperThreadTest.cc195
-rw-r--r--power-libperfmgr/libperfmgr/tests/PropertyNodeTest.cc222
-rw-r--r--power-libperfmgr/libperfmgr/tests/RequestGroupTest.cc164
-rw-r--r--power-libperfmgr/libperfmgr/tools/ConfigVerifier.cc177
l---------pwrstats_util/.clang-format1
-rw-r--r--pwrstats_util/Android.bp75
-rw-r--r--pwrstats_util/OWNERS3
-rw-r--r--pwrstats_util/PowerStatsCollector.cpp131
-rw-r--r--pwrstats_util/PowerStatsCollector.h64
-rw-r--r--pwrstats_util/dataproviders/DataProviderHelper.cpp50
-rw-r--r--pwrstats_util/dataproviders/DataProviderHelper.h28
-rw-r--r--pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.cpp127
-rw-r--r--pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.h32
-rw-r--r--pwrstats_util/dataproviders/RailEnergyDataProvider.cpp123
-rw-r--r--pwrstats_util/dataproviders/RailEnergyDataProvider.h37
-rw-r--r--pwrstats_util/pwrstats_util.cpp221
-rw-r--r--pwrstats_util/pwrstatsutil.proto50
-rw-r--r--thermal/Android.bp11
-rw-r--r--thermal/OWNERS3
-rw-r--r--thermal/device.mk10
-rwxr-xr-xthermal/init.thermal.logging.sh25
-rw-r--r--thermal/pixel-thermal-logd.rc14
-rw-r--r--vibrator/Android.bp48
-rw-r--r--vibrator/OWNERS3
-rw-r--r--vibrator/common/Android.bp33
-rw-r--r--vibrator/common/HardwareBase.cpp136
-rw-r--r--vibrator/common/HardwareBase.h208
-rw-r--r--vibrator/common/TEST_MAPPING14
-rw-r--r--vibrator/common/bench/Android.bp32
-rw-r--r--vibrator/common/bench/benchmark.cpp272
-rw-r--r--vibrator/common/utils.h163
-rw-r--r--vibrator/cs40l25/Android.bp58
-rw-r--r--vibrator/cs40l25/Hardware.h138
-rw-r--r--vibrator/cs40l25/TEST_MAPPING20
-rw-r--r--vibrator/cs40l25/Vibrator.cpp628
-rw-r--r--vibrator/cs40l25/Vibrator.h180
-rw-r--r--vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc57
-rw-r--r--vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.xml6
-rw-r--r--vibrator/cs40l25/bench/Android.bp32
-rw-r--r--vibrator/cs40l25/bench/benchmark.cpp166
-rw-r--r--vibrator/cs40l25/device.mk9
-rw-r--r--vibrator/cs40l25/diag/Android.bp20
-rw-r--r--vibrator/cs40l25/diag/diag-vibrator.sh64
-rw-r--r--vibrator/cs40l25/service.cpp38
-rw-r--r--vibrator/cs40l25/tests/Android.bp31
-rw-r--r--vibrator/cs40l25/tests/mocks.h69
-rw-r--r--vibrator/cs40l25/tests/test-hwapi.cpp351
-rw-r--r--vibrator/cs40l25/tests/test-hwcal.cpp299
-rw-r--r--vibrator/cs40l25/tests/test-vibrator.cpp661
-rw-r--r--vibrator/cs40l25/tests/types.h33
-rw-r--r--vibrator/cs40l25/tests/utils.h39
-rw-r--r--vibrator/drv2624/Android.bp58
-rw-r--r--vibrator/drv2624/Hardware.h146
-rw-r--r--vibrator/drv2624/TEST_MAPPING15
-rw-r--r--vibrator/drv2624/Vibrator.cpp362
-rw-r--r--vibrator/drv2624/Vibrator.h177
-rw-r--r--vibrator/drv2624/android.hardware.vibrator-service.drv2624.rc15
-rw-r--r--vibrator/drv2624/android.hardware.vibrator-service.drv2624.xml6
-rw-r--r--vibrator/drv2624/bench/Android.bp32
-rw-r--r--vibrator/drv2624/bench/benchmark.cpp188
-rw-r--r--vibrator/drv2624/device.mk5
-rw-r--r--vibrator/drv2624/service.cpp44
-rw-r--r--vibrator/drv2624/tests/Android.bp31
-rw-r--r--vibrator/drv2624/tests/mocks.h64
-rw-r--r--vibrator/drv2624/tests/test-hwapi.cpp402
-rw-r--r--vibrator/drv2624/tests/test-hwcal.cpp394
-rw-r--r--vibrator/drv2624/tests/test-vibrator.cpp453
-rw-r--r--vibrator/drv2624/tests/types.h27
-rw-r--r--vibrator/drv2624/tests/utils.h (renamed from power-libperfmgr/display-helper.h)19
148 files changed, 14034 insertions, 166 deletions
diff --git a/CleanSpec.mk b/CleanSpec.mk
new file mode 100644
index 0000000..d0b6dff
--- /dev/null
+++ b/CleanSpec.mk
@@ -0,0 +1,60 @@
+# Copyright 2019 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+# If you don't need to do a full clean build but would like to touch
+# a file or delete some intermediate files, add a clean step to the end
+# of the list. These steps will only be run once, if they haven't been
+# run before.
+#
+# E.g.:
+# $(call add-clean-step, touch -c external/sqlite/sqlite3.h)
+# $(call add-clean-step, rm -rf $(PRODUCT_OUT)/obj/STATIC_LIBRARIES/libz_intermediates)
+#
+# Always use "touch -c" and "rm -f" or "rm -rf" to gracefully deal with
+# files that are missing or have been moved.
+#
+# Use $(PRODUCT_OUT) to get to the "out/target/product/blah/" directory.
+# Use $(OUT_DIR) to refer to the "out" directory.
+#
+# If you need to re-do something that's already mentioned, just copy
+# the command and add it to the bottom of the list. E.g., if a change
+# that you made last week required touching a file and a change you
+# made today requires touching the same file, just copy the old
+# touch step and add it to the end of the list.
+#
+# ************************************************
+# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
+# ************************************************
+
+# For example:
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/APPS/AndroidTests_intermediates)
+#$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/core_intermediates)
+#$(call add-clean-step, find $(OUT_DIR) -type f -name "IGTalkSession*" -print0 | xargs -0 rm -f)
+#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
+
+# CS40L25 Vibrator HAL Version Change from 1.3 to 1.4
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.vibrator@1.3-service.cs40l25.rc)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.vibrator@1.3-service.cs40l25)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/android.hardware.vibrator@1.3-service.cs40l25.xml)
+
+# CS40L25 Vibrator HAL Version switch to AIDL
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.vibrator@1.4-service.cs40l25.rc)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.vibrator@1.4-service.cs40l25)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/android.hardware.vibrator@1.4-service.cs40l25.xml)
+
+# DRV2624 Vibrator HAL Version switch to AIDL
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/init/android.hardware.vibrator@1.3-service.drv2624.rc)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/bin/hw/android.hardware.vibrator@1.3-service.drv2624)
+$(call add-clean-step, rm -rf $(PRODUCT_OUT)/vendor/etc/vintf/manifest/android.hardware.vibrator@1.3-service.drv2624.xml)
diff --git a/atrace/OWNERS b/atrace/OWNERS
new file mode 100644
index 0000000..05e234a
--- /dev/null
+++ b/atrace/OWNERS
@@ -0,0 +1,2 @@
+wvw@google.com
+namhyung@google.com
diff --git a/bootctrl/Android.bp b/bootctrl/Android.bp
new file mode 100644
index 0000000..852800e
--- /dev/null
+++ b/bootctrl/Android.bp
@@ -0,0 +1,29 @@
+cc_library {
+ name: "android.hardware.boot@1.1-impl-pixel-legacy",
+ stem: "android.hardware.boot@1.0-impl-1.1-pixel-legacy",
+ vendor: true,
+ recovery_available: true,
+ srcs: [
+ "BootControlShared.cpp",
+ "LegacyBootControl.cpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ relative_install_path: "hw",
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libhardware",
+ "libhidlbase",
+ "libutils",
+ "android.hardware.boot@1.0",
+ "android.hardware.boot@1.1",
+ ],
+ static_libs: [
+ "libboot_control",
+ "libbootloader_message_vendor",
+ "libfstab",
+ ],
+}
diff --git a/bootctrl/BootControlShared.cpp b/bootctrl/BootControlShared.cpp
new file mode 100644
index 0000000..125bec4
--- /dev/null
+++ b/bootctrl/BootControlShared.cpp
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "bootcontrolhal"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+#include <libboot_control/libboot_control.h>
+
+#include "BootControlShared.h"
+
+namespace android {
+namespace hardware {
+namespace boot {
+namespace V1_1 {
+namespace implementation {
+
+using android::bootable::GetMiscVirtualAbMergeStatus;
+using android::bootable::InitMiscVirtualAbMessageIfNeeded;
+using android::bootable::SetMiscVirtualAbMergeStatus;
+using android::hardware::boot::V1_1::MergeStatus;
+
+BootControlShared::BootControlShared() {}
+
+bool BootControlShared::Init() {
+ return InitMiscVirtualAbMessageIfNeeded();
+}
+
+Return<bool> BootControlShared::setSnapshotMergeStatus(MergeStatus status) {
+ return SetMiscVirtualAbMergeStatus(getCurrentSlot(), status);
+}
+
+Return<MergeStatus> BootControlShared::getSnapshotMergeStatus() {
+ MergeStatus status;
+ if (!GetMiscVirtualAbMergeStatus(getCurrentSlot(), &status)) {
+ return MergeStatus::UNKNOWN;
+ }
+ return status;
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace boot
+} // namespace hardware
+} // namespace android
diff --git a/bootctrl/BootControlShared.h b/bootctrl/BootControlShared.h
new file mode 100644
index 0000000..ff49e3e
--- /dev/null
+++ b/bootctrl/BootControlShared.h
@@ -0,0 +1,46 @@
+/*
+ * 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 <android/hardware/boot/1.1/IBootControl.h>
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+
+namespace android {
+namespace hardware {
+namespace boot {
+namespace V1_1 {
+namespace implementation {
+
+using ::android::hardware::Return;
+
+struct BootControlShared : public IBootControl {
+ BootControlShared();
+
+ bool Init();
+
+ Return<bool> setSnapshotMergeStatus(MergeStatus status) override;
+ Return<MergeStatus> getSnapshotMergeStatus() override;
+};
+
+extern "C" IBootControl *HIDL_FETCH_IBootControl(const char *name);
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace boot
+} // namespace hardware
+} // namespace android
diff --git a/bootctrl/LegacyBootControl.cpp b/bootctrl/LegacyBootControl.cpp
new file mode 100644
index 0000000..83254e9
--- /dev/null
+++ b/bootctrl/LegacyBootControl.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "android.hardware.boot@1.1-impl"
+
+#include <log/log.h>
+
+#include <hardware/boot_control.h>
+#include <hardware/hardware.h>
+#include "LegacyBootControl.h"
+
+#ifdef BOOT_CONTROL_RECOVERY
+extern const hw_module_t HAL_MODULE_INFO_SYM;
+#endif
+
+namespace android {
+namespace hardware {
+namespace boot {
+namespace V1_1 {
+namespace implementation {
+
+using ::android::hardware::boot::V1_0::CommandResult;
+
+BootControl::BootControl(boot_control_module_t *module) : mModule(module) {}
+
+// Methods from ::android::hardware::boot::V1_0::IBootControl follow.
+Return<uint32_t> BootControl::getNumberSlots() {
+ return mModule->getNumberSlots(mModule);
+}
+
+Return<uint32_t> BootControl::getCurrentSlot() {
+ return mModule->getCurrentSlot(mModule);
+}
+
+Return<void> BootControl::markBootSuccessful(markBootSuccessful_cb _hidl_cb) {
+ int ret = mModule->markBootSuccessful(mModule);
+ struct CommandResult cr;
+ cr.success = (ret == 0);
+ cr.errMsg = strerror(-ret);
+ _hidl_cb(cr);
+ return Void();
+}
+
+Return<void> BootControl::setActiveBootSlot(uint32_t slot, setActiveBootSlot_cb _hidl_cb) {
+ int ret = mModule->setActiveBootSlot(mModule, slot);
+ struct CommandResult cr;
+ cr.success = (ret == 0);
+ cr.errMsg = strerror(-ret);
+ _hidl_cb(cr);
+ return Void();
+}
+
+Return<void> BootControl::setSlotAsUnbootable(uint32_t slot, setSlotAsUnbootable_cb _hidl_cb) {
+ int ret = mModule->setSlotAsUnbootable(mModule, slot);
+ struct CommandResult cr;
+ cr.success = (ret == 0);
+ cr.errMsg = strerror(-ret);
+ _hidl_cb(cr);
+ return Void();
+}
+
+Return<BoolResult> BootControl::isSlotBootable(uint32_t slot) {
+ int32_t ret = mModule->isSlotBootable(mModule, slot);
+ if (ret < 0) {
+ return BoolResult::INVALID_SLOT;
+ }
+ return ret ? BoolResult::TRUE : BoolResult::FALSE;
+}
+
+Return<BoolResult> BootControl::isSlotMarkedSuccessful(uint32_t slot) {
+ int32_t ret = mModule->isSlotMarkedSuccessful(mModule, slot);
+ if (ret < 0) {
+ return BoolResult::INVALID_SLOT;
+ }
+ return ret ? BoolResult::TRUE : BoolResult::FALSE;
+}
+
+Return<void> BootControl::getSuffix(uint32_t slot, getSuffix_cb _hidl_cb) {
+ hidl_string ans;
+ const char *suffix = mModule->getSuffix(mModule, slot);
+ if (suffix) {
+ ans = suffix;
+ }
+ _hidl_cb(ans);
+ return Void();
+}
+
+IBootControl *HIDL_FETCH_IBootControl(const char * /* hal */) {
+ int ret = 0;
+ boot_control_module_t *module = NULL;
+ hw_module_t **hwm = reinterpret_cast<hw_module_t **>(&module);
+ ret = hw_get_module(BOOT_CONTROL_HARDWARE_MODULE_ID, const_cast<const hw_module_t **>(hwm));
+ if (ret) {
+ ALOGE("hw_get_module %s failed: %d", BOOT_CONTROL_HARDWARE_MODULE_ID, ret);
+ return nullptr;
+ }
+ module->init(module);
+
+ auto hal = new BootControl(module);
+ if (!hal->Init()) {
+ ALOGE("Failed to initialize boot control HAL");
+ }
+ return hal;
+}
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace boot
+} // namespace hardware
+} // namespace android
diff --git a/bootctrl/LegacyBootControl.h b/bootctrl/LegacyBootControl.h
new file mode 100644
index 0000000..0653cbd
--- /dev/null
+++ b/bootctrl/LegacyBootControl.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <hidl/MQDescriptor.h>
+#include <hidl/Status.h>
+
+#include "BootControlShared.h"
+
+namespace android {
+namespace hardware {
+namespace boot {
+namespace V1_1 {
+namespace implementation {
+
+using ::android::hardware::Return;
+using ::android::hardware::boot::V1_0::BoolResult;
+
+struct BootControl : public BootControlShared {
+ explicit BootControl(boot_control_module_t *module);
+
+ // Methods from ::android::hardware::boot::V1_0::IBootControl follow.
+ Return<uint32_t> getNumberSlots() override;
+ Return<uint32_t> getCurrentSlot() override;
+ Return<void> markBootSuccessful(markBootSuccessful_cb _hidl_cb) override;
+ Return<void> setActiveBootSlot(uint32_t slot, setActiveBootSlot_cb _hidl_cb) override;
+ Return<void> setSlotAsUnbootable(uint32_t slot, setSlotAsUnbootable_cb _hidl_cb) override;
+ Return<BoolResult> isSlotBootable(uint32_t slot) override;
+ Return<BoolResult> isSlotMarkedSuccessful(uint32_t slot) override;
+ Return<void> getSuffix(uint32_t slot, getSuffix_cb _hidl_cb) override;
+
+ private:
+ boot_control_module_t *mModule;
+};
+
+extern "C" IBootControl *HIDL_FETCH_IBootControl(const char *name);
+
+} // namespace implementation
+} // namespace V1_1
+} // namespace boot
+} // namespace hardware
+} // namespace android
diff --git a/common/fstab.firmware b/common/fstab.firmware
new file mode 100644
index 0000000..5968dcc
--- /dev/null
+++ b/common/fstab.firmware
@@ -0,0 +1,4 @@
+# Android fstab file.
+
+#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
+/dev/block/bootdevice/by-name/modem /vendor/firmware_mnt vfat ro,shortname=lower,uid=0,gid=1000,dmask=227,fmask=337,context=u:object_r:firmware_file:s0 wait,slotselect
diff --git a/common/fstab.persist b/common/fstab.persist
new file mode 100644
index 0000000..f3840b1
--- /dev/null
+++ b/common/fstab.persist
@@ -0,0 +1,4 @@
+# Keep persist in an fstab file, since we need to run fsck on it after abnormal shutdown.
+
+#<src> <mnt_point> <type> <mnt_flags and options> <fs_mgr_flags>
+/dev/block/platform/soc/1d84000.ufshc/by-name/persist /mnt/vendor/persist ext4 nosuid,nodev,noatime,barrier=1 wait
diff --git a/common/init.insmod.sh b/common/init.insmod.sh
new file mode 100755
index 0000000..29e04c8
--- /dev/null
+++ b/common/init.insmod.sh
@@ -0,0 +1,38 @@
+#!/vendor/bin/sh
+
+########################################################
+### init.insmod.cfg format: ###
+### ----------------------------------------------- ###
+### [insmod|setprop|enable/moprobe] [path|prop name] ###
+### ... ###
+########################################################
+
+if [ $# -eq 1 ]; then
+ cfg_file=$1
+else
+ exit 1
+fi
+
+if [ -f $cfg_file ]; then
+ while IFS="|" read -r action arg
+ do
+ case $action in
+ "insmod") insmod $arg ;;
+ "setprop") setprop $arg 1 ;;
+ "enable") echo 1 > $arg ;;
+ "modprobe")
+ case ${arg} in
+ "-b *" | "-b")
+ arg="-b $(cat /vendor/lib/modules/modules.load)" ;;
+ "*" | "")
+ arg="$(cat /vendor/lib/modules/modules.load)" ;;
+ esac
+ modprobe -a -d /vendor/lib/modules $arg ;;
+ esac
+ done < $cfg_file
+fi
+
+# set property even if there is no insmod config
+# as property value "1" is expected in early-boot trigger
+setprop vendor.all.modules.ready 1
+setprop vendor.all.devices.ready 1
diff --git a/common/init.pixel.rc b/common/init.pixel.rc
new file mode 100644
index 0000000..9c4d223
--- /dev/null
+++ b/common/init.pixel.rc
@@ -0,0 +1,34 @@
+on early-init
+ mount_all /vendor/etc/fstab.persist --early
+
+on fs
+ mount_all /vendor/etc/fstab.firmware --early
+
+on late-fs
+ mount_all /vendor/etc/fstab.firmware --late
+
+on property:sys.boot_completed=1
+ swapon_all /vendor/etc/fstab.firmware
+
+# Write the dark theme magic to /misc partition.
+service vendor.theme_set /vendor/bin/misc_writer --set-dark-theme
+ disabled
+ oneshot
+
+# Clear the dark theme magic in /misc partition.
+service vendor.theme_clear /vendor/bin/misc_writer --clear-dark-theme
+ disabled
+ oneshot
+
+# Set dark boot flag on dark mode (UiModeManager.MODE_NIGHT_YES == 2).
+on property:persist.sys.theme=2
+ start vendor.theme_set
+
+# Clear the dark boot flag on light mode (UiModeManager.MODE_NIGHT_NO == 1) or auto mode
+# (UiModeManager.MODE_NIGHT_AUTO == 0).
+on property:persist.sys.theme=1
+ start vendor.theme_clear
+
+on property:persist.sys.theme=0
+ start vendor.theme_clear
+
diff --git a/common/pixel-common-device.mk b/common/pixel-common-device.mk
new file mode 100644
index 0000000..0f7f6af
--- /dev/null
+++ b/common/pixel-common-device.mk
@@ -0,0 +1,11 @@
+PRODUCT_COPY_FILES += \
+ hardware/google/pixel/common/fstab.firmware:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.firmware \
+ hardware/google/pixel/common/fstab.persist:$(TARGET_COPY_OUT_VENDOR)/etc/fstab.persist \
+ hardware/google/pixel/common/init.pixel.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/init.pixel.rc \
+ hardware/google/pixel/common/init.insmod.sh:$(TARGET_COPY_OUT_VENDOR)/bin/init.insmod.sh
+
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/common
+
+# Write flags to the vendor space in /misc partition.
+PRODUCT_PACKAGES += \
+ misc_writer
diff --git a/health/Android.bp b/health/Android.bp
index 46a0a4d..b563b43 100644
--- a/health/Android.bp
+++ b/health/Android.bp
@@ -9,6 +9,7 @@ cc_library {
"CycleCountBackupRestore.cpp",
"DeviceHealth.cpp",
"LowBatteryShutdownMetrics.cpp",
+ "BatteryDefender.cpp",
],
cflags: [
@@ -32,3 +33,41 @@ cc_library {
"libutils",
],
}
+
+cc_test {
+ name: "HealthTestCases",
+
+ compile_multilib: "both",
+ multilib: {
+ lib32: {
+ suffix: "32",
+ },
+ lib64: {
+ suffix: "64",
+ },
+ },
+
+ srcs: [
+ "test/TestBatteryDefender.cpp",
+ ],
+
+ local_include_dirs: [
+ "include/pixelhealth",
+ ],
+
+ static_libs: [
+ "libgmock",
+ "libpixelhealth",
+ ],
+
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libutils",
+ ],
+
+ test_suites: [
+ "device-tests",
+ ],
+ vendor: true,
+}
diff --git a/health/BatteryDefender.cpp b/health/BatteryDefender.cpp
new file mode 100644
index 0000000..37283da
--- /dev/null
+++ b/health/BatteryDefender.cpp
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BatteryDefender"
+
+#include <pixelhealth/BatteryDefender.h>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/parsebool.h>
+#include <android-base/parseint.h>
+#include <android-base/properties.h>
+
+#include <time.h>
+#include <utils/Timers.h>
+
+#include <cutils/klog.h>
+
+namespace hardware {
+namespace google {
+namespace pixel {
+namespace health {
+
+BatteryDefender::BatteryDefender(const char *pathChargeLevelStart, const char *pathChargeLevelStop,
+ const int32_t timeToActivateSecs,
+ const int32_t timeToClearTimerSecs)
+ : kPathChargeLevelStart(pathChargeLevelStart),
+ kPathChargeLevelStop(pathChargeLevelStop),
+ kTimeToActivateSecs(timeToActivateSecs),
+ kTimeToClearTimerSecs(timeToClearTimerSecs) {
+ mTimePreviousSecs = getTime();
+}
+
+void BatteryDefender::clearStateData(void) {
+ mHasReachedHighCapacityLevel = false;
+ mTimeActiveSecs = 0;
+ mTimeChargerNotPresentSecs = 0;
+ mTimeChargerPresentSecs = 0;
+}
+
+void BatteryDefender::loadPersistentStorage(void) {
+ if (mIsPowerAvailable) {
+ // Load accumulated time from persisted storage
+ mTimeChargerPresentSecs = readFileToInt(kPathPersistChargerPresentTime);
+ mTimeActiveSecs = readFileToInt(kPathPersistDefenderActiveTime);
+ }
+}
+
+int64_t BatteryDefender::getTime(void) {
+ return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
+}
+
+int64_t BatteryDefender::getDeltaTimeSeconds(int64_t *timeStartSecs) {
+ const int64_t timeCurrentSecs = getTime();
+ const int64_t timePreviousSecs = *timeStartSecs;
+ *timeStartSecs = timeCurrentSecs;
+ return timeCurrentSecs - timePreviousSecs;
+}
+
+void BatteryDefender::removeLineEndings(std::string *str) {
+ str->erase(std::remove(str->begin(), str->end(), '\n'), str->end());
+ str->erase(std::remove(str->begin(), str->end(), '\r'), str->end());
+}
+
+int BatteryDefender::readFileToInt(const char *path) {
+ std::string buffer;
+ int value = 0; // default
+ if (!android::base::ReadFileToString(path, &buffer)) {
+ LOG(ERROR) << "Failed to read " << path;
+ } else {
+ removeLineEndings(&buffer);
+ if (!android::base::ParseInt(buffer.c_str(), &value)) {
+ LOG(ERROR) << "Failed to parse " << path;
+ }
+ }
+
+ return value;
+}
+
+bool BatteryDefender::writeIntToFile(const char *path, const int value) {
+ bool success = android::base::WriteStringToFile(std::to_string(value), path);
+ if (!success) {
+ LOG(ERROR) << "Failed to write " << path;
+ }
+
+ return success;
+}
+
+void BatteryDefender::writeTimeToFile(const char *path, const int value, int64_t *previous) {
+ // 30 second delay before repeated writes
+ const bool hasTimeChangedSignificantly = ((value == 0) || (*previous == -1) ||
+ (value > *previous + 30) || (value < *previous - 30));
+ if ((value != *previous) && hasTimeChangedSignificantly) {
+ writeIntToFile(path, value);
+ *previous = value;
+ }
+}
+
+void BatteryDefender::writeChargeLevelsToFile(const int vendorStart, const int vendorStop) {
+ int chargeLevelStart = vendorStart;
+ int chargeLevelStop = vendorStop;
+ if (mCurrentState == STATE_ACTIVE) {
+ chargeLevelStart = kChargeLevelDefenderStart;
+ chargeLevelStop = kChargeLevelDefenderStop;
+ }
+
+ // Disable battery defender effects in charger mode until
+ // b/149598262 is resolved
+ if (android::base::GetProperty(kPropBootmode, "undefined") != "charger") {
+ if (chargeLevelStart != mChargeLevelStartPrevious) {
+ if (writeIntToFile(kPathChargeLevelStart, chargeLevelStart)) {
+ mChargeLevelStartPrevious = chargeLevelStart;
+ }
+ }
+ if (chargeLevelStop != mChargeLevelStopPrevious) {
+ if (writeIntToFile(kPathChargeLevelStop, chargeLevelStop)) {
+ mChargeLevelStopPrevious = chargeLevelStop;
+ }
+ }
+ }
+}
+
+bool BatteryDefender::isChargePowerAvailable(void) {
+ // USB presence is an indicator of connectivity
+ const bool chargerPresentWired = readFileToInt(kPathWiredChargerPresent) != 0;
+
+ // Wireless online is an indicator of a device having charge power
+ const bool chargerOnlineWireless = readFileToInt(kPathWirelessChargerOnline) != 0;
+
+ return chargerPresentWired || chargerOnlineWireless;
+}
+
+bool BatteryDefender::isDefaultChargeLevel(const int start, const int stop) {
+ return ((start == kChargeLevelDefaultStart) && (stop == kChargeLevelDefaultStop));
+}
+
+bool BatteryDefender::isBatteryDefenderDisabled(const int vendorStart, const int vendorStop) {
+ const bool isDefaultVendorChargeLevel = isDefaultChargeLevel(vendorStart, vendorStop);
+ const bool isExplicitlyDisabled =
+ android::base::GetBoolProperty(kPropBatteryDefenderDisable, false);
+ const bool isDebuggable = android::base::GetBoolProperty(kPropDebuggable, false);
+
+ return isExplicitlyDisabled || (isDefaultVendorChargeLevel == false) || (isDebuggable == false);
+}
+
+void BatteryDefender::addTimeToChargeTimers(void) {
+ if (mIsPowerAvailable) {
+ if (mHasReachedHighCapacityLevel) {
+ mTimeChargerPresentSecs += mTimeBetweenUpdateCalls;
+ }
+ mTimeChargerNotPresentSecs = 0;
+ } else {
+ mTimeChargerNotPresentSecs += mTimeBetweenUpdateCalls;
+ }
+}
+
+int32_t BatteryDefender::getTimeToActivate(void) {
+ // Use the default constructor value if the modified property is not between 60 and INT_MAX
+ // (seconds)
+ return android::base::GetIntProperty(kPropBatteryDefenderThreshold, kTimeToActivateSecs,
+ (int32_t)ONE_MIN_IN_SECONDS, INT32_MAX);
+}
+
+void BatteryDefender::stateMachine_runAction(const state_E state) {
+ switch (state) {
+ case STATE_INIT:
+ loadPersistentStorage();
+ break;
+
+ case STATE_DISABLED:
+ case STATE_DISCONNECTED:
+ clearStateData();
+ break;
+
+ case STATE_CONNECTED:
+ addTimeToChargeTimers();
+ if (readFileToInt(kPathBatteryCapacity) == kChargeHighCapacityLevel) {
+ mHasReachedHighCapacityLevel = true;
+ }
+ break;
+
+ case STATE_ACTIVE:
+ addTimeToChargeTimers();
+ mTimeActiveSecs += mTimeBetweenUpdateCalls;
+ break;
+
+ default:
+ break;
+ }
+
+ // Must be loaded after init has set the property
+ mTimeToActivateSecsModified = getTimeToActivate();
+}
+
+BatteryDefender::state_E BatteryDefender::stateMachine_getNextState(const state_E state) {
+ state_E nextState = state;
+
+ if (mIsDefenderDisabled) {
+ nextState = STATE_DISABLED;
+ } else {
+ switch (state) {
+ case STATE_INIT:
+ if (mIsPowerAvailable) {
+ if (mTimeChargerPresentSecs > mTimeToActivateSecsModified) {
+ nextState = STATE_ACTIVE;
+ } else {
+ nextState = STATE_CONNECTED;
+ }
+ } else {
+ nextState = STATE_DISCONNECTED;
+ }
+ break;
+
+ case STATE_DISABLED:
+ nextState = STATE_DISCONNECTED;
+ break;
+
+ case STATE_DISCONNECTED:
+ if (mIsPowerAvailable) {
+ nextState = STATE_CONNECTED;
+ }
+ break;
+
+ case STATE_CONNECTED:
+ if (mTimeChargerPresentSecs > mTimeToActivateSecsModified) {
+ nextState = STATE_ACTIVE;
+ } else if (mTimeChargerNotPresentSecs > kTimeToClearTimerSecs) {
+ nextState = STATE_DISCONNECTED;
+ }
+ break;
+
+ case STATE_ACTIVE:
+ // Latch unless disabled or unless the health module has restarted (ie. reboot)
+ default:
+ break;
+ }
+ }
+
+ return nextState;
+}
+
+// This will run once at the rising edge of a new state transition,
+// in addition to runAction()
+void BatteryDefender::stateMachine_firstAction(const state_E state) {
+ switch (state) {
+ case STATE_DISABLED:
+ clearStateData();
+ LOG(INFO) << "Disabled!";
+ break;
+
+ case STATE_DISCONNECTED:
+ clearStateData();
+ break;
+
+ case STATE_CONNECTED:
+ // Time already accumulated on state transition implies that there has
+ // already been a full charge cycle (this could happen on boot).
+ if (mTimeChargerPresentSecs > 0) {
+ mHasReachedHighCapacityLevel = true;
+ }
+ break;
+
+ case STATE_ACTIVE:
+ mHasReachedHighCapacityLevel = true;
+ LOG(INFO) << "Started with " << mTimeChargerPresentSecs
+ << " seconds of power availability!";
+ break;
+
+ case STATE_INIT:
+ default:
+ // No actions
+ break;
+ }
+}
+
+void BatteryDefender::update(void) {
+ // Update module inputs
+ const int chargeLevelVendorStart =
+ android::base::GetIntProperty(kPropChargeLevelVendorStart, kChargeLevelDefaultStart);
+ const int chargeLevelVendorStop =
+ android::base::GetIntProperty(kPropChargeLevelVendorStop, kChargeLevelDefaultStop);
+ mIsDefenderDisabled = isBatteryDefenderDisabled(chargeLevelVendorStart, chargeLevelVendorStop);
+ mIsPowerAvailable = isChargePowerAvailable();
+ mTimeBetweenUpdateCalls = getDeltaTimeSeconds(&mTimePreviousSecs);
+
+ // Run state machine
+ stateMachine_runAction(mCurrentState);
+ const state_E nextState = stateMachine_getNextState(mCurrentState);
+ if (nextState != mCurrentState) {
+ stateMachine_firstAction(nextState);
+ }
+ mCurrentState = nextState;
+
+ // Store outputs
+ writeTimeToFile(kPathPersistChargerPresentTime, mTimeChargerPresentSecs,
+ &mTimeChargerPresentSecsPrevious);
+ writeTimeToFile(kPathPersistDefenderActiveTime, mTimeActiveSecs, &mTimeActiveSecsPrevious);
+ writeChargeLevelsToFile(chargeLevelVendorStart, chargeLevelVendorStop);
+ android::base::SetProperty(kPropBatteryDefenderState, stateStringMap[mCurrentState]);
+}
+
+} // namespace health
+} // namespace pixel
+} // namespace google
+} // namespace hardware
diff --git a/health/include/pixelhealth/BatteryDefender.h b/health/include/pixelhealth/BatteryDefender.h
new file mode 100644
index 0000000..30512c2
--- /dev/null
+++ b/health/include/pixelhealth/BatteryDefender.h
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HARDWARE_GOOGLE_PIXEL_HEALTH_BATTERYDEFENDER_H
+#define HARDWARE_GOOGLE_PIXEL_HEALTH_BATTERYDEFENDER_H
+
+#include <stdbool.h>
+#include <time.h>
+#include <string>
+
+namespace hardware {
+namespace google {
+namespace pixel {
+namespace health {
+
+const uint32_t ONE_MIN_IN_SECONDS = 60;
+const uint32_t ONE_HOUR_IN_MINUTES = 60;
+const uint32_t ONE_DAY_IN_HOURS = 24;
+const uint32_t ONE_DAY_IN_SECONDS = ONE_DAY_IN_HOURS * ONE_HOUR_IN_MINUTES * ONE_MIN_IN_SECONDS;
+
+const uint32_t DEFAULT_TIME_TO_ACTIVATE_SECONDS = (14 * ONE_DAY_IN_SECONDS);
+const uint32_t DEFAULT_TIME_TO_CLEAR_SECONDS = (5 * ONE_MIN_IN_SECONDS);
+const int DEFAULT_CHARGE_LEVEL_START = 0;
+const int DEFAULT_CHARGE_LEVEL_STOP = 100;
+const int DEFAULT_CHARGE_LEVEL_DEFENDER_START = 60;
+const int DEFAULT_CHARGE_LEVEL_DEFENDER_STOP = 70;
+const int DEFAULT_CAPACITY_LEVEL = 100;
+
+class BatteryDefender {
+ public:
+ // Set default google charger paths - can be overridden for other devices
+ BatteryDefender(const char *pathChargeLevelStart =
+ "/sys/devices/platform/soc/soc:google,charger/charge_start_level",
+ const char *pathChargeLevelStop =
+ "/sys/devices/platform/soc/soc:google,charger/charge_stop_level",
+ const int32_t timeToActivateSecs = DEFAULT_TIME_TO_ACTIVATE_SECONDS,
+ const int32_t timeToClearTimerSecs = DEFAULT_TIME_TO_CLEAR_SECONDS);
+
+ // This function shall be called periodically in HealthService
+ void update(void);
+
+ private:
+ enum state_E {
+ STATE_INIT,
+ STATE_DISABLED,
+ STATE_DISCONNECTED,
+ STATE_CONNECTED,
+ STATE_ACTIVE,
+ STATE_COUNT,
+ };
+ const char *const stateStringMap[STATE_COUNT] = {
+ [STATE_INIT] = "INIT",
+ [STATE_DISABLED] = "DISABLED",
+ [STATE_DISCONNECTED] = "DISCONNECTED",
+ [STATE_CONNECTED] = "CONNECTED",
+ [STATE_ACTIVE] = "ACTIVE",
+ };
+
+ const char *const kPathChargeLevelStart;
+ const char *const kPathChargeLevelStop;
+ const int32_t kTimeToActivateSecs;
+ const int32_t kTimeToClearTimerSecs;
+
+ // Sysfs
+ const char *const kPathWirelessChargerOnline = "/sys/class/power_supply/wireless/online";
+ const char *const kPathWiredChargerPresent = "/sys/class/power_supply/usb/present";
+ const char *const kPathBatteryCapacity = "/sys/class/power_supply/battery/capacity";
+ const char *const kPathPersistChargerPresentTime =
+ "/mnt/vendor/persist/battery/defender_charger_time";
+ const char *const kPathPersistDefenderActiveTime =
+ "/mnt/vendor/persist/battery/defender_active_time";
+
+ // Properties
+ const char *const kPropChargeLevelVendorStart = "persist.vendor.charge.start.level";
+ const char *const kPropChargeLevelVendorStop = "persist.vendor.charge.stop.level";
+ const char *const kPropBatteryDefenderState = "vendor.battery.defender.state";
+ const char *const kPropBatteryDefenderDisable = "vendor.battery.defender.disable";
+ const char *const kPropBatteryDefenderThreshold = "vendor.battery.defender.threshold";
+ const char *const kPropDebuggable = "ro.debuggable";
+ const char *const kPropBootmode = "ro.bootmode";
+
+ // Default thresholds
+ const int kChargeLevelDefaultStart = DEFAULT_CHARGE_LEVEL_START;
+ const int kChargeLevelDefaultStop = DEFAULT_CHARGE_LEVEL_STOP;
+ const int kChargeLevelDefenderStart = DEFAULT_CHARGE_LEVEL_DEFENDER_START;
+ const int kChargeLevelDefenderStop = DEFAULT_CHARGE_LEVEL_DEFENDER_STOP;
+ const int kChargeHighCapacityLevel = DEFAULT_CAPACITY_LEVEL;
+
+ // Inputs
+ int64_t mTimeBetweenUpdateCalls = 0;
+ int64_t mTimePreviousSecs;
+ bool mIsPowerAvailable = false;
+ bool mIsDefenderDisabled = false;
+ int32_t mTimeToActivateSecsModified;
+
+ // State
+ state_E mCurrentState = STATE_INIT;
+ int64_t mTimeChargerPresentSecs = 0;
+ int64_t mTimeChargerPresentSecsPrevious = -1;
+ int64_t mTimeChargerNotPresentSecs = 0;
+ int64_t mTimeActiveSecs = 0;
+ int64_t mTimeActiveSecsPrevious = -1;
+ int mChargeLevelStartPrevious = DEFAULT_CHARGE_LEVEL_START;
+ int mChargeLevelStopPrevious = DEFAULT_CHARGE_LEVEL_STOP;
+ bool mHasReachedHighCapacityLevel = false;
+
+ void stateMachine_runAction(const state_E state); // Process state actions
+ state_E stateMachine_getNextState(const state_E state); // Check transitions
+ void stateMachine_firstAction(const state_E state); // Process entry actions
+
+ void clearStateData(void);
+ void loadPersistentStorage(void);
+ int64_t getTime(void);
+ int64_t getDeltaTimeSeconds(int64_t *timeStartSecs);
+ int32_t getTimeToActivate(void);
+ void removeLineEndings(std::string *str);
+ int readFileToInt(const char *path);
+ bool writeIntToFile(const char *path, const int value);
+ void writeTimeToFile(const char *path, const int value, int64_t *previous);
+ void writeChargeLevelsToFile(const int vendorStart, const int vendorStop);
+ bool isChargePowerAvailable(void);
+ bool isDefaultChargeLevel(const int start, const int stop);
+ bool isBatteryDefenderDisabled(const int vendorStart, const int vendorStop);
+ void addTimeToChargeTimers(void);
+};
+
+} // namespace health
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+
+#endif /* HARDWARE_GOOGLE_PIXEL_HEALTH_BATTERYDEFENDER_H */
diff --git a/health/include/pixelhealth/CycleCountBackupRestore.h b/health/include/pixelhealth/CycleCountBackupRestore.h
index bdac889..a646c65 100644
--- a/health/include/pixelhealth/CycleCountBackupRestore.h
+++ b/health/include/pixelhealth/CycleCountBackupRestore.h
@@ -35,7 +35,7 @@ class CycleCountBackupRestore {
void Backup(int battery_level);
private:
- const char *kPersistSerial = "/persist/battery/serial_number";
+ const char *kPersistSerial = "/mnt/vendor/persist/battery/serial_number";
int nb_buckets_;
int *sw_bins_;
diff --git a/health/test/TestBatteryDefender.cpp b/health/test/TestBatteryDefender.cpp
new file mode 100644
index 0000000..50656c6
--- /dev/null
+++ b/health/test/TestBatteryDefender.cpp
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "BatteryDefender.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <utils/Timers.h>
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+
+class HealthInterface {
+ public:
+ virtual ~HealthInterface() {}
+ virtual bool ReadFileToString(const std::string &path, std::string *content,
+ bool follow_symlinks);
+ virtual int GetIntProperty(const std::string &key, int default_value, int min, int max);
+ virtual bool GetBoolProperty(const std::string &key, bool default_value);
+ virtual bool SetProperty(const std::string &key, const std::string &value);
+ virtual bool WriteStringToFile(const std::string &content, const std::string &path,
+ bool follow_symlinks);
+};
+
+class HealthInterfaceMock : public HealthInterface {
+ public:
+ virtual ~HealthInterfaceMock() {}
+
+ MOCK_METHOD3(ReadFileToString,
+ bool(const std::string &path, std::string *content, bool follow_symlinks));
+ MOCK_METHOD4(GetIntProperty, int(const std::string &key, int default_value, int min, int max));
+ MOCK_METHOD2(GetBoolProperty, bool(const std::string &key, bool default_value));
+ MOCK_METHOD2(SetProperty, bool(const std::string &key, const std::string &value));
+ MOCK_METHOD3(WriteStringToFile,
+ bool(const std::string &content, const std::string &path, bool follow_symlinks));
+};
+
+HealthInterfaceMock *mock;
+
+namespace android {
+namespace base {
+
+bool ReadFileToString(const std::string &path, std::string *content, bool follow_symlinks) {
+ return mock->ReadFileToString(path, content, follow_symlinks);
+}
+
+bool WriteStringToFile(const std::string &content, const std::string &path, bool follow_symlinks) {
+ return mock->WriteStringToFile(content, path, follow_symlinks);
+}
+
+template <typename T>
+T GetIntProperty(const std::string &key, T default_value, T min, T max) {
+ return (T)(mock->GetIntProperty(key, default_value, min, max));
+}
+
+bool GetBoolProperty(const std::string &key, bool default_value) {
+ return mock->GetBoolProperty(key, default_value);
+}
+
+template int8_t GetIntProperty(const std::string &, int8_t, int8_t, int8_t);
+template int16_t GetIntProperty(const std::string &, int16_t, int16_t, int16_t);
+template int32_t GetIntProperty(const std::string &, int32_t, int32_t, int32_t);
+template int64_t GetIntProperty(const std::string &, int64_t, int64_t, int64_t);
+
+bool SetProperty(const std::string &key, const std::string &value) {
+ return mock->SetProperty(key, value);
+}
+
+} // namespace base
+} // namespace android
+
+nsecs_t testvar_systemTimeSecs = 0;
+nsecs_t systemTime(int clock) {
+ UNUSED(clock);
+ return seconds_to_nanoseconds(testvar_systemTimeSecs);
+}
+
+namespace hardware {
+namespace google {
+namespace pixel {
+namespace health {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::AtLeast;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+
+class BatteryDefenderTest : public ::testing::Test {
+ public:
+ BatteryDefenderTest() {}
+
+ void SetUp() {
+ mock = &mockFixture;
+
+ EXPECT_CALL(*mock, SetProperty(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*mock, ReadFileToString(_, _, _)).Times(AnyNumber());
+ EXPECT_CALL(*mock, GetIntProperty(_, _, _, _)).Times(AnyNumber());
+ EXPECT_CALL(*mock, GetBoolProperty(_, _)).Times(AnyNumber());
+ EXPECT_CALL(*mock, WriteStringToFile(_, _, _)).Times(AnyNumber());
+
+ ON_CALL(*mock, ReadFileToString(_, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::string("0")), Return(true)));
+
+ ON_CALL(*mock, WriteStringToFile(_, _, _)).WillByDefault(Return(true));
+ }
+
+ void TearDown() {}
+
+ private:
+ HealthInterfaceMock mockFixture;
+};
+
+const char *kPathWirelessChargerOnline = "/sys/class/power_supply/wireless/online";
+const char *kPathWiredChargerPresent = "/sys/class/power_supply/usb/present";
+const char *kPathBatteryCapacity = "/sys/class/power_supply/battery/capacity";
+const char *kPathPersistChargerPresentTime = "/mnt/vendor/persist/battery/defender_charger_time";
+const char *kPathPersistDefenderActiveTime = "/mnt/vendor/persist/battery/defender_active_time";
+const char *kPathStartLevel = "/sys/devices/platform/soc/soc:google,charger/charge_start_level";
+const char *kPathStopLevel = "/sys/devices/platform/soc/soc:google,charger/charge_stop_level";
+
+const char *kPropChargeLevelVendorStart = "persist.vendor.charge.start.level";
+const char *kPropChargeLevelVendorStop = "persist.vendor.charge.stop.level";
+const char *kPropBatteryDefenderState = "vendor.battery.defender.state";
+const char *kPropBatteryDefenderDisable = "vendor.battery.defender.disable";
+const char *kPropBatteryDefenderThreshold = "vendor.battery.defender.threshold";
+const char *kPropDebuggable = "ro.debuggable";
+
+static void enableDefender(void) {
+ ON_CALL(*mock, GetIntProperty(kPropChargeLevelVendorStart, _, _, _)).WillByDefault(Return(0));
+ ON_CALL(*mock, GetIntProperty(kPropChargeLevelVendorStop, _, _, _)).WillByDefault(Return(100));
+ ON_CALL(*mock, GetBoolProperty(kPropBatteryDefenderDisable, _)).WillByDefault(Return(false));
+ ON_CALL(*mock, GetBoolProperty(kPropDebuggable, _)).WillByDefault(Return(true));
+}
+
+static void powerAvailable(void) {
+ ON_CALL(*mock, ReadFileToString(kPathWirelessChargerOnline, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::string("1")), Return(true)));
+ ON_CALL(*mock, ReadFileToString(kPathWiredChargerPresent, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::string("1")), Return(true)));
+}
+
+static void defaultThreshold(void) {
+ ON_CALL(*mock, GetIntProperty(kPropBatteryDefenderThreshold, _, _, _))
+ .WillByDefault(Return(DEFAULT_TIME_TO_ACTIVATE_SECONDS));
+}
+
+static void capacityReached(void) {
+ ON_CALL(*mock, ReadFileToString(kPathBatteryCapacity, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::to_string(100)), Return(true)));
+}
+
+static void initToConnectedCapacityReached(void) {
+ ON_CALL(*mock, ReadFileToString(kPathPersistChargerPresentTime, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::to_string(1000)), Return(true)));
+}
+
+static void initToActive(void) {
+ ON_CALL(*mock, ReadFileToString(kPathPersistChargerPresentTime, _, _))
+ .WillByDefault(
+ DoAll(SetArgPointee<1>(std::to_string(DEFAULT_TIME_TO_ACTIVATE_SECONDS + 1)),
+ Return(true)));
+}
+
+TEST_F(BatteryDefenderTest, EnableAndDisconnected) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ // No power
+
+ // Enable Battery Defender
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "DISCONNECTED"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, DisableNonDefaultLevels) {
+ BatteryDefender battDefender;
+
+ // Enable Battery Defender
+ EXPECT_CALL(*mock, GetIntProperty(kPropChargeLevelVendorStart, _, _, _)).WillOnce(Return(30));
+ EXPECT_CALL(*mock, GetIntProperty(kPropChargeLevelVendorStop, _, _, _)).WillOnce(Return(35));
+
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "DISABLED"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, DisableDebuggable) {
+ BatteryDefender battDefender;
+
+ // Enable Battery Defender
+ EXPECT_CALL(*mock, GetBoolProperty(kPropDebuggable, _)).WillOnce(Return(false));
+
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "DISABLED"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, DisableExplicit) {
+ BatteryDefender battDefender;
+
+ // Enable Battery Defender
+ EXPECT_CALL(*mock, GetBoolProperty(kPropBatteryDefenderDisable, _)).WillOnce(Return(true));
+
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "DISABLED"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, InitActive) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ powerAvailable();
+ defaultThreshold();
+
+ EXPECT_CALL(*mock, ReadFileToString(kPathPersistChargerPresentTime, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(std::to_string(DEFAULT_TIME_TO_ACTIVATE_SECONDS + 1)),
+ Return(true)));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "ACTIVE"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, InitConnectedCapacityReached) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ powerAvailable();
+ defaultThreshold();
+
+ InSequence s;
+
+ EXPECT_CALL(*mock, ReadFileToString(kPathPersistChargerPresentTime, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(std::to_string(DEFAULT_TIME_TO_ACTIVATE_SECONDS - 1)),
+ Return(true)));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ battDefender.update();
+
+ testvar_systemTimeSecs++;
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(DEFAULT_TIME_TO_ACTIVATE_SECONDS),
+ kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, InitConnected) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ powerAvailable();
+ defaultThreshold();
+
+ InSequence s;
+
+ EXPECT_CALL(*mock, ReadFileToString(kPathPersistChargerPresentTime, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(std::to_string(0)), Return(true)));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ battDefender.update();
+
+ // mHasReachedHighCapacityLevel shall be false
+ testvar_systemTimeSecs += DEFAULT_TIME_TO_ACTIVATE_SECONDS + 1;
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, TriggerTime) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ powerAvailable();
+ defaultThreshold();
+
+ InSequence s;
+
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 1;
+ battDefender.update();
+
+ // Reached 100% capacity at least once
+ EXPECT_CALL(*mock, ReadFileToString(kPathBatteryCapacity, _, _))
+ .WillOnce(DoAll(SetArgPointee<1>(std::to_string(100)), Return(true)));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 1;
+ battDefender.update();
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(DEFAULT_TIME_TO_ACTIVATE_SECONDS),
+ kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += DEFAULT_TIME_TO_ACTIVATE_SECONDS;
+ battDefender.update();
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(DEFAULT_TIME_TO_ACTIVATE_SECONDS + 1),
+ kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "ACTIVE"));
+ testvar_systemTimeSecs += 1;
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, ChargeLevels) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ powerAvailable();
+ defaultThreshold();
+ initToConnectedCapacityReached();
+
+ InSequence s;
+
+ // No expectations needed; default values already set
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 0;
+ battDefender.update();
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(60), kPathStartLevel, _));
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(70), kPathStopLevel, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "ACTIVE"));
+ testvar_systemTimeSecs += DEFAULT_TIME_TO_ACTIVATE_SECONDS + 1;
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, ActiveTime) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ powerAvailable();
+ defaultThreshold();
+ initToActive();
+
+ InSequence s;
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(60), kPathStartLevel, _));
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(70), kPathStopLevel, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "ACTIVE"));
+ battDefender.update();
+}
+
+TEST_F(BatteryDefenderTest, ConnectDisconnectCycle) {
+ BatteryDefender battDefender;
+
+ enableDefender();
+ defaultThreshold();
+ initToConnectedCapacityReached();
+
+ InSequence s;
+
+ // Power ON
+ ON_CALL(*mock, ReadFileToString(kPathWirelessChargerOnline, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::string("1")), Return(true)));
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(1000), kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60;
+ battDefender.update();
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(1060), kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60;
+ battDefender.update();
+
+ // Power OFF
+ ON_CALL(*mock, ReadFileToString(kPathWirelessChargerOnline, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::string("0")), Return(true)));
+
+ // Maintain kPathPersistChargerPresentTime = 1060
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60;
+ battDefender.update();
+
+ // Maintain kPathPersistChargerPresentTime = 1060
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60 * 4;
+ battDefender.update();
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(0), kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "DISCONNECTED"));
+ testvar_systemTimeSecs += 1;
+ battDefender.update();
+
+ // Power ON
+ ON_CALL(*mock, ReadFileToString(kPathWirelessChargerOnline, _, _))
+ .WillByDefault(DoAll(SetArgPointee<1>(std::string("1")), Return(true)));
+
+ // Maintain kPathPersistChargerPresentTime = 0
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60;
+ battDefender.update();
+
+ capacityReached();
+ // Maintain kPathPersistChargerPresentTime = 0
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60;
+ battDefender.update();
+
+ EXPECT_CALL(*mock, WriteStringToFile(std::to_string(60), kPathPersistChargerPresentTime, _));
+ EXPECT_CALL(*mock, SetProperty(kPropBatteryDefenderState, "CONNECTED"));
+ testvar_systemTimeSecs += 60;
+ battDefender.update();
+}
+
+} // namespace health
+} // namespace pixel
+} // namespace google
+} // namespace hardware
diff --git a/mm/Android.bp b/mm/Android.bp
new file mode 100644
index 0000000..f733d7e
--- /dev/null
+++ b/mm/Android.bp
@@ -0,0 +1,8 @@
+sh_binary {
+ name: "mm_logd",
+ src: "init.mm.logging.sh",
+ vendor: true,
+ init_rc: [
+ "pixel-mm-logd.rc",
+ ],
+}
diff --git a/mm/device.mk b/mm/device.mk
new file mode 100644
index 0000000..150410e
--- /dev/null
+++ b/mm/device.mk
@@ -0,0 +1,9 @@
+PRODUCT_COPY_FILES += \
+ hardware/google/pixel/mm/pixel-mm.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/pixel-mm.rc
+
+ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+PRODUCT_PACKAGES += \
+ mm_logd
+endif
+
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/mm
diff --git a/mm/device_legacy.mk b/mm/device_legacy.mk
new file mode 100644
index 0000000..140fb85
--- /dev/null
+++ b/mm/device_legacy.mk
@@ -0,0 +1,9 @@
+PRODUCT_COPY_FILES += \
+ hardware/google/pixel/mm/pixel-mm-legacy.rc:$(TARGET_COPY_OUT_VENDOR)/etc/init/pixel-mm-legacy.rc
+
+ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+PRODUCT_PACKAGES += \
+ mm_logd
+endif
+
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/mm
diff --git a/mm/init.mm.logging.sh b/mm/init.mm.logging.sh
new file mode 100755
index 0000000..38c54e0
--- /dev/null
+++ b/mm/init.mm.logging.sh
@@ -0,0 +1,34 @@
+#!/vendor/bin/sh
+
+if [ $# -eq 1 ]; then
+ interval=$1
+else
+ exit 1
+fi
+
+kcompactd_pid=`pidof -x kcompactd0`
+kswapd_pid=`pidof -x kswapd0`
+
+while true
+do
+ log_time=`date '+%m-%d-%H-%M-%S'`
+
+ log_vmstat=`cat /proc/vmstat`
+ log_kcompactd=`cat /proc/$kcompactd_pid/stat`
+ log_kswapd=`cat /proc/$kswapd_pid/stat`
+ log_stat=`cat /proc/stat`
+
+ log_line="$log_time $log_vmstat"
+ echo $log_line >> /data/vendor/mm/vmstat/log
+
+ log_line="$log_time $log_kcompactd"
+ echo $log_line >> /data/vendor/mm/kcompactd/log
+
+ log_line="$log_time $log_kswapd"
+ echo $log_line >> /data/vendor/mm/kswapd/log
+
+ log_line="$log_time $log_stat"
+ echo $log_line >> /data/vendor/mm/stat/log
+
+ sleep $interval
+done
diff --git a/mm/pixel-mm-legacy.rc b/mm/pixel-mm-legacy.rc
new file mode 100644
index 0000000..67b619a
--- /dev/null
+++ b/mm/pixel-mm-legacy.rc
@@ -0,0 +1,42 @@
+#
+# Copyright (C) 2020 The Android Open-Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+on property:sys.boot_completed=1
+ # Create mm_event trace point.
+ # For legacy devices, only mm_event is using this trace instance.
+ # Debugfs is only used in legacy devices and going to be deprecated.
+ # If others want to put more,it should get hard review from pixel-perf-team.
+ mkdir /sys/kernel/debug/tracing/instances/pixel-trace 0755 system system
+ chown system system /sys/kernel/debug/tracing/instances/pixel-trace/trace
+ chmod 0660 /sys/kernel/debug/tracing/instances/pixel-trace/trace
+ chown system system /sys/kernel/debug/tracing/instances/pixel-trace/tracing_on
+ chmod 0660 /sys/kernel/debug/tracing/instances/pixel-trace/tracing_on
+ write /sys/kernel/debug/tracing/instances/pixel-trace/buffer_size_kb 64
+ write /sys/kernel/debug/tracing/instances/pixel-trace/events/mm_event/enable 1
+ write /sys/kernel/debug/tracing/instances/pixel-trace/events/f2fs/f2fs_iostat/enable 1
+ write /sys/kernel/debug/tracing/instances/pixel-trace/events/ufs/ufs_stats/enable 1
+
+# turns off tracing right before bugreporting to keep more traces
+on property:init.svc.dumpstatez=running
+ write /d/tracing/instances/pixel-trace/tracing_on 0
+
+on property:init.svc.dumpstatez=stopped
+ write /d/tracing/instances/pixel-trace/tracing_on 1
+
+on property:init.svc.bugreport=running
+ write /d/tracing/instances/pixel-trace/tracing_on 0
+
+on property:init.svc.bugreport=stopped
+ write /d/tracing/instances/pixel-trace/tracing_on 1
diff --git a/mm/pixel-mm-logd.rc b/mm/pixel-mm-logd.rc
new file mode 100644
index 0000000..40bcbe4
--- /dev/null
+++ b/mm/pixel-mm-logd.rc
@@ -0,0 +1,19 @@
+on property:persist.vendor.log.mm=1
+ mkdir /data/vendor/mm 0700 root system
+ mkdir /data/vendor/mm/vmstat 0700 root system
+ mkdir /data/vendor/mm/stat 0700 root system
+ mkdir /data/vendor/mm/kswapd 0700 root system
+ mkdir /data/vendor/mm/kcompactd 0700 root system
+ start vendor.mm.logd
+
+on property:persist.vendor.log.mm=0
+ stop vendor.mm.logd
+
+on property:persist.vendor.log.mm=1 && property:persist.vendor.log.mm.interval=*
+ restart vendor.mm.logd
+
+service vendor.mm.logd /vendor/bin/mm_logd ${persist.vendor.log.mm.interval:-60}
+ class main
+ user root
+ group root system
+ disabled
diff --git a/mm/pixel-mm.rc b/mm/pixel-mm.rc
new file mode 100644
index 0000000..8523fc1
--- /dev/null
+++ b/mm/pixel-mm.rc
@@ -0,0 +1,41 @@
+#
+# Copyright (C) 2020 The Android Open-Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+on property:sys.boot_completed=1
+ # Create mm_event trace point.
+ # Currently, only mm_event is using this trace instance.
+ # If others want to put more,it should get hard review from pixel-perf-team.
+ mkdir /sys/kernel/tracing/instances/pixel-trace 0755 system system
+ chown system system /sys/kernel/tracing/instances/pixel-trace/trace
+ chmod 0660 /sys/kernel/tracing/instances/pixel-trace/trace
+ chown system system /sys/kernel/tracing/instances/pixel-trace/tracing_on
+ chmod 0660 /sys/kernel/tracing/instances/pixel-trace/tracing_on
+ write /sys/kernel/tracing/instances/pixel-trace/buffer_size_kb 64
+ write /sys/kernel/tracing/instances/pixel-trace/events/mm_event/enable 1
+ write /sys/kernel/tracing/instances/pixel-trace/events/f2fs/f2fs_iostat/enable 1
+ write /sys/kernel/tracing/instances/pixel-trace/events/ufs/ufs_stats/enable 1
+
+# turns off tracing right before bugreporting to keep more traces
+on property:init.svc.dumpstatez=running
+ write /sys/kernel/tracing/instances/pixel-trace/tracing_on 0
+
+on property:init.svc.dumpstatez=stopped
+ write /sys/kernel/tracing/instances/pixel-trace/tracing_on 1
+
+on property:init.svc.bugreport=running
+ write /sys/kernel/tracing/instances/pixel-trace/tracing_on 0
+
+on property:init.svc.bugreport=stopped
+ write /sys/kernel/tracing/instances/pixel-trace/tracing_on 1
diff --git a/pixelstats/Android.bp b/pixelstats/Android.bp
index d0627aa..7f2ee61 100644
--- a/pixelstats/Android.bp
+++ b/pixelstats/Android.bp
@@ -44,8 +44,11 @@ cc_library {
srcs: [
"DropDetect.cpp",
+ "OrientationCollector.cpp",
"SysfsCollector.cpp",
"UeventListener.cpp",
+ "WlcReporter.cpp",
+ "BatteryCapacityReporter.cpp",
],
cflags: [
"-Wall",
@@ -59,6 +62,7 @@ cc_library {
"libhidlbase",
"liblog",
"libutils",
+ "libsensorndkbridge",
"pixelatoms-cpp",
],
export_shared_lib_headers: [
diff --git a/pixelstats/BatteryCapacityReporter.cpp b/pixelstats/BatteryCapacityReporter.cpp
new file mode 100644
index 0000000..9b2995f
--- /dev/null
+++ b/pixelstats/BatteryCapacityReporter.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "pixelstats-uevent: BatteryCapacityFG"
+
+#include <log/log.h>
+#include <time.h>
+#include <utils/Timers.h>
+#include <cmath>
+
+#include <android-base/file.h>
+
+#include <android/frameworks/stats/1.0/IStats.h>
+#include <pixelstats/BatteryCapacityReporter.h>
+
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+using android::base::ReadFileToString;
+using android::frameworks::stats::V1_0::IStats;
+using android::frameworks::stats::V1_0::VendorAtom;
+using android::hardware::google::pixel::PixelAtoms::BatteryCapacityFG;
+
+BatteryCapacityReporter::BatteryCapacityReporter() {
+ // Remove the need for a translation function/table, while removing the dependency on the
+ // generated <pixelatoms.pb.h> in BatteryCapacityReporter.h.
+ static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_UNKNOWN) ==
+ static_cast<int>(BatteryCapacityFG::LOG_REASON_UNKNOWN));
+ static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_CONNECTED) ==
+ static_cast<int>(BatteryCapacityFG::LOG_REASON_CONNECTED));
+ static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_DISCONNECTED) ==
+ static_cast<int>(BatteryCapacityFG::LOG_REASON_DISCONNECTED));
+ static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_FULL_CHARGE) ==
+ static_cast<int>(BatteryCapacityFG::LOG_REASON_FULL_CHARGE));
+ static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_PERCENT_SKIP) ==
+ static_cast<int>(BatteryCapacityFG::LOG_REASON_PERCENT_SKIP));
+ static_assert(static_cast<int>(BatteryCapacityReporter::LOG_REASON_DIVERGING_FG) ==
+ static_cast<int>(BatteryCapacityFG::LOG_REASON_DIVERGING_FG));
+}
+
+void BatteryCapacityReporter::checkAndReport(const std::string &path) {
+ if (parse(path)) {
+ if (check()) {
+ report();
+ }
+ }
+}
+
+int64_t BatteryCapacityReporter::getTimeSecs(void) {
+ return nanoseconds_to_seconds(systemTime(SYSTEM_TIME_BOOTTIME));
+}
+
+bool BatteryCapacityReporter::parse(const std::string &path) {
+ std::string batterySSOCContents;
+ if (!ReadFileToString(path, &batterySSOCContents)) {
+ ALOGE("Unable to read ssoc_details path: %s - %s", path.c_str(), strerror(errno));
+ return false;
+ }
+
+ // Parse file. Example format:
+ // soc: l=97% gdf=97.72 uic=97.72 rl=97.72
+ // curve:[15.00 15.00][97.87 97.87][100.00 100.00]
+ // status: ct=1 rl=0 s=1
+ if (sscanf(batterySSOCContents.c_str(),
+ "soc: %*s gdf=%f %*s rl=%f\n"
+ "curve:[%*f %*f][%f %f][%*f %*f]\n"
+ "status: %*s %*s s=%d",
+ &gdf_, &ssoc_, &gdf_curve_, &ssoc_curve_, &status_) != 5) {
+ ALOGE("Unable to parse ssoc_details [%s] from file %s to int.", batterySSOCContents.c_str(),
+ path.c_str());
+ return false;
+ }
+
+ return true;
+}
+
+bool BatteryCapacityReporter::check(void) {
+ if (unexpected_event_timer_active_) {
+ // A 30 minute timer with a boolean gate helps prevent uninitialized timers and potential
+ // overflows.
+ // - Active when the timer is less than 30 minutes, thus continues checking the elapsed
+ // time.
+ // - Once expired (> 30 min), active becomes false and the timer no longer needs to check
+ // the elapsed time.
+ unexpected_event_timer_active_ =
+ (getTimeSecs() - unexpected_event_timer_secs_) <= (30 * 60);
+ }
+
+ LogReason log_reason = LOG_REASON_UNKNOWN;
+ if (status_previous_ != status_) {
+ // Handle nominal events
+
+ if (status_ == SOC_STATUS_CONNECTED) {
+ log_reason = LOG_REASON_CONNECTED;
+
+ } else if (status_ == SOC_STATUS_DISCONNECTED) {
+ log_reason = LOG_REASON_DISCONNECTED;
+
+ } else if (status_ == SOC_STATUS_FULL) {
+ log_reason = LOG_REASON_FULL_CHARGE;
+ }
+
+ status_previous_ = status_;
+
+ } else if (unexpected_event_timer_active_ == false) {
+ // Handle abnormal events at a minimum period
+
+ const float diff = fabsf(ssoc_ - gdf_);
+
+ if (fabsf(ssoc_ - ssoc_previous_) >= 2.0f) {
+ unexpected_event_timer_secs_ = getTimeSecs();
+ unexpected_event_timer_active_ = true;
+ log_reason = LOG_REASON_PERCENT_SKIP;
+
+ // Every +- 1% when above a 4% SOC difference (w/ timer)
+ } else if (static_cast<int>(round(ssoc_gdf_diff_previous_)) !=
+ static_cast<int>(round(diff)) &&
+ diff >= 4.0f) {
+ unexpected_event_timer_secs_ = getTimeSecs();
+ unexpected_event_timer_active_ = true;
+ log_reason = LOG_REASON_DIVERGING_FG;
+
+ ssoc_gdf_diff_previous_ = diff;
+ }
+ }
+ ssoc_previous_ = ssoc_;
+
+ log_reason_ = log_reason;
+ return (log_reason != LOG_REASON_UNKNOWN);
+}
+
+void BatteryCapacityReporter::report(void) {
+ sp<IStats> stats_client = IStats::tryGetService();
+ if (!stats_client) {
+ ALOGD("Couldn't connect to IStats service");
+ return;
+ }
+
+ // Load values array
+ std::vector<VendorAtom::Value> values(5);
+ VendorAtom::Value tmp;
+ tmp.intValue(log_reason_);
+ values[BatteryCapacityFG::kCapacityLogReasonFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.floatValue(gdf_);
+ values[BatteryCapacityFG::kCapacityGdfFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.floatValue(ssoc_);
+ values[BatteryCapacityFG::kCapacitySsocFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.floatValue(gdf_curve_);
+ values[BatteryCapacityFG::kCapacityGdfCurveFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.floatValue(ssoc_curve_);
+ values[BatteryCapacityFG::kCapacitySsocCurveFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::FG_CAPACITY,
+ .values = values};
+ Return<void> ret = stats_client->reportVendorAtom(event);
+ if (!ret.isOk())
+ ALOGE("Unable to report to IStats service");
+}
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
diff --git a/pixelstats/DropDetect.cpp b/pixelstats/DropDetect.cpp
index 5d51b76..1b1bb1c 100644
--- a/pixelstats/DropDetect.cpp
+++ b/pixelstats/DropDetect.cpp
@@ -24,6 +24,7 @@
#include <log/log.h>
#include <inttypes.h>
+#include <math.h>
using android::sp;
using android::chre::HostProtocolHost;
@@ -42,7 +43,7 @@ namespace pixel {
namespace { // anonymous namespace for file-local definitions
-// This struct is defined in nanoapps/drop/messaging.h
+// The following two structs are defined in nanoapps/drop/messaging.h
// by the DropDetect nanoapp.
struct __attribute__((__packed__)) DropEventPayload {
float confidence;
@@ -50,6 +51,13 @@ struct __attribute__((__packed__)) DropEventPayload {
int32_t free_fall_duration_ns;
};
+struct __attribute__((__packed__)) DropEventPayloadV2 {
+ uint64_t free_fall_duration_ns;
+ float impact_accel_x;
+ float impact_accel_y;
+ float impact_accel_z;
+};
+
// This enum is defined in nanoapps/drop/messaging.h
// by the DropDetect nanoapp.
enum DropConstants {
@@ -58,6 +66,7 @@ enum DropConstants {
kDropDisableRequest = 3,
kDropDisableNotification = 4,
kDropEventDetection = 5,
+ kDropEventDetectionV2 = 6,
};
void requestNanoappList(SocketClient &client) {
@@ -113,36 +122,78 @@ void DropDetect::handleNanoappListResponse(const fbs::NanoappListResponseT &resp
ALOGE("Drop Detect app not found");
}
-/**
- * listen for messages from the DropDetect nanoapp and report them to
- * PixelStats.
- */
-void DropDetect::handleNanoappMessage(const fbs::NanoappMessageT &message) {
- struct DropEventPayload *message_struct;
-
- if (message.app_id != kDropDetectAppId || message.message_type != kDropEventDetection ||
- message.message.size() < sizeof(struct DropEventPayload))
- return;
- message_struct = (struct DropEventPayload *)&message.message[0];
+static PhysicalDropDetected dropEventFromNanoappPayload(const struct DropEventPayload *p) {
ALOGI("Received drop detect message! Confidence %f Peak %f Duration %g",
- message_struct->confidence, message_struct->accel_magnitude_peak,
- message_struct->free_fall_duration_ns / 1e9);
- uint8_t confidence = message_struct->confidence * 100;
+ p->confidence, p->accel_magnitude_peak, p->free_fall_duration_ns / 1e9);
+
+ uint8_t confidence = p->confidence * 100;
confidence = std::min<int>(confidence, 100);
confidence = std::max<int>(0, confidence);
+ int32_t accel_magnitude_peak_1000ths_g = p->accel_magnitude_peak * 1000.0;
+ int32_t free_fall_duration_ms = p->free_fall_duration_ns / 1000000;
- int32_t accel_magnitude_peak_1000ths_g = message_struct->accel_magnitude_peak * 1000.0;
- int32_t free_fall_duration_ms = message_struct->free_fall_duration_ns / 1000000;
+ return PhysicalDropDetected{ confidence,
+ accel_magnitude_peak_1000ths_g,
+ free_fall_duration_ms };
+}
+
+static PhysicalDropDetected dropEventFromNanoappPayload(const struct DropEventPayloadV2 *p) {
+ ALOGI("Received drop detect message: "
+ "duration %g ms, impact acceleration: x = %f, y = %f, z = %f",
+ p->free_fall_duration_ns / 1e6,
+ p->impact_accel_x,
+ p->impact_accel_y,
+ p->impact_accel_z);
+
+ float impact_magnitude = sqrt(p->impact_accel_x * p->impact_accel_x +
+ p->impact_accel_y * p->impact_accel_y +
+ p->impact_accel_z * p->impact_accel_z);
+ /* Scale impact magnitude as percentage between [50, 100] m/s2. */
+ constexpr float min_confidence_magnitude = 50;
+ constexpr float max_confidence_magnitude = 100;
+ uint8_t confidence_percentage =
+ impact_magnitude < min_confidence_magnitude ? 0 :
+ impact_magnitude > max_confidence_magnitude ? 100 :
+ (impact_magnitude - min_confidence_magnitude) /
+ (max_confidence_magnitude - min_confidence_magnitude) * 100;
+
+ int32_t free_fall_duration_ms = static_cast<int32_t>(p->free_fall_duration_ns / 1000000);
+
+ return PhysicalDropDetected{
+ .confidencePctg = confidence_percentage,
+ .accelPeak = static_cast<int32_t>(impact_magnitude * 1000),
+ .freefallDuration = free_fall_duration_ms,
+ };
+}
+static void reportDropEventToStatsd(const PhysicalDropDetected& drop) {
sp<IStats> stats_client = IStats::tryGetService();
- if (stats_client) {
- PhysicalDropDetected drop = {confidence, accel_magnitude_peak_1000ths_g,
- free_fall_duration_ms};
+ if (!stats_client) {
+ ALOGE("Unable to connect to Stats service");
+ } else {
Return<void> ret = stats_client->reportPhysicalDropDetected(drop);
if (!ret.isOk())
ALOGE("Unable to report physical drop to Stats service");
- } else
- ALOGE("Unable to connect to Stats service");
+ }
+}
+
+/**
+ * listen for messages from the DropDetect nanoapp and report them to
+ * PixelStats.
+ */
+void DropDetect::handleNanoappMessage(const fbs::NanoappMessageT &message) {
+ if (message.app_id != kDropDetectAppId)
+ return;
+
+ if (message.message_type == kDropEventDetection &&
+ message.message.size() >= sizeof(struct DropEventPayload)) {
+ reportDropEventToStatsd(dropEventFromNanoappPayload(
+ reinterpret_cast<const struct DropEventPayload *>(&message.message[0])));
+ } else if (message.message_type == kDropEventDetectionV2 &&
+ message.message.size() >= sizeof(struct DropEventPayloadV2)) {
+ reportDropEventToStatsd(dropEventFromNanoappPayload(
+ reinterpret_cast<const struct DropEventPayloadV2 *>(&message.message[0])));
+ }
}
} // namespace pixel
diff --git a/pixelstats/OWNERS b/pixelstats/OWNERS
index 98935da..c0767dd 100644
--- a/pixelstats/OWNERS
+++ b/pixelstats/OWNERS
@@ -1,2 +1,3 @@
-maggiewhite@google.com
+tstrudel@google.com
achant@google.com
+per-file pixelatoms.proto = yro@google.com
diff --git a/pixelstats/OrientationCollector.cpp b/pixelstats/OrientationCollector.cpp
new file mode 100644
index 0000000..2647a06
--- /dev/null
+++ b/pixelstats/OrientationCollector.cpp
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "pixelstats-orientation"
+
+#include <android/sensor.h>
+#include <log/log.h>
+#include <pixelstats/OrientationCollector.h>
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+#define GET_EVENT_TIMEOUT_MILLIS 200
+#define SENSOR_TYPE_DEVICE_ORIENTATION 27
+#define ORIENTATION_UNKNOWN -1
+
+sp<OrientationCollector> OrientationCollector::createOrientationCollector() {
+ sp<OrientationCollector> orientationCollector;
+ orientationCollector = new OrientationCollector();
+ if (orientationCollector != nullptr && orientationCollector->init() < 0) {
+ orientationCollector->disableOrientationSensor();
+ return nullptr;
+ }
+ return orientationCollector;
+}
+
+/* pollOrientation
+ * orientation: [out] orientation value from sensor Hal.
+ * Return: OK, or error code from sensor Hal
+ */
+int32_t OrientationCollector::pollOrientation(int *orientation) {
+ int err = OK;
+ int eventCount = 0;
+ ASensorEvent sensorEvent;
+
+ *orientation = ORIENTATION_UNKNOWN;
+ /* Get sensor events. */
+ /* Get sensor sample events. */
+ err = getEvents(&sensorEvent, 1, &eventCount);
+ if (eventCount > 0) {
+ ALOGV("%s: ##event data: %f,%f,%f", __func__, sensorEvent.data[0], sensorEvent.data[1],
+ sensorEvent.data[2]);
+ *orientation = sensorEvent.data[0];
+ }
+ return err;
+}
+
+/*
+ * Collects sensor samples and returns them in the sensor sample event list
+ * specified by event_list. Limits the number of returned sample events to the
+ * value specified by event_list_size. Returns the number of returned sample
+ * events in p_event_count.
+ *
+ * event_list List of collected sample events.
+ * event_list_size Size of sample event list.
+ * event_count Returned count of the number of collected sample
+ * events.
+ */
+int OrientationCollector::getEvents(ASensorEvent *event_list, size_t event_list_size,
+ int *event_count) {
+ int rv;
+ int err = OK;
+
+ /* Wait for a sensor event to be available */
+ /* The timeout is used to prevent blocking for long time, when sensor pool is
+ * empty, for example the device is put on a horizontal wireless charger.
+ */
+ rv = ALooper_pollOnce(GET_EVENT_TIMEOUT_MILLIS, nullptr, nullptr, nullptr);
+ if (rv == ALOOPER_POLL_ERROR) {
+ ALOGI("Sensor event looper returned a poll error.\n");
+ err = UNKNOWN_ERROR;
+ goto out;
+ }
+
+ /* Get sensor events. */
+ *event_count = ASensorEventQueue_getEvents(mQueue, event_list, event_list_size);
+
+out:
+ return err;
+}
+
+int32_t OrientationCollector::init() {
+ int err = OK;
+ ALooper *looper = nullptr;
+
+ // Get orientation sensor events from the NDK
+ mSensorManager = ASensorManager_getInstanceForPackage(nullptr);
+ if (mSensorManager == nullptr) {
+ ALOGE("%s: Unable to get sensorManager.\n", __func__);
+ return UNKNOWN_ERROR;
+ }
+ looper = ALooper_forThread();
+ if (looper == nullptr) {
+ looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+ }
+ if (looper == nullptr) {
+ ALOGE("%s: Failed to prepare an event looper.\n", __FUNCTION__);
+ return UNKNOWN_ERROR;
+ }
+ mQueue = ASensorManager_createEventQueue(mSensorManager, looper, 0, nullptr, nullptr);
+ mOrientationSensor =
+ ASensorManager_getDefaultSensor(mSensorManager, SENSOR_TYPE_DEVICE_ORIENTATION);
+ if (mOrientationSensor == nullptr) {
+ ALOGE("%s: Unable to get orientation sensor.\n", __func__);
+ return UNKNOWN_ERROR;
+ }
+ err = ASensorEventQueue_registerSensor(mQueue, mOrientationSensor,
+ ASensor_getMinDelay(mOrientationSensor), 0);
+ if (err < 0) {
+ ALOGE("%s: Unable to register for orientation sensor events\n", __func__);
+ }
+ return err;
+}
+
+void OrientationCollector::disableOrientationSensor() {
+ if (mSensorManager != nullptr && mQueue != nullptr) {
+ ASensorEventQueue_disableSensor(mQueue, mOrientationSensor);
+ ASensorManager_destroyEventQueue(mSensorManager, mQueue);
+ }
+}
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
diff --git a/pixelstats/SysfsCollector.cpp b/pixelstats/SysfsCollector.cpp
index f588c41..bf1e3d5 100644
--- a/pixelstats/SysfsCollector.cpp
+++ b/pixelstats/SysfsCollector.cpp
@@ -20,6 +20,7 @@
#include <android-base/file.h>
#include <android-base/parseint.h>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android/frameworks/stats/1.0/IStats.h>
#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
@@ -28,6 +29,7 @@
#include <utils/Timers.h>
#include <sys/timerfd.h>
+#include <mntent.h>
#include <string>
namespace android {
@@ -37,6 +39,7 @@ namespace pixel {
using android::sp;
using android::base::ReadFileToString;
+using android::base::StartsWith;
using android::frameworks::stats::V1_0::ChargeCycles;
using android::frameworks::stats::V1_0::HardwareFailed;
using android::frameworks::stats::V1_0::IStats;
@@ -45,6 +48,11 @@ using android::frameworks::stats::V1_0::SpeakerImpedance;
using android::frameworks::stats::V1_0::SpeechDspStat;
using android::frameworks::stats::V1_0::VendorAtom;
using android::hardware::google::pixel::PixelAtoms::BatteryCapacity;
+using android::hardware::google::pixel::PixelAtoms::StorageUfsHealth;
+using android::hardware::google::pixel::PixelAtoms::F2fsStatsInfo;
+using android::hardware::google::pixel::PixelAtoms::ZramMmStat;
+using android::hardware::google::pixel::PixelAtoms::ZramBdStat;
+using android::hardware::google::pixel::PixelAtoms::BootStatsInfo;
SysfsCollector::SysfsCollector(const struct SysfsPaths &sysfs_paths)
: kSlowioReadCntPath(sysfs_paths.SlowioReadCntPath),
@@ -57,7 +65,14 @@ SysfsCollector::SysfsCollector(const struct SysfsPaths &sysfs_paths)
kCodec1Path(sysfs_paths.Codec1Path),
kSpeechDspPath(sysfs_paths.SpeechDspPath),
kBatteryCapacityCC(sysfs_paths.BatteryCapacityCC),
- kBatteryCapacityVFSOC(sysfs_paths.BatteryCapacityVFSOC) {}
+ kBatteryCapacityVFSOC(sysfs_paths.BatteryCapacityVFSOC),
+ kUFSLifetimeA(sysfs_paths.UFSLifetimeA),
+ kUFSLifetimeB(sysfs_paths.UFSLifetimeB),
+ kUFSLifetimeC(sysfs_paths.UFSLifetimeC),
+ kF2fsStatsPath(sysfs_paths.F2fsStatsPath),
+ kUserdataBlockProp(sysfs_paths.UserdataBlockProp),
+ kZramMmStatPath("/sys/block/zram0/mm_stat"),
+ kZramBdStatPath("/sys/block/zram0/bd_stat") {}
bool SysfsCollector::ReadFileToInt(const std::string &path, int *val) {
return ReadFileToInt(path.c_str(), val);
@@ -69,6 +84,11 @@ bool SysfsCollector::ReadFileToInt(const char *const path, int *val) {
if (!ReadFileToString(path, &file_contents)) {
ALOGE("Unable to read %s - %s", path, strerror(errno));
return false;
+ } else if (StartsWith(file_contents, "0x")) {
+ if (sscanf(file_contents.c_str(), "0x%x", val) != 1) {
+ ALOGE("Unable to convert %s to hex - %s", path, strerror(errno));
+ return false;
+ }
} else if (sscanf(file_contents.c_str(), "%d", val) != 1) {
ALOGE("Unable to convert %s to int - %s", path, strerror(errno));
return false;
@@ -277,6 +297,287 @@ void SysfsCollector::logBatteryCapacity() {
ALOGE("Unable to report ChargeStats to Stats service");
}
+void SysfsCollector::logUFSLifetime() {
+ std::string file_contents;
+ if (kUFSLifetimeA == nullptr || strlen(kUFSLifetimeA) == 0) {
+ ALOGV("UFS lifetimeA path not specified");
+ return;
+ }
+ if (kUFSLifetimeB == nullptr || strlen(kUFSLifetimeB) == 0) {
+ ALOGV("UFS lifetimeB path not specified");
+ return;
+ }
+ if (kUFSLifetimeC == nullptr || strlen(kUFSLifetimeC) == 0) {
+ ALOGV("UFS lifetimeC path not specified");
+ return;
+ }
+
+ int lifetimeA = 0, lifetimeB = 0, lifetimeC = 0;
+ if (!ReadFileToInt(kUFSLifetimeA, &lifetimeA) ||
+ !ReadFileToInt(kUFSLifetimeB, &lifetimeB) ||
+ !ReadFileToInt(kUFSLifetimeC, &lifetimeC)) {
+ ALOGE("Unable to read UFS lifetime : %s", strerror(errno));
+ return;
+ }
+
+ // Load values array
+ std::vector<VendorAtom::Value> values(3);
+ VendorAtom::Value tmp;
+ tmp.intValue(lifetimeA);
+ values[StorageUfsHealth::kLifetimeAFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(lifetimeB);
+ values[StorageUfsHealth::kLifetimeBFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(lifetimeC);
+ values[StorageUfsHealth::kLifetimeCFieldNumber - kVendorAtomOffset] = tmp;
+
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::STORAGE_UFS_HEALTH,
+ .values = values};
+ Return<void> ret = stats_->reportVendorAtom(event);
+ if (!ret.isOk()) {
+ ALOGE("Unable to report UfsHealthStat to Stats service");
+ }
+}
+
+static std::string getUserDataBlock() {
+ std::unique_ptr<std::FILE, int (*)(std::FILE*)> fp(setmntent("/proc/mounts", "re"), endmntent);
+ if (fp == nullptr) {
+ ALOGE("Error opening /proc/mounts");
+ return "";
+ }
+
+ mntent* mentry;
+ while ((mentry = getmntent(fp.get())) != nullptr) {
+ if (strcmp(mentry->mnt_dir, "/data") == 0) {
+ return std::string(basename(mentry->mnt_fsname));
+ }
+ }
+ return "";
+}
+
+void SysfsCollector::logF2fsStats() {
+ int dirty, free, cp_calls_fg, gc_calls_fg, moved_block_fg, vblocks;
+ int cp_calls_bg, gc_calls_bg, moved_block_bg;
+
+ if (kF2fsStatsPath == nullptr) {
+ ALOGE("F2fs stats path not specified");
+ return;
+ }
+
+ std::string userdataBlock = getUserDataBlock();
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/dirty_segments"), &dirty)) {
+ ALOGV("Unable to read dirty segments");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/free_segments"), &free)) {
+ ALOGV("Unable to read free segments");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/cp_foreground_calls"), &cp_calls_fg)) {
+ ALOGV("Unable to read cp_foreground_calls");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/cp_background_calls"), &cp_calls_bg)) {
+ ALOGV("Unable to read cp_background_calls");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/gc_foreground_calls"), &gc_calls_fg)) {
+ ALOGV("Unable to read gc_foreground_calls");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/gc_background_calls"), &gc_calls_bg)) {
+ ALOGV("Unable to read gc_background_calls");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/moved_blocks_foreground"), &moved_block_fg)) {
+ ALOGV("Unable to read moved_blocks_foreground");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/moved_blocks_background"), &moved_block_bg)) {
+ ALOGV("Unable to read moved_blocks_background");
+ }
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/avg_vblocks"), &vblocks)) {
+ ALOGV("Unable to read avg_vblocks");
+ }
+
+ // Load values array
+ std::vector<VendorAtom::Value> values(9);
+ VendorAtom::Value tmp;
+ tmp.intValue(dirty);
+ values[F2fsStatsInfo::kDirtySegmentsFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(free);
+ values[F2fsStatsInfo::kFreeSegmentsFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(cp_calls_fg);
+ values[F2fsStatsInfo::kCpCallsFgFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(cp_calls_bg);
+ values[F2fsStatsInfo::kCpCallsBgFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(gc_calls_fg);
+ values[F2fsStatsInfo::kGcCallsFgFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(gc_calls_bg);
+ values[F2fsStatsInfo::kGcCallsBgFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(moved_block_fg);
+ values[F2fsStatsInfo::kMovedBlocksFgFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(moved_block_bg);
+ values[F2fsStatsInfo::kMovedBlocksBgFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(vblocks);
+ values[F2fsStatsInfo::kValidBlocksFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::F2FS_STATS,
+ .values = values};
+ Return<void> ret = stats_->reportVendorAtom(event);
+ if (!ret.isOk()) {
+ ALOGE("Unable to report F2fs stats to Stats service");
+ }
+}
+
+void SysfsCollector::reportZramMmStat() {
+ std::string file_contents;
+ if (!kZramMmStatPath) {
+ ALOGV("ZramMmStat path not specified");
+ return;
+ }
+
+ if (!ReadFileToString(kZramMmStatPath, &file_contents)) {
+ ALOGE("Unable to ZramMmStat %s - %s", kZramMmStatPath, strerror(errno));
+ return;
+ } else {
+ int64_t orig_data_size = 0;
+ int64_t compr_data_size = 0;
+ int64_t mem_used_total = 0;
+ int64_t mem_limit = 0;
+ int64_t max_used_total = 0;
+ int64_t same_pages = 0;
+ int64_t pages_compacted = 0;
+ int64_t huge_pages = 0;
+
+ if (sscanf(file_contents.c_str(), "%lu %lu %lu %lu %lu %lu %lu %lu",
+ &orig_data_size, &compr_data_size, &mem_used_total, &mem_limit,
+ &max_used_total, &same_pages, &pages_compacted, &huge_pages) != 8) {
+ ALOGE("Unable to parse ZramMmStat %s from file %s to int.",
+ file_contents.c_str(), kZramMmStatPath);
+ }
+
+ // Load values array
+ std::vector<VendorAtom::Value> values(5);
+ VendorAtom::Value tmp;
+ tmp.intValue(orig_data_size);
+ values[ZramMmStat::kOrigDataSizeFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(compr_data_size);
+ values[ZramMmStat::kComprDataSizeFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(mem_used_total);
+ values[ZramMmStat::kMemUsedTotalFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(same_pages);
+ values[ZramMmStat::kSamePagesFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(huge_pages);
+ values[ZramMmStat::kHugePagesFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::ZRAM_MM_STAT,
+ .values = values};
+ Return<void> ret = stats_->reportVendorAtom(event);
+ if (!ret.isOk())
+ ALOGE("Zram Unable to report ZramMmStat to Stats service");
+ }
+}
+
+void SysfsCollector::reportZramBdStat() {
+ std::string file_contents;
+ if (!kZramBdStatPath) {
+ ALOGV("ZramBdStat path not specified");
+ return;
+ }
+
+ if (!ReadFileToString(kZramBdStatPath, &file_contents)) {
+ ALOGE("Unable to ZramBdStat %s - %s", kZramBdStatPath, strerror(errno));
+ return;
+ } else {
+ int64_t bd_count = 0;
+ int64_t bd_reads = 0;
+ int64_t bd_writes = 0;
+
+ if (sscanf(file_contents.c_str(), "%lu %lu %lu",
+ &bd_count, &bd_reads, &bd_writes) != 3) {
+ ALOGE("Unable to parse ZramBdStat %s from file %s to int.",
+ file_contents.c_str(), kZramBdStatPath);
+ }
+
+ // Load values array
+ std::vector<VendorAtom::Value> values(3);
+ VendorAtom::Value tmp;
+ tmp.intValue(bd_count);
+ values[ZramBdStat::kBdCountFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(bd_reads);
+ values[ZramBdStat::kBdReadsFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(bd_writes);
+ values[ZramBdStat::kBdWritesFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::ZRAM_BD_STAT,
+ .values = values};
+ Return<void> ret = stats_->reportVendorAtom(event);
+ if (!ret.isOk())
+ ALOGE("Zram Unable to report ZramBdStat to Stats service");
+ }
+}
+
+void SysfsCollector::logZramStats() {
+ reportZramMmStat();
+ reportZramBdStat();
+}
+
+void SysfsCollector::logBootStats() {
+ int mounted_time_sec = 0;
+
+ if (kF2fsStatsPath == nullptr) {
+ ALOGE("F2fs stats path not specified");
+ return;
+ }
+
+ std::string userdataBlock = getUserDataBlock();
+
+ if (!ReadFileToInt(kF2fsStatsPath + (userdataBlock + "/mounted_time_sec"), &mounted_time_sec)) {
+ ALOGV("Unable to read mounted_time_sec");
+ return;
+ }
+
+ int fsck_time_ms = android::base::GetIntProperty("ro.boottime.init.fsck.data", 0);
+ int checkpoint_time_ms = android::base::GetIntProperty("ro.boottime.init.mount.data", 0);
+
+ if (fsck_time_ms == 0 && checkpoint_time_ms == 0) {
+ ALOGV("Not yet initialized");
+ return;
+ }
+
+ // Load values array
+ std::vector<VendorAtom::Value> values(3);
+ VendorAtom::Value tmp;
+ tmp.intValue(mounted_time_sec);
+ values[BootStatsInfo::kMountedTimeSecFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(fsck_time_ms / 1000);
+ values[BootStatsInfo::kFsckTimeSecFieldNumber - kVendorAtomOffset] = tmp;
+ tmp.intValue(checkpoint_time_ms / 1000);
+ values[BootStatsInfo::kCheckpointTimeSecFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::BOOT_STATS,
+ .values = values};
+ Return<void> ret = stats_->reportVendorAtom(event);
+ if (!ret.isOk()) {
+ ALOGE("Unable to report Boot stats to Stats service");
+ } else {
+ log_once_reported = true;
+ }
+}
+
void SysfsCollector::logAll() {
stats_ = IStats::tryGetService();
if (!stats_) {
@@ -284,6 +585,10 @@ void SysfsCollector::logAll() {
return;
}
+ // Collect once per service init; can be multiple due to service reinit
+ if (!log_once_reported) {
+ logBootStats();
+ }
logBatteryChargeCycles();
logCodecFailed();
logCodec1Failed();
@@ -291,6 +596,9 @@ void SysfsCollector::logAll() {
logSpeakerImpedance();
logSpeechDspStat();
logBatteryCapacity();
+ logUFSLifetime();
+ logF2fsStats();
+ logZramStats();
stats_.clear();
}
diff --git a/pixelstats/UeventListener.cpp b/pixelstats/UeventListener.cpp
index 4614293..f7b3c54 100644
--- a/pixelstats/UeventListener.cpp
+++ b/pixelstats/UeventListener.cpp
@@ -22,9 +22,9 @@
#include <android-base/strings.h>
#include <android/frameworks/stats/1.0/IStats.h>
#include <cutils/uevent.h>
-#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
#include <log/log.h>
#include <pixelstats/UeventListener.h>
+#include <pixelstats/WlcReporter.h>
#include <unistd.h>
#include <utils/StrongPointer.h>
@@ -37,6 +37,7 @@ using android::frameworks::stats::V1_0::HardwareFailed;
using android::frameworks::stats::V1_0::IStats;
using android::frameworks::stats::V1_0::UsbPortOverheatEvent;
using android::frameworks::stats::V1_0::VendorAtom;
+using android::hardware::google::pixel::WlcReporter;
using android::hardware::google::pixel::PixelAtoms::ChargeStats;
using android::hardware::google::pixel::PixelAtoms::VoltageTierStats;
@@ -76,8 +77,9 @@ void UeventListener::ReportMicBrokenOrDegraded(const int mic, const bool isbroke
Return<void> ret = stats_client->reportHardwareFailed(failure);
if (!ret.isOk())
ALOGE("Unable to report physical drop to Stats service");
- } else
+ } else {
ALOGE("Unable to connect to Stats service");
+ }
}
void UeventListener::ReportMicStatusUevents(const char *devpath, const char *mic_status) {
@@ -95,9 +97,9 @@ void UeventListener::ReportMicStatusUevents(const char *devpath, const char *mic
else
return;
- if (!value[1].compare("true"))
+ if (!value[1].compare("true")) {
ReportMicBrokenOrDegraded(0, isbroken);
- else {
+ } else {
int mic_status = atoi(value[1].c_str());
if (mic_status > 0 && mic_status <= 7) {
@@ -138,7 +140,7 @@ void UeventListener::ReportUsbPortOverheatEvent(const char *driver) {
}
}
-void UeventListener::ReportChargeStats(sp<IStats> &stats_client, const char *line) {
+void UeventListener::ReportChargeStats(const sp<IStats> &stats_client, const char *line) {
std::vector<int> charge_stats_fields = {
ChargeStats::kAdapterTypeFieldNumber, ChargeStats::kAdapterVoltageFieldNumber,
ChargeStats::kAdapterAmperageFieldNumber, ChargeStats::kSsocInFieldNumber,
@@ -167,7 +169,7 @@ void UeventListener::ReportChargeStats(sp<IStats> &stats_client, const char *lin
ALOGE("Unable to report ChargeStats to Stats service");
}
-void UeventListener::ReportVoltageTierStats(sp<IStats> &stats_client, const char *line) {
+void UeventListener::ReportVoltageTierStats(const sp<IStats> &stats_client, const char *line) {
std::vector<int> voltage_tier_stats_fields = {VoltageTierStats::kVoltageTierFieldNumber,
VoltageTierStats::kSocInFieldNumber,
VoltageTierStats::kCcInFieldNumber,
@@ -233,7 +235,7 @@ void UeventListener::ReportChargeMetricsEvent(const char *driver) {
}
if (!WriteStringToFile(kChargeMetricsPath.c_str(), std::to_string(0))) {
- ALOGE("Couldn't clear %s", kChargeMetricsPath.c_str());
+ ALOGE("Couldn't clear %s", kChargeMetricsPath.c_str());
}
sp<IStats> stats_client = IStats::tryGetService();
@@ -249,12 +251,59 @@ void UeventListener::ReportChargeMetricsEvent(const char *driver) {
}
}
+/* ReportWlc
+ * Report wireless relate metrics when wireless charging start
+ */
+void UeventListener::ReportWlc(const char *driver) {
+ if (!driver || strncmp(driver, "POWER_SUPPLY_PRESENT=", strlen("POWER_SUPPLY_PRESENT="))) {
+ return;
+ }
+ if (wireless_charging_supported_) {
+ sp<WlcReporter> wlc_reporter = new WlcReporter();
+ if (wlc_reporter != nullptr) {
+ wireless_charging_state_ =
+ wlc_reporter->checkAndReport(wireless_charging_state_);
+ }
+ }
+}
+
+/**
+ * Report raw battery capacity, system battery capacity and associated
+ * battery capacity curves. This data is collected to verify the filter
+ * applied on the battery capacity. This will allow debugging of issues
+ * ranging from incorrect fuel gauge hardware calculations to issues
+ * with the software reported battery capacity.
+ *
+ * The data is retrieved by parsing the battery power supply's ssoc_details.
+ *
+ * This atom logs data in 5 potential events:
+ * 1. When a device is connected
+ * 2. When a device is disconnected
+ * 3. When a device has reached a full charge (from the UI's perspective)
+ * 4. When there is a >= 2 percent skip in the UI reported SOC
+ * 5. When there is a difference of >= 4 percent between the raw hardware
+ * battery capacity and the system reported battery capacity.
+ */
+void UeventListener::ReportBatteryCapacityFGEvent(const char *subsystem) {
+ if (!subsystem || strcmp(subsystem, "SUBSYSTEM=power_supply")) {
+ return;
+ }
+
+ // Indicates an implicit disable of the battery capacity reporting
+ if (kBatterySSOCPath.empty()) {
+ return;
+ }
+
+ battery_capacity_reporter_.checkAndReport(kBatterySSOCPath);
+}
+
bool UeventListener::ProcessUevent() {
char msg[UEVENT_MSG_LEN + 2];
char *cp;
- const char *action, *power_supply_typec_mode, *driver, *product;
+ const char *driver, *product, *subsystem;
const char *mic_break_status, *mic_degrade_status;
const char *devpath;
+ const char *powpresent;
int n;
if (uevent_fd_ < 0) {
@@ -273,8 +322,8 @@ bool UeventListener::ProcessUevent() {
msg[n] = '\0';
msg[n + 1] = '\0';
- action = power_supply_typec_mode = driver = product = NULL;
- mic_break_status = mic_degrade_status = devpath = NULL;
+ driver = product = subsystem = NULL;
+ mic_break_status = mic_degrade_status = devpath = powpresent = NULL;
/**
* msg is a sequence of null-terminated strings.
@@ -283,11 +332,7 @@ bool UeventListener::ProcessUevent() {
*/
cp = msg;
while (*cp) {
- if (!strncmp(cp, "ACTION=", strlen("ACTION="))) {
- action = cp;
- } else if (!strncmp(cp, "POWER_SUPPLY_TYPEC_MODE=", strlen("POWER_SUPPLY_TYPEC_MODE="))) {
- power_supply_typec_mode = cp;
- } else if (!strncmp(cp, "DRIVER=", strlen("DRIVER="))) {
+ if (!strncmp(cp, "DRIVER=", strlen("DRIVER="))) {
driver = cp;
} else if (!strncmp(cp, "PRODUCT=", strlen("PRODUCT="))) {
product = cp;
@@ -297,6 +342,11 @@ bool UeventListener::ProcessUevent() {
mic_degrade_status = cp;
} else if (!strncmp(cp, "DEVPATH=", strlen("DEVPATH="))) {
devpath = cp;
+ } else if (!strncmp(cp, "POWER_SUPPLY_PRESENT=",
+ strlen("POWER_SUPPLY_PRESENT="))) {
+ powpresent = cp;
+ } else if (!strncmp(cp, "SUBSYSTEM=", strlen("SUBSYSTEM="))) {
+ subsystem = cp;
}
/* advance to after the next \0 */
@@ -309,22 +359,35 @@ bool UeventListener::ProcessUevent() {
ReportMicStatusUevents(devpath, mic_degrade_status);
ReportUsbPortOverheatEvent(driver);
ReportChargeMetricsEvent(driver);
+ ReportWlc(powpresent);
+ ReportBatteryCapacityFGEvent(subsystem);
return true;
}
-UeventListener::UeventListener(const std::string audio_uevent, const std::string overheat_path,
+UeventListener::UeventListener(const std::string audio_uevent, const std::string ssoc_details_path,
+ const std::string overheat_path,
const std::string charge_metrics_path)
: kAudioUevent(audio_uevent),
+ kBatterySSOCPath(ssoc_details_path),
kUsbPortOverheatPath(overheat_path),
kChargeMetricsPath(charge_metrics_path),
- uevent_fd_(-1) {}
+ uevent_fd_(-1),
+ wireless_charging_state_(false) {}
/* Thread function to continuously monitor uevents.
* Exit after kMaxConsecutiveErrors to prevent spinning. */
void UeventListener::ListenForever() {
constexpr int kMaxConsecutiveErrors = 10;
int consecutive_errors = 0;
+ sp<WlcReporter> wlc_reporter = new WlcReporter();
+ if (wlc_reporter != nullptr) {
+ wireless_charging_supported_ = wlc_reporter->isWlcSupported();
+ } else {
+ ALOGE("Fail to create WlcReporter.");
+ wireless_charging_supported_ = false;
+ }
+
while (1) {
if (ProcessUevent()) {
consecutive_errors = 0;
diff --git a/pixelstats/WlcReporter.cpp b/pixelstats/WlcReporter.cpp
new file mode 100644
index 0000000..7c9a93f
--- /dev/null
+++ b/pixelstats/WlcReporter.cpp
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "pixelstats-wlc"
+
+#include <android-base/file.h>
+#include <log/log.h>
+#include <pixelstats/OrientationCollector.h>
+#include <pixelstats/WlcReporter.h>
+
+#define POWER_SUPPLY_SYSFS_PATH "/sys/class/power_supply/wireless/online"
+#define POWER_SUPPLY_PTMC_PATH "/sys/class/power_supply/wireless/ptmc_id"
+#define GOOGLE_PTMC_ID 72
+
+using android::base::ReadFileToString;
+using android::frameworks::stats::V1_0::IStats;
+using android::frameworks::stats::V1_0::VendorAtom;
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+bool WlcReporter::checkAndReport(bool isWirelessChargingLast) {
+ bool wireless_charging = isWlcOnline();
+ if (wireless_charging && !isWirelessChargingLast) {
+ doLog();
+ }
+ return wireless_charging;
+}
+
+bool WlcReporter::readFileToInt(const char *const path, int *val) {
+ std::string file_contents;
+
+ if (!ReadFileToString(path, &file_contents)) {
+ ALOGE("Unable to read %s - %s", path, strerror(errno));
+ return false;
+ } else if (sscanf(file_contents.c_str(), "%d", val) != 1) {
+ ALOGE("Unable to convert %s (%s) to int - %s", path, file_contents.c_str(),
+ strerror(errno));
+ return false;
+ }
+ return true;
+}
+
+int WlcReporter::readPtmcId() {
+ int id = 0;
+ readFileToInt(POWER_SUPPLY_PTMC_PATH, &id);
+ return id;
+}
+
+/* Reference to frameworks/native/libs/ui/include/ui/DisplayInfo.h
+ * translate orientation value from sensor to enum define in
+ * pixelatoms.proto
+ */
+int WlcReporter::translateDeviceOrientationToAtomValue(int orientation) {
+ switch (orientation) {
+ case 0:
+ return PixelAtoms::DeviceOrientation::ORIENTATION_0;
+ case 1:
+ return PixelAtoms::DeviceOrientation::ORIENTATION_90;
+ case 2:
+ return PixelAtoms::DeviceOrientation::ORIENTATION_180;
+ case 3:
+ return PixelAtoms::DeviceOrientation::ORIENTATION_270;
+ default:
+ return PixelAtoms::DeviceOrientation::ORIENTATION_UNKNOWN;
+ }
+}
+
+void WlcReporter::doLog() {
+ sp<IStats> stats_client = IStats::tryGetService();
+
+ if (stats_client == nullptr) {
+ ALOGE("logWlc get IStats fail.");
+ return;
+ }
+ std::vector<VendorAtom::Value> values(1);
+
+ int vendoriCharger = (readPtmcId() == GOOGLE_PTMC_ID)
+ ? PixelAtoms::WirelessChargingStats::VENDOR_GOOGLE
+ : PixelAtoms::WirelessChargingStats::VENDOR_UNKNOWN;
+ VendorAtom::Value tmp;
+ tmp.intValue(vendoriCharger);
+ values[PixelAtoms::WirelessChargingStats::kChargerVendorFieldNumber - kVendorAtomOffset] = tmp;
+
+ // Send vendor atom to IStats HAL
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::WIRELESS_CHARGING_STATS,
+ .values = values};
+ Return<void> retStat = stats_client->reportVendorAtom(event);
+ if (!retStat.isOk())
+ ALOGE("Unable to report WLC_STATS to Stats service");
+
+ int orientationFromSensor;
+ sp<OrientationCollector> orientationCollector;
+ orientationCollector = OrientationCollector::createOrientationCollector();
+ if (orientationCollector != nullptr) {
+ orientationCollector->pollOrientation(&orientationFromSensor);
+ VendorAtom::Value tmp;
+ tmp.intValue(translateDeviceOrientationToAtomValue(orientationFromSensor));
+ values[PixelAtoms::DeviceOrientation::kOrientationFieldNumber - kVendorAtomOffset] = tmp;
+
+ VendorAtom event = {.reverseDomainName = PixelAtoms::ReverseDomainNames().pixel(),
+ .atomId = PixelAtoms::Ids::DEVICE_ORIENTATION,
+ .values = values};
+ Return<void> retOrientation = stats_client->reportVendorAtom(event);
+ if (!retOrientation.isOk())
+ ALOGE("Unable to report Orientation to Stats service");
+ orientationCollector->disableOrientationSensor();
+ }
+}
+
+bool WlcReporter::isWlcSupported() {
+ std::string file_contents;
+
+ if (!ReadFileToString(POWER_SUPPLY_SYSFS_PATH, &file_contents)) {
+ ALOGV("Unable to read %s - %s", POWER_SUPPLY_SYSFS_PATH, strerror(errno));
+ return false;
+ } else {
+ return true;
+ }
+}
+
+bool WlcReporter::isWlcOnline() {
+ std::string file_contents;
+
+ if (!ReadFileToString(POWER_SUPPLY_SYSFS_PATH, &file_contents)) {
+ ALOGE("Unable to read %s - %s", POWER_SUPPLY_SYSFS_PATH, strerror(errno));
+ return false;
+ }
+ ALOGV("isWlcOnline value: %s", file_contents.c_str());
+ return file_contents == "1\n";
+}
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
diff --git a/pixelstats/device.mk b/pixelstats/device.mk
new file mode 100644
index 0000000..cef2e99
--- /dev/null
+++ b/pixelstats/device.mk
@@ -0,0 +1,5 @@
+# Reliability reporting
+PRODUCT_PACKAGES += \
+ pixelstats-vendor
+
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/pixelstats
diff --git a/pixelstats/include/pixelstats/BatteryCapacityReporter.h b/pixelstats/include/pixelstats/BatteryCapacityReporter.h
new file mode 100644
index 0000000..eb0effe
--- /dev/null
+++ b/pixelstats/include/pixelstats/BatteryCapacityReporter.h
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HARDWARE_GOOGLE_PIXEL_PIXELSTATS_BATTERYCAPACITYREPORTER_H
+#define HARDWARE_GOOGLE_PIXEL_PIXELSTATS_BATTERYCAPACITYREPORTER_H
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+/**
+ * A class to upload battery capacity metrics
+ */
+class BatteryCapacityReporter {
+ public:
+ BatteryCapacityReporter();
+ void checkAndReport(const std::string &path);
+
+ private:
+ int64_t getTimeSecs();
+
+ bool parse(const std::string &path);
+ bool check(void);
+ void report(void);
+
+ /**
+ * SOC status translation from sysfs node
+ */
+ enum SOCStatus {
+ SOC_STATUS_UNKNOWN = 0,
+ SOC_STATUS_CONNECTED = 1,
+ SOC_STATUS_DISCONNECTED = 2,
+ SOC_STATUS_FULL = 3,
+ };
+
+ enum LogReason {
+ LOG_REASON_UNKNOWN = 0,
+ LOG_REASON_CONNECTED = 1,
+ LOG_REASON_DISCONNECTED = 2,
+ LOG_REASON_FULL_CHARGE = 3,
+ LOG_REASON_PERCENT_SKIP = 4,
+ LOG_REASON_DIVERGING_FG = 5,
+ };
+
+ SOCStatus status_ = SOC_STATUS_UNKNOWN;
+ SOCStatus status_previous_ = SOC_STATUS_UNKNOWN;
+ float gdf_ = 0.0;
+ float ssoc_ = 0.0f;
+ float gdf_curve_ = 0.0f;
+ float ssoc_curve_ = 0.0f;
+ float ssoc_previous_ = -1.0f;
+ float ssoc_gdf_diff_previous_ = 0.0f;
+ int64_t unexpected_event_timer_secs_ = 0;
+ bool unexpected_event_timer_active_ = false;
+ LogReason log_reason_ = LOG_REASON_UNKNOWN;
+
+ // Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
+ // store everything in the values array at the index of the field number
+ // -2.
+ const int kVendorAtomOffset = 2;
+};
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
+
+#endif // HARDWARE_GOOGLE_PIXEL_PIXELSTATS_BATTERYCAPACITYREPORTER_H
diff --git a/pixelstats/include/pixelstats/OrientationCollector.h b/pixelstats/include/pixelstats/OrientationCollector.h
new file mode 100644
index 0000000..e3ca641
--- /dev/null
+++ b/pixelstats/include/pixelstats/OrientationCollector.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android/hidl/base/1.0/IBase.h>
+#include <android/sensor.h>
+
+#ifndef HARDWARE_GOOGLE_PIXEL_ORIENTATION_COLLECTOR_H
+#define HARDWARE_GOOGLE_PIXEL_ORIENTATION_COLLECTOR_H
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+/**
+ * A class to help use Senser
+ */
+class OrientationCollector : public RefBase {
+ public:
+ static sp<OrientationCollector> createOrientationCollector();
+
+ int32_t pollOrientation(int *orientation);
+ int32_t init();
+ void disableOrientationSensor();
+
+ private:
+ ASensorEventQueue *mQueue;
+ ASensorManager *mSensorManager = nullptr;
+ ASensorRef mOrientationSensor;
+
+ int getEvents(ASensorEvent *event_list, size_t event_list_size, int *event_count);
+};
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
+
+#endif // HARDWARE_GOOGLE_PIXEL_ORIENTATION_COLLECTOR_H
diff --git a/pixelstats/include/pixelstats/SysfsCollector.h b/pixelstats/include/pixelstats/SysfsCollector.h
index d784ed5..c6cc608 100644
--- a/pixelstats/include/pixelstats/SysfsCollector.h
+++ b/pixelstats/include/pixelstats/SysfsCollector.h
@@ -43,6 +43,13 @@ class SysfsCollector {
const char *const SpeechDspPath;
const char *const BatteryCapacityCC;
const char *const BatteryCapacityVFSOC;
+ const char *const UFSLifetimeA;
+ const char *const UFSLifetimeB;
+ const char *const UFSLifetimeC;
+ const char *const F2fsStatsPath;
+ const char *const UserdataBlockProp;
+ const char *const ZramMmStatPath;
+ const char *const ZramBdStatPath;
};
SysfsCollector(const struct SysfsPaths &paths);
@@ -60,8 +67,16 @@ class SysfsCollector {
void logSpeakerImpedance();
void logSpeechDspStat();
void logBatteryCapacity();
+ void logUFSLifetime();
+ void logF2fsStats();
+ void logZramStats();
+ void logBootStats();
void reportSlowIoFromFile(const char *path, const SlowIo::IoOperation &operation_s);
+ void reportZramMmStat();
+ void reportZramBdStat();
+
+ unsigned int getValueFromStatus(std::string &f2fsStatus, const char * key);
const char *const kSlowioReadCntPath;
const char *const kSlowioWriteCntPath;
@@ -74,12 +89,21 @@ class SysfsCollector {
const char *const kSpeechDspPath;
const char *const kBatteryCapacityCC;
const char *const kBatteryCapacityVFSOC;
+ const char *const kUFSLifetimeA;
+ const char *const kUFSLifetimeB;
+ const char *const kUFSLifetimeC;
+ const char *const kF2fsStatsPath;
+ const char *const kUserdataBlockProp;
+ const char *const kZramMmStatPath;
+ const char *const kZramBdStatPath;
sp<IStats> stats_;
// Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
// store everything in the values array at the index of the field number
// -2.
const int kVendorAtomOffset = 2;
+
+ bool log_once_reported = false;
};
} // namespace pixel
diff --git a/pixelstats/include/pixelstats/UeventListener.h b/pixelstats/include/pixelstats/UeventListener.h
index e1c0301..6ffcfb2 100644
--- a/pixelstats/include/pixelstats/UeventListener.h
+++ b/pixelstats/include/pixelstats/UeventListener.h
@@ -19,6 +19,7 @@
#include <android-base/chrono_utils.h>
#include <android/frameworks/stats/1.0/IStats.h>
+#include <pixelstats/BatteryCapacityReporter.h>
using android::frameworks::stats::V1_0::IStats;
using android::frameworks::stats::V1_0::UsbPortOverheatEvent;
@@ -37,32 +38,42 @@ namespace pixel {
class UeventListener {
public:
UeventListener(
- const std::string audio_uevent,
+ const std::string audio_uevent, const std::string ssoc_details_path = "",
const std::string overheat_path =
"/sys/devices/platform/soc/soc:google,overheat_mitigation",
const std::string charge_metrics_path = "/sys/class/power_supply/battery/charge_stats");
bool ProcessUevent(); // Process a single Uevent.
void ListenForever(); // Process Uevents forever
+
private:
bool ReadFileToInt(const std::string &path, int *val);
bool ReadFileToInt(const char *path, int *val);
void ReportMicStatusUevents(const char *devpath, const char *mic_status);
void ReportMicBrokenOrDegraded(const int mic, const bool isBroken);
void ReportUsbPortOverheatEvent(const char *driver);
- void ReportChargeStats(sp<IStats> &stats_client, const char *line);
- void ReportVoltageTierStats(sp<IStats> &stats_client, const char *line);
+ void ReportChargeStats(const sp<IStats> &stats_client, const char *line);
+ void ReportVoltageTierStats(const sp<IStats> &stats_client, const char *line);
void ReportChargeMetricsEvent(const char *driver);
+ void ReportWlc(const char *driver);
+ void ReportBatteryCapacityFGEvent(const char *subsystem);
const std::string kAudioUevent;
+ const std::string kBatterySSOCPath;
const std::string kUsbPortOverheatPath;
const std::string kChargeMetricsPath;
+
+ BatteryCapacityReporter battery_capacity_reporter_;
+
// Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
// store everything in the values array at the index of the field number
// -2.
const int kVendorAtomOffset = 2;
int uevent_fd_;
+
+ bool wireless_charging_state_;
+ bool wireless_charging_supported_;
};
} // namespace pixel
diff --git a/pixelstats/include/pixelstats/WlcReporter.h b/pixelstats/include/pixelstats/WlcReporter.h
new file mode 100644
index 0000000..d29b7b4
--- /dev/null
+++ b/pixelstats/include/pixelstats/WlcReporter.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef HARDWARE_GOOGLE_PIXEL_PIXELSTATS_WLCREPORTER_H
+#define HARDWARE_GOOGLE_PIXEL_PIXELSTATS_WLCREPORTER_H
+
+#include <android/frameworks/stats/1.0/IStats.h>
+#include <hardware/google/pixel/pixelstats/pixelatoms.pb.h>
+
+using android::frameworks::stats::V1_0::IStats;
+
+namespace android {
+namespace hardware {
+namespace google {
+namespace pixel {
+
+/**
+ * A class to upload wireless metrics
+ */
+class WlcReporter : public RefBase {
+ public:
+ /* checkAndReport
+ * isWirelessChargingLast: last wireless charge state
+ * true, for wireless charging
+ * Return: current wireless charge state
+ */
+ bool checkAndReport(bool isWirelessChargingLast);
+ bool isWlcSupported();
+
+ private:
+ bool isWlcOnline();
+ bool readFileToInt(const char *path, int *val);
+
+ void doLog();
+ // Translate device orientation value from sensor Hal to atom enum value
+ int translateDeviceOrientationToAtomValue(int orientation);
+
+ // Proto messages are 1-indexed and VendorAtom field numbers start at 2, so
+ // store everything in the values array at the index of the field number
+ // -2.
+ const int kVendorAtomOffset = 2;
+ int readPtmcId();
+};
+
+} // namespace pixel
+} // namespace google
+} // namespace hardware
+} // namespace android
+
+#endif // HARDWARE_GOOGLE_PIXEL_PIXELSTATS_WLCREPORTER_H
diff --git a/pixelstats/pixelatoms.proto b/pixelstats/pixelatoms.proto
index 804a952..dc2c32a 100644
--- a/pixelstats/pixelatoms.proto
+++ b/pixelstats/pixelatoms.proto
@@ -37,6 +37,14 @@ enum Ids {
CHARGE_STATS = 105000;
VOLTAGE_TIER_STATS = 105001;
BATTERY_CAPACITY = 105002;
+ STORAGE_UFS_HEALTH = 105003;
+ F2FS_STATS = 105004;
+ ZRAM_MM_STAT = 105005;
+ ZRAM_BD_STAT = 105006;
+ BOOT_STATS = 105007;
+ WIRELESS_CHARGING_STATS = 105008;
+ DEVICE_ORIENTATION = 105009;
+ FG_CAPACITY = 105010;
// AOSP atom ID range ends at 109999
}
@@ -128,3 +136,117 @@ message BatteryCapacity {
/* Sum of the change in state of charge (battery level). */
optional int32 delta_vfsoc_sum = 3;
}
+
+/* A message containing health values of UFS */
+message StorageUfsHealth {
+ /* The value of lifetimeA for UFS health */
+ optional int32 lifetime_a = 2;
+ /* The value of lifetimeB for UFS health */
+ optional int32 lifetime_b = 3;
+ /* The value of lifetimeC for UFS health */
+ optional int32 lifetime_c = 4;
+}
+
+/* A message containing filesystem stats of F2FS */
+message F2fsStatsInfo {
+ /* The value of dirty segments of f2fs */
+ optional int32 dirty_segments = 2;
+ /* The value of free segments of f2fs */
+ optional int32 free_segments = 3;
+ /* The times of checkpoint function called in foreground*/
+ optional int32 cp_calls_fg = 4;
+ /* The times of checkpoint function called in background */
+ optional int32 cp_calls_bg = 5;
+ /* The times of garbage collection function called in foreground */
+ optional int32 gc_calls_fg = 6;
+ /* The times of garbage collection function called in background */
+ optional int32 gc_calls_bg = 7;
+ /* The amount of blocks been moved by garbage collection in foreground */
+ optional int32 moved_blocks_fg = 8;
+ /* The amount of blocks been moved by garbage collection in background */
+ optional int32 moved_blocks_bg = 9;
+ /* The average of how many valid blocks is in a segment */
+ optional int32 valid_blocks = 10;
+}
+
+message ZramMmStat {
+ /* The value of original memory size */
+ optional int64 orig_data_size = 2;
+ /* The value of compressed memory size */
+ optional int64 compr_data_size = 3;
+ /* The value of consumed memory size to store compressed memory */
+ optional int64 mem_used_total = 4;
+ /* The value of number of page filled with same elements data */
+ optional int64 same_pages = 5;
+ /* The value of number of incompressible page */
+ optional int64 huge_pages = 6;
+}
+
+message ZramBdStat {
+ /* the number of pages in backing device */
+ optional int64 bd_count = 2;
+ /* The number of pages readed from backing device */
+ optional int64 bd_reads = 3;
+ /* The number of pages written to backing device */
+ optional int64 bd_writes = 4;
+}
+
+/* A message containing boot times */
+message BootStatsInfo {
+ /* The F2FS fsck time in secs */
+ optional int32 fsck_time_sec = 2;
+ /* The F2FS mounted time in secs */
+ optional int32 mounted_time_sec = 3;
+ /* The F2FS checkpoint=disable time in secs */
+ optional int32 checkpoint_time_sec = 4;
+}
+
+/* A message containing wireless charging health info. */
+message WirelessChargingStats {
+ /* Captures if a google charger used when start wireless charging */
+ enum ChargerVendor {
+ VENDOR_UNKNOWN = 0;
+ VENDOR_GOOGLE = 1;
+ }
+
+ optional ChargerVendor charger_vendor = 2;
+}
+
+/* Current device Orientation */
+message DeviceOrientation {
+ enum Orientation {
+ ORIENTATION_UNKNOWN = 0;
+ ORIENTATION_0 = 1;
+ ORIENTATION_90 = 2;
+ ORIENTATION_180 = 3;
+ ORIENTATION_270 = 4;
+ }
+
+ /* Device orientation. */
+ optional Orientation orientation = 2;
+}
+
+/* Raw battery capacity stats */
+message BatteryCapacityFG {
+ enum LogReason {
+ LOG_REASON_UNKNOWN = 0;
+ LOG_REASON_CONNECTED = 1;
+ LOG_REASON_DISCONNECTED = 2;
+ LOG_REASON_FULL_CHARGE = 3;
+ LOG_REASON_PERCENT_SKIP = 4;
+ LOG_REASON_DIVERGING_FG = 5;
+ }
+
+ /* Uevent logging reason, enumerated above. */
+ optional LogReason capacity_log_reason = 2;
+
+ /* The battery capacity reported from the FG (fuel gauge) hardware */
+ optional float capacity_gdf = 3;
+ /* The filtered system battery capacity reported to the UI */
+ optional float capacity_ssoc = 4;
+ /* The fuel gauge capacity curve midpoint FG (fuel gauge) value */
+ optional float capacity_gdf_curve = 5;
+ /* The fuel gauge capacity curve midpoint UI value */
+ optional float capacity_ssoc_curve = 6;
+}
+
diff --git a/power-libperfmgr/Android.bp b/power-libperfmgr/Android.bp
index ca2aeda..c4d0f87 100644
--- a/power-libperfmgr/Android.bp
+++ b/power-libperfmgr/Android.bp
@@ -13,19 +13,34 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+cc_library {
+ name: "libdisppower-pixel",
+ proprietary: true,
+ srcs: [
+ "disp-power/DisplayLowPower.cpp",
+ "disp-power/InteractionHandler.cpp",
+ ],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libperfmgr",
+ "libutils",
+ ],
+}
+
cc_library_headers {
name: "pixel_power_headers",
- vendor_available: true,
- export_include_dirs: ["."],
+ vendor: true,
+ export_include_dirs: ["hidl"],
}
cc_binary {
name: "android.hardware.power@1.3-service.pixel-libperfmgr",
relative_install_path: "hw",
- vintf_fragments: ["android.hardware.power@1.3-service.pixel.xml"],
- init_rc: ["android.hardware.power@1.3-service.pixel-libperfmgr.rc"],
- srcs: ["service.cpp", "Power.cpp", "InteractionHandler.cpp",
- "display-helper.cpp"],
+ vintf_fragments: ["hidl/android.hardware.power@1.3-service.pixel.xml"],
+ init_rc: ["hidl/android.hardware.power@1.3-service.pixel-libperfmgr.rc"],
+ srcs: ["hidl/service.cpp", "hidl/Power.cpp"],
cflags: [
"-Wall",
"-Werror",
@@ -40,7 +55,32 @@ cc_binary {
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
+ "libdisppower-pixel",
"libperfmgr",
],
proprietary: true,
}
+
+cc_binary {
+ name: "android.hardware.power-service.pixel-libperfmgr",
+ relative_install_path: "hw",
+ init_rc: ["aidl/android.hardware.power-service.pixel-libperfmgr.rc"],
+ vintf_fragments: ["aidl/android.hardware.power-service.pixel.xml"],
+ vendor: true,
+ shared_libs: [
+ "android.hardware.power-ndk_platform",
+ "libbase",
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libbinder_ndk",
+ "libdisppower-pixel",
+ "libperfmgr",
+ "pixel-power-ext-ndk_platform",
+ ],
+ srcs: [
+ "aidl/service.cpp",
+ "aidl/Power.cpp",
+ "aidl/PowerExt.cpp",
+ ],
+}
diff --git a/power-libperfmgr/OWNERS b/power-libperfmgr/OWNERS
new file mode 100644
index 0000000..9ce2db2
--- /dev/null
+++ b/power-libperfmgr/OWNERS
@@ -0,0 +1,4 @@
+wvw@google.com
+joaodias@google.com
+jychen@google.com
+paillon@google.com
diff --git a/power-libperfmgr/aidl/Power.cpp b/power-libperfmgr/aidl/Power.cpp
new file mode 100644
index 0000000..d944cf4
--- /dev/null
+++ b/power-libperfmgr/aidl/Power.cpp
@@ -0,0 +1,256 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
+#define LOG_TAG "android.hardware.power-service.pixel-libperfmgr"
+
+#include "Power.h"
+
+#include <mutex>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+#include "disp-power/DisplayLowPower.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+constexpr char kPowerHalStateProp[] = "vendor.powerhal.state";
+constexpr char kPowerHalAudioProp[] = "vendor.powerhal.audio";
+constexpr char kPowerHalRenderingProp[] = "vendor.powerhal.rendering";
+
+Power::Power(std::shared_ptr<HintManager> hm, std::shared_ptr<DisplayLowPower> dlpw)
+ : mHintManager(hm),
+ mDisplayLowPower(dlpw),
+ mInteractionHandler(nullptr),
+ mVRModeOn(false),
+ mSustainedPerfModeOn(false) {
+ mInteractionHandler = std::make_unique<InteractionHandler>(mHintManager);
+ mInteractionHandler->Init();
+
+ std::string state = ::android::base::GetProperty(kPowerHalStateProp, "");
+ if (state == "SUSTAINED_PERFORMANCE") {
+ ALOGI("Initialize with SUSTAINED_PERFORMANCE on");
+ mHintManager->DoHint("SUSTAINED_PERFORMANCE");
+ mSustainedPerfModeOn = true;
+ } else if (state == "VR") {
+ ALOGI("Initialize with VR on");
+ mHintManager->DoHint(state);
+ mVRModeOn = true;
+ } else if (state == "VR_SUSTAINED_PERFORMANCE") {
+ ALOGI("Initialize with SUSTAINED_PERFORMANCE and VR on");
+ mHintManager->DoHint("VR_SUSTAINED_PERFORMANCE");
+ mSustainedPerfModeOn = true;
+ mVRModeOn = true;
+ } else {
+ ALOGI("Initialize PowerHAL");
+ }
+
+ state = ::android::base::GetProperty(kPowerHalAudioProp, "");
+ if (state == "AUDIO_STREAMING_LOW_LATENCY") {
+ ALOGI("Initialize with AUDIO_LOW_LATENCY on");
+ mHintManager->DoHint(state);
+ }
+
+ state = ::android::base::GetProperty(kPowerHalRenderingProp, "");
+ if (state == "EXPENSIVE_RENDERING") {
+ ALOGI("Initialize with EXPENSIVE_RENDERING on");
+ mHintManager->DoHint("EXPENSIVE_RENDERING");
+ }
+
+ // Now start to take powerhint
+ ALOGI("PowerHAL ready to process hints");
+}
+
+ndk::ScopedAStatus Power::setMode(Mode type, bool enabled) {
+ LOG(DEBUG) << "Power setMode: " << toString(type) << " to: " << enabled;
+ ATRACE_INT(toString(type).c_str(), enabled);
+ switch (type) {
+ case Mode::LOW_POWER:
+ mDisplayLowPower->SetDisplayLowPower(enabled);
+ if (enabled) {
+ mHintManager->DoHint(toString(type));
+ } else {
+ mHintManager->EndHint(toString(type));
+ }
+ break;
+ case Mode::SUSTAINED_PERFORMANCE:
+ if (enabled && !mSustainedPerfModeOn) {
+ if (!mVRModeOn) { // Sustained mode only.
+ mHintManager->DoHint("SUSTAINED_PERFORMANCE");
+ } else { // Sustained + VR mode.
+ mHintManager->EndHint("VR");
+ mHintManager->DoHint("VR_SUSTAINED_PERFORMANCE");
+ }
+ mSustainedPerfModeOn = true;
+ } else if (!enabled && mSustainedPerfModeOn) {
+ mHintManager->EndHint("VR_SUSTAINED_PERFORMANCE");
+ mHintManager->EndHint("SUSTAINED_PERFORMANCE");
+ if (mVRModeOn) { // Switch back to VR Mode.
+ mHintManager->DoHint("VR");
+ }
+ mSustainedPerfModeOn = false;
+ }
+ break;
+ case Mode::VR:
+ if (enabled && !mVRModeOn) {
+ if (!mSustainedPerfModeOn) { // VR mode only.
+ mHintManager->DoHint("VR");
+ } else { // Sustained + VR mode.
+ mHintManager->EndHint("SUSTAINED_PERFORMANCE");
+ mHintManager->DoHint("VR_SUSTAINED_PERFORMANCE");
+ }
+ mVRModeOn = true;
+ } else if (!enabled && mVRModeOn) {
+ mHintManager->EndHint("VR_SUSTAINED_PERFORMANCE");
+ mHintManager->EndHint("VR");
+ if (mSustainedPerfModeOn) { // Switch back to sustained Mode.
+ mHintManager->DoHint("SUSTAINED_PERFORMANCE");
+ }
+ mVRModeOn = false;
+ }
+ break;
+ case Mode::LAUNCH:
+ if (mVRModeOn || mSustainedPerfModeOn) {
+ break;
+ }
+ [[fallthrough]];
+ case Mode::DOUBLE_TAP_TO_WAKE:
+ [[fallthrough]];
+ case Mode::FIXED_PERFORMANCE:
+ [[fallthrough]];
+ case Mode::EXPENSIVE_RENDERING:
+ [[fallthrough]];
+ case Mode::INTERACTIVE:
+ [[fallthrough]];
+ case Mode::DEVICE_IDLE:
+ [[fallthrough]];
+ case Mode::DISPLAY_INACTIVE:
+ [[fallthrough]];
+ case Mode::AUDIO_STREAMING_LOW_LATENCY:
+ [[fallthrough]];
+ case Mode::CAMERA_STREAMING_SECURE:
+ [[fallthrough]];
+ case Mode::CAMERA_STREAMING_LOW:
+ [[fallthrough]];
+ case Mode::CAMERA_STREAMING_MID:
+ [[fallthrough]];
+ case Mode::CAMERA_STREAMING_HIGH:
+ [[fallthrough]];
+ default:
+ if (enabled) {
+ mHintManager->DoHint(toString(type));
+ } else {
+ mHintManager->EndHint(toString(type));
+ }
+ break;
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Power::isModeSupported(Mode type, bool *_aidl_return) {
+ bool supported = mHintManager->IsHintSupported(toString(type));
+ // LOW_POWER handled insides PowerHAL specifically
+ if (type == Mode::LOW_POWER) {
+ supported = true;
+ }
+ LOG(INFO) << "Power mode " << toString(type) << " isModeSupported: " << supported;
+ *_aidl_return = supported;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Power::setBoost(Boost type, int32_t durationMs) {
+ LOG(DEBUG) << "Power setBoost: " << toString(type) << " duration: " << durationMs;
+ ATRACE_INT(toString(type).c_str(), durationMs);
+ switch (type) {
+ case Boost::INTERACTION:
+ if (mVRModeOn || mSustainedPerfModeOn) {
+ break;
+ }
+ mInteractionHandler->Acquire(durationMs);
+ break;
+ case Boost::DISPLAY_UPDATE_IMMINENT:
+ [[fallthrough]];
+ case Boost::ML_ACC:
+ [[fallthrough]];
+ case Boost::AUDIO_LAUNCH:
+ [[fallthrough]];
+ case Boost::CAMERA_LAUNCH:
+ [[fallthrough]];
+ case Boost::CAMERA_SHOT:
+ [[fallthrough]];
+ default:
+ if (mVRModeOn || mSustainedPerfModeOn) {
+ break;
+ }
+ if (durationMs > 0) {
+ mHintManager->DoHint(toString(type), std::chrono::milliseconds(durationMs));
+ } else if (durationMs == 0) {
+ mHintManager->DoHint(toString(type));
+ } else {
+ mHintManager->EndHint(toString(type));
+ }
+ break;
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Power::isBoostSupported(Boost type, bool *_aidl_return) {
+ bool supported = mHintManager->IsHintSupported(toString(type));
+ LOG(INFO) << "Power boost " << toString(type) << " isBoostSupported: " << supported;
+ *_aidl_return = supported;
+ return ndk::ScopedAStatus::ok();
+}
+
+constexpr const char *boolToString(bool b) {
+ return b ? "true" : "false";
+}
+
+binder_status_t Power::dump(int fd, const char **, uint32_t) {
+ std::string buf(::android::base::StringPrintf(
+ "HintManager Running: %s\n"
+ "VRMode: %s\n"
+ "SustainedPerformanceMode: %s\n",
+ boolToString(mHintManager->IsRunning()), boolToString(mVRModeOn),
+ boolToString(mSustainedPerfModeOn)));
+ // Dump nodes through libperfmgr
+ mHintManager->DumpToFd(fd);
+ if (!::android::base::WriteStringToFd(buf, fd)) {
+ PLOG(ERROR) << "Failed to dump state to fd";
+ }
+ fsync(fd);
+ return STATUS_OK;
+}
+
+} // namespace pixel
+} // namespace impl
+} // namespace power
+} // namespace hardware
+} // namespace google
+} // namespace aidl
diff --git a/power-libperfmgr/aidl/Power.h b/power-libperfmgr/aidl/Power.h
new file mode 100644
index 0000000..8b90cb4
--- /dev/null
+++ b/power-libperfmgr/aidl/Power.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <thread>
+
+#include <aidl/android/hardware/power/BnPower.h>
+#include <perfmgr/HintManager.h>
+
+#include "disp-power/DisplayLowPower.h"
+#include "disp-power/InteractionHandler.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+using ::InteractionHandler;
+using ::aidl::android::hardware::power::Boost;
+using ::aidl::android::hardware::power::Mode;
+using ::android::perfmgr::HintManager;
+
+class Power : public ::aidl::android::hardware::power::BnPower {
+ public:
+ Power(std::shared_ptr<HintManager> hm, std::shared_ptr<DisplayLowPower> dlpw);
+ ndk::ScopedAStatus setMode(Mode type, bool enabled) override;
+ ndk::ScopedAStatus isModeSupported(Mode type, bool *_aidl_return) override;
+ ndk::ScopedAStatus setBoost(Boost type, int32_t durationMs) override;
+ ndk::ScopedAStatus isBoostSupported(Boost type, bool *_aidl_return) override;
+ binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
+
+ private:
+ std::shared_ptr<HintManager> mHintManager;
+ std::shared_ptr<DisplayLowPower> mDisplayLowPower;
+ std::unique_ptr<InteractionHandler> mInteractionHandler;
+ std::atomic<bool> mVRModeOn;
+ std::atomic<bool> mSustainedPerfModeOn;
+};
+
+} // namespace pixel
+} // namespace impl
+} // namespace power
+} // namespace hardware
+} // namespace google
+} // namespace aidl
diff --git a/power-libperfmgr/aidl/PowerExt.cpp b/power-libperfmgr/aidl/PowerExt.cpp
new file mode 100644
index 0000000..24e855d
--- /dev/null
+++ b/power-libperfmgr/aidl/PowerExt.cpp
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
+#define LOG_TAG "android.hardware.power-service.pixel.ext-libperfmgr"
+
+#include "PowerExt.h"
+
+#include <mutex>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+#include <utils/Log.h>
+#include <utils/Trace.h>
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+ndk::ScopedAStatus PowerExt::setMode(const std::string &mode, bool enabled) {
+ LOG(DEBUG) << "PowerExt setMode: " << mode << " to: " << enabled;
+ ATRACE_INT(mode.c_str(), enabled);
+
+ if (enabled) {
+ mHintManager->DoHint(mode);
+ } else {
+ mHintManager->EndHint(mode);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerExt::isModeSupported(const std::string &mode, bool *_aidl_return) {
+ bool supported = mHintManager->IsHintSupported(mode);
+ LOG(INFO) << "PowerExt mode " << mode << " isModeSupported: " << supported;
+ *_aidl_return = supported;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerExt::setBoost(const std::string &boost, int32_t durationMs) {
+ LOG(DEBUG) << "PowerExt setBoost: " << boost << " duration: " << durationMs;
+ ATRACE_INT(boost.c_str(), durationMs);
+
+ if (durationMs > 0) {
+ mHintManager->DoHint(boost, std::chrono::milliseconds(durationMs));
+ } else if (durationMs == 0) {
+ mHintManager->DoHint(boost);
+ } else {
+ mHintManager->EndHint(boost);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus PowerExt::isBoostSupported(const std::string &boost, bool *_aidl_return) {
+ bool supported = mHintManager->IsHintSupported(boost);
+ LOG(INFO) << "PowerExt boost " << boost << " isBoostSupported: " << supported;
+ *_aidl_return = supported;
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace pixel
+} // namespace impl
+} // namespace power
+} // namespace hardware
+} // namespace google
+} // namespace aidl
diff --git a/power-libperfmgr/aidl/PowerExt.h b/power-libperfmgr/aidl/PowerExt.h
new file mode 100644
index 0000000..65cec2c
--- /dev/null
+++ b/power-libperfmgr/aidl/PowerExt.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <thread>
+
+#include <aidl/google/hardware/power/extension/pixel/BnPowerExt.h>
+#include <perfmgr/HintManager.h>
+
+#include "disp-power/DisplayLowPower.h"
+
+namespace aidl {
+namespace google {
+namespace hardware {
+namespace power {
+namespace impl {
+namespace pixel {
+
+using ::android::perfmgr::HintManager;
+
+class PowerExt : public ::aidl::google::hardware::power::extension::pixel::BnPowerExt {
+ public:
+ PowerExt(std::shared_ptr<HintManager> hm, std::shared_ptr<DisplayLowPower> dlpw)
+ : mHintManager(hm), mDisplayLowPower(dlpw) {}
+ ndk::ScopedAStatus setMode(const std::string &mode, bool enabled) override;
+ ndk::ScopedAStatus isModeSupported(const std::string &mode, bool *_aidl_return) override;
+ ndk::ScopedAStatus setBoost(const std::string &boost, int32_t durationMs) override;
+ ndk::ScopedAStatus isBoostSupported(const std::string &boost, bool *_aidl_return) override;
+
+ private:
+ std::shared_ptr<HintManager> mHintManager;
+ std::shared_ptr<DisplayLowPower> mDisplayLowPower;
+};
+
+} // namespace pixel
+} // namespace impl
+} // namespace power
+} // namespace hardware
+} // namespace google
+} // namespace aidl
diff --git a/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc b/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc
new file mode 100644
index 0000000..8367da9
--- /dev/null
+++ b/power-libperfmgr/aidl/android.hardware.power-service.pixel-libperfmgr.rc
@@ -0,0 +1,17 @@
+service vendor.power-hal-aidl /vendor/bin/hw/android.hardware.power-service.pixel-libperfmgr
+ class hal
+ user root
+ group system
+ priority -20
+
+# restart powerHAL when framework died
+on property:init.svc.zygote=restarting && property:vendor.powerhal.state=*
+ setprop vendor.powerhal.state ""
+ setprop vendor.powerhal.audio ""
+ setprop vendor.powerhal.rendering ""
+ restart vendor.power-hal-aidl
+
+# restart powerHAL when audioHAL died
+on property:init.svc.vendor.audio-hal-2-0=restarting && property:vendor.powerhal.audio=AUDIO_STREAMING_LOW_LATENCY
+ setprop vendor.powerhal.audio ""
+ restart vendor.power-hal-aidl
diff --git a/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml b/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml
new file mode 100644
index 0000000..caf6ea2
--- /dev/null
+++ b/power-libperfmgr/aidl/android.hardware.power-service.pixel.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.power</name>
+ <fqname>IPower/default</fqname>
+ </hal>
+</manifest>
diff --git a/power-libperfmgr/aidl/device.mk b/power-libperfmgr/aidl/device.mk
new file mode 100644
index 0000000..c848597
--- /dev/null
+++ b/power-libperfmgr/aidl/device.mk
@@ -0,0 +1,5 @@
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/power-libperfmgr
+
+# power HAL
+PRODUCT_PACKAGES += \
+ android.hardware.power-service.pixel-libperfmgr
diff --git a/power-libperfmgr/aidl/service.cpp b/power-libperfmgr/aidl/service.cpp
new file mode 100644
index 0000000..aeb6356
--- /dev/null
+++ b/power-libperfmgr/aidl/service.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "android.hardware.power-service.pixel-libperfmgr"
+
+#include <thread>
+
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include "Power.h"
+#include "PowerExt.h"
+#include "disp-power/DisplayLowPower.h"
+
+using aidl::google::hardware::power::impl::pixel::Power;
+using aidl::google::hardware::power::impl::pixel::PowerExt;
+using ::android::perfmgr::HintManager;
+
+constexpr char kPowerHalConfigPath[] = "/vendor/etc/powerhint.json";
+constexpr char kPowerHalInitProp[] = "vendor.powerhal.init";
+
+int main() {
+ LOG(INFO) << "Pixel Power HAL AIDL Service with Extension is starting.";
+
+ // Parse config but do not start the looper
+ std::shared_ptr<HintManager> hm = HintManager::GetFromJSON(kPowerHalConfigPath, false);
+ if (!hm) {
+ LOG(FATAL) << "Invalid config: " << kPowerHalConfigPath;
+ }
+
+ std::shared_ptr<DisplayLowPower> dlpw = std::make_shared<DisplayLowPower>();
+
+ // single thread
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+
+ // core service
+ std::shared_ptr<Power> pw = ndk::SharedRefBase::make<Power>(hm, dlpw);
+ ndk::SpAIBinder pwBinder = pw->asBinder();
+
+ // extension service
+ std::shared_ptr<PowerExt> pwExt = ndk::SharedRefBase::make<PowerExt>(hm, dlpw);
+
+ // attach the extension to the same binder we will be registering
+ CHECK(STATUS_OK == AIBinder_setExtension(pwBinder.get(), pwExt->asBinder().get()));
+
+ const std::string instance = std::string() + Power::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(pw->asBinder().get(), instance.c_str());
+ CHECK(status == STATUS_OK);
+ LOG(INFO) << "Pixel Power HAL AIDL Service with Extension is started.";
+
+ std::thread initThread([&]() {
+ ::android::base::WaitForProperty(kPowerHalInitProp, "1");
+ hm->Start();
+ dlpw->Init();
+ });
+ initThread.detach();
+
+ ABinderProcess_joinThreadPool();
+
+ // should not reach
+ LOG(ERROR) << "Pixel Power HAL AIDL Service with Extension just died.";
+ return EXIT_FAILURE;
+}
diff --git a/power-libperfmgr/disp-power/DisplayLowPower.cpp b/power-libperfmgr/disp-power/DisplayLowPower.cpp
new file mode 100644
index 0000000..d38972a
--- /dev/null
+++ b/power-libperfmgr/disp-power/DisplayLowPower.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "android.hardware.power@-service.pixel-libperfmgr"
+
+#include <errno.h>
+#include <unistd.h>
+
+#include <cutils/sockets.h>
+#include <log/log.h>
+
+#include "DisplayLowPower.h"
+
+DisplayLowPower::DisplayLowPower() : mFossStatus(false) {}
+
+void DisplayLowPower::Init() {
+ ConnectPpsDaemon();
+}
+
+void DisplayLowPower::SetDisplayLowPower(bool enable) {
+ SetFoss(enable);
+}
+
+void DisplayLowPower::ConnectPpsDaemon() {
+ constexpr const char kPpsDaemon[] = "pps";
+
+ mPpsSocket.reset(
+ socket_local_client(kPpsDaemon, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
+ if (mPpsSocket.get() < 0) {
+ ALOGW("Connecting to PPS daemon failed (%s)", strerror(errno));
+ }
+}
+
+int DisplayLowPower::SendPpsCommand(const std::string_view cmd) {
+ if (TEMP_FAILURE_RETRY(write(mPpsSocket.get(), cmd.data(), cmd.size())) < 0) {
+ ALOGE("Failed to send pps command '%s' over socket (%s)", cmd.data(), strerror(errno));
+ return -1;
+ }
+
+ return 0;
+}
+
+void DisplayLowPower::SetFoss(bool enable) {
+ if (mPpsSocket.get() < 0 || mFossStatus == enable) {
+ return;
+ }
+
+ ALOGI("%s foss", (enable) ? "Enable" : "Disable");
+
+ std::string_view foss_cmd;
+ if (enable) {
+ foss_cmd = "foss:on";
+ } else {
+ foss_cmd = "foss:off";
+ }
+
+ if (!SendPpsCommand(foss_cmd)) {
+ mFossStatus = enable;
+ }
+}
diff --git a/power-libperfmgr/disp-power/DisplayLowPower.h b/power-libperfmgr/disp-power/DisplayLowPower.h
new file mode 100644
index 0000000..c0d6c33
--- /dev/null
+++ b/power-libperfmgr/disp-power/DisplayLowPower.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <android-base/unique_fd.h>
+
+#include <string_view>
+
+class DisplayLowPower {
+ public:
+ DisplayLowPower();
+ ~DisplayLowPower() {}
+ void Init();
+ void SetDisplayLowPower(bool enable);
+
+ private:
+ void ConnectPpsDaemon();
+ int SendPpsCommand(const std::string_view cmd);
+ void SetFoss(bool enable);
+
+ android::base::unique_fd mPpsSocket;
+ bool mFossStatus;
+};
diff --git a/power-libperfmgr/InteractionHandler.cpp b/power-libperfmgr/disp-power/InteractionHandler.cpp
index da6a917..1826958 100644
--- a/power-libperfmgr/InteractionHandler.cpp
+++ b/power-libperfmgr/disp-power/InteractionHandler.cpp
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-#define LOG_TAG "android.hardware.power@1.3-service.pixel-libperfmgr"
+#define LOG_TAG "android.hardware.power@-service.pixel-libperfmgr"
#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
#include <fcntl.h>
diff --git a/power-libperfmgr/InteractionHandler.h b/power-libperfmgr/disp-power/InteractionHandler.h
index ba767f1..ba767f1 100644
--- a/power-libperfmgr/InteractionHandler.h
+++ b/power-libperfmgr/disp-power/InteractionHandler.h
diff --git a/power-libperfmgr/display-helper.cpp b/power-libperfmgr/display-helper.cpp
deleted file mode 100644
index 2369c63..0000000
--- a/power-libperfmgr/display-helper.cpp
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#define LOG_NIDEBUG 0
-#define LOG_TAG "android.hardware.power@1.3-service.pixel-libperfmgr"
-
-#include <dlfcn.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/stat.h>
-#include <sys/types.h>
-#include <unistd.h>
-
-#include <cutils/sockets.h>
-#include <log/log.h>
-
-#include "display-helper.h"
-
-#define DAEMON_SOCKET "pps"
-
-static int daemon_socket = -1;
-
-static int connectPPDaemon() {
- // Setup socket connection, if not already done.
- if (daemon_socket < 0)
- daemon_socket =
- socket_local_client(DAEMON_SOCKET, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM);
-
- if (daemon_socket < 0) {
- ALOGE("Connecting to socket failed: %s", strerror(errno));
- return -1;
- }
- return 0;
-}
-
-static int ppdComm(const char *cmd) {
- int ret = -1;
-
- ret = connectPPDaemon();
- if (ret < 0)
- return ret;
-
- ret = write(daemon_socket, cmd, strlen(cmd));
- if (ret < 0) {
- ALOGE("Failed to send data over socket, %s", strerror(errno));
- return ret;
- }
- return 0;
-}
-
-void set_display_lpm(int enable) {
- ALOGI("set_display_lpm state: %d", enable);
- if (enable) {
- ppdComm("foss:on");
- } else {
- ppdComm("foss:off");
- }
-}
diff --git a/power-libperfmgr/AudioStreaming.h b/power-libperfmgr/hidl/AudioStreaming.h
index d8772d5..d8772d5 100644
--- a/power-libperfmgr/AudioStreaming.h
+++ b/power-libperfmgr/hidl/AudioStreaming.h
diff --git a/power-libperfmgr/CameraMode.h b/power-libperfmgr/hidl/CameraMode.h
index 1e05623..1e05623 100644
--- a/power-libperfmgr/CameraMode.h
+++ b/power-libperfmgr/hidl/CameraMode.h
diff --git a/power-libperfmgr/Power.cpp b/power-libperfmgr/hidl/Power.cpp
index f199bfb..2b43b0f 100644
--- a/power-libperfmgr/Power.cpp
+++ b/power-libperfmgr/hidl/Power.cpp
@@ -17,20 +17,21 @@
#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
#define LOG_TAG "android.hardware.power@1.3-service.pixel-libperfmgr"
+#include "Power.h"
+
+#include <mutex>
+
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <mutex>
-
#include <utils/Log.h>
#include <utils/Trace.h>
#include "AudioStreaming.h"
-#include "Power.h"
-#include "display-helper.h"
+#include "disp-power/DisplayLowPower.h"
namespace android {
namespace hardware {
@@ -61,6 +62,7 @@ static const std::map<enum CameraStreamingMode, std::string> kCamStreamingHint =
Power::Power()
: mHintManager(nullptr),
mInteractionHandler(nullptr),
+ mDisplayLowPower(nullptr),
mVRModeOn(false),
mSustainedPerfModeOn(false),
mCameraStreamingMode(CAMERA_STREAMING_OFF),
@@ -73,6 +75,8 @@ Power::Power()
}
mInteractionHandler = std::make_unique<InteractionHandler>(mHintManager);
mInteractionHandler->Init();
+ mDisplayLowPower = std::make_unique<DisplayLowPower>();
+ mDisplayLowPower->Init();
std::string state = android::base::GetProperty(kPowerHalStateProp, "");
if (state == "CAMERA_STREAMING") {
ALOGI("Initialize with CAMERA_STREAMING on");
@@ -198,13 +202,7 @@ Return<void> Power::powerHint(PowerHint_1_0 hint, int32_t data) {
}
break;
case PowerHint_1_0::LOW_POWER:
- if (data) {
- // Device in battery saver mode, enable display low power mode
- set_display_lpm(true);
- } else {
- // Device exiting battery saver mode, disable display low power mode
- set_display_lpm(false);
- }
+ mDisplayLowPower->SetDisplayLowPower(static_cast<bool>(data));
break;
default:
break;
@@ -312,8 +310,8 @@ Return<void> Power::powerHintAsync_1_2(PowerHint_1_2 hint, int32_t data) {
mCameraStreamingMode = mode;
const auto prop = (mCameraStreamingMode == CAMERA_STREAMING_OFF)
- ? ""
- : kCamStreamingHint.at(mode).c_str();
+ ? ""
+ : kCamStreamingHint.at(mode).c_str();
if (!android::base::SetProperty(kPowerHalStateProp, prop)) {
ALOGE("%s: could set powerHAL state %s property", __func__, prop);
}
@@ -366,13 +364,13 @@ Return<void> Power::debug(const hidl_handle &handle, const hidl_vec<hidl_string>
int fd = handle->data[0];
std::string buf(android::base::StringPrintf(
- "HintManager Running: %s\n"
- "VRMode: %s\n"
- "CameraStreamingMode: %s\n"
- "SustainedPerformanceMode: %s\n",
- boolToString(mHintManager->IsRunning()), boolToString(mVRModeOn),
- kCamStreamingHint.at(mCameraStreamingMode).c_str(),
- boolToString(mSustainedPerfModeOn)));
+ "HintManager Running: %s\n"
+ "VRMode: %s\n"
+ "CameraStreamingMode: %s\n"
+ "SustainedPerformanceMode: %s\n",
+ boolToString(mHintManager->IsRunning()), boolToString(mVRModeOn),
+ kCamStreamingHint.at(mCameraStreamingMode).c_str(),
+ boolToString(mSustainedPerfModeOn)));
// Dump nodes through libperfmgr
mHintManager->DumpToFd(fd);
if (!android::base::WriteStringToFd(buf, fd)) {
diff --git a/power-libperfmgr/Power.h b/power-libperfmgr/hidl/Power.h
index b43fecd..9bac407 100644
--- a/power-libperfmgr/Power.h
+++ b/power-libperfmgr/hidl/Power.h
@@ -27,7 +27,8 @@
#include <perfmgr/HintManager.h>
#include "CameraMode.h"
-#include "InteractionHandler.h"
+#include "disp-power/DisplayLowPower.h"
+#include "disp-power/InteractionHandler.h"
namespace android {
namespace hardware {
@@ -72,6 +73,7 @@ class Power : public IPower {
private:
std::shared_ptr<HintManager> mHintManager;
std::unique_ptr<InteractionHandler> mInteractionHandler;
+ std::unique_ptr<DisplayLowPower> mDisplayLowPower;
std::atomic<bool> mVRModeOn;
std::atomic<bool> mSustainedPerfModeOn;
std::atomic<enum CameraStreamingMode> mCameraStreamingMode;
diff --git a/power-libperfmgr/android.hardware.power@1.3-service.pixel-libperfmgr.rc b/power-libperfmgr/hidl/android.hardware.power@1.3-service.pixel-libperfmgr.rc
index 6179f6e..a59c5b6 100644
--- a/power-libperfmgr/android.hardware.power@1.3-service.pixel-libperfmgr.rc
+++ b/power-libperfmgr/hidl/android.hardware.power@1.3-service.pixel-libperfmgr.rc
@@ -10,17 +10,17 @@ service vendor.power-hal-1-3 /vendor/bin/hw/android.hardware.power@1.3-service.p
# restart powerHAL when framework died
on property:init.svc.zygote=restarting && property:vendor.powerhal.state=*
- setprop vendor.powerhal.state ""
- setprop vendor.powerhal.audio ""
- setprop vendor.powerhal.rendering ""
- restart vendor.power-hal-1-3
+ setprop vendor.powerhal.state ""
+ setprop vendor.powerhal.audio ""
+ setprop vendor.powerhal.rendering ""
+ restart vendor.power-hal-1-3
# restart powerHAL when cameraHAL died
on property:init.svc.vendor.camera-provider-2-4=restarting && property:vendor.powerhal.state=CAMERA_STREAMING
- setprop vendor.powerhal.state ""
- restart vendor.power-hal-1-3
+ setprop vendor.powerhal.state ""
+ restart vendor.power-hal-1-3
# restart powerHAL when audioHAL died
on property:init.svc.vendor.audio-hal-2-0=restarting && property:vendor.powerhal.audio=AUDIO_LOW_LATENCY
- setprop vendor.powerhal.audio ""
- restart vendor.power-hal-1-3
+ setprop vendor.powerhal.audio ""
+ restart vendor.power-hal-1-3
diff --git a/power-libperfmgr/android.hardware.power@1.3-service.pixel.xml b/power-libperfmgr/hidl/android.hardware.power@1.3-service.pixel.xml
index e52398c..e52398c 100644
--- a/power-libperfmgr/android.hardware.power@1.3-service.pixel.xml
+++ b/power-libperfmgr/hidl/android.hardware.power@1.3-service.pixel.xml
diff --git a/power-libperfmgr/hidl/device.mk b/power-libperfmgr/hidl/device.mk
new file mode 100644
index 0000000..5de6a2c
--- /dev/null
+++ b/power-libperfmgr/hidl/device.mk
@@ -0,0 +1,5 @@
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/power-libperfmgr
+
+# power HAL
+PRODUCT_PACKAGES += \
+ android.hardware.power@1.3-service.pixel-libperfmgr
diff --git a/power-libperfmgr/service.cpp b/power-libperfmgr/hidl/service.cpp
index 7bcc907..7bcc907 100644
--- a/power-libperfmgr/service.cpp
+++ b/power-libperfmgr/hidl/service.cpp
diff --git a/power-libperfmgr/libperfmgr/Android.bp b/power-libperfmgr/libperfmgr/Android.bp
new file mode 100644
index 0000000..c84e731
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/Android.bp
@@ -0,0 +1,78 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+cc_defaults {
+ name: "libperfmgr_defaults",
+ local_include_dirs: ["include"],
+ shared_libs: [
+ "libbase",
+ "libcutils",
+ "libutils",
+ ],
+ static_libs: [
+ "libjsoncpp",
+ ],
+ cflags: [
+ "-Wall",
+ "-Werror",
+ ],
+ tidy: true,
+ tidy_checks: [
+ "android-*",
+ "cert-*",
+ "clang-analyzer-security*",
+ ],
+ tidy_flags: [
+ "-warnings-as-errors=android-*,clang-analyzer-security*,cert-*"
+ ],
+}
+
+cc_library {
+ name: "libperfmgr",
+ vendor_available: true,
+ defaults: ["libperfmgr_defaults"],
+ export_include_dirs: ["include"],
+ srcs: [
+ "RequestGroup.cc",
+ "Node.cc",
+ "FileNode.cc",
+ "PropertyNode.cc",
+ "NodeLooperThread.cc",
+ "HintManager.cc",
+ ]
+}
+
+cc_test {
+ name: "libperfmgr_test",
+ defaults: ["libperfmgr_defaults"],
+ static_libs: ["libperfmgr"],
+ srcs: [
+ "tests/RequestGroupTest.cc",
+ "tests/FileNodeTest.cc",
+ "tests/PropertyNodeTest.cc",
+ "tests/NodeLooperThreadTest.cc",
+ "tests/HintManagerTest.cc",
+ ]
+}
+
+cc_binary {
+ name: "perfmgr_config_verifier",
+ defaults: ["libperfmgr_defaults"],
+ static_libs: ["libperfmgr"],
+ srcs: [
+ "tools/ConfigVerifier.cc",
+ ]
+}
diff --git a/power-libperfmgr/libperfmgr/FileNode.cc b/power-libperfmgr/libperfmgr/FileNode.cc
new file mode 100644
index 0000000..070df54
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/FileNode.cc
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
+#define LOG_TAG "libperfmgr"
+
+#include "perfmgr/FileNode.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace perfmgr {
+
+FileNode::FileNode(std::string name, std::string node_path,
+ std::vector<RequestGroup> req_sorted,
+ std::size_t default_val_index, bool reset_on_init,
+ bool hold_fd)
+ : Node(std::move(name), std::move(node_path), std::move(req_sorted),
+ default_val_index, reset_on_init),
+ hold_fd_(hold_fd),
+ warn_timeout_(
+ android::base::GetBoolProperty("ro.debuggable", false) ? 5ms : 50ms) {
+}
+
+std::chrono::milliseconds FileNode::Update(bool log_error) {
+ std::size_t value_index = default_val_index_;
+ std::chrono::milliseconds expire_time = std::chrono::milliseconds::max();
+
+ // Find the highest outstanding request's expire time
+ for (std::size_t i = 0; i < req_sorted_.size(); i++) {
+ if (req_sorted_[i].GetExpireTime(&expire_time)) {
+ value_index = i;
+ break;
+ }
+ }
+
+ // Update node only if request index changes
+ if (value_index != current_val_index_ || reset_on_init_) {
+ ATRACE_BEGIN(GetName().c_str());
+ const std::string& req_value =
+ req_sorted_[value_index].GetRequestValue();
+
+ android::base::Timer t;
+ fd_.reset(TEMP_FAILURE_RETRY(
+ open(node_path_.c_str(), O_WRONLY | O_CLOEXEC | O_TRUNC)));
+
+ if (fd_ == -1 || !android::base::WriteStringToFd(req_value, fd_)) {
+ if (log_error) {
+ LOG(WARNING) << "Failed to write to node: " << node_path_
+ << " with value: " << req_value << ", fd: " << fd_;
+ }
+ // Retry in 500ms or sooner
+ expire_time = std::min(expire_time, std::chrono::milliseconds(500));
+ } else {
+ // For regular file system, we need fsync
+ fsync(fd_);
+ // Some dev node requires file to remain open during the entire hint
+ // duration e.g. /dev/cpu_dma_latency, so fd_ is intentionally kept
+ // open during any requested value other than default one. If
+ // request a default value, node will write the value and then
+ // release the fd.
+ if ((!hold_fd_) || value_index == default_val_index_) {
+ fd_.reset();
+ }
+ auto duration = t.duration();
+ if (duration > warn_timeout_) {
+ LOG(WARNING) << "Slow writing to file: '" << node_path_
+ << "' with value: '" << req_value
+ << "' took: " << duration.count() << " ms";
+ }
+ // Update current index only when succeed
+ current_val_index_ = value_index;
+ reset_on_init_ = false;
+ }
+ ATRACE_END();
+ }
+ return expire_time;
+}
+
+bool FileNode::GetHoldFd() const {
+ return hold_fd_;
+}
+
+void FileNode::DumpToFd(int fd) const {
+ std::string node_value;
+ if (!android::base::ReadFileToString(node_path_, &node_value)) {
+ LOG(ERROR) << "Failed to read node path: " << node_path_;
+ }
+ node_value = android::base::Trim(node_value);
+ std::string buf(android::base::StringPrintf(
+ "%s\t%s\t%zu\t%s\n", name_.c_str(), node_path_.c_str(),
+ current_val_index_, node_value.c_str()));
+ if (!android::base::WriteStringToFd(buf, fd)) {
+ LOG(ERROR) << "Failed to dump fd: " << fd;
+ }
+ for (std::size_t i = 0; i < req_sorted_.size(); i++) {
+ req_sorted_[i].DumpToFd(
+ fd, android::base::StringPrintf("\t\tReq%zu:\t", i));
+ }
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/HintManager.cc b/power-libperfmgr/libperfmgr/HintManager.cc
new file mode 100644
index 0000000..1b546ae
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/HintManager.cc
@@ -0,0 +1,392 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "libperfmgr"
+
+#include "perfmgr/HintManager.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <json/reader.h>
+#include <json/value.h>
+
+#include <algorithm>
+#include <set>
+
+#include "perfmgr/FileNode.h"
+#include "perfmgr/PropertyNode.h"
+
+namespace android {
+namespace perfmgr {
+
+bool HintManager::ValidateHint(const std::string& hint_type) const {
+ if (nm_.get() == nullptr) {
+ LOG(ERROR) << "NodeLooperThread not present";
+ return false;
+ }
+ return IsHintSupported(hint_type);
+}
+
+bool HintManager::IsHintSupported(const std::string& hint_type) const {
+ if (actions_.find(hint_type) == actions_.end()) {
+ LOG(INFO) << "Hint type not present in actions: " << hint_type;
+ return false;
+ }
+ return true;
+}
+
+bool HintManager::DoHint(const std::string& hint_type) {
+ LOG(VERBOSE) << "Do Powerhint: " << hint_type;
+ return ValidateHint(hint_type)
+ ? nm_->Request(actions_.at(hint_type), hint_type)
+ : false;
+}
+
+bool HintManager::DoHint(const std::string& hint_type,
+ std::chrono::milliseconds timeout_ms_override) {
+ LOG(VERBOSE) << "Do Powerhint: " << hint_type << " for "
+ << timeout_ms_override.count() << "ms";
+ if (!ValidateHint(hint_type)) {
+ return false;
+ }
+ std::vector<NodeAction> actions_override = actions_.at(hint_type);
+ for (auto& action : actions_override) {
+ action.timeout_ms = timeout_ms_override;
+ }
+ return nm_->Request(actions_override, hint_type);
+}
+
+bool HintManager::EndHint(const std::string& hint_type) {
+ LOG(VERBOSE) << "End Powerhint: " << hint_type;
+ return ValidateHint(hint_type)
+ ? nm_->Cancel(actions_.at(hint_type), hint_type)
+ : false;
+}
+
+bool HintManager::IsRunning() const {
+ return (nm_.get() == nullptr) ? false : nm_->isRunning();
+}
+
+std::vector<std::string> HintManager::GetHints() const {
+ std::vector<std::string> hints;
+ for (auto const& action : actions_) {
+ hints.push_back(action.first);
+ }
+ return hints;
+}
+
+void HintManager::DumpToFd(int fd) {
+ std::string header(
+ "========== Begin perfmgr nodes ==========\n"
+ "Node Name\t"
+ "Node Path\t"
+ "Current Index\t"
+ "Current Value\n");
+ if (!android::base::WriteStringToFd(header, fd)) {
+ LOG(ERROR) << "Failed to dump fd: " << fd;
+ }
+ nm_->DumpToFd(fd);
+ std::string footer("========== End perfmgr nodes ==========\n");
+ if (!android::base::WriteStringToFd(footer, fd)) {
+ LOG(ERROR) << "Failed to dump fd: " << fd;
+ }
+ fsync(fd);
+}
+
+bool HintManager::Start() {
+ return nm_->Start();
+}
+
+std::unique_ptr<HintManager> HintManager::GetFromJSON(
+ const std::string& config_path, bool start) {
+ std::string json_doc;
+
+ if (!android::base::ReadFileToString(config_path, &json_doc)) {
+ LOG(ERROR) << "Failed to read JSON config from " << config_path;
+ return nullptr;
+ }
+
+ std::vector<std::unique_ptr<Node>> nodes = ParseNodes(json_doc);
+ if (nodes.empty()) {
+ LOG(ERROR) << "Failed to parse Nodes section from " << config_path;
+ return nullptr;
+ }
+ std::map<std::string, std::vector<NodeAction>> actions =
+ HintManager::ParseActions(json_doc, nodes);
+
+ if (actions.empty()) {
+ LOG(ERROR) << "Failed to parse Actions section from " << config_path;
+ return nullptr;
+ }
+
+ sp<NodeLooperThread> nm = new NodeLooperThread(std::move(nodes));
+ std::unique_ptr<HintManager> hm =
+ std::make_unique<HintManager>(std::move(nm), actions);
+
+ LOG(INFO) << "Initialized HintManager from JSON config: " << config_path;
+
+ if (start) {
+ hm->Start();
+ }
+ return hm;
+}
+
+std::vector<std::unique_ptr<Node>> HintManager::ParseNodes(
+ const std::string& json_doc) {
+ // function starts
+ std::vector<std::unique_ptr<Node>> nodes_parsed;
+ std::set<std::string> nodes_name_parsed;
+ std::set<std::string> nodes_path_parsed;
+ Json::Value root;
+ Json::Reader reader;
+
+ if (!reader.parse(json_doc, root)) {
+ LOG(ERROR) << "Failed to parse JSON config";
+ return nodes_parsed;
+ }
+
+ Json::Value nodes = root["Nodes"];
+ for (Json::Value::ArrayIndex i = 0; i < nodes.size(); ++i) {
+ std::string name = nodes[i]["Name"].asString();
+ LOG(VERBOSE) << "Node[" << i << "]'s Name: " << name;
+ if (name.empty()) {
+ LOG(ERROR) << "Failed to read "
+ << "Node[" << i << "]'s Name";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+
+ auto result = nodes_name_parsed.insert(name);
+ if (!result.second) {
+ LOG(ERROR) << "Duplicate Node[" << i << "]'s Name";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+
+ std::string path = nodes[i]["Path"].asString();
+ LOG(VERBOSE) << "Node[" << i << "]'s Path: " << path;
+ if (path.empty()) {
+ LOG(ERROR) << "Failed to read "
+ << "Node[" << i << "]'s Path";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+
+ result = nodes_path_parsed.insert(path);
+ if (!result.second) {
+ LOG(ERROR) << "Duplicate Node[" << i << "]'s Path";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+
+ bool is_file = true;
+ std::string node_type = nodes[i]["Type"].asString();
+ LOG(VERBOSE) << "Node[" << i << "]'s Type: " << node_type;
+ if (node_type.empty()) {
+ LOG(ERROR) << "Failed to read "
+ << "Node[" << i << "]'s Type, set to 'File' as default";
+ } else if (node_type == "File") {
+ is_file = true;
+ } else if (node_type == "Property") {
+ is_file = false;
+ } else {
+ LOG(ERROR) << "Invalid Node[" << i
+ << "]'s Type: only File and Property supported.";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+
+ std::vector<RequestGroup> values_parsed;
+ std::set<std::string> values_set_parsed;
+ Json::Value values = nodes[i]["Values"];
+ for (Json::Value::ArrayIndex j = 0; j < values.size(); ++j) {
+ std::string value = values[j].asString();
+ LOG(VERBOSE) << "Node[" << i << "]'s Value[" << j << "]: " << value;
+ auto result = values_set_parsed.insert(value);
+ if (!result.second) {
+ LOG(ERROR) << "Duplicate value parsed in Node[" << i
+ << "]'s Value[" << j << "]";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+ if (is_file && value.empty()) {
+ LOG(ERROR) << "Failed to read Node[" << i << "]'s Value[" << j
+ << "]";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+ values_parsed.emplace_back(value);
+ }
+ if (values_parsed.size() < 1) {
+ LOG(ERROR) << "Failed to read Node[" << i << "]'s Values";
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+
+ Json::UInt64 default_index = values_parsed.size() - 1;
+ if (nodes[i]["DefaultIndex"].empty() ||
+ !nodes[i]["DefaultIndex"].isUInt64()) {
+ LOG(INFO) << "Failed to read Node[" << i
+ << "]'s DefaultIndex, set to last index: "
+ << default_index;
+ } else {
+ default_index = nodes[i]["DefaultIndex"].asUInt64();
+ }
+ if (default_index > values_parsed.size() - 1) {
+ default_index = values_parsed.size() - 1;
+ LOG(ERROR) << "Node[" << i
+ << "]'s DefaultIndex out of bound, max value index: "
+ << default_index;
+ nodes_parsed.clear();
+ return nodes_parsed;
+ }
+ LOG(VERBOSE) << "Node[" << i << "]'s DefaultIndex: " << default_index;
+
+ bool reset = false;
+ if (nodes[i]["ResetOnInit"].empty() ||
+ !nodes[i]["ResetOnInit"].isBool()) {
+ LOG(INFO) << "Failed to read Node[" << i
+ << "]'s ResetOnInit, set to 'false'";
+ } else {
+ reset = nodes[i]["ResetOnInit"].asBool();
+ }
+ LOG(VERBOSE) << "Node[" << i << "]'s ResetOnInit: " << std::boolalpha
+ << reset << std::noboolalpha;
+
+ if (is_file) {
+ bool hold_fd = false;
+ if (nodes[i]["HoldFd"].empty() || !nodes[i]["HoldFd"].isBool()) {
+ LOG(INFO) << "Failed to read Node[" << i
+ << "]'s HoldFd, set to 'false'";
+ } else {
+ hold_fd = nodes[i]["HoldFd"].asBool();
+ }
+ LOG(VERBOSE) << "Node[" << i << "]'s HoldFd: " << std::boolalpha
+ << hold_fd << std::noboolalpha;
+
+ nodes_parsed.emplace_back(std::make_unique<FileNode>(
+ name, path, values_parsed,
+ static_cast<std::size_t>(default_index), reset, hold_fd));
+ } else {
+ nodes_parsed.emplace_back(std::make_unique<PropertyNode>(
+ name, path, values_parsed,
+ static_cast<std::size_t>(default_index), reset));
+ }
+ }
+ LOG(INFO) << nodes_parsed.size() << " Nodes parsed successfully";
+ return nodes_parsed;
+}
+
+std::map<std::string, std::vector<NodeAction>> HintManager::ParseActions(
+ const std::string& json_doc,
+ const std::vector<std::unique_ptr<Node>>& nodes) {
+ // function starts
+ std::map<std::string, std::vector<NodeAction>> actions_parsed;
+ Json::Value root;
+ Json::Reader reader;
+
+ if (!reader.parse(json_doc, root)) {
+ LOG(ERROR) << "Failed to parse JSON config";
+ return actions_parsed;
+ }
+
+ Json::Value actions = root["Actions"];
+ std::size_t total_parsed = 0;
+
+ std::map<std::string, std::size_t> nodes_index;
+ for (std::size_t i = 0; i < nodes.size(); ++i) {
+ nodes_index[nodes[i]->GetName()] = i;
+ }
+
+ for (Json::Value::ArrayIndex i = 0; i < actions.size(); ++i) {
+ const std::string& hint_type = actions[i]["PowerHint"].asString();
+ LOG(VERBOSE) << "Action[" << i << "]'s PowerHint: " << hint_type;
+ if (hint_type.empty()) {
+ LOG(ERROR) << "Failed to read "
+ << "Action[" << i << "]'s PowerHint";
+ actions_parsed.clear();
+ return actions_parsed;
+ }
+
+ std::string node_name = actions[i]["Node"].asString();
+ LOG(VERBOSE) << "Action[" << i << "]'s Node: " << node_name;
+ std::size_t node_index;
+
+ if (nodes_index.find(node_name) == nodes_index.end()) {
+ LOG(ERROR) << "Failed to find "
+ << "Action[" << i << "]'s Node from Nodes section: ["
+ << node_name << "]";
+ actions_parsed.clear();
+ return actions_parsed;
+ }
+ node_index = nodes_index[node_name];
+
+ std::string value_name = actions[i]["Value"].asString();
+ LOG(VERBOSE) << "Action[" << i << "]'s Value: " << value_name;
+ std::size_t value_index = 0;
+
+ if (!nodes[node_index]->GetValueIndex(value_name, &value_index)) {
+ LOG(ERROR) << "Failed to read Action[" << i << "]'s Value";
+ LOG(ERROR) << "Action[" << i << "]'s Value " << value_name
+ << " is not defined in Node[" << node_name << "]";
+ actions_parsed.clear();
+ return actions_parsed;
+ }
+ LOG(VERBOSE) << "Action[" << i << "]'s ValueIndex: " << value_index;
+
+ Json::UInt64 duration = 0;
+ if (actions[i]["Duration"].empty() ||
+ !actions[i]["Duration"].isUInt64()) {
+ LOG(ERROR) << "Failed to read Action[" << i << "]'s Duration";
+ actions_parsed.clear();
+ return actions_parsed;
+ } else {
+ duration = actions[i]["Duration"].asUInt64();
+ }
+ LOG(VERBOSE) << "Action[" << i << "]'s Duration: " << duration;
+
+ if (actions_parsed.find(hint_type) == actions_parsed.end()) {
+ actions_parsed[hint_type] = std::vector<NodeAction>{
+ {node_index, value_index, std::chrono::milliseconds(duration)}};
+ } else {
+ for (const auto& action : actions_parsed[hint_type]) {
+ if (action.node_index == node_index) {
+ LOG(ERROR)
+ << "Action[" << i
+ << "]'s NodeIndex is duplicated with another Action";
+ actions_parsed.clear();
+ return actions_parsed;
+ }
+ }
+ actions_parsed[hint_type].emplace_back(
+ node_index, value_index, std::chrono::milliseconds(duration));
+ }
+
+ ++total_parsed;
+ }
+
+ LOG(INFO) << total_parsed << " Actions parsed successfully";
+
+ for (const auto& action : actions_parsed) {
+ LOG(INFO) << "PowerHint " << action.first << " has "
+ << action.second.size() << " actions parsed";
+ }
+
+ return actions_parsed;
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/Node.cc b/power-libperfmgr/libperfmgr/Node.cc
new file mode 100644
index 0000000..eb180e6
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/Node.cc
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "libperfmgr"
+
+#include "perfmgr/Node.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+namespace android {
+namespace perfmgr {
+
+Node::Node(std::string name, std::string node_path,
+ std::vector<RequestGroup> req_sorted, std::size_t default_val_index,
+ bool reset_on_init)
+ : name_(std::move(name)),
+ node_path_(std::move(node_path)),
+ req_sorted_(std::move(req_sorted)),
+ default_val_index_(default_val_index),
+ reset_on_init_(reset_on_init),
+ current_val_index_(default_val_index) {}
+
+bool Node::AddRequest(std::size_t value_index, const std::string& hint_type,
+ ReqTime end_time) {
+ if (value_index >= req_sorted_.size()) {
+ LOG(ERROR) << "Value index out of bound: " << value_index
+ << " ,size: " << req_sorted_.size();
+ return false;
+ }
+ // Add/Update request to the new end_time for the specific hint_type
+ req_sorted_[value_index].AddRequest(hint_type, end_time);
+ return true;
+}
+
+bool Node::RemoveRequest(const std::string& hint_type) {
+ bool ret = false;
+ // Remove all requests for the specific hint_type
+ for (auto& value : req_sorted_) {
+ ret = value.RemoveRequest(hint_type) || ret;
+ }
+ return ret;
+}
+
+const std::string& Node::GetName() const {
+ return name_;
+}
+
+const std::string& Node::GetPath() const {
+ return node_path_;
+}
+
+bool Node::GetValueIndex(const std::string& value, std::size_t* index) const {
+ bool found = false;
+ for (std::size_t i = 0; i < req_sorted_.size(); i++) {
+ if (req_sorted_[i].GetRequestValue() == value) {
+ *index = i;
+ found = true;
+ break;
+ }
+ }
+ return found;
+}
+
+std::size_t Node::GetDefaultIndex() const {
+ return default_val_index_;
+}
+
+bool Node::GetResetOnInit() const {
+ return reset_on_init_;
+}
+
+std::vector<std::string> Node::GetValues() const {
+ std::vector<std::string> values;
+ for (const auto& value : req_sorted_) {
+ values.emplace_back(value.GetRequestValue());
+ }
+ return values;
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/NodeLooperThread.cc b/power-libperfmgr/libperfmgr/NodeLooperThread.cc
new file mode 100644
index 0000000..39ffc2e
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/NodeLooperThread.cc
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
+#define LOG_TAG "libperfmgr"
+
+#include "perfmgr/NodeLooperThread.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace perfmgr {
+
+bool NodeLooperThread::Request(const std::vector<NodeAction>& actions,
+ const std::string& hint_type) {
+ if (::android::Thread::exitPending()) {
+ LOG(WARNING) << "NodeLooperThread is exiting";
+ return false;
+ }
+ if (!::android::Thread::isRunning()) {
+ LOG(WARNING) << "NodeLooperThread is not running, request " << hint_type;
+ }
+
+ bool ret = true;
+ ::android::AutoMutex _l(lock_);
+ for (const auto& a : actions) {
+ if (a.node_index >= nodes_.size()) {
+ LOG(ERROR) << "Node index out of bound: " << a.node_index
+ << " ,size: " << nodes_.size();
+ ret = false;
+ } else {
+ // End time set to steady time point max
+ ReqTime end_time = ReqTime::max();
+ // Timeout is non-zero
+ if (a.timeout_ms != std::chrono::milliseconds::zero()) {
+ auto now = std::chrono::steady_clock::now();
+ // Overflow protection in case timeout_ms is too big to overflow
+ // time point which is unsigned integer
+ if (std::chrono::duration_cast<std::chrono::milliseconds>(
+ ReqTime::max() - now) > a.timeout_ms) {
+ end_time = now + a.timeout_ms;
+ }
+ }
+ ret = nodes_[a.node_index]->AddRequest(a.value_index, hint_type,
+ end_time) &&
+ ret;
+ }
+ }
+ wake_cond_.signal();
+ return ret;
+}
+
+bool NodeLooperThread::Cancel(const std::vector<NodeAction>& actions,
+ const std::string& hint_type) {
+ if (::android::Thread::exitPending()) {
+ LOG(WARNING) << "NodeLooperThread is exiting";
+ return false;
+ }
+ if (!::android::Thread::isRunning()) {
+ LOG(WARNING) << "NodeLooperThread is not running, cancel " << hint_type;
+ }
+
+ bool ret = true;
+ ::android::AutoMutex _l(lock_);
+ for (const auto& a : actions) {
+ if (a.node_index >= nodes_.size()) {
+ LOG(ERROR) << "Node index out of bound: " << a.node_index
+ << " ,size: " << nodes_.size();
+ ret = false;
+ } else {
+ nodes_[a.node_index]->RemoveRequest(hint_type);
+ }
+ }
+ wake_cond_.signal();
+ return ret;
+}
+
+void NodeLooperThread::DumpToFd(int fd) {
+ ::android::AutoMutex _l(lock_);
+ for (auto& n : nodes_) {
+ n->DumpToFd(fd);
+ }
+}
+
+bool NodeLooperThread::threadLoop() {
+ ::android::AutoMutex _l(lock_);
+ std::chrono::milliseconds timeout_ms = kMaxUpdatePeriod;
+
+ // Update 2 passes: some node may have dependency in other node
+ // e.g. update cpufreq min to VAL while cpufreq max still set to
+ // a value lower than VAL, is expected to fail in first pass
+ ATRACE_BEGIN("update_nodes");
+ for (auto& n : nodes_) {
+ n->Update(false);
+ }
+ for (auto& n : nodes_) {
+ timeout_ms = std::min(n->Update(true), timeout_ms);
+ }
+ ATRACE_END();
+
+ nsecs_t sleep_timeout_ns = std::numeric_limits<nsecs_t>::max();
+ if (timeout_ms.count() < sleep_timeout_ns / 1000 / 1000) {
+ sleep_timeout_ns = timeout_ms.count() * 1000 * 1000;
+ }
+ // VERBOSE level won't print by default in user/userdebug build
+ LOG(VERBOSE) << "NodeLooperThread will wait for " << sleep_timeout_ns
+ << "ns";
+ ATRACE_BEGIN("wait");
+ wake_cond_.waitRelative(lock_, sleep_timeout_ns);
+ ATRACE_END();
+ return true;
+}
+
+bool NodeLooperThread::Start() {
+ auto ret = this->run("NodeLooperThread", PRIORITY_HIGHEST);
+ if (ret != NO_ERROR) {
+ LOG(ERROR) << "NodeLooperThread start failed: " << ret;
+ } else {
+ LOG(INFO) << "NodeLooperThread started";
+ }
+ return ret == NO_ERROR;
+}
+
+void NodeLooperThread::Stop() {
+ if (::android::Thread::isRunning()) {
+ LOG(INFO) << "NodeLooperThread stopping";
+ {
+ ::android::AutoMutex _l(lock_);
+ wake_cond_.signal();
+ ::android::Thread::requestExit();
+ }
+ ::android::Thread::join();
+ LOG(INFO) << "NodeLooperThread stopped";
+ }
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/PropertyNode.cc b/power-libperfmgr/libperfmgr/PropertyNode.cc
new file mode 100644
index 0000000..a06b304
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/PropertyNode.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define ATRACE_TAG (ATRACE_TAG_POWER | ATRACE_TAG_HAL)
+#define LOG_TAG "libperfmgr"
+
+#include "perfmgr/PropertyNode.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+#include <utils/Trace.h>
+
+namespace android {
+namespace perfmgr {
+
+PropertyNode::PropertyNode(std::string name, std::string node_path,
+ std::vector<RequestGroup> req_sorted,
+ std::size_t default_val_index, bool reset_on_init)
+ : Node(std::move(name), std::move(node_path), std::move(req_sorted),
+ default_val_index, reset_on_init) {}
+
+std::chrono::milliseconds PropertyNode::Update(bool) {
+ std::size_t value_index = default_val_index_;
+ std::chrono::milliseconds expire_time = std::chrono::milliseconds::max();
+
+ // Find the highest outstanding request's expire time
+ for (std::size_t i = 0; i < req_sorted_.size(); i++) {
+ if (req_sorted_[i].GetExpireTime(&expire_time)) {
+ value_index = i;
+ break;
+ }
+ }
+
+ // Update node only if request index changes
+ if (value_index != current_val_index_ || reset_on_init_) {
+ ATRACE_BEGIN(GetName().c_str());
+ const std::string& req_value =
+ req_sorted_[value_index].GetRequestValue();
+
+ if (!android::base::SetProperty(node_path_, req_value)) {
+ LOG(WARNING) << "Failed to set property to : " << node_path_
+ << " with value: " << req_value;
+ } else {
+ // Update current index only when succeed
+ current_val_index_ = value_index;
+ reset_on_init_ = false;
+ }
+ ATRACE_END();
+ }
+ return expire_time;
+}
+
+void PropertyNode::DumpToFd(int fd) const {
+ std::string node_value = android::base::GetProperty(node_path_, "");
+ std::string buf(android::base::StringPrintf(
+ "%s\t%s\t%zu\t%s\n", name_.c_str(), node_path_.c_str(),
+ current_val_index_, node_value.c_str()));
+ if (!android::base::WriteStringToFd(buf, fd)) {
+ LOG(ERROR) << "Failed to dump fd: " << fd;
+ }
+ for (std::size_t i = 0; i < req_sorted_.size(); i++) {
+ req_sorted_[i].DumpToFd(
+ fd, android::base::StringPrintf("\t\tReq%zu:\t", i));
+ }
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/RequestGroup.cc b/power-libperfmgr/libperfmgr/RequestGroup.cc
new file mode 100644
index 0000000..4fedd33
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/RequestGroup.cc
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "libperfmgr"
+
+#include "perfmgr/RequestGroup.h"
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+
+#include <sstream>
+
+namespace android {
+namespace perfmgr {
+
+bool RequestGroup::AddRequest(const std::string& hint_type, ReqTime end_time) {
+ if (request_map_.find(hint_type) == request_map_.end()) {
+ request_map_.emplace(hint_type, end_time);
+ return true;
+ } else {
+ if (request_map_[hint_type] < end_time) {
+ request_map_[hint_type] = end_time;
+ }
+ return false;
+ }
+}
+
+bool RequestGroup::RemoveRequest(const std::string& hint_type) {
+ return request_map_.erase(hint_type);
+}
+
+const std::string& RequestGroup::GetRequestValue() const {
+ return request_value_;
+}
+
+bool RequestGroup::GetExpireTime(std::chrono::milliseconds* expire_time) {
+ ReqTime now = std::chrono::steady_clock::now();
+ *expire_time = std::chrono::milliseconds::max();
+
+ bool active = false;
+ for (auto it = request_map_.begin(); it != request_map_.end();) {
+ auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
+ it->second - now);
+ if (duration <= std::chrono::milliseconds::zero()) {
+ it = request_map_.erase(it);
+ } else {
+ *expire_time = std::min(duration, *expire_time);
+ active = true;
+ ++it;
+ }
+ }
+ return active;
+}
+
+void RequestGroup::DumpToFd(int fd, const std::string& prefix) const {
+ std::ostringstream dump_buf;
+ ReqTime now = std::chrono::steady_clock::now();
+ for (auto it = request_map_.begin(); it != request_map_.end(); it++) {
+ auto remaining_duration =
+ std::chrono::duration_cast<std::chrono::milliseconds>(it->second -
+ now);
+ dump_buf << prefix << it->first << "\t" << remaining_duration.count()
+ << "\t" << request_value_ << "\n";
+ }
+ if (!android::base::WriteStringToFd(dump_buf.str(), fd)) {
+ LOG(ERROR) << "Failed to dump fd: " << fd;
+ }
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/TEST_MAPPING b/power-libperfmgr/libperfmgr/TEST_MAPPING
new file mode 100644
index 0000000..f40eb38
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/TEST_MAPPING
@@ -0,0 +1,12 @@
+{
+ "presubmit": [
+ {
+ "name": "libperfmgr_test"
+ }
+ ],
+ "pts-experimental": [
+ {
+ "name": "libperfmgr_test"
+ }
+ ]
+}
diff --git a/power-libperfmgr/libperfmgr/config_schema.json b/power-libperfmgr/libperfmgr/config_schema.json
new file mode 100644
index 0000000..712af50
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/config_schema.json
@@ -0,0 +1,126 @@
+{
+ "definitions": {},
+ "$schema": "http://json-schema.org/draft-06/schema#",
+ "type": "object",
+ "id": "config_schema.json",
+ "required": [
+ "Nodes",
+ "Actions"
+ ],
+ "properties": {
+ "Nodes": {
+ "type": "array",
+ "id": "/properties/Nodes",
+ "minItems": 1,
+ "uniqueItems": true,
+ "items": {
+ "type": "object",
+ "id": "/properties/Nodes/items",
+ "required": [
+ "Name",
+ "Path",
+ "Values"
+ ],
+ "properties": {
+ "Name": {
+ "type": "string",
+ "id": "/properties/Nodes/items/properties/Name",
+ "title": "The Name Schema.",
+ "description": "The name of the node.",
+ "minLength": 1
+ },
+ "Path": {
+ "type": "string",
+ "id": "/properties/Nodes/items/properties/Path",
+ "title": "The Path Schema.",
+ "description": "For File type node, it is filesystem path of the file; for Property type node, it is the key of the property.",
+ "minLength": 1
+ },
+ "Values": {
+ "type": "array",
+ "id": "/properties/Nodes/items/properties/Values",
+ "minItems": 1,
+ "uniqueItems": true,
+ "items": {
+ "type": "string",
+ "id": "/properties/Nodes/items/properties/Values/items",
+ "title": "The Values Schema.",
+ "description": "The Values array lists all possible values that can be set in the Actions section, and the list of values is sorted based on their priority, with the highest priority first."
+ }
+ },
+ "DefaultIndex": {
+ "type": "integer",
+ "id": "/properties/Nodes/items/properties/DefaultIndex",
+ "title": "The Default Index Schema.",
+ "description": "The default index of the node, if not present, it will be set to max index of Values.",
+ "minimum": 0
+ },
+ "ResetOnInit": {
+ "type": "boolean",
+ "id": "/properties/Nodes/items/properties/ResetOnInit",
+ "title": "The Reset On Init Schema.",
+ "description": "Flag if node will be set to default value on initialization; if not present, it will be set to false."
+ },
+ "Type": {
+ "type": "string",
+ "id": "/properties/Nodes/items/properties/Type",
+ "title": "The type Schema.",
+ "description": "Type of Node (File or Property), if not present, it will be set to File."
+ },
+ "HoldFd": {
+ "type": "boolean",
+ "id": "/properties/Nodes/items/properties/HoldFd",
+ "title": "The Hold Fd Schema.",
+ "description": "Flag if node will hold the file descriptor on non-default values; if not present, it will be set to false. This is only honoured for File type node."
+ }
+ }
+ }
+ },
+ "Actions": {
+ "type": "array",
+ "id": "/properties/Actions",
+ "minItems": 1,
+ "uniqueItems": true,
+ "items": {
+ "type": "object",
+ "id": "/properties/Actions/items",
+ "required": [
+ "PowerHint",
+ "Node",
+ "ValueIndex",
+ "Duration"
+ ],
+ "properties": {
+ "PowerHint": {
+ "type": "string",
+ "id": "/properties/Actions/items/properties/PowerHint",
+ "title": "The PowerHint Schema.",
+ "description": "The PowerHint name of the action.",
+ "minLength": 1
+ },
+ "Node": {
+ "type": "string",
+ "id": "/properties/Actions/items/properties/Node",
+ "title": "The Node Schema.",
+ "description": "The Node name of the action, which is defined in Nodes.",
+ "minLength": 1
+ },
+ "Value": {
+ "type": "string",
+ "id": "/properties/Actions/items/properties/Value",
+ "title": "The Value Index Schema.",
+ "description": "The value of action, which is defined in Nodes.",
+ "minLength": 1
+ },
+ "Duration": {
+ "type": "integer",
+ "id": "/properties/Actions/items/properties/Duration",
+ "title": "The Duration Schema.",
+ "description": "The number of milliseconds that this action will be active (zero means forever).",
+ "minimum": 0
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/FileNode.h b/power-libperfmgr/libperfmgr/include/perfmgr/FileNode.h
new file mode 100644
index 0000000..b8f2db6
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/FileNode.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBPERFMGR_FILENODE_H_
+#define ANDROID_LIBPERFMGR_FILENODE_H_
+
+#include <android-base/unique_fd.h>
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "perfmgr/Node.h"
+
+namespace android {
+namespace perfmgr {
+
+// FileNode represents file
+class FileNode : public Node {
+ public:
+ FileNode(std::string name, std::string node_path,
+ std::vector<RequestGroup> req_sorted, std::size_t default_val_index,
+ bool reset_on_init, bool hold_fd = false);
+
+ std::chrono::milliseconds Update(bool log_error) override;
+
+ bool GetHoldFd() const;
+
+ void DumpToFd(int fd) const override;
+
+ private:
+ FileNode(const Node& other) = delete;
+ FileNode& operator=(Node const&) = delete;
+
+ const bool hold_fd_;
+ const std::chrono::milliseconds warn_timeout_;
+ android::base::unique_fd fd_;
+};
+
+} // namespace perfmgr
+} // namespace android
+
+#endif // ANDROID_LIBPERFMGR_FILENODE_H_
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h
new file mode 100644
index 0000000..bfb96fd
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/HintManager.h
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBPERFMGR_HINTMANAGER_H_
+#define ANDROID_LIBPERFMGR_HINTMANAGER_H_
+
+#include <cstddef>
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfmgr/NodeLooperThread.h"
+
+namespace android {
+namespace perfmgr {
+
+// HintManager is the external interface of the library to be used by PowerHAL
+// to do power hints with sysfs nodes. HintManager maintains a representation of
+// the actions that are parsed from the configuration file as a mapping from a
+// PowerHint to the set of actions that are performed for that PowerHint.
+class HintManager {
+ public:
+ HintManager(sp<NodeLooperThread> nm,
+ const std::map<std::string, std::vector<NodeAction>>& actions)
+ : nm_(std::move(nm)), actions_(actions) {}
+ ~HintManager() {
+ if (nm_.get() != nullptr) nm_->Stop();
+ }
+
+ // Return true if the sysfs manager thread is running.
+ bool IsRunning() const;
+
+ // Do hint based on hint_type which defined as PowerHint in the actions
+ // section of the JSON config. Return true with valid hint_type and also
+ // NodeLooperThread::Request succeeds; otherwise return false.
+ bool DoHint(const std::string& hint_type);
+
+ // Do hint with the override time for all actions defined for the given
+ // hint_type. Return true with valid hint_type and also
+ // NodeLooperThread::Request succeeds; otherwise return false.
+ bool DoHint(const std::string& hint_type,
+ std::chrono::milliseconds timeout_ms_override);
+
+ // End hint early. Return true with valid hint_type and also
+ // NodeLooperThread::Cancel succeeds; otherwise return false.
+ bool EndHint(const std::string& hint_type);
+
+ // Query if given hint supported.
+ bool IsHintSupported(const std::string& hint_type) const;
+
+ // Static method to construct HintManager from the JSON config file.
+ static std::unique_ptr<HintManager> GetFromJSON(
+ const std::string& config_path, bool start = true);
+
+ // Return available hints managed by HintManager
+ std::vector<std::string> GetHints() const;
+
+ // Dump internal status to fd
+ void DumpToFd(int fd);
+
+ // Start thread loop
+ bool Start();
+
+ protected:
+ static std::vector<std::unique_ptr<Node>> ParseNodes(
+ const std::string& json_doc);
+ static std::map<std::string, std::vector<NodeAction>> ParseActions(
+ const std::string& json_doc,
+ const std::vector<std::unique_ptr<Node>>& nodes);
+
+ private:
+ HintManager(HintManager const&) = delete;
+ void operator=(HintManager const&) = delete;
+ bool ValidateHint(const std::string& hint_type) const;
+
+ sp<NodeLooperThread> nm_;
+ const std::map<std::string, std::vector<NodeAction>> actions_;
+};
+
+} // namespace perfmgr
+} // namespace android
+
+#endif // ANDROID_LIBPERFMGR_HINTMANAGER_H_
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/Node.h b/power-libperfmgr/libperfmgr/include/perfmgr/Node.h
new file mode 100644
index 0000000..e0db077
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/Node.h
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBPERFMGR_NODE_H_
+#define ANDROID_LIBPERFMGR_NODE_H_
+
+#include <android-base/unique_fd.h>
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "perfmgr/RequestGroup.h"
+
+namespace android {
+namespace perfmgr {
+
+// The Node class provides an interface for adding and cancelling powerhint
+// requests, as well as checking the next time that an in-progress powerhint
+// request will expire. There are additional methods for getting the Node’s name
+// and the index of a value, which may be used for initialization, debugging,
+// and request management. The core of the Node class is a vector of
+// RequestGroups named req_sorted_, which is used to track the in-progress
+// requests on the node. Each entry in the vector corresponds to a possible
+// value for the node, in priority order. For example, the first entry in the
+// vector for the cpu0 cluster represents the in-progress requests to boost the
+// cluster’s frequency to the highest available value. The next entry represents
+// the in-progress requests to boost the cluster’s frequency to the next highest
+// value. For each value, there may be multiple requests because different
+// powerhints may request the same value, and the requests may have different
+// expiration times. All of the in-progress powerhints for a given value are
+// collected in a RequestGroup. Node class is not thread safe so it needs
+// protection from caller e.g. NodeLooperThread.
+class Node {
+ public:
+ virtual ~Node() {}
+
+ // Return true if successfully add a request
+ bool AddRequest(std::size_t value_index, const std::string& hint_type,
+ ReqTime end_time);
+
+ // Return true if successfully remove a request
+ bool RemoveRequest(const std::string& hint_type);
+
+ // Return the nearest expire time of active requests; return
+ // std::chrono::milliseconds::max() if no active request on Node; update
+ // node's controlled file node value and the current value index based on
+ // active request.
+ virtual std::chrono::milliseconds Update(bool log_error) = 0;
+
+ const std::string& GetName() const;
+ const std::string& GetPath() const;
+ std::vector<std::string> GetValues() const;
+ std::size_t GetDefaultIndex() const;
+ bool GetResetOnInit() const;
+ bool GetValueIndex(const std::string& value, std::size_t* index) const;
+ virtual void DumpToFd(int fd) const = 0;
+
+ protected:
+ Node(std::string name, std::string node_path,
+ std::vector<RequestGroup> req_sorted, std::size_t default_val_index,
+ bool reset_on_init);
+ Node(const Node& other) = delete;
+ Node& operator=(Node const&) = delete;
+
+ const std::string name_;
+ const std::string node_path_;
+ // request vector, one entry per possible value, sorted by priority.
+ std::vector<RequestGroup> req_sorted_;
+ const std::size_t default_val_index_;
+ // node will be explicitly initialized when first time called Update().
+ bool reset_on_init_;
+ std::size_t current_val_index_;
+};
+
+} // namespace perfmgr
+} // namespace android
+
+#endif // ANDROID_LIBPERFMGR_NODE_H_
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/NodeLooperThread.h b/power-libperfmgr/libperfmgr/include/perfmgr/NodeLooperThread.h
new file mode 100644
index 0000000..eba0fc7
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/NodeLooperThread.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBPERFMGR_NODELOOPERTHREAD_H_
+#define ANDROID_LIBPERFMGR_NODELOOPERTHREAD_H_
+
+#include <utils/Thread.h>
+
+#include <cstddef>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "perfmgr/Node.h"
+
+namespace android {
+namespace perfmgr {
+
+// The NodeAction specifies the sysfs node, the value to be assigned, and the
+// timeout for this action:
+struct NodeAction {
+ NodeAction(std::size_t node_index, std::size_t value_index,
+ std::chrono::milliseconds timeout_ms)
+ : node_index(node_index),
+ value_index(value_index),
+ timeout_ms(timeout_ms) {}
+ std::size_t node_index;
+ std::size_t value_index;
+ std::chrono::milliseconds timeout_ms; // 0ms for forever
+};
+
+// The NodeLooperThread is responsible for managing each of the sysfs nodes
+// specified in the configuration. At initialization, the NodeLooperThrea holds
+// a vector containing the nodes defined in the configuration. The NodeManager
+// gets powerhint requests and cancellations from the HintManager, maintains
+// state about the current set of powerhint requests on each sysfs node, and
+// decides how to apply the requests. The NodeLooperThread contains a ThreadLoop
+// to maintain the sysfs nodes, and that thread is woken up both to handle
+// powerhint requests and when the timeout expires for an in-progress powerhint.
+class NodeLooperThread : public ::android::Thread {
+ public:
+ explicit NodeLooperThread(std::vector<std::unique_ptr<Node>> nodes)
+ : Thread(false), nodes_(std::move(nodes)) {}
+ virtual ~NodeLooperThread() { Stop(); }
+
+ // Need call Stop() as the threadloop will hold a strong pointer
+ // itself and wait for Condition fired or timeout (60s) before
+ // the out looper can call deconstructor to Stop() thread
+ void Stop();
+
+ // Return true when successfully adds request from actions for the hint_type
+ // in each individual node. Return false if any of the actions has either
+ // invalid node index or value index.
+ bool Request(const std::vector<NodeAction>& actions,
+ const std::string& hint_type);
+ // Return when successfully cancels request from actions for the hint_type
+ // in each individual node. Return false if any of the actions has invalid
+ // node index.
+ bool Cancel(const std::vector<NodeAction>& actions,
+ const std::string& hint_type);
+
+ // Dump all nodes to fd
+ void DumpToFd(int fd);
+
+ // Return true when successfully started the looper thread
+ bool Start();
+
+ private:
+ NodeLooperThread(NodeLooperThread const&) = delete;
+ void operator=(NodeLooperThread const&) = delete;
+ bool threadLoop() override;
+
+ static constexpr auto kMaxUpdatePeriod = std::chrono::milliseconds::max();
+
+ std::vector<std::unique_ptr<Node>> nodes_; // parsed from Config
+
+ // conditional variable from C++ standard library can be affected by wall
+ // time change as it is using CLOCK_REAL (b/35756266). The component should
+ // not be impacted by wall time, thus need use Android specific Condition
+ // class for waking up threadloop.
+ ::android::Condition wake_cond_;
+
+ // lock to protect nodes_
+ ::android::Mutex lock_;
+};
+
+} // namespace perfmgr
+} // namespace android
+
+#endif // ANDROID_LIBPERFMGR_NODELOOPERTHREAD_H_
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/PropertyNode.h b/power-libperfmgr/libperfmgr/include/perfmgr/PropertyNode.h
new file mode 100644
index 0000000..feaf85f
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/PropertyNode.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBPERFMGR_PROPERTYNODE_H_
+#define ANDROID_LIBPERFMGR_PROPERTYNODE_H_
+
+#include <cstddef>
+#include <string>
+#include <vector>
+
+#include "perfmgr/Node.h"
+
+namespace android {
+namespace perfmgr {
+
+// PropertyNode represents managed system properties
+class PropertyNode : public Node {
+ public:
+ PropertyNode(std::string name, std::string node_path,
+ std::vector<RequestGroup> req_sorted,
+ std::size_t default_val_index, bool reset_on_init);
+
+ std::chrono::milliseconds Update(bool log_error) override;
+
+ void DumpToFd(int fd) const override;
+
+ private:
+ PropertyNode(const Node& other) = delete;
+ PropertyNode& operator=(Node const&) = delete;
+};
+
+} // namespace perfmgr
+} // namespace android
+
+#endif // ANDROID_LIBPERFMGR_PROPERTYNODE_H_
diff --git a/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h b/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h
new file mode 100644
index 0000000..698a821
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/include/perfmgr/RequestGroup.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_LIBPERFMGR_REQUESTGROUP_H_
+#define ANDROID_LIBPERFMGR_REQUESTGROUP_H_
+
+#include <chrono>
+#include <map>
+#include <string>
+#include <utility>
+
+namespace android {
+namespace perfmgr {
+
+using ReqTime = std::chrono::time_point<std::chrono::steady_clock>;
+
+// The RequestGroup type represents the set of requests for a given value on a
+// particular sysfs node, and the interface is simple: there is a function to
+// add requests, a function to remove requests, and a function to check for the
+// next expiration time if there is an outstanding request, and a function to
+// check the requested value. There may only be one request per PowerHint, so
+// the representation is simple: a map from PowerHint to the expiration time for
+// that hint.
+class RequestGroup {
+ public:
+ RequestGroup(std::string request_value) // NOLINT(runtime/explicit)
+ : request_value_(std::move(request_value)) {}
+
+ // Remove expired request in the map and return true when request_map_ is
+ // not empty, false when request_map_ is empty; also update expire_time with
+ // nearest timeout in request_map_ or std::chrono::milliseconds::max() when
+ // request_map_ is empty.
+ bool GetExpireTime(std::chrono::milliseconds* expire_time);
+ // Return the request value.
+ const std::string& GetRequestValue() const;
+ // Return true for adding request, false for extending expire time of
+ // existing active request on given hint_type.
+ bool AddRequest(const std::string& hint_type, ReqTime end_time);
+ // Return true for removing request, false if request is not active on given
+ // hint_type. If request exits and the new end_time is less than the active
+ // time, expire time will not be updated; also returns false.
+ bool RemoveRequest(const std::string& hint_type);
+ // Dump internal status to fd
+ void DumpToFd(int fd, const std::string& prefix) const;
+
+ private:
+ const std::string request_value_;
+ std::map<std::string, ReqTime> request_map_;
+};
+
+} // namespace perfmgr
+} // namespace android
+
+#endif // ANDROID_LIBPERFMGR_REQUESTGROUP_H_
diff --git a/power-libperfmgr/libperfmgr/tests/FileNodeTest.cc b/power-libperfmgr/libperfmgr/tests/FileNodeTest.cc
new file mode 100644
index 0000000..d163be8
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/tests/FileNodeTest.cc
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <thread>
+
+#include "perfmgr/FileNode.h"
+
+namespace android {
+namespace perfmgr {
+
+using namespace std::chrono_literals;
+
+constexpr double kTIMING_TOLERANCE_MS = std::chrono::milliseconds(25).count();
+constexpr auto kSLEEP_TOLERANCE_MS = 2ms;
+
+static inline void _VerifyPathValue(const std::string& path,
+ const std::string& value) {
+ std::string s;
+ EXPECT_TRUE(android::base::ReadFileToString(path, &s)) << strerror(errno);
+ EXPECT_EQ(value, s);
+}
+
+// Test init with no default value
+TEST(FileNodeTest, NoInitDefaultTest) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 1, false);
+ t.Update(false);
+ _VerifyPathValue(tf.path, "");
+}
+
+// Test init with default value
+TEST(FileNodeTest, InitDefaultTest) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 1, true);
+ t.Update(false);
+ _VerifyPathValue(tf.path, "value1");
+ TemporaryFile tf2;
+ FileNode t2("t2", tf2.path, {{"value0"}, {"value1"}, {"value2"}}, 0, true);
+ t2.Update(false);
+ _VerifyPathValue(tf2.path, "value0");
+}
+
+// Test DumpToFd
+TEST(FileNodeTest, DumpToFdTest) {
+ TemporaryFile tf;
+ FileNode t("test_dump", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 1,
+ true);
+ t.Update(false);
+ TemporaryFile dumptf;
+ t.DumpToFd(dumptf.fd);
+ fsync(dumptf.fd);
+ std::string buf(
+ android::base::StringPrintf("test_dump\t%s\t1\tvalue1\n", tf.path));
+ _VerifyPathValue(dumptf.path, buf);
+}
+
+// Test GetValueIndex
+TEST(FileNodeTest, GetValueIndexTest) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 1, false);
+ std::size_t index = 0;
+ EXPECT_TRUE(t.GetValueIndex("value2", &index));
+ EXPECT_EQ(2u, index);
+ index = 1234;
+ EXPECT_FALSE(t.GetValueIndex("NON_EXIST", &index));
+ EXPECT_EQ(1234u, index);
+}
+
+// Test GetValues
+TEST(FileNodeTest, GetValuesTest) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 1, false);
+ std::vector values = t.GetValues();
+ EXPECT_EQ(3u, values.size());
+ EXPECT_EQ("value0", values[0]);
+ EXPECT_EQ("value1", values[1]);
+ EXPECT_EQ("value2", values[2]);
+}
+
+// Test get more properties
+TEST(FileNodeTest, GetPropertiesTest) {
+ std::string test_name = "TESTREQ_1";
+ std::string test_path = "TEST_PATH";
+ FileNode t(test_name, test_path, {}, 0, false, true);
+ EXPECT_EQ(test_name, t.GetName());
+ EXPECT_EQ(test_path, t.GetPath());
+ EXPECT_EQ(0u, t.GetValues().size());
+ EXPECT_EQ(0u, t.GetDefaultIndex());
+ EXPECT_FALSE(t.GetResetOnInit());
+ EXPECT_TRUE(t.GetHoldFd());
+}
+
+// Test add request fail and retry
+TEST(FileNodeTest, AddRequestTestFail) {
+ FileNode t("t", "/sys/android/nonexist_node_test",
+ {{"value0"}, {"value1"}, {"value2"}}, 2, true);
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 200ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 2000ms));
+ expire_time = t.Update(true);
+ // Retry in 500 ms
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+}
+
+// Test add request
+TEST(FileNodeTest, AddRequestTest) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 2, true);
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 500ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ _VerifyPathValue(tf.path, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 200ms));
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Let high prio request timeout, now only request @ value1 active
+ std::this_thread::sleep_for(expire_time + kSLEEP_TOLERANCE_MS);
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(300).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Let all requests timeout, now default value2
+ std::this_thread::sleep_for(expire_time + kSLEEP_TOLERANCE_MS);
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value2");
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+}
+
+// Test remove request
+TEST(FileNodeTest, RemoveRequestTest) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 2, true);
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 500ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ _VerifyPathValue(tf.path, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 200ms));
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove high prio request, now only request @ value1 active
+ t.RemoveRequest("LAUNCH");
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove request, now default value2
+ t.RemoveRequest("INTERACTION");
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value2");
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+}
+
+// Test add request with holding fd
+TEST(FileNodeTest, AddRequestTestHoldFdOverride) {
+ TemporaryFile tf;
+ FileNode t("t", tf.path, {{"value0"}, {"value1"}, {"value2"}}, 2, true,
+ true);
+ EXPECT_TRUE(t.GetHoldFd());
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 500ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ _VerifyPathValue(tf.path, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 200ms));
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 shorter
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 100ms));
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 longer
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 300ms));
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(300).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove high prio request, now only request @ value1 active
+ t.RemoveRequest("LAUNCH");
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove request, now default value2
+ t.RemoveRequest("INTERACTION");
+ expire_time = t.Update(true);
+ _VerifyPathValue(tf.path, "value2");
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc b/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc
new file mode 100644
index 0000000..a4d3188
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/tests/HintManagerTest.cc
@@ -0,0 +1,575 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <thread>
+
+#include "perfmgr/FileNode.h"
+#include "perfmgr/HintManager.h"
+#include "perfmgr/PropertyNode.h"
+
+namespace android {
+namespace perfmgr {
+
+using namespace std::chrono_literals;
+
+constexpr auto kSLEEP_TOLERANCE_MS = 50ms;
+
+// JSON_CONFIG
+// {
+// "Nodes": [
+// {
+// "Name": "CPUCluster0MinFreq",
+// "Path": "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq",
+// "Values": [
+// "1512000",
+// "1134000",
+// "384000"
+// ],
+// "DefaultIndex": 2,
+// "ResetOnInit": true
+// },
+// {
+// "Name": "CPUCluster1MinFreq",
+// "Path": "/sys/devices/system/cpu/cpu4/cpufreq/scaling_min_freq",
+// "Values": [
+// "1512000",
+// "1134000",
+// "384000"
+// ],
+// "HoldFd": true
+// },
+// {
+// "Name": "ModeProperty",
+// "Path": "vendor.pwhal.mode",
+// "Values": [
+// "HIGH",
+// "LOW",
+// "NONE"
+// ],
+// "Type": "Property"
+// }
+// ],
+// "Actions": [
+// {
+// "PowerHint": "INTERACTION",
+// "Node": "CPUCluster1MinFreq",
+// "Value": "1134000",
+// "Duration": 800
+// },
+// {
+// "PowerHint": "INTERACTION",
+// "Node": "ModeProperty",
+// "Value": "LOW",
+// "Duration": 800
+// },
+// {
+// "PowerHint": "LAUNCH",
+// "Node": "CPUCluster0MinFreq",
+// "Value": "1134000",
+// "Duration": 500
+// },
+// {
+// "PowerHint": "LAUNCH",
+// "Node": "ModeProperty",
+// "Value": "HIGH",
+// "Duration": 500
+// },
+// {
+// "PowerHint": "LAUNCH",
+// "Node": "CPUCluster1MinFreq",
+// "Value": "1512000",
+// "Duration": 2000
+// }
+// ]
+// }
+constexpr char kJSON_RAW[] =
+ "{\"Nodes\":[{\"Name\":\"CPUCluster0MinFreq\",\"Path\":\"/sys/devices/"
+ "system/cpu/cpu0/cpufreq/"
+ "scaling_min_freq\",\"Values\":[\"1512000\",\"1134000\",\"384000\"],"
+ "\"DefaultIndex\":2,\"ResetOnInit\":true},{\"Name\":\"CPUCluster1MinFreq\","
+ "\"Path\":\"/sys/devices/system/cpu/cpu4/cpufreq/"
+ "scaling_min_freq\",\"Values\":[\"1512000\",\"1134000\",\"384000\"],"
+ "\"HoldFd\":true},{\"Name\":\"ModeProperty\",\"Path\":\"vendor.pwhal."
+ "mode\",\"Values\":[\"HIGH\",\"LOW\",\"NONE\"],\"Type\":\"Property\"}],"
+ "\"Actions\":[{\"PowerHint\":\"INTERACTION\",\"Node\":"
+ "\"CPUCluster1MinFreq\",\"Value\":\"1134000\",\"Duration\":800},{"
+ "\"PowerHint\":\"INTERACTION\",\"Node\":\"ModeProperty\",\"Value\":\"LOW\","
+ "\"Duration\":800},{\"PowerHint\":\"LAUNCH\",\"Node\":"
+ "\"CPUCluster0MinFreq\",\"Value\":\"1134000\",\"Duration\":500},{"
+ "\"PowerHint\":\"LAUNCH\",\"Node\":\"ModeProperty\",\"Value\":\"HIGH\","
+ "\"Duration\":500},{\"PowerHint\":\"LAUNCH\",\"Node\":"
+ "\"CPUCluster1MinFreq\",\"Value\":\"1512000\",\"Duration\":2000}]}";
+
+class HintManagerTest : public ::testing::Test, public HintManager {
+ protected:
+ HintManagerTest()
+ : HintManager(nullptr,
+ std::map<std::string, std::vector<NodeAction>>{}) {
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+ prop_ = "vendor.pwhal.mode";
+ }
+
+ virtual void SetUp() {
+ // Set up 3 dummy nodes
+ std::unique_ptr<TemporaryFile> tf = std::make_unique<TemporaryFile>();
+ nodes_.emplace_back(new FileNode(
+ "n0", tf->path, {{"n0_value0"}, {"n0_value1"}, {"n0_value2"}}, 2,
+ false));
+ files_.emplace_back(std::move(tf));
+ tf = std::make_unique<TemporaryFile>();
+ nodes_.emplace_back(new FileNode(
+ "n1", tf->path, {{"n1_value0"}, {"n1_value1"}, {"n1_value2"}}, 2,
+ true));
+ files_.emplace_back(std::move(tf));
+ nodes_.emplace_back(new PropertyNode(
+ "n2", prop_, {{"n2_value0"}, {"n2_value1"}, {"n2_value2"}}, 2,
+ true));
+ nm_ = new NodeLooperThread(std::move(nodes_));
+ // Set up dummy actions
+ // "INTERACTION"
+ // Node0, value1, 800ms
+ // Node1, value1, forever
+ // Node2, value1, 800ms
+ // "LAUNCH"
+ // Node0, value0, forever
+ // Node1, value0, 400ms
+ // Node2, value0, 400ms
+ actions_ = std::map<std::string, std::vector<NodeAction>>{
+ {"INTERACTION", {{0, 1, 800ms}, {1, 1, 0ms}, {2, 1, 800ms}}},
+ {"LAUNCH", {{0, 0, 0ms}, {1, 0, 400ms}, {2, 0, 400ms}}}};
+
+ // Prepare dummy files to replace the nodes' path in example json_doc
+ files_.emplace_back(std::make_unique<TemporaryFile>());
+ files_.emplace_back(std::make_unique<TemporaryFile>());
+ // replace file path
+ json_doc_ = kJSON_RAW;
+ std::string from =
+ "/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), files_[0 + 2]->path);
+ from = "/sys/devices/system/cpu/cpu4/cpufreq/scaling_min_freq";
+ start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), files_[1 + 2]->path);
+ EXPECT_TRUE(android::base::SetProperty(prop_, ""))
+ << "failed to clear property";
+ }
+
+ virtual void TearDown() {
+ actions_.clear();
+ nodes_.clear();
+ files_.clear();
+ nm_ = nullptr;
+ }
+ sp<NodeLooperThread> nm_;
+ std::map<std::string, std::vector<NodeAction>> actions_;
+ std::vector<std::unique_ptr<Node>> nodes_;
+ std::vector<std::unique_ptr<TemporaryFile>> files_;
+ std::string json_doc_;
+ std::string prop_;
+};
+
+static inline void _VerifyPropertyValue(const std::string& path,
+ const std::string& value) {
+ std::string s = android::base::GetProperty(path, "");
+ EXPECT_EQ(value, s);
+}
+
+static inline void _VerifyPathValue(const std::string& path,
+ const std::string& value) {
+ std::string s;
+ EXPECT_TRUE(android::base::ReadFileToString(path, &s)) << strerror(errno);
+ EXPECT_EQ(value, s);
+}
+
+// Test GetHints
+TEST_F(HintManagerTest, GetHintsTest) {
+ HintManager hm(nm_, actions_);
+ EXPECT_TRUE(hm.Start());
+ std::vector<std::string> hints = hm.GetHints();
+ EXPECT_TRUE(hm.IsRunning());
+ EXPECT_EQ(2u, hints.size());
+ EXPECT_NE(std::find(hints.begin(), hints.end(), "INTERACTION"), hints.end());
+ EXPECT_NE(std::find(hints.begin(), hints.end(), "LAUNCH"), hints.end());
+}
+
+// Test initialization of default values
+TEST_F(HintManagerTest, HintInitDefaultTest) {
+ HintManager hm(nm_, actions_);
+ EXPECT_TRUE(hm.Start());
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ EXPECT_TRUE(hm.IsRunning());
+ _VerifyPathValue(files_[0]->path, "");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ _VerifyPropertyValue(prop_, "n2_value2");
+}
+
+// Test IsHintSupported
+TEST_F(HintManagerTest, HintSupportedTest) {
+ HintManager hm(nm_, actions_);
+ EXPECT_TRUE(hm.IsHintSupported("INTERACTION"));
+ EXPECT_TRUE(hm.IsHintSupported("LAUNCH"));
+ EXPECT_FALSE(hm.IsHintSupported("NO_SUCH_HINT"));
+}
+
+// Test DumpToFd
+TEST_F(HintManagerTest, DumpToFdTest) {
+ HintManager hm(nm_, actions_);
+ TemporaryFile dumptf;
+ hm.DumpToFd(dumptf.fd);
+ fsync(dumptf.fd);
+ std::ostringstream dump_buf;
+ dump_buf << "========== Begin perfmgr nodes ==========\nNode Name\tNode "
+ "Path\tCurrent Index\tCurrent Value\nn0\t"
+ << files_[0]->path << "\t2\t\nn1\t" << files_[1]->path
+ << "\t2\t\nn2\tvendor.pwhal.mode\t2\t\n========== End perfmgr "
+ "nodes ==========\n";
+ _VerifyPathValue(dumptf.path, dump_buf.str());
+ TemporaryFile dumptf_started;
+ EXPECT_TRUE(hm.Start());
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ EXPECT_TRUE(hm.IsRunning());
+ hm.DumpToFd(dumptf_started.fd);
+ fsync(dumptf_started.fd);
+ dump_buf.str("");
+ dump_buf.clear();
+ dump_buf << "========== Begin perfmgr nodes ==========\nNode Name\tNode "
+ "Path\tCurrent Index\tCurrent Value\nn0\t"
+ << files_[0]->path << "\t2\t\nn1\t" << files_[1]->path
+ << "\t2\tn1_value2\nn2\tvendor.pwhal.mode\t2\tn2_value2\n========="
+ "= End perfmgr nodes ==========\n";
+ _VerifyPathValue(dumptf_started.path, dump_buf.str());
+}
+
+// Test hint/cancel/expire with dummy actions
+TEST_F(HintManagerTest, HintTest) {
+ HintManager hm(nm_, actions_);
+ EXPECT_TRUE(hm.Start());
+ EXPECT_TRUE(hm.IsRunning());
+ EXPECT_TRUE(hm.DoHint("INTERACTION"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value1");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ _VerifyPropertyValue(prop_, "n2_value1");
+ // this won't change the expire time of INTERACTION hint
+ EXPECT_TRUE(hm.DoHint("INTERACTION", 200ms));
+ // now place new hint
+ EXPECT_TRUE(hm.DoHint("LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value0");
+ _VerifyPropertyValue(prop_, "n2_value0");
+ EXPECT_TRUE(hm.DoHint("LAUNCH", 500ms));
+ // "LAUNCH" node1 not expired
+ std::this_thread::sleep_for(400ms);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value0");
+ _VerifyPropertyValue(prop_, "n2_value0");
+ // "LAUNCH" node1 expired
+ std::this_thread::sleep_for(100ms + kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ _VerifyPropertyValue(prop_, "n2_value1");
+ EXPECT_TRUE(hm.EndHint("LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ // "LAUNCH" canceled
+ _VerifyPathValue(files_[0]->path, "n0_value1");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ _VerifyPropertyValue(prop_, "n2_value1");
+ std::this_thread::sleep_for(200ms);
+ // "INTERACTION" node0 expired
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ _VerifyPropertyValue(prop_, "n2_value2");
+ EXPECT_TRUE(hm.EndHint("INTERACTION"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ // "INTERACTION" canceled
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ _VerifyPropertyValue(prop_, "n2_value2");
+}
+
+// Test parsing nodes
+TEST_F(HintManagerTest, ParseNodesTest) {
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(3u, nodes.size());
+ EXPECT_EQ("CPUCluster0MinFreq", nodes[0]->GetName());
+ EXPECT_EQ("CPUCluster1MinFreq", nodes[1]->GetName());
+ EXPECT_EQ(files_[0 + 2]->path, nodes[0]->GetPath());
+ EXPECT_EQ(files_[1 + 2]->path, nodes[1]->GetPath());
+ EXPECT_EQ("1512000", nodes[0]->GetValues()[0]);
+ EXPECT_EQ("1134000", nodes[0]->GetValues()[1]);
+ EXPECT_EQ("384000", nodes[0]->GetValues()[2]);
+ EXPECT_EQ("1512000", nodes[1]->GetValues()[0]);
+ EXPECT_EQ("1134000", nodes[1]->GetValues()[1]);
+ EXPECT_EQ("384000", nodes[1]->GetValues()[2]);
+ EXPECT_EQ(2u, nodes[0]->GetDefaultIndex());
+ EXPECT_EQ(2u, nodes[1]->GetDefaultIndex());
+ EXPECT_TRUE(nodes[0]->GetResetOnInit());
+ EXPECT_FALSE(nodes[1]->GetResetOnInit());
+ // no dynamic_cast intentionally in Android
+ EXPECT_FALSE(reinterpret_cast<FileNode*>(nodes[0].get())->GetHoldFd());
+ EXPECT_TRUE(reinterpret_cast<FileNode*>(nodes[1].get())->GetHoldFd());
+ EXPECT_EQ("ModeProperty", nodes[2]->GetName());
+ EXPECT_EQ(prop_, nodes[2]->GetPath());
+ EXPECT_EQ("HIGH", nodes[2]->GetValues()[0]);
+ EXPECT_EQ("LOW", nodes[2]->GetValues()[1]);
+ EXPECT_EQ("NONE", nodes[2]->GetValues()[2]);
+ EXPECT_EQ(2u, nodes[2]->GetDefaultIndex());
+ EXPECT_FALSE(nodes[2]->GetResetOnInit());
+}
+
+// Test parsing nodes with duplicate name
+TEST_F(HintManagerTest, ParseNodesDuplicateNameTest) {
+ std::string from = "CPUCluster0MinFreq";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "CPUCluster1MinFreq");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(0u, nodes.size());
+}
+
+TEST_F(HintManagerTest, ParsePropertyNodesDuplicatNameTest) {
+ std::string from = "ModeProperty";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "CPUCluster1MinFreq");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(0u, nodes.size());
+}
+
+// Test parsing nodes with duplicate path
+TEST_F(HintManagerTest, ParseNodesDuplicatePathTest) {
+ std::string from = files_[0 + 2]->path;
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), files_[1 + 2]->path);
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(0u, nodes.size());
+}
+
+// Test parsing file node with duplicate value
+TEST_F(HintManagerTest, ParseFileNodesDuplicateValueTest) {
+ std::string from = "1512000";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "1134000");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(0u, nodes.size());
+}
+
+// Test parsing property node with duplicate value
+TEST_F(HintManagerTest, ParsePropertyNodesDuplicateValueTest) {
+ std::string from = "HIGH";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "LOW");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(0u, nodes.size());
+}
+
+// Test parsing file node with empty value
+TEST_F(HintManagerTest, ParseFileNodesEmptyValueTest) {
+ std::string from = "384000";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(0u, nodes.size());
+}
+
+// Test parsing property node with empty value
+TEST_F(HintManagerTest, ParsePropertyNodesEmptyValueTest) {
+ std::string from = "LOW";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(3u, nodes.size());
+ EXPECT_EQ("CPUCluster0MinFreq", nodes[0]->GetName());
+ EXPECT_EQ("CPUCluster1MinFreq", nodes[1]->GetName());
+ EXPECT_EQ(files_[0 + 2]->path, nodes[0]->GetPath());
+ EXPECT_EQ(files_[1 + 2]->path, nodes[1]->GetPath());
+ EXPECT_EQ("1512000", nodes[0]->GetValues()[0]);
+ EXPECT_EQ("1134000", nodes[0]->GetValues()[1]);
+ EXPECT_EQ("384000", nodes[0]->GetValues()[2]);
+ EXPECT_EQ("1512000", nodes[1]->GetValues()[0]);
+ EXPECT_EQ("1134000", nodes[1]->GetValues()[1]);
+ EXPECT_EQ("384000", nodes[1]->GetValues()[2]);
+ EXPECT_EQ(2u, nodes[0]->GetDefaultIndex());
+ EXPECT_EQ(2u, nodes[1]->GetDefaultIndex());
+ EXPECT_TRUE(nodes[0]->GetResetOnInit());
+ EXPECT_FALSE(nodes[1]->GetResetOnInit());
+ // no dynamic_cast intentionally in Android
+ EXPECT_FALSE(reinterpret_cast<FileNode*>(nodes[0].get())->GetHoldFd());
+ EXPECT_TRUE(reinterpret_cast<FileNode*>(nodes[1].get())->GetHoldFd());
+ EXPECT_EQ("ModeProperty", nodes[2]->GetName());
+ EXPECT_EQ(prop_, nodes[2]->GetPath());
+ EXPECT_EQ("HIGH", nodes[2]->GetValues()[0]);
+ EXPECT_EQ("", nodes[2]->GetValues()[1]);
+ EXPECT_EQ("NONE", nodes[2]->GetValues()[2]);
+ EXPECT_EQ(2u, nodes[2]->GetDefaultIndex());
+ EXPECT_FALSE(nodes[2]->GetResetOnInit());
+}
+
+// Test parsing invalid json for nodes
+TEST_F(HintManagerTest, ParseBadFileNodesTest) {
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes("invalid json");
+ EXPECT_EQ(0u, nodes.size());
+ nodes = HintManager::ParseNodes(
+ "{\"devices\":{\"15\":[\"armeabi-v7a\"],\"16\":[\"armeabi-v7a\"],"
+ "\"26\":[\"armeabi-v7a\",\"arm64-v8a\",\"x86\",\"x86_64\"]}}");
+ EXPECT_EQ(0u, nodes.size());
+}
+
+// Test parsing actions
+TEST_F(HintManagerTest, ParseActionsTest) {
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ std::map<std::string, std::vector<NodeAction>> actions =
+ HintManager::ParseActions(json_doc_, nodes);
+ EXPECT_EQ(2u, actions.size());
+
+ EXPECT_EQ(2u, actions["INTERACTION"].size());
+ EXPECT_EQ(1u, actions["INTERACTION"][0].node_index);
+ EXPECT_EQ(1u, actions["INTERACTION"][0].value_index);
+ EXPECT_EQ(std::chrono::milliseconds(800).count(),
+ actions["INTERACTION"][0].timeout_ms.count());
+
+ EXPECT_EQ(2u, actions["INTERACTION"][1].node_index);
+ EXPECT_EQ(1u, actions["INTERACTION"][1].value_index);
+ EXPECT_EQ(std::chrono::milliseconds(800).count(),
+ actions["INTERACTION"][1].timeout_ms.count());
+
+ EXPECT_EQ(3u, actions["LAUNCH"].size());
+
+ EXPECT_EQ(0u, actions["LAUNCH"][0].node_index);
+ EXPECT_EQ(1u, actions["LAUNCH"][0].value_index);
+ EXPECT_EQ(std::chrono::milliseconds(500).count(),
+ actions["LAUNCH"][0].timeout_ms.count());
+
+ EXPECT_EQ(2u, actions["LAUNCH"][1].node_index);
+ EXPECT_EQ(0u, actions["LAUNCH"][1].value_index);
+ EXPECT_EQ(std::chrono::milliseconds(500).count(),
+ actions["LAUNCH"][1].timeout_ms.count());
+
+ EXPECT_EQ(1u, actions["LAUNCH"][2].node_index);
+ EXPECT_EQ(0u, actions["LAUNCH"][2].value_index);
+ EXPECT_EQ(std::chrono::milliseconds(2000).count(),
+ actions["LAUNCH"][2].timeout_ms.count());
+}
+
+// Test parsing actions with duplicate File node
+TEST_F(HintManagerTest, ParseActionDuplicateFileNodeTest) {
+ std::string from = "\"Node\":\"CPUCluster0MinFreq\"";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(),
+ "\"Node\":\"CPUCluster1MinFreq\"");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(3u, nodes.size());
+ std::map<std::string, std::vector<NodeAction>> actions =
+ HintManager::ParseActions(json_doc_, nodes);
+ EXPECT_EQ(0u, actions.size());
+}
+
+// Test parsing actions with duplicate Property node
+TEST_F(HintManagerTest, ParseActionDuplicatePropertyNodeTest) {
+ std::string from = "\"Node\":\"CPUCluster0MinFreq\"";
+ size_t start_pos = json_doc_.find(from);
+ json_doc_.replace(start_pos, from.length(), "\"Node\":\"ModeProperty\"");
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ EXPECT_EQ(3u, nodes.size());
+ std::map<std::string, std::vector<NodeAction>> actions =
+ HintManager::ParseActions(json_doc_, nodes);
+ EXPECT_EQ(0u, actions.size());
+}
+
+// Test parsing invalid json for actions
+TEST_F(HintManagerTest, ParseBadActionsTest) {
+ std::vector<std::unique_ptr<Node>> nodes =
+ HintManager::ParseNodes(json_doc_);
+ std::map<std::string, std::vector<NodeAction>> actions =
+ HintManager::ParseActions("invalid json", nodes);
+ EXPECT_EQ(0u, actions.size());
+ actions = HintManager::ParseActions(
+ "{\"devices\":{\"15\":[\"armeabi-v7a\"],\"16\":[\"armeabi-v7a\"],"
+ "\"26\":[\"armeabi-v7a\",\"arm64-v8a\",\"x86\",\"x86_64\"]}}",
+ nodes);
+ EXPECT_EQ(0u, actions.size());
+}
+
+// Test hint/cancel/expire with json config
+TEST_F(HintManagerTest, GetFromJSONTest) {
+ TemporaryFile json_file;
+ ASSERT_TRUE(android::base::WriteStringToFile(json_doc_, json_file.path))
+ << strerror(errno);
+ std::unique_ptr<HintManager> hm =
+ HintManager::GetFromJSON(json_file.path, false);
+ EXPECT_NE(nullptr, hm.get());
+ EXPECT_FALSE(hm->IsRunning());
+ EXPECT_TRUE(hm->Start());
+ EXPECT_TRUE(hm->IsRunning());
+ hm = HintManager::GetFromJSON(json_file.path);
+ EXPECT_NE(nullptr, hm.get());
+ EXPECT_TRUE(hm->IsRunning());
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ EXPECT_TRUE(hm->IsRunning());
+ // Initial default value on Node0
+ _VerifyPathValue(files_[0 + 2]->path, "384000");
+ _VerifyPathValue(files_[1 + 2]->path, "");
+ _VerifyPropertyValue(prop_, "");
+ // Do INTERACTION
+ EXPECT_TRUE(hm->DoHint("INTERACTION"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0 + 2]->path, "384000");
+ _VerifyPathValue(files_[1 + 2]->path, "1134000");
+ _VerifyPropertyValue(prop_, "LOW");
+ // Do LAUNCH
+ EXPECT_TRUE(hm->DoHint("LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0 + 2]->path, "1134000");
+ _VerifyPathValue(files_[1 + 2]->path, "1512000");
+ _VerifyPropertyValue(prop_, "HIGH");
+ std::this_thread::sleep_for(500ms);
+ // "LAUNCH" node0 expired
+ _VerifyPathValue(files_[0 + 2]->path, "384000");
+ _VerifyPathValue(files_[1 + 2]->path, "1512000");
+ _VerifyPropertyValue(prop_, "LOW");
+ EXPECT_TRUE(hm->EndHint("LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ // "LAUNCH" canceled
+ _VerifyPathValue(files_[0 + 2]->path, "384000");
+ _VerifyPathValue(files_[1 + 2]->path, "1134000");
+ _VerifyPropertyValue(prop_, "LOW");
+ std::this_thread::sleep_for(300ms);
+ // "INTERACTION" node1 expired
+ _VerifyPathValue(files_[0 + 2]->path, "384000");
+ _VerifyPathValue(files_[1 + 2]->path, "384000");
+ _VerifyPropertyValue(prop_, "NONE");
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/tests/NodeLooperThreadTest.cc b/power-libperfmgr/libperfmgr/tests/NodeLooperThreadTest.cc
new file mode 100644
index 0000000..9283d9f
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/tests/NodeLooperThreadTest.cc
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <thread>
+
+#include "perfmgr/FileNode.h"
+#include "perfmgr/NodeLooperThread.h"
+
+namespace android {
+namespace perfmgr {
+
+using namespace std::chrono_literals;
+
+constexpr auto kSLEEP_TOLERANCE_MS = 50ms;
+
+class NodeLooperThreadTest : public ::testing::Test {
+ protected:
+ virtual void SetUp() {
+ std::unique_ptr<TemporaryFile> tf = std::make_unique<TemporaryFile>();
+ nodes_.emplace_back(new FileNode(
+ "n0", tf->path, {{"n0_value0"}, {"n0_value1"}, {"n0_value2"}}, 2,
+ false));
+ files_.emplace_back(std::move(tf));
+ tf = std::make_unique<TemporaryFile>();
+ nodes_.emplace_back(new FileNode(
+ "n1", tf->path, {{"n1_value0"}, {"n1_value1"}, {"n1_value2"}}, 2,
+ true));
+ files_.emplace_back(std::move(tf));
+ }
+
+ virtual void TearDown() {
+ nodes_.clear();
+ files_.clear();
+ }
+ std::vector<std::unique_ptr<Node>> nodes_;
+ std::vector<std::unique_ptr<TemporaryFile>> files_;
+};
+
+static inline void _VerifyPathValue(const std::string& path,
+ const std::string& value) {
+ std::string s;
+ EXPECT_TRUE(android::base::ReadFileToString(path, &s)) << strerror(errno);
+ EXPECT_EQ(value, s);
+}
+
+// Test default value init
+TEST_F(NodeLooperThreadTest, InitRunTest) {
+ sp<NodeLooperThread> th = new NodeLooperThread(std::move(nodes_));
+ EXPECT_TRUE(th->Start());
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ EXPECT_TRUE(th->isRunning());
+ _VerifyPathValue(files_[0]->path, "");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ th->Stop();
+ EXPECT_FALSE(th->isRunning());
+}
+
+// Test add request
+TEST_F(NodeLooperThreadTest, AddRequest) {
+ sp<NodeLooperThread> th = new NodeLooperThread(std::move(nodes_));
+ EXPECT_TRUE(th->Start());
+ EXPECT_TRUE(th->isRunning());
+ // Dummy LAUNCH boost actions:
+ // Node0, value0, 200ms
+ // Node1, value1, 400ms
+ std::vector<NodeAction> actions{{0, 0, 200ms}, {1, 1, 400ms}};
+ EXPECT_TRUE(th->Request(actions, "LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ std::this_thread::sleep_for(200ms);
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ std::this_thread::sleep_for(200ms);
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ th->Stop();
+ EXPECT_FALSE(th->isRunning());
+}
+
+// Test request to override expire time
+TEST_F(NodeLooperThreadTest, AddRequestOverride) {
+ sp<NodeLooperThread> th = new NodeLooperThread(std::move(nodes_));
+ EXPECT_TRUE(th->Start());
+ EXPECT_TRUE(th->isRunning());
+ // Dummy LAUNCH boost actions:
+ // Node0, value0, 200ms
+ // Node1, value1, 500ms
+ std::vector<NodeAction> actions{{0, 0, 200ms}, {1, 1, 500ms}};
+ EXPECT_TRUE(th->Request(actions, "LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ // Dummy LAUNCH boost actions:
+ // Node0, value0, 300ms will extend
+ // Node1, value1, 100ms will not extend
+ actions = std::vector<NodeAction>{{0, 0, 300ms}, {1, 1, 100ms}};
+ EXPECT_TRUE(th->Request(actions, "LAUNCH"));
+ std::this_thread::sleep_for(200ms);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ std::this_thread::sleep_for(150ms);
+ // Node0 value0 expired
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ std::this_thread::sleep_for(150ms);
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ th->Stop();
+ EXPECT_FALSE(th->isRunning());
+}
+
+// Test cancel request
+TEST_F(NodeLooperThreadTest, CancelRequest) {
+ sp<NodeLooperThread> th = new NodeLooperThread(std::move(nodes_));
+ EXPECT_TRUE(th->Start());
+ EXPECT_TRUE(th->isRunning());
+ // Dummy LAUNCH boost actions:
+ // Node0, value0, forever
+ // Node1, value1, forever
+ std::vector<NodeAction> actions{{0, 0, 0ms}, {1, 1, 0ms}};
+ EXPECT_TRUE(th->Request(actions, "LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ EXPECT_TRUE(th->Cancel(actions, "LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ th->Stop();
+ EXPECT_FALSE(th->isRunning());
+}
+
+// Test multiple request
+TEST_F(NodeLooperThreadTest, MultipleRequest) {
+ sp<NodeLooperThread> th = new NodeLooperThread(std::move(nodes_));
+ EXPECT_TRUE(th->Start());
+ EXPECT_TRUE(th->isRunning());
+ // Dummy LAUNCH boost actions:
+ // Node0, value1, 800ms
+ // Node1, value1, forever
+ std::vector<NodeAction> actions_interaction{{0, 1, 800ms}, {1, 1, 0ms}};
+ EXPECT_TRUE(th->Request(actions_interaction, "INTERACTION"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value1");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ // Dummy LAUNCH boost actions:
+ // Node0, value0, forever
+ // Node1, value0, 400ms
+ std::vector<NodeAction> actions_launch{{0, 0, 0ms}, {1, 0, 400ms}};
+ EXPECT_TRUE(th->Request(actions_launch, "LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value0");
+ std::this_thread::sleep_for(400ms);
+ // "LAUNCH" node1 expired
+ _VerifyPathValue(files_[0]->path, "n0_value0");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ EXPECT_TRUE(th->Cancel(actions_launch, "LAUNCH"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ // "LAUNCH" canceled
+ _VerifyPathValue(files_[0]->path, "n0_value1");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ std::this_thread::sleep_for(400ms);
+ // "INTERACTION" node0 expired
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value1");
+ EXPECT_TRUE(th->Cancel(actions_interaction, "INTERACTION"));
+ std::this_thread::sleep_for(kSLEEP_TOLERANCE_MS);
+ // "INTERACTION" canceled
+ _VerifyPathValue(files_[0]->path, "n0_value2");
+ _VerifyPathValue(files_[1]->path, "n1_value2");
+ th->Stop();
+ EXPECT_FALSE(th->isRunning());
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/tests/PropertyNodeTest.cc b/power-libperfmgr/libperfmgr/tests/PropertyNodeTest.cc
new file mode 100644
index 0000000..73f41c5
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/tests/PropertyNodeTest.cc
@@ -0,0 +1,222 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <thread>
+
+#include "perfmgr/PropertyNode.h"
+
+namespace android {
+namespace perfmgr {
+
+using namespace std::chrono_literals;
+
+constexpr double kTIMING_TOLERANCE_MS = std::chrono::milliseconds(25).count();
+constexpr auto kSLEEP_TOLERANCE_MS = 2ms;
+
+static inline void _VerifyPropertyValue(const std::string& path,
+ const std::string& value) {
+ std::string s = android::base::GetProperty(path, "");
+ EXPECT_EQ(value, s);
+}
+
+static inline const std::string _InitProperty(const std::string& path) {
+ EXPECT_TRUE(android::base::SetProperty(path, ""))
+ << "failed to clear property";
+ return path;
+}
+
+// Test init with no default value
+TEST(PropertyNodeTest, NoInitDefaultTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {"value2"}}, 1, false);
+ t.Update(false);
+ _VerifyPropertyValue(key, "");
+}
+
+// Test init with default value
+TEST(PropertyNodeTest, InitDefaultTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {"value2"}}, 1, true);
+ t.Update(false);
+ _VerifyPropertyValue(key, "value1");
+ std::string key2 = _InitProperty("test.libperfmgr.key2");
+ PropertyNode t2("t2", key2, {{"value0"}, {"value1"}, {"value2"}}, 0, true);
+ t2.Update(false);
+ _VerifyPropertyValue(key2, "value0");
+}
+
+// Test DumpToFd
+TEST(PropertyNodeTest, DumpToFdTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("test_dump", key, {{"value0"}, {"value1"}, {"value2"}}, 1,
+ true);
+ t.Update(false);
+ TemporaryFile dumptf;
+ t.DumpToFd(dumptf.fd);
+ fsync(dumptf.fd);
+ std::string buf(
+ android::base::StringPrintf("test_dump\t%s\t1\tvalue1\n", key.c_str()));
+ std::string s;
+ EXPECT_TRUE(android::base::ReadFileToString(dumptf.path, &s))
+ << strerror(errno);
+ EXPECT_EQ(buf, s);
+}
+
+// Test GetValueIndex
+TEST(PropertyNodeTest, GetValueIndexTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {"value2"}}, 1, false);
+ std::size_t index = 0;
+ EXPECT_TRUE(t.GetValueIndex("value2", &index));
+ EXPECT_EQ(2u, index);
+ index = 1234;
+ EXPECT_FALSE(t.GetValueIndex("NON_EXIST", &index));
+ EXPECT_EQ(1234u, index);
+}
+
+// Test GetValues
+TEST(PropertyNodeTest, GetValuesTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {"value2"}}, 1, false);
+ std::vector values = t.GetValues();
+ EXPECT_EQ(3u, values.size());
+ EXPECT_EQ("value0", values[0]);
+ EXPECT_EQ("value1", values[1]);
+ EXPECT_EQ("value2", values[2]);
+}
+
+// Test get more properties
+TEST(PropertyNodeTest, GetPropertiesTest) {
+ std::string test_name = "TESTREQ_1";
+ std::string test_path = "TEST_PATH";
+ PropertyNode t(test_name, test_path, {}, 0, false);
+ EXPECT_EQ(test_name, t.GetName());
+ EXPECT_EQ(test_path, t.GetPath());
+ EXPECT_EQ(0u, t.GetValues().size());
+ EXPECT_EQ(0u, t.GetDefaultIndex());
+ EXPECT_FALSE(t.GetResetOnInit());
+}
+
+// Test add request
+TEST(PropertyNodeTest, AddRequestTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {""}}, 2, true);
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 500ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ _VerifyPropertyValue(key, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 200ms));
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Let high prio request timeout, now only request @ value1 active
+ std::this_thread::sleep_for(expire_time + kSLEEP_TOLERANCE_MS);
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(300).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Let all requests timeout, now default value2
+ std::this_thread::sleep_for(expire_time + kSLEEP_TOLERANCE_MS);
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "");
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+}
+
+// Test remove request
+TEST(PropertyNodeTest, RemoveRequestTest) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {"value2"}}, 2, true);
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 500ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ _VerifyPropertyValue(key, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 200ms));
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove high prio request, now only request @ value1 active
+ t.RemoveRequest("LAUNCH");
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove request, now default value2
+ t.RemoveRequest("INTERACTION");
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value2");
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+}
+
+// Test add request
+TEST(PropertyNodeTest, AddRequestTestOverride) {
+ std::string key = _InitProperty("test.libperfmgr.key");
+ PropertyNode t("t", key, {{"value0"}, {"value1"}, {"value2"}}, 2, true);
+ auto start = std::chrono::steady_clock::now();
+ EXPECT_TRUE(t.AddRequest(1, "INTERACTION", start + 500ms));
+ std::chrono::milliseconds expire_time = t.Update(true);
+ // Add request @ value1
+ _VerifyPropertyValue(key, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 higher prio than value1
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 200ms));
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 shorter
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 100ms));
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(200).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Add request @ value0 longer
+ EXPECT_TRUE(t.AddRequest(0, "LAUNCH", start + 300ms));
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value0");
+ EXPECT_NEAR(std::chrono::milliseconds(300).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove high prio request, now only request @ value1 active
+ t.RemoveRequest("LAUNCH");
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value1");
+ EXPECT_NEAR(std::chrono::milliseconds(500).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ // Remove request, now default value2
+ t.RemoveRequest("INTERACTION");
+ expire_time = t.Update(true);
+ _VerifyPropertyValue(key, "value2");
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/tests/RequestGroupTest.cc b/power-libperfmgr/libperfmgr/tests/RequestGroupTest.cc
new file mode 100644
index 0000000..f7a67a3
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/tests/RequestGroupTest.cc
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include <algorithm>
+#include <thread>
+
+#include "perfmgr/RequestGroup.h"
+
+namespace android {
+namespace perfmgr {
+
+using namespace std::chrono_literals;
+
+constexpr double kTIMING_TOLERANCE_MS = std::chrono::milliseconds(25).count();
+
+// Test GetRequestValue()
+TEST(RequestGroupTest, GetRequestValueTest) {
+ std::string test_str = "TESTREQ_1";
+ RequestGroup req(test_str);
+ EXPECT_EQ(test_str, req.GetRequestValue());
+}
+
+// Test AddRequest()
+TEST(RequestGroupTest, AddRequestTest) {
+ RequestGroup req("");
+ auto start = std::chrono::steady_clock::now();
+ auto duration = 500ms;
+ bool ret = req.AddRequest("INTERACTION", start + duration);
+ EXPECT_EQ(true, ret);
+ auto sleep_time = 200ms;
+ std::this_thread::sleep_for(sleep_time);
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ EXPECT_NEAR((duration - sleep_time).count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ EXPECT_EQ(true, active);
+}
+
+// Test AddRequest() with a huge expire time which could be done in some long
+// persist power hint such as VR_MODE
+TEST(RequestGroupTest, AddRequestNoExpireTest) {
+ RequestGroup req("");
+ bool ret = req.AddRequest("INTERACTION", ReqTime::max());
+ EXPECT_EQ(true, ret);
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ auto expect = std::chrono::duration_cast<std::chrono::milliseconds>(
+ ReqTime::max() - std::chrono::steady_clock::now());
+ EXPECT_NEAR(expect.count(), expire_time.count(), kTIMING_TOLERANCE_MS);
+ // expire time is greater than 1 year
+ EXPECT_LE(365 * 24 * 60 * 60 * 1000, expire_time.count());
+ EXPECT_EQ(true, active);
+}
+
+// Test AddRequest() and expires
+TEST(RequestGroupTest, AddRequestTestExpire) {
+ RequestGroup req("");
+ auto start = std::chrono::steady_clock::now();
+ auto duration = 5ms;
+ bool ret = req.AddRequest("INTERACTION", start + duration);
+ EXPECT_EQ(true, ret);
+ ret = req.AddRequest("INTERACTION", start + duration + 1ms);
+ EXPECT_EQ(false, ret);
+ std::this_thread::sleep_for(duration + 10ms);
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+ EXPECT_EQ(false, active);
+}
+
+// Test AddRequest() with new value
+TEST(RequestGroupTest, AddRequestNewValue) {
+ RequestGroup req("");
+ auto start = std::chrono::steady_clock::now();
+ auto duration = 5000ms;
+ bool ret = req.AddRequest("INTERACTION", start + duration);
+ EXPECT_EQ(true, ret);
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ EXPECT_NEAR(duration.count(), expire_time.count(), kTIMING_TOLERANCE_MS);
+ EXPECT_EQ(true, active);
+ // Add a request shorter than the current outstanding one, expiration time
+ // not changed
+ auto shorter_duration = 100ms;
+ ret = req.AddRequest("INTERACTION", start + shorter_duration);
+ EXPECT_EQ(false, ret);
+ active = req.GetExpireTime(&expire_time);
+ EXPECT_NEAR(duration.count(), expire_time.count(), kTIMING_TOLERANCE_MS);
+ EXPECT_EQ(true, active);
+ // Add a request longer than the current outstanding one, expiration time
+ // changed
+ duration = 10000ms;
+ ret = req.AddRequest("INTERACTION", start + duration);
+ EXPECT_EQ(false, ret);
+ active = req.GetExpireTime(&expire_time);
+ EXPECT_NEAR(duration.count(), expire_time.count(), kTIMING_TOLERANCE_MS);
+ EXPECT_EQ(true, active);
+}
+
+// Test multiple AddRequest() with different hint_type
+TEST(RequestGroupTest, AddRequestTestMultiple) {
+ RequestGroup req("");
+ auto start = std::chrono::steady_clock::now();
+ auto duration_interact = 500ms;
+ req.AddRequest("INTERACTION", start + duration_interact);
+ auto duration_launch = 5000ms;
+ req.AddRequest("LAUNCH", start + duration_launch);
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ EXPECT_NEAR(std::min(duration_interact, duration_launch).count(),
+ expire_time.count(), kTIMING_TOLERANCE_MS);
+ EXPECT_EQ(true, active);
+}
+
+// Test RemoveRequest()
+TEST(RequestGroupTest, RemoveRequestTest) {
+ RequestGroup req("");
+ auto start = std::chrono::steady_clock::now();
+ auto duration_interact = 500ms;
+ req.AddRequest("INTERACTION", start + duration_interact);
+ bool ret = req.RemoveRequest("INTERACTION");
+ EXPECT_EQ(true, ret);
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ EXPECT_EQ(std::chrono::milliseconds::max(), expire_time);
+ EXPECT_EQ(false, active);
+ // Test removing an already-removed request
+ ret = req.RemoveRequest("INTERACTION");
+ EXPECT_EQ(false, ret);
+}
+
+// Test multiple RemoveRequest() with different hint_type
+TEST(RequestGroupTest, RemoveRequestTestMultiple) {
+ RequestGroup req("");
+ auto start = std::chrono::steady_clock::now();
+ auto duration_interact = 500ms;
+ req.AddRequest("INTERACTION", start + duration_interact);
+ auto duration_launch = 50000ms;
+ req.AddRequest("LAUNCH", start + duration_launch);
+ req.RemoveRequest("INTERACTION");
+ std::chrono::milliseconds expire_time;
+ bool active = req.GetExpireTime(&expire_time);
+ EXPECT_NEAR(duration_launch.count(), expire_time.count(),
+ kTIMING_TOLERANCE_MS);
+ EXPECT_EQ(true, active);
+}
+
+} // namespace perfmgr
+} // namespace android
diff --git a/power-libperfmgr/libperfmgr/tools/ConfigVerifier.cc b/power-libperfmgr/libperfmgr/tools/ConfigVerifier.cc
new file mode 100644
index 0000000..ad95910
--- /dev/null
+++ b/power-libperfmgr/libperfmgr/tools/ConfigVerifier.cc
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specic language governing permissions and
+ * limitations under the License.
+ */
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <getopt.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <thread>
+
+#include "perfmgr/HintManager.h"
+
+namespace android {
+namespace perfmgr {
+
+class NodeVerifier : public HintManager {
+ public:
+ static bool VerifyNodes(const std::string& config_path) {
+ std::string json_doc;
+
+ if (!android::base::ReadFileToString(config_path, &json_doc)) {
+ LOG(ERROR) << "Failed to read JSON config from " << config_path;
+ return false;
+ }
+
+ std::vector<std::unique_ptr<Node>> nodes = ParseNodes(json_doc);
+ if (nodes.empty()) {
+ LOG(ERROR) << "Failed to parse Nodes section from " << config_path;
+ return false;
+ }
+
+ return true;
+ }
+
+ private:
+ NodeVerifier(sp<NodeLooperThread> nm,
+ const std::map<std::string, std::vector<NodeAction>>& actions)
+ : HintManager(std::move(nm), actions) {}
+};
+
+} // namespace perfmgr
+} // namespace android
+
+static void printUsage(const char* exec_name) {
+ std::string usage = exec_name;
+ usage =
+ usage +
+ " is a command-line tool to verify Nodes in Json config are writable.\n"
+ "Usages:\n"
+ " [su system] " +
+ exec_name +
+ " [options]\n"
+ "\n"
+ "Options:\n"
+ " --config, -c [PATH]\n"
+ " path to Json config file\n\n"
+ " --exec_hint, -e\n"
+ " do hints in Json config\n\n"
+ " --hint_name, -i\n"
+ " do only the specific hint\n\n"
+ " --hint_duration, -d [duration]\n"
+ " duration in ms for each hint\n\n"
+ " --help, -h\n"
+ " print this message\n\n"
+ " --verbose, -v\n"
+ " print verbose log during execution\n\n";
+
+ LOG(INFO) << usage;
+}
+
+static void execConfig(const std::string& json_file,
+ const std::string& hint_name, uint64_t hint_duration) {
+ std::unique_ptr<android::perfmgr::HintManager> hm =
+ android::perfmgr::HintManager::GetFromJSON(json_file);
+ if (!hm.get() || !hm->IsRunning()) {
+ LOG(ERROR) << "Failed to Parse JSON config";
+ }
+ std::vector<std::string> hints = hm->GetHints();
+ for (const auto& hint : hints) {
+ if (!hint_name.empty() && hint_name != hint) continue;
+ LOG(INFO) << "Do hint: " << hint;
+ hm->DoHint(hint, std::chrono::milliseconds(hint_duration));
+ std::this_thread::yield();
+ std::this_thread::sleep_for(std::chrono::milliseconds(hint_duration));
+ LOG(INFO) << "End hint: " << hint;
+ hm->EndHint(hint);
+ std::this_thread::yield();
+ }
+}
+
+int main(int argc, char* argv[]) {
+ android::base::InitLogging(argv, android::base::StdioLogger);
+
+ if (getuid() == 0) {
+ LOG(WARNING) << "Running as root might mask node permission";
+ }
+
+ std::string config_path;
+ std::string hint_name;
+ bool exec_hint = false;
+ uint64_t hint_duration = 100;
+
+ while (true) {
+ static struct option opts[] = {
+ {"config", required_argument, nullptr, 'c'},
+ {"exec_hint", no_argument, nullptr, 'e'},
+ {"hint_name", required_argument, nullptr, 'i'},
+ {"hint_duration", required_argument, nullptr, 'd'},
+ {"help", no_argument, nullptr, 'h'},
+ {"verbose", no_argument, nullptr, 'v'},
+ {0, 0, 0, 0} // termination of the option list
+ };
+
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "c:ei:d:hv", opts, &option_index);
+ if (c == -1) {
+ break;
+ }
+
+ switch (c) {
+ case 'c':
+ config_path = optarg;
+ break;
+ case 'e':
+ exec_hint = true;
+ break;
+ case 'i':
+ hint_name = optarg;
+ break;
+ case 'd':
+ hint_duration = strtoul(optarg, NULL, 10);
+ break;
+ case 'v':
+ android::base::SetMinimumLogSeverity(android::base::VERBOSE);
+ break;
+ case 'h':
+ printUsage(argv[0]);
+ return 0;
+ default:
+ // getopt already prints "invalid option -- %c" for us.
+ return 1;
+ }
+ }
+
+ if (config_path.empty()) {
+ LOG(ERROR) << "Need specify JSON config";
+ printUsage(argv[0]);
+ return 1;
+ }
+
+ if (exec_hint) {
+ execConfig(config_path, hint_name, hint_duration);
+ return 0;
+ }
+
+ if (android::perfmgr::NodeVerifier::VerifyNodes(config_path)) {
+ LOG(INFO) << "Verified writing to JSON config";
+ return 0;
+ } else {
+ LOG(ERROR) << "Failed to verify nodes in JSON config";
+ return 1;
+ }
+}
diff --git a/pwrstats_util/.clang-format b/pwrstats_util/.clang-format
new file mode 120000
index 0000000..0457b53
--- /dev/null
+++ b/pwrstats_util/.clang-format
@@ -0,0 +1 @@
+../../../../build/soong/scripts/system-clang-format \ No newline at end of file
diff --git a/pwrstats_util/Android.bp b/pwrstats_util/Android.bp
new file mode 100644
index 0000000..ee9f824
--- /dev/null
+++ b/pwrstats_util/Android.bp
@@ -0,0 +1,75 @@
+//
+// 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.
+cc_library_static {
+ name: "libpowerstatsutil",
+ defaults: ["pwrstatsutil_defaults"],
+ export_include_dirs: ["."],
+ srcs: [
+ "pwrstats_util.cpp",
+ "PowerStatsCollector.cpp",
+ "dataproviders/PowerEntityResidencyDataProvider.cpp",
+ "dataproviders/RailEnergyDataProvider.cpp",
+ "dataproviders/DataProviderHelper.cpp",
+ "pwrstatsutil.proto",
+ ],
+ proto: {
+ export_proto_headers: true,
+ type: "full",
+ },
+ shared_libs: [
+ "libhidlbase",
+ "android.hardware.power.stats@1.0",
+ ],
+}
+
+// Useful defaults for pwrstats_util
+cc_defaults {
+ name: "pwrstatsutil_defaults",
+ vendor: true,
+ cflags: [
+ "-Wall",
+
+ // Try to catch typical 32-bit assumptions that break with 64-bit pointers.
+ "-Werror=pointer-to-int-cast",
+ "-Werror=int-to-pointer-cast",
+ "-Werror=type-limits",
+ "-Werror",
+ "-Wno-unused-parameter",
+ ],
+ tidy: true,
+ tidy_checks: [
+ "android-*",
+ "cert-*",
+ "clang-analyzer-security*",
+ "google-*",
+ "misc-*",
+ "performance-*",
+ ],
+ tidy_flags: [
+ "-warnings-as-errors="
+ + "'android-*'"
+ + ",'clang-analyzer-security*'"
+ + ",'cert-*'"
+ + ",'google-*'"
+ + ",'performance-*'"
+ + ",'misc-*'"
+ ],
+ shared_libs: [
+ "libbase",
+ "libutils",
+ "liblog",
+ "libprotobuf-cpp-full"
+ ],
+}
diff --git a/pwrstats_util/OWNERS b/pwrstats_util/OWNERS
new file mode 100644
index 0000000..b290b49
--- /dev/null
+++ b/pwrstats_util/OWNERS
@@ -0,0 +1,3 @@
+bsschwar@google.com
+krossmo@google.com
+tstrudel@google.com
diff --git a/pwrstats_util/PowerStatsCollector.cpp b/pwrstats_util/PowerStatsCollector.cpp
new file mode 100644
index 0000000..d2b1c6d
--- /dev/null
+++ b/pwrstats_util/PowerStatsCollector.cpp
@@ -0,0 +1,131 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "pwrstats_util"
+
+#include "PowerStatsCollector.h"
+
+#include <android-base/logging.h>
+
+void PowerStatsCollector::addDataProvider(std::unique_ptr<IPowerStatProvider> p) {
+ mStatProviders.emplace(p->typeOf(), std::move(p));
+}
+
+int PowerStatsCollector::get(std::vector<PowerStatistic>* stats) const {
+ if (!stats) {
+ LOG(ERROR) << __func__ << ": bad args; stat is null";
+ return 1;
+ }
+
+ stats->clear();
+ for (auto&& provider : mStatProviders) {
+ PowerStatistic stat;
+ if (provider.second->get(&stat) != 0) {
+ LOG(ERROR) << __func__ << ": a data provider failed";
+ stats->clear();
+ return 1;
+ }
+
+ stats->emplace_back(stat);
+ }
+ return 0;
+}
+
+int PowerStatsCollector::get(const std::vector<PowerStatistic>& start,
+ std::vector<PowerStatistic>* interval) const {
+ if (!interval) {
+ LOG(ERROR) << __func__ << ": bad args; interval is null";
+ return 1;
+ }
+
+ interval->clear();
+ for (auto const& curStat : start) {
+ auto provider = mStatProviders.find(curStat.power_stat_case());
+ if (provider == mStatProviders.end()) {
+ LOG(ERROR) << __func__ << ": a provider is missing";
+ interval->clear();
+ return 1;
+ }
+
+ PowerStatistic curInterval;
+ if (provider->second->get(curStat, &curInterval) != 0) {
+ LOG(ERROR) << __func__ << ": a data provider failed";
+ interval->clear();
+ return 1;
+ }
+ interval->emplace_back(curInterval);
+ }
+ return 0;
+}
+
+void PowerStatsCollector::dump(const std::vector<PowerStatistic>& stats,
+ std::ostream* output) const {
+ if (!output) {
+ LOG(ERROR) << __func__ << ": bad args; output is null";
+ return;
+ }
+
+ for (auto const& stat : stats) {
+ auto provider = mStatProviders.find(stat.power_stat_case());
+ if (provider == mStatProviders.end()) {
+ LOG(ERROR) << __func__ << ": a provider is missing";
+ return;
+ }
+
+ provider->second->dump(stat, output);
+ }
+}
+
+int IPowerStatProvider::get(PowerStatistic* stat) const {
+ if (!stat) {
+ LOG(ERROR) << __func__ << ": bad args; stat is null";
+ return 1;
+ }
+
+ return getImpl(stat);
+}
+
+int IPowerStatProvider::get(const PowerStatistic& start, PowerStatistic* interval) const {
+ if (!interval) {
+ LOG(ERROR) << __func__ << ": bad args; interval is null";
+ return 1;
+ }
+
+ if (typeOf() != start.power_stat_case()) {
+ LOG(ERROR) << __func__ << ": bad args; start is incorrect type";
+ return 1;
+ }
+
+ if (0 != getImpl(interval)) {
+ LOG(ERROR) << __func__ << ": unable to retrieve stats";
+ return 1;
+ }
+
+ return getImpl(start, interval);
+}
+
+void IPowerStatProvider::dump(const PowerStatistic& stat, std::ostream* output) const {
+ if (!output) {
+ LOG(ERROR) << __func__ << ": bad args; output is null";
+ return;
+ }
+
+ if (typeOf() != stat.power_stat_case()) {
+ LOG(ERROR) << __func__ << ": bad args; stat is incorrect type";
+ return;
+ }
+
+ dumpImpl(stat, output);
+}
diff --git a/pwrstats_util/PowerStatsCollector.h b/pwrstats_util/PowerStatsCollector.h
new file mode 100644
index 0000000..cf7c991
--- /dev/null
+++ b/pwrstats_util/PowerStatsCollector.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+#ifndef POWERSTATSCOLLECTOR_H
+#define POWERSTATSCOLLECTOR_H
+
+#include <memory>
+#include <unordered_map>
+#include <vector>
+
+#include <pwrstatsutil.pb.h>
+
+using com::google::android::pwrstatsutil::PowerStatistic;
+using PowerStatCase = com::google::android::pwrstatsutil::PowerStatistic::PowerStatCase;
+
+/**
+ * Classes that inherit from this can be used to provide stats in the form of key/value
+ * pairs to PwrStatsUtil.
+ **/
+class IPowerStatProvider {
+ public:
+ virtual ~IPowerStatProvider() = default;
+ int get(PowerStatistic* stat) const;
+ int get(const PowerStatistic& start, PowerStatistic* interval) const;
+ void dump(const PowerStatistic& stat, std::ostream* output) const;
+ virtual PowerStatCase typeOf() const = 0;
+
+ private:
+ virtual int getImpl(PowerStatistic* stat) const = 0;
+ virtual int getImpl(const PowerStatistic& start, PowerStatistic* interval) const = 0;
+ virtual void dumpImpl(const PowerStatistic& stat, std::ostream* output) const = 0;
+};
+
+/**
+ * This class is used to return stats in the form of key/value pairs for all registered classes
+ * that implement IPowerStatProvider.
+ **/
+class PowerStatsCollector {
+ public:
+ PowerStatsCollector() = default;
+ int get(std::vector<PowerStatistic>* stats) const;
+ int get(const std::vector<PowerStatistic>& start, std::vector<PowerStatistic>* interval) const;
+ void dump(const std::vector<PowerStatistic>& stats, std::ostream* output) const;
+ void addDataProvider(std::unique_ptr<IPowerStatProvider> statProvider);
+
+ private:
+ std::unordered_map<PowerStatCase, std::unique_ptr<IPowerStatProvider>> mStatProviders;
+};
+
+int run(int argc, char** argv, const PowerStatsCollector& collector);
+
+#endif // POWERSTATSCOLLECTOR_H
diff --git a/pwrstats_util/dataproviders/DataProviderHelper.cpp b/pwrstats_util/dataproviders/DataProviderHelper.cpp
new file mode 100644
index 0000000..1494c54
--- /dev/null
+++ b/pwrstats_util/dataproviders/DataProviderHelper.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "pwrstats_util"
+
+#include "DataProviderHelper.h"
+#include <android-base/logging.h>
+
+int StateResidencyInterval(const StateResidency_Residency& startResidency,
+ StateResidency_Residency* intervalResidency) {
+ // If start and interval are not the same size then they cannot have matching data
+ if (startResidency.size() != intervalResidency->size()) {
+ LOG(ERROR) << __func__ << ": mismatched data";
+ return 1;
+ }
+
+ for (int i = 0; i < startResidency.size(); ++i) {
+ // Check and make sure each entry matches. Data are in sorted order so if there is a
+ // mismatch then we will bail.
+ if (startResidency.Get(i).entity_name() != intervalResidency->Get(i).entity_name() ||
+ startResidency.Get(i).state_name() != intervalResidency->Get(i).state_name()) {
+ LOG(ERROR) << __func__ << ": mismatched data";
+ return 1;
+ }
+
+ auto delta = intervalResidency->Get(i).time_ms() - startResidency.Get(i).time_ms();
+ intervalResidency->Mutable(i)->set_time_ms(delta);
+ }
+ return 0;
+}
+
+void StateResidencyDump(const StateResidency_Residency& stateResidency, std::ostream* output) {
+ for (auto const& residency : stateResidency) {
+ *output << residency.entity_name() << ":" << residency.state_name() << "="
+ << residency.time_ms() << std::endl;
+ }
+ *output << std::endl;
+}
diff --git a/pwrstats_util/dataproviders/DataProviderHelper.h b/pwrstats_util/dataproviders/DataProviderHelper.h
new file mode 100644
index 0000000..7f931ff
--- /dev/null
+++ b/pwrstats_util/dataproviders/DataProviderHelper.h
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+#ifndef DATAPROVIDERHELPER_H
+#define DATAPROVIDERHELPER_H
+
+#include <pwrstatsutil.pb.h>
+
+using StateResidency_Residency = ::google::protobuf::RepeatedPtrField<
+ ::com::google::android::pwrstatsutil::StateResidency_Residency>;
+int StateResidencyInterval(const StateResidency_Residency& start,
+ StateResidency_Residency* interval);
+
+void StateResidencyDump(const StateResidency_Residency& stateResidency, std::ostream* output);
+
+#endif // DATAPROVIDERHELPER_H
diff --git a/pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.cpp b/pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.cpp
new file mode 100644
index 0000000..fc5d66a
--- /dev/null
+++ b/pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.cpp
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "pwrstats_util"
+
+#include "PowerEntityResidencyDataProvider.h"
+#include "DataProviderHelper.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/power/stats/1.0/IPowerStats.h>
+
+using android::sp;
+using android::hardware::Return;
+
+/**
+ * Power Entity State Residency data provider:
+ * Provides data monitored by Power Stats HAL 1.0
+ **/
+
+int PowerEntityResidencyDataProvider::getImpl(PowerStatistic* stat) const {
+ sp<android::hardware::power::stats::V1_0::IPowerStats> powerStatsService =
+ android::hardware::power::stats::V1_0::IPowerStats::getService();
+ if (powerStatsService == nullptr) {
+ LOG(ERROR) << "unable to get power.stats HAL service";
+ return 1;
+ }
+
+ std::unordered_map<uint32_t, std::string> entityNames;
+ std::unordered_map<uint32_t, std::unordered_map<uint32_t, std::string>> stateNames;
+
+ // Create map of entity names based on entity id
+ Return<void> ret;
+ ret = powerStatsService->getPowerEntityInfo([&entityNames](auto infos, auto /* status */) {
+ for (auto const& info : infos) {
+ entityNames.emplace(info.powerEntityId, info.powerEntityName);
+ }
+ });
+ if (!ret.isOk()) {
+ LOG(ERROR) << __func__ << ": unable to get entity info";
+ return 1;
+ }
+
+ // Create map of each entity's states based on entity and state id
+ ret = powerStatsService->getPowerEntityStateInfo({}, [&stateNames](auto stateSpaces,
+ auto /* status */) {
+ for (auto const& stateSpace : stateSpaces) {
+ stateNames.emplace(stateSpace.powerEntityId,
+ std::unordered_map<uint32_t, std::string>());
+ auto& entityStateNames = stateNames.at(stateSpace.powerEntityId);
+ for (auto const& state : stateSpace.states) {
+ entityStateNames.emplace(state.powerEntityStateId, state.powerEntityStateName);
+ }
+ }
+ });
+ if (!ret.isOk()) {
+ LOG(ERROR) << __func__ << ": unable to get state info";
+ return 1;
+ }
+
+ // Retrieve residency data and create the PowerStatistic::PowerEntityStateResidency
+ ret = powerStatsService->getPowerEntityStateResidencyData({}, [&entityNames, &stateNames,
+ &stat](auto results,
+ auto /* status */) {
+ auto residencies = stat->mutable_power_entity_state_residency();
+ for (auto const& result : results) {
+ for (auto const& curStateResidency : result.stateResidencyData) {
+ auto residency = residencies->add_residency();
+ residency->set_entity_name(entityNames.at(result.powerEntityId));
+ residency->set_state_name(stateNames.at(result.powerEntityId)
+ .at(curStateResidency.powerEntityStateId));
+ residency->set_time_ms(static_cast<uint64_t>(curStateResidency.totalTimeInStateMs));
+ }
+ }
+
+ // Sort entries first by entity_name, then by state_name.
+ // Sorting is needed to make interval processing efficient.
+ std::sort(residencies->mutable_residency()->begin(),
+ residencies->mutable_residency()->end(), [](const auto& a, const auto& b) {
+ if (a.entity_name() != b.entity_name()) {
+ return a.entity_name() < b.entity_name();
+ }
+
+ return a.state_name() < b.state_name();
+ });
+ });
+ if (!ret.isOk()) {
+ LOG(ERROR) << __func__ << ": Unable to get residency info";
+ return 1;
+ }
+
+ return 0;
+}
+
+int PowerEntityResidencyDataProvider::getImpl(const PowerStatistic& start,
+ PowerStatistic* interval) const {
+ auto startResidency = start.power_entity_state_residency().residency();
+ auto intervalResidency = interval->mutable_power_entity_state_residency()->mutable_residency();
+
+ if (0 != StateResidencyInterval(startResidency, intervalResidency)) {
+ interval->clear_power_entity_state_residency();
+ return 1;
+ }
+
+ return 0;
+}
+
+void PowerEntityResidencyDataProvider::dumpImpl(const PowerStatistic& stat,
+ std::ostream* output) const {
+ *output << "Power Entity State Residencies:" << std::endl;
+ StateResidencyDump(stat.power_entity_state_residency().residency(), output);
+}
+
+PowerStatCase PowerEntityResidencyDataProvider::typeOf() const {
+ return PowerStatCase::kPowerEntityStateResidency;
+}
diff --git a/pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.h b/pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.h
new file mode 100644
index 0000000..b7264bb
--- /dev/null
+++ b/pwrstats_util/dataproviders/PowerEntityResidencyDataProvider.h
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+#ifndef POWERENTITYRESIDENCYDATAPROVIDER_H
+#define POWERENTITYRESIDENCYDATAPROVIDER_H
+
+#include "PowerStatsCollector.h"
+
+class PowerEntityResidencyDataProvider : public IPowerStatProvider {
+ public:
+ PowerEntityResidencyDataProvider() = default;
+ PowerStatCase typeOf() const override;
+
+ private:
+ int getImpl(PowerStatistic* stat) const override;
+ int getImpl(const PowerStatistic& start, PowerStatistic* interval) const override;
+ void dumpImpl(const PowerStatistic& stat, std::ostream* output) const override;
+};
+
+#endif // POWERENTITYRESIDENCYDATAPROVIDER_H
diff --git a/pwrstats_util/dataproviders/RailEnergyDataProvider.cpp b/pwrstats_util/dataproviders/RailEnergyDataProvider.cpp
new file mode 100644
index 0000000..7a2213e
--- /dev/null
+++ b/pwrstats_util/dataproviders/RailEnergyDataProvider.cpp
@@ -0,0 +1,123 @@
+/*
+ * 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.
+ */
+#define LOG_TAG "pwrstats_util"
+
+#include "RailEnergyDataProvider.h"
+#include <android-base/logging.h>
+#include <android/hardware/power/stats/1.0/IPowerStats.h>
+
+using android::sp;
+using android::hardware::Return;
+using android::hardware::power::stats::V1_0::IPowerStats;
+using android::hardware::power::stats::V1_0::Status;
+
+int RailEnergyDataProvider::getImpl(PowerStatistic* stat) const {
+ sp<android::hardware::power::stats::V1_0::IPowerStats> powerStatsService =
+ android::hardware::power::stats::V1_0::IPowerStats::getService();
+ if (powerStatsService == nullptr) {
+ LOG(ERROR) << "unable to get power.stats HAL service";
+ return 1;
+ }
+
+ std::unordered_map<uint32_t, std::string> railNames;
+
+ Return<void> ret;
+ Status retStatus = Status::SUCCESS;
+ ret = powerStatsService->getRailInfo([&railNames, &retStatus](auto railInfos, auto status) {
+ retStatus = status;
+ if (status != Status::SUCCESS) {
+ return;
+ }
+
+ for (auto const& info : railInfos) {
+ railNames.emplace(info.index, info.railName);
+ }
+ });
+ if (retStatus == Status::NOT_SUPPORTED) {
+ LOG(WARNING) << __func__ << ": rail energy stats not supported";
+ return 0;
+ }
+ if (!ret.isOk() || retStatus != Status::SUCCESS) {
+ LOG(ERROR) << __func__ << ": no rail information available";
+ return 1;
+ }
+
+ auto railEntries = stat->mutable_rail_energy();
+ bool resultSuccess = true;
+ ret = powerStatsService->getEnergyData(
+ {}, [&railEntries, &railNames, &resultSuccess](auto energyData, auto status) {
+ if (status != Status::SUCCESS) {
+ LOG(ERROR) << __func__ << ": unable to get rail energy";
+ resultSuccess = false;
+ return;
+ }
+
+ for (auto const& energyDatum : energyData) {
+ auto entry = railEntries->add_entry();
+ entry->set_rail_name(railNames.at(energyDatum.index));
+ entry->set_energy_uws(energyDatum.energy);
+ }
+ });
+ if (!ret.isOk() || !resultSuccess) {
+ stat->clear_rail_energy();
+ LOG(ERROR) << __func__ << ": failed to get rail energy stats";
+ return 1;
+ }
+
+ // Sort entries by name. Sorting is needed to make interval processing efficient.
+ std::sort(railEntries->mutable_entry()->begin(), railEntries->mutable_entry()->end(),
+ [](const auto& a, const auto& b) { return a.rail_name() < b.rail_name(); });
+
+ return 0;
+}
+
+int RailEnergyDataProvider::getImpl(const PowerStatistic& start, PowerStatistic* interval) const {
+ auto startEnergy = start.rail_energy().entry();
+ auto intervalEnergy = interval->mutable_rail_energy()->mutable_entry();
+
+ // If start and interval are not the same size then they cannot have matching data
+ if (startEnergy.size() != intervalEnergy->size()) {
+ LOG(ERROR) << __func__ << ": mismatched data";
+ interval->clear_rail_energy();
+ return 1;
+ }
+
+ for (int i = 0; i < startEnergy.size(); ++i) {
+ // Check and make sure each entry matches. Data are in sorted order so if there is a
+ // mismatch then we will bail.
+ if (startEnergy.Get(i).rail_name() != intervalEnergy->Get(i).rail_name()) {
+ LOG(ERROR) << __func__ << ": mismatched data";
+ interval->clear_rail_energy();
+ return 1;
+ }
+
+ auto delta = intervalEnergy->Get(i).energy_uws() - startEnergy.Get(i).energy_uws();
+ intervalEnergy->Mutable(i)->set_energy_uws(delta);
+ }
+ return 0;
+}
+
+void RailEnergyDataProvider::dumpImpl(const PowerStatistic& stat, std::ostream* output) const {
+ *output << "Rail Energy:" << std::endl;
+ for (auto const& rail : stat.rail_energy().entry()) {
+ *output << rail.rail_name() << "=" << rail.energy_uws() << std::endl;
+ }
+ *output << std::endl;
+}
+
+PowerStatCase RailEnergyDataProvider::typeOf() const {
+ return PowerStatCase::kRailEnergy;
+}
diff --git a/pwrstats_util/dataproviders/RailEnergyDataProvider.h b/pwrstats_util/dataproviders/RailEnergyDataProvider.h
new file mode 100644
index 0000000..abdaa6d
--- /dev/null
+++ b/pwrstats_util/dataproviders/RailEnergyDataProvider.h
@@ -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.
+ */
+#ifndef RAILENERGYDATAPROVIDER_H
+#define RAILENERGYDATAPROVIDER_H
+
+#include "PowerStatsCollector.h"
+
+/**
+ * Rail Energy data provider:
+ * Provides data via Power Stats HAL 1.0
+ * data is in units of microwatt-seconds (uWs)
+ **/
+class RailEnergyDataProvider : public IPowerStatProvider {
+ public:
+ RailEnergyDataProvider() = default;
+ PowerStatCase typeOf() const override;
+
+ private:
+ int getImpl(PowerStatistic* stat) const override;
+ int getImpl(const PowerStatistic& start, PowerStatistic* interval) const override;
+ void dumpImpl(const PowerStatistic& stat, std::ostream* output) const override;
+};
+
+#endif // RAILENERGYDATAPROVIDER_H
diff --git a/pwrstats_util/pwrstats_util.cpp b/pwrstats_util/pwrstats_util.cpp
new file mode 100644
index 0000000..55ef071
--- /dev/null
+++ b/pwrstats_util/pwrstats_util.cpp
@@ -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.
+ */
+#define LOG_TAG "pwrstats_util"
+
+#include <android-base/logging.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <algorithm>
+#include <chrono>
+#include <csignal>
+#include <fstream>
+#include <iostream>
+
+#include <pwrstatsutil.pb.h>
+#include "PowerStatsCollector.h"
+
+namespace {
+volatile std::sig_atomic_t gSignalStatus;
+}
+
+static void signalHandler(int signal) {
+ gSignalStatus = signal;
+}
+
+class Options {
+ public:
+ bool humanReadable;
+ bool daemonMode;
+ std::string filePath;
+};
+
+static Options parseArgs(int argc, char** argv) {
+ Options opt = {
+ .humanReadable = false,
+ .daemonMode = false,
+ };
+
+ static struct option long_options[] = {/* These options set a flag. */
+ {"human-readable", no_argument, 0, 0},
+ {"daemon", required_argument, 0, 'd'},
+ {0, 0, 0, 0}};
+
+ // getopt_long stores the option index here
+ int option_index = 0;
+
+ int c;
+ while ((c = getopt_long(argc, argv, "d:", long_options, &option_index)) != -1) {
+ switch (c) {
+ case 0:
+ if ("human-readable" == std::string(long_options[option_index].name)) {
+ opt.humanReadable = true;
+ }
+ break;
+ case 'd':
+ opt.daemonMode = true;
+ opt.filePath = std::string(optarg);
+ break;
+ default: /* '?' */
+ std::cerr << "pwrstats_util: Prints out device power stats." << std::endl
+ << "--human-readable: human-readable format" << std::endl
+ << "--daemon <path/to/file>, -d <path/to/file>: daemon mode. Spawns a "
+ "daemon process and prints out its <pid>. kill -INT <pid> will "
+ "trigger a write to specified file."
+ << std::endl;
+ exit(EXIT_FAILURE);
+ }
+ }
+ return opt;
+}
+
+static void snapshot(const Options& opt, const PowerStatsCollector& collector) {
+ std::vector<PowerStatistic> stats;
+ int ret = collector.get(&stats);
+ if (ret) {
+ exit(EXIT_FAILURE);
+ }
+
+ if (opt.humanReadable) {
+ collector.dump(stats, &std::cout);
+ } else {
+ for (const auto& stat : stats) {
+ stat.SerializeToOstream(&std::cout);
+ }
+ }
+
+ exit(EXIT_SUCCESS);
+}
+
+static void daemon(const Options& opt, const PowerStatsCollector& collector) {
+ // Following a subset of steps outlined in http://man7.org/linux/man-pages/man7/daemon.7.html
+
+ // Call fork to create child process
+ pid_t pid;
+ if ((pid = fork()) < 0) {
+ LOG(ERROR) << "can't fork" << std::endl;
+ exit(EXIT_FAILURE);
+ } else if (pid != 0) {
+ std::cout << "pid = " << pid << std::endl;
+ exit(EXIT_SUCCESS);
+ }
+ // Daemon process:
+
+ // Get maximum number of file descriptors
+ struct rlimit rl;
+ if (getrlimit(RLIMIT_NOFILE, &rl) < 0) {
+ LOG(ERROR) << "can't get file limit" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ // Close all open file descriptors
+ if (rl.rlim_max == RLIM_INFINITY) {
+ rl.rlim_max = 1024;
+ }
+ for (int i = 0; i < rl.rlim_max; i++) {
+ close(i);
+ }
+
+ // Detach from any terminal and create an independent session
+ if (setsid() < 0) {
+ LOG(ERROR) << "SID creation failed";
+ exit(EXIT_FAILURE);
+ }
+
+ // connect /dev/null to standard input, output, and error.
+ int devnull = open("/dev/null", O_RDWR | O_CLOEXEC);
+ dup2(devnull, STDIN_FILENO);
+ dup2(devnull, STDOUT_FILENO);
+ dup2(devnull, STDERR_FILENO);
+
+ // Reset the umask to 0
+ umask(0);
+
+ // Change the current directory to the root
+ // directory (/), in order to avoid that the daemon involuntarily
+ // blocks mount points from being unmounted
+ if (chdir("/") < 0) {
+ LOG(ERROR) << "can't change directory to /" << std::endl;
+ exit(EXIT_FAILURE);
+ }
+
+ // Install a signal handler
+ std::signal(SIGINT, signalHandler);
+
+ // get the start_data
+ auto start_time = std::chrono::system_clock::now();
+
+ std::vector<PowerStatistic> start_stats;
+ int ret = collector.get(&start_stats);
+ if (ret) {
+ LOG(ERROR) << "failed to get start stats";
+ exit(EXIT_FAILURE);
+ }
+
+ // Wait for INT signal
+ while (gSignalStatus != SIGINT) {
+ pause();
+ }
+
+ // get the end data
+ std::vector<PowerStatistic> interval_stats;
+ ret = collector.get(start_stats, &interval_stats);
+ if (ret) {
+ LOG(ERROR) << "failed to get interval stats";
+ exit(EXIT_FAILURE);
+ }
+ auto end_time = std::chrono::system_clock::now();
+
+ std::chrono::duration<double> elapsed_seconds = end_time - start_time;
+
+ // Write data to file
+ std::ofstream myfile(opt.filePath, std::ios::out | std::ios::binary);
+ if (!myfile.is_open()) {
+ LOG(ERROR) << "failed to open file";
+ exit(EXIT_FAILURE);
+ }
+ myfile << "elapsed time: " << elapsed_seconds.count() << "s" << std::endl;
+ if (opt.humanReadable) {
+ collector.dump(interval_stats, &myfile);
+ } else {
+ for (const auto& stat : interval_stats) {
+ stat.SerializeToOstream(&myfile);
+ }
+ }
+
+ myfile.close();
+
+ exit(EXIT_SUCCESS);
+}
+
+static void runWithOptions(const Options& opt, const PowerStatsCollector& collector) {
+ if (opt.daemonMode) {
+ daemon(opt, collector);
+ } else {
+ snapshot(opt, collector);
+ }
+}
+
+int run(int argc, char** argv, const PowerStatsCollector& collector) {
+ Options opt = parseArgs(argc, argv);
+
+ runWithOptions(opt, collector);
+
+ return 0;
+}
diff --git a/pwrstats_util/pwrstatsutil.proto b/pwrstats_util/pwrstatsutil.proto
new file mode 100644
index 0000000..ee708a6
--- /dev/null
+++ b/pwrstats_util/pwrstatsutil.proto
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+syntax = "proto3";
+
+option java_package = "com.google.android.pwrstatsutil";
+package com.google.android.pwrstatsutil;
+
+
+message PowerStatistic {
+ oneof power_stat {
+ StateResidency power_entity_state_residency = 1;
+ RailEnergy rail_energy = 2;
+ StateResidency c_state_residency = 3;
+ // add new power_stats here
+ }
+}
+
+// Utility message for items that provide a state residency in milliseconds
+message StateResidency {
+ message Residency {
+ string entity_name = 1;
+ string state_name = 2;
+ uint64 time_ms = 3;
+ }
+
+ repeated Residency residency = 1;
+}
+
+// Rail energy data in uWs
+message RailEnergy {
+ message RailEntry {
+ string rail_name = 1;
+ uint64 energy_uws = 2;
+ }
+
+ repeated RailEntry entry = 1;
+}
diff --git a/thermal/Android.bp b/thermal/Android.bp
index dce083b..7875ea4 100644
--- a/thermal/Android.bp
+++ b/thermal/Android.bp
@@ -39,6 +39,15 @@ cc_binary {
"clang-analyzer-security*",
],
tidy_flags: [
- "-warnings-as-errors=android-*,clang-analyzer-security*,cert-*"
+ "-warnings-as-errors=android-*,clang-analyzer-security*,cert-*"
+ ],
+}
+
+sh_binary {
+ name: "thermal_logd",
+ src: "init.thermal.logging.sh",
+ vendor: true,
+ init_rc: [
+ "pixel-thermal-logd.rc",
],
}
diff --git a/thermal/OWNERS b/thermal/OWNERS
new file mode 100644
index 0000000..0205505
--- /dev/null
+++ b/thermal/OWNERS
@@ -0,0 +1,3 @@
+wvw@google.com
+paillon@google.com
+jychen@google.com
diff --git a/thermal/device.mk b/thermal/device.mk
new file mode 100644
index 0000000..a2d68a3
--- /dev/null
+++ b/thermal/device.mk
@@ -0,0 +1,10 @@
+# Thermal HAL
+PRODUCT_PACKAGES += \
+ android.hardware.thermal@2.0-service.pixel \
+
+ifneq (,$(filter userdebug eng, $(TARGET_BUILD_VARIANT)))
+PRODUCT_PACKAGES += \
+ thermal_logd
+endif
+
+BOARD_SEPOLICY_DIRS += hardware/google/pixel-sepolicy/thermal
diff --git a/thermal/init.thermal.logging.sh b/thermal/init.thermal.logging.sh
new file mode 100755
index 0000000..de385ab
--- /dev/null
+++ b/thermal/init.thermal.logging.sh
@@ -0,0 +1,25 @@
+#!/vendor/bin/sh
+
+if [ $# -eq 1 ]; then
+ interval=$1
+else
+ exit 1
+fi
+
+while true
+do
+ logline="tz:"
+ for f in /sys/class/thermal/thermal*
+ do
+ temp=`cat $f/temp`
+ logline+="|$temp"
+ done
+ logline+=" cdev:"
+ for f in /sys/class/thermal/cooling_device*
+ do
+ cur_state=`cat $f/cur_state`
+ logline+="|$cur_state"
+ done
+ log -p w -t THERMAL_LOG $logline
+ sleep $interval
+done
diff --git a/thermal/pixel-thermal-logd.rc b/thermal/pixel-thermal-logd.rc
new file mode 100644
index 0000000..c69282d
--- /dev/null
+++ b/thermal/pixel-thermal-logd.rc
@@ -0,0 +1,14 @@
+on property:persist.vendor.log.thermal=1
+ start vendor.thermal.logd
+
+on property:persist.vendor.log.thermal=0
+ stop vendor.thermal.logd
+
+on property:persist.vendor.log.thermal=1 && property:persist.vendor.log.thermal.interval=*
+ restart vendor.thermal.logd
+
+service vendor.thermal.logd /vendor/bin/thermal_logd ${persist.vendor.log.thermal.interval:-5}
+ class main
+ user root
+ group root system
+ disabled
diff --git a/vibrator/Android.bp b/vibrator/Android.bp
new file mode 100644
index 0000000..fd8c8c1
--- /dev/null
+++ b/vibrator/Android.bp
@@ -0,0 +1,48 @@
+//
+// 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.
+
+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-ndk_platform",
+ ],
+}
+
+cc_defaults {
+ name: "PixelVibratorTestDefaults",
+ defaults: ["PixelVibratorDefaults"],
+ static_libs: [
+ "android.hardware.vibrator-ndk_platform",
+ ],
+ test_suites: ["device-tests"],
+ require_root: true,
+}
diff --git a/vibrator/OWNERS b/vibrator/OWNERS
new file mode 100644
index 0000000..928c9ff
--- /dev/null
+++ b/vibrator/OWNERS
@@ -0,0 +1,3 @@
+chasewu@google.com
+eliptus@google.com
+michaelwr@google.com
diff --git a/vibrator/common/Android.bp b/vibrator/common/Android.bp
new file mode 100644
index 0000000..d40e7a9
--- /dev/null
+++ b/vibrator/common/Android.bp
@@ -0,0 +1,33 @@
+//
+// 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.
+
+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..d87806d
--- /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 <fstream>
+#include <sstream>
+
+#include <cutils/properties.h>
+#include <log/log.h>
+
+#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<struct context *>(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..fb5acab
--- /dev/null
+++ b/vibrator/common/HardwareBase.h
@@ -0,0 +1,208 @@
+/*
+ * 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 <list>
+#include <map>
+#include <sstream>
+#include <string>
+
+#include <android-base/unique_fd.h>
+#include <log/log.h>
+#include <sys/epoll.h>
+#include <utils/Trace.h>
+
+#include "utils.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::android::base::unique_fd;
+
+class HwApiBase {
+ private:
+ using NamesMap = std::map<const std::ios *, std::string>;
+
+ class RecordInterface {
+ public:
+ virtual std::string toString(const NamesMap &names) = 0;
+ virtual ~RecordInterface() {}
+ };
+ template <typename T>
+ 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<std::unique_ptr<RecordInterface>>;
+
+ 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 <typename T>
+ void open(const std::string &name, T *stream);
+ bool has(const std::ios &stream);
+ template <typename T>
+ bool get(T *value, std::istream *stream);
+ template <typename T>
+ bool set(const T &value, std::ostream *stream);
+ template <typename T>
+ bool poll(const T &value, std::istream *stream);
+ template <typename T>
+ 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;
+};
+
+#define HWAPI_RECORD(args...) HwApiBase::record(__FUNCTION__, ##args)
+
+template <typename T>
+void HwApiBase::open(const std::string &name, T *stream) {
+ saveName(name, stream);
+ utils::openNoCreate(mPathPrefix + name, stream);
+}
+
+template <typename T>
+bool HwApiBase::get(T *value, std::istream *stream) {
+ ATRACE_NAME("HwApi::get");
+ 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 <typename T>
+bool HwApiBase::set(const T &value, std::ostream *stream) {
+ ATRACE_NAME("HwApi::set");
+ using utils::operator<<;
+ 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 <typename T>
+bool HwApiBase::poll(const T &value, std::istream *stream) {
+ 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;
+
+ 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)) {
+ epoll_wait(epollFd, &event, 1, -1);
+ }
+
+ HWAPI_RECORD(value, stream);
+ return ret;
+}
+
+template <typename T>
+void HwApiBase::record(const char *func, const T &value, const std::ios *stream) {
+ std::lock_guard<std::mutex> lock(mRecordsMutex);
+ mRecords.emplace_back(std::make_unique<Record<T>>(func, value, stream));
+ mRecords.pop_front();
+}
+
+template <typename T>
+std::string HwApiBase::Record<T>::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 <typename T>
+ bool getProperty(const char *key, T *value, const T defval);
+ template <typename T>
+ bool getPersist(const char *key, T *value);
+
+ private:
+ std::string mPropertyPrefix;
+ std::map<std::string, std::string> mCalData;
+};
+
+template <typename T>
+bool HwCalBase::getProperty(const char *key, T *outval, const T defval) {
+ ATRACE_NAME("HwCal::getProperty");
+ *outval = utils::getProperty(mPropertyPrefix + key, defval);
+ return true;
+}
+
+template <typename T>
+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/TEST_MAPPING b/vibrator/common/TEST_MAPPING
new file mode 100644
index 0000000..3c77ebc
--- /dev/null
+++ b/vibrator/common/TEST_MAPPING
@@ -0,0 +1,14 @@
+{
+ "postsubmit": [
+ {
+ "name": "VibratorHalIntegrationBenchmark"
+ },
+ // TODO(b/137256707): Consolidate with above.
+ {
+ "name": "VibratorHalIntegrationBenchmark",
+ "keywords": [
+ "primary-device"
+ ]
+ }
+ ]
+}
diff --git a/vibrator/common/bench/Android.bp b/vibrator/common/bench/Android.bp
new file mode 100644
index 0000000..641b73a
--- /dev/null
+++ b/vibrator/common/bench/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+
+cc_benchmark {
+ name: "VibratorHalIntegrationBenchmark",
+ defaults: ["hidl_defaults"],
+ srcs: [
+ "benchmark.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.vibrator@1.0",
+ "android.hardware.vibrator@1.1",
+ "android.hardware.vibrator@1.2",
+ "android.hardware.vibrator@1.3",
+ "libhardware",
+ "libhidlbase",
+ "libutils",
+ ],
+ test_suites: ["device-tests"],
+}
diff --git a/vibrator/common/bench/benchmark.cpp b/vibrator/common/bench/benchmark.cpp
new file mode 100644
index 0000000..5886934
--- /dev/null
+++ b/vibrator/common/bench/benchmark.cpp
@@ -0,0 +1,272 @@
+/*
+ * 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 "benchmark/benchmark.h"
+
+#include <android/hardware/vibrator/1.3/IVibrator.h>
+
+using ::android::sp;
+using ::android::hardware::hidl_enum_range;
+using ::android::hardware::Return;
+using ::android::hardware::details::hidl_enum_values;
+using ::benchmark::Counter;
+using ::benchmark::Fixture;
+using ::benchmark::kMicrosecond;
+using ::benchmark::State;
+using ::benchmark::internal::Benchmark;
+using ::std::chrono::duration;
+using ::std::chrono::duration_cast;
+using ::std::chrono::high_resolution_clock;
+
+namespace V1_0 = ::android::hardware::vibrator::V1_0;
+namespace V1_1 = ::android::hardware::vibrator::V1_1;
+namespace V1_2 = ::android::hardware::vibrator::V1_2;
+namespace V1_3 = ::android::hardware::vibrator::V1_3;
+
+template <typename I>
+class VibratorBench : public Fixture {
+ public:
+ void SetUp(State & /*state*/) override { mVibrator = I::getService(); }
+
+ void TearDown(State & /*state*/) override {
+ if (!mVibrator) {
+ return;
+ }
+ mVibrator->off();
+ }
+
+ static void DefaultConfig(Benchmark *b) { b->Unit(kMicrosecond); }
+
+ static void DefaultArgs(Benchmark * /*b*/) {
+ // none
+ }
+
+ protected:
+ auto getOtherArg(const State &state, std::size_t index) const { return state.range(index + 0); }
+
+ protected:
+ sp<I> mVibrator;
+};
+
+enum class EmptyEnum : uint32_t;
+template <>
+inline constexpr std::array<EmptyEnum, 0> hidl_enum_values<EmptyEnum> = {};
+
+template <typename T, typename U>
+std::set<T> difference(const hidl_enum_range<T> &t, const hidl_enum_range<U> &u) {
+ class Compare {
+ public:
+ bool operator()(const T &a, const U &b) { return a < static_cast<T>(b); }
+ bool operator()(const U &a, const T &b) { return static_cast<T>(a) < b; }
+ };
+ std::set<T> ret;
+
+ std::set_difference(t.begin(), t.end(), u.begin(), u.end(),
+ std::insert_iterator<decltype(ret)>(ret, ret.begin()), Compare());
+
+ return ret;
+}
+
+template <typename I, typename E1, typename E2 = EmptyEnum>
+class VibratorEffectsBench : public VibratorBench<I> {
+ public:
+ using Effect = E1;
+ using EffectStrength = V1_0::EffectStrength;
+ using Status = V1_0::Status;
+
+ public:
+ static void DefaultArgs(Benchmark *b) {
+ b->ArgNames({"Effect", "Strength"});
+ for (const auto &effect : difference(hidl_enum_range<E1>(), hidl_enum_range<E2>())) {
+ for (const auto &strength : hidl_enum_range<EffectStrength>()) {
+ b->Args({static_cast<long>(effect), static_cast<long>(strength)});
+ }
+ }
+ }
+
+ void performBench(State *state, Return<void> (I::*performApi)(Effect, EffectStrength,
+ typename I::perform_cb)) {
+ auto effect = getEffect(*state);
+ auto strength = getStrength(*state);
+ bool supported = true;
+
+ (*this->mVibrator.*performApi)(effect, strength, [&](Status status, uint32_t /*lengthMs*/) {
+ if (status == Status::UNSUPPORTED_OPERATION) {
+ supported = false;
+ }
+ });
+
+ if (!supported) {
+ return;
+ }
+
+ for (auto _ : *state) {
+ state->ResumeTiming();
+ (*this->mVibrator.*performApi)(effect, strength,
+ [](Status /*status*/, uint32_t /*lengthMs*/) {});
+ state->PauseTiming();
+ this->mVibrator->off();
+ }
+ }
+
+ protected:
+ auto getEffect(const State &state) const {
+ return static_cast<Effect>(this->getOtherArg(state, 0));
+ }
+
+ auto getStrength(const State &state) const {
+ return static_cast<EffectStrength>(this->getOtherArg(state, 1));
+ }
+};
+
+#define BENCHMARK_WRAPPER(fixt, test, code) \
+ BENCHMARK_DEFINE_F(fixt, test) \
+ /* NOLINTNEXTLINE */ \
+ (State & state) { \
+ if (!mVibrator) { \
+ return; \
+ } \
+ \
+ code \
+ } \
+ BENCHMARK_REGISTER_F(fixt, test)->Apply(fixt::DefaultConfig)->Apply(fixt::DefaultArgs)
+
+using VibratorBench_V1_0 = VibratorBench<V1_0::IVibrator>;
+
+BENCHMARK_WRAPPER(VibratorBench_V1_0, on, {
+ uint32_t ms = UINT32_MAX;
+
+ for (auto _ : state) {
+ state.ResumeTiming();
+ mVibrator->on(ms);
+ state.PauseTiming();
+ mVibrator->off();
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench_V1_0, off, {
+ uint32_t ms = UINT32_MAX;
+
+ for (auto _ : state) {
+ state.PauseTiming();
+ mVibrator->on(ms);
+ state.ResumeTiming();
+ mVibrator->off();
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench_V1_0, supportsAmplitudeControl, {
+ for (auto _ : state) {
+ mVibrator->supportsAmplitudeControl();
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench_V1_0, setAmplitude, {
+ uint8_t amplitude = UINT8_MAX;
+
+ if (!mVibrator->supportsAmplitudeControl()) {
+ return;
+ }
+
+ mVibrator->on(UINT32_MAX);
+
+ for (auto _ : state) {
+ mVibrator->setAmplitude(amplitude);
+ }
+
+ mVibrator->off();
+});
+
+using VibratorEffectsBench_V1_0 = VibratorEffectsBench<V1_0::IVibrator, V1_0::Effect>;
+
+BENCHMARK_WRAPPER(VibratorEffectsBench_V1_0, perform,
+ { performBench(&state, &V1_0::IVibrator::perform); });
+
+using VibratorEffectsBench_V1_1 =
+ VibratorEffectsBench<V1_1::IVibrator, V1_1::Effect_1_1, V1_0::Effect>;
+
+BENCHMARK_WRAPPER(VibratorEffectsBench_V1_1, perform_1_1,
+ { performBench(&state, &V1_1::IVibrator::perform_1_1); });
+
+using VibratorEffectsBench_V1_2 =
+ VibratorEffectsBench<V1_2::IVibrator, V1_2::Effect, V1_1::Effect_1_1>;
+
+BENCHMARK_WRAPPER(VibratorEffectsBench_V1_2, perform_1_2,
+ { performBench(&state, &V1_2::IVibrator::perform_1_2); });
+
+using VibratorBench_V1_3 = VibratorBench<V1_3::IVibrator>;
+
+BENCHMARK_WRAPPER(VibratorBench_V1_3, supportsExternalControl, {
+ for (auto _ : state) {
+ mVibrator->supportsExternalControl();
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench_V1_3, setExternalControl, {
+ bool enable = true;
+
+ if (!mVibrator->supportsExternalControl()) {
+ return;
+ }
+
+ for (auto _ : state) {
+ state.ResumeTiming();
+ mVibrator->setExternalControl(enable);
+ state.PauseTiming();
+ mVibrator->setExternalControl(false);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench_V1_3, supportsExternalAmplitudeControl, {
+ if (!mVibrator->supportsExternalControl()) {
+ return;
+ }
+
+ mVibrator->setExternalControl(true);
+
+ for (auto _ : state) {
+ mVibrator->supportsAmplitudeControl();
+ }
+
+ mVibrator->setExternalControl(false);
+});
+
+BENCHMARK_WRAPPER(VibratorBench_V1_3, setExternalAmplitude, {
+ uint8_t amplitude = UINT8_MAX;
+
+ if (!mVibrator->supportsExternalControl()) {
+ return;
+ }
+
+ mVibrator->setExternalControl(true);
+
+ if (!mVibrator->supportsAmplitudeControl()) {
+ return;
+ }
+
+ for (auto _ : state) {
+ mVibrator->setAmplitude(amplitude);
+ }
+
+ mVibrator->setExternalControl(false);
+});
+
+using VibratorEffectsBench_V1_3 = VibratorEffectsBench<V1_3::IVibrator, V1_3::Effect, V1_2::Effect>;
+
+BENCHMARK_WRAPPER(VibratorEffectsBench_V1_3, perform_1_3,
+ { performBench(&state, &V1_3::IVibrator::perform_1_3); });
+
+BENCHMARK_MAIN();
diff --git a/vibrator/common/utils.h b/vibrator/common/utils.h
new file mode 100644
index 0000000..4ffd2b0
--- /dev/null
+++ b/vibrator/common/utils.h
@@ -0,0 +1,163 @@
+/*
+ * 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 <fstream>
+#include <map>
+#include <sstream>
+
+#include <android-base/macros.h>
+#include <android-base/properties.h>
+#include <log/log.h>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+namespace utils {
+
+template <typename T>
+class Is_Iterable {
+ private:
+ template <typename U>
+ static std::true_type test(typename U::iterator *u);
+
+ template <typename U>
+ static std::false_type test(U *u);
+
+ public:
+ static const bool value = decltype(test<T>(0))::value;
+};
+
+template <typename T, bool B>
+using Enable_If_Iterable = std::enable_if_t<Is_Iterable<T>::value == B>;
+
+template <typename T, typename U = void>
+using Enable_If_Signed = std::enable_if_t<std::is_signed_v<T>, U>;
+
+template <typename T, typename U = void>
+using Enable_If_Unsigned = std::enable_if_t<std::is_unsigned_v<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 <typename T>
+inline auto toUnderlying(const T value) {
+ return static_cast<std::underlying_type_t<T>>(value);
+}
+
+template <typename T>
+inline Enable_If_Iterable<T, true> unpack(std::istream &stream, T *value) {
+ for (auto &entry : *value) {
+ stream >> entry;
+ }
+}
+
+template <typename T>
+inline Enable_If_Iterable<T, false> unpack(std::istream &stream, T *value) {
+ stream >> *value;
+}
+
+template <>
+inline void unpack<std::string>(std::istream &stream, std::string *value) {
+ *value = std::string(std::istreambuf_iterator(stream), {});
+ stream.setstate(std::istream::eofbit);
+}
+
+template <typename T>
+inline Enable_If_Signed<T, T> getProperty(const std::string &key, const T def) {
+ return ::android::base::GetIntProperty(key, def);
+}
+
+template <typename T>
+inline Enable_If_Unsigned<T, T> getProperty(const std::string &key, const T def) {
+ return ::android::base::GetUintProperty(key, def);
+}
+
+template <>
+inline bool getProperty<bool>(const std::string &key, const bool def) {
+ return ::android::base::GetBoolProperty(key, def);
+}
+
+template <typename T>
+static void openNoCreate(const std::string &file, T *outStream) {
+ auto mode = std::is_base_of_v<std::ostream, T> ? 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 <typename T>
+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<std::string, std::ifstream> 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/cs40l25/Android.bp b/vibrator/cs40l25/Android.bp
new file mode 100644
index 0000000..1beca23
--- /dev/null
+++ b/vibrator/cs40l25/Android.bp
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+ name: "android.hardware.vibrator-defaults.cs40l25",
+ cflags: [
+ "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)",
+ "-DLOG_TAG=\"android.hardware.vibrator-cs40l25\"",
+ ],
+}
+
+cc_defaults {
+ name: "VibratorHalCs40l25BinaryDefaults",
+ defaults: [
+ "PixelVibratorBinaryDefaults",
+ "android.hardware.vibrator-defaults.cs40l25",
+ ],
+}
+
+cc_defaults {
+ name: "VibratorHalCs40l25TestDefaults",
+ defaults: [
+ "PixelVibratorTestDefaults",
+ "android.hardware.vibrator-defaults.cs40l25",
+ ],
+ static_libs: ["android.hardware.vibrator-impl.cs40l25"],
+}
+
+cc_library {
+ name: "android.hardware.vibrator-impl.cs40l25",
+ defaults: ["VibratorHalCs40l25BinaryDefaults"],
+ srcs: ["Vibrator.cpp"],
+ export_include_dirs: ["."],
+ vendor_available: true,
+ visibility: [":__subpackages__"],
+}
+
+cc_binary {
+ name: "android.hardware.vibrator-service.cs40l25",
+ defaults: ["VibratorHalCs40l25BinaryDefaults"],
+ init_rc: ["android.hardware.vibrator-service.cs40l25.rc"],
+ vintf_fragments: ["android.hardware.vibrator-service.cs40l25.xml"],
+ srcs: ["service.cpp"],
+ static_libs: ["android.hardware.vibrator-impl.cs40l25"],
+ proprietary: true,
+}
diff --git a/vibrator/cs40l25/Hardware.h b/vibrator/cs40l25/Hardware.h
new file mode 100644
index 0000000..81936ed
--- /dev/null
+++ b/vibrator/cs40l25/Hardware.h
@@ -0,0 +1,138 @@
+/*
+ * 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 "HardwareBase.h"
+#include "Vibrator.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+class HwApi : public Vibrator::HwApi, private HwApiBase {
+ public:
+ HwApi() {
+ open("device/f0_stored", &mF0);
+ open("device/redc_stored", &mRedc);
+ open("device/q_stored", &mQ);
+ open("activate", &mActivate);
+ open("duration", &mDuration);
+ open("state", &mState);
+ open("device/cp_trigger_duration", &mEffectDuration);
+ open("device/cp_trigger_index", &mEffectIndex);
+ open("device/cp_trigger_queue", &mEffectQueue);
+ open("device/cp_dig_scale", &mEffectScale);
+ open("device/dig_scale", &mGlobalScale);
+ open("device/asp_enable", &mAspEnable);
+ open("device/gpio1_fall_index", &mGpioFallIndex);
+ open("device/gpio1_fall_dig_scale", &mGpioFallScale);
+ open("device/gpio1_rise_index", &mGpioRiseIndex);
+ open("device/gpio1_rise_dig_scale", &mGpioRiseScale);
+ open("device/vibe_state", &mVibeState);
+ open("device/num_waves", &mEffectCount);
+ }
+
+ bool setF0(uint32_t value) override { return set(value, &mF0); }
+ bool setRedc(uint32_t value) override { return set(value, &mRedc); }
+ bool setQ(uint32_t value) override { return set(value, &mQ); }
+ bool setActivate(bool value) override { return set(value, &mActivate); }
+ bool setDuration(uint32_t value) override { return set(value, &mDuration); }
+ bool getEffectCount(uint32_t *value) override { return get(value, &mEffectCount); }
+ bool getEffectDuration(uint32_t *value) override { return get(value, &mEffectDuration); }
+ bool setEffectIndex(uint32_t value) override { return set(value, &mEffectIndex); }
+ bool setEffectQueue(std::string value) override { return set(value, &mEffectQueue); }
+ bool hasEffectScale() override { return has(mEffectScale); }
+ bool setEffectScale(uint32_t value) override { return set(value, &mEffectScale); }
+ bool setGlobalScale(uint32_t value) override { return set(value, &mGlobalScale); }
+ bool setState(bool value) override { return set(value, &mState); }
+ bool hasAspEnable() override { return has(mAspEnable); }
+ bool getAspEnable(bool *value) override { return get(value, &mAspEnable); }
+ bool setAspEnable(bool value) override { return set(value, &mAspEnable); }
+ bool setGpioFallIndex(uint32_t value) override { return set(value, &mGpioFallIndex); }
+ bool setGpioFallScale(uint32_t value) override { return set(value, &mGpioFallScale); }
+ bool setGpioRiseIndex(uint32_t value) override { return set(value, &mGpioRiseIndex); }
+ bool setGpioRiseScale(uint32_t value) override { return set(value, &mGpioRiseScale); }
+ bool pollVibeState(bool value) override { return poll(value, &mVibeState); }
+ void debug(int fd) override { HwApiBase::debug(fd); }
+
+ private:
+ std::ofstream mF0;
+ std::ofstream mRedc;
+ std::ofstream mQ;
+ std::ofstream mActivate;
+ std::ofstream mDuration;
+ std::ifstream mEffectCount;
+ std::ifstream mEffectDuration;
+ std::ofstream mEffectIndex;
+ std::ofstream mEffectQueue;
+ std::ofstream mEffectScale;
+ std::ofstream mGlobalScale;
+ std::ofstream mState;
+ std::fstream mAspEnable;
+ std::ofstream mGpioFallIndex;
+ std::ofstream mGpioFallScale;
+ std::ofstream mGpioRiseIndex;
+ std::ofstream mGpioRiseScale;
+ std::ifstream mVibeState;
+};
+
+class HwCal : public Vibrator::HwCal, private HwCalBase {
+ private:
+ static constexpr char F0_CONFIG[] = "f0_measured";
+ static constexpr char REDC_CONFIG[] = "redc_measured";
+ static constexpr char Q_CONFIG[] = "q_measured";
+ static constexpr char Q_INDEX[] = "q_index";
+ static constexpr char VOLTAGES_CONFIG[] = "v_levels";
+
+ static constexpr uint32_t Q_FLOAT_TO_FIXED = 1 << 16;
+ static constexpr float Q_INDEX_TO_FLOAT = 1.5f;
+ static constexpr uint32_t Q_INDEX_TO_FIXED = Q_INDEX_TO_FLOAT * Q_FLOAT_TO_FIXED;
+ static constexpr uint32_t Q_INDEX_OFFSET = 2.0f * Q_FLOAT_TO_FIXED;
+
+ static constexpr uint32_t Q_DEFAULT = 15.5 * Q_FLOAT_TO_FIXED;
+ static constexpr std::array<uint32_t, 6> V_LEVELS_DEFAULT = {60, 70, 80, 90, 100, 76};
+
+ public:
+ HwCal() {}
+
+ bool getF0(uint32_t *value) override { return getPersist(F0_CONFIG, value); }
+ bool getRedc(uint32_t *value) override { return getPersist(REDC_CONFIG, value); }
+ bool getQ(uint32_t *value) override {
+ if (getPersist(Q_CONFIG, value)) {
+ return true;
+ }
+ if (getPersist(Q_INDEX, value)) {
+ *value = *value * Q_INDEX_TO_FIXED + Q_INDEX_OFFSET;
+ return true;
+ }
+ *value = Q_DEFAULT;
+ return true;
+ }
+ bool getVolLevels(std::array<uint32_t, 6> *value) override {
+ if (getPersist(VOLTAGES_CONFIG, value)) {
+ return true;
+ }
+ *value = V_LEVELS_DEFAULT;
+ return true;
+ }
+ void debug(int fd) override { HwCalBase::debug(fd); }
+};
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l25/TEST_MAPPING b/vibrator/cs40l25/TEST_MAPPING
new file mode 100644
index 0000000..c684d70
--- /dev/null
+++ b/vibrator/cs40l25/TEST_MAPPING
@@ -0,0 +1,20 @@
+{
+ "presubmit": [
+ {
+ "name": "VibratorHalCs40l25TestSuite",
+ "keywords": [
+ "nextgen"
+ ]
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "VibratorHalCs40l25Benchmark"
+ }
+ ],
+ "pts-experimental": [
+ {
+ "name": "VibratorHalCs40l25TestSuite"
+ }
+ ]
+}
diff --git a/vibrator/cs40l25/Vibrator.cpp b/vibrator/cs40l25/Vibrator.cpp
new file mode 100644
index 0000000..df8cbf0
--- /dev/null
+++ b/vibrator/cs40l25/Vibrator.cpp
@@ -0,0 +1,628 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "Vibrator.h"
+
+#include <hardware/hardware.h>
+#include <hardware/vibrator.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <cinttypes>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof((x)) / sizeof((x)[0]))
+#endif
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+static constexpr uint32_t BASE_CONTINUOUS_EFFECT_OFFSET = 32768;
+
+static constexpr uint32_t WAVEFORM_EFFECT_0_20_LEVEL = 0;
+static constexpr uint32_t WAVEFORM_EFFECT_1_00_LEVEL = 4;
+static constexpr uint32_t WAVEFORM_EFFECT_LEVEL_MINIMUM = 4;
+
+static constexpr uint32_t WAVEFORM_DOUBLE_CLICK_SILENCE_MS = 100;
+
+static constexpr uint32_t WAVEFORM_LONG_VIBRATION_EFFECT_INDEX = 0;
+static constexpr uint32_t WAVEFORM_LONG_VIBRATION_THRESHOLD_MS = 50;
+static constexpr uint32_t WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX = 3 + BASE_CONTINUOUS_EFFECT_OFFSET;
+
+static constexpr uint32_t WAVEFORM_CLICK_INDEX = 2;
+static constexpr uint32_t WAVEFORM_QUICK_RISE_INDEX = 6;
+static constexpr uint32_t WAVEFORM_SLOW_RISE_INDEX = 7;
+static constexpr uint32_t WAVEFORM_QUICK_FALL_INDEX = 8;
+static constexpr uint32_t WAVEFORM_LIGHT_TICK_INDEX = 9;
+
+static constexpr uint32_t WAVEFORM_TRIGGER_QUEUE_INDEX = 65534;
+
+static constexpr uint32_t VOLTAGE_GLOBAL_SCALE_LEVEL = 5;
+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 int8_t MAX_PAUSE_TIMING_ERROR_MS = 1; // ALERT Irq Handling
+static constexpr uint32_t MAX_TIME_MS = UINT32_MAX;
+
+static constexpr float AMP_ATTENUATE_STEP_SIZE = 0.125f;
+static constexpr float EFFECT_FREQUENCY_KHZ = 48.0f;
+
+static constexpr auto ASYNC_COMPLETION_TIMEOUT = std::chrono::milliseconds(100);
+
+static constexpr int32_t COMPOSE_DELAY_MAX_MS = 10000;
+static constexpr int32_t COMPOSE_SIZE_MAX = 127;
+
+static uint8_t amplitudeToScale(float amplitude, float maximum) {
+ return std::round((-20 * std::log10(amplitude / static_cast<float>(maximum))) /
+ (AMP_ATTENUATE_STEP_SIZE));
+}
+
+enum class AlwaysOnId : uint32_t {
+ GPIO_RISE,
+ GPIO_FALL,
+};
+
+Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal)
+ : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)), mAsyncHandle(std::async([] {})) {
+ uint32_t caldata;
+ uint32_t effectCount;
+ std::array<uint32_t, 6> volLevels;
+
+ if (!mHwApi->setState(true)) {
+ ALOGE("Failed to set state (%d): %s", errno, strerror(errno));
+ }
+
+ if (mHwCal->getF0(&caldata)) {
+ mHwApi->setF0(caldata);
+ }
+ if (mHwCal->getRedc(&caldata)) {
+ mHwApi->setRedc(caldata);
+ }
+ if (mHwCal->getQ(&caldata)) {
+ mHwApi->setQ(caldata);
+ }
+
+ mHwCal->getVolLevels(&volLevels);
+ /*
+ * Given voltage levels for two intensities, assuming a linear function,
+ * solve for 'f(0)' in 'v = f(i) = a + b * i' (i.e 'v0 - (v1 - v0) / ((i1 - i0) / i0)').
+ */
+ mEffectVolMin = std::max(std::lround(volLevels[WAVEFORM_EFFECT_0_20_LEVEL] -
+ (volLevels[WAVEFORM_EFFECT_1_00_LEVEL] -
+ volLevels[WAVEFORM_EFFECT_0_20_LEVEL]) /
+ 4.0f),
+ static_cast<long>(WAVEFORM_EFFECT_LEVEL_MINIMUM));
+ mEffectVolMax = volLevels[WAVEFORM_EFFECT_1_00_LEVEL];
+ mGlobalVolMax = volLevels[VOLTAGE_GLOBAL_SCALE_LEVEL];
+
+ mHwApi->getEffectCount(&effectCount);
+ mEffectDurations.resize(effectCount);
+ for (size_t effectIndex = 0; effectIndex < effectCount; effectIndex++) {
+ mHwApi->setEffectIndex(effectIndex);
+ uint32_t effectDuration;
+ if (mHwApi->getEffectDuration(&effectDuration)) {
+ mEffectDurations[effectIndex] = std::ceil(effectDuration / EFFECT_FREQUENCY_KHZ);
+ }
+ }
+}
+
+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_COMPOSE_EFFECTS | IVibrator::CAP_ALWAYS_ON_CONTROL;
+ if (mHwApi->hasEffectScale()) {
+ ret |= IVibrator::CAP_AMPLITUDE_CONTROL;
+ }
+ if (mHwApi->hasAspEnable()) {
+ ret |= IVibrator::CAP_EXTERNAL_CONTROL;
+ }
+ *_aidl_return = ret;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::off() {
+ ATRACE_NAME("Vibrator::off");
+ setGlobalAmplitude(false);
+ if (!mHwApi->setActivate(0)) {
+ ALOGE("Failed to turn vibrator off (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
+ const std::shared_ptr<IVibratorCallback> &callback) {
+ ATRACE_NAME("Vibrator::on");
+ const uint32_t index = timeoutMs < WAVEFORM_LONG_VIBRATION_THRESHOLD_MS
+ ? WAVEFORM_SHORT_VIBRATION_EFFECT_INDEX
+ : WAVEFORM_LONG_VIBRATION_EFFECT_INDEX;
+ if (MAX_COLD_START_LATENCY_MS <= UINT32_MAX - timeoutMs) {
+ timeoutMs += MAX_COLD_START_LATENCY_MS;
+ }
+ setGlobalAmplitude(true);
+ return on(timeoutMs, index, callback);
+}
+
+ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength,
+ const std::shared_ptr<IVibratorCallback> &callback,
+ int32_t *_aidl_return) {
+ ATRACE_NAME("Vibrator::perform");
+ return performEffect(effect, strength, callback, _aidl_return);
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect> *_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);
+ }
+
+ if (!isUnderExternalControl()) {
+ return setEffectAmplitude(amplitude, 1.0);
+ } else {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+}
+
+ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) {
+ ATRACE_NAME("Vibrator::setExternalControl");
+ setGlobalAmplitude(enabled);
+
+ if (!mHwApi->setAspEnable(enabled)) {
+ ALOGE("Failed to set external control (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ 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<CompositePrimitive> *supported) {
+ *supported = {
+ CompositePrimitive::NOOP, CompositePrimitive::CLICK,
+ CompositePrimitive::QUICK_RISE, CompositePrimitive::SLOW_RISE,
+ CompositePrimitive::QUICK_FALL, CompositePrimitive::LIGHT_TICK,
+ };
+ 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<CompositeEffect> &composite,
+ const std::shared_ptr<IVibratorCallback> &callback) {
+ ATRACE_NAME("Vibrator::compose");
+ std::ostringstream effectBuilder;
+ std::string effectQueue;
+
+ if (composite.size() > COMPOSE_SIZE_MAX) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ for (auto &e : composite) {
+ if (e.scale < 0.0f || e.scale > 1.0f) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ if (e.delayMs) {
+ if (e.delayMs > COMPOSE_DELAY_MAX_MS) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+ effectBuilder << e.delayMs << ",";
+ }
+ if (e.primitive != CompositePrimitive::NOOP) {
+ ndk::ScopedAStatus status;
+ uint32_t effectIndex;
+
+ status = getPrimitiveDetails(e.primitive, &effectIndex);
+ if (!status.isOk()) {
+ return status;
+ }
+
+ effectBuilder << effectIndex << "." << intensityToVolLevel(e.scale) << ",";
+ }
+ }
+
+ if (effectBuilder.tellp() == 0) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
+
+ effectBuilder << 0;
+
+ effectQueue = effectBuilder.str();
+
+ return performEffect(0 /*ignored*/, 0 /*ignored*/, &effectQueue, callback);
+}
+
+ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, uint32_t effectIndex,
+ const std::shared_ptr<IVibratorCallback> &callback) {
+ if (mAsyncHandle.wait_for(ASYNC_COMPLETION_TIMEOUT) != std::future_status::ready) {
+ ALOGE("Previous vibration pending.");
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ mHwApi->setEffectIndex(effectIndex);
+ mHwApi->setDuration(timeoutMs);
+ mHwApi->setActivate(1);
+
+ mAsyncHandle = std::async(&Vibrator::waitForComplete, this, callback);
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setEffectAmplitude(float amplitude, float maximum) {
+ int32_t scale = amplitudeToScale(amplitude, maximum);
+
+ if (!mHwApi->setEffectScale(scale)) {
+ ALOGE("Failed to set effect amplitude (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setGlobalAmplitude(bool set) {
+ uint8_t amplitude = set ? mGlobalVolMax : VOLTAGE_SCALE_MAX;
+ int32_t scale = amplitudeToScale(amplitude, VOLTAGE_SCALE_MAX);
+
+ if (!mHwApi->setGlobalScale(scale)) {
+ ALOGE("Failed to set global amplitude (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) {
+ *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK};
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) {
+ ndk::ScopedAStatus status;
+ uint32_t effectIndex;
+ uint32_t timeMs;
+ uint32_t volLevel;
+ uint32_t scale;
+
+ status = getSimpleDetails(effect, strength, &effectIndex, &timeMs, &volLevel);
+ if (!status.isOk()) {
+ return status;
+ }
+
+ scale = amplitudeToScale(volLevel, VOLTAGE_SCALE_MAX);
+
+ switch (static_cast<AlwaysOnId>(id)) {
+ case AlwaysOnId::GPIO_RISE:
+ mHwApi->setGpioRiseIndex(effectIndex);
+ mHwApi->setGpioRiseScale(scale);
+ return ndk::ScopedAStatus::ok();
+ case AlwaysOnId::GPIO_FALL:
+ mHwApi->setGpioFallIndex(effectIndex);
+ mHwApi->setGpioFallScale(scale);
+ return ndk::ScopedAStatus::ok();
+ }
+
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+ndk::ScopedAStatus Vibrator::alwaysOnDisable(int32_t id) {
+ switch (static_cast<AlwaysOnId>(id)) {
+ case AlwaysOnId::GPIO_RISE:
+ mHwApi->setGpioRiseIndex(0);
+ return ndk::ScopedAStatus::ok();
+ case AlwaysOnId::GPIO_FALL:
+ mHwApi->setGpioFallIndex(0);
+ return ndk::ScopedAStatus::ok();
+ }
+
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+bool Vibrator::isUnderExternalControl() {
+ bool isAspEnabled;
+ mHwApi->getAspEnable(&isAspEnabled);
+ return isAspEnabled;
+}
+
+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, " Voltage Levels:\n");
+ dprintf(fd, " Effect Min: %" PRIu32 "\n", mEffectVolMin);
+ dprintf(fd, " Effect Max: %" PRIu32 "\n", mEffectVolMax);
+ dprintf(fd, " Global Max: %" PRIu32 "\n", mGlobalVolMax);
+
+ dprintf(fd, " Effect Durations:");
+ for (auto d : mEffectDurations) {
+ dprintf(fd, " %" PRIu32, d);
+ }
+ dprintf(fd, "\n");
+
+ dprintf(fd, "\n");
+
+ mHwApi->debug(fd);
+
+ dprintf(fd, "\n");
+
+ mHwCal->debug(fd);
+
+ fsync(fd);
+ return STATUS_OK;
+}
+
+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);
+ 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, uint32_t * /*outVolLevel*/,
+ std::string *outEffectQueue) {
+ ndk::ScopedAStatus status;
+ uint32_t timeMs;
+ std::ostringstream effectBuilder;
+ uint32_t thisEffectIndex;
+ uint32_t thisTimeMs;
+ uint32_t thisVolLevel;
+
+ switch (effect) {
+ case Effect::DOUBLE_CLICK:
+ timeMs = 0;
+
+ status = getSimpleDetails(Effect::CLICK, strength, &thisEffectIndex, &thisTimeMs,
+ &thisVolLevel);
+ if (!status.isOk()) {
+ return status;
+ }
+ effectBuilder << thisEffectIndex << "." << thisVolLevel;
+ timeMs += thisTimeMs;
+
+ effectBuilder << ",";
+
+ effectBuilder << WAVEFORM_DOUBLE_CLICK_SILENCE_MS;
+ timeMs += WAVEFORM_DOUBLE_CLICK_SILENCE_MS + MAX_PAUSE_TIMING_ERROR_MS;
+
+ effectBuilder << ",";
+
+ status = getSimpleDetails(Effect::HEAVY_CLICK, strength, &thisEffectIndex, &thisTimeMs,
+ &thisVolLevel);
+ if (!status.isOk()) {
+ return status;
+ }
+ effectBuilder << thisEffectIndex << "." << thisVolLevel;
+ timeMs += thisTimeMs;
+
+ break;
+ default:
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+
+ *outTimeMs = timeMs;
+ *outEffectQueue = effectBuilder.str();
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getPrimitiveDetails(CompositePrimitive primitive,
+ uint32_t *outEffectIndex) {
+ uint32_t effectIndex;
+
+ switch (primitive) {
+ case CompositePrimitive::NOOP:
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ case CompositePrimitive::CLICK:
+ effectIndex = WAVEFORM_CLICK_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;
+ default:
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+
+ *outEffectIndex = effectIndex;
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setEffectQueue(const std::string &effectQueue) {
+ if (!mHwApi->setEffectQueue(effectQueue)) {
+ ALOGE("Failed to write \"%s\" to effect queue (%d): %s", effectQueue.c_str(), errno,
+ strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength,
+ const std::shared_ptr<IVibratorCallback> &callback,
+ int32_t *outTimeMs) {
+ ndk::ScopedAStatus status;
+ uint32_t effectIndex;
+ uint32_t timeMs = 0;
+ uint32_t volLevel;
+ std::string effectQueue;
+
+ 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:
+ status = getCompoundDetails(effect, strength, &timeMs, &volLevel, &effectQueue);
+ break;
+ default:
+ status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ break;
+ }
+ if (!status.isOk()) {
+ goto exit;
+ }
+
+ status = performEffect(effectIndex, volLevel, &effectQueue, callback);
+
+exit:
+
+ *outTimeMs = timeMs;
+ return status;
+}
+
+ndk::ScopedAStatus Vibrator::performEffect(uint32_t effectIndex, uint32_t volLevel,
+ const std::string *effectQueue,
+ const std::shared_ptr<IVibratorCallback> &callback) {
+ if (effectQueue && !effectQueue->empty()) {
+ ndk::ScopedAStatus status = setEffectQueue(*effectQueue);
+ if (!status.isOk()) {
+ return status;
+ }
+ setEffectAmplitude(VOLTAGE_SCALE_MAX, VOLTAGE_SCALE_MAX);
+ effectIndex = WAVEFORM_TRIGGER_QUEUE_INDEX;
+ } else {
+ setEffectAmplitude(volLevel, VOLTAGE_SCALE_MAX);
+ }
+
+ return on(MAX_TIME_MS, effectIndex, callback);
+}
+
+void Vibrator::waitForComplete(std::shared_ptr<IVibratorCallback> &&callback) {
+ mHwApi->pollVibeState(false);
+ mHwApi->setActivate(false);
+
+ if (callback) {
+ auto ret = callback->onComplete();
+ if (!ret.isOk()) {
+ ALOGE("Failed completion callback: %d", ret.getExceptionCode());
+ }
+ }
+}
+
+uint32_t Vibrator::intensityToVolLevel(float intensity) {
+ return std::lround(intensity * (mEffectVolMax - mEffectVolMin)) + mEffectVolMin;
+}
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l25/Vibrator.h b/vibrator/cs40l25/Vibrator.h
new file mode 100644
index 0000000..3d15406
--- /dev/null
+++ b/vibrator/cs40l25/Vibrator.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <aidl/android/hardware/vibrator/BnVibrator.h>
+
+#include <array>
+#include <fstream>
+#include <future>
+
+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(uint32_t value) = 0;
+ // Stores the LRA series resistance to be used for click
+ // compensation.
+ virtual bool setRedc(uint32_t value) = 0;
+ // Stores the LRA Q factor to be used for Q-dependent waveform
+ // selection.
+ virtual bool setQ(uint32_t value) = 0;
+ // Activates/deactivates the vibrator for durations specified by
+ // setDuration().
+ virtual bool setActivate(bool value) = 0;
+ // Specifies the vibration duration in milliseconds.
+ virtual bool setDuration(uint32_t value) = 0;
+ // Reports the number of effect waveforms loaded in firmware.
+ virtual bool getEffectCount(uint32_t *value) = 0;
+ // Reports the duration of the waveform selected by
+ // setEffectIndex(), measured in 48-kHz periods.
+ virtual bool getEffectDuration(uint32_t *value) = 0;
+ // Selects the waveform associated with vibration calls from
+ // the Android vibrator HAL.
+ virtual bool setEffectIndex(uint32_t value) = 0;
+ // Specifies an array of waveforms, delays, and repetition markers to
+ // generate complex waveforms.
+ virtual bool setEffectQueue(std::string value) = 0;
+ // Reports whether setEffectScale() is supported.
+ virtual bool hasEffectScale() = 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 setEffectScale(uint32_t value) = 0;
+ // Indicates the number of 0.125-dB steps of attenuation to apply to
+ // any output waveform (additive to all other set*Scale()
+ // controls).
+ virtual bool setGlobalScale(uint32_t value) = 0;
+ // Specifies the active state of the vibrator
+ // (true = enabled, false = disabled).
+ virtual bool setState(bool value) = 0;
+ // Reports whether getAspEnable()/setAspEnable() is supported.
+ virtual bool hasAspEnable() = 0;
+ // Enables/disables ASP playback.
+ virtual bool getAspEnable(bool *value) = 0;
+ // Reports enabled/disabled state of ASP playback.
+ virtual bool setAspEnable(bool value) = 0;
+ // Selects the waveform associated with a GPIO1 falling edge.
+ virtual bool setGpioFallIndex(uint32_t value) = 0;
+ // Indicates the number of 0.125-dB steps of attenuation to apply to
+ // waveforms triggered in response to a GPIO1 falling edge.
+ virtual bool setGpioFallScale(uint32_t value) = 0;
+ // Selects the waveform associated with a GPIO1 rising edge.
+ virtual bool setGpioRiseIndex(uint32_t value) = 0;
+ // Indicates the number of 0.125-dB steps of attenuation to apply to
+ // waveforms triggered in response to a GPIO1 rising edge.
+ virtual bool setGpioRiseScale(uint32_t value) = 0;
+ // Blocks until vibrator reaches desired state
+ // (true = enabled, false = disabled).
+ virtual bool pollVibeState(bool value) = 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;
+ // Obtains the LRA resonant frequency to be used for PWLE playback
+ // and click compensation.
+ virtual bool getF0(uint32_t *value) = 0;
+ // Obtains the LRA series resistance to be used for click
+ // compensation.
+ virtual bool getRedc(uint32_t *value) = 0;
+ // Obtains the LRA Q factor to be used for Q-dependent waveform
+ // selection.
+ virtual bool getQ(uint32_t *value) = 0;
+ // Obtains the discreet voltage levels to be applied for the various
+ // waveforms, in units of 1%.
+ virtual bool getVolLevels(std::array<uint32_t, 6> *value) = 0;
+ // Emit diagnostic information to the given file.
+ virtual void debug(int fd) = 0;
+ };
+
+ public:
+ Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal);
+
+ ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override;
+ ndk::ScopedAStatus off() override;
+ ndk::ScopedAStatus on(int32_t timeoutMs,
+ const std::shared_ptr<IVibratorCallback> &callback) override;
+ ndk::ScopedAStatus perform(Effect effect, EffectStrength strength,
+ const std::shared_ptr<IVibratorCallback> &callback,
+ int32_t *_aidl_return) override;
+ ndk::ScopedAStatus getSupportedEffects(std::vector<Effect> *_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<CompositePrimitive> *supported) override;
+ ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive,
+ int32_t *durationMs) override;
+ ndk::ScopedAStatus compose(const std::vector<CompositeEffect> &composite,
+ const std::shared_ptr<IVibratorCallback> &callback) override;
+ ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) override;
+ ndk::ScopedAStatus alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) override;
+ ndk::ScopedAStatus alwaysOnDisable(int32_t id) override;
+
+ binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
+
+ private:
+ ndk::ScopedAStatus on(uint32_t timeoutMs, uint32_t effectIndex,
+ const std::shared_ptr<IVibratorCallback> &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, uint32_t *outVolLevel,
+ std::string *outEffectQueue);
+ ndk::ScopedAStatus getPrimitiveDetails(CompositePrimitive primitive, uint32_t *outEffectIndex);
+ ndk::ScopedAStatus setEffectQueue(const std::string &effectQueue);
+ ndk::ScopedAStatus performEffect(Effect effect, EffectStrength strength,
+ const std::shared_ptr<IVibratorCallback> &callback,
+ int32_t *outTimeMs);
+ ndk::ScopedAStatus performEffect(uint32_t effectIndex, uint32_t volLevel,
+ const std::string *effectQueue,
+ const std::shared_ptr<IVibratorCallback> &callback);
+ bool isUnderExternalControl();
+ void waitForComplete(std::shared_ptr<IVibratorCallback> &&callback);
+ uint32_t intensityToVolLevel(float intensity);
+
+ std::unique_ptr<HwApi> mHwApi;
+ std::unique_ptr<HwCal> mHwCal;
+ uint32_t mEffectVolMin;
+ uint32_t mEffectVolMax;
+ uint32_t mGlobalVolMax;
+ std::vector<uint32_t> mEffectDurations;
+ std::future<void> mAsyncHandle;
+};
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc
new file mode 100644
index 0000000..5508313
--- /dev/null
+++ b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.rc
@@ -0,0 +1,57 @@
+on early-boot
+
+ mkdir /mnt/vendor/persist/haptics 0770 system system
+ chmod 770 /mnt/vendor/persist/haptics
+ chmod 440 /mnt/vendor/persist/haptics/cs40l25a.cal
+ chown system system /mnt/vendor/persist/haptics
+ chown system system /mnt/vendor/persist/haptics/cs40l25a.cal
+
+ chown system system /sys/class/leds/vibrator/device/asp_enable
+ chown system system /sys/class/leds/vibrator/device/comp_enable
+ chown system system /sys/class/leds/vibrator/device/cp_dig_scale
+ chown system system /sys/class/leds/vibrator/device/cp_trigger_duration
+ chown system system /sys/class/leds/vibrator/device/cp_trigger_index
+ chown system system /sys/class/leds/vibrator/device/cp_trigger_q_sub
+ chown system system /sys/class/leds/vibrator/device/cp_trigger_queue
+ chown system system /sys/class/leds/vibrator/device/dig_scale
+ chown system system /sys/class/leds/vibrator/device/exc_enable
+ chown system system /sys/class/leds/vibrator/device/f0_stored
+ chown system system /sys/class/leds/vibrator/device/fw_rev
+ chown system system /sys/class/leds/vibrator/device/gpio1_fall_dig_scale
+ chown system system /sys/class/leds/vibrator/device/gpio1_fall_index
+ chown system system /sys/class/leds/vibrator/device/gpio1_rise_dig_scale
+ chown system system /sys/class/leds/vibrator/device/gpio1_rise_index
+ chown system system /sys/class/leds/vibrator/device/heartbeat
+ chown system system /sys/class/leds/vibrator/device/hw_reset
+ chown system system /sys/class/leds/vibrator/device/num_waves
+ chown system system /sys/class/leds/vibrator/device/q_stored
+ chown system system /sys/class/leds/vibrator/device/redc_comp_enable
+ chown system system /sys/class/leds/vibrator/device/redc_stored
+ chown system system /sys/class/leds/vibrator/device/standby_timeout
+ chown system system /sys/class/leds/vibrator/device/vbatt_max
+ chown system system /sys/class/leds/vibrator/device/vbatt_min
+ chown system system /sys/class/leds/vibrator/device/vibe_state
+
+service vendor.vibrator.cs40l25 /vendor/bin/hw/android.hardware.vibrator-service.cs40l25
+ class hal
+ user system
+ group system
+
+ setenv PROPERTY_PREFIX ro.vibrator.hal.
+ setenv CALIBRATION_FILEPATH /mnt/vendor/persist/haptics/cs40l25a.cal
+
+ setenv HWAPI_PATH_PREFIX /sys/class/leds/vibrator/
+ setenv HWAPI_DEBUG_PATHS "
+ device/asp_enable
+ device/f0_stored
+ device/fw_rev
+ device/gpio1_fall_dig_scale
+ device/gpio1_fall_index
+ device/gpio1_rise_dig_scale
+ device/gpio1_rise_index
+ device/heartbeat
+ device/num_waves
+ device/q_stored
+ device/redc_stored
+ state
+ "
diff --git a/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.xml b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.xml
new file mode 100644
index 0000000..49b11ec
--- /dev/null
+++ b/vibrator/cs40l25/android.hardware.vibrator-service.cs40l25.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.vibrator</name>
+ <fqname>IVibrator/default</fqname>
+ </hal>
+</manifest>
diff --git a/vibrator/cs40l25/bench/Android.bp b/vibrator/cs40l25/bench/Android.bp
new file mode 100644
index 0000000..87a9dc0
--- /dev/null
+++ b/vibrator/cs40l25/bench/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+
+cc_benchmark {
+ name: "VibratorHalCs40l25Benchmark",
+ defaults: ["VibratorHalCs40l25TestDefaults"],
+ srcs: [
+ "benchmark.cpp",
+ ],
+ static_libs: [
+ "libc++fs",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ // TODO(b/135767253): Remove when fixed.
+ test_suites: ["device-tests"],
+ // TODO(b/142024316): Remove when fixed.
+ require_root: true,
+}
diff --git a/vibrator/cs40l25/bench/benchmark.cpp b/vibrator/cs40l25/bench/benchmark.cpp
new file mode 100644
index 0000000..c43a2a2
--- /dev/null
+++ b/vibrator/cs40l25/bench/benchmark.cpp
@@ -0,0 +1,166 @@
+/* * 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 "benchmark/benchmark.h"
+
+#include <android-base/file.h>
+#include <cutils/fs.h>
+
+#include "Hardware.h"
+#include "Vibrator.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+class VibratorBench : public benchmark::Fixture {
+ private:
+ static constexpr const char *FILE_NAMES[]{
+ "device/f0_stored",
+ "device/redc_stored",
+ "device/q_stored",
+ "activate",
+ "duration",
+ "state",
+ "device/cp_trigger_duration",
+ "device/cp_trigger_index",
+ "device/cp_trigger_queue",
+ "device/cp_dig_scale",
+ "device/dig_scale",
+ "device/asp_enable",
+ "device/gpio1_fall_index",
+ "device/gpio1_fall_dig_scale",
+ "device/gpio1_rise_index",
+ "device/gpio1_rise_dig_scale",
+ "device/vibe_state",
+ "device/num_waves",
+ };
+
+ public:
+ void SetUp(::benchmark::State & /*state*/) override {
+ auto prefix = std::filesystem::path(mFilesDir.path) / "";
+ const std::map<const std::string, const std::string> content{
+ {"duration", std::to_string((uint32_t)std::rand() ?: 1)},
+ {"device/asp_enable", std::to_string(0)},
+ {"device/cp_trigger_duration", std::to_string(0)},
+ {"device/num_waves", std::to_string(10)},
+ {"device/vibe_state", std::to_string(0)},
+ };
+
+ setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+
+ for (auto n : FILE_NAMES) {
+ const auto it = content.find(n);
+ const auto name = std::filesystem::path(n);
+ const auto path = std::filesystem::path(mFilesDir.path) / name;
+
+ fs_mkdirs(path.c_str(), S_IRWXU);
+
+ if (it != content.end()) {
+ std::ofstream{path} << it->second << std::endl;
+ } else {
+ symlink("/dev/null", path.c_str());
+ }
+ }
+
+ mVibrator = ndk::SharedRefBase::make<Vibrator>(std::make_unique<HwApi>(),
+ std::make_unique<HwCal>());
+ }
+
+ static void DefaultArgs(benchmark::internal::Benchmark *b) { b->Unit(benchmark::kMicrosecond); }
+
+ static void SupportedEffectArgs(benchmark::internal::Benchmark *b) {
+ b->ArgNames({"Effect", "Strength"});
+ for (Effect effect : ndk::enum_range<Effect>()) {
+ for (EffectStrength strength : ndk::enum_range<EffectStrength>()) {
+ b->Args({static_cast<long>(effect), static_cast<long>(strength)});
+ }
+ }
+ }
+
+ protected:
+ TemporaryDir mFilesDir;
+ std::shared_ptr<IVibrator> mVibrator;
+};
+
+#define BENCHMARK_WRAPPER(fixt, test, code) \
+ BENCHMARK_DEFINE_F(fixt, test) \
+ /* NOLINTNEXTLINE */ \
+ (benchmark::State & state){code} BENCHMARK_REGISTER_F(fixt, test)->Apply(fixt::DefaultArgs)
+
+BENCHMARK_WRAPPER(VibratorBench, on, {
+ uint32_t duration = std::rand() ?: 1;
+
+ for (auto _ : state) {
+ mVibrator->on(duration, nullptr);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, off, {
+ for (auto _ : state) {
+ mVibrator->off();
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
+ uint8_t amplitude = std::rand() ?: 1;
+
+ for (auto _ : state) {
+ mVibrator->setAmplitude(amplitude);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, setExternalControl_enable, {
+ for (auto _ : state) {
+ mVibrator->setExternalControl(true);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, setExternalControl_disable, {
+ for (auto _ : state) {
+ mVibrator->setExternalControl(false);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, getCapabilities, {
+ int32_t capabilities;
+
+ for (auto _ : state) {
+ mVibrator->getCapabilities(&capabilities);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, perform, {
+ Effect effect = Effect(state.range(0));
+ EffectStrength strength = EffectStrength(state.range(1));
+ int32_t lengthMs;
+
+ ndk::ScopedAStatus status = mVibrator->perform(effect, strength, nullptr, &lengthMs);
+
+ if (!status.isOk()) {
+ return;
+ }
+
+ for (auto _ : state) {
+ mVibrator->perform(effect, strength, nullptr, &lengthMs);
+ }
+})->Apply(VibratorBench::SupportedEffectArgs);
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
+
+BENCHMARK_MAIN();
diff --git a/vibrator/cs40l25/device.mk b/vibrator/cs40l25/device.mk
new file mode 100644
index 0000000..8dbf019
--- /dev/null
+++ b/vibrator/cs40l25/device.mk
@@ -0,0 +1,9 @@
+PRODUCT_PACKAGES += \
+ android.hardware.vibrator-service.cs40l25 \
+
+PRODUCT_PACKAGES_DEBUG += \
+ diag-vibrator \
+
+BOARD_SEPOLICY_DIRS += \
+ hardware/google/pixel-sepolicy/vibrator/common \
+ hardware/google/pixel-sepolicy/vibrator/cs40l25 \
diff --git a/vibrator/cs40l25/diag/Android.bp b/vibrator/cs40l25/diag/Android.bp
new file mode 100644
index 0000000..acd8632
--- /dev/null
+++ b/vibrator/cs40l25/diag/Android.bp
@@ -0,0 +1,20 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+sh_binary {
+ name: "diag-vibrator",
+ src: "diag-vibrator.sh",
+ vendor: true,
+}
diff --git a/vibrator/cs40l25/diag/diag-vibrator.sh b/vibrator/cs40l25/diag/diag-vibrator.sh
new file mode 100644
index 0000000..30dca9d
--- /dev/null
+++ b/vibrator/cs40l25/diag/diag-vibrator.sh
@@ -0,0 +1,64 @@
+#!/vendor/bin/sh
+
+set -e
+
+do_id()
+{
+ cat /sys/class/leds/vibrator/device/modalias | sed 's/^i2c://g'
+}
+
+do_ping()
+{
+ test "$(do_id)" == "cs40l25a"
+}
+
+do_enable()
+{
+ /system/bin/idlcli vibrator on "${@}"
+}
+
+do_disable()
+{
+ /system/bin/idlcli vibrator off
+}
+
+do_state()
+{
+ local state="$(cat /sys/class/leds/vibrator/device/vibe_state)"
+ if [[ "${state}" == "0" ]]
+ then
+ echo "stopped"
+ else
+ echo "running"
+ fi
+}
+
+do_dump()
+{
+ local loc="$(basename /sys/class/leds/vibrator/device/driver/*-0043)"
+ cat /sys/kernel/debug/regmap/${loc}/registers
+}
+
+do_help()
+{
+ local name="$(basename "${0}")"
+ echo "Usage:"
+ echo " ${name} id - Prints controller ID"
+ echo " ${name} ping - Verifies probe succedded"
+ echo " ${name} enable <ms> - Enables vibrator for <ms> milliseconds"
+ echo " ${name} disable - Disables vibrator."
+ echo " ${name} state - Returns 'stopped' or 'running' state."
+ echo " ${name} dump - Dumps memory mapped registers."
+}
+
+if [[ "${#}" -gt "0" ]]
+then
+ cmd="do_${1}" && shift
+fi
+
+if ! typeset -f "${cmd}" >/dev/null
+then
+ cmd="do_help"
+fi
+
+exec "${cmd}" "${@}"
diff --git a/vibrator/cs40l25/service.cpp b/vibrator/cs40l25/service.cpp
new file mode 100644
index 0000000..01a99c9
--- /dev/null
+++ b/vibrator/cs40l25/service.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Hardware.h"
+#include "Vibrator.h"
+
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <log/log.h>
+
+using aidl::android::hardware::vibrator::HwApi;
+using aidl::android::hardware::vibrator::HwCal;
+using aidl::android::hardware::vibrator::Vibrator;
+
+int main() {
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<Vibrator> vib = ndk::SharedRefBase::make<Vibrator>(std::make_unique<HwApi>(),
+ std::make_unique<HwCal>());
+
+ const std::string instance = std::string() + Vibrator::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(vib->asBinder().get(), instance.c_str());
+ LOG_ALWAYS_FATAL_IF(status != STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/vibrator/cs40l25/tests/Android.bp b/vibrator/cs40l25/tests/Android.bp
new file mode 100644
index 0000000..7acdece
--- /dev/null
+++ b/vibrator/cs40l25/tests/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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.
+
+cc_test {
+ name: "VibratorHalCs40l25TestSuite",
+ defaults: ["VibratorHalCs40l25TestDefaults"],
+ srcs: [
+ "test-hwapi.cpp",
+ "test-hwcal.cpp",
+ "test-vibrator.cpp",
+ ],
+ static_libs: [
+ "libc++fs",
+ "libgmock",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/vibrator/cs40l25/tests/mocks.h b/vibrator/cs40l25/tests/mocks.h
new file mode 100644
index 0000000..f0fd27a
--- /dev/null
+++ b/vibrator/cs40l25/tests/mocks.h
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+
+#include "Vibrator.h"
+
+class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi {
+ public:
+ MOCK_METHOD0(destructor, void());
+ MOCK_METHOD1(setF0, bool(uint32_t value));
+ MOCK_METHOD1(setRedc, bool(uint32_t value));
+ MOCK_METHOD1(setQ, bool(uint32_t value));
+ MOCK_METHOD1(setActivate, bool(bool value));
+ MOCK_METHOD1(setDuration, bool(uint32_t value));
+ MOCK_METHOD1(getEffectCount, bool(uint32_t *value));
+ MOCK_METHOD1(getEffectDuration, bool(uint32_t *value));
+ MOCK_METHOD1(setEffectIndex, bool(uint32_t value));
+ MOCK_METHOD1(setEffectQueue, bool(std::string value));
+ MOCK_METHOD0(hasEffectScale, bool());
+ MOCK_METHOD1(setEffectScale, bool(uint32_t value));
+ MOCK_METHOD1(setGlobalScale, bool(uint32_t value));
+ MOCK_METHOD1(setState, bool(bool value));
+ MOCK_METHOD0(hasAspEnable, bool());
+ MOCK_METHOD1(getAspEnable, bool(bool *value));
+ MOCK_METHOD1(setAspEnable, bool(bool value));
+ MOCK_METHOD1(setGpioFallIndex, bool(uint32_t value));
+ MOCK_METHOD1(setGpioFallScale, bool(uint32_t value));
+ MOCK_METHOD1(setGpioRiseIndex, bool(uint32_t value));
+ MOCK_METHOD1(setGpioRiseScale, bool(uint32_t value));
+ MOCK_METHOD1(pollVibeState, bool(bool value));
+ 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(getF0, bool(uint32_t *value));
+ MOCK_METHOD1(getRedc, bool(uint32_t *value));
+ MOCK_METHOD1(getQ, bool(uint32_t *value));
+ MOCK_METHOD1(getVolLevels, bool(std::array<uint32_t, 6> *value));
+ MOCK_METHOD1(debug, void(int fd));
+
+ ~MockCal() override { destructor(); };
+};
+
+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/cs40l25/tests/test-hwapi.cpp b/vibrator/cs40l25/tests/test-hwapi.cpp
new file mode 100644
index 0000000..597bf75
--- /dev/null
+++ b/vibrator/cs40l25/tests/test-hwapi.cpp
@@ -0,0 +1,351 @@
+/*
+ * 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 <android-base/file.h>
+#include <cutils/fs.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <fstream>
+
+#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[]{
+ "device/f0_stored",
+ "device/redc_stored",
+ "device/q_stored",
+ "activate",
+ "duration",
+ "state",
+ "device/cp_trigger_duration",
+ "device/cp_trigger_index",
+ "device/cp_trigger_queue",
+ "device/cp_dig_scale",
+ "device/dig_scale",
+ "device/asp_enable",
+ "device/gpio1_fall_index",
+ "device/gpio1_fall_dig_scale",
+ "device/gpio1_rise_index",
+ "device/gpio1_rise_dig_scale",
+ "device/num_waves",
+ };
+
+ 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<HwApi>();
+
+ 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<HwApi>();
+ }
+
+ 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 <typename T>
+ void expectContent(const std::string &name, const T &value) {
+ mExpectedContent[name] << value << std::endl;
+ }
+
+ // Set actual file content for an input test.
+ template <typename T>
+ void updateContent(const std::string &name, const T &value) {
+ std::ofstream(mFileMap[name]) << value << std::endl;
+ }
+
+ template <typename T>
+ 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<char>(file),
+ std::istreambuf_iterator<char>());
+ EXPECT_EQ(expect, actual) << a.first;
+ }
+ }
+
+ protected:
+ std::unique_ptr<Vibrator::HwApi> mHwApi;
+ std::unique_ptr<Vibrator::HwApi> mNoApi;
+ std::map<std::string, std::string> mFileMap;
+ TemporaryDir mFilesDir;
+ TemporaryDir mEmptyDir;
+ std::map<std::string, std::stringstream> mExpectedContent;
+};
+
+template <typename T>
+class HwApiTypedTest : public HwApiTest,
+ public WithParamInterface<std::tuple<std::string, std::function<T>>> {
+ public:
+ static auto PrintParam(const TestParamInfo<typename HwApiTypedTest::ParamType> &info) {
+ return ParamNameFixup(std::get<0>(info.param));
+ }
+ static auto MakeParam(std::string name, std::function<T> func) {
+ return std::make_tuple(name, func);
+ }
+};
+
+using HasTest = HwApiTypedTest<bool(Vibrator::HwApi &)>;
+
+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("device/cp_dig_scale", &Vibrator::HwApi::hasEffectScale),
+ HasTest::MakeParam("device/asp_enable", &Vibrator::HwApi::hasAspEnable),
+ }),
+ HasTest::PrintParam);
+
+using GetBoolTest = HwApiTypedTest<bool(Vibrator::HwApi &, bool *)>;
+
+TEST_P(GetBoolTest, success_returnsTrue) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ bool expect = true;
+ bool actual = !expect;
+
+ expectAndUpdateContent(name, "1");
+
+ EXPECT_TRUE(func(*mHwApi, &actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_P(GetBoolTest, success_returnsFalse) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ bool expect = false;
+ bool actual = !expect;
+
+ expectAndUpdateContent(name, "0");
+
+ EXPECT_TRUE(func(*mHwApi, &actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_P(GetBoolTest, failure) {
+ auto param = GetParam();
+ auto func = std::get<1>(param);
+ bool value;
+
+ EXPECT_FALSE(func(*mNoApi, &value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, GetBoolTest,
+ ValuesIn({
+ GetBoolTest::MakeParam("device/asp_enable",
+ &Vibrator::HwApi::getAspEnable),
+ }),
+ GetBoolTest::PrintParam);
+
+using GetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t *)>;
+
+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("device/num_waves",
+ &Vibrator::HwApi::getEffectCount),
+ GetUint32Test::MakeParam("device/cp_trigger_duration",
+ &Vibrator::HwApi::getEffectDuration),
+ }),
+ GetUint32Test::PrintParam);
+
+using SetBoolTest = HwApiTypedTest<bool(Vibrator::HwApi &, bool)>;
+
+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("activate", &Vibrator::HwApi::setActivate),
+ SetBoolTest::MakeParam("state", &Vibrator::HwApi::setState),
+ SetBoolTest::MakeParam("device/asp_enable",
+ &Vibrator::HwApi::setAspEnable),
+ }),
+ SetBoolTest::PrintParam);
+
+using SetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t)>;
+
+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("device/f0_stored", &Vibrator::HwApi::setF0),
+ SetUint32Test::MakeParam("device/redc_stored", &Vibrator::HwApi::setRedc),
+ SetUint32Test::MakeParam("device/q_stored", &Vibrator::HwApi::setQ),
+ SetUint32Test::MakeParam("duration", &Vibrator::HwApi::setDuration),
+ SetUint32Test::MakeParam("device/cp_trigger_index",
+ &Vibrator::HwApi::setEffectIndex),
+ SetUint32Test::MakeParam("device/cp_dig_scale", &Vibrator::HwApi::setEffectScale),
+ SetUint32Test::MakeParam("device/dig_scale", &Vibrator::HwApi::setGlobalScale),
+ SetUint32Test::MakeParam("device/gpio1_fall_index",
+ &Vibrator::HwApi::setGpioFallIndex),
+ SetUint32Test::MakeParam("device/gpio1_fall_dig_scale",
+ &Vibrator::HwApi::setGpioFallScale),
+ SetUint32Test::MakeParam("device/gpio1_rise_index",
+ &Vibrator::HwApi::setGpioRiseIndex),
+ SetUint32Test::MakeParam("device/gpio1_rise_dig_scale",
+ &Vibrator::HwApi::setGpioRiseScale),
+ }),
+ SetUint32Test::PrintParam);
+
+using SetStringTest = HwApiTypedTest<bool(Vibrator::HwApi &, std::string)>;
+
+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("device/cp_trigger_queue",
+ &Vibrator::HwApi::setEffectQueue),
+ }),
+ SetStringTest::PrintParam);
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l25/tests/test-hwcal.cpp b/vibrator/cs40l25/tests/test-hwcal.cpp
new file mode 100644
index 0000000..c9b7186
--- /dev/null
+++ b/vibrator/cs40l25/tests/test-hwcal.cpp
@@ -0,0 +1,299 @@
+/*
+ * 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 <android-base/file.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+
+#include "Hardware.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::testing::Test;
+
+class HwCalTest : public Test {
+ protected:
+ static constexpr uint32_t Q_DEFAULT = 15.5f * (1 << 16);
+ static constexpr std::array<uint32_t, 6> V_DEFAULT = {60, 70, 80, 90, 100, 76};
+
+ public:
+ void SetUp() override { setenv("CALIBRATION_FILEPATH", mCalFile.path, true); }
+
+ private:
+ static void pack(std::ostream &stream, const uint32_t &value, std::string lpad,
+ std::string rpad) {
+ stream << lpad << value << rpad;
+ }
+
+ template <typename T, typename std::array<T, 0>::size_type N>
+ static void pack(std::ostream &stream, const std::array<T, N> &value, std::string lpad,
+ std::string rpad) {
+ for (auto &entry : value) {
+ pack(stream, entry, lpad, rpad);
+ }
+ }
+
+ protected:
+ void createHwCal() { mHwCal = std::make_unique<HwCal>(); }
+
+ template <typename T>
+ 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<Vibrator::HwCal> mHwCal;
+ TemporaryFile mCalFile;
+};
+
+TEST_F(HwCalTest, f0_measured) {
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ write("f0_measured", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getF0(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, f0_missing) {
+ uint32_t actual;
+
+ createHwCal();
+
+ EXPECT_FALSE(mHwCal->getF0(&actual));
+}
+
+TEST_F(HwCalTest, redc_measured) {
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ write("redc_measured", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getRedc(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, redc_missing) {
+ uint32_t actual;
+
+ createHwCal();
+
+ EXPECT_FALSE(mHwCal->getRedc(&actual));
+}
+
+TEST_F(HwCalTest, q_measured) {
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ write("q_measured", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getQ(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, q_index) {
+ uint8_t value = std::rand();
+ uint32_t expect = value * 1.5f * (1 << 16) + 2.0f * (1 << 16);
+ uint32_t actual = ~expect;
+
+ write("q_index", value);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getQ(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, q_missing) {
+ uint32_t expect = Q_DEFAULT;
+ uint32_t actual = ~expect;
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getQ(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, q_nofile) {
+ uint32_t expect = Q_DEFAULT;
+ uint32_t actual = ~expect;
+
+ write("q_measured", actual);
+ unlink();
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getQ(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_levels) {
+ std::array<uint32_t, 6> expect;
+ std::array<uint32_t, 6> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) {
+ e = std::rand();
+ return ~e;
+ });
+
+ write("v_levels", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_missing) {
+ std::array<uint32_t, 6> expect = V_DEFAULT;
+ std::array<uint32_t, 6> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_short) {
+ std::array<uint32_t, 6> expect = V_DEFAULT;
+ std::array<uint32_t, 6> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ write("v_levels", std::array<uint32_t, expect.size() - 1>());
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_long) {
+ std::array<uint32_t, 6> expect = V_DEFAULT;
+ std::array<uint32_t, 6> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ write("v_levels", std::array<uint32_t, expect.size() + 1>());
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, v_nofile) {
+ std::array<uint32_t, 6> expect = V_DEFAULT;
+ std::array<uint32_t, 6> actual;
+
+ std::transform(expect.begin(), expect.end(), actual.begin(), [](uint32_t &e) { return ~e; });
+
+ write("v_levels", actual);
+ unlink();
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getVolLevels(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, multiple) {
+ uint32_t f0Expect = std::rand();
+ uint32_t f0Actual = ~f0Expect;
+ uint32_t redcExpect = std::rand();
+ uint32_t redcActual = ~redcExpect;
+ uint32_t qExpect = std::rand();
+ uint32_t qActual = ~qExpect;
+ std::array<uint32_t, 6> volExpect;
+ std::array<uint32_t, 6> volActual;
+
+ std::transform(volExpect.begin(), volExpect.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_levels", volExpect);
+
+ 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->getVolLevels(&volActual));
+ EXPECT_EQ(volExpect, volActual);
+}
+
+TEST_F(HwCalTest, trimming) {
+ uint32_t f0Expect = std::rand();
+ uint32_t f0Actual = ~f0Expect;
+ uint32_t redcExpect = std::rand();
+ uint32_t redcActual = ~redcExpect;
+ uint32_t qExpect = std::rand();
+ uint32_t qActual = ~qExpect;
+ std::array<uint32_t, 6> volExpect;
+ std::array<uint32_t, 6> volActual;
+
+ std::transform(volExpect.begin(), volExpect.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_levels", volExpect, " \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->getVolLevels(&volActual));
+ EXPECT_EQ(volExpect, volActual);
+}
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l25/tests/test-vibrator.cpp b/vibrator/cs40l25/tests/test-vibrator.cpp
new file mode 100644
index 0000000..1217cc2
--- /dev/null
+++ b/vibrator/cs40l25/tests/test-vibrator.cpp
@@ -0,0 +1,661 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <aidl/android/hardware/vibrator/BnVibratorCallback.h>
+#include <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <future>
+
+#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::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 <typename T, typename U, typename... Args>
+static EffectQueue Queue(const T &first, const U &second, Args... rest);
+
+static EffectLevel Level(float intensity);
+static EffectScale Scale(float intensity);
+
+// Constants With Arbitrary Values
+
+static constexpr std::array<EffectLevel, 6> V_LEVELS{40, 50, 60, 70, 80, 90};
+static constexpr std::array<EffectDuration, 10> EFFECT_DURATIONS{0, 0, 15, 0, 50,
+ 100, 150, 200, 250, 8};
+
+// Constants With Prescribed Values
+
+static const std::map<Effect, EffectIndex> EFFECT_INDEX{
+ {Effect::CLICK, 2},
+ {Effect::TICK, 2},
+ {Effect::HEAVY_CLICK, 2},
+ {Effect::TEXTURE_TICK, 9},
+};
+
+static constexpr EffectIndex QUEUE_INDEX{65534};
+
+static const EffectScale ON_GLOBAL_SCALE{levelToScale(V_LEVELS[5])};
+static const EffectIndex ON_EFFECT_INDEX{0};
+
+static const std::map<EffectTuple, EffectScale> EFFECT_SCALE{
+ {{Effect::CLICK, EffectStrength::LIGHT}, Scale(0.7f * 0.5f)},
+ {{Effect::CLICK, EffectStrength::MEDIUM}, Scale(0.7f * 0.7f)},
+ {{Effect::CLICK, EffectStrength::STRONG}, Scale(0.7f * 1.0f)},
+ {{Effect::TICK, EffectStrength::LIGHT}, Scale(0.5f * 0.5f)},
+ {{Effect::TICK, EffectStrength::MEDIUM}, Scale(0.5f * 0.7f)},
+ {{Effect::TICK, EffectStrength::STRONG}, Scale(0.5f * 1.0f)},
+ {{Effect::HEAVY_CLICK, EffectStrength::LIGHT}, Scale(1.0f * 0.5f)},
+ {{Effect::HEAVY_CLICK, EffectStrength::MEDIUM}, Scale(1.0f * 0.7f)},
+ {{Effect::HEAVY_CLICK, EffectStrength::STRONG}, Scale(1.0f * 1.0f)},
+ {{Effect::TEXTURE_TICK, EffectStrength::LIGHT}, Scale(0.5f * 0.5f)},
+ {{Effect::TEXTURE_TICK, EffectStrength::MEDIUM}, Scale(0.5f * 0.7f)},
+ {{Effect::TEXTURE_TICK, EffectStrength::STRONG}, Scale(0.5f * 1.0f)},
+};
+
+static const std::map<EffectTuple, EffectQueue> EFFECT_QUEUE{
+ {{Effect::DOUBLE_CLICK, EffectStrength::LIGHT},
+ Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), Level(0.7f * 0.5f)}, 100,
+ QueueEffect{EFFECT_INDEX.at(Effect::CLICK), Level(1.0f * 0.5f)})},
+ {{Effect::DOUBLE_CLICK, EffectStrength::MEDIUM},
+ Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), Level(0.7f * 0.7f)}, 100,
+ QueueEffect{EFFECT_INDEX.at(Effect::CLICK), Level(1.0f * 0.7f)})},
+ {{Effect::DOUBLE_CLICK, EffectStrength::STRONG},
+ Queue(QueueEffect{EFFECT_INDEX.at(Effect::CLICK), Level(0.7f * 1.0f)}, 100,
+ QueueEffect{EFFECT_INDEX.at(Effect::CLICK), Level(1.0f * 1.0f)})},
+};
+
+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 <typename T, typename U, typename... Args>
+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) {
+ auto vMin = std::max(V_LEVELS[0] - (V_LEVELS[4] - V_LEVELS[0]) / 4.0f, 4.0f);
+ auto vMax = V_LEVELS[4];
+ return std::lround(intensity * (vMax - vMin)) + vMin;
+}
+
+static EffectScale Scale(float intensity) {
+ return levelToScale(Level(intensity));
+}
+
+class VibratorTest : public Test {
+ public:
+ void SetUp() override {
+ std::unique_ptr<MockApi> mockapi;
+ std::unique_ptr<MockCal> mockcal;
+
+ createMock(&mockapi, &mockcal);
+ createVibrator(std::move(mockapi), std::move(mockcal));
+ }
+
+ void TearDown() override { deleteVibrator(); }
+
+ protected:
+ void createMock(std::unique_ptr<MockApi> *mockapi, std::unique_ptr<MockCal> *mockcal) {
+ *mockapi = std::make_unique<MockApi>();
+ *mockcal = std::make_unique<MockCal>();
+
+ mMockApi = mockapi->get();
+ mMockCal = mockcal->get();
+
+ ON_CALL(*mMockApi, destructor()).WillByDefault(Assign(&mMockApi, nullptr));
+
+ ON_CALL(*mMockApi, getEffectCount(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(EFFECT_DURATIONS.size()), Return(true)));
+
+ ON_CALL(*mMockApi, setEffectIndex(_))
+ .WillByDefault(Invoke(this, &VibratorTest::setEffectIndex));
+
+ ON_CALL(*mMockApi, getEffectDuration(_))
+ .WillByDefault(Invoke(this, &VibratorTest::getEffectDuration));
+
+ ON_CALL(*mMockCal, destructor()).WillByDefault(Assign(&mMockCal, nullptr));
+
+ ON_CALL(*mMockCal, getVolLevels(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(V_LEVELS), Return(true)));
+
+ relaxMock(false);
+ }
+
+ void createVibrator(std::unique_ptr<MockApi> mockapi, std::unique_ptr<MockCal> mockcal,
+ bool relaxed = true) {
+ if (relaxed) {
+ relaxMock(true);
+ }
+ mVibrator = ndk::SharedRefBase::make<Vibrator>(std::move(mockapi), std::move(mockcal));
+ if (relaxed) {
+ relaxMock(false);
+ }
+ }
+
+ void deleteVibrator(bool relaxed = true) {
+ if (relaxed) {
+ relaxMock(true);
+ }
+ mVibrator.reset();
+ }
+
+ bool setEffectIndex(EffectIndex index) {
+ mEffectIndex = index;
+ return true;
+ }
+
+ bool getEffectDuration(EffectDuration *duration) {
+ if (mEffectIndex < EFFECT_DURATIONS.size()) {
+ *duration = msToCycles(EFFECT_DURATIONS[mEffectIndex]);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ 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, setRedc(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setQ(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setActivate(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setDuration(_)).Times(times);
+ EXPECT_CALL(*mMockApi, getEffectCount(_)).Times(times);
+ EXPECT_CALL(*mMockApi, getEffectDuration(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setEffectIndex(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setEffectQueue(_)).Times(times);
+ EXPECT_CALL(*mMockApi, hasEffectScale()).Times(times);
+ EXPECT_CALL(*mMockApi, setEffectScale(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setGlobalScale(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setState(_)).Times(times);
+ EXPECT_CALL(*mMockApi, hasAspEnable()).Times(times);
+ EXPECT_CALL(*mMockApi, getAspEnable(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setAspEnable(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setGpioFallIndex(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setGpioFallScale(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setGpioRiseIndex(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setGpioRiseScale(_)).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, getVolLevels(_)).Times(times);
+ EXPECT_CALL(*mMockCal, debug(_)).Times(times);
+ }
+
+ protected:
+ MockApi *mMockApi;
+ MockCal *mMockCal;
+ std::shared_ptr<IVibrator> mVibrator;
+ uint32_t mEffectIndex;
+};
+
+TEST_F(VibratorTest, Constructor) {
+ std::unique_ptr<MockApi> mockapi;
+ std::unique_ptr<MockCal> mockcal;
+ uint32_t f0Val = std::rand();
+ uint32_t redcVal = std::rand();
+ uint32_t qVal = std::rand();
+ Expectation volGet;
+ Sequence f0Seq, redcSeq, qSeq, volSeq, durSeq;
+
+ 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(SetArgPointee<0>(f0Val), Return(true)));
+ EXPECT_CALL(*mMockApi, setF0(f0Val)).InSequence(f0Seq).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, getRedc(_))
+ .InSequence(redcSeq)
+ .WillOnce(DoAll(SetArgPointee<0>(redcVal), Return(true)));
+ EXPECT_CALL(*mMockApi, setRedc(redcVal)).InSequence(redcSeq).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, getQ(_))
+ .InSequence(qSeq)
+ .WillOnce(DoAll(SetArgPointee<0>(qVal), Return(true)));
+ EXPECT_CALL(*mMockApi, setQ(qVal)).InSequence(qSeq).WillOnce(Return(true));
+
+ volGet = EXPECT_CALL(*mMockCal, getVolLevels(_)).WillOnce(DoDefault());
+
+ EXPECT_CALL(*mMockApi, setState(true)).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, getEffectCount(_)).InSequence(durSeq).WillOnce(DoDefault());
+
+ for (auto &d : EFFECT_DURATIONS) {
+ EXPECT_CALL(*mMockApi, setEffectIndex(&d - &EFFECT_DURATIONS[0]))
+ .InSequence(durSeq)
+ .WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, getEffectDuration(_)).InSequence(durSeq).WillOnce(DoDefault());
+ }
+
+ createVibrator(std::move(mockapi), std::move(mockcal), false);
+}
+
+TEST_F(VibratorTest, on) {
+ Sequence s1, s2, s3;
+ uint16_t duration = std::rand() + 1;
+
+ EXPECT_CALL(*mMockApi, setGlobalScale(ON_GLOBAL_SCALE)).InSequence(s1).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setEffectIndex(ON_EFFECT_INDEX)).InSequence(s2).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockApi, setDuration(Ge(duration))).InSequence(s3).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setActivate(true)).InSequence(s1, s2, s3).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->on(duration, nullptr).isOk());
+}
+
+TEST_F(VibratorTest, off) {
+ EXPECT_CALL(*mMockApi, setActivate(false)).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setGlobalScale(0)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->off().isOk());
+}
+
+TEST_F(VibratorTest, supportsAmplitudeControl_supported) {
+ EXPECT_CALL(*mMockApi, hasEffectScale()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, hasAspEnable()).WillOnce(Return(true));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_GT(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsAmplitudeControl_unsupported1) {
+ EXPECT_CALL(*mMockApi, hasEffectScale()).WillOnce(Return(false));
+ EXPECT_CALL(*mMockApi, hasAspEnable()).WillOnce(Return(true));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsAmplitudeControl_unsupported2) {
+ EXPECT_CALL(*mMockApi, hasEffectScale()).WillOnce(Return(false));
+ EXPECT_CALL(*mMockApi, hasAspEnable()).WillOnce(Return(false));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsExternalAmplitudeControl_unsupported) {
+ EXPECT_CALL(*mMockApi, hasEffectScale()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, hasAspEnable()).WillOnce(Return(true));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, setAmplitude_supported) {
+ Sequence s;
+ EffectAmplitude amplitude = static_cast<float>(std::rand()) / RAND_MAX ?: 1.0f;
+
+ EXPECT_CALL(*mMockApi, getAspEnable(_))
+ .InSequence(s)
+ .WillOnce(DoAll(SetArgPointee<0>(false), Return(true)));
+ EXPECT_CALL(*mMockApi, setEffectScale(amplitudeToScale(amplitude)))
+ .InSequence(s)
+ .WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setAmplitude(amplitude).isOk());
+}
+
+TEST_F(VibratorTest, setAmplitude_unsupported) {
+ EXPECT_CALL(*mMockApi, getAspEnable(_)).WillOnce(DoAll(SetArgPointee<0>(true), Return(true)));
+
+ EXPECT_EQ(EX_UNSUPPORTED_OPERATION, mVibrator->setAmplitude(1).getExceptionCode());
+}
+
+TEST_F(VibratorTest, supportsExternalControl_supported) {
+ EXPECT_CALL(*mMockApi, hasEffectScale()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, hasAspEnable()).WillOnce(Return(true));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_GT(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, supportsExternalControl_unsupported) {
+ EXPECT_CALL(*mMockApi, hasEffectScale()).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, hasAspEnable()).WillOnce(Return(false));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_F(VibratorTest, setExternalControl_enable) {
+ Sequence s;
+
+ EXPECT_CALL(*mMockApi, setGlobalScale(ON_GLOBAL_SCALE)).InSequence(s).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setAspEnable(true)).InSequence(s).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setExternalControl(true).isOk());
+}
+
+TEST_F(VibratorTest, setExternalControl_disable) {
+ EXPECT_CALL(*mMockApi, setAspEnable(false)).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setGlobalScale(0)).WillOnce(Return(true));
+
+ EXPECT_TRUE(mVibrator->setExternalControl(false).isOk());
+}
+
+class EffectsTest : public VibratorTest, public WithParamInterface<EffectTuple> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &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<MockVibratorCallback>();
+ std::promise<void> promise;
+ std::future<void> future{promise.get_future()};
+ auto complete = [&promise] {
+ promise.set_value();
+ return ndk::ScopedAStatus::ok();
+ };
+
+ ExpectationSet eSetup;
+ Expectation eActivate, ePoll;
+
+ if (scale != EFFECT_SCALE.end()) {
+ EffectIndex index = EFFECT_INDEX.at(effect);
+ duration = EFFECT_DURATIONS[index];
+
+ eSetup += EXPECT_CALL(*mMockApi, setEffectIndex(index)).WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, setEffectScale(scale->second)).WillOnce(Return(true));
+ } else if (queue != EFFECT_QUEUE.end()) {
+ duration = std::get<1>(queue->second);
+
+ eSetup += EXPECT_CALL(*mMockApi, setEffectIndex(QUEUE_INDEX)).WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, setEffectQueue(std::get<0>(queue->second)))
+ .WillOnce(Return(true));
+ eSetup += EXPECT_CALL(*mMockApi, setEffectScale(0)).WillOnce(Return(true));
+ } else {
+ duration = 0;
+ }
+
+ if (duration) {
+ eSetup += EXPECT_CALL(*mMockApi, setDuration(Ge(duration))).WillOnce(Return(true));
+ eActivate = EXPECT_CALL(*mMockApi, setActivate(true)).After(eSetup).WillOnce(Return(true));
+ ePoll = EXPECT_CALL(*mMockApi, pollVibeState(false))
+ .After(eActivate)
+ .WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setActivate(false)).After(ePoll).WillOnce(Return(true));
+ EXPECT_CALL(*callback, onComplete()).After(ePoll).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);
+ }
+}
+
+TEST_P(EffectsTest, alwaysOnEnable) {
+ auto param = GetParam();
+ auto effect = std::get<0>(param);
+ auto strength = std::get<1>(param);
+ auto scale = EFFECT_SCALE.find(param);
+ bool supported = (scale != EFFECT_SCALE.end());
+
+ if (supported) {
+ EXPECT_CALL(*mMockApi, setGpioRiseIndex(EFFECT_INDEX.at(effect))).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setGpioRiseScale(scale->second)).WillOnce(Return(true));
+ }
+
+ ndk::ScopedAStatus status = mVibrator->alwaysOnEnable(0, effect, strength);
+ if (supported) {
+ EXPECT_EQ(EX_NONE, status.getExceptionCode());
+ } else {
+ EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode());
+ }
+}
+
+const std::vector<Effect> kEffects{ndk::enum_range<Effect>().begin(),
+ ndk::enum_range<Effect>().end()};
+const std::vector<EffectStrength> kEffectStrengths{ndk::enum_range<EffectStrength>().begin(),
+ ndk::enum_range<EffectStrength>().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<PrimitiveParam> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &info) {
+ return toString(info.param.primitive);
+ }
+};
+
+const std::vector<PrimitiveParam> kPrimitiveParams = {
+ {CompositePrimitive::NOOP, 0}, {CompositePrimitive::CLICK, 2},
+ {CompositePrimitive::QUICK_RISE, 6}, {CompositePrimitive::SLOW_RISE, 7},
+ {CompositePrimitive::QUICK_FALL, 8},
+};
+
+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<CompositeEffect> composite;
+ EffectQueue queue;
+};
+
+class ComposeTest : public VibratorTest, public WithParamInterface<ComposeParam> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &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, ePoll;
+ auto callback = ndk::SharedRefBase::make<MockVibratorCallback>();
+ std::promise<void> promise;
+ std::future<void> future{promise.get_future()};
+ auto complete = [&promise] {
+ promise.set_value();
+ return ndk::ScopedAStatus::ok();
+ };
+
+ eSetup += EXPECT_CALL(*mMockApi, setEffectIndex(QUEUE_INDEX)).WillOnce(DoDefault());
+ eSetup += EXPECT_CALL(*mMockApi, setEffectQueue(queue)).WillOnce(Return(true));
+ eSetup += EXPECT_CALL(*mMockApi, setEffectScale(0)).WillOnce(Return(true));
+ eSetup += EXPECT_CALL(*mMockApi, setDuration(UINT32_MAX)).WillOnce(Return(true));
+ eActivate = EXPECT_CALL(*mMockApi, setActivate(true)).After(eSetup).WillOnce(Return(true));
+ ePoll = EXPECT_CALL(*mMockApi, pollVibeState(false)).After(eActivate).WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setActivate(false)).After(ePoll).WillOnce(Return(true));
+ EXPECT_CALL(*callback, onComplete()).After(ePoll).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<ComposeParam> kComposeParams = {
+ {"click", {{0, CompositePrimitive::CLICK, 1.0f}}, Queue(QueueEffect(2, Level(1.0f)), 0)},
+ {"quick_rise",
+ {{3, CompositePrimitive::QUICK_RISE, 0.4f}},
+ Queue(3, QueueEffect(6, Level(0.4f)), 0)},
+ {"slow_rise",
+ {{4, CompositePrimitive::SLOW_RISE, 0.0f}},
+ Queue(4, QueueEffect(7, Level(0.0f)), 0)},
+ {"quick_fall",
+ {{5, CompositePrimitive::QUICK_FALL, 1.0f}},
+ Queue(5, QueueEffect(8, Level(1.0f)), 0)},
+ {"snap",
+ {{7, CompositePrimitive::QUICK_RISE, 1.0f}, {0, CompositePrimitive::QUICK_FALL, 1.0f}},
+ Queue(7, QueueEffect(6, Level(1.0f)), QueueEffect(8, Level(1.0f)), 0)},
+};
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, ComposeTest,
+ ValuesIn(kComposeParams.begin(), kComposeParams.end()),
+ ComposeTest::PrintParam);
+
+class AlwaysOnTest : public VibratorTest, public WithParamInterface<int32_t> {
+ public:
+ static auto PrintParam(const TestParamInfo<ParamType> &info) {
+ return std::to_string(info.param);
+ }
+};
+
+TEST_P(AlwaysOnTest, alwaysOnEnable) {
+ auto param = GetParam();
+ auto scale = EFFECT_SCALE.begin();
+
+ std::advance(scale, std::rand() % EFFECT_SCALE.size());
+
+ auto effect = std::get<0>(scale->first);
+ auto strength = std::get<1>(scale->first);
+
+ switch (param) {
+ case 0:
+ EXPECT_CALL(*mMockApi, setGpioRiseIndex(EFFECT_INDEX.at(effect)))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setGpioRiseScale(scale->second)).WillOnce(Return(true));
+ break;
+ case 1:
+ EXPECT_CALL(*mMockApi, setGpioFallIndex(EFFECT_INDEX.at(effect)))
+ .WillOnce(Return(true));
+ EXPECT_CALL(*mMockApi, setGpioFallScale(scale->second)).WillOnce(Return(true));
+ break;
+ }
+
+ ndk::ScopedAStatus status = mVibrator->alwaysOnEnable(param, effect, strength);
+ EXPECT_EQ(EX_NONE, status.getExceptionCode());
+}
+
+TEST_P(AlwaysOnTest, alwaysOnDisable) {
+ auto param = GetParam();
+
+ switch (param) {
+ case 0:
+ EXPECT_CALL(*mMockApi, setGpioRiseIndex(0)).WillOnce(Return(true));
+ break;
+ case 1:
+ EXPECT_CALL(*mMockApi, setGpioFallIndex(0)).WillOnce(Return(true));
+ break;
+ }
+
+ ndk::ScopedAStatus status = mVibrator->alwaysOnDisable(param);
+ EXPECT_EQ(EX_NONE, status.getExceptionCode());
+}
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, AlwaysOnTest, Range(0, 1), AlwaysOnTest::PrintParam);
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/cs40l25/tests/types.h b/vibrator/cs40l25/tests/types.h
new file mode 100644
index 0000000..c8d379d
--- /dev/null
+++ b/vibrator/cs40l25/tests/types.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
+using EffectIndex = uint16_t;
+using EffectLevel = uint32_t;
+using EffectAmplitude = float;
+using EffectScale = uint16_t;
+using EffectDuration = uint32_t;
+using EffectQueue = std::tuple<std::string, EffectDuration>;
+using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect,
+ ::aidl::android::hardware::vibrator::EffectStrength>;
+
+using QueueEffect = std::tuple<EffectIndex, EffectLevel>;
+using QueueDelay = uint32_t;
+
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
diff --git a/vibrator/cs40l25/tests/utils.h b/vibrator/cs40l25/tests/utils.h
new file mode 100644
index 0000000..6cc87af
--- /dev/null
+++ b/vibrator/cs40l25/tests/utils.h
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H
+
+#include <cmath>
+
+#include "types.h"
+
+static inline EffectScale toScale(float target, float maximum) {
+ return std::round((-20 * std::log10(target / static_cast<float>(maximum))) / 0.125f);
+}
+
+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
diff --git a/vibrator/drv2624/Android.bp b/vibrator/drv2624/Android.bp
new file mode 100644
index 0000000..594aaeb
--- /dev/null
+++ b/vibrator/drv2624/Android.bp
@@ -0,0 +1,58 @@
+//
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+cc_defaults {
+ name: "android.hardware.vibrator-defaults.drv2624",
+ cflags: [
+ "-DATRACE_TAG=(ATRACE_TAG_VIBRATOR | ATRACE_TAG_HAL)",
+ "-DLOG_TAG=\"android.hardware.vibrator-drv2624\"",
+ ],
+}
+
+cc_defaults {
+ name: "VibratorHalDrv2624BinaryDefaults",
+ defaults: [
+ "PixelVibratorBinaryDefaults",
+ "android.hardware.vibrator-defaults.drv2624",
+ ],
+}
+
+cc_defaults {
+ name: "VibratorHalDrv2624TestDefaults",
+ defaults: [
+ "PixelVibratorTestDefaults",
+ "android.hardware.vibrator-defaults.drv2624",
+ ],
+ static_libs: ["android.hardware.vibrator-impl.drv2624"],
+}
+
+cc_library {
+ name: "android.hardware.vibrator-impl.drv2624",
+ defaults: ["VibratorHalDrv2624BinaryDefaults"],
+ srcs: ["Vibrator.cpp"],
+ export_include_dirs: ["."],
+ vendor_available: true,
+ visibility: [":__subpackages__"],
+}
+
+cc_binary {
+ name: "android.hardware.vibrator-service.drv2624",
+ defaults: ["VibratorHalDrv2624BinaryDefaults"],
+ init_rc: ["android.hardware.vibrator-service.drv2624.rc"],
+ vintf_fragments: ["android.hardware.vibrator-service.drv2624.xml"],
+ srcs: ["service.cpp"],
+ static_libs: ["android.hardware.vibrator-impl.drv2624"],
+ proprietary: true,
+}
diff --git a/vibrator/drv2624/Hardware.h b/vibrator/drv2624/Hardware.h
new file mode 100644
index 0000000..a07aec7
--- /dev/null
+++ b/vibrator/drv2624/Hardware.h
@@ -0,0 +1,146 @@
+/*
+ * 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 "HardwareBase.h"
+#include "Vibrator.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+class HwApi : public Vibrator::HwApi, private HwApiBase {
+ public:
+ static std::unique_ptr<HwApi> Create() {
+ auto hwapi = std::unique_ptr<HwApi>(new HwApi());
+ // the following streams are required
+ if (!hwapi->mActivate.is_open() || !hwapi->mDuration.is_open() ||
+ !hwapi->mState.is_open()) {
+ return nullptr;
+ }
+ return hwapi;
+ }
+
+ bool setAutocal(std::string value) override { return set(value, &mAutocal); }
+ bool setOlLraPeriod(uint32_t value) override { return set(value, &mOlLraPeriod); }
+ bool setActivate(bool value) override { return set(value, &mActivate); }
+ bool setDuration(uint32_t value) override { return set(value, &mDuration); }
+ bool setState(bool value) override { return set(value, &mState); }
+ bool hasRtpInput() override { return has(mRtpInput); }
+ bool setRtpInput(int8_t value) override { return set(value, &mRtpInput); }
+ bool setMode(std::string value) override { return set(value, &mMode); }
+ bool setSequencer(std::string value) override { return set(value, &mSequencer); }
+ bool setScale(uint8_t value) override { return set(value, &mScale); }
+ bool setCtrlLoop(bool value) override { return set(value, &mCtrlLoop); }
+ bool setLpTriggerEffect(uint32_t value) override { return set(value, &mLpTrigger); }
+ bool setLraWaveShape(uint32_t value) override { return set(value, &mLraWaveShape); }
+ bool setOdClamp(uint32_t value) override { return set(value, &mOdClamp); }
+ void debug(int fd) override { HwApiBase::debug(fd); }
+
+ private:
+ HwApi() {
+ open("device/autocal", &mAutocal);
+ open("device/ol_lra_period", &mOlLraPeriod);
+ open("activate", &mActivate);
+ open("duration", &mDuration);
+ open("state", &mState);
+ open("device/rtp_input", &mRtpInput);
+ open("device/mode", &mMode);
+ open("device/set_sequencer", &mSequencer);
+ open("device/scale", &mScale);
+ open("device/ctrl_loop", &mCtrlLoop);
+ open("device/lp_trigger_effect", &mLpTrigger);
+ open("device/lra_wave_shape", &mLraWaveShape);
+ open("device/od_clamp", &mOdClamp);
+ }
+
+ private:
+ std::ofstream mAutocal;
+ std::ofstream mOlLraPeriod;
+ std::ofstream mActivate;
+ std::ofstream mDuration;
+ std::ofstream mState;
+ std::ofstream mRtpInput;
+ std::ofstream mMode;
+ std::ofstream mSequencer;
+ std::ofstream mScale;
+ std::ofstream mCtrlLoop;
+ std::ofstream mLpTrigger;
+ std::ofstream mLraWaveShape;
+ std::ofstream mOdClamp;
+};
+
+class HwCal : public Vibrator::HwCal, private HwCalBase {
+ private:
+ static constexpr char AUTOCAL_CONFIG[] = "autocal";
+ static constexpr char LRA_PERIOD_CONFIG[] = "lra_period";
+
+ static constexpr uint32_t WAVEFORM_CLICK_EFFECT_MS = 6;
+ static constexpr uint32_t WAVEFORM_TICK_EFFECT_MS = 2;
+ static constexpr uint32_t WAVEFORM_DOUBLE_CLICK_EFFECT_MS = 135;
+ static constexpr uint32_t WAVEFORM_HEAVY_CLICK_EFFECT_MS = 8;
+
+ static constexpr uint32_t DEFAULT_LRA_PERIOD = 262;
+ static constexpr uint32_t DEFAULT_FREQUENCY_SHIFT = 10;
+ static constexpr uint32_t DEFAULT_VOLTAGE_MAX = 107; // 2.15V;
+
+ public:
+ HwCal() {}
+
+ bool getAutocal(std::string *value) override { return getPersist(AUTOCAL_CONFIG, value); }
+ bool getLraPeriod(uint32_t *value) override {
+ if (getPersist(LRA_PERIOD_CONFIG, value)) {
+ return true;
+ }
+ *value = DEFAULT_LRA_PERIOD;
+ return true;
+ }
+ bool getCloseLoopThreshold(uint32_t *value) override {
+ return getProperty("closeloop.threshold", value, UINT32_MAX);
+ return true;
+ }
+ bool getDynamicConfig(bool *value) override {
+ return getProperty("config.dynamic", value, false);
+ }
+ bool getLongFrequencyShift(uint32_t *value) override {
+ return getProperty("long.frequency.shift", value, DEFAULT_FREQUENCY_SHIFT);
+ }
+ bool getShortVoltageMax(uint32_t *value) override {
+ return getProperty("short.voltage", value, DEFAULT_VOLTAGE_MAX);
+ }
+ bool getLongVoltageMax(uint32_t *value) override {
+ return getProperty("long.voltage", value, DEFAULT_VOLTAGE_MAX);
+ }
+ bool getClickDuration(uint32_t *value) override {
+ return getProperty("click.duration", value, WAVEFORM_CLICK_EFFECT_MS);
+ }
+ bool getTickDuration(uint32_t *value) override {
+ return getProperty("tick.duration", value, WAVEFORM_TICK_EFFECT_MS);
+ }
+ bool getDoubleClickDuration(uint32_t *value) override {
+ return getProperty("double_click.duration", value, WAVEFORM_DOUBLE_CLICK_EFFECT_MS);
+ }
+ bool getHeavyClickDuration(uint32_t *value) override {
+ return getProperty("heavyclick.duration", value, WAVEFORM_HEAVY_CLICK_EFFECT_MS);
+ }
+ void debug(int fd) override { HwCalBase::debug(fd); }
+};
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/drv2624/TEST_MAPPING b/vibrator/drv2624/TEST_MAPPING
new file mode 100644
index 0000000..ec34f66
--- /dev/null
+++ b/vibrator/drv2624/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+ "presubmit": [
+ {
+ "name": "VibratorHalDrv2624TestSuite"
+ }
+ ],
+ "postsubmit": [
+ {
+ "name": "VibratorHalDrv2624Benchmark",
+ "keywords": [
+ "primary-device"
+ ]
+ }
+ ]
+}
diff --git a/vibrator/drv2624/Vibrator.cpp b/vibrator/drv2624/Vibrator.cpp
new file mode 100644
index 0000000..9e8683b
--- /dev/null
+++ b/vibrator/drv2624/Vibrator.cpp
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+#include "Vibrator.h"
+#include "utils.h"
+
+#include <cutils/properties.h>
+#include <hardware/hardware.h>
+#include <hardware/vibrator.h>
+#include <log/log.h>
+#include <utils/Trace.h>
+
+#include <cinttypes>
+#include <cmath>
+#include <fstream>
+#include <iostream>
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+static constexpr int8_t MAX_RTP_INPUT = 127;
+static constexpr int8_t MIN_RTP_INPUT = 0;
+
+static constexpr char RTP_MODE[] = "rtp";
+static constexpr char WAVEFORM_MODE[] = "waveform";
+
+// Use effect #1 in the waveform library for CLICK effect
+static constexpr char WAVEFORM_CLICK_EFFECT_SEQ[] = "1 0";
+
+// Use effect #2 in the waveform library for TICK effect
+static constexpr char WAVEFORM_TICK_EFFECT_SEQ[] = "2 0";
+
+// Use effect #3 in the waveform library for DOUBLE_CLICK effect
+static constexpr char WAVEFORM_DOUBLE_CLICK_EFFECT_SEQ[] = "3 0";
+
+// Use effect #4 in the waveform library for HEAVY_CLICK effect
+static constexpr char WAVEFORM_HEAVY_CLICK_EFFECT_SEQ[] = "4 0";
+
+static std::uint32_t freqPeriodFormula(std::uint32_t in) {
+ return 1000000000 / (24615 * in);
+}
+
+using utils::toUnderlying;
+
+Vibrator::Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal)
+ : mHwApi(std::move(hwapi)), mHwCal(std::move(hwcal)) {
+ std::string autocal;
+ uint32_t lraPeriod;
+ bool dynamicConfig;
+
+ if (!mHwApi->setState(true)) {
+ ALOGE("Failed to set state (%d): %s", errno, strerror(errno));
+ }
+
+ if (mHwCal->getAutocal(&autocal)) {
+ mHwApi->setAutocal(autocal);
+ }
+ mHwCal->getLraPeriod(&lraPeriod);
+
+ mHwCal->getCloseLoopThreshold(&mCloseLoopThreshold);
+ mHwCal->getDynamicConfig(&dynamicConfig);
+
+ if (dynamicConfig) {
+ uint32_t longFreqencyShift;
+ uint32_t shortVoltageMax, longVoltageMax;
+
+ mHwCal->getLongFrequencyShift(&longFreqencyShift);
+ mHwCal->getShortVoltageMax(&shortVoltageMax);
+ mHwCal->getLongVoltageMax(&longVoltageMax);
+
+ mEffectConfig.reset(new VibrationConfig({
+ .shape = WaveShape::SINE,
+ .odClamp = shortVoltageMax,
+ .olLraPeriod = lraPeriod,
+ }));
+ mSteadyConfig.reset(new VibrationConfig({
+ .shape = WaveShape::SQUARE,
+ .odClamp = longVoltageMax,
+ // 1. Change long lra period to frequency
+ // 2. Get frequency': subtract the frequency shift from the frequency
+ // 3. Get final long lra period after put the frequency' to formula
+ .olLraPeriod = freqPeriodFormula(freqPeriodFormula(lraPeriod) - longFreqencyShift),
+ }));
+ } else {
+ mHwApi->setOlLraPeriod(lraPeriod);
+ }
+
+ mHwCal->getClickDuration(&mClickDuration);
+ mHwCal->getTickDuration(&mTickDuration);
+ mHwCal->getDoubleClickDuration(&mDoubleClickDuration);
+ mHwCal->getHeavyClickDuration(&mHeavyClickDuration);
+
+ // This enables effect #1 from the waveform library to be triggered by SLPI
+ // while the AP is in suspend mode
+ if (!mHwApi->setLpTriggerEffect(1)) {
+ ALOGW("Failed to set LP trigger mode (%d): %s", errno, strerror(errno));
+ }
+}
+
+ndk::ScopedAStatus Vibrator::getCapabilities(int32_t *_aidl_return) {
+ ATRACE_NAME("Vibrator::getCapabilities");
+ int32_t ret = 0;
+ if (mHwApi->hasRtpInput()) {
+ ret |= IVibrator::CAP_AMPLITUDE_CONTROL;
+ }
+ *_aidl_return = ret;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::on(uint32_t timeoutMs, const char mode[],
+ const std::unique_ptr<VibrationConfig> &config) {
+ LoopControl loopMode = LoopControl::OPEN;
+
+ // Open-loop mode is used for short click for over-drive
+ // Close-loop mode is used for long notification for stability
+ if (mode == RTP_MODE && timeoutMs > mCloseLoopThreshold) {
+ loopMode = LoopControl::CLOSE;
+ }
+
+ mHwApi->setCtrlLoop(toUnderlying(loopMode));
+ if (!mHwApi->setDuration(timeoutMs)) {
+ ALOGE("Failed to set duration (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ mHwApi->setMode(mode);
+ if (config != nullptr) {
+ mHwApi->setLraWaveShape(toUnderlying(config->shape));
+ mHwApi->setOdClamp(config->odClamp);
+ mHwApi->setOlLraPeriod(config->olLraPeriod);
+ }
+
+ if (!mHwApi->setActivate(1)) {
+ ALOGE("Failed to activate (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::on(int32_t timeoutMs,
+ const std::shared_ptr<IVibratorCallback> &callback) {
+ ATRACE_NAME("Vibrator::on");
+ if (callback) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+ return on(timeoutMs, RTP_MODE, mSteadyConfig);
+}
+
+ndk::ScopedAStatus Vibrator::off() {
+ ATRACE_NAME("Vibrator::off");
+ if (!mHwApi->setActivate(0)) {
+ ALOGE("Failed to turn vibrator off (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ 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);
+ }
+
+ int32_t rtp_input = std::round(amplitude * (MAX_RTP_INPUT - MIN_RTP_INPUT) + MIN_RTP_INPUT);
+
+ if (!mHwApi->setRtpInput(rtp_input)) {
+ ALOGE("Failed to set amplitude (%d): %s", errno, strerror(errno));
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::setExternalControl(bool enabled) {
+ ATRACE_NAME("Vibrator::setExternalControl");
+ ALOGE("Not support in DRV2624 solution, %d", enabled);
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+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, " Close Loop Thresh: %" PRIu32 "\n", mCloseLoopThreshold);
+ if (mSteadyConfig) {
+ dprintf(fd, " Steady Shape: %" PRIu32 "\n", mSteadyConfig->shape);
+ dprintf(fd, " Steady OD Clamp: %" PRIu32 "\n", mSteadyConfig->odClamp);
+ dprintf(fd, " Steady OL LRA Period: %" PRIu32 "\n", mSteadyConfig->olLraPeriod);
+ }
+ if (mEffectConfig) {
+ dprintf(fd, " Effect Shape: %" PRIu32 "\n", mEffectConfig->shape);
+ dprintf(fd, " Effect OD Clamp: %" PRIu32 "\n", mEffectConfig->odClamp);
+ dprintf(fd, " Effect OL LRA Period: %" PRIu32 "\n", mEffectConfig->olLraPeriod);
+ }
+ dprintf(fd, " Click Duration: %" PRIu32 "\n", mClickDuration);
+ dprintf(fd, " Tick Duration: %" PRIu32 "\n", mTickDuration);
+ dprintf(fd, " Double Click Duration: %" PRIu32 "\n", mDoubleClickDuration);
+ dprintf(fd, " Heavy Click Duration: %" PRIu32 "\n", mHeavyClickDuration);
+
+ dprintf(fd, "\n");
+
+ mHwApi->debug(fd);
+
+ dprintf(fd, "\n");
+
+ mHwCal->debug(fd);
+
+ fsync(fd);
+ return STATUS_OK;
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedEffects(std::vector<Effect> *_aidl_return) {
+ *_aidl_return = {Effect::TEXTURE_TICK, Effect::TICK, Effect::CLICK, Effect::HEAVY_CLICK,
+ Effect::DOUBLE_CLICK};
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::perform(Effect effect, EffectStrength strength,
+ const std::shared_ptr<IVibratorCallback> &callback,
+ int32_t *_aidl_return) {
+ ATRACE_NAME("Vibrator::perform");
+ ndk::ScopedAStatus status;
+
+ if (callback) {
+ status = ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ } else {
+ status = performEffect(effect, strength, _aidl_return);
+ }
+
+ return status;
+}
+
+static ndk::ScopedAStatus convertEffectStrength(EffectStrength strength, uint8_t *outScale) {
+ uint8_t scale;
+
+ switch (strength) {
+ case EffectStrength::LIGHT:
+ scale = 2; // 50%
+ break;
+ case EffectStrength::MEDIUM:
+ case EffectStrength::STRONG:
+ scale = 0; // 100%
+ break;
+ default:
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+
+ *outScale = scale;
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::performEffect(Effect effect, EffectStrength strength,
+ int32_t *outTimeMs) {
+ ndk::ScopedAStatus status;
+ uint32_t timeMS = 0;
+ uint8_t scale;
+
+ switch (effect) {
+ case Effect::TEXTURE_TICK:
+ mHwApi->setSequencer(WAVEFORM_TICK_EFFECT_SEQ);
+ timeMS = mTickDuration;
+ break;
+ case Effect::CLICK:
+ mHwApi->setSequencer(WAVEFORM_CLICK_EFFECT_SEQ);
+ timeMS = mClickDuration;
+ break;
+ case Effect::DOUBLE_CLICK:
+ mHwApi->setSequencer(WAVEFORM_DOUBLE_CLICK_EFFECT_SEQ);
+ timeMS = mDoubleClickDuration;
+ break;
+ case Effect::TICK:
+ mHwApi->setSequencer(WAVEFORM_TICK_EFFECT_SEQ);
+ timeMS = mTickDuration;
+ break;
+ case Effect::HEAVY_CLICK:
+ mHwApi->setSequencer(WAVEFORM_HEAVY_CLICK_EFFECT_SEQ);
+ timeMS = mHeavyClickDuration;
+ break;
+ default:
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ }
+
+ status = convertEffectStrength(strength, &scale);
+ if (!status.isOk()) {
+ return status;
+ }
+
+ mHwApi->setScale(scale);
+ status = on(timeMS, WAVEFORM_MODE, mEffectConfig);
+ if (!status.isOk()) {
+ return status;
+ }
+
+ *outTimeMs = timeMS;
+
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedAlwaysOnEffects(std::vector<Effect> * /*_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::getCompositionDelayMax(int32_t * /*maxDelayMs*/) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Vibrator::getCompositionSizeMax(int32_t * /*maxSize*/) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Vibrator::getSupportedPrimitives(
+ std::vector<CompositePrimitive> * /*supported*/) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Vibrator::getPrimitiveDuration(CompositePrimitive /*primitive*/,
+ int32_t * /*durationMs*/) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Vibrator::compose(const std::vector<CompositeEffect> & /*composite*/,
+ const std::shared_ptr<IVibratorCallback> & /*callback*/) {
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/drv2624/Vibrator.h b/vibrator/drv2624/Vibrator.h
new file mode 100644
index 0000000..971014d
--- /dev/null
+++ b/vibrator/drv2624/Vibrator.h
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <aidl/android/hardware/vibrator/BnVibrator.h>
+
+#include <fstream>
+
+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 COMP, BEMF, and GAIN calibration values to use.
+ // <COMP> <BEMF> <GAIN>
+ virtual bool setAutocal(std::string value) = 0;
+ // Stores the open-loop LRA frequency to be used.
+ virtual bool setOlLraPeriod(uint32_t value) = 0;
+ // Activates/deactivates the vibrator for durations specified by
+ // setDuration().
+ virtual bool setActivate(bool value) = 0;
+ // Specifies the vibration duration in milliseconds.
+ virtual bool setDuration(uint32_t value) = 0;
+ // Specifies the active state of the vibrator
+ // (true = enabled, false = disabled).
+ virtual bool setState(bool value) = 0;
+ // Reports whether setRtpInput() is supported.
+ virtual bool hasRtpInput() = 0;
+ // Specifies the playback amplitude of the haptic waveforms in RTP mode.
+ // Negative numbers indicates braking.
+ virtual bool setRtpInput(int8_t value) = 0;
+ // Specifies the mode of operation.
+ // rtp - RTP Mode
+ // waveform - Waveform Sequencer Mode
+ // diag - Diagnostics Routine
+ // autocal - Automatic Level Calibration Routine
+ virtual bool setMode(std::string value) = 0;
+ // Specifies a waveform sequence in index-count pairs.
+ // <index-1> <count-1> [<index-2> <cound-2> ...]
+ virtual bool setSequencer(std::string value) = 0;
+ // Specifies the scaling of effects in Waveform mode.
+ // 0 - 100%
+ // 1 - 75%
+ // 2 - 50%
+ // 3 - 25%
+ virtual bool setScale(uint8_t value) = 0;
+ // Selects either closed loop or open loop mode.
+ // (true = open, false = closed).
+ virtual bool setCtrlLoop(bool value) = 0;
+ // Specifies waveform index to be played in low-power trigger mode.
+ // 0 - Disabled
+ // 1+ - Waveform Index
+ virtual bool setLpTriggerEffect(uint32_t value) = 0;
+ // Specifies which shape to use for driving the LRA when in open loop
+ // mode.
+ // 0 - Square Wave
+ // 1 - Sine Wave
+ virtual bool setLraWaveShape(uint32_t value) = 0;
+ // Specifies the maximum voltage for automatic overdrive and automatic
+ // braking periods.
+ virtual bool setOdClamp(uint32_t value) = 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;
+ // Obtains the COMP, BEMF, and GAIN calibration values to use.
+ virtual bool getAutocal(std::string *value) = 0;
+ // Obtains the open-loop LRA frequency to be used.
+ virtual bool getLraPeriod(uint32_t *value) = 0;
+ // Obtains threshold in ms, above which close-loop should be used.
+ virtual bool getCloseLoopThreshold(uint32_t *value) = 0;
+ // Obtains dynamic/static configuration choice.
+ virtual bool getDynamicConfig(bool *value) = 0;
+ // Obtains LRA frequency shift for long (steady) vibrations.
+ virtual bool getLongFrequencyShift(uint32_t *value) = 0;
+ // Obtains maximum voltage for short (effect) vibrations
+ virtual bool getShortVoltageMax(uint32_t *value) = 0;
+ // Obtains maximum voltage for long (steady) vibrations
+ virtual bool getLongVoltageMax(uint32_t *value) = 0;
+ // Obtains the duration for the click effect
+ virtual bool getClickDuration(uint32_t *value) = 0;
+ // Obtains the duration for the tick effect
+ virtual bool getTickDuration(uint32_t *value) = 0;
+ // Obtains the duration for the double-click effect
+ virtual bool getDoubleClickDuration(uint32_t *value) = 0;
+ // Obtains the duration for the heavy-click effect
+ virtual bool getHeavyClickDuration(uint32_t *value) = 0;
+ // Emit diagnostic information to the given file.
+ virtual void debug(int fd) = 0;
+ };
+
+ private:
+ enum class LoopControl : bool {
+ CLOSE = false,
+ OPEN = true,
+ };
+
+ enum class WaveShape : uint32_t {
+ SQUARE = 0,
+ SINE = 1,
+ };
+
+ struct VibrationConfig {
+ WaveShape shape;
+ uint32_t odClamp;
+ uint32_t olLraPeriod;
+ };
+
+ public:
+ Vibrator(std::unique_ptr<HwApi> hwapi, std::unique_ptr<HwCal> hwcal);
+
+ ndk::ScopedAStatus getCapabilities(int32_t *_aidl_return) override;
+ ndk::ScopedAStatus off() override;
+ ndk::ScopedAStatus on(int32_t timeoutMs,
+ const std::shared_ptr<IVibratorCallback> &callback) override;
+ ndk::ScopedAStatus perform(Effect effect, EffectStrength strength,
+ const std::shared_ptr<IVibratorCallback> &callback,
+ int32_t *_aidl_return) override;
+ ndk::ScopedAStatus getSupportedEffects(std::vector<Effect> *_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<CompositePrimitive> *supported) override;
+ ndk::ScopedAStatus getPrimitiveDuration(CompositePrimitive primitive,
+ int32_t *durationMs) override;
+ ndk::ScopedAStatus compose(const std::vector<CompositeEffect> &composite,
+ const std::shared_ptr<IVibratorCallback> &callback) override;
+ ndk::ScopedAStatus getSupportedAlwaysOnEffects(std::vector<Effect> *_aidl_return) override;
+ ndk::ScopedAStatus alwaysOnEnable(int32_t id, Effect effect, EffectStrength strength) override;
+ ndk::ScopedAStatus alwaysOnDisable(int32_t id) override;
+
+ binder_status_t dump(int fd, const char **args, uint32_t numArgs) override;
+
+ private:
+ ndk::ScopedAStatus on(uint32_t timeoutMs, const char mode[],
+ const std::unique_ptr<VibrationConfig> &config);
+ ndk::ScopedAStatus performEffect(Effect effect, EffectStrength strength, int32_t *outTimeMs);
+
+ std::unique_ptr<HwApi> mHwApi;
+ std::unique_ptr<HwCal> mHwCal;
+ uint32_t mCloseLoopThreshold;
+ std::unique_ptr<VibrationConfig> mSteadyConfig;
+ std::unique_ptr<VibrationConfig> mEffectConfig;
+ uint32_t mClickDuration;
+ uint32_t mTickDuration;
+ uint32_t mDoubleClickDuration;
+ uint32_t mHeavyClickDuration;
+};
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/drv2624/android.hardware.vibrator-service.drv2624.rc b/vibrator/drv2624/android.hardware.vibrator-service.drv2624.rc
new file mode 100644
index 0000000..fe3e548
--- /dev/null
+++ b/vibrator/drv2624/android.hardware.vibrator-service.drv2624.rc
@@ -0,0 +1,15 @@
+service vendor.vibrator.drv2624 /vendor/bin/hw/android.hardware.vibrator-service.drv2624
+ class hal
+ user system
+ group system
+
+ setenv PROPERTY_PREFIX ro.vibrator.hal.
+ setenv CALIBRATION_FILEPATH /persist/haptics/drv2624.cal
+
+ setenv HWAPI_PATH_PREFIX /sys/class/leds/vibrator/
+ setenv HWAPI_DEBUG_PATHS "
+ device/autocal
+ device/lp_trigger_effect
+ device/ol_lra_period
+ state
+ "
diff --git a/vibrator/drv2624/android.hardware.vibrator-service.drv2624.xml b/vibrator/drv2624/android.hardware.vibrator-service.drv2624.xml
new file mode 100644
index 0000000..49b11ec
--- /dev/null
+++ b/vibrator/drv2624/android.hardware.vibrator-service.drv2624.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.vibrator</name>
+ <fqname>IVibrator/default</fqname>
+ </hal>
+</manifest>
diff --git a/vibrator/drv2624/bench/Android.bp b/vibrator/drv2624/bench/Android.bp
new file mode 100644
index 0000000..6227dc5
--- /dev/null
+++ b/vibrator/drv2624/bench/Android.bp
@@ -0,0 +1,32 @@
+//
+// 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.
+
+cc_benchmark {
+ name: "VibratorHalDrv2624Benchmark",
+ defaults: ["VibratorHalDrv2624TestDefaults"],
+ srcs: [
+ "benchmark.cpp",
+ ],
+ static_libs: [
+ "libc++fs",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+ // TODO(b/135767253): Remove when fixed.
+ test_suites: ["device-tests"],
+ // TODO(b/142024316): Remove when fixed.
+ require_root: true,
+}
diff --git a/vibrator/drv2624/bench/benchmark.cpp b/vibrator/drv2624/bench/benchmark.cpp
new file mode 100644
index 0000000..7a425b3
--- /dev/null
+++ b/vibrator/drv2624/bench/benchmark.cpp
@@ -0,0 +1,188 @@
+/* * 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 "benchmark/benchmark.h"
+
+#include <android-base/file.h>
+#include <android-base/properties.h>
+#include <cutils/fs.h>
+
+#include "Hardware.h"
+#include "Vibrator.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::android::base::SetProperty;
+
+class VibratorBench : public benchmark::Fixture {
+ private:
+ static constexpr const char *FILE_NAMES[]{
+ "device/autocal",
+ "device/ol_lra_period",
+ "activate",
+ "duration",
+ "state",
+ "device/rtp_input",
+ "device/mode",
+ "device/set_sequencer",
+ "device/scale",
+ "device/ctrl_loop",
+ "device/lp_trigger_effect",
+ "device/lra_wave_shape",
+ "device/od_clamp",
+ };
+ static constexpr char PROPERTY_PREFIX[] = "test.vibrator.hal.";
+
+ public:
+ void SetUp(::benchmark::State &state) override {
+ auto prefix = std::filesystem::path(mFilesDir.path) / "";
+
+ setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+
+ for (auto n : FILE_NAMES) {
+ const auto name = std::filesystem::path(n);
+ const auto path = std::filesystem::path(mFilesDir.path) / name;
+
+ fs_mkdirs(path.c_str(), S_IRWXU);
+ symlink("/dev/null", path.c_str());
+ }
+
+ setenv("PROPERTY_PREFIX", PROPERTY_PREFIX, true);
+
+ SetProperty(std::string() + PROPERTY_PREFIX + "config.dynamic", getDynamicConfig(state));
+
+ mVibrator = ndk::SharedRefBase::make<Vibrator>(HwApi::Create(), std::make_unique<HwCal>());
+ }
+
+ static void DefaultConfig(benchmark::internal::Benchmark *b) {
+ b->Unit(benchmark::kMicrosecond);
+ }
+
+ static void DefaultArgs(benchmark::internal::Benchmark *b) {
+ b->ArgNames({"DynamicConfig"});
+ b->Args({false});
+ b->Args({true});
+ }
+
+ protected:
+ std::string getDynamicConfig(const ::benchmark::State &state) const {
+ return std::to_string(state.range(0));
+ }
+
+ auto getOtherArg(const ::benchmark::State &state, std::size_t index) const {
+ return state.range(index + 1);
+ }
+
+ protected:
+ TemporaryDir mFilesDir;
+ std::shared_ptr<IVibrator> mVibrator;
+};
+
+#define BENCHMARK_WRAPPER(fixt, test, code) \
+ BENCHMARK_DEFINE_F(fixt, test) \
+ /* NOLINTNEXTLINE */ \
+ (benchmark::State & state){code} BENCHMARK_REGISTER_F(fixt, test) \
+ ->Apply(fixt::DefaultConfig) \
+ ->Apply(fixt::DefaultArgs)
+
+BENCHMARK_WRAPPER(VibratorBench, on, {
+ uint32_t duration = std::rand() ?: 1;
+
+ for (auto _ : state) {
+ mVibrator->on(duration, nullptr);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, off, {
+ for (auto _ : state) {
+ mVibrator->off();
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, setAmplitude, {
+ uint8_t amplitude = std::rand() ?: 1;
+
+ for (auto _ : state) {
+ mVibrator->setAmplitude(amplitude);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, setExternalControl_enable, {
+ for (auto _ : state) {
+ mVibrator->setExternalControl(true);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, setExternalControl_disable, {
+ for (auto _ : state) {
+ mVibrator->setExternalControl(false);
+ }
+});
+
+BENCHMARK_WRAPPER(VibratorBench, getCapabilities, {
+ int32_t capabilities;
+
+ for (auto _ : state) {
+ mVibrator->getCapabilities(&capabilities);
+ }
+});
+
+class VibratorEffectsBench : public VibratorBench {
+ public:
+ static void DefaultArgs(benchmark::internal::Benchmark *b) {
+ b->ArgNames({"DynamicConfig", "Effect", "Strength"});
+ for (const auto &dynamic : {false, true}) {
+ for (const auto &effect : ndk::enum_range<Effect>()) {
+ for (const auto &strength : ndk::enum_range<EffectStrength>()) {
+ b->Args({dynamic, static_cast<long>(effect), static_cast<long>(strength)});
+ }
+ }
+ }
+ }
+
+ protected:
+ auto getEffect(const ::benchmark::State &state) const {
+ return static_cast<Effect>(getOtherArg(state, 0));
+ }
+
+ auto getStrength(const ::benchmark::State &state) const {
+ return static_cast<EffectStrength>(getOtherArg(state, 1));
+ }
+};
+
+BENCHMARK_WRAPPER(VibratorEffectsBench, perform, {
+ Effect effect = getEffect(state);
+ EffectStrength strength = getStrength(state);
+ int32_t lengthMs;
+
+ ndk::ScopedAStatus status = mVibrator->perform(effect, strength, nullptr, &lengthMs);
+
+ if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
+ return;
+ }
+
+ for (auto _ : state) {
+ mVibrator->perform(effect, strength, nullptr, &lengthMs);
+ }
+});
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
+
+BENCHMARK_MAIN();
diff --git a/vibrator/drv2624/device.mk b/vibrator/drv2624/device.mk
new file mode 100644
index 0000000..1a3e184
--- /dev/null
+++ b/vibrator/drv2624/device.mk
@@ -0,0 +1,5 @@
+PRODUCT_PACKAGES += \
+ android.hardware.vibrator-service.drv2624 \
+
+BOARD_SEPOLICY_DIRS += \
+ hardware/google/pixel-sepolicy/vibrator/drv2624 \
diff --git a/vibrator/drv2624/service.cpp b/vibrator/drv2624/service.cpp
new file mode 100644
index 0000000..d4287a0
--- /dev/null
+++ b/vibrator/drv2624/service.cpp
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "Hardware.h"
+#include "Vibrator.h"
+
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <log/log.h>
+
+using aidl::android::hardware::vibrator::HwApi;
+using aidl::android::hardware::vibrator::HwCal;
+using aidl::android::hardware::vibrator::Vibrator;
+
+int main() {
+ auto hwapi = HwApi::Create();
+
+ if (!hwapi) {
+ return EXIT_FAILURE;
+ }
+
+ ABinderProcess_setThreadPoolMaxThreadCount(0);
+ std::shared_ptr<Vibrator> vib =
+ ndk::SharedRefBase::make<Vibrator>(std::move(hwapi), std::make_unique<HwCal>());
+
+ const std::string instance = std::string() + Vibrator::descriptor + "/default";
+ binder_status_t status = AServiceManager_addService(vib->asBinder().get(), instance.c_str());
+ LOG_ALWAYS_FATAL_IF(status != STATUS_OK);
+
+ ABinderProcess_joinThreadPool();
+ return EXIT_FAILURE; // should not reach
+}
diff --git a/vibrator/drv2624/tests/Android.bp b/vibrator/drv2624/tests/Android.bp
new file mode 100644
index 0000000..4c85740
--- /dev/null
+++ b/vibrator/drv2624/tests/Android.bp
@@ -0,0 +1,31 @@
+//
+// 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.
+
+cc_test {
+ name: "VibratorHalDrv2624TestSuite",
+ defaults: ["VibratorHalDrv2624TestDefaults"],
+ srcs: [
+ "test-hwapi.cpp",
+ "test-hwcal.cpp",
+ "test-vibrator.cpp",
+ ],
+ static_libs: [
+ "libc++fs",
+ "libgmock",
+ ],
+ shared_libs: [
+ "libbase",
+ ],
+}
diff --git a/vibrator/drv2624/tests/mocks.h b/vibrator/drv2624/tests/mocks.h
new file mode 100644
index 0000000..bbb4408
--- /dev/null
+++ b/vibrator/drv2624/tests/mocks.h
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
+
+#include "Vibrator.h"
+
+class MockApi : public ::aidl::android::hardware::vibrator::Vibrator::HwApi {
+ public:
+ MOCK_METHOD0(destructor, void());
+ MOCK_METHOD1(setAutocal, bool(std::string value));
+ MOCK_METHOD1(setOlLraPeriod, bool(uint32_t value));
+ MOCK_METHOD1(setActivate, bool(bool value));
+ MOCK_METHOD1(setDuration, bool(uint32_t value));
+ MOCK_METHOD1(setState, bool(bool value));
+ MOCK_METHOD0(hasRtpInput, bool());
+ MOCK_METHOD1(setRtpInput, bool(int8_t value));
+ MOCK_METHOD1(setMode, bool(std::string value));
+ MOCK_METHOD1(setSequencer, bool(std::string value));
+ MOCK_METHOD1(setScale, bool(uint8_t value));
+ MOCK_METHOD1(setCtrlLoop, bool(bool value));
+ MOCK_METHOD1(setLpTriggerEffect, bool(uint32_t value));
+ MOCK_METHOD1(setLraWaveShape, bool(uint32_t value));
+ MOCK_METHOD1(setOdClamp, bool(uint32_t value));
+ 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(getAutocal, bool(std::string &value)); // NOLINT
+ MOCK_METHOD1(getLraPeriod, bool(uint32_t *value));
+ MOCK_METHOD1(getCloseLoopThreshold, bool(uint32_t *value));
+ MOCK_METHOD1(getDynamicConfig, bool(bool *value));
+ MOCK_METHOD1(getLongFrequencyShift, bool(uint32_t *value));
+ MOCK_METHOD1(getShortVoltageMax, bool(uint32_t *value));
+ MOCK_METHOD1(getLongVoltageMax, bool(uint32_t *value));
+ MOCK_METHOD1(getClickDuration, bool(uint32_t *value));
+ MOCK_METHOD1(getTickDuration, bool(uint32_t *value));
+ MOCK_METHOD1(getDoubleClickDuration, bool(uint32_t *value));
+ MOCK_METHOD1(getHeavyClickDuration, bool(uint32_t *value));
+ MOCK_METHOD1(debug, void(int fd));
+
+ ~MockCal() override { destructor(); };
+ // b/132668253: Workaround gMock Compilation Issue
+ bool getAutocal(std::string *value) { return getAutocal(*value); }
+};
+
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_MOCKS_H
diff --git a/vibrator/drv2624/tests/test-hwapi.cpp b/vibrator/drv2624/tests/test-hwapi.cpp
new file mode 100644
index 0000000..b58e02b
--- /dev/null
+++ b/vibrator/drv2624/tests/test-hwapi.cpp
@@ -0,0 +1,402 @@
+/*
+ * 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 <android-base/file.h>
+#include <cutils/fs.h>
+#include <gtest/gtest.h>
+
+#include <cstdlib>
+#include <fstream>
+
+#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 {
+ protected:
+ static constexpr const char *FILE_NAMES[]{
+ "device/autocal",
+ "device/ol_lra_period",
+ "activate",
+ "duration",
+ "state",
+ "device/rtp_input",
+ "device/mode",
+ "device/set_sequencer",
+ "device/scale",
+ "device/ctrl_loop",
+ "device/lp_trigger_effect",
+ "device/lra_wave_shape",
+ "device/od_clamp",
+ };
+
+ static constexpr const char *REQUIRED[]{
+ "activate",
+ "duration",
+ "state",
+ };
+
+ 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 = HwApi::Create();
+
+ for (auto n : REQUIRED) {
+ auto name = std::filesystem::path(n);
+ auto path = std::filesystem::path(mEmptyDir.path) / name;
+ fs_mkdirs(path.c_str(), S_IRWXU);
+ std::ofstream touch{path};
+ }
+ prefix = std::filesystem::path(mEmptyDir.path) / "";
+ setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+ mNoApi = HwApi::Create();
+ }
+
+ void TearDown() override { verifyContents(); }
+
+ protected:
+ // Set expected file content for a test.
+ template <typename T>
+ void expectContent(const std::string &name, const T &value) {
+ mExpectedContent[name] << value << std::endl;
+ }
+
+ // Set actual file content for an input test.
+ template <typename T>
+ void updateContent(const std::string &name, const T &value) {
+ std::ofstream(mFileMap[name]) << value << std::endl;
+ }
+
+ template <typename T>
+ 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<char>(file),
+ std::istreambuf_iterator<char>());
+ EXPECT_EQ(expect, actual) << a.first;
+ }
+ }
+
+ // TODO(eliptus): Determine how to induce errors in required files
+ static bool isRequired(const std::string &name) {
+ for (auto n : REQUIRED) {
+ if (std::string(n) == name) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ static auto ParamNameFixup(std::string str) {
+ std::replace(str.begin(), str.end(), '/', '_');
+ return str;
+ }
+
+ protected:
+ std::unique_ptr<Vibrator::HwApi> mHwApi;
+ std::unique_ptr<Vibrator::HwApi> mNoApi;
+ std::map<std::string, std::string> mFileMap;
+ TemporaryDir mFilesDir;
+ TemporaryDir mEmptyDir;
+ std::map<std::string, std::stringstream> mExpectedContent;
+};
+
+class CreateTest : public HwApiTest, public WithParamInterface<const char *> {
+ public:
+ void SetUp() override{};
+ void TearDown() override{};
+
+ static auto PrintParam(const TestParamInfo<CreateTest::ParamType> &info) {
+ return ParamNameFixup(info.param);
+ }
+ static auto &AllParams() { return FILE_NAMES; }
+};
+
+TEST_P(CreateTest, file_missing) {
+ auto skip = std::string(GetParam());
+ TemporaryDir dir;
+ std::unique_ptr<HwApi> hwapi;
+ std::string prefix;
+
+ for (auto n : FILE_NAMES) {
+ auto name = std::string(n);
+ auto path = std::string(dir.path) + "/" + name;
+ if (name == skip) {
+ continue;
+ }
+ fs_mkdirs(path.c_str(), S_IRWXU);
+ std::ofstream touch{path};
+ }
+
+ prefix = std::filesystem::path(dir.path) / "";
+ setenv("HWAPI_PATH_PREFIX", prefix.c_str(), true);
+ hwapi = HwApi::Create();
+ if (isRequired(skip)) {
+ EXPECT_EQ(nullptr, hwapi);
+ } else {
+ EXPECT_NE(nullptr, hwapi);
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, CreateTest, ValuesIn(CreateTest::AllParams()),
+ CreateTest::PrintParam);
+
+template <typename T>
+class HwApiTypedTest : public HwApiTest,
+ public WithParamInterface<std::tuple<std::string, std::function<T>>> {
+ public:
+ static auto PrintParam(const TestParamInfo<typename HwApiTypedTest::ParamType> &info) {
+ return ParamNameFixup(std::get<0>(info.param));
+ }
+ static auto MakeParam(std::string name, std::function<T> func) {
+ return std::make_tuple(name, func);
+ }
+};
+
+using HasTest = HwApiTypedTest<bool(Vibrator::HwApi &)>;
+
+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("device/rtp_input",
+ &Vibrator::HwApi::hasRtpInput),
+ }),
+ HasTest::PrintParam);
+
+using SetBoolTest = HwApiTypedTest<bool(Vibrator::HwApi &, bool)>;
+
+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 name = std::get<0>(param);
+ auto func = std::get<1>(param);
+
+ if (isRequired(name)) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(func(*mNoApi, true));
+ EXPECT_FALSE(func(*mNoApi, false));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetBoolTest,
+ ValuesIn({
+ SetBoolTest::MakeParam("activate", &Vibrator::HwApi::setActivate),
+ SetBoolTest::MakeParam("state", &Vibrator::HwApi::setState),
+ SetBoolTest::MakeParam("device/ctrl_loop",
+ &Vibrator::HwApi::setCtrlLoop),
+ }),
+ SetBoolTest::PrintParam);
+
+using SetInt8Test = HwApiTypedTest<bool(Vibrator::HwApi &, int8_t)>;
+
+TEST_P(SetInt8Test, success) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ int8_t value = std::rand();
+
+ expectContent(name, +value);
+
+ EXPECT_TRUE(func(*mHwApi, value));
+}
+
+TEST_P(SetInt8Test, failure) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ int8_t value = std::rand();
+
+ if (isRequired(name)) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetInt8Test,
+ ValuesIn({
+ SetInt8Test::MakeParam("device/rtp_input",
+ &Vibrator::HwApi::setRtpInput),
+ }),
+ SetInt8Test::PrintParam);
+
+using SetUint8Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint8_t)>;
+
+TEST_P(SetUint8Test, success) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ uint8_t value = std::rand();
+
+ expectContent(name, +value);
+
+ EXPECT_TRUE(func(*mHwApi, value));
+}
+
+TEST_P(SetUint8Test, failure) {
+ auto param = GetParam();
+ auto name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ uint8_t value = std::rand();
+
+ if (isRequired(name)) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(HwApiTests, SetUint8Test,
+ ValuesIn({
+ SetUint8Test::MakeParam("device/scale", &Vibrator::HwApi::setScale),
+ }),
+ SetUint8Test::PrintParam);
+
+using SetUint32Test = HwApiTypedTest<bool(Vibrator::HwApi &, uint32_t)>;
+
+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 name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ uint32_t value = std::rand();
+
+ if (isRequired(name)) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ HwApiTests, SetUint32Test,
+ ValuesIn({
+ SetUint32Test::MakeParam("device/ol_lra_period", &Vibrator::HwApi::setOlLraPeriod),
+ SetUint32Test::MakeParam("duration", &Vibrator::HwApi::setDuration),
+ SetUint32Test::MakeParam("device/lp_trigger_effect",
+ &Vibrator::HwApi::setLpTriggerEffect),
+ SetUint32Test::MakeParam("device/lra_wave_shape",
+ &Vibrator::HwApi::setLraWaveShape),
+ SetUint32Test::MakeParam("device/od_clamp", &Vibrator::HwApi::setOdClamp),
+ }),
+ SetUint32Test::PrintParam);
+
+using SetStringTest = HwApiTypedTest<bool(Vibrator::HwApi &, std::string)>;
+
+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 name = std::get<0>(param);
+ auto func = std::get<1>(param);
+ std::string value = TemporaryFile().path;
+
+ if (isRequired(name)) {
+ GTEST_SKIP();
+ }
+
+ EXPECT_FALSE(func(*mNoApi, value));
+}
+
+INSTANTIATE_TEST_CASE_P(
+ HwApiTests, SetStringTest,
+ ValuesIn({
+ SetStringTest::MakeParam("device/autocal", &Vibrator::HwApi::setAutocal),
+ SetStringTest::MakeParam("device/mode", &Vibrator::HwApi::setMode),
+ SetStringTest::MakeParam("device/set_sequencer", &Vibrator::HwApi::setSequencer),
+ }),
+ SetStringTest::PrintParam);
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/drv2624/tests/test-hwcal.cpp b/vibrator/drv2624/tests/test-hwcal.cpp
new file mode 100644
index 0000000..50fe373
--- /dev/null
+++ b/vibrator/drv2624/tests/test-hwcal.cpp
@@ -0,0 +1,394 @@
+/*
+ * 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 <android-base/file.h>
+#include <android-base/properties.h>
+#include <gtest/gtest.h>
+
+#include <fstream>
+
+#include "Hardware.h"
+
+namespace aidl {
+namespace android {
+namespace hardware {
+namespace vibrator {
+
+using ::android::base::SetProperty;
+using ::android::base::WaitForProperty;
+
+using ::testing::Test;
+
+class HwCalTest : public Test {
+ protected:
+ static constexpr char PROPERTY_PREFIX[] = "test.vibrator.hal.";
+
+ static constexpr uint32_t DEFAULT_LRA_PERIOD = 262;
+
+ static constexpr uint32_t DEFAULT_FREQUENCY_SHIFT = 10;
+ static constexpr uint32_t DEFAULT_VOLTAGE_MAX = 107;
+
+ static constexpr uint32_t DEFAULT_CLICK_DURATION_MS = 6;
+ static constexpr uint32_t DEFAULT_TICK_DURATION_MS = 2;
+ static constexpr uint32_t DEFAULT_DOUBLE_CLICK_DURATION_MS = 135;
+ static constexpr uint32_t DEFAULT_HEAVY_CLICK_DURATION_MS = 8;
+
+ public:
+ void SetUp() override {
+ setenv("PROPERTY_PREFIX", PROPERTY_PREFIX, true);
+ setenv("CALIBRATION_FILEPATH", mCalFile.path, true);
+ }
+
+ private:
+ template <typename T>
+ static void pack(std::ostream &stream, const T &value, std::string lpad, std::string rpad) {
+ stream << lpad << value << rpad;
+ }
+
+ protected:
+ void createHwCal() { mHwCal = std::make_unique<HwCal>(); }
+
+ template <typename T>
+ 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<Vibrator::HwCal> mHwCal;
+ TemporaryFile mCalFile;
+};
+
+TEST_F(HwCalTest, closeloop_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "closeloop.threshold", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getCloseLoopThreshold(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, closeloop_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = UINT32_MAX;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "closeloop.threshold", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getCloseLoopThreshold(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, dynamicconfig_presentFalse) {
+ std::string prefix{PROPERTY_PREFIX};
+ bool expect = false;
+ bool actual = !expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "config.dynamic", "0"));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getDynamicConfig(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, dynamicconfig_presentTrue) {
+ std::string prefix{PROPERTY_PREFIX};
+ bool expect = true;
+ bool actual = !expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "config.dynamic", "1"));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getDynamicConfig(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, dynamicconfig_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ bool expect = false;
+ bool actual = !expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "config.dynamic", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getDynamicConfig(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, freqshift_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "long.frequency.shift", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLongFrequencyShift(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, freqshift_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_FREQUENCY_SHIFT;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "long.frequency.shift", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLongFrequencyShift(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, shortvolt_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "short.voltage", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getShortVoltageMax(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, shortvolt_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_VOLTAGE_MAX;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "short.voltage", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getShortVoltageMax(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, longvolt_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "long.voltage", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLongVoltageMax(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, longvolt_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_VOLTAGE_MAX;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "long.voltage", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLongVoltageMax(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, click_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "click.duration", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getClickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, click_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_CLICK_DURATION_MS;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "click.duration", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getClickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, tick_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "tick.duration", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, tick_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_TICK_DURATION_MS;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "tick.duration", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getTickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, doubleclick) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_DOUBLE_CLICK_DURATION_MS;
+ uint32_t actual = ~expect;
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getDoubleClickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, heavyclick_present) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "heavyclick.duration", std::to_string(expect)));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getHeavyClickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, heavyclick_missing) {
+ std::string prefix{PROPERTY_PREFIX};
+ uint32_t expect = DEFAULT_HEAVY_CLICK_DURATION_MS;
+ uint32_t actual = ~expect;
+
+ EXPECT_TRUE(SetProperty(prefix + "heavyclick.duration", std::string()));
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getHeavyClickDuration(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, autocal_present) {
+ std::string expect = std::to_string(std::rand()) + " " + std::to_string(std::rand()) + " " +
+ std::to_string(std::rand());
+ std::string actual = "";
+
+ write("autocal", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getAutocal(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, autocal_missing) {
+ std::string actual;
+
+ createHwCal();
+
+ EXPECT_FALSE(mHwCal->getAutocal(&actual));
+}
+
+TEST_F(HwCalTest, lra_period_present) {
+ uint32_t expect = std::rand();
+ uint32_t actual = ~expect;
+
+ write("lra_period", expect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLraPeriod(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, lra_period_missing) {
+ uint32_t expect = DEFAULT_LRA_PERIOD;
+ uint32_t actual = ~expect;
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getLraPeriod(&actual));
+ EXPECT_EQ(expect, actual);
+}
+
+TEST_F(HwCalTest, multiple) {
+ std::string autocalExpect = std::to_string(std::rand()) + " " + std::to_string(std::rand()) +
+ " " + std::to_string(std::rand());
+ std::string autocalActual = "";
+ uint32_t lraPeriodExpect = std::rand();
+ uint32_t lraPeriodActual = ~lraPeriodExpect;
+
+ write("autocal", autocalExpect);
+ write("lra_period", lraPeriodExpect);
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getAutocal(&autocalActual));
+ EXPECT_EQ(autocalExpect, autocalActual);
+ EXPECT_TRUE(mHwCal->getLraPeriod(&lraPeriodActual));
+ EXPECT_EQ(lraPeriodExpect, lraPeriodActual);
+}
+
+TEST_F(HwCalTest, trimming) {
+ std::string autocalExpect = std::to_string(std::rand()) + " " + std::to_string(std::rand()) +
+ " " + std::to_string(std::rand());
+ std::string autocalActual = "";
+ uint32_t lraPeriodExpect = std::rand();
+ uint32_t lraPeriodActual = ~lraPeriodExpect;
+
+ write("autocal", autocalExpect, " \t", "\t ");
+ write("lra_period", lraPeriodExpect, " \t", "\t ");
+
+ createHwCal();
+
+ EXPECT_TRUE(mHwCal->getAutocal(&autocalActual));
+ EXPECT_EQ(autocalExpect, autocalActual);
+ EXPECT_TRUE(mHwCal->getLraPeriod(&lraPeriodActual));
+ EXPECT_EQ(lraPeriodExpect, lraPeriodActual);
+}
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/drv2624/tests/test-vibrator.cpp b/vibrator/drv2624/tests/test-vibrator.cpp
new file mode 100644
index 0000000..2b952ce
--- /dev/null
+++ b/vibrator/drv2624/tests/test-vibrator.cpp
@@ -0,0 +1,453 @@
+/*
+ * 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 <android-base/logging.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#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::AnyOf;
+using ::testing::Assign;
+using ::testing::Combine;
+using ::testing::DoAll;
+using ::testing::DoDefault;
+using ::testing::Exactly;
+using ::testing::ExpectationSet;
+using ::testing::Mock;
+using ::testing::Return;
+using ::testing::Sequence;
+using ::testing::SetArgPointee;
+using ::testing::SetArgReferee;
+using ::testing::Test;
+using ::testing::TestParamInfo;
+using ::testing::ValuesIn;
+using ::testing::WithParamInterface;
+
+// Constants With Prescribed Values
+
+static const std::map<EffectTuple, EffectSequence> EFFECT_SEQUENCES{
+ {{Effect::CLICK, EffectStrength::LIGHT}, {"1 0", 2}},
+ {{Effect::CLICK, EffectStrength::MEDIUM}, {"1 0", 0}},
+ {{Effect::CLICK, EffectStrength::STRONG}, {"1 0", 0}},
+ {{Effect::TICK, EffectStrength::LIGHT}, {"2 0", 2}},
+ {{Effect::TICK, EffectStrength::MEDIUM}, {"2 0", 0}},
+ {{Effect::TICK, EffectStrength::STRONG}, {"2 0", 0}},
+ {{Effect::DOUBLE_CLICK, EffectStrength::LIGHT}, {"3 0", 2}},
+ {{Effect::DOUBLE_CLICK, EffectStrength::MEDIUM}, {"3 0", 0}},
+ {{Effect::DOUBLE_CLICK, EffectStrength::STRONG}, {"3 0", 0}},
+ {{Effect::HEAVY_CLICK, EffectStrength::LIGHT}, {"4 0", 2}},
+ {{Effect::HEAVY_CLICK, EffectStrength::MEDIUM}, {"4 0", 0}},
+ {{Effect::HEAVY_CLICK, EffectStrength::STRONG}, {"4 0", 0}},
+ {{Effect::TEXTURE_TICK, EffectStrength::LIGHT}, {"2 0", 2}},
+ {{Effect::TEXTURE_TICK, EffectStrength::MEDIUM}, {"2 0", 0}},
+ {{Effect::TEXTURE_TICK, EffectStrength::STRONG}, {"2 0", 0}},
+};
+
+static uint32_t freqPeriodFormula(uint32_t in) {
+ return 1000000000 / (24615 * in);
+}
+
+template <typename... T>
+class VibratorTestTemplate : public Test, public WithParamInterface<std::tuple<bool, T...>> {
+ public:
+ static auto GetDynamicConfig(typename VibratorTestTemplate::ParamType param) {
+ return std::get<0>(param);
+ }
+ template <std::size_t I>
+ static auto GetOtherParam(typename VibratorTestTemplate::ParamType param) {
+ return std::get<I + 1>(param);
+ }
+
+ static auto PrintParam(const TestParamInfo<typename VibratorTestTemplate::ParamType> &info) {
+ auto dynamic = GetDynamicConfig(info.param);
+ return std::string() + (dynamic ? "Dynamic" : "Static") + "Config";
+ }
+
+ static auto MakeParam(bool dynamicConfig, T... others) {
+ return std::make_tuple(dynamicConfig, others...);
+ }
+
+ void SetUp() override {
+ std::unique_ptr<MockApi> mockapi;
+ std::unique_ptr<MockCal> mockcal;
+
+ mCloseLoopThreshold = std::rand();
+ // ensure close-loop test is possible
+ if (mCloseLoopThreshold == UINT32_MAX) {
+ mCloseLoopThreshold--;
+ }
+
+ mShortLraPeriod = std::rand();
+ if (getDynamicConfig()) {
+ mLongFrequencyShift = std::rand();
+ mLongLraPeriod =
+ freqPeriodFormula(freqPeriodFormula(mShortLraPeriod) - mLongFrequencyShift);
+ mShortVoltageMax = std::rand();
+ mLongVoltageMax = std::rand();
+ }
+
+ mEffectDurations[Effect::CLICK] = std::rand();
+ mEffectDurations[Effect::TICK] = std::rand();
+ mEffectDurations[Effect::DOUBLE_CLICK] = std::rand();
+ mEffectDurations[Effect::HEAVY_CLICK] = std::rand();
+ mEffectDurations[Effect::TEXTURE_TICK] = mEffectDurations[Effect::TICK];
+
+ createMock(&mockapi, &mockcal);
+ createVibrator(std::move(mockapi), std::move(mockcal));
+ }
+
+ void TearDown() override { deleteVibrator(); }
+
+ protected:
+ auto getDynamicConfig() const { return GetDynamicConfig(VibratorTestTemplate::GetParam()); }
+
+ void createMock(std::unique_ptr<MockApi> *mockapi, std::unique_ptr<MockCal> *mockcal) {
+ *mockapi = std::make_unique<MockApi>();
+ *mockcal = std::make_unique<MockCal>();
+
+ mMockApi = mockapi->get();
+ mMockCal = mockcal->get();
+
+ ON_CALL(*mMockApi, destructor()).WillByDefault(Assign(&mMockApi, nullptr));
+ ON_CALL(*mMockApi, setOlLraPeriod(_)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setActivate(_)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setDuration(_)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setMode(_)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setCtrlLoop(_)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setLraWaveShape(_)).WillByDefault(Return(true));
+ ON_CALL(*mMockApi, setOdClamp(_)).WillByDefault(Return(true));
+
+ ON_CALL(*mMockCal, destructor()).WillByDefault(Assign(&mMockCal, nullptr));
+ ON_CALL(*mMockCal, getLraPeriod(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mShortLraPeriod), Return(true)));
+ ON_CALL(*mMockCal, getCloseLoopThreshold(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mCloseLoopThreshold), Return(true)));
+ ON_CALL(*mMockCal, getDynamicConfig(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(getDynamicConfig()), Return(true)));
+
+ if (getDynamicConfig()) {
+ ON_CALL(*mMockCal, getLongFrequencyShift(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mLongFrequencyShift), Return(true)));
+ ON_CALL(*mMockCal, getShortVoltageMax(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mShortVoltageMax), Return(true)));
+ ON_CALL(*mMockCal, getLongVoltageMax(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mLongVoltageMax), Return(true)));
+ }
+
+ ON_CALL(*mMockCal, getClickDuration(_))
+ .WillByDefault(
+ DoAll(SetArgPointee<0>(mEffectDurations[Effect::CLICK]), Return(true)));
+ ON_CALL(*mMockCal, getTickDuration(_))
+ .WillByDefault(
+ DoAll(SetArgPointee<0>(mEffectDurations[Effect::TICK]), Return(true)));
+ ON_CALL(*mMockCal, getDoubleClickDuration(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mEffectDurations[Effect::DOUBLE_CLICK]),
+ Return(true)));
+ ON_CALL(*mMockCal, getHeavyClickDuration(_))
+ .WillByDefault(DoAll(SetArgPointee<0>(mEffectDurations[Effect::HEAVY_CLICK]),
+ Return(true)));
+
+ relaxMock(false);
+ }
+
+ void createVibrator(std::unique_ptr<MockApi> mockapi, std::unique_ptr<MockCal> mockcal,
+ bool relaxed = true) {
+ if (relaxed) {
+ relaxMock(true);
+ }
+ mVibrator = ndk::SharedRefBase::make<Vibrator>(std::move(mockapi), std::move(mockcal));
+ if (relaxed) {
+ relaxMock(false);
+ }
+ }
+
+ void deleteVibrator(bool relaxed = true) {
+ if (relaxed) {
+ relaxMock(true);
+ }
+ mVibrator.reset();
+ }
+
+ 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, setAutocal(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setOlLraPeriod(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setActivate(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setDuration(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setState(_)).Times(times);
+ EXPECT_CALL(*mMockApi, hasRtpInput()).Times(times);
+ EXPECT_CALL(*mMockApi, setRtpInput(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setMode(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setSequencer(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setScale(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setCtrlLoop(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setLpTriggerEffect(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setLraWaveShape(_)).Times(times);
+ EXPECT_CALL(*mMockApi, setOdClamp(_)).Times(times);
+ EXPECT_CALL(*mMockApi, debug(_)).Times(times);
+
+ EXPECT_CALL(*mMockCal, destructor()).Times(times);
+ EXPECT_CALL(*mMockCal, getAutocal(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getLraPeriod(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getCloseLoopThreshold(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getDynamicConfig(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getShortVoltageMax(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getLongVoltageMax(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getClickDuration(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getTickDuration(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getDoubleClickDuration(_)).Times(times);
+ EXPECT_CALL(*mMockCal, getHeavyClickDuration(_)).Times(times);
+ EXPECT_CALL(*mMockCal, debug(_)).Times(times);
+ }
+
+ protected:
+ MockApi *mMockApi;
+ MockCal *mMockCal;
+ std::shared_ptr<IVibrator> mVibrator;
+
+ EffectDuration mCloseLoopThreshold;
+ uint32_t mLongFrequencyShift;
+ uint32_t mShortLraPeriod;
+ uint32_t mLongLraPeriod;
+ uint32_t mShortVoltageMax;
+ uint32_t mLongVoltageMax;
+ std::map<Effect, EffectDuration> mEffectDurations;
+};
+
+using BasicTest = VibratorTestTemplate<>;
+
+TEST_P(BasicTest, Constructor) {
+ std::unique_ptr<MockApi> mockapi;
+ std::unique_ptr<MockCal> mockcal;
+ std::string autocalVal = std::to_string(std::rand()) + " " + std::to_string(std::rand()) + " " +
+ std::to_string(std::rand());
+ Sequence autocalSeq, lraPeriodSeq;
+
+ EXPECT_CALL(*mMockApi, destructor()).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, destructor()).WillOnce(DoDefault());
+
+ deleteVibrator(false);
+
+ createMock(&mockapi, &mockcal);
+
+ EXPECT_CALL(*mMockApi, setState(true)).WillOnce(Return(true));
+
+ EXPECT_CALL(*mMockCal, getAutocal(_))
+ .InSequence(autocalSeq)
+ .WillOnce(DoAll(SetArgReferee<0>(autocalVal), Return(true)));
+ EXPECT_CALL(*mMockApi, setAutocal(autocalVal)).InSequence(autocalSeq).WillOnce(DoDefault());
+
+ EXPECT_CALL(*mMockCal, getLraPeriod(_)).InSequence(lraPeriodSeq).WillOnce(DoDefault());
+
+ EXPECT_CALL(*mMockCal, getCloseLoopThreshold(_)).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, getDynamicConfig(_)).WillOnce(DoDefault());
+
+ if (getDynamicConfig()) {
+ EXPECT_CALL(*mMockCal, getLongFrequencyShift(_)).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, getShortVoltageMax(_)).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, getLongVoltageMax(_)).WillOnce(DoDefault());
+ } else {
+ EXPECT_CALL(*mMockApi, setOlLraPeriod(mShortLraPeriod))
+ .InSequence(lraPeriodSeq)
+ .WillOnce(DoDefault());
+ }
+
+ EXPECT_CALL(*mMockCal, getClickDuration(_)).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, getTickDuration(_)).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, getDoubleClickDuration(_)).WillOnce(DoDefault());
+ EXPECT_CALL(*mMockCal, getHeavyClickDuration(_)).WillOnce(DoDefault());
+
+ EXPECT_CALL(*mMockApi, setLpTriggerEffect(1)).WillOnce(Return(true));
+
+ createVibrator(std::move(mockapi), std::move(mockcal), false);
+}
+
+TEST_P(BasicTest, on) {
+ EffectDuration duration = std::rand();
+ ExpectationSet e;
+
+ e += EXPECT_CALL(*mMockApi, setCtrlLoop(_)).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setMode("rtp")).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setDuration(duration)).WillOnce(DoDefault());
+
+ if (getDynamicConfig()) {
+ e += EXPECT_CALL(*mMockApi, setLraWaveShape(0)).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setOdClamp(mLongVoltageMax)).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setOlLraPeriod(mLongLraPeriod)).WillOnce(DoDefault());
+ }
+
+ EXPECT_CALL(*mMockApi, setActivate(true)).After(e).WillOnce(DoDefault());
+
+ EXPECT_EQ(EX_NONE, mVibrator->on(duration, nullptr).getExceptionCode());
+}
+
+TEST_P(BasicTest, on_openLoop) {
+ EffectDuration duration = mCloseLoopThreshold;
+
+ relaxMock(true);
+
+ EXPECT_CALL(*mMockApi, setCtrlLoop(true)).WillOnce(DoDefault());
+
+ EXPECT_EQ(EX_NONE, mVibrator->on(duration, nullptr).getExceptionCode());
+}
+
+TEST_P(BasicTest, on_closeLoop) {
+ EffectDuration duration = mCloseLoopThreshold + 1;
+
+ relaxMock(true);
+
+ EXPECT_CALL(*mMockApi, setCtrlLoop(false)).WillOnce(DoDefault());
+
+ EXPECT_EQ(EX_NONE, mVibrator->on(duration, nullptr).getExceptionCode());
+}
+
+TEST_P(BasicTest, off) {
+ EXPECT_CALL(*mMockApi, setActivate(false)).WillOnce(DoDefault());
+
+ EXPECT_EQ(EX_NONE, mVibrator->off().getExceptionCode());
+}
+
+TEST_P(BasicTest, supportsAmplitudeControl_supported) {
+ EXPECT_CALL(*mMockApi, hasRtpInput()).WillOnce(Return(true));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_GT(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_P(BasicTest, supportsAmplitudeControl_unsupported) {
+ EXPECT_CALL(*mMockApi, hasRtpInput()).WillOnce(Return(false));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_AMPLITUDE_CONTROL, 0);
+}
+
+TEST_P(BasicTest, setAmplitude) {
+ EffectAmplitude amplitude = static_cast<float>(std::rand()) / RAND_MAX ?: 1.0f;
+
+ EXPECT_CALL(*mMockApi, setRtpInput(amplitudeToRtpInput(amplitude))).WillOnce(Return(true));
+
+ EXPECT_EQ(EX_NONE, mVibrator->setAmplitude(amplitude).getExceptionCode());
+}
+
+TEST_P(BasicTest, supportsExternalControl_unsupported) {
+ EXPECT_CALL(*mMockApi, hasRtpInput()).WillOnce(Return(false));
+
+ int32_t capabilities;
+ EXPECT_TRUE(mVibrator->getCapabilities(&capabilities).isOk());
+ EXPECT_EQ(capabilities & IVibrator::CAP_EXTERNAL_CONTROL, 0);
+}
+
+TEST_P(BasicTest, setExternalControl_enable) {
+ EXPECT_EQ(EX_UNSUPPORTED_OPERATION, mVibrator->setExternalControl(true).getExceptionCode());
+}
+
+TEST_P(BasicTest, setExternalControl_disable) {
+ EXPECT_EQ(EX_UNSUPPORTED_OPERATION, mVibrator->setExternalControl(false).getExceptionCode());
+}
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, BasicTest,
+ ValuesIn({BasicTest::MakeParam(false), BasicTest::MakeParam(true)}),
+ BasicTest::PrintParam);
+
+class EffectsTest : public VibratorTestTemplate<EffectTuple> {
+ public:
+ static auto GetEffectTuple(ParamType param) { return GetOtherParam<0>(param); }
+
+ static auto PrintParam(const TestParamInfo<ParamType> &info) {
+ auto prefix = VibratorTestTemplate::PrintParam(info);
+ auto tuple = GetEffectTuple(info.param);
+ auto effect = std::get<0>(tuple);
+ auto strength = std::get<1>(tuple);
+ return prefix + "_" + toString(effect) + "_" + toString(strength);
+ }
+
+ protected:
+ auto getEffectTuple() const { return GetEffectTuple(GetParam()); }
+};
+
+TEST_P(EffectsTest, perform) {
+ auto tuple = getEffectTuple();
+ auto effect = std::get<0>(tuple);
+ auto strength = std::get<1>(tuple);
+ auto seqIter = EFFECT_SEQUENCES.find(tuple);
+ auto durIter = mEffectDurations.find(effect);
+ EffectDuration duration;
+
+ if (seqIter != EFFECT_SEQUENCES.end() && durIter != mEffectDurations.end()) {
+ auto sequence = std::get<0>(seqIter->second);
+ auto scale = std::get<1>(seqIter->second);
+ ExpectationSet e;
+
+ duration = durIter->second;
+
+ e += EXPECT_CALL(*mMockApi, setSequencer(sequence)).WillOnce(Return(true));
+ e += EXPECT_CALL(*mMockApi, setScale(scale)).WillOnce(Return(true));
+ e += EXPECT_CALL(*mMockApi, setCtrlLoop(1)).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setMode("waveform")).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setDuration(duration)).WillOnce(DoDefault());
+
+ if (getDynamicConfig()) {
+ e += EXPECT_CALL(*mMockApi, setLraWaveShape(1)).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setOdClamp(mShortVoltageMax)).WillOnce(DoDefault());
+ e += EXPECT_CALL(*mMockApi, setOlLraPeriod(mShortLraPeriod)).WillOnce(DoDefault());
+ }
+
+ EXPECT_CALL(*mMockApi, setActivate(true)).After(e).WillOnce(DoDefault());
+ } else {
+ duration = 0;
+ }
+
+ int32_t lengthMs;
+ ndk::ScopedAStatus status = mVibrator->perform(effect, strength, nullptr, &lengthMs);
+ if (duration) {
+ EXPECT_EQ(EX_NONE, status.getExceptionCode());
+ EXPECT_LE(duration, lengthMs);
+ } else {
+ EXPECT_EQ(EX_UNSUPPORTED_OPERATION, status.getExceptionCode());
+ }
+}
+
+INSTANTIATE_TEST_CASE_P(VibratorTests, EffectsTest,
+ Combine(ValuesIn({false, true}),
+ Combine(ValuesIn(ndk::enum_range<Effect>().begin(),
+ ndk::enum_range<Effect>().end()),
+ ValuesIn(ndk::enum_range<EffectStrength>().begin(),
+ ndk::enum_range<EffectStrength>().end()))),
+ EffectsTest::PrintParam);
+
+} // namespace vibrator
+} // namespace hardware
+} // namespace android
+} // namespace aidl
diff --git a/vibrator/drv2624/tests/types.h b/vibrator/drv2624/tests/types.h
new file mode 100644
index 0000000..6ff21a6
--- /dev/null
+++ b/vibrator/drv2624/tests/types.h
@@ -0,0 +1,27 @@
+/*
+ * 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.
+ */
+#ifndef ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+#define ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
+
+#include <aidl/android/hardware/vibrator/IVibrator.h>
+
+using EffectAmplitude = float;
+using EffectDuration = uint32_t;
+using EffectSequence = std::tuple<std::string, uint8_t>;
+using EffectTuple = std::tuple<::aidl::android::hardware::vibrator::Effect,
+ ::aidl::android::hardware::vibrator::EffectStrength>;
+
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_TYPES_H
diff --git a/power-libperfmgr/display-helper.h b/vibrator/drv2624/tests/utils.h
index 70b9697..766ac5c 100644
--- a/power-libperfmgr/display-helper.h
+++ b/vibrator/drv2624/tests/utils.h
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * 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.
@@ -13,16 +13,15 @@
* 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
-#ifndef POWER_LIBPERFMGR_DISPLAY_HELPER_H_
-#define POWER_LIBPERFMGR_DISPLAY_HELPER_H_
+#include <cmath>
-enum display_lpm_state {
- DISPLAY_LPM_OFF = 0,
- DISPLAY_LPM_ON,
- DISPLAY_LPM_UNKNOWN,
-};
+#include "types.h"
-void set_display_lpm(int enable);
+static inline int32_t amplitudeToRtpInput(EffectAmplitude amplitude) {
+ return std::round(amplitude * 127);
+}
-#endif // POWER_LIBPERFMGR_DISPLAY_HELPER_H_
+#endif // ANDROID_HARDWARE_VIBRATOR_TEST_UTILS_H