Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 17 additions & 4 deletions lib/data/app_permissions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,16 @@ class AppPermissions {
/// Opens the app settings page.
Future<void> toAppSettings() => openAppSettings();

/// Status of the OEM "display pop-up windows while running in background"
/// capability (MIUI/HyperOS), which gates showing the incoming-call UI over
/// the lock screen. Reports granted where the capability does not apply.
Future<CallkeepSpecialPermissionStatus> backgroundActivityStartStatus() =>
_webtritCallkeepPermissions.getBackgroundActivityStartPermissionStatus();

/// Opens the OEM permissions screen hosting the "display pop-up windows while
/// running in background" toggle (falls back to app settings natively).
Future<void> toBackgroundActivityStartSettings() => _webtritCallkeepPermissions.openBackgroundActivityStartSettings();

/// Attempts to open the settings screen for the given special permission.
///
/// If the specific permission screen (e.g., full screen intent) is not supported
Expand All @@ -180,10 +190,13 @@ class AppPermissions {
/// are typically located outside the standard app settings.
Future<void> toSpecialPermissionsSetting(CallkeepSpecialPermissions permission) async {
try {
if (permission == CallkeepSpecialPermissions.fullScreenIntent) {
await _webtritCallkeepPermissions.openFullScreenIntentSettings();
} else {
await _webtritCallkeepPermissions.openSettings();
switch (permission) {
case CallkeepSpecialPermissions.fullScreenIntent:
await _webtritCallkeepPermissions.openFullScreenIntentSettings();
// Not reached via the current flow (backgroundActivityStart is not in
// _specialPermissions); the live trigger is toBackgroundActivityStartSettings().
case CallkeepSpecialPermissions.backgroundActivityStart:
Comment thread
SERDUN marked this conversation as resolved.
await _webtritCallkeepPermissions.openBackgroundActivityStartSettings();
}
} catch (e) {
await _webtritCallkeepPermissions.openSettings();
Expand Down
61 changes: 46 additions & 15 deletions lib/features/permissions/cubit/permissions_cubit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,20 @@ class PermissionsCubit extends Cubit<PermissionsState> {
final missingSpecialPermissions = await _getDeniedSpecialPermissions();
_logger.info('Denied special permissions: $missingSpecialPermissions');

emit(state.copyWith(isPermanentlyDenied: isDenied, missingSpecialPermissions: missingSpecialPermissions));
final manufacturerTip = await _resolveManufacturerTip();
_logger.info('Manufacturer tip: $manufacturerTip');

// checkPermissions() is fire-and-forget on every resume and emits after
// several awaits; bail out if the cubit was closed in the meantime.
if (isClosed) return;

emit(
Comment thread
SERDUN marked this conversation as resolved.
state.copyWith(
isPermanentlyDenied: isDenied,
missingSpecialPermissions: missingSpecialPermissions,
manufacturerTip: manufacturerTip,
),
);
}

/// Executes the comprehensive permission request sequence:
Expand Down Expand Up @@ -65,10 +78,7 @@ class PermissionsCubit extends Cubit<PermissionsState> {
final missingSpecialPermissions = await _getDeniedSpecialPermissions();
_logger.info('Denied special permissions: $missingSpecialPermissions');

final manufacturer = _checkManufacturer();
_logger.info('Manufacturer: $manufacturer');

final manufacturerTip = _getManufacturerTip(manufacturer, missingSpecialPermissions);
final manufacturerTip = await _resolveManufacturerTip();
_logger.info('Manufacturer tip: $manufacturerTip');

final isDenied = await appPermissions.isDenied;
Expand All @@ -94,15 +104,22 @@ class PermissionsCubit extends Cubit<PermissionsState> {
}
}

ManufacturerTip? _getManufacturerTip(
Manufacturer? manufacturer,
List<CallkeepSpecialPermissions> specialPermissions,
) {
final hasManufacturer = manufacturer != null;
final currentTip = state.manufacturerTip;

// Determine if we need to set or keep the manufacturer tip
return currentTip ?? (hasManufacturer ? ManufacturerTip(manufacturer: manufacturer) : null);
/// Resolves manufacturer-specific guidance for reliable lock-screen calls.
///
/// On Xiaomi/HyperOS the incoming-call UI cannot cover the lock screen unless
/// the OEM "display pop-up windows while running in background" capability is
/// granted, which the standard Android permissions do not cover. The tip is
/// surfaced only while that capability is missing, so it clears itself once
/// the user enables it and returns to the app. A dismissed tip is preserved.
Future<ManufacturerTip?> _resolveManufacturerTip() async {
Comment thread
SERDUN marked this conversation as resolved.
final manufacturer = _checkManufacturer();
_logger.info('Manufacturer: $manufacturer');
if (manufacturer == null) return null;

final backgroundStartDenied = (await appPermissions.backgroundActivityStartStatus()).isDenied;
if (!backgroundStartDenied) return null;

return state.manufacturerTip ?? ManufacturerTip(manufacturer: manufacturer);
}

Future<void> _requestFirebaseMessagingPermission() async {
Expand All @@ -127,7 +144,15 @@ class PermissionsCubit extends Cubit<PermissionsState> {
/// This is used to provide manufacturer-specific instructions or tips for enabling permissions.
/// Returns `null` if the manufacturer is not in the predefined list.
Manufacturer? _checkManufacturer() {
return Manufacturer.values.asNameMap()[deviceInfo.manufacturer.toLowerCase()];
final manufacturer = deviceInfo.manufacturer.toLowerCase();
// Xiaomi sub-brands (Redmi/Poco) report their own manufacturer string but
// share the same MIUI/HyperOS lock-screen restriction. Match the same family
// the native side (PermissionsHelper.isXiaomiFamily) uses, so guidance shows
// on every affected device rather than only the literal "xiaomi".
if (manufacturer.contains('xiaomi') || manufacturer.contains('redmi') || manufacturer.contains('poco')) {
return Manufacturer.xiaomi;
}
return Manufacturer.values.asNameMap()[manufacturer];
}

void dismissError() {
Expand All @@ -142,6 +167,12 @@ class PermissionsCubit extends Cubit<PermissionsState> {
appPermissions.toAppSettings();
}

/// Opens the OEM permissions screen hosting the "display pop-up windows while
/// running in background" toggle (Xiaomi/HyperOS), with a native fallback.
void openManufacturerCallPermissionSettings() {
appPermissions.toBackgroundActivityStartSettings();
}

void openAppSpecialPermissionSettings(CallkeepSpecialPermissions permission) {
appPermissions.toSpecialPermissionsSetting(permission);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ extension SpecialPermissionTips on CallkeepSpecialPermissions {
context.l10n.permission_manageFullScreenNotificationInstructions_step4,
context.l10n.permission_manageFullScreenNotificationInstructions_step5,
];
// Reached only if backgroundActivityStart is added to
// AppPermissions._specialPermissions; today it is surfaced via the
// manufacturer tip (ManufacturerTips.tips), which uses the same strings.
case CallkeepSpecialPermissions.backgroundActivityStart:
return [
context.l10n.permission_manufacturer_Text_xiaomi_tip1,
context.l10n.permission_manufacturer_Text_xiaomi_tip2,
];
}
}
}
2 changes: 1 addition & 1 deletion lib/features/permissions/view/permissions_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ class _PermissionsScreenState extends State<PermissionsScreen> with WidgetsBindi

return ManufacturerPermission(
manufacturer: manufacturer,
onGoToAppSettings: permissionCubit.openAppSettings,
onGoToAppSettings: permissionCubit.openManufacturerCallPermissionSettings,
onPop: onPopCallback(),
);
},
Expand Down
9 changes: 9 additions & 0 deletions lib/features/permissions/widgets/special_permission.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,15 @@ class SpecialPermission extends StatelessWidget {
onGoToAppSettings: onGoToAppSettings,
onPop: onPop,
),
// backgroundActivityStart is surfaced through ManufacturerPermission, not
// this pipeline (it is not in AppPermissions._specialPermissions), so this
// arm is currently unreachable and exists only to keep the switch exhaustive.
CallkeepSpecialPermissions.backgroundActivityStart => PermissionTips(
title: context.l10n.permission_manufacturer_Text_heading,
instruction: specialPermissions.tips(context),
onGoToAppSettings: onGoToAppSettings,
onPop: onPop,
),
},
);
}
Expand Down
4 changes: 2 additions & 2 deletions lib/l10n/app_localizations.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2853,13 +2853,13 @@ abstract class AppLocalizations {
/// No description provided for @permission_manufacturer_Text_xiaomi_tip1.
///
/// In en, this message translates to:
/// **'Go to \"App settings\" → \"Notifications\".'**
/// **'Open this app\'s \"Other permissions\" screen using the button below.'**
String get permission_manufacturer_Text_xiaomi_tip1;

/// No description provided for @permission_manufacturer_Text_xiaomi_tip2.
///
/// In en, this message translates to:
/// **'Find and turn on \"Lockscreen notifications\".'**
/// **'Turn on \"Display pop-up windows while running in background\" (and \"Show on lock screen\") so incoming calls can appear over the lock screen.'**
String get permission_manufacturer_Text_xiaomi_tip2;

/// No description provided for @permission_Text_description.
Expand Down
6 changes: 4 additions & 2 deletions lib/l10n/app_localizations_en.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1546,10 +1546,12 @@ class AppLocalizationsEn extends AppLocalizations {
String get permission_manufacturer_Text_trailing => 'Permissions could be changed at any time in the future.';

@override
String get permission_manufacturer_Text_xiaomi_tip1 => 'Go to \"App settings\" → \"Notifications\".';
String get permission_manufacturer_Text_xiaomi_tip1 =>
'Open this app\'s \"Other permissions\" screen using the button below.';

@override
String get permission_manufacturer_Text_xiaomi_tip2 => 'Find and turn on \"Lockscreen notifications\".';
String get permission_manufacturer_Text_xiaomi_tip2 =>
'Turn on \"Display pop-up windows while running in background\" (and \"Show on lock screen\") so incoming calls can appear over the lock screen.';

@override
String get permission_Text_description =>
Expand Down
6 changes: 4 additions & 2 deletions lib/l10n/app_localizations_it.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1568,10 +1568,12 @@ class AppLocalizationsIt extends AppLocalizations {
'I permessi potrebbero essere modificati in qualsiasi momento in futuro.';

@override
String get permission_manufacturer_Text_xiaomi_tip1 => 'Vai su \"Impostazioni dell\'app\" → \"Notifiche\".';
String get permission_manufacturer_Text_xiaomi_tip1 =>
'Apri la schermata \"Altre autorizzazioni\" di questa app con il pulsante in basso.';

@override
String get permission_manufacturer_Text_xiaomi_tip2 => 'Trova e attiva \"Notifiche sulla schermata di blocco\".';
String get permission_manufacturer_Text_xiaomi_tip2 =>
'Attiva \"Mostra finestre popup in background\" (e \"Mostra nella schermata di blocco\") per far apparire le chiamate in arrivo sopra la schermata di blocco.';

@override
String get permission_Text_description =>
Expand Down
6 changes: 4 additions & 2 deletions lib/l10n/app_localizations_uk.g.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1570,10 +1570,12 @@ class AppLocalizationsUk extends AppLocalizations {
String get permission_manufacturer_Text_trailing => 'Дозволи можуть бути змінені в будь-який час у майбутньому.';

@override
String get permission_manufacturer_Text_xiaomi_tip1 => 'Перейдіть у «Налаштування програми» → «Сповіщення».';
String get permission_manufacturer_Text_xiaomi_tip1 =>
'Відкрийте екран «Інші дозволи» цього застосунку кнопкою нижче.';

@override
String get permission_manufacturer_Text_xiaomi_tip2 => 'Знайдіть і ввімкніть «Сповіщення на екрані блокування».';
String get permission_manufacturer_Text_xiaomi_tip2 =>
'Увімкніть «Показ спливаючих вікон під час роботи у фоні» (та «Показ на екрані блокування»), щоб вхідні дзвінки зʼявлялися поверх екрана блокування.';

@override
String get permission_Text_description =>
Expand Down
4 changes: 2 additions & 2 deletions lib/l10n/arb/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1342,9 +1342,9 @@
"@permission_manufacturer_Text_heading": {},
"permission_manufacturer_Text_trailing": "Permissions could be changed at any time in the future.",
"@permission_manufacturer_Text_trailing": {},
"permission_manufacturer_Text_xiaomi_tip1": "Go to \"App settings\" → \"Notifications\".",
"permission_manufacturer_Text_xiaomi_tip1": "Open this app's \"Other permissions\" screen using the button below.",
"@permission_manufacturer_Text_xiaomi_tip1": {},
"permission_manufacturer_Text_xiaomi_tip2": "Find and turn on \"Lockscreen notifications\".",
"permission_manufacturer_Text_xiaomi_tip2": "Turn on \"Display pop-up windows while running in background\" (and \"Show on lock screen\") so incoming calls can appear over the lock screen.",
"@permission_manufacturer_Text_xiaomi_tip2": {},
"permission_Text_description": "To ensure the best user experience, the app needs to be granted the following permissions: microphone for audio calls, camera for video calls, and contacts to simplify reaching them from the app.\n\nPermissions could be changed at any time in the future.",
"@permission_Text_description": {},
Expand Down
4 changes: 2 additions & 2 deletions lib/l10n/arb/app_it.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1339,9 +1339,9 @@
"@permission_manufacturer_Text_heading": {},
"permission_manufacturer_Text_trailing": "I permessi potrebbero essere modificati in qualsiasi momento in futuro.",
"@permission_manufacturer_Text_trailing": {},
"permission_manufacturer_Text_xiaomi_tip1": "Vai su \"Impostazioni dell'app\" → \"Notifiche\".",
"permission_manufacturer_Text_xiaomi_tip1": "Apri la schermata \"Altre autorizzazioni\" di questa app con il pulsante in basso.",
"@permission_manufacturer_Text_xiaomi_tip1": {},
"permission_manufacturer_Text_xiaomi_tip2": "Trova e attiva \"Notifiche sulla schermata di blocco\".",
"permission_manufacturer_Text_xiaomi_tip2": "Attiva \"Mostra finestre popup in background\" (e \"Mostra nella schermata di blocco\") per far apparire le chiamate in arrivo sopra la schermata di blocco.",
"@permission_manufacturer_Text_xiaomi_tip2": {},
"permission_Text_description": "Per garantire la migliore esperienza utente, all'app devono essere concesse le seguenti autorizzazioni: microfono per le chiamate audio, fotocamera per le videochiamate e contatti per semplificare il raggiungimento degli utenti dall'app.\n\nLe autorizzazioni possono essere modificate in qualsiasi momento anche successivamente.",
"@permission_Text_description": {},
Expand Down
4 changes: 2 additions & 2 deletions lib/l10n/arb/app_uk.arb
Original file line number Diff line number Diff line change
Expand Up @@ -1339,9 +1339,9 @@
"@permission_manufacturer_Text_heading": {},
"permission_manufacturer_Text_trailing": "Дозволи можуть бути змінені в будь-який час у майбутньому.",
"@permission_manufacturer_Text_trailing": {},
"permission_manufacturer_Text_xiaomi_tip1": "Перейдіть у «Налаштування програми» → «Сповіщення».",
"permission_manufacturer_Text_xiaomi_tip1": "Відкрийте екран «Інші дозволи» цього застосунку кнопкою нижче.",
"@permission_manufacturer_Text_xiaomi_tip1": {},
"permission_manufacturer_Text_xiaomi_tip2": "Знайдіть і ввімкніть «Сповіщення на екрані блокування».",
"permission_manufacturer_Text_xiaomi_tip2": "Увімкніть «Показ спливаючих вікон під час роботи у фоні» (та «Показ на екрані блокування»), щоб вхідні дзвінки зʼявлялися поверх екрана блокування.",
"@permission_manufacturer_Text_xiaomi_tip2": {},
"permission_Text_description": "Для забезпечення найкращого досвіду користувача застосунок потребує наступні дозволи: мікрофон для аудіодзвінків, камера для відеодзвінків та доступ до контактів для спрощення їх використання в застосунку.\n\nДозволи можуть бути змінені у майбутньому.",
"@permission_Text_description": {},
Expand Down
Loading