added gatekeeper notification options
This commit is contained in:
@@ -60,6 +60,15 @@
|
||||
android:exported="false"
|
||||
android:launchMode="singleTop" />
|
||||
|
||||
<receiver
|
||||
android:name="eu.weblibre.flutter_mozilla_components.gatekeeper.GatekeeperNotificationActionReceiver"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="eu.weblibre.gecko.gatekeeper.ALLOW_ONCE" />
|
||||
<action android:name="eu.weblibre.gecko.gatekeeper.ALWAYS_ALLOW" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
|
||||
<receiver
|
||||
android:name="org.ironfoxoss.unifiedpush.PushReceiver"
|
||||
tools:node="remove" />
|
||||
|
||||
+14
-4
@@ -20,16 +20,16 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:weblibre/features/about/domain/providers.dart';
|
||||
import 'package:weblibre/features/intent_gatekeeper/domain/entities/intent_source_policy.dart';
|
||||
import 'package:weblibre/features/intent_gatekeeper/domain/entities/pending_intent_decision.dart';
|
||||
import 'package:weblibre/features/intent_gatekeeper/domain/services/native_gatekeeper_replicator.dart';
|
||||
import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
|
||||
part 'intent_gatekeeper.g.dart';
|
||||
|
||||
const _ownPackageName = 'eu.weblibre.gecko';
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class IntentGatekeeper extends _$IntentGatekeeper {
|
||||
late StreamController<PendingIntentDecision> _decisionRequests;
|
||||
@@ -62,14 +62,24 @@ class IntentGatekeeper extends _$IntentGatekeeper {
|
||||
required String? fromPackageName,
|
||||
required String? url,
|
||||
}) async {
|
||||
final settings = ref.read(generalSettingsWithDefaultsProvider);
|
||||
await ref
|
||||
.read(nativeIntentGatekeeperReplicatorProvider.notifier)
|
||||
.syncPendingAllows();
|
||||
|
||||
final settings = await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetchSettings();
|
||||
|
||||
if (!settings.blockExternalAppsEnabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final ownPackageName = (await ref.read(
|
||||
packageInfoProvider.future,
|
||||
)).packageName;
|
||||
|
||||
// Internal / unknown callers: no package to gate on — let through.
|
||||
if (fromPackageName == null || fromPackageName == _ownPackageName) {
|
||||
if (fromPackageName == null || fromPackageName == ownPackageName) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
+60
-1
@@ -26,6 +26,7 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:simple_intent_receiver/simple_intent_receiver.dart';
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/features/intent_gatekeeper/domain/entities/intent_source_policy.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
|
||||
part 'native_gatekeeper_replicator.g.dart';
|
||||
@@ -34,10 +35,14 @@ part 'native_gatekeeper_replicator.g.dart';
|
||||
/// `IntentReceiverActivity` can reject intents without launching Flutter.
|
||||
/// Only blocked packages are replicated — allow/unknown still fall through to
|
||||
/// the Flutter gatekeeper dialog.
|
||||
///
|
||||
/// Also consumes any "always allow" decisions made via notification actions
|
||||
/// and merges them into Flutter's policy before the next gatekeeper check.
|
||||
@Riverpod(keepAlive: true)
|
||||
class NativeIntentGatekeeperReplicator
|
||||
extends _$NativeIntentGatekeeperReplicator {
|
||||
final _api = IntentGatekeeperHostApi();
|
||||
Future<void>? _pendingAllowSync;
|
||||
|
||||
Future<void> _push(
|
||||
({bool enabled, Map<String, IntentSourcePolicy> policies}) config,
|
||||
@@ -58,8 +63,53 @@ class NativeIntentGatekeeperReplicator
|
||||
}
|
||||
}
|
||||
|
||||
/// Reads any packages the user approved via notification and persists them
|
||||
/// as `allow` policies in the Flutter settings.
|
||||
Future<void> _syncPendingAllowsInternal() async {
|
||||
try {
|
||||
final pending = await _api.getPendingAlwaysAllows();
|
||||
if (pending.isEmpty) return;
|
||||
|
||||
await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(
|
||||
(current) => current.copyWith.externalAppIntentPolicies({
|
||||
...current.externalAppIntentPolicies,
|
||||
for (final pkg in pending) pkg: IntentSourcePolicy.allow,
|
||||
}),
|
||||
);
|
||||
await _api.ackPendingAlwaysAllows(pending);
|
||||
} catch (error, stackTrace) {
|
||||
logger.e(
|
||||
'Failed to consume pending always-allows from native',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Deduplicates native pending-allow synchronization across concurrent
|
||||
/// callers so startup intent handling and config replication observe the same
|
||||
/// persisted policy state.
|
||||
Future<void> syncPendingAllows() {
|
||||
final inFlight = _pendingAllowSync;
|
||||
if (inFlight != null) {
|
||||
return inFlight;
|
||||
}
|
||||
|
||||
final future = _syncPendingAllowsInternal();
|
||||
_pendingAllowSync = future.whenComplete(() {
|
||||
if (identical(_pendingAllowSync, future)) {
|
||||
_pendingAllowSync = null;
|
||||
}
|
||||
});
|
||||
return _pendingAllowSync!;
|
||||
}
|
||||
|
||||
@override
|
||||
void build() {
|
||||
unawaited(syncPendingAllows());
|
||||
|
||||
ref.listen(
|
||||
generalSettingsWithDefaultsProvider.select(
|
||||
(settings) => EquatableValue((
|
||||
@@ -80,7 +130,16 @@ class NativeIntentGatekeeperReplicator
|
||||
)) {
|
||||
return;
|
||||
}
|
||||
unawaited(_push(next));
|
||||
unawaited(() async {
|
||||
await syncPendingAllows();
|
||||
final settings = await ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.fetchSettings();
|
||||
await _push((
|
||||
enabled: settings.blockExternalAppsEnabled,
|
||||
policies: settings.externalAppIntentPolicies,
|
||||
));
|
||||
}());
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+13
-1
@@ -12,6 +12,9 @@ part of 'native_gatekeeper_replicator.dart';
|
||||
/// `IntentReceiverActivity` can reject intents without launching Flutter.
|
||||
/// Only blocked packages are replicated — allow/unknown still fall through to
|
||||
/// the Flutter gatekeeper dialog.
|
||||
///
|
||||
/// On startup, also consumes any "always allow" decisions made via notification
|
||||
/// actions while Flutter was not running, and merges them into Flutter's policy.
|
||||
|
||||
@ProviderFor(NativeIntentGatekeeperReplicator)
|
||||
final nativeIntentGatekeeperReplicatorProvider =
|
||||
@@ -21,12 +24,18 @@ final nativeIntentGatekeeperReplicatorProvider =
|
||||
/// `IntentReceiverActivity` can reject intents without launching Flutter.
|
||||
/// Only blocked packages are replicated — allow/unknown still fall through to
|
||||
/// the Flutter gatekeeper dialog.
|
||||
///
|
||||
/// On startup, also consumes any "always allow" decisions made via notification
|
||||
/// actions while Flutter was not running, and merges them into Flutter's policy.
|
||||
final class NativeIntentGatekeeperReplicatorProvider
|
||||
extends $NotifierProvider<NativeIntentGatekeeperReplicator, void> {
|
||||
/// Mirrors the Flutter-side block list to the native side so the
|
||||
/// `IntentReceiverActivity` can reject intents without launching Flutter.
|
||||
/// Only blocked packages are replicated — allow/unknown still fall through to
|
||||
/// the Flutter gatekeeper dialog.
|
||||
///
|
||||
/// On startup, also consumes any "always allow" decisions made via notification
|
||||
/// actions while Flutter was not running, and merges them into Flutter's policy.
|
||||
NativeIntentGatekeeperReplicatorProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
@@ -56,12 +65,15 @@ final class NativeIntentGatekeeperReplicatorProvider
|
||||
}
|
||||
|
||||
String _$nativeIntentGatekeeperReplicatorHash() =>
|
||||
r'ee97dbd489e4e946e0a98cd640300f939f3b0682';
|
||||
r'ed1ba2a318467317d6fb412495171acb8819e483';
|
||||
|
||||
/// Mirrors the Flutter-side block list to the native side so the
|
||||
/// `IntentReceiverActivity` can reject intents without launching Flutter.
|
||||
/// Only blocked packages are replicated — allow/unknown still fall through to
|
||||
/// the Flutter gatekeeper dialog.
|
||||
///
|
||||
/// On startup, also consumes any "always allow" decisions made via notification
|
||||
/// actions while Flutter was not running, and merges them into Flutter's policy.
|
||||
|
||||
abstract class _$NativeIntentGatekeeperReplicator extends $Notifier<void> {
|
||||
void build();
|
||||
|
||||
@@ -27,16 +27,34 @@ import 'package:simple_intent_receiver/simple_intent_receiver.dart';
|
||||
import 'package:uri_to_file/uri_to_file.dart' as uri_to_file;
|
||||
import 'package:weblibre/core/logger.dart';
|
||||
import 'package:weblibre/data/models/received_intent_parameter.dart';
|
||||
import 'package:weblibre/features/intent_gatekeeper/domain/entities/intent_source_policy.dart';
|
||||
import 'package:weblibre/features/intent_gatekeeper/domain/services/intent_gatekeeper.dart';
|
||||
import 'package:weblibre/features/share_intent/domain/entities/intent_container_mode.dart';
|
||||
import 'package:weblibre/features/user/data/models/general_settings.dart';
|
||||
import 'package:weblibre/features/user/domain/repositories/general_settings.dart';
|
||||
|
||||
part 'sharing_intent.g.dart';
|
||||
|
||||
const _alwaysAllowPackageExtra = 'eu.weblibre.gatekeeper.always_allow_package';
|
||||
|
||||
StreamTransformer<Intent, ReceivedIntentParameter>
|
||||
_buildSharingIntentTransformer(
|
||||
IntentGatekeeper gatekeeper,
|
||||
) => StreamTransformer<Intent, ReceivedIntentParameter>.fromHandlers(
|
||||
handleData: (intent, sink) async {
|
||||
final alwaysAllowPackage =
|
||||
intent.extra[_alwaysAllowPackageExtra] as String?;
|
||||
if (alwaysAllowPackage != null) {
|
||||
await gatekeeper.ref
|
||||
.read(generalSettingsRepositoryProvider.notifier)
|
||||
.updateSettings(
|
||||
(current) => current.copyWith.externalAppIntentPolicies({
|
||||
...current.externalAppIntentPolicies,
|
||||
alwaysAllowPackage: IntentSourcePolicy.allow,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
final shortcutContextId = intent.action == 'android.intent.action.VIEW'
|
||||
? intent.extra['pwa_context_id'] as String?
|
||||
: null;
|
||||
|
||||
Reference in New Issue
Block a user