diff --git a/lib/app.dart b/lib/app.dart index 7c3acae6d..49e742f2f 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -43,12 +43,8 @@ class EnteApp extends StatefulWidget { } class _EnteAppState extends State with WidgetsBindingObserver { - static const initialInteractionTimeout = Duration(seconds: 10); - static const defaultInteractionTimeout = Duration(seconds: 5); - final _logger = Logger("EnteAppState"); late Locale locale; - late Timer _userInteractionTimer; @override void initState() { @@ -57,7 +53,6 @@ class _EnteAppState extends State with WidgetsBindingObserver { locale = widget.locale; setupIntentAction(); WidgetsBinding.instance.addObserver(this); - _setupInteractionTimer(timeout: initialInteractionTimeout); } setLocale(Locale newLocale) { @@ -76,30 +71,12 @@ class _EnteAppState extends State with WidgetsBindingObserver { } } - void _resetTimer() { - _userInteractionTimer.cancel(); - _setupInteractionTimer(); - } - - void _setupInteractionTimer({Duration timeout = defaultInteractionTimeout}) { - if (Platform.isAndroid || kDebugMode) { - _userInteractionTimer = Timer(timeout, () { - debugPrint("user is not interacting with the app"); - MachineLearningController.instance.onUserInteractionEvent(false); - }); - } else { - MachineLearningController.instance.onUserInteractionEvent(false); - } - } - @override Widget build(BuildContext context) { if (Platform.isAndroid || kDebugMode) { return Listener( onPointerDown: (event) { - MachineLearningController.instance.onUserInteractionEvent(true); - debugPrint("user is interacting with the app"); - _resetTimer(); + MachineLearningController.instance.onUserInteraction(); }, child: AdaptiveTheme( light: lightThemeData, @@ -149,7 +126,6 @@ class _EnteAppState extends State with WidgetsBindingObserver { @override void dispose() { WidgetsBinding.instance.removeObserver(this); - _userInteractionTimer.cancel(); super.dispose(); } diff --git a/lib/services/machine_learning/machine_learning_controller.dart b/lib/services/machine_learning/machine_learning_controller.dart index 79120e9e4..ce2dd892e 100644 --- a/lib/services/machine_learning/machine_learning_controller.dart +++ b/lib/services/machine_learning/machine_learning_controller.dart @@ -1,7 +1,7 @@ +import "dart:async"; import "dart:io"; import "package:battery_info/battery_info_plugin.dart"; -import "package:battery_info/enums/charging_status.dart"; import "package:battery_info/model/android_battery_info.dart"; import "package:logging/logging.dart"; import "package:photos/core/event_bus.dart"; @@ -17,35 +17,70 @@ class MachineLearningController { static const kMaximumTemperature = 36; // 36 degree celsius static const kMinimumBatteryLevel = 20; // 20% + static const kInitialInteractionTimeout = Duration(seconds: 10); + static const kDefaultInteractionTimeout = Duration(seconds: 5); + static const kUnhealthyStates = ["over_heat", "over_voltage", "dead"]; + + bool _isDeviceHealthy = true; + bool _isUserInteracting = true; + bool _isRunningML = false; + late Timer _userInteractionTimer; void init() { if (Platform.isAndroid) { + _startInteractionTimer(timeout: kInitialInteractionTimeout); BatteryInfoPlugin() .androidBatteryInfoStream .listen((AndroidBatteryInfo? batteryInfo) { - _logger.info("Battery info: ${batteryInfo!.toJson()}"); - if (_shouldRunMachineLearning(batteryInfo)) { - Bus.instance.fire(MachineLearningControlEvent(true)); - } else { - Bus.instance.fire(MachineLearningControlEvent(false)); - } + _onBatteryStateUpdate(batteryInfo); }); + } else { + // Always run Machine Learning on iOS + Bus.instance.fire(MachineLearningControlEvent(true)); } } - void onUserInteractionEvent(bool isUserInteracting) { - Bus.instance.fire(MachineLearningControlEvent(!isUserInteracting)); + void onUserInteraction() { + _logger.info("User is interacting with the app"); + _isUserInteracting = true; + _fireControlEvent(); + _resetTimer(); } - bool _shouldRunMachineLearning(AndroidBatteryInfo info) { - if (info.chargingStatus == ChargingStatus.Charging || - info.chargingStatus == ChargingStatus.Full) { - return _isAcceptableTemperature( - info.temperature ?? kMaximumTemperature, + void _fireControlEvent() { + final shouldRunML = _isDeviceHealthy && !_isUserInteracting; + if (shouldRunML != _isRunningML) { + _isRunningML = shouldRunML; + _logger.info( + "Firing event with device health: $_isDeviceHealthy and user interaction: $_isUserInteracting", ); + Bus.instance.fire(MachineLearningControlEvent(shouldRunML)); } + } + + void _startInteractionTimer({Duration timeout = kDefaultInteractionTimeout}) { + _userInteractionTimer = Timer(timeout, () { + _logger.info("User is not interacting with the app"); + _isUserInteracting = false; + _fireControlEvent(); + }); + } + + void _resetTimer() { + _userInteractionTimer.cancel(); + _startInteractionTimer(); + } + + void _onBatteryStateUpdate(AndroidBatteryInfo? batteryInfo) { + _logger.info("Battery info: ${batteryInfo!.toJson()}"); + _isDeviceHealthy = _computeIsDeviceHealthy(batteryInfo); + _fireControlEvent(); + } + + bool _computeIsDeviceHealthy(AndroidBatteryInfo info) { return _hasSufficientBattery(info.batteryLevel ?? kMinimumBatteryLevel) && - _isAcceptableTemperature(info.temperature ?? kMaximumTemperature); + _isAcceptableTemperature(info.temperature ?? kMaximumTemperature) && + _isBatteryHealthy(info.health ?? ""); } bool _hasSufficientBattery(int batteryLevel) { @@ -55,4 +90,8 @@ class MachineLearningController { bool _isAcceptableTemperature(int temperature) { return temperature <= kMaximumTemperature; } + + bool _isBatteryHealthy(String health) { + return !kUnhealthyStates.contains(health); + } }