Skip to content

Commit

Permalink
Bugfix/8850 add battery check during ESC (#5138)
Browse files Browse the repository at this point in the history
* feat(libs): add LOW_BATTERY_PERCENTAGE environment variable

* feat(common): add low battery check in battery statuses hook

* feat(llm): move battery check drawer into firmware update screen

* feat(llm): prevent re-display of fw update drawer post-cancellation

* feat(llm,common,env): add changeset

* feat(llm): don't open fw update drawer only when cancelled

* feat(llm): remove getEnv from battery warning drawer

* chore: add logs to getBatteryStatus

* feat(common): return custom task event for unknown apdu for battery stat

* feat(llm): fix eslint warning

* feat(llm): fix typo

* feat(common): improve tracer for get battery statuses

* feat(common): remove error when UnknownApdu is set for battery check

---------

Co-authored-by: Alexandre Magaud <[email protected]>
  • Loading branch information
aussedatlo and alexandremgo authored Dec 1, 2023
1 parent 95acaec commit 9d35080
Show file tree
Hide file tree
Showing 11 changed files with 230 additions and 138 deletions.
7 changes: 7 additions & 0 deletions .changeset/purple-wolves-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"live-mobile": patch
"@ledgerhq/live-common": patch
"@ledgerhq/live-env": patch
---

add low battery warning during early security check
132 changes: 9 additions & 123 deletions apps/ledger-live-mobile/src/components/FirmwareUpdateBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import React, { useState, useCallback, useEffect } from "react";
import React, { useState, useCallback } from "react";
import { Platform, Pressable } from "react-native";
import { useNavigation, useRoute, useTheme } from "@react-navigation/native";
import { useNavigation, useRoute } from "@react-navigation/native";
import { DeviceModelInfo } from "@ledgerhq/types-live";
import { useTranslation } from "react-i18next";
import { useSelector } from "react-redux";
import {
Alert,
Text,
Flex,
IconsLegacy,
IconBadge,
Icons,
InfiniteLoader,
} from "@ledgerhq/native-ui";
import { Alert, Text, Flex, Icons } from "@ledgerhq/native-ui";
import { DownloadMedium, UsbMedium } from "@ledgerhq/native-ui/assets/icons";
import { DeviceModelId, getDeviceModel } from "@ledgerhq/devices";
import { useFeature } from "@ledgerhq/live-common/featureFlags/index";
import { StackNavigationProp } from "@react-navigation/stack";
import { BatteryStatusTypes } from "@ledgerhq/live-common/hw/getBatteryStatus";
import isFirmwareUpdateVersionSupported from "@ledgerhq/live-common/hw/isFirmwareUpdateVersionSupported";
import useLatestFirmware from "@ledgerhq/live-common/hooks/useLatestFirmware";
import { useBatteryStatuses } from "@ledgerhq/live-common/deviceSDK/hooks/useBatteryStatuses";
import { BatteryStatusFlags } from "@ledgerhq/types-devices";

import { ScreenName, NavigatorName } from "../const";
import {
Expand All @@ -32,31 +21,19 @@ import {
import { hasConnectedDeviceSelector } from "../reducers/appstate";
import Button from "./Button";
import QueuedDrawer from "./QueuedDrawer";
import { renderConnectYourDevice } from "./DeviceAction/rendering";
import { DeviceActionError } from "./DeviceAction/common";
import { UpdateStep } from "../screens/FirmwareUpdate";

export type FirmwareUpdateBannerProps = {
onBackFromUpdate: (updateState: UpdateStep) => void;
};

const requiredBatteryStatuses = [
BatteryStatusTypes.BATTERY_PERCENTAGE,
BatteryStatusTypes.BATTERY_FLAGS,
];

const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) => {
const lastSeenDevice: DeviceModelInfo | null | undefined = useSelector(lastSeenDeviceSelector);

const lastConnectedDevice = useSelector(lastConnectedDeviceSelector);
const hasConnectedDevice = useSelector(hasConnectedDeviceSelector);
const hasCompletedOnboarding: boolean = useSelector(hasCompletedOnboardingSelector);

const { dark } = useTheme();
const theme: "dark" | "light" = dark ? "dark" : "light";
const [loading, setLoading] = useState(false);
const [showBatteryWarningDrawer, setShowBatteryWarningDrawer] = useState<boolean>(false);

const [showUnsupportedUpdateDrawer, setShowUnsupportedUpdateDrawer] = useState<boolean>(false);

const { t } = useTranslation();
Expand All @@ -68,16 +45,6 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =

const latestFirmware = useLatestFirmware(lastSeenDevice?.deviceInfo);

const {
requestCompleted: batteryRequestCompleted,
batteryStatusesState,
triggerRequest: triggerBatteryCheck,
cancelRequest: cancelBatteryCheck,
} = useBatteryStatuses({
deviceId: lastConnectedDevice?.deviceId,
statuses: requiredBatteryStatuses,
});

const onExperimentalFirmwareUpdate = useCallback(() => {
if (newFwUpdateUxFeatureFlag?.enabled) {
navigation.navigate(NavigatorName.Manager, {
Expand All @@ -86,10 +53,7 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
device: lastConnectedDevice,
deviceInfo: lastSeenDevice?.deviceInfo,
firmwareUpdateContext: latestFirmware,
onBackFromUpdate: (updateState: UpdateStep) => {
cancelBatteryCheck();
if (onBackFromUpdate) onBackFromUpdate(updateState);
},
onBackFromUpdate,
},
});
return;
Expand All @@ -113,32 +77,9 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
lastConnectedDevice,
lastSeenDevice?.deviceInfo,
latestFirmware,
cancelBatteryCheck,
onBackFromUpdate,
]);

// Effect that will check the battery of stax before triggering the update and display a warning preventing the update
// in case the battery is too low and the device is not charging
useEffect(() => {
if (batteryRequestCompleted && batteryStatusesState.error === null) {
const [percentage, statusFlags] = batteryStatusesState.batteryStatuses as [
number,
BatteryStatusFlags,
];

percentage < 20 && statusFlags.charging === 0
? setShowBatteryWarningDrawer(true)
: onExperimentalFirmwareUpdate();

setLoading(false);
}
}, [
batteryRequestCompleted,
batteryStatusesState.batteryStatuses,
batteryStatusesState.error,
onExperimentalFirmwareUpdate,
]);

const showBanner = Boolean(latestFirmware);
const version = latestFirmware?.final?.name ?? "";

Expand All @@ -161,8 +102,9 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
if (lastConnectedDevice?.modelId === DeviceModelId.stax && newFwUpdateUxFeatureFlag?.enabled) {
// This leads to a check on the battery before triggering update, it is only necessary for Stax and on the new UX
// (because it's the only type of update that can happen via BLE)
setLoading(true);
triggerBatteryCheck();
// setLoading(true);
// triggerBatteryCheck();
onExperimentalFirmwareUpdate();
}
// Path with any device model, wired and on android
else if (isUsbFwVersionUpdateSupported && wiredDevice && Platform.OS === "android") {
Expand All @@ -175,7 +117,6 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
newFwUpdateUxFeatureFlag?.enabled,
isUsbFwVersionUpdateSupported,
wiredDevice,
triggerBatteryCheck,
onExperimentalFirmwareUpdate,
]);

Expand All @@ -188,7 +129,7 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
return showBanner && hasCompletedOnboarding && hasConnectedDevice ? (
<>
{newFwUpdateUxFeatureFlag?.enabled ? (
<Pressable onPress={onClickUpdate} disabled={loading}>
<Pressable onPress={onClickUpdate}>
<Flex
flexDirection="row"
alignItems="flex-start"
Expand All @@ -198,9 +139,7 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
pl={5}
>
<Flex flexDirection="row" alignItems="center" mb={5} mr={4}>
{loading ? (
<InfiniteLoader size={24} color="primary.c80" />
) : lastConnectedDevice?.modelId === DeviceModelId.stax ? (
{lastConnectedDevice?.modelId === DeviceModelId.stax ? (
<Icons.Stax color="primary.c80" size="M" />
) : (
<Icons.Nano color="primary.c80" size="M" />
Expand Down Expand Up @@ -266,59 +205,6 @@ const FirmwareUpdateBanner = ({ onBackFromUpdate }: FirmwareUpdateBannerProps) =
>
<Button type="primary" title={t("common.close")} onPress={onCloseUsbWarningDrawer} />
</QueuedDrawer>
<QueuedDrawer
isRequestingToBeOpened={showBatteryWarningDrawer}
onClose={() => setShowBatteryWarningDrawer(false)}
>
<Flex alignItems="center" justifyContent="center" px={1}>
<IconBadge iconColor="primary.c100" iconSize={32} Icon={IconsLegacy.BatteryHalfMedium} />
<Text fontSize={7} fontWeight="semiBold" textAlign="center" mt={6}>
{t("FirmwareUpdate.staxBatteryLow")}
</Text>
<Text fontSize={4} textAlign="center" color="neutral.c80" mt={6}>
{t("FirmwareUpdate.staxBatteryLowDescription")}
</Text>
<Button
type="main"
outline={false}
onPress={() => setShowBatteryWarningDrawer(false)}
mt={8}
alignSelf="stretch"
>
{t("common.close")}
</Button>
</Flex>
</QueuedDrawer>
<QueuedDrawer
isRequestingToBeOpened={
batteryStatusesState.error !== null || batteryStatusesState.lockedDevice
}
onClose={() => {
cancelBatteryCheck();
setLoading(false);
}}
>
{lastConnectedDevice && (
<Flex alignItems="center" justifyContent="center" px={1}>
{batteryStatusesState.error?.name === "CantOpenDevice" ||
batteryStatusesState.lockedDevice ? (
renderConnectYourDevice({
t,
device: lastConnectedDevice,
theme,
fullScreen: false,
})
) : (
<DeviceActionError
device={lastConnectedDevice}
t={t}
errorName={batteryStatusesState.error?.name ?? "BatteryStatusNotRetrieved"}
translationContext="FirmwareUpdate.batteryStatusErrors"
/>
)}
</Flex>
)}
</QueuedDrawer>
</>
) : null;
};
Expand Down
3 changes: 2 additions & 1 deletion apps/ledger-live-mobile/src/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -4240,7 +4240,7 @@
"waitForFirmwareUpdate": "Wait for the firmware update to finish on your device",
"waitForInstallationToFinish": "Wait for installation to finish on your {{deviceName}}",
"staxBatteryLow": "Ledger Stax battery too low to update",
"staxBatteryLowDescription": "To start the OS update, a minimum of 20% charge is needed, or your Ledger Stax must be charging.",
"staxBatteryLowDescription": "To start the OS update, a minimum of {{lowBatteryPercentage}}% charge is needed, or your Ledger Stax must be charging.",
"updateCancelled": "OS update install cancelled on {{deviceName}}",
"updateCancelledDescription": "If this was a mistake, restart the update.",
"updateNotYetComplete": "OS update is in progress",
Expand All @@ -4255,6 +4255,7 @@
"newVersionNumber": "New version",
"success": "Firmware updated",
"update": "Update",
"retryBatteryCheck": "Retry after pluggin",
"Notifications": {
"confirmOnDevice": "We require your confirmation on the device",
"preparingUpdate": "Transferring update, we will notify you when we're done",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from "react";
import QueuedDrawer, { Props as QueuedDrawerProps } from "../../components/QueuedDrawer";
import { Button, Flex, IconBadge, IconsLegacy, Text } from "@ledgerhq/native-ui";
import { useTranslation } from "react-i18next";
import { GetBatteryStatusesActionState } from "@ledgerhq/live-common/deviceSDK/actions/getBatteryStatuses";
import { Device } from "@ledgerhq/types-devices";

type Props = QueuedDrawerProps & {
state: GetBatteryStatusesActionState;
lowBatteryPercentage: number;
device: Device;
onRetry: () => void;
onQuit: () => void;
};

const BatteryWarningDrawer: React.FC<Props> = ({
device,
state,
lowBatteryPercentage,
onRetry,
onQuit,
...props
}) => {
const { t } = useTranslation();

return (
<>
<QueuedDrawer noCloseButton {...props}>
<Flex>
<Flex alignItems="center" justifyContent="center" mb={8}>
<IconBadge
iconColor="primary.c100"
iconSize={32}
Icon={IconsLegacy.BatteryHalfMedium}
/>
<Text fontSize={7} fontWeight="semiBold" textAlign="center" mt={6}>
{t("FirmwareUpdate.staxBatteryLow")}
</Text>
<Text fontSize={4} textAlign="center" color="neutral.c80" mt={6}>
{t("FirmwareUpdate.staxBatteryLowDescription", {
lowBatteryPercentage,
})}
</Text>
</Flex>

<Button type="main" outline={false} onPress={onRetry} mt={6} alignSelf="stretch">
{t("FirmwareUpdate.retryBatteryCheck")}
</Button>
<Button type="default" outline={false} onPress={onQuit} mt={6}>
{t("FirmwareUpdate.quitUpdate")}
</Button>
</Flex>
</QueuedDrawer>
</>
);
};

export default BatteryWarningDrawer;
Loading

1 comment on commit 9d35080

@vercel
Copy link

@vercel vercel bot commented on 9d35080 Dec 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.