513 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			513 lines
		
	
	
		
			18 KiB
		
	
	
	
		
			C++
		
	
	
	
| /*
 | |
|  * 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 <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 <cutils/klog.h>
 | |
| #include <dirent.h>
 | |
| #include <pixelhealth/BatteryDefender.h>
 | |
| #include <pixelhealth/HealthHelper.h>
 | |
| #include <stdio.h>
 | |
| #include <sys/types.h>
 | |
| #include <time.h>
 | |
| #include <utils/Timers.h>
 | |
| 
 | |
| #include <unordered_map>
 | |
| 
 | |
| using aidl::android::hardware::health::BatteryHealth;
 | |
| using aidl::android::hardware::health::HealthInfo;
 | |
| 
 | |
| namespace hardware {
 | |
| namespace google {
 | |
| namespace pixel {
 | |
| namespace health {
 | |
| 
 | |
| BatteryDefender::BatteryDefender(const std::string pathWirelessPresent,
 | |
|                                  const std::string pathChargeLevelStart,
 | |
|                                  const std::string pathChargeLevelStop,
 | |
|                                  const int32_t timeToActivateSecs,
 | |
|                                  const int32_t timeToClearTimerSecs, const bool useTypeC)
 | |
| 
 | |
|     : mPathWirelessPresent(pathWirelessPresent),
 | |
|       kPathChargeLevelStart(pathChargeLevelStart),
 | |
|       kPathChargeLevelStop(pathChargeLevelStop),
 | |
|       kTimeToActivateSecs(timeToActivateSecs),
 | |
|       kTimeToClearTimerSecs(timeToClearTimerSecs),
 | |
|       kUseTypeC(useTypeC) {
 | |
|     mTimePreviousSecs = getTime();
 | |
| }
 | |
| 
 | |
| void BatteryDefender::clearStateData(void) {
 | |
|     mHasReachedHighCapacityLevel = false;
 | |
|     mTimeActiveSecs = 0;
 | |
|     mTimeChargerNotPresentSecs = 0;
 | |
|     mTimeChargerPresentSecs = 0;
 | |
| }
 | |
| 
 | |
| void BatteryDefender::setWirelessNotSupported(void) {
 | |
|     mPathWirelessPresent = PATH_NOT_SUPPORTED;
 | |
| }
 | |
| 
 | |
| 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 std::string &path, const bool silent) {
 | |
|     std::string buffer;
 | |
|     int value = 0;  // default
 | |
| 
 | |
|     if (path == PATH_NOT_SUPPORTED) {
 | |
|         return value;
 | |
|     }
 | |
| 
 | |
|     if (!android::base::ReadFileToString(path, &buffer)) {
 | |
|         if (silent == false) {
 | |
|             LOG(ERROR) << "Failed to read " << path;
 | |
|         }
 | |
|     } else {
 | |
|         removeLineEndings(&buffer);
 | |
|         if (!android::base::ParseInt(buffer, &value)) {
 | |
|             LOG(ERROR) << "Failed to parse " << path;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return value;
 | |
| }
 | |
| 
 | |
| bool BatteryDefender::writeIntToFile(const std::string &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 std::string &path, const int value, int64_t *previous) {
 | |
|     // Some number of seconds delay before repeated writes
 | |
|     const bool hasTimeChangedSignificantly =
 | |
|             ((value == 0) || (*previous == -1) || (value > (*previous + kWriteDelaySecs)) ||
 | |
|              (value < (*previous - kWriteDelaySecs)));
 | |
|     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) {
 | |
|         const int newDefenderLevelStart = android::base::GetIntProperty(
 | |
|                 kPropBatteryDefenderCtrlStartSOC, kChargeLevelDefenderStart, 0, 100);
 | |
|         const int newDefenderLevelStop = android::base::GetIntProperty(
 | |
|                 kPropBatteryDefenderCtrlStopSOC, kChargeLevelDefenderStop, 0, 100);
 | |
|         const bool overrideLevelsValid =
 | |
|                 (newDefenderLevelStart <= newDefenderLevelStop) && (newDefenderLevelStop != 0);
 | |
| 
 | |
|         if (overrideLevelsValid) {
 | |
|             chargeLevelStart = newDefenderLevelStart;
 | |
|             chargeLevelStop = newDefenderLevelStop;
 | |
|         } else {
 | |
|             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::isTypeCSink(const std::string &path) {
 | |
|     std::string buffer;
 | |
| 
 | |
|     if (!android::base::ReadFileToString(path, &buffer)) {
 | |
|         LOG(ERROR) << "Failed to read " << path;
 | |
|     }
 | |
| 
 | |
|     return (buffer.find("[sink]") != std::string::npos);
 | |
| }
 | |
| 
 | |
| bool BatteryDefender::isWiredPresent(void) {
 | |
|     // Default to USB "present" if type C is not used.
 | |
|     if (!kUseTypeC) {
 | |
|         return readFileToInt(kPathUSBChargerPresent) != 0;
 | |
|     }
 | |
| 
 | |
|     DIR *dp = opendir(kTypeCPath.c_str());
 | |
|     if (dp == NULL) {
 | |
|         LOG(ERROR) << "Failed to read " << kTypeCPath;
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     struct dirent *ep;
 | |
|     std::unordered_map<std::string, bool> names;
 | |
|     while ((ep = readdir(dp))) {
 | |
|         if (ep->d_type == DT_LNK) {
 | |
|             if (std::string::npos != std::string(ep->d_name).find("-partner")) {
 | |
|                 std::string portName = std::strtok(ep->d_name, "-");
 | |
|                 std::string path = kTypeCPath + portName + "/power_role";
 | |
|                 if (isTypeCSink(path)) {
 | |
|                     closedir(dp);
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
|     }
 | |
|     closedir(dp);
 | |
| 
 | |
|     return false;
 | |
| }
 | |
| 
 | |
| bool BatteryDefender::isDockPresent(void) {
 | |
|     return readFileToInt(kPathDOCKChargerPresent, true) != 0;
 | |
| }
 | |
| 
 | |
| bool BatteryDefender::isChargePowerAvailable(void) {
 | |
|     // USB presence is an indicator of power availability
 | |
|     const bool chargerPresentWired = isWiredPresent();
 | |
|     const bool chargerPresentWireless = readFileToInt(mPathWirelessPresent) != 0;
 | |
|     const bool chargerPresentDock = isDockPresent();
 | |
|     mIsWiredPresent = chargerPresentWired;
 | |
|     mIsWirelessPresent = chargerPresentWireless;
 | |
|     mIsDockPresent = chargerPresentDock;
 | |
| 
 | |
|     return chargerPresentWired || chargerPresentWireless || chargerPresentDock;
 | |
| }
 | |
| 
 | |
| 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 isOverrideDisabled =
 | |
|             android::base::GetBoolProperty(kPropBatteryDefenderDisable, false);
 | |
|     const bool isCtrlEnabled =
 | |
|             android::base::GetBoolProperty(kPropBatteryDefenderCtrlEnable, kDefaultEnable);
 | |
| 
 | |
|     return isOverrideDisabled || (isDefaultVendorChargeLevel == false) || (isCtrlEnabled == false);
 | |
| }
 | |
| 
 | |
| bool BatteryDefender::isDockDefendTrigger(void) {
 | |
|     return readFileToInt(kPathDockState, true) == 1;
 | |
| }
 | |
| 
 | |
| 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)
 | |
|     const int32_t timeToActivateOverride =
 | |
|             android::base::GetIntProperty(kPropBatteryDefenderThreshold, kTimeToActivateSecs,
 | |
|                                           (int32_t)ONE_MIN_IN_SECONDS, INT32_MAX);
 | |
| 
 | |
|     const bool overrideActive = timeToActivateOverride != kTimeToActivateSecs;
 | |
|     if (overrideActive) {
 | |
|         return timeToActivateOverride;
 | |
|     } else {
 | |
|         // No overrides taken; apply ctrl time to activate...
 | |
|         // Note; do not allow less than 1 day trigger time
 | |
|         return android::base::GetIntProperty(kPropBatteryDefenderCtrlActivateTime,
 | |
|                                              kTimeToActivateSecs, (int32_t)ONE_DAY_IN_SECONDS,
 | |
|                                              INT32_MAX);
 | |
|     }
 | |
| }
 | |
| 
 | |
| void BatteryDefender::stateMachine_runAction(const state_E state, const HealthInfo &health_info) {
 | |
|     switch (state) {
 | |
|         case STATE_INIT:
 | |
|             loadPersistentStorage();
 | |
|             if (health_info.chargerUsbOnline || health_info.chargerAcOnline) {
 | |
|                 mWasAcOnline = health_info.chargerAcOnline;
 | |
|                 mWasUsbOnline = health_info.chargerUsbOnline;
 | |
|             }
 | |
|             break;
 | |
| 
 | |
|         case STATE_DISABLED:
 | |
|         case STATE_DISCONNECTED:
 | |
|             clearStateData();
 | |
|             break;
 | |
| 
 | |
|         case STATE_CONNECTED: {
 | |
|             addTimeToChargeTimers();
 | |
| 
 | |
|             const int triggerLevel = android::base::GetIntProperty(
 | |
|                     kPropBatteryDefenderCtrlTriggerSOC, kChargeHighCapacityLevel, 0, 100);
 | |
|             if (health_info.batteryLevel >= triggerLevel) {
 | |
|                 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;
 | |
|                 }
 | |
|                 FALLTHROUGH_INTENDED;
 | |
| 
 | |
|             case STATE_ACTIVE: {
 | |
|                 const int timeToClear = android::base::GetIntProperty(
 | |
|                         kPropBatteryDefenderCtrlResumeTime, kTimeToClearTimerSecs, 0, INT32_MAX);
 | |
| 
 | |
|                 const int bdClear = android::base::GetIntProperty(kPropBatteryDefenderCtrlClear, 0);
 | |
| 
 | |
|                 if (bdClear > 0) {
 | |
|                     android::base::SetProperty(kPropBatteryDefenderCtrlClear, "0");
 | |
|                     nextState = STATE_DISCONNECTED;
 | |
|                 }
 | |
| 
 | |
|                 /* Check for mIsPowerAvailable in case timeToClear is 0 */
 | |
