diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt index d7f336648..0e7a68ecd 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/MainActivity.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.app.SearchManager import android.appwidget.AppWidgetManager import android.content.ClipData +import android.content.Context import android.content.Intent import android.net.Uri import android.os.Build @@ -317,6 +318,13 @@ open class MainActivity : FlutterFragmentActivity() { INTENT_DATA_KEY_URI to uri.toString(), ) + val keyguardManager = getSystemService(Context.KEYGUARD_SERVICE) as android.app.KeyguardManager + val isLocked = keyguardManager.isKeyguardLocked + if (isLocked) { + // device is locked, so access to content is limited to intent URI by default + fields[INTENT_DATA_KEY_SECURE_URIS] = listOf(uri.toString()) + } + if (action == MediaStore.ACTION_REVIEW_SECURE) { val uris = ArrayList() intent.clipData?.let { clipData -> @@ -324,7 +332,9 @@ open class MainActivity : FlutterFragmentActivity() { clipData.getItemAt(i).uri?.let { uris.add(it.toString()) } } } - fields[INTENT_DATA_KEY_SECURE_URIS] = uris + if (uris.isNotEmpty()) { + fields[INTENT_DATA_KEY_SECURE_URIS] = uris + } } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && intent.hasExtra(MediaStore.EXTRA_BRIGHTNESS)) { fields[INTENT_DATA_KEY_BRIGHTNESS] = intent.getFloatExtra(MediaStore.EXTRA_BRIGHTNESS, 0f) diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt index 603739bde..3fe35ae01 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/DeviceHandler.kt @@ -33,6 +33,7 @@ class DeviceHandler(private val context: Context) : MethodCallHandler { "getDefaultTimeZoneRawOffsetMillis" -> safe(call, result, ::getDefaultTimeZoneRawOffsetMillis) "getLocales" -> safe(call, result, ::getLocales) "getPerformanceClass" -> safe(call, result, ::getPerformanceClass) + "isLocked" -> safe(call, result, ::isLocked) "isSystemFilePickerEnabled" -> safe(call, result, ::isSystemFilePickerEnabled) "requestMediaManagePermission" -> safe(call, result, ::requestMediaManagePermission) "getAvailableHeapSize" -> safe(call, result, ::getAvailableHeapSize) @@ -49,13 +50,11 @@ class DeviceHandler(private val context: Context) : MethodCallHandler { val sdkInt = Build.VERSION.SDK_INT result.success( hashMapOf( - "canGrantDirectoryAccess" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), "canPinShortcut" to ShortcutManagerCompat.isRequestPinShortcutSupported(context), "canRenderFlagEmojis" to (sdkInt >= Build.VERSION_CODES.M), "canRenderSubdivisionFlagEmojis" to (sdkInt >= Build.VERSION_CODES.O), "canRequestManageMedia" to (sdkInt >= Build.VERSION_CODES.S), "canSetLockScreenWallpaper" to (sdkInt >= Build.VERSION_CODES.N), - "canUseCrypto" to (sdkInt >= Build.VERSION_CODES.LOLLIPOP), "hasGeocoder" to Geocoder.isPresent(), "isDynamicColorAvailable" to DynamicColors.isDynamicColorAvailable(), "showPinShortcutFeedback" to (sdkInt >= Build.VERSION_CODES.O), @@ -100,6 +99,12 @@ class DeviceHandler(private val context: Context) : MethodCallHandler { result.success(Build.VERSION.SDK_INT) } + private fun isLocked(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { + val keyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as android.app.KeyguardManager + val isLocked = keyguardManager.isKeyguardLocked + result.success(isLocked) + } + private fun isSystemFilePickerEnabled(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) { val enabled = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).resolveActivity(context.packageManager) != null result.success(enabled) diff --git a/lib/model/availability.dart b/lib/model/availability.dart index 20c22d564..c6c3af3d0 100644 --- a/lib/model/availability.dart +++ b/lib/model/availability.dart @@ -6,8 +6,12 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/foundation.dart'; abstract class AvesAvailability { + Future onNewIntent(); + void onResume(); + bool get isLocked; + Future get isConnected; Future get canLocatePlaces; @@ -16,15 +20,24 @@ abstract class AvesAvailability { } class LiveAvesAvailability implements AvesAvailability { - bool? _isConnected; + bool? _isConnected, _isLocked; LiveAvesAvailability() { Connectivity().onConnectivityChanged.listen(_updateConnectivityFromResult); } + @override + Future onNewIntent() async { + _isLocked = await deviceService.isLocked(); + debugPrint('Device is locked=$_isLocked'); + } + @override void onResume() => _isConnected = null; + @override + bool get isLocked => _isLocked ?? false; + @override Future get isConnected async { if (_isConnected != null) return SynchronousFuture(_isConnected!); diff --git a/lib/model/device.dart b/lib/model/device.dart index 3c7a60d7a..f14e57eb9 100644 --- a/lib/model/device.dart +++ b/lib/model/device.dart @@ -9,8 +9,8 @@ final Device device = Device._private(); class Device { late final String _packageName, _packageVersion, _userAgent; - late final bool _canAuthenticateUser, _canGrantDirectoryAccess, _canPinShortcut; - late final bool _canRenderFlagEmojis, _canRenderSubdivisionFlagEmojis, _canRequestManageMedia, _canSetLockScreenWallpaper, _canUseCrypto; + late final bool _canAuthenticateUser, _canPinShortcut; + late final bool _canRenderFlagEmojis, _canRenderSubdivisionFlagEmojis, _canRequestManageMedia, _canSetLockScreenWallpaper; late final bool _hasGeocoder, _isDynamicColorAvailable, _isTelevision, _showPinShortcutFeedback, _supportEdgeToEdgeUIMode, _supportPictureInPicture; String get packageName => _packageName; @@ -21,8 +21,6 @@ class Device { bool get canAuthenticateUser => _canAuthenticateUser; - bool get canGrantDirectoryAccess => _canGrantDirectoryAccess; - bool get canPinShortcut => _canPinShortcut; bool get canRenderFlagEmojis => _canRenderFlagEmojis; @@ -33,10 +31,6 @@ class Device { bool get canSetLockScreenWallpaper => _canSetLockScreenWallpaper; - bool get canUseCrypto => _canUseCrypto; - - bool get canUseVaults => canAuthenticateUser || canUseCrypto; - bool get hasGeocoder => _hasGeocoder; bool get isDynamicColorAvailable => _isDynamicColorAvailable; @@ -71,13 +65,11 @@ class Device { } final capabilities = await deviceService.getCapabilities(); - _canGrantDirectoryAccess = capabilities['canGrantDirectoryAccess'] ?? false; _canPinShortcut = capabilities['canPinShortcut'] ?? false; _canRenderFlagEmojis = capabilities['canRenderFlagEmojis'] ?? false; _canRenderSubdivisionFlagEmojis = capabilities['canRenderSubdivisionFlagEmojis'] ?? false; _canRequestManageMedia = capabilities['canRequestManageMedia'] ?? false; _canSetLockScreenWallpaper = capabilities['canSetLockScreenWallpaper'] ?? false; - _canUseCrypto = capabilities['canUseCrypto'] ?? false; _hasGeocoder = capabilities['hasGeocoder'] ?? false; _isDynamicColorAvailable = capabilities['isDynamicColorAvailable'] ?? false; _showPinShortcutFeedback = capabilities['showPinShortcutFeedback'] ?? false; diff --git a/lib/services/device_service.dart b/lib/services/device_service.dart index 26500505b..0eac24328 100644 --- a/lib/services/device_service.dart +++ b/lib/services/device_service.dart @@ -14,6 +14,8 @@ abstract class DeviceService { Future getPerformanceClass(); + Future isLocked(); + Future isSystemFilePickerEnabled(); Future requestMediaManagePermission(); @@ -89,6 +91,17 @@ class PlatformDeviceService implements DeviceService { return 0; } + @override + Future isLocked() async { + try { + final result = await _platform.invokeMethod('isLocked'); + if (result != null) return result as bool; + } on PlatformException catch (e, stack) { + await reportService.recordError(e, stack); + } + return false; + } + @override Future isSystemFilePickerEnabled() async { try { diff --git a/lib/widgets/debug/device.dart b/lib/widgets/debug/device.dart index c9bf6d9d4..6a758660a 100644 --- a/lib/widgets/debug/device.dart +++ b/lib/widgets/debug/device.dart @@ -25,14 +25,11 @@ class _DebugDeviceSectionState extends State with AutomaticK 'packageVersion': device.packageVersion, 'userAgent': device.userAgent, 'canAuthenticateUser': '${device.canAuthenticateUser}', - 'canGrantDirectoryAccess': '${device.canGrantDirectoryAccess}', 'canPinShortcut': '${device.canPinShortcut}', 'canRenderFlagEmojis': '${device.canRenderFlagEmojis}', 'canRenderSubdivisionFlagEmojis': '${device.canRenderSubdivisionFlagEmojis}', 'canRequestManageMedia': '${device.canRequestManageMedia}', 'canSetLockScreenWallpaper': '${device.canSetLockScreenWallpaper}', - 'canUseCrypto': '${device.canUseCrypto}', - 'canUseVaults': '${device.canUseVaults}', 'hasGeocoder': '${device.hasGeocoder}', 'isDynamicColorAvailable': '${device.isDynamicColorAvailable}', 'isTelevision': '${device.isTelevision}', diff --git a/lib/widgets/dialogs/filter_editors/edit_vault_dialog.dart b/lib/widgets/dialogs/filter_editors/edit_vault_dialog.dart index 19cc23aa1..4c2a88fae 100644 --- a/lib/widgets/dialogs/filter_editors/edit_vault_dialog.dart +++ b/lib/widgets/dialogs/filter_editors/edit_vault_dialog.dart @@ -42,11 +42,9 @@ class _EditVaultDialogState extends State with FeedbackMixin, V final List _lockTypeOptions = [ if (device.canAuthenticateUser) VaultLockType.system, - if (device.canUseCrypto) ...[ - VaultLockType.pattern, - VaultLockType.pin, - VaultLockType.password, - ], + VaultLockType.pattern, + VaultLockType.pin, + VaultLockType.password, ]; VaultDetails? get initialDetails => widget.initialDetails; diff --git a/lib/widgets/filter_grids/common/action_delegates/album_set.dart b/lib/widgets/filter_grids/common/action_delegates/album_set.dart index 2d9e1611d..4aa4ca671 100644 --- a/lib/widgets/filter_grids/common/action_delegates/album_set.dart +++ b/lib/widgets/filter_grids/common/action_delegates/album_set.dart @@ -1,7 +1,6 @@ import 'dart:io'; import 'package:aves/app_mode.dart'; -import 'package:aves/model/device.dart'; import 'package:aves/model/entry/entry.dart'; import 'package:aves/model/filters/album.dart'; import 'package:aves/model/filters/filters.dart'; @@ -78,12 +77,10 @@ class AlbumChipSetActionDelegate extends ChipSetActionDelegate with final selectedSingleItem = selectedFilters.length == 1; final isMain = appMode == AppMode.main; - final canCreate = !settings.isReadOnly && appMode.canCreateFilter && !isSelecting; switch (action) { case ChipSetAction.createAlbum: - return canCreate; case ChipSetAction.createVault: - return canCreate && device.canUseVaults; + return !settings.isReadOnly && appMode.canCreateFilter && !isSelecting; case ChipSetAction.delete: case ChipSetAction.rename: return isMain && isSelecting && !settings.isReadOnly; diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 58882d4be..af2980cca 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -98,6 +98,7 @@ class _HomePageState extends State { _initialExplorerPath = null; _secureUris = null; + await availability.onNewIntent(); await androidFileUtils.init(); if (!{ IntentActions.edit, diff --git a/lib/widgets/settings/privacy/privacy.dart b/lib/widgets/settings/privacy/privacy.dart index 6fc24936e..671f77ce6 100644 --- a/lib/widgets/settings/privacy/privacy.dart +++ b/lib/widgets/settings/privacy/privacy.dart @@ -43,7 +43,7 @@ class PrivacySection extends SettingsSection { SettingsTilePrivacySaveSearchHistory(), if (!settings.useTvLayout) SettingsTilePrivacyEnableBin(), SettingsTilePrivacyHiddenItems(), - if (!settings.useTvLayout && device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(), + if (!settings.useTvLayout) SettingsTilePrivacyStorageAccess(), ]; } } diff --git a/lib/widgets/viewer/action/entry_action_delegate.dart b/lib/widgets/viewer/action/entry_action_delegate.dart index 5b6da5965..5ee6e98ca 100644 --- a/lib/widgets/viewer/action/entry_action_delegate.dart +++ b/lib/widgets/viewer/action/entry_action_delegate.dart @@ -159,6 +159,10 @@ class EntryActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAwareMix case EntryAction.convertMotionPhotoToStillImage: case EntryAction.viewMotionPhotoVideo: return _metadataActionDelegate.canApply(targetEntry, action); + case EntryAction.convert: + case EntryAction.copy: + case EntryAction.move: + return !availability.isLocked; default: return true; } diff --git a/lib/widgets/viewer/overlay/viewer_buttons.dart b/lib/widgets/viewer/overlay/viewer_buttons.dart index 2b757b544..32e613aa3 100644 --- a/lib/widgets/viewer/overlay/viewer_buttons.dart +++ b/lib/widgets/viewer/overlay/viewer_buttons.dart @@ -7,6 +7,7 @@ import 'package:aves/model/entry/extensions/props.dart'; import 'package:aves/model/settings/enums/accessibility_animations.dart'; import 'package:aves/model/settings/settings.dart'; import 'package:aves/model/source/collection_lens.dart'; +import 'package:aves/services/common/services.dart'; import 'package:aves/theme/icons.dart'; import 'package:aves/view/view.dart'; import 'package:aves/widgets/common/action_controls/quick_choosers/move_button.dart'; @@ -278,6 +279,7 @@ class _ViewerButtonRowContentState extends State { ...topLevelActions.map((action) => _buildPopupMenuItem(context, action, videoController)), if (exportActions.isNotEmpty) PopupMenuExpansionPanel( + enabled: !availability.isLocked, value: 'export', expandedNotifier: _popupExpandedNotifier, icon: AIcons.export, @@ -345,18 +347,18 @@ class _ViewerButtonRowContentState extends State { } PopupMenuItem _buildPopupMenuItem(BuildContext context, EntryAction action, AvesVideoController? videoController) { - late final bool enabled; + var enabled = widget.actionDelegate.canApply(action); switch (action) { case EntryAction.videoCaptureFrame: - enabled = videoController?.canCaptureFrameNotifier.value ?? false; + enabled &= videoController?.canCaptureFrameNotifier.value ?? false; case EntryAction.videoToggleMute: - enabled = videoController?.canMuteNotifier.value ?? false; + enabled &= videoController?.canMuteNotifier.value ?? false; case EntryAction.videoSelectStreams: - enabled = videoController?.canSelectStreamNotifier.value ?? false; + enabled &= videoController?.canSelectStreamNotifier.value ?? false; case EntryAction.videoSetSpeed: - enabled = videoController?.canSetSpeedNotifier.value ?? false; + enabled &= videoController?.canSetSpeedNotifier.value ?? false; default: - enabled = true; + break; } Widget? child;