From 5ad830f191fd3b593191307cabeca0b7c659d4c1 Mon Sep 17 00:00:00 2001 From: Adam Velebil Date: Tue, 8 Nov 2022 11:50:36 +0100 Subject: [PATCH] Make camera optional on Android --- .../com/yubico/authenticator/MainActivity.kt | 8 +++++ .../android/src/main/AndroidManifest.xml | 2 +- lib/android/app_methods.dart | 4 +++ lib/android/init.dart | 1 + lib/android/state.dart | 2 ++ lib/app/views/main_page.dart | 33 ++++++++++--------- lib/oath/views/oath_screen.dart | 3 +- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt index ad960728..312e0890 100644 --- a/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt +++ b/android/app/src/main/kotlin/com/yubico/authenticator/MainActivity.kt @@ -18,6 +18,7 @@ package com.yubico.authenticator import android.content.* import android.content.pm.PackageManager +import android.hardware.camera2.CameraManager import android.hardware.usb.UsbDevice import android.hardware.usb.UsbManager import android.nfc.NfcAdapter @@ -328,6 +329,13 @@ class MainActivity : FlutterFragmentActivity() { } result.success(true) } + "hasCamera" -> { + val cameraService = + getSystemService(Context.CAMERA_SERVICE) as CameraManager + result.success( + cameraService.cameraIdList.isNotEmpty() + ) + } else -> Log.w(TAG, "Unknown app method: ${methodCall.method}") } } diff --git a/android/flutter_plugins/qrscanner_zxing/android/src/main/AndroidManifest.xml b/android/flutter_plugins/qrscanner_zxing/android/src/main/AndroidManifest.xml index f7c73edb..c32a545c 100644 --- a/android/flutter_plugins/qrscanner_zxing/android/src/main/AndroidManifest.xml +++ b/android/flutter_plugins/qrscanner_zxing/android/src/main/AndroidManifest.xml @@ -3,6 +3,6 @@ - + diff --git a/lib/android/app_methods.dart b/lib/android/app_methods.dart index a8386e1a..30a0d7fb 100644 --- a/lib/android/app_methods.dart +++ b/lib/android/app_methods.dart @@ -18,6 +18,10 @@ import 'package:flutter/services.dart'; const appMethodsChannel = MethodChannel('app.methods'); +Future getHasCamera() async { + return await appMethodsChannel.invokeMethod('hasCamera'); +} + Future getAndroidSdkVersion() async { return await appMethodsChannel.invokeMethod('getAndroidSdkVersion'); } diff --git a/lib/android/init.dart b/lib/android/init.dart index 25d773b0..8237b680 100644 --- a/lib/android/init.dart +++ b/lib/android/init.dart @@ -71,6 +71,7 @@ Future initialize() async { windowStateProvider.overrideWithProvider(androidWindowStateProvider), clipboardProvider.overrideWithProvider(androidClipboardProvider), androidSdkVersionProvider.overrideWithValue(await getAndroidSdkVersion()), + androidHasCameraProvider.overrideWithValue(await getHasCamera()), supportedThemesProvider .overrideWithProvider(androidSupportedThemesProvider) ], diff --git a/lib/android/state.dart b/lib/android/state.dart index 51aeaa1a..60e40ee6 100644 --- a/lib/android/state.dart +++ b/lib/android/state.dart @@ -64,6 +64,8 @@ class _AndroidClipboard extends AppClipboard { final androidSdkVersionProvider = Provider((ref) => -1); +final androidHasCameraProvider = Provider((ref) => false); + final androidSupportedThemesProvider = StateProvider>((ref) { if (ref.read(androidSdkVersionProvider) < 29) { // the user can select from light or dark theme of the app diff --git a/lib/app/views/main_page.dart b/lib/app/views/main_page.dart index a76fbdcb..fe2a3c6a 100755 --- a/lib/app/views/main_page.dart +++ b/lib/app/views/main_page.dart @@ -16,18 +16,19 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yubico_authenticator/android/state.dart'; import 'package:yubico_authenticator/cancellation_exception.dart'; import 'package:yubico_authenticator/core/state.dart'; -import '../../oath/models.dart'; -import 'message_page.dart'; -import 'device_error_screen.dart'; -import '../models.dart'; -import '../state.dart'; -import '../message.dart'; import '../../fido/views/fido_screen.dart'; +import '../../oath/models.dart'; import '../../oath/views/add_account_page.dart'; import '../../oath/views/oath_screen.dart'; +import '../message.dart'; +import '../models.dart'; +import '../state.dart'; +import 'device_error_screen.dart'; +import 'message_page.dart'; class MainPage extends ConsumerWidget { const MainPage({super.key}); @@ -74,16 +75,18 @@ class MainPage extends ConsumerWidget { tooltip: 'Add account', onPressed: () async { CredentialData? otpauth; - final scanner = ref.read(qrScannerProvider); - if (scanner != null) { - try { - final url = await scanner.scanQr(); - if (url != null) { - otpauth = CredentialData.fromUri(Uri.parse(url)); + if (ref.read(androidHasCameraProvider)) { + final scanner = ref.read(qrScannerProvider); + if (scanner != null) { + try { + final url = await scanner.scanQr(); + if (url != null) { + otpauth = CredentialData.fromUri(Uri.parse(url)); + } + } on CancellationException catch (_) { + // ignored - user cancelled + return; } - } on CancellationException catch (_) { - // ignored - user cancelled - return; } } await showBlurDialog( diff --git a/lib/oath/views/oath_screen.dart b/lib/oath/views/oath_screen.dart index 7c5eb840..8ffaf2ce 100755 --- a/lib/oath/views/oath_screen.dart +++ b/lib/oath/views/oath_screen.dart @@ -20,6 +20,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:yubico_authenticator/android/state.dart'; import 'package:yubico_authenticator/widgets/delayed_visibility.dart'; import '../../app/message.dart'; @@ -237,7 +238,7 @@ class _UnlockedViewState extends ConsumerState<_UnlockedView> { action: capacity == null || capacity > used ? () async { CredentialData? otpauth; - if (Platform.isAndroid) { + if (Platform.isAndroid && ref.read(androidHasCameraProvider)) { final scanner = ref.read(qrScannerProvider); if (scanner != null) { final url = await scanner.scanQr();