|                 if ((mTimeChargerNotPresentSecs >= timeToClear) && (mIsPowerAvailable == false)) {
 | |
|                     nextState = STATE_DISCONNECTED;
 | |
|                 }
 | |
|             } break;
 | |
| 
 | |
|             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:
 | |
|             LOG(INFO) << "Disabled!";
 | |
|             FALLTHROUGH_INTENDED;
 | |
| 
 | |
|         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::updateDefenderProperties(
 | |
|         aidl::android::hardware::health::HealthInfo *health_info) {
 | |
|     /**
 | |
|      * Override the OVERHEAT flag for UI updates to settings.
 | |
|      * Also, force AC/USB online if active and still connected to power.
 | |
|      */
 | |
|     if (mCurrentState == STATE_ACTIVE) {
 | |
|         health_info->batteryHealth = BatteryHealth::OVERHEAT;
 | |
|     }
 | |
| 
 | |
|     /* Do the same as above when dock-defend triggers */
 | |
|     if (mIsDockDefendTrigger) {
 | |
|         health_info->batteryHealth = BatteryHealth::OVERHEAT;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * If the kernel is forcing the input current limit to 0, then the online status may
 | |
|      * need to be overwritten. Also, setting a charge limit below the current charge level
 | |
|      * may disable the adapter.
 | |
|      * Note; only override "online" if necessary (all "online"s are false).
 | |
|      */
 | |
|     if (health_info->chargerUsbOnline == false && health_info->chargerAcOnline == false) {
 | |
|         /* Override if the USB is connected and a battery defender is active */
 | |
|         if (mIsWiredPresent && health_info->batteryHealth == BatteryHealth::OVERHEAT) {
 | |
|             if (mWasAcOnline) {
 | |
|                 health_info->chargerAcOnline = true;
 | |
|             }
 | |
|             if (mWasUsbOnline) {
 | |
|                 health_info->chargerUsbOnline = true;
 | |
|             }
 | |
|         }
 | |
|     } else {
 | |
|         /* One of these booleans will always be true if updated here */
 | |
|         mWasAcOnline = health_info->chargerAcOnline;
 | |
|         mWasUsbOnline = health_info->chargerUsbOnline;
 | |
|     }
 | |
| 
 | |
|     /* Do the same as above for wireless adapters */
 | |
|     if (health_info->chargerWirelessOnline == false) {
 | |
|         if (mIsWirelessPresent && health_info->batteryHealth == BatteryHealth::OVERHEAT) {
 | |
|             health_info->chargerWirelessOnline = true;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     /* Do the same as above for dock adapters */
 | |
|     if (health_info->chargerDockOnline == false) {
 | |
|         /* Override if the USB is connected and a battery defender is active */
 | |
|         if (mIsDockPresent && health_info->batteryHealth == BatteryHealth::OVERHEAT) {
 | |
|             health_info->chargerDockOnline = true;
 | |
|         }
 | |
|     }
 | |
| }
 | |
| 
 | |
| void BatteryDefender::update(HealthInfo *health_info) {
 | |
|     if (!health_info) {
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // 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);
 | |
|     mIsDockDefendTrigger = isDockDefendTrigger();
 | |
| 
 | |
|     // Run state machine
 | |
|     stateMachine_runAction(mCurrentState, *health_info);
 | |
|     const state_E nextState = stateMachine_getNextState(mCurrentState);
 | |
|     if (nextState != mCurrentState) {
 | |
|         stateMachine_firstAction(nextState);
 | |
|     }
 | |
|     mCurrentState = nextState;
 | |
| 
 | |
|     // Verify/update battery defender battery properties
 | |
|     updateDefenderProperties(health_info); /* May override battery properties */
 | |
| 
 | |
|     // Store outputs
 | |
|     writeTimeToFile(kPathPersistChargerPresentTime, mTimeChargerPresentSecs,
 | |
|                     &mTimeChargerPresentSecsPrevious);
 | |
|     writeTimeToFile(kPathPersistDefenderActiveTime, mTimeActiveSecs, &mTimeActiveSecsPrevious);
 | |
|     writeChargeLevelsToFile(chargeLevelVendorStart, chargeLevelVendorStop);
 | |
|     android::base::SetProperty(kPropBatteryDefenderState, kStateStringMap[mCurrentState]);
 | |
| }
 | |
| 
 | |
| void BatteryDefender::update(struct android::BatteryProperties *props) {
 | |
|     if (!props) {
 | |
|         return;
 | |
|     }
 | |
|     HealthInfo health_info = ToHealthInfo(props);
 | |
|     update(&health_info);
 | |
|     // Propagate the changes to props
 | |
|     props->chargerAcOnline = health_info.chargerAcOnline;
 | |
|     props->chargerUsbOnline = health_info.chargerUsbOnline;
 | |
|     props->chargerWirelessOnline = health_info.chargerWirelessOnline;
 | |
|     props->chargerDockOnline = health_info.chargerDockOnline;
 | |
|     props->batteryHealth = static_cast<int>(health_info.batteryHealth);
 | |
|     // update() doesn't change other fields.
 | |
| }
 | |
| 
 | |
| }  // namespace health
 | |
| }  // namespace pixel
 | |
| }  // namespace google
 | |
| }  // namespace hardware
 |