#1160 secure view: prevent access to export actions
This commit is contained in:
parent
31f4737d3c
commit
f9bfbd5bea
12 changed files with 65 additions and 33 deletions
|
@ -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<String>()
|
||||
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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -6,8 +6,12 @@ import 'package:connectivity_plus/connectivity_plus.dart';
|
|||
import 'package:flutter/foundation.dart';
|
||||
|
||||
abstract class AvesAvailability {
|
||||
Future<void> onNewIntent();
|
||||
|
||||
void onResume();
|
||||
|
||||
bool get isLocked;
|
||||
|
||||
Future<bool> get isConnected;
|
||||
|
||||
Future<bool> 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<void> onNewIntent() async {
|
||||
_isLocked = await deviceService.isLocked();
|
||||
debugPrint('Device is locked=$_isLocked');
|
||||
}
|
||||
|
||||
@override
|
||||
void onResume() => _isConnected = null;
|
||||
|
||||
@override
|
||||
bool get isLocked => _isLocked ?? false;
|
||||
|
||||
@override
|
||||
Future<bool> get isConnected async {
|
||||
if (_isConnected != null) return SynchronousFuture(_isConnected!);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -14,6 +14,8 @@ abstract class DeviceService {
|
|||
|
||||
Future<int> getPerformanceClass();
|
||||
|
||||
Future<bool> isLocked();
|
||||
|
||||
Future<bool> isSystemFilePickerEnabled();
|
||||
|
||||
Future<void> requestMediaManagePermission();
|
||||
|
@ -89,6 +91,17 @@ class PlatformDeviceService implements DeviceService {
|
|||
return 0;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> 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<bool> isSystemFilePickerEnabled() async {
|
||||
try {
|
||||
|
|
|
@ -25,14 +25,11 @@ class _DebugDeviceSectionState extends State<DebugDeviceSection> 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}',
|
||||
|
|
|
@ -42,11 +42,9 @@ class _EditVaultDialogState extends State<EditVaultDialog> with FeedbackMixin, V
|
|||
|
||||
final List<VaultLockType> _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;
|
||||
|
|
|
@ -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<AlbumFilter> 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;
|
||||
|
|
|
@ -98,6 +98,7 @@ class _HomePageState extends State<HomePage> {
|
|||
_initialExplorerPath = null;
|
||||
_secureUris = null;
|
||||
|
||||
await availability.onNewIntent();
|
||||
await androidFileUtils.init();
|
||||
if (!{
|
||||
IntentActions.edit,
|
||||
|
|
|
@ -43,7 +43,7 @@ class PrivacySection extends SettingsSection {
|
|||
SettingsTilePrivacySaveSearchHistory(),
|
||||
if (!settings.useTvLayout) SettingsTilePrivacyEnableBin(),
|
||||
SettingsTilePrivacyHiddenItems(),
|
||||
if (!settings.useTvLayout && device.canGrantDirectoryAccess) SettingsTilePrivacyStorageAccess(),
|
||||
if (!settings.useTvLayout) SettingsTilePrivacyStorageAccess(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<ViewerButtonRowContent> {
|
|||
...topLevelActions.map((action) => _buildPopupMenuItem(context, action, videoController)),
|
||||
if (exportActions.isNotEmpty)
|
||||
PopupMenuExpansionPanel<EntryAction>(
|
||||
enabled: !availability.isLocked,
|
||||
value: 'export',
|
||||
expandedNotifier: _popupExpandedNotifier,
|
||||
icon: AIcons.export,
|
||||
|
@ -345,18 +347,18 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
|
|||
}
|
||||
|
||||
PopupMenuItem<EntryAction> _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;
|
||||
|
|
Loading…
Reference in a new issue