From 8460127e543b736fd17bd2095176ac6320de4701 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Such=C3=BD?= Date: Tue, 10 Dec 2024 14:46:41 +0100 Subject: [PATCH] feat(mobile): add usb priority mode for FW update (#15848) --- .../io/trezor/rnusb/ReactNativeUsbModule.kt | 12 +++++++-- .../src/ReactNativeUsbModule.ts | 1 + packages/react-native-usb/src/index.ts | 3 +++ suite-native/app/app.config.ts | 2 +- suite-native/firmware/package.json | 1 + .../firmware/src/hooks/useFirmware.tsx | 3 +++ .../FirmwareUpdateInProgressScreen.tsx | 25 ++++++++++++++++++- suite-native/firmware/tsconfig.json | 3 +++ yarn.lock | 1 + 9 files changed, 47 insertions(+), 4 deletions(-) diff --git a/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt b/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt index 14cdf92544f..911d4b309e6 100644 --- a/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt +++ b/packages/react-native-usb/android/src/main/java/io/trezor/rnusb/ReactNativeUsbModule.kt @@ -35,6 +35,8 @@ const val INTERFACE_INDEX = 0 class ReactNativeUsbModule : Module() { private val moduleCoroutineScope = CoroutineScope(Dispatchers.IO) private var isAppInForeground = false + // We use priority mode to prevent closing device when app is in background for example during firmware update + private var isInPriorityMode = false // List of devices for which permission has already been requested to prevent redundant requests if the user denies permission. private var devicesRequestedPermissions = mutableListOf() @@ -57,6 +59,10 @@ class ReactNativeUsbModule : Module() { // Defines event names that the module can send to JavaScript. Events(ON_DEVICE_CONNECT_EVENT_NAME, ON_DEVICE_DISCONNECT_EVENT_NAME) + Function("setPriorityMode") { priorityMode: Boolean -> + isInPriorityMode = priorityMode + } + AsyncFunction("getDevices") { return@AsyncFunction getDevices() } @@ -175,8 +181,10 @@ class ReactNativeUsbModule : Module() { } OnActivityEntersBackground { - isAppInForeground = false - closeAllOpenedDevices() + if (!isInPriorityMode) { + isAppInForeground = false + closeAllOpenedDevices() + } } OnDestroy { diff --git a/packages/react-native-usb/src/ReactNativeUsbModule.ts b/packages/react-native-usb/src/ReactNativeUsbModule.ts index 547b7433971..b0e0a1395f0 100644 --- a/packages/react-native-usb/src/ReactNativeUsbModule.ts +++ b/packages/react-native-usb/src/ReactNativeUsbModule.ts @@ -16,6 +16,7 @@ declare class ReactNativeUsbModuleDeclaration extends NativeModule selectConfiguration: (deviceName: string, configurationValue: number) => Promise; transferIn: (deviceName: string, endpointNumber: number, length: number) => Promise; transferOut: (deviceName: string, endpointNumber: number, data: string) => Promise; + setPriorityMode: (isInPriorityMode: boolean) => void; } // It loads the native module object from the JSI or falls back to diff --git a/packages/react-native-usb/src/index.ts b/packages/react-native-usb/src/index.ts index e5877eff732..6657d6feebf 100644 --- a/packages/react-native-usb/src/index.ts +++ b/packages/react-native-usb/src/index.ts @@ -10,6 +10,9 @@ const debugLog = (...args: any[]) => { } }; +export const setPriorityMode = (isInPriorityMode: boolean) => + ReactNativeUsbModule.setPriorityMode(isInPriorityMode); + const open = (deviceName: string) => ReactNativeUsbModule.open(deviceName); const close = (deviceName: string) => ReactNativeUsbModule.close(deviceName); diff --git a/suite-native/app/app.config.ts b/suite-native/app/app.config.ts index 7bd6a95ca12..dd46843d031 100644 --- a/suite-native/app/app.config.ts +++ b/suite-native/app/app.config.ts @@ -172,7 +172,7 @@ export default ({ config }: ConfigContext): ExpoConfig => { slug: appSlugs[buildType], owner: appOwners[buildType], version: suiteNativeVersion, - runtimeVersion: '17', + runtimeVersion: '18', ...(buildType === 'production' ? {} : { diff --git a/suite-native/firmware/package.json b/suite-native/firmware/package.json index f34a5d4ee8d..2b40d2d752e 100644 --- a/suite-native/firmware/package.json +++ b/suite-native/firmware/package.json @@ -18,6 +18,7 @@ "@suite-common/icons": "workspace:*", "@suite-native/link": "workspace:*", "@trezor/connect": "workspace:*", + "@trezor/react-native-usb": "workspace:*", "@trezor/styles": "workspace:*", "react": "18.2.0", "react-native": "0.76.1", diff --git a/suite-native/firmware/src/hooks/useFirmware.tsx b/suite-native/firmware/src/hooks/useFirmware.tsx index 340c8bb98b8..80cce2a8a53 100644 --- a/suite-native/firmware/src/hooks/useFirmware.tsx +++ b/suite-native/firmware/src/hooks/useFirmware.tsx @@ -7,6 +7,7 @@ import { UseFirmwareInstallationParams, } from '@suite-common/firmware'; import { TxKeyPath, useTranslate } from '@suite-native/intl'; +import { setPriorityMode } from '@trezor/react-native-usb'; import { nativeFirmwareActions } from '../nativeFirmwareSlice'; @@ -60,6 +61,7 @@ export const useFirmware = (params: UseFirmwareInstallationParams) => { }, [progress, status, setMayBeStuckedTimeout, resetMayBeStuckedTimeout]); const firmwareUpdate = useCallback(async () => { + setPriorityMode(true); const result = await firmwareUpdateCommon({ ignoreBaseUrl: true }) .unwrap() .catch(error => { @@ -73,6 +75,7 @@ export const useFirmware = (params: UseFirmwareInstallationParams) => { return connectResponse; }) .finally(() => { + setPriorityMode(false); resetMayBeStuckedTimeout(); }); diff --git a/suite-native/firmware/src/screens/FirmwareUpdateInProgressScreen.tsx b/suite-native/firmware/src/screens/FirmwareUpdateInProgressScreen.tsx index 3e31a2b1476..8abd4c17b99 100644 --- a/suite-native/firmware/src/screens/FirmwareUpdateInProgressScreen.tsx +++ b/suite-native/firmware/src/screens/FirmwareUpdateInProgressScreen.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import Animated, { + FadeIn, FadeInDown, FadeInUp, FadeOutDown, @@ -11,7 +12,7 @@ import { useDispatch } from 'react-redux'; import { useNavigation } from '@react-navigation/native'; import { authorizeDeviceThunk } from '@suite-common/wallet-core'; -import { Box, Button, Text, VStack } from '@suite-native/atoms'; +import { Box, Button, Text, VStack, IconButton } from '@suite-native/atoms'; import { ConfirmOnTrezorImage, setDeviceForceRememberedThunk } from '@suite-native/device'; import { requestPrioritizedDeviceAccess } from '@suite-native/device-mutex'; import { Translation } from '@suite-native/intl'; @@ -44,6 +45,12 @@ const bottomButtonsContainerStyle = prepareNativeStyle<{ bottom: number }>((util bottom, })); +const cancelButtonStyle = prepareNativeStyle(utils => ({ + position: 'absolute', + left: utils.spacings.sp8, + top: utils.spacings.sp8, +})); + export const FirmwareUpdateInProgressScreen = () => { const dispatch = useDispatch(); const { applyStyle } = useNativeStyles(); @@ -81,6 +88,10 @@ export const FirmwareUpdateInProgressScreen = () => { navigation.goBack(); }, [dispatch, navigation]); + const handleCancel = useCallback(() => { + navigation.goBack(); + }, [navigation]); + const startFirmwareUpdate = useCallback(async () => { setIsFirmwareInstallationRunning(true); @@ -161,6 +172,18 @@ export const FirmwareUpdateInProgressScreen = () => { return ( + {isError && ( + + + + )} diff --git a/suite-native/firmware/tsconfig.json b/suite-native/firmware/tsconfig.json index bb182f4593b..2f426f0ce24 100644 --- a/suite-native/firmware/tsconfig.json +++ b/suite-native/firmware/tsconfig.json @@ -6,6 +6,9 @@ { "path": "../../suite-common/icons" }, { "path": "../link" }, { "path": "../../packages/connect" }, + { + "path": "../../packages/react-native-usb" + }, { "path": "../../packages/styles" } ] } diff --git a/yarn.lock b/yarn.lock index 4564c55c07f..0020d687d14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10311,6 +10311,7 @@ __metadata: "@suite-common/icons": "workspace:*" "@suite-native/link": "workspace:*" "@trezor/connect": "workspace:*" + "@trezor/react-native-usb": "workspace:*" "@trezor/styles": "workspace:*" react: "npm:18.2.0" react-native: "npm:0.76.1"