[mob][photos] Fix wakelock bugs (#5691)

## Description

#### New `EnteWakelockService` singleton that wraps wakelock_plus APIs
- Persist enable/disable (across sessions) state in `SharedPreferences` 
- Re apply wakelock on app init based on stored state
- Makes sure the wakelock setting across sessions if set is respected
when wakelock is updated for other non-across-session purposes.


### Bugs fixed:
- App not staying awake after disabling auto lock in back up settings
when killed and reopened.
- App not staying awake when video is playing (only on
native_video_player)

## Tests

Tested all cases.
This commit is contained in:
Ashil
2025-04-22 14:02:42 +05:30
committed by GitHub
8 changed files with 103 additions and 65 deletions

View File

@@ -33,7 +33,6 @@ import 'package:photos/services/sync/sync_service.dart';
import 'package:photos/utils/file_uploader.dart';
import "package:photos/utils/lock_screen_settings.dart";
import 'package:photos/utils/validator_util.dart';
import "package:photos/utils/wakelock_util.dart";
import 'package:shared_preferences/shared_preferences.dart';
import "package:tuple/tuple.dart";
import 'package:uuid/uuid.dart';
@@ -53,10 +52,6 @@ class Configuration {
static const keyKey = "key";
static const keyShouldBackupOverMobileData = "should_backup_over_mobile_data";
static const keyShouldBackupVideos = "should_backup_videos";
// keyShouldKeepDeviceAwake is used to determine whether the device screen
// should be kept on while the app is in foreground.
static const keyShouldKeepDeviceAwake = "should_keep_device_awake";
static const keyShowSystemLockScreen = "should_show_lock_screen";
static const keyHasSelectedAnyBackupFolder =
"has_selected_any_folder_for_backup";
@@ -578,16 +573,6 @@ class Configuration {
}
}
bool shouldKeepDeviceAwake() {
final keepAwake = _preferences.get(keyShouldKeepDeviceAwake);
return keepAwake == null ? false : keepAwake as bool;
}
Future<void> setShouldKeepDeviceAwake(bool value) async {
await _preferences.setBool(keyShouldKeepDeviceAwake, value);
await EnteWakeLock.toggle(enable: value);
}
Future<void> setShouldBackupVideos(bool value) async {
await _preferences.setBool(keyShouldBackupVideos, value);
if (value) {

View File

@@ -44,6 +44,7 @@ import 'package:photos/services/search_service.dart';
import 'package:photos/services/sync/local_sync_service.dart';
import 'package:photos/services/sync/remote_sync_service.dart';
import "package:photos/services/sync/sync_service.dart";
import "package:photos/services/wake_lock_service.dart";
import 'package:photos/ui/tools/app_lock.dart';
import 'package:photos/ui/tools/lock_screen.dart';
import "package:photos/utils/email_util.dart";
@@ -276,6 +277,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
MLDataDB.instance,
preferences,
);
EnteWakeLockService.instance.init(preferences);
logLocalSettings();
initComplete = true;
_logger.info("Initialization done $tlog");

View File

@@ -0,0 +1,51 @@
import "package:shared_preferences/shared_preferences.dart";
import "package:wakelock_plus/wakelock_plus.dart";
enum WakeLockFor {
videoPlayback,
fasterBackupsOniOSByKeepingScreenAwake,
machineLearningSettingsScreen,
handlingMediaKitEdgeCase,
}
/// Use this wrapper to use wakelock. This class makes sure that the wakelock
/// setting across sessions if set is respected when wakelock is updated for
/// other non across session purposes.
/// Only place where this wrapper is not used for accessing wakelock APIs is
/// in media_kit package.
class EnteWakeLockService {
static const String kKeepAppAwakeAcrossSessions =
"keepAppAwakeAcrossSessions";
EnteWakeLockService._privateConstructor();
static final EnteWakeLockService instance =
EnteWakeLockService._privateConstructor();
late SharedPreferences _prefs;
void init(SharedPreferences prefs) {
_prefs = prefs;
if (_prefs.getBool(kKeepAppAwakeAcrossSessions) ?? false) {
WakelockPlus.enable();
}
}
void updateWakeLock({
required bool enable,
required WakeLockFor wakeLockFor,
}) {
if (wakeLockFor == WakeLockFor.fasterBackupsOniOSByKeepingScreenAwake ||
wakeLockFor == WakeLockFor.handlingMediaKitEdgeCase) {
WakelockPlus.toggle(enable: enable);
_prefs.setBool(kKeepAppAwakeAcrossSessions, enable);
} else {
if (!shouldKeepAppAwakeAcrossSessions) {
WakelockPlus.toggle(enable: enable);
}
}
}
bool get shouldKeepAppAwakeAcrossSessions =>
_prefs.getBool(kKeepAppAwakeAcrossSessions) ?? false;
}

View File

@@ -1,9 +1,10 @@
import 'dart:io';
import "dart:io";
import 'package:flutter/material.dart';
import 'package:photos/core/configuration.dart';
import "package:photos/generated/l10n.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/wake_lock_service.dart";
import 'package:photos/theme/ente_theme.dart';
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import 'package:photos/ui/components/captioned_text_widget.dart';
@@ -134,13 +135,15 @@ class BackupSettingsScreen extends StatelessWidget {
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: ToggleSwitchWidget(
value: () => Configuration.instance
.shouldKeepDeviceAwake(),
onChanged: () {
return Configuration.instance
.setShouldKeepDeviceAwake(
!Configuration.instance
.shouldKeepDeviceAwake(),
value: () => EnteWakeLockService.instance
.shouldKeepAppAwakeAcrossSessions,
onChanged: () async {
EnteWakeLockService.instance
.updateWakeLock(
enable: !EnteWakeLockService.instance
.shouldKeepAppAwakeAcrossSessions,
wakeLockFor: WakeLockFor
.fasterBackupsOniOSByKeepingScreenAwake,
);
},
),

View File

@@ -12,6 +12,7 @@ import "package:photos/services/machine_learning/semantic_search/clip/clip_image
import "package:photos/services/machine_learning/semantic_search/clip/clip_text_encoder.dart";
import 'package:photos/services/machine_learning/semantic_search/semantic_search_service.dart';
import "package:photos/services/remote_assets_service.dart";
import "package:photos/services/wake_lock_service.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/common/loading_widget.dart";
import "package:photos/ui/common/web_page.dart";
@@ -31,7 +32,6 @@ import "package:photos/ui/settings/ml/enable_ml_consent.dart";
import "package:photos/ui/settings/ml/ml_user_dev_screen.dart";
import "package:photos/utils/ml_util.dart";
import "package:photos/utils/network_util.dart";
import "package:photos/utils/wakelock_util.dart";
class MachineLearningSettingsPage extends StatefulWidget {
const MachineLearningSettingsPage({super.key});
@@ -43,7 +43,6 @@ class MachineLearningSettingsPage extends StatefulWidget {
class _MachineLearningSettingsPageState
extends State<MachineLearningSettingsPage> {
final EnteWakeLock _wakeLock = EnteWakeLock();
Timer? _timer;
int _titleTapCount = 0;
Timer? _advancedOptionsTimer;
@@ -51,7 +50,10 @@ class _MachineLearningSettingsPageState
@override
void initState() {
super.initState();
_wakeLock.enable();
EnteWakeLockService.instance.updateWakeLock(
enable: true,
wakeLockFor: WakeLockFor.machineLearningSettingsScreen,
);
machineLearningController.forceOverrideML(turnOn: true);
if (!MLIndexingIsolate.instance.areModelsDownloaded) {
_timer = Timer.periodic(const Duration(seconds: 10), (timer) {
@@ -68,7 +70,10 @@ class _MachineLearningSettingsPageState
@override
void dispose() {
super.dispose();
_wakeLock.disable();
EnteWakeLockService.instance.updateWakeLock(
enable: false,
wakeLockFor: WakeLockFor.machineLearningSettingsScreen,
);
machineLearningController.forceOverrideML(turnOn: false);
_timer?.cancel();
_advancedOptionsTimer?.cancel();

View File

@@ -16,6 +16,7 @@ import "package:photos/models/file/extensions/file_props.dart";
import "package:photos/models/file/file.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/files_service.dart";
import "package:photos/services/wake_lock_service.dart";
import "package:photos/theme/colors.dart";
import "package:photos/ui/actions/file/file_actions.dart";
import "package:photos/ui/common/loading_widget.dart";
@@ -161,6 +162,12 @@ class _VideoWidgetMediaKitNewState extends State<VideoWidgetMediaKitNew>
WidgetsBinding.instance.removeObserver(this);
player.dispose();
_captionUpdatedSubscription.cancel();
if (EnteWakeLockService.instance.shouldKeepAppAwakeAcrossSessions) {
EnteWakeLockService.instance.updateWakeLock(
enable: true,
wakeLockFor: WakeLockFor.handlingMediaKitEdgeCase,
);
}
super.dispose();
}

View File

@@ -20,6 +20,7 @@ import "package:photos/models/file/file.dart";
import "package:photos/models/preview/playlist_data.dart";
import "package:photos/service_locator.dart";
import "package:photos/services/files_service.dart";
import "package:photos/services/wake_lock_service.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/actions/file/file_actions.dart";
@@ -126,6 +127,9 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
}
}
});
EnteWakeLockService.instance
.updateWakeLock(enable: true, wakeLockFor: WakeLockFor.videoPlayback);
}
Future<void> setVideoSource() async {
@@ -220,6 +224,8 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
_debouncer.cancelDebounceTimer();
_elTooltipController.dispose();
_captionUpdatedSubscription.cancel();
EnteWakeLockService.instance
.updateWakeLock(enable: false, wakeLockFor: WakeLockFor.videoPlayback);
super.dispose();
}
@@ -478,6 +484,8 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
widget.playbackCallback!(false);
}
}
_handleWakeLockOnPlaybackChanges();
}
void _onError(String errorMessage) {
@@ -544,6 +552,21 @@ class _VideoWidgetNativeState extends State<VideoWidgetNative>
}
}
void _handleWakeLockOnPlaybackChanges() {
final playbackStatus = _controller?.playbackStatus;
if (playbackStatus == PlaybackStatus.playing) {
EnteWakeLockService.instance.updateWakeLock(
enable: true,
wakeLockFor: WakeLockFor.videoPlayback,
);
} else {
EnteWakeLockService.instance.updateWakeLock(
enable: false,
wakeLockFor: WakeLockFor.videoPlayback,
);
}
}
Widget _getLoadingWidget() {
return Stack(
key: const ValueKey("video_loading"),

View File

@@ -1,38 +0,0 @@
import "dart:async" show unawaited;
import "package:wakelock_plus/wakelock_plus.dart";
class EnteWakeLock {
bool _wakeLockEnabledHere = false;
void enable() {
WakelockPlus.enabled.then((value) {
if (value == false) {
WakelockPlus.enable();
//wakeLockEnabledHere will not be set to true if wakeLock is already enabled from settings on iOS.
//We shouldn't disable when video is not playing if it was enabled manually by the user from ente settings by user.
_wakeLockEnabledHere = true;
}
});
}
void disable() {
if (_wakeLockEnabledHere) {
WakelockPlus.disable();
}
}
void dispose() {
if (_wakeLockEnabledHere) {
unawaited(
WakelockPlus.enabled.then((isEnabled) {
isEnabled ? WakelockPlus.disable() : null;
}),
);
}
}
static Future<void> toggle({required bool enable}) async {
await WakelockPlus.toggle(enable: enable);
}
